craftcms / element-api

Create a JSON API/Feed for your elements in Craft.
MIT License
498 stars 57 forks source link

How to access custom behaviour method in Element Api #135

Closed chandamotiyani closed 4 years ago

chandamotiyani commented 4 years ago

Description

I am performing a search feature on the site for which I have used Element API. We do limit the access of products and events by user permissions. I have attached element-api.php and MembershipPermissionBehavior.php please let me know that how can I check for the member permission and return only those records in search results.

Additional information: I can access as below in twig file:

craft.events().search(searchQuery).withPermission().orderBy('price desc')

Q:1: How to use withPermission() in element API? Q:2: Is there extended documentation for using search in twig file? I.e: I have different types of events and products and I want to query the search for each of them separately instead querying them whole at a time

there are group types in control panel of craft for events as tours, tastings, events - same are named as groupHandle now how to do search for each this type individually? Same question I have for the products - it has type, wine|giftpacks|merchandise so how to perform search for the products individually for each of its type? Have attached screen shots for product types and event groups, hope that helps.

I know its bit complex but @brandonkelly you rock always 👍 Hopping solution for this :)

element-api.php

<?php
use craft\elements\Entry;
use craft\commerce\elements\Product;
use modules\Events\Elements\Event;
use modules\Memberships\MembershipPermissionBehavior;
return [
    'endpoints' => [
        'search.entries' => function() {
            // settings
            //$section_handle = 'pages';

            $phrase = Craft::$app->request->getParam('phrase');
            $limit = Craft::$app->request->getParam('limit');
            $state = Craft::$app->request->getParam('state');
            $criteria = [
                'limit' => $limit,
                'search' => 'title:'.$phrase,

            ];

            return [
                'elementType' => Entry::class,
                'criteria' => $criteria,
                'paginate' => false,
                'transformer' => function(Entry $entry) use ($state) {
                    return [
                        'title' => $entry->title,
                        'url' => $entry->url,
                        'sUrl' => "/search?state=".$state."&search=".$entry->title,
                    ];
                },

            ];
        },
        'search.products' => function() {

            // settings
            $phrase = Craft::$app->request->getParam('phrase');
            $limit = Craft::$app->request->getParam('limit');
            $state = Craft::$app->request->getParam('state');
            $criteria = [
                'search' => 'title:'.$phrase,
                'limit' => $limit,
                'orderBy' => 'defaultPrice DESC'
            ];

            return [
                'elementType' => Product::class,
                'criteria' => $criteria,
                'paginate' => false,
                'transformer' => function(Product $entry) use ($state) {

                    return [
                        'title' => $entry->title,
                        'url' => $entry->url,
                        'sUrl' => "/search?state=".$state."&search=".$entry->title,
                    ];
                },
            ];
        },

        'search.events' => function() {

            // settings
            $phrase = Craft::$app->request->getParam('phrase');
            $limit = Craft::$app->request->getParam('limit');
            $state = Craft::$app->request->getParam('state');
            $criteria = [
                'search' => 'title:'.$phrase,
                'limit' => $limit,
                'orderBy' => 'price DESC'
            ];

            return [
                'elementType' => Event::class,
                'criteria' => $criteria,
                'paginate' => false,
                'transformer' => function(Event $entry) use ($state) {

                    return [
                        'title' => $entry->title,
                        'url' => $entry->url,
                        'sUrl' => "/search?state=".$state."&search=".$entry->title,
                    ];
                },
            ];
        },
    ]
];

MembershipPermissionBehavior.php

class MembershipPermissionBehavior extends Behavior
{
    function withPermission(){

        $user = \Craft::$app->user;
        if($user->getId()){
            $userPermissions = \Craft::$app->getUserPermissions()->getPermissionsByUserId($user->getId());
        }
        else{
            $userPermissions = [];
        }
        //if this element has the permission field, apply the filter for this user.
        $userPermissionCategories = [];
        $wheres = [];
        foreach ( $userPermissions as $userPermission ) {
            $permName = str_replace( 'view-', '', $userPermission );
            $wheres[] = ["LIKE", "field_memberPermission", "$permName"];
        }
        if($user->getIsGuest()){
            $wheres[] = ["LIKE", "field_memberPermission", "guest"];
        }
        $wheres = array_merge(["OR"], $wheres);
        $wheres[] = ['=', 'field_memberPermissionVisible', true];
        $this->owner->andWhere(['OR', $wheres]);

        return $this->owner;
    }
}
Screen Shot 2020-08-06 at 6 48 49 pm Screen Shot 2020-08-06 at 6 52 32 pm
brandonkelly commented 4 years ago

