lordthorzonus /
yii2-algolia
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
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
|
|||
| 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 |
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: