Passed
Push — v1 ( 1a72e5...92dfb3 )
by Andrew
08:37 queued 03:57
created

src/services/Similar.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * Similar plugin for Craft CMS 3.x
4
 *
5
 * Similar for Craft lets you find elements, Entries, Categories, Commerce
6
 * Products, etc, that are similar, based on... other related elements.
7
 *
8
 * @link      https://nystudio107.com/
9
 * @copyright Copyright (c) 2018 nystudio107.com
10
 */
11
12
namespace nystudio107\similar\services;
13
14
use Craft;
15
use craft\base\Component;
16
use craft\base\Element;
17
use craft\base\ElementInterface;
18
use craft\elements\db\ElementQuery;
19
use craft\elements\db\ElementQueryInterface;
20
use craft\elements\db\EntryQuery;
21
use craft\elements\Entry;
22
use craft\events\CancelableEvent;
23
24
use yii\base\Exception;
25
26
/**
27
 * @author    nystudio107.com
28
 * @package   Similar
29
 * @since     1.0.0
30
 */
31
class Similar extends Component
32
{
33
    // Public Properties
34
    // =========================================================================
35
36
    /**
37
     * @var string The previous order in the query
38
     */
39
    public $preOrder;
40
41
    // Public Methods
42
    // =========================================================================
43
44
    /**
45
     * @param $data
46
     *
47
     * @return mixed
48
     * @throws Exception
49
     */
50
    public function find($data)
51
    {
52
        if (!isset($data['element'])) {
53
            throw new Exception('Required parameter `element` was not supplied to `craft.similar.find`.');
54
        }
55
56
        if (!isset($data['context'])) {
57
            throw new Exception('Required parameter `context` was not supplied to `craft.similar.find`.');
58
        }
59
60
        /** @var Element $element */
61
        $element = $data['element'];
62
        $context = $data['context'];
63
        $criteria = $data['criteria'] ?? [];
64
        if (\is_object($criteria)) {
65
            /** @var ElementQueryInterface $criteria */
66
            $criteria = $criteria->toArray();
67
        }
68
69
        // Get an ElementQuery for this Element
70
        $elementClass = \is_object($element) ? \get_class($element) : $element;
71
        /** @var EntryQuery $query */
72
        $query = $this->getElementQuery($elementClass, $criteria);
73
74
        // If the $query is null, just return an empty Entry
75
        if (!$query) { // no results
76
            return new Entry();
77
        }
78
79
        // Stash any orderBy directives from the $query for our anonymous function
80
        $this->preOrder = $query->orderBy;
81
82
        // Extract the $tagIds from the $context
83
        if (\is_array($context)) {
84
            $tagIds = $context;
85
        } else {
86
            /** @var ElementQueryInterface $context */
87
            $tagIds = $context->ids();
88
        }
89
90
        // We need to modify the actual craft\db\Query after the ElementQuery has been prepared
91
        $query->on(ElementQuery::EVENT_AFTER_PREPARE, [$this, 'eventAfterPrepareHandler']);
92
        // Return the data as an array, and only fetch the `id` and `siteId`
93
        $query->asArray(true);
94
        $query->select(['elements.id', 'elements_sites.siteId']);
95
        $query->andWhere('elements.id != :id', ['id' => $element->id]);
96
        $query->andWhere(['in', '{{%relations}}.targetId', $tagIds]);
97
        $query->leftJoin('{{%relations}}', 'elements.id={{%relations}}.sourceId');
98
        $results = $query->all();
99
100
        // Fetch the elements based on the returned `id` and `siteId`
101
        $elements = Craft::$app->getElements();
102
        $models = [];
103
        foreach ($results as $config) {
104
            $model = $elements->getElementById($config['id'], $elementClass, $config['siteId']);
105
            if ($model) {
106
                // The `count` property is added dynamically by our CountBehavior behavior
107
                /** @noinspection PhpUndefinedFieldInspection */
108
                $model->count = $config['count'];
109
                $models[] = $model;
110
            }
111
        }
112
113
        return $models;
114
    }
115
116
    // Protected Methods
117
    // =========================================================================
118
119
    /**
120
     * @param CancelableEvent $event
121
     */
122
    protected function eventAfterPrepareHandler(CancelableEvent $event)
123
    {
124
        /** @var ElementQuery $query */
125
        $query = $event->sender;
126
        // Add in the `count` param so we know how many were fetched
127
        $query->query->addSelect(['COUNT(*) as count']);
128
        $query->query->orderBy('count DESC, '.str_replace('`', '', $this->preOrder));
129
        $query->query->groupBy('{{%relations}}.sourceId');
130
        $query->subQuery->groupBy('elements.id');
0 ignored issues
show
The method groupBy() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

130
        $query->subQuery->/** @scrutinizer ignore-call */ 
131
                          groupBy('elements.id');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
131
        $event->isValid = true;
132
    }
133
134
    /**
135
     * Returns the element query based on $elementType and $criteria
136
     *
137
     * @var string|ElementInterface $elementType
138
     * @var array                   $criteria
139
     *
140
     * @return ElementQueryInterface
141
     */
142
    protected function getElementQuery($elementType, array $criteria): ElementQueryInterface
143
    {
144
        /** @var string|ElementInterface $elementType */
145
        $query = $elementType::find();
146
        Craft::configure($query, $criteria);
147
148
        return $query;
149
    }
150
}
151