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
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?
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;
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.
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();
}
}
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'];
}
}