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 Arrilot\BitrixModels\Queries; |
||
4 | |||
5 | use Arrilot\BitrixModels\Models\BaseBitrixModel; |
||
6 | use BadMethodCallException; |
||
7 | use Bitrix\Main\Data\Cache; |
||
8 | use Closure; |
||
9 | use CPHPCache; |
||
10 | use Illuminate\Pagination\Paginator; |
||
11 | use Illuminate\Pagination\LengthAwarePaginator; |
||
12 | use Illuminate\Support\Collection; |
||
13 | use LogicException; |
||
14 | |||
15 | abstract class BaseQuery |
||
16 | { |
||
17 | use BaseRelationQuery; |
||
18 | |||
19 | /** |
||
20 | * Query select. |
||
21 | * |
||
22 | * @var array |
||
23 | */ |
||
24 | public $select = []; |
||
25 | /** |
||
26 | * Bitrix object to be queried. |
||
27 | * |
||
28 | * @var object|string |
||
29 | */ |
||
30 | protected $bxObject; |
||
31 | |||
32 | /** |
||
33 | * Name of the model that calls the query. |
||
34 | * |
||
35 | * @var string |
||
36 | */ |
||
37 | protected $modelName; |
||
38 | |||
39 | /** |
||
40 | * Model that calls the query. |
||
41 | * |
||
42 | * @var object |
||
43 | */ |
||
44 | protected $model; |
||
45 | |||
46 | /** |
||
47 | * Query sort. |
||
48 | * |
||
49 | * @var array |
||
50 | */ |
||
51 | public $sort = []; |
||
52 | |||
53 | /** |
||
54 | * Query filter. |
||
55 | * |
||
56 | * @var array |
||
57 | */ |
||
58 | public $filter = []; |
||
59 | |||
60 | /** |
||
61 | * Query navigation. |
||
62 | * |
||
63 | * @var array|bool |
||
64 | */ |
||
65 | public $navigation = false; |
||
66 | |||
67 | /** |
||
68 | * The key to list items in array of results. |
||
69 | * Set to false to have auto incrementing integer. |
||
70 | * |
||
71 | * @var string|bool |
||
72 | */ |
||
73 | public $keyBy = 'ID'; |
||
74 | |||
75 | /** |
||
76 | * Number of minutes to cache a query |
||
77 | * |
||
78 | * @var double|int |
||
79 | */ |
||
80 | public $cacheTtl = 0; |
||
81 | |||
82 | /** |
||
83 | * Indicates that the query should be stopped instead of touching the DB. |
||
84 | * Can be set in query scopes or manually. |
||
85 | * |
||
86 | * @var bool |
||
87 | */ |
||
88 | protected $queryShouldBeStopped = false; |
||
89 | |||
90 | /** |
||
91 | * Get count of users that match $filter. |
||
92 | * |
||
93 | * @return int |
||
94 | */ |
||
95 | abstract public function count(); |
||
96 | |||
97 | /** |
||
98 | * Подготавливает запрос и вызывает loadModels() |
||
99 | * |
||
100 | * @return Collection |
||
101 | */ |
||
102 | public function getList() |
||
103 | { |
||
104 | if (!is_null($this->primaryModel)) { |
||
105 | // Запрос - подгрузка релейшена. Надо добавить filter |
||
106 | $this->filterByModels([$this->primaryModel]); |
||
107 | } |
||
108 | |||
109 | if ($this->queryShouldBeStopped) { |
||
110 | return new Collection(); |
||
111 | } |
||
112 | |||
113 | $models = $this->loadModels(); |
||
114 | |||
115 | if (!empty($this->with)) { |
||
116 | $this->findWith($this->with, $models); |
||
117 | } |
||
118 | |||
119 | return $models; |
||
0 ignored issues
–
show
Bug
Compatibility
introduced
by
![]() |
|||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Get list of items. |
||
124 | * |
||
125 | * @return Collection |
||
126 | */ |
||
127 | abstract protected function loadModels(); |
||
128 | |||
129 | /** |
||
130 | * Constructor. |
||
131 | * |
||
132 | * @param object|string $bxObject |
||
133 | * @param string $modelName |
||
134 | */ |
||
135 | public function __construct($bxObject, $modelName) |
||
136 | { |
||
137 | $this->bxObject = $bxObject; |
||
138 | $this->modelName = $modelName; |
||
139 | $this->model = new $modelName(); |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * Get the first item that matches query params. |
||
144 | * |
||
145 | * @return mixed |
||
146 | */ |
||
147 | public function first() |
||
148 | { |
||
149 | return $this->limit(1)->getList()->first(null, false); |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Get item by its id. |
||
154 | * |
||
155 | * @param int $id |
||
156 | * |
||
157 | * @return mixed |
||
158 | */ |
||
159 | public function getById($id) |
||
160 | { |
||
161 | if (!$id || $this->queryShouldBeStopped) { |
||
162 | return false; |
||
163 | } |
||
164 | |||
165 | $this->sort = []; |
||
166 | $this->filter['ID'] = $id; |
||
167 | |||
168 | return $this->getList()->first(null, false); |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Setter for sort. |
||
173 | * |
||
174 | * @param mixed $by |
||
175 | * @param string $order |
||
176 | * |
||
177 | * @return $this |
||
178 | */ |
||
179 | public function sort($by, $order = 'ASC') |
||
180 | { |
||
181 | $this->sort = is_array($by) ? $by : [$by => $order]; |
||
182 | |||
183 | return $this; |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Another setter for sort. |
||
188 | * |
||
189 | * @param mixed $by |
||
190 | * @param string $order |
||
191 | * |
||
192 | * @return $this |
||
193 | */ |
||
194 | public function order($by, $order = 'ASC') |
||
195 | { |
||
196 | return $this->sort($by, $order); |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * Setter for filter. |
||
201 | * |
||
202 | * @param array $filter |
||
203 | * |
||
204 | * @return $this |
||
205 | */ |
||
206 | public function filter($filter) |
||
207 | { |
||
208 | $this->filter = array_merge($this->filter, $filter); |
||
209 | |||
210 | return $this; |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Reset filter. |
||
215 | * |
||
216 | * @return $this |
||
217 | */ |
||
218 | public function resetFilter() |
||
219 | { |
||
220 | $this->filter = []; |
||
221 | |||
222 | return $this; |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Add another filter to filters array. |
||
227 | * |
||
228 | * @param array $filters |
||
229 | * |
||
230 | * @return $this |
||
231 | */ |
||
232 | public function addFilter($filters) |
||
233 | { |
||
234 | foreach ($filters as $field => $value) { |
||
235 | $this->filter[$field] = $value; |
||
236 | } |
||
237 | |||
238 | return $this; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Setter for navigation. |
||
243 | * |
||
244 | * @param $value |
||
245 | * |
||
246 | * @return $this |
||
247 | */ |
||
248 | public function navigation($value) |
||
249 | { |
||
250 | $this->navigation = $value; |
||
251 | |||
252 | return $this; |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * Setter for select. |
||
257 | * |
||
258 | * @param $value |
||
259 | * |
||
260 | * @return $this |
||
261 | */ |
||
262 | public function select($value) |
||
263 | { |
||
264 | $this->select = is_array($value) ? $value : func_get_args(); |
||
265 | |||
266 | return $this; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Setter for cache ttl. |
||
271 | * |
||
272 | * @param float|int $minutes |
||
273 | * |
||
274 | * @return $this |
||
275 | */ |
||
276 | public function cache($minutes) |
||
277 | { |
||
278 | $this->cacheTtl = $minutes; |
||
279 | |||
280 | return $this; |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Setter for keyBy. |
||
285 | * |
||
286 | * @param string $value |
||
287 | * |
||
288 | * @return $this |
||
289 | */ |
||
290 | public function keyBy($value) |
||
291 | { |
||
292 | $this->keyBy = $value; |
||
293 | |||
294 | return $this; |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * Set the "limit" value of the query. |
||
299 | * |
||
300 | * @param int $value |
||
301 | * |
||
302 | * @return $this |
||
303 | */ |
||
304 | public function limit($value) |
||
305 | { |
||
306 | $this->navigation['nPageSize'] = $value; |
||
307 | |||
308 | return $this; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Set the "page number" value of the query. |
||
313 | * |
||
314 | * @param int $num |
||
315 | * |
||
316 | * @return $this |
||
317 | */ |
||
318 | public function page($num) |
||
319 | { |
||
320 | $this->navigation['iNumPage'] = $num; |
||
321 | |||
322 | return $this; |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Alias for "limit". |
||
327 | * |
||
328 | * @param int $value |
||
329 | * |
||
330 | * @return $this |
||
331 | */ |
||
332 | public function take($value) |
||
333 | { |
||
334 | return $this->limit($value); |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * Set the limit and offset for a given page. |
||
339 | * |
||
340 | * @param int $page |
||
341 | * @param int $perPage |
||
342 | * @return $this |
||
343 | */ |
||
344 | public function forPage($page, $perPage = 15) |
||
345 | { |
||
346 | return $this->take($perPage)->page($page); |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * Paginate the given query into a paginator. |
||
351 | * |
||
352 | * @param int $perPage |
||
353 | * @param string $pageName |
||
354 | * |
||
355 | * @return \Illuminate\Pagination\LengthAwarePaginator |
||
356 | */ |
||
357 | View Code Duplication | public function paginate($perPage = 15, $pageName = 'page') |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
358 | { |
||
359 | $page = Paginator::resolveCurrentPage($pageName); |
||
360 | $total = $this->count(); |
||
361 | $results = $this->forPage($page, $perPage)->getList(); |
||
362 | |||
363 | return new LengthAwarePaginator($results, $total, $perPage, $page, [ |
||
364 | 'path' => Paginator::resolveCurrentPath(), |
||
365 | 'pageName' => $pageName, |
||
366 | ]); |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * Get a paginator only supporting simple next and previous links. |
||
371 | * |
||
372 | * This is more efficient on larger data-sets, etc. |
||
373 | * |
||
374 | * @param int $perPage |
||
375 | * @param string $pageName |
||
376 | * |
||
377 | * @return \Illuminate\Pagination\Paginator |
||
378 | */ |
||
379 | View Code Duplication | public function simplePaginate($perPage = 15, $pageName = 'page') |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
380 | { |
||
381 | $page = Paginator::resolveCurrentPage($pageName); |
||
382 | $results = $this->forPage($page, $perPage + 1)->getList(); |
||
383 | |||
384 | return new Paginator($results, $perPage, $page, [ |
||
385 | 'path' => Paginator::resolveCurrentPath(), |
||
386 | 'pageName' => $pageName, |
||
387 | ]); |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Stop the query from touching DB. |
||
392 | * |
||
393 | * @return $this |
||
394 | */ |
||
395 | public function stopQuery() |
||
396 | { |
||
397 | $this->queryShouldBeStopped = true; |
||
398 | |||
399 | return $this; |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Adds $item to $results using keyBy value. |
||
404 | * |
||
405 | * @param $results |
||
406 | * @param BaseBitrixModel $object |
||
407 | * |
||
408 | * @return void |
||
409 | */ |
||
410 | protected function addItemToResultsUsingKeyBy(&$results, BaseBitrixModel $object) |
||
411 | { |
||
412 | $item = $object->fields; |
||
413 | if (!array_key_exists($this->keyBy, $item)) { |
||
414 | throw new LogicException("Field {$this->keyBy} is not found in object"); |
||
415 | } |
||
416 | |||
417 | $keyByValue = $item[$this->keyBy]; |
||
418 | |||
419 | if (!isset($results[$keyByValue])) { |
||
420 | $results[$keyByValue] = $object; |
||
421 | } else { |
||
422 | $oldFields = $results[$keyByValue]->fields; |
||
423 | foreach ($oldFields as $field => $oldValue) { |
||
424 | // пропускаем служебные поля. |
||
425 | if (in_array($field, ['_were_multiplied', 'PROPERTIES'])) { |
||
426 | continue; |
||
427 | } |
||
428 | |||
429 | $alreadyMultiplied = !empty($oldFields['_were_multiplied'][$field]); |
||
430 | |||
431 | // мультиплицируем только несовпадающие значения полей |
||
432 | $newValue = $item[$field]; |
||
433 | if ($oldValue !== $newValue) { |
||
434 | // если еще не мультиплицировали поле, то его надо превратить в массив. |
||
435 | if (!$alreadyMultiplied) { |
||
436 | $oldFields[$field] = [ |
||
437 | $oldFields[$field] |
||
438 | ]; |
||
439 | $oldFields['_were_multiplied'][$field] = true; |
||
440 | } |
||
441 | |||
442 | // добавляем новое значению поле если такого еще нет. |
||
443 | if (empty($oldFields[$field]) || (is_array($oldFields[$field]) && !in_array($newValue, $oldFields[$field]))) { |
||
444 | $oldFields[$field][] = $newValue; |
||
445 | } |
||
446 | } |
||
447 | } |
||
448 | |||
449 | $results[$keyByValue]->fields = $oldFields; |
||
450 | } |
||
451 | } |
||
452 | |||
453 | /** |
||
454 | * Determine if all fields must be selected. |
||
455 | * |
||
456 | * @return bool |
||
457 | */ |
||
458 | protected function fieldsMustBeSelected() |
||
459 | { |
||
460 | return in_array('FIELDS', $this->select); |
||
461 | } |
||
462 | |||
463 | /** |
||
464 | * Determine if all fields must be selected. |
||
465 | * |
||
466 | * @return bool |
||
467 | */ |
||
468 | protected function propsMustBeSelected() |
||
469 | { |
||
470 | return in_array('PROPS', $this->select) |
||
471 | || in_array('PROPERTIES', $this->select) |
||
472 | || in_array('PROPERTY_VALUES', $this->select); |
||
473 | } |
||
474 | |||
475 | /** |
||
476 | * Set $array[$new] as $array[$old] and delete $array[$old]. |
||
477 | * |
||
478 | * @param array $array |
||
479 | * @param $old |
||
480 | * @param $new |
||
481 | * |
||
482 | * return null |
||
483 | */ |
||
484 | protected function substituteField(&$array, $old, $new) |
||
485 | { |
||
486 | if (isset($array[$old]) && !isset($array[$new])) { |
||
487 | $array[$new] = $array[$old]; |
||
488 | } |
||
489 | |||
490 | unset($array[$old]); |
||
491 | } |
||
492 | |||
493 | /** |
||
494 | * Clear select array from duplication and additional fields. |
||
495 | * |
||
496 | * @return array |
||
497 | */ |
||
498 | protected function clearSelectArray() |
||
499 | { |
||
500 | $strip = ['FIELDS', 'PROPS', 'PROPERTIES', 'PROPERTY_VALUES', 'GROUPS', 'GROUP_ID', 'GROUPS_ID']; |
||
501 | |||
502 | return array_values(array_diff(array_unique($this->select), $strip)); |
||
503 | } |
||
504 | |||
505 | /** |
||
506 | * Store closure's result in the cache for a given number of minutes. |
||
507 | * |
||
508 | * @param string $key |
||
509 | * @param double $minutes |
||
510 | * @param Closure $callback |
||
511 | * @return mixed |
||
512 | */ |
||
513 | protected function rememberInCache($key, $minutes, Closure $callback) |
||
514 | { |
||
515 | $minutes = (double) $minutes; |
||
516 | if ($minutes <= 0) { |
||
517 | return $callback(); |
||
518 | } |
||
519 | |||
520 | $cache = Cache::createInstance(); |
||
521 | if ($cache->initCache($minutes * 60, $key, '/bitrix-models')) { |
||
522 | $vars = $cache->getVars(); |
||
523 | return !empty($vars['isCollection']) ? new Collection($vars['cache']) : $vars['cache']; |
||
524 | } |
||
525 | |||
526 | $cache->startDataCache(); |
||
527 | $result = $callback(); |
||
528 | |||
529 | // Bitrix cache is bad for storing collections. Let's convert it to array. |
||
530 | $isCollection = $result instanceof Collection; |
||
531 | if ($isCollection) { |
||
532 | $result = $result->all(); |
||
533 | } |
||
534 | |||
535 | $cache->endDataCache(['cache' => $result, 'isCollection' => $isCollection]); |
||
536 | |||
537 | return $isCollection ? new Collection($result) : $result; |
||
538 | } |
||
539 | |||
540 | protected function handleCacheIfNeeded($cacheKeyParams, Closure $callback) |
||
541 | { |
||
542 | return $this->cacheTtl |
||
543 | ? $this->rememberInCache(md5(json_encode($cacheKeyParams)), $this->cacheTtl, $callback) |
||
544 | : $callback(); |
||
545 | } |
||
546 | |||
547 | /** |
||
548 | * Handle dynamic method calls into the method. |
||
549 | * |
||
550 | * @param string $method |
||
551 | * @param array $parameters |
||
552 | * |
||
553 | * @throws BadMethodCallException |
||
554 | * |
||
555 | * @return $this |
||
556 | */ |
||
557 | public function __call($method, $parameters) |
||
558 | { |
||
559 | if (method_exists($this->model, 'scope'.$method)) { |
||
560 | array_unshift($parameters, $this); |
||
561 | |||
562 | $query = call_user_func_array([$this->model, 'scope'.$method], $parameters); |
||
563 | |||
564 | if ($query === false) { |
||
565 | $this->stopQuery(); |
||
566 | } |
||
567 | |||
568 | return $query instanceof static ? $query : $this; |
||
569 | } |
||
570 | |||
571 | $className = get_class($this); |
||
572 | |||
573 | throw new BadMethodCallException("Call to undefined method {$className}::{$method}()"); |
||
574 | } |
||
575 | |||
576 | protected function prepareMultiFilter(&$key, &$value) |
||
0 ignored issues
–
show
|
|||
577 | { |
||
578 | |||
579 | } |
||
580 | } |
||
581 |