Q:1: How to use withPermission() in element API?

You can only set element query properties viacraft\elementapi\resources\ElementResource::$criteria. If you want to be able to call your withPermissions() method, you would need to extend craft\elementapi\resources\ElementResource, and override its getElementQuery() method.

Q:2: Is there extended documentation for using search in twig file? I.e: I have different types of events and products and I want to query the search for each of them separately instead querying them whole at a time

There is https://craftcms.com/docs/3.x/searching.html, which has a Templating section.

chandamotiyani commented 4 years ago

https://craftcms.com/docs/3.x/searching.html I already have gone through that documentation but it is not extended one and not resolving what I'm looking for! My question is that I need to query the search for each type of event and product separately, so I need a proper method for that!

Where do I specify the group of the event here in the following query?

craft.events().search(searchQuery).withPermission().orderBy('price desc')

And where do I specify the type of product in following

craft.products().search(searchQuery).withPermission().orderBy('price desc')

I tried to query for one of the group i.e: craft.events().tastings().search(searchQuery).withPermission().orderBy('price desc') craft.events('tastings').search(searchQuery).withPermission().orderBy('price desc')
craft.events().groupHandle('tastings').search(searchQuery).withPermission().orderBy('price desc')

But it is not working out, so maybe there is some other way of doing it.

Also one more thing - In search results with craft.events().search(searchQuery).withPermission().orderBy('price desc') this query gives result for the Tours and Events but not for Tastings Type, I'm expecting to get the results for all types whichever is matching with the searched term with this query but except events comes under Tastings group, everything else is being returned.

As we could use section for entries() to query against each section individually in the same way I need to know for the custom module which is Events and Products which is a module of Commerce Plugin.

brandonkelly commented 4 years ago

My question is that I need to query the search for each type of event and product separately, so I need a proper method for that!

You would likely need to do that with multiple queries, one per event type / product type.

Where do I specify the group of the event here in the following query?

craft.events().search(searchQuery).withPermission().orderBy('price desc')

Anywhere after events() and before you end up calling .all().

But it is not working out, so maybe there is some other way of doing it.

You can try calling getRawSql() on it once you’ve set all of your parameters, and paste the SQL into a MySQL client, and maybe that will shed some light on what’s wrong with it.

{% set query = craft.events()
  .groupHandle('tastings')
  .search(searchQuery)
  .withPermission()
  .orderBy('price desc') %}

{{ query.getRawSql() }}
chandamotiyani commented 4 years ago

@brandonkelly where to create that file in which I can extend craft\elementapi\resources\ElementResource?

brandonkelly commented 4 years ago

You could define that class in the same config/element-api.php file, near the top.

chandamotiyani commented 4 years ago

@brandonkelly after defining how to initialize the class that the element API uses that instead of the default one??

use craft\elements\Entry;
use craft\commerce\elements\Product;
use modules\Events\Elements\Event;
use modules\Memberships\MembershipPermissionBehavior;
use craft\base\ElementInterface;
use craft\elementapi\resources\ElementResource;
use craft\elements\db\ElementQueryInterface;
class CriteriaResource extends ElementResource {
    /**
     * @var string The element type class name
     */
    public $elementType;
    /**
     * @var array The element criteria params that should be used to filter the matching elements
     */
    public $criteria = [];
    protected function getElementQuery(): ElementQueryInterface
    {
        /** @var string|ElementInterface $elementType */
        $elementType = $this->elementType;
        $query = $elementType::find()->withPermission();
        Craft::configure($query, $this->criteria);
        return $query;
    }
}
return [
    'endpoints' => [
        'search.entries' => function() {
            // settings
            //$section_handle = 'pages';
            $phrase = Craft::$app->request->getParam('phrase');
            $limit = Craft::$app->request->getParam('limit');
            $state = Craft::$app->request->getParam('state');
            $criteria = [
                'limit' => $limit,
                'search' => 'title:'.$phrase,
            ];
            return [
                'elementType' => Entry::class,
                'criteria' => $criteria,
                'paginate' => false,
                'transformer' => function(Entry $entry) use ($state) {
                    return [
                        'title' => $entry->title,
                        'url' => $entry->url,
                        'sUrl' => "/search?state=".$state."&search=".$entry->title,
                    ];
                },
            ];
        },
        'search.products' => function() {
            // settings
            $phrase = Craft::$app->request->getParam('phrase');
            $limit = Craft::$app->request->getParam('limit');
            $state = Craft::$app->request->getParam('state');
            $criteria = [
                'search' => 'title:'.$phrase,
                'limit' => $limit,
                'orderBy' => 'defaultPrice DESC'
            ];
            return [
                'elementType' => Product::class,
                'criteria' => $criteria,
                'paginate' => false,
                'transformer' => function(Product $entry) use ($state) {

                    return [
                        'title' => $entry->title,
                        'url' => $entry->url,
                        'sUrl' => "/search?state=".$state."&search=".$entry->title,
                    ];
                },
            ];
        },
        'search.events' => function() {
            // settings
            $phrase = Craft::$app->request->getParam('phrase');
            $limit = Craft::$app->request->getParam('limit');
            $state = Craft::$app->request->getParam('state');
            $criteria = [
                'search' => 'title:'.$phrase,
                'limit' => $limit,
                'orderBy' => 'price DESC'
            ];
            return [
                'elementType' => Event::class,
                'criteria' => $criteria,
                'paginate' => false,
                'transformer' => function(Event $entry) use ($state) {
                    return [
                        'title' => $entry->title,
                        'url' => $entry->url,
                        'sUrl' => "/search?state=".$state."&search=".$entry->title,
                    ];
                },
            ];
        },
    ]
];
brandonkelly commented 4 years ago

No, before it is referenced.

chandamotiyani commented 4 years ago

@brandonkelly I tried to do like the following: How to set elementType and criteria because it is returning an error

{"error":{"code":0,"message":"Setting unknown property: CriteriaResource::search"}}
{"error":{"code":0,"message":Endpoint has an invalid elementType}}
class CriteriaResource extends ElementResource
{
    /**
     * @var string The element type class name
     */
    public $elementType;
    /**
     * @var array The element criteria params that should be used to filter the matching elements
     */
    public $criteria = [];
    **public function __construct(array $config = [], $elementType)
    {
        parent::__construct($config);
        $this->elementType = $elementType;
    }**
    protected function getElementQuery(): ElementQueryInterface
    {
        /** @var string|ElementInterface $elementType */
        $elementType = $this->elementType;
        $query = $elementType::find()->withPermission();
        Craft::configure($query, $this->criteria);
        return $query;
    }
}
'search.products' => function () {
            // settings
            $phrase = Craft::$app->request->getParam('phrase');
            $limit = Craft::$app->request->getParam('limit');
            $state = Craft::$app->request->getParam('state');
            $criteria = [
                'search' => 'title:' . $phrase,
                'limit' => $limit,
                'orderBy' => 'defaultPrice DESC'
            ];
            return [
                'elementType' => Product::class,
                **'criteria' => new CriteriaResource($criteria, Product::class),**
                'paginate' => false,
                'transformer' => function (Product $entry) use ($state) {
                    return [
                        'title' => $entry->title,
                        'url' => $entry->url,
                        'sUrl' => "/search?state=" . $state . "&search=" . $entry->title,
                    ];
                },
            ];
        },
brandonkelly commented 4 years ago

Assuming you’ve properly registered your MembershipPermissionBehavior class to the element query, all you should have to do is:

use craft\base\ElementQueryInterface;
use craft\elementapi\resources\ElementResource;

class MyElementResource extends ElementResource
{
    protected function getElementQuery(): ElementQueryInterface
    {
        $query = parent::getElementQuery();
        $query-> withPermission();
        return $query;
    }
}

// ...

return [
    'endpoints' => [
        'my-endpoint' => function() {
            // ...
            return [
                'class' => MyElementResource::class,
                // ...
            ];
        },
    ],
    // ...
];
chandamotiyani commented 4 years ago

Thank you very much @brandonkelly It is done finally :smile::confetti_ball::tada::clap: