Additional field in articles from custom template - sulu

I have a problem. In elastic search, we have data returned as follows:
"uuid": "8f39a450-64c2-407c-9836-32ed3b4db1ce",
"locale": "de",
"title": "Blah nedvsger",
"route_path": "/articles/blah-nedvsger",
"type": "news",
"type_translation": "sulu_article.news",
"structure_type": "news",
"changer_full_name": "Adam Ministrator",
"creator_full_name": "Adam Ministrator",
"changed": "2021-07-06T09:52:42+0000",
"created": "2021-07-06T09:49:50+0000",
"excerpt": {},
"seo": {},
"authored": "2021-07-06T09:49:50+0000",
"author_full_name": "Adam Ministrator",
"teaser_description": "",
"published": "2021-07-06T09:49:51+0000",
"published_state": true,
"localization_state": {},
"author_id": 1,
"creator_contact_id": 1,
"changer_contact_id": 1,
"pages": [],
"content_data": "{\"title\":\"Blah nedvsger\",\"routePath\":\"\\/articles\\/blah-nedvsger\",\"hero_image\":{\"id\":null},\"overline\":\"asfdafasdf\",\"headline\":\"adfgasdf\",\"auth\":[\"c1\"],\"introduction\":\"asdfd\",\"blocks\":[],\"related\":{\"audienceTargeting\":null,\"categories\":null,\"categoryOperator\":\"or\",\"dataSource\":null,\"includeSubFolders\":null,\"limitResult\":null,\"presentAs\":null,\"sortBy\":\"published\",\"sortMethod\":\"asc\",\"tagOperator\":\"or\",\"types\":[\"country\",\"default\",\"fascination\",\"news\"],\"tags\":null}}",
"main_webspace": "magazine",
"additional_webspaces": [
"olympics2021"
],
"content_fields": []
Now, I want to show value of field "headline" from content_data, and we've done this:
ADDDED articles.xml column as:
<property
name="contentData"
visibility="always"
translation="Content data"
>
<transformer type="json.headline" />
</property>
Created Json.js, with code (npm run build, and all):
import React from 'react';
import type {Node} from 'react';
import type {FieldTransformer} from 'sulu-admin-bundle/types';
export default class JsonHeadlineTransformer implements FieldTransformer {
transform(value: *): Node
{
console.log(value);
let json = {};
try
{
json = JSON.parse(value);
}
catch (error)
{
return;
}
if('headline' in json)
{
return json.headline;
}
}
}
And it returns empty string. In console it returns undefined. Where are we wrong?

