Issues (1)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/AlgoliaManager.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace leinonen\Yii2Algolia;
4
5
use AlgoliaSearch\Index;
6
use AlgoliaSearch\Client;
7
use leinonen\Yii2Algolia\ActiveRecord\Searchable;
8
use yii\db\ActiveQueryInterface;
9
use leinonen\Yii2Algolia\ActiveRecord\ActiveQueryChunker;
10
use leinonen\Yii2Algolia\ActiveRecord\ActiveRecordFactory;
11
12
/**
13
 * @method setConnectTimeout(int $connectTimeout, int $timeout = 30, int $searchTimeout = 5)
14
 * @method enableRateLimitForward(string $adminAPIKey, string $endUserIP, string $rateLimitAPIKey)
15
 * @method setForwarderFor(string $ip)
16
 * @method setAlgoliaUserToken(string $token)
17
 * @method disableRateLimitForward()
18
 * @method isAlive()
19
 * @method setExtraHeader(string $key, string $value)
20
 * @method mixed multipleQueries(array $queries, string $indexNameKey = "indexName", string $strategy = "none")
21
 * @method mixed listIndexes()
22
 * @method deleteIndex(string $indexName)
23
 * @method mixed moveIndex(string $srcIndexName, string $dstIndexName)
24
 * @method mixed copyIndex(string $srcIndexName, string $dstIndexName)
25
 * @method scopedCopyIndex(string $srcIndexName, string $dstIndexName, array $scope = [], array $requestHeaders = [])
26
 * @method mixed getLogs(int $offset = 0, int $length = 10, string $type = "all")
27
 * @method assignUserID($userID, $clusterName)
28
 * @method removeUserID($userID)
29
 * @method listClusters()
30
 * @method getUserID($userID)
31
 * @method listUserIDs($page = 0, $hitsPerPage = 20)
32
 * @method getTopUserID()
33
 * @method searchUserIDs($query, $clusterName = null, $page = null, $hitsPerPage = null)
34
 * @method Index initIndex(string $indexName)
35
 * @method mixed listApiKeys()
36
 * @method mixed getApiKey(string $key)
37
 * @method mixed deleteApiKey(string $key)
38
 * @method mixed addApiKey(array $obj, int $validity = 0, int $maxQueriesPerIPPerHour = 0, int $maxHitsPerQuery = 0, array $indexes = null)
39
 * @method mixed updateApiKey(string $key, array $obj, int $validity = 0, int $maxQueriesPerIPPerHour = 0, int $maxHitsPerQuery = 0, array $indexes = null)
40
 * @method mixed batch(array $requests)
41
 * @method string generateSecuredApiKey(string $privateApiKey, mixed $query, string $userToken = null)
42
 * @method string buildQuery(array $args)
43
 * @method mixed request(\AlgoliaSearch\ClientContext $context, string $method, string $path, array $params, array $data, array $hostsArray, int $connectTimeout, int $readTimeout)
44
 * @method mixed doRequest(\AlgoliaSearch\ClientContext $context, string $method, string $path, array $params, array $data, array $hostsArray, int $connectTimeout, int $readTimeout)
45
 * @method \AlgoliaSearch\PlacesIndex initPlaces(string $appId = null, string $appKey = null, array $hostsArray = null, array $options = [])
46
 * @method getContext()
47
 *
48
 * @see Client
49
 */
50
class AlgoliaManager
51
{
52
    /**
53
      * Size for the chunks used in reindexing methods.
54
      */
55
     const CHUNK_SIZE = 500;
56
57
    /**
58
     * @var AlgoliaFactory
59
     */
60
    protected $factory;
61
62
    /**
63
     * @var AlgoliaConfig
64
     */
65
    protected $config;
66
67
    /**
68
     * @var Client
69
     */
70
    protected $client;
71
72
    /**
73
     * @var ActiveRecordFactory
74
     */
75
    protected $activeRecordFactory;
76
77
    /**
78
     * @var null|string
79
     */
80
    protected $env;
81
82
    /**
83 65
     * @var ActiveQueryChunker
84
     */
85
    private $activeQueryChunker;
86
87
    /**
88 65
     * Initiates a new AlgoliaManager.
89 65
     *
90 65
     * @param Client $client
91 65
     * @param ActiveRecordFactory $activeRecordFactory
92
     * @param ActiveQueryChunker $activeQueryChunker
93
     */
94
    public function __construct(
95
        Client $client,
96
        ActiveRecordFactory $activeRecordFactory,
97
        ActiveQueryChunker $activeQueryChunker
98 29
    ) {
99
        $this->client = $client;
100 29
        $this->activeRecordFactory = $activeRecordFactory;
101
        $this->activeQueryChunker = $activeQueryChunker;
102
    }
103
104
    /**
105
     * Returns the Algolia Client.
106
     *
107
     * @return Client
108 65
     */
109
    public function getClient()
110 65
    {
111 65
        return $this->client;
112
    }
113
114
    /**
115
     * Sets the environment for the manager.
116
     *
117
     * @param string $env
118 1
     */
119
    public function setEnv($env)
120 1
    {
121
        $this->env = $env;
122
    }
123
124
    /**
125
     * Returns the environment for the manager.
126
     *
127
     * @return null|string
128
     */
129
    public function getEnv()
130 10
    {
131
        return $this->env;
132 10
    }
133 10
134
    /**
135 10
     * Indexes a searchable model to all indices.
136 10
     *
137 10
     * @param SearchableInterface $searchableModel
138 10
     *
139
     * @return array
140 10
     */
141
    public function pushToIndices(SearchableInterface $searchableModel)
142
    {
143
        $indices = $this->initIndices($searchableModel);
144
        $record = $searchableModel->getAlgoliaRecord();
145
146
        return $this->processIndices($indices, function (Index $index) use ($record, $searchableModel) {
147
            return $index->addObject($record, $searchableModel->getObjectID());
148
        });
149
    }
150 5
151
    /**
152 5
     * Indexes multiple searchable models in a batch. The given searchable models must be of the same class.
153 2
     *
154
     * @param SearchableInterface[] $searchableModels
155 2
     *
156
     * @return array
157 2
     */
158
    public function pushMultipleToIndices(array $searchableModels)
159 2
    {
160 2
        $algoliaRecords = $this->getAlgoliaRecordsFromSearchableModelArray($searchableModels);
161
        $indices = $this->initIndices($searchableModels[0]);
162 2
163
        return $this->processIndices($indices, function (Index $index) use ($algoliaRecords) {
164
            return $index->addObjects($algoliaRecords);
165
        });
166
    }
167
168
    /**
169
     * Updates the models data in all indices.
170
     *
171
     * @param SearchableInterface $searchableModel
172 3
     *
173
     * @return array
174 3
     */
175 3
    public function updateInIndices(SearchableInterface $searchableModel)
176
    {
177 3
        $indices = $this->initIndices($searchableModel);
178 3
        $record = $searchableModel->getAlgoliaRecord();
179 3
        $record['objectID'] = $searchableModel->getObjectID();
180 3
181 3
        return $this->processIndices($indices, function (Index $index) use ($record) {
182
            return $index->saveObject($record);
183 3
        });
184
    }
185
186
    /**
187
     * Updates multiple models data in all indices. The given searchable models must be of the same class.
188
     *
189
     * @param SearchableInterface[] $searchableModels
190
     *
191
     * @return array
192
     */
193 4
    public function updateMultipleInIndices(array $searchableModels)
194
    {
195 4
        $algoliaRecords = $this->getAlgoliaRecordsFromSearchableModelArray($searchableModels);
196 2
        $indices = $this->initIndices($searchableModels[0]);
197
198 2
        return $this->processIndices($indices, function (Index $index) use ($algoliaRecords) {
199
            return $index->saveObjects($algoliaRecords);
200 2
        });
201
    }
202 2
203 2
    /**
204
     * Removes a searchable model from indices.
205 2
     *
206
     * @param SearchableInterface $searchableModel
207
     *
208
     * @return array
209
     * @throws \InvalidArgumentException
210
     */
211
    public function removeFromIndices(SearchableInterface $searchableModel)
212
    {
213
        $indices = $indices = $this->initIndices($searchableModel);
214
        $objectID = $searchableModel->getObjectID();
215
216 3
        return $this->processIndices($indices, function (Index $index) use ($objectID) {
217
            return $index->deleteObject($objectID);
218 3
        });
219 3
    }
220
221 3
    /**
222 3
     * Removes multiple models from all indices. The given searchable models must be of the same class.
223 3
     *
224 3
     * @param array $searchableModels
225
     *
226 3
     * @return array
227
     * @throws \InvalidArgumentException
228
     */
229
    public function removeMultipleFromIndices(array $searchableModels)
230
    {
231
        $algoliaRecords = $this->getAlgoliaRecordsFromSearchableModelArray($searchableModels);
232
        $indices = $this->initIndices($searchableModels[0]);
233
        $objectIds = \array_map(function ($algoliaRecord) {
234
            return $algoliaRecord['objectID'];
235
        }, $algoliaRecords);
236
237 5
        return $this->processIndices($indices, function (Index $index) use ($objectIds) {
238
            return $index->deleteObjects($objectIds);
239 5
        });
240 2
    }
241
242 2
    /**
243 2
     * Re-indexes the indices safely for the given ActiveRecord Class.
244
     *
245 2
     * @param string $className The name of the ActiveRecord to be indexed
246
     *
247 2
     * @return array
248
     */
249 2
    public function reindex($className)
250 2
    {
251
        $this->checkImplementsSearchableInterface($className);
252 2
        $activeRecord = $this->activeRecordFactory->make($className);
253
254
        $records = $this->activeQueryChunker->chunk(
255
            $activeRecord->find(),
256
            self::CHUNK_SIZE,
257
            function ($activeRecordEntities) {
258
                return $this->getAlgoliaRecordsFromSearchableModelArray($activeRecordEntities);
259
            }
260
        );
261
262 4
        /* @var SearchableInterface $activeRecord */
263
        $indices = $this->initIndices($activeRecord);
264 4
265 3
        return $this->processIndices($indices, function (Index $index) use ($records) {
266
            return $this->reindexAtomically($index, $records);
267 3
        });
268 3
    }
269 3
270
    /**
271 3
     * Re-indexes the related indices for the given array only with the objects from the given array.
272
     * The given array must consist of Searchable objects of same class.
273 3
     *
274
     * @param SearchableInterface[] $searchableModels
275
     *
276 3
     * @throws \InvalidArgumentException
277 3
     * @return array
278
     */
279 3
    public function reindexOnly(array $searchableModels)
280 3
    {
281 3
        $records = $this->getAlgoliaRecordsFromSearchableModelArray($searchableModels);
282
        $indices = $this->initIndices($searchableModels[0]);
283 3
284
        return $this->processIndices($indices, function (Index $index) use ($records) {
285
            return $this->reindexAtomically($index, $records);
286
        });
287
    }
288
289
    /**
290
     * Re-indexes the related indices for the given ActiveQueryInterface.
291
     * The result of the given ActiveQuery must consist from Searchable models of the same class.
292
     *
293
     * @param ActiveQueryInterface $activeQuery
294
     *
295 3
     * @return array
296
     */
297 3
    public function reindexByActiveQuery(ActiveQueryInterface $activeQuery)
298 1
    {
299
        $indices = null;
300 1
        $records = $this->activeQueryChunker->chunk(
301
            $activeQuery,
302 1
            self::CHUNK_SIZE,
303 1
            function ($activeRecordEntities) use (&$indices) {
304 1
                $records = $this->getAlgoliaRecordsFromSearchableModelArray($activeRecordEntities);
305
306 1
                // The converting ActiveRecords to Algolia ones already does the type checking
307
                // so it's safe to init indices here during the first chunk.
308
                if ($indices === null) {
309
                    $indices = $this->initIndices($activeRecordEntities[0]);
310
                }
311
312
                return $records;
313
            }
314
        );
315
316
        return $this->processIndices($indices, function (Index $index) use ($records) {
0 ignored issues
show
$indices is of type null, but the function expects a array<integer,object<AlgoliaSearch\Index>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
317 5
            return $this->reindexAtomically($index, $records);
318
        });
319 5
    }
320
321 5
    /**
322 5
     * Clears the indices for the given Class that implements SearchableInterface.
323 5
     *
324
     * @param string $className The name of the Class which indices are to be cleared.
325 5
     *
326
     * @throws \InvalidArgumentException
327
     * @return array
328
     */
329 2
    public function clearIndices($className)
330 2
    {
331 2
        $this->checkImplementsSearchableInterface($className);
332
        /** @var SearchableInterface $activeRecord */
333 2
        $activeRecord = $this->activeRecordFactory->make($className);
334
        $indices = $indices = $this->initIndices($activeRecord);
335 5
336
        return $this->processIndices($indices, function (Index $index) {
337 2
            return $index->clearIndex();
338
        });
339 2
    }
340 2
341 2
    /**
342
     * @param string $className The name of the class which is to be searched.
343 2
     * @param string $query
344
     * @param null|array $searchParameters Optional search parameters given as an associative array.
345
     *
346
     * @link https://github.com/algolia/algoliasearch-client-php#search-parameters Allowed search parameters.
347
     *
348
     * @return array
349
     */
350
    public function search($className, $query, array $searchParameters = null)
351
    {
352
        $this->checkImplementsSearchableInterface($className);
353
        /* @var SearchableInterface $activeRecord */
354 2
        $activeRecord = $this->activeRecordFactory->make($className);
355
        $indices = $indices = $this->initIndices($activeRecord);
356 2
357 1
        return $this->processIndices($indices, function (Index $index) use ($query, $searchParameters) {
358 1
            return $index->search($query, $searchParameters);
359
        });
360
    }
361 1
362
    /**
363 1
     * Dynamically pass methods to the Algolia Client.
364 1
     *
365 1
     * @param string $method
366
     * @param array $parameters
367 1
     *
368
     * @return mixed
369
     */
370
    public function __call($method, $parameters)
371
    {
372
        return \call_user_func_array([$this->getClient(), $method], $parameters);
373
    }
374
375
    /**
376
     * Checks if the given class implements SearchableInterface.
377
     *
378
     * @param string $class Either name or instance of the class to be checked.
379 5
     */
380
    private function checkImplementsSearchableInterface($class)
381 5
    {
382 4
        $reflectionClass = new \ReflectionClass($class);
383 4
384
        if (! $reflectionClass->implementsInterface(SearchableInterface::class)) {
385
            throw new \InvalidArgumentException("The class: {$reflectionClass->name} doesn't implement leinonen\\Yii2Algolia\\SearchableInterface");
386 4
        }
387
    }
388 4
389 4
    /**
390 4
     * Initializes indices for the given SearchableModel.
391
     *
392 4
     * @param SearchableInterface $searchableModel
393
     *
394
     * @return Index[]
395
     */
396
    private function initIndices(SearchableInterface $searchableModel)
397
    {
398
        $indexNames = $searchableModel->getIndices();
399
400
        return \array_map(function ($indexName) {
401
            if ($this->env !== null) {
402
                $indexName = $this->env . '_' . $indexName;
403 26
            }
404
405 26
            return $this->initIndex($indexName);
406
        }, $indexNames);
407
    }
408
409
    /**
410
     * Maps an array of searchable models into an Algolia friendly array.
411
     *
412
     * @param SearchableInterface[] $searchableModels
413 29
     *
414
     * @return array
415 29
     */
416
    private function getAlgoliaRecordsFromSearchableModelArray(array $searchableModels)
417 29
    {
418 7
        if (empty($searchableModels)) {
419
            throw new \InvalidArgumentException('The given array should not be empty');
420 22
        }
421
422
        // Use the first element of the array to define what kind of models we are indexing.
423
        $arrayType = \get_class($searchableModels[0]);
424
        $this->checkImplementsSearchableInterface($arrayType);
425
426
        return \array_map(function (SearchableInterface $searchableModel) use ($arrayType) {
427
            if (! $searchableModel instanceof $arrayType) {
428
                throw new \InvalidArgumentException('The given array should not contain multiple different classes');
429 25
            }
430
431 25
            $algoliaRecord = $searchableModel->getAlgoliaRecord();
432
            $algoliaRecord['objectID'] = $searchableModel->getObjectID();
433
434 25
            return $algoliaRecord;
435 3
        }, $searchableModels);
436 3
    }
437
438 25
    /**
439 25
     * Reindex atomically the given index with the given records.
440
     *
441 25
     * @param Index $index
442
     * @param array $algoliaRecords
443
     *
444
     * @return mixed
445
     */
446
    private function reindexAtomically(Index $index, array $algoliaRecords)
447
    {
448
        $temporaryIndexName = 'tmp_' . $index->indexName;
449
450
        $temporaryIndex = $this->initIndex($temporaryIndexName);
451 25
        $temporaryIndex->addObjects($algoliaRecords);
452
453 25
        $settings = $index->getSettings();
454 4
455
        // Temporary index overrides all the settings on the main one.
456
        // So we need to set the original settings on the temporary one before atomically moving the index.
457
        $temporaryIndex->setSettings($settings);
458 21
459 21
        return $this->moveIndex($temporaryIndexName, $index->indexName);
460
    }
461 17
462 17
    /**
463 5
     * Performs actions for given indices returning an array of responses from those actions.
464
     *
465
     * @param Index[] $indices
466 17
     * @param callable $callback
467 17
     *
468
     * @return array The response as an array in format of ['indexName' => $responseFromAlgoliaClient]
469 17
     */
470 17
    private function processIndices($indices, callable $callback)
471
    {
472 12
        $response = [];
473
474
        foreach ($indices as $index) {
475
            $response[$index->indexName] = \call_user_func($callback, $index);
476
        }
477
478
        return $response;
479
    }
480
}
481