I did it in simplest way, I created in src/Admin/Controller file ArticleController and inherited Bundle one, and just changed getFieldDescriptors method, like:
<?php
namespace App\Controller\Admin;
use Sulu\Bundle\ArticleBundle\Controller\ArticleController as SuluArticleController;
use FOS\RestBundle\View\ViewHandlerInterface;
use ONGR\ElasticsearchBundle\Service\Manager;
use Sulu\Bundle\ArticleBundle\Document\Index\DocumentFactoryInterface;
use Sulu\Bundle\ArticleBundle\ListBuilder\ElasticSearchFieldDescriptor;
use Sulu\Component\Content\Mapper\ContentMapperInterface;
use Sulu\Component\DocumentManager\DocumentManagerInterface;
use Sulu\Component\DocumentManager\MetadataFactoryInterface;
use Sulu\Component\Hash\RequestHashCheckerInterface;
use Sulu\Component\Rest\ListBuilder\FieldDescriptorInterface;
use Sulu\Component\Rest\ListBuilder\ListRestHelperInterface;
use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Provides API for articles.
*/
class ArticleController extends SuluArticleController
// AbstractRestController implements ClassResourceInterface, SecuredControllerInterface
{
/**
* #var bool
*/
private $displayTabAll;
public function __construct(
ViewHandlerInterface $viewHandler,
DocumentManagerInterface $documentManager,
ContentMapperInterface $contentMapper,
MetadataFactoryInterface $metadataFactory,
ListRestHelperInterface $restHelper,
Manager $manager,
DocumentFactoryInterface $documentFactory,
FormFactoryInterface $formFactory,
RequestHashCheckerInterface $requestHashChecker,
SecurityCheckerInterface $securityChecker,
bool $displayTabAll = true,
?TokenStorageInterface $tokenStorage = null
)
{
parent::__construct(
$viewHandler,
$documentManager,
$contentMapper,
$metadataFactory,
$restHelper,
$manager,
$documentFactory,
$formFactory,
$requestHashChecker,
$securityChecker,
$displayTabAll,
$tokenStorage
);
}
/**
* Create field-descriptor array.
*
* #return ElasticSearchFieldDescriptor[]
*/
protected function getFieldDescriptors(): array
{
return [
'uuid' => ElasticSearchFieldDescriptor::create('id', 'public.id')
->setVisibility(FieldDescriptorInterface::VISIBILITY_NO)
->build(),
'typeTranslation' => ElasticSearchFieldDescriptor::create('typeTranslation', 'sulu_article.list.type')
->setSortField('typeTranslation.raw')
->setVisibility(
$this->displayTabAll ?
FieldDescriptorInterface::VISIBILITY_YES :
FieldDescriptorInterface::VISIBILITY_NEVER
)
->build(),
'title' => ElasticSearchFieldDescriptor::create('title', 'public.title')
->setSortField('title.raw')
->build(),
'creatorFullName' => ElasticSearchFieldDescriptor::create('creatorFullName', 'sulu_article.list.creator')
->setSortField('creatorFullName.raw')
->build(),
'changerFullName' => ElasticSearchFieldDescriptor::create('changerFullName', 'sulu_article.list.changer')
->setSortField('changerFullName.raw')
->build(),
'authorFullName' => ElasticSearchFieldDescriptor::create('authorFullName', 'sulu_article.author')
->setSortField('authorFullName.raw')
->build(),
'created' => ElasticSearchFieldDescriptor::create('created', 'public.created')
->setSortField('created')
->setType('datetime')
->setVisibility(FieldDescriptorInterface::VISIBILITY_NO)
->build(),
'changed' => ElasticSearchFieldDescriptor::create('changed', 'public.changed')
->setSortField('changed')
->setType('datetime')
->setVisibility(FieldDescriptorInterface::VISIBILITY_NO)
->build(),
'authored' => ElasticSearchFieldDescriptor::create('authored', 'sulu_article.authored')
->setSortField('authored')
->setType('datetime')
->build(),
'localizationState' => ElasticSearchFieldDescriptor::create('localizationState')
->setVisibility(FieldDescriptorInterface::VISIBILITY_NO)
->build(),
'published' => ElasticSearchFieldDescriptor::create('published')
->setVisibility(FieldDescriptorInterface::VISIBILITY_NO)
->build(),
'publishedState' => ElasticSearchFieldDescriptor::create('publishedState')
->setVisibility(FieldDescriptorInterface::VISIBILITY_NO)
->build(),
'routePath' => ElasticSearchFieldDescriptor::create('routePath')
->setVisibility(FieldDescriptorInterface::VISIBILITY_NO)
->build(),
'contentData' => ElasticSearchFieldDescriptor::create('contentData')
->setVisibility(FieldDescriptorInterface::VISIBILITY_YES)
->build(),
];
}
}

Related

Yii2: rest api model get data

I am using REST API in my project and everything works great. I describe a model using a model
<?php
namespace api\modules\v1\models;
use Carbon\Carbon;
use Yii;
class Comment extends \common\models\Comment
{
public function fields()
{
return [
'id',
'user' => function(Comment $model) {
return User::findOne($model->user_id);
},
'text',
'image' => function(Comment $model) {
return Yii::$app->params['link'].$model->image;
},
'created_at' => function(Comment $model) {
Carbon::setLocale(Yii::$app->language);
return Carbon::createFromTimeStamp(strtotime($model->created_at))->diffForHumans();
},
'children' => function(Comment $model) {
$comments = Comment::find()
->where(['comment_id' => $model->id]);
if (!$comments->exists()) {
return false;
}
return $comments->all();
},
'like',
'news_id',
'comment_id'
];
}
}
The data is returned in the specified format and that's great. But I need to send data to the controller using websockets. For example, when a new comment arrives, send it to all users.
$post = Yii::$app->request->post();
$image = UploadedFile::getInstanceByName('image');
$model = new \api\modules\v1\models\Comment([
'news_id' => $post['feed_id'],
'comment_id' => $post['comment_id'] ?? null,
'user_id' => Yii::$app->user->identity->id,
]);
$model->text = $model->findLinks($post['text']);
if ($image && !$image->error) {
if (!file_exists(Yii::$app->params['comment.pathAbsolute'])) {
if (!FileHelper::createDirectory(Yii::$app->params['comment.pathAbsolute'], 0777)) {
throw new \Exception('Помилка створення папки');
}
}
$serverName = Yii::$app->security->generateRandomString(16).'.'.$image->extension;
if ($image->saveAs(Yii::$app->params['comment.pathAbsolute'].$serverName)) {
$model->image = $serverName;
} else {
throw new \Exception($image->error);
}
}
if (!$model->save()) {
throw new \Exception($model->error());
}
Helper::ws(false, 'updateComment', ['feed_id' => $post['feed_id'], 'comment' => $model]);
And when I pass the $model, the data is passed as it is stored in the database. Is it possible to call a method or something so that the data is passed as I described in the model api?

AgGrid - How can i have radio button filter instead of checkbox?

I have a custom filter values such as:
filterParams: {
values: ['Admin', 'Proje Yöneticisi', 'Muhasebe'],
defaultToNothingSelected: true,
suppressSelectAll: true
},
However, I can choose multiple values like this. But I don't want to do that, I want to choose only one value instead of multiple choices.
Is there a way to convert this checkbox filter into a radio filter?
Thanks.
You can make a custom filter and there is a video on it: https://www.youtube.com/watch?v=yO3_nTyDv6o
Create a component like this, i am dynamically looking up the options to be displayed based on the extra column parameters supplied in the column def (e.g. thats where props.meta comes in)
import { Button, Radio, RadioGroup, Stack } from "#chakra-ui/react";
import { IFilterParams } from "ag-grid-community";
import React from "react";
import { IRegistryDataColumn } from "../../../../models/RegistryDataColumn";
interface IProps extends IFilterParams {
meta?: IRegistryDataColumn;
}
interface IOption {
value: string;
label: string;
}
export const FilterRadio = React.forwardRef((props: IProps, ref) => {
const [radioOptions, setRadioOptions] = React.useState<IOption[]>([]);
const [filterState, setFilterState] = React.useState<string>();
const handleClear = () => {
setFilterState(undefined);
};
// expose AG Grid Filter Lifecycle callbacks
React.useImperativeHandle(ref, () => {
return {
isFilterActive() {
return filterState !== undefined;
},
doesFilterPass(params) {
const isPass =
params.data[props.colDef.field as string] === filterState;
return isPass;
},
getModel() {},
setModel() {},
};
});
React.useEffect(() => {
props.filterChangedCallback();
}, [filterState]);
React.useEffect(() => {
const radioOptionsUpdate: IOption[] = [];
if (props.meta?.radio_options) {
Object.entries(props.meta.radio_options).forEach(([key, value]) => {
radioOptionsUpdate.push({ value: value.value, label: value.label });
});
}
setRadioOptions(radioOptionsUpdate);
}, [props.meta?.radio_options]);
return (
<Stack p={4} spacing={6} style={{ display: "inline-block" }}>
<Button size="sm" onClick={handleClear}>
Clear filter
</Button>
<RadioGroup onChange={setFilterState} value={filterState}>
<Stack spacing={4}>
{radioOptions.map((option) => (
<Radio key={option.value} value={option.value}>
{option.label}
</Radio>
))}
</Stack>
</RadioGroup>
</Stack>
);
});
And then include it in the column definition:
newCol.filter = FilterRadio;

How to order custom attribute in Datatable?

Say my User model's balance attribute from getBalanceAttribute() returns the sum of amount from user's Transaction model, how can this be orderable in the Datatable?
User.php
public function transactions()
{
return $this->hasMany(\App\Transaction::class);
}
public function getBalanceAttribute()
{
return $this->transactions()->sum('amount');
}
Transaction.php
public function user()
{
return $this->belongsTo(\App\User::class);
}
UserCrudController.php
...
public function setup()
{
...
$this->crud->addColumn(
[
'name' => "balance",
'label' => "Balance",
'type' => 'number',
// Here the column is clickable but is not actually sorted.
'orderable' => true,
],
...
}
Thank you in advance!
Unfortunately, Backpack cannot make a model_function column orderable, since model functions are called after the SQL has already gotten back.
Sorry.

How to use socialize(facebook) with laravel 5.1

I followed this guide
Laravel\Socialite\SocialiteServiceProvider in my config/app.php is ok, and aliases should also be ok : 'Socialite' => Laravel\Socialite\Facades\Socialite::class,
Here is my code :
config/services.php
'facebook' => [
'client_id' => 'xxxx',
'client_secret' => 'xxxxx',
'redirect' => 'http://localhost:8000/auth/fb',
]
Route
Route::get('auth/fb', 'FBController#redirectToProvider');
Route::get('auth/fb/callback', 'FBController#handleProviderCallback');
Controller
namespace App\Http\Controllers;
use Socialite;
use Illuminate\Routing\Controller;
class FBController extends Controller`
{
public function redirectToProvider()
{
return Socialite::driver('facebook')->redirect();
}
public function handleProviderCallback()
{
$user = Socialite::driver('facebook')->user();
}
}
but is error in
FatalErrorException in FBController.php line 14:
Class 'App\Http\Controllers\Socialite' not found
What can I do to fix this?
Replace use Socialite; with use Socialize; and all instances where Socialite replace it with Socialize
namespace App\Http\Controllers;
use Socialize;
use Illuminate\Routing\Controller;
class FBController extends Controller
{
public function redirectToProvider()
{
return Socialize::driver('facebook')->redirect();
}
public function handleProviderCallback()
{
$user = Socialize::driver('facebook')->user();
}
}

Subdocuments in MongoDB and Yii2

How should I declare the attributes public function of a class (model) that extends from ActiveRecord if I'm willing to use subdocuments?
Take for example this simple MongoDB structure:
_id: 'a34tfsert89w734y0tas9dgtuwn034t3',
name: 'Bob',
surnames: {
'first': 'Foo',
'second': 'Bar'
},
age: 27,
preferences: {
lang: 'en',
currency: 'EUR'
}
How should my attributes function look like?
public function attributes() {
return [
'_id',
'name',
'surnames',
'surnames.first', <--- like this?
.....
]
}
The MongoDb Extension for Yii 2 does not provide any special way to work with embedded documents (sub-documents). To do that you will need to first deal with custom validations. You could try the following approach: The general pattern is to first build a custom validator, say \common\validators\EmbedDocValidator.php
namespace common\validators;
use yii\validators\Validator;
class EmbedDocValidator extends Validator
{
public $scenario;
public $model;
/**
* Validates a single attribute.
* Child classes must implement this method to provide the actual validation logic.
*
* #param \yii\mongodb\ActiveRecord $object the data object to be validated
* #param string $attribute the name of the attribute to be validated.
*/
public function validateAttribute($object, $attribute)
{
$attr = $object->{$attribute};
if (is_array($attr)) {
$model = new $this->model;
if($this->scenario){
$model->scenario = $this->scenario;
}
$model->attributes = $attr;
if (!$model->validate()) {
foreach ($model->getErrors() as $errorAttr) {
foreach ($errorAttr as $value) {
$this->addError($object, $attribute, $value);
}
}
}
} else {
$this->addError($object, $attribute, 'should be an array');
}
}
}
and model for the embedded document \common\models\Preferences.php
namespace common\models;
use yii\base\Model;
class Preferences extends Model
{
/**
* #var string $lang
*/
public $lang;
/**
* #var string $currency
*/
public $currency;
public function rules()
{
return [
[['lang', 'currency'], 'required'],
];
}
}
And setup the validator in the top-level model
In common\models\User.php:
public function rules()
{
return [
[['preferences', 'name'], 'required'],
['preferences', 'common\validators\EmbedDocValidator', 'scenario' => 'user','model'=>'\common\models\Preferences'],
];
}
The general recommendation is avoiding use of embedded documents moving their attributes at the top level of the document. For example: instead of
{
name: 'Bob',
surnames: {
'first': 'Foo',
'second': 'Bar'
},
age: 27,
preferences: {
lang: 'en',
currency: 'EUR'
}
}
use following structure:
{
name: 'Bob',
surnames_first: 'Foo',
surnames_second: 'Bar'
age: 27,
preferences_lang: 'en',
preferences_currency: 'EUR'
}
which you can then declare as an ActiveRecord class by extending yii\mongodb\ActiveRecord and implement the collectionName and 'attributes' methods:
use yii\mongodb\ActiveRecord;
class User extends ActiveRecord
{
/**
* #return string the name of the index associated with this ActiveRecord class.
*/
public static function collectionName()
{
return 'user';
}
/**
* #return array list of attribute names.
*/
public function attributes()
{
return ['_id', 'name', 'surnames_first', 'surnames_second', 'age', 'preferences_lang', 'preferences_currency'];
}
}