Completed
Push — master ( 25f351...a717a6 )
by
unknown
03:43
created

Vortex::decodeMethodFromRequestedOrder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Macroparts\Vortex;
4
5
abstract class Vortex
6
{
7
    use \UnserAllerLib_Api_V4_AdapterProvider;
8
9
    const DIRECTIVE_FIELDS = 'fields';
10
11
    /**
12
     * @var \Doctrine\ORM\EntityManager
13
     */
14
    private $entityManager;
15
16
    /**
17
     * Defines data that can be included with an api call. You'd try to return only the most frequently used data
18
     * in default payload. Other available raw and aggregated data you'd make includable. Every availableInclude
19
     * has it's own include method and has to be mentioned in this configuration.
20
     *
21
     * Go to the Project Repository for an example configuration
22
     *
23
     * @var array
24
     */
25
    protected static $includeWhitelist = [];
26
    protected static $filterWhitelist = [];
27
    protected static $orderWhitelist = [];
28
29
    private $abstractFilterWhitelist = [
30
        'id' => '',
31
        'any' => '',
32
        'all' => ''
33
    ];
34
35
    /**
36
     * Used in the include configuration to tell if a property is a recursive inclusion or not
37
     */
38
    const INCLUDE_DIRECT = 0;
39
    const INCLUDE_RECURSIVE = 1;
40
41
    /**
42
     * Collects operations that have to be run on the doctrine result after query execution
43
     *
44
     * @var array
45
     */
46
    private $resultArrayFixSchedule = [];
47
48
    /**
49
     * Repositories can define a set of properties that are included by default
50
     *
51
     * @var array
52
     */
53
    protected static $defaultIncludes = [];
54
55
    /**
56
     * Repositories can define a default order which is taken by default
57
     *
58
     * @var array
59
     */
60
    protected static $defaultOrder = [];
61
62
    /**
63
     * If an ID is specified in the URL, it will be saved here for usage in child adapters.
64
     *
65
     * @var int|null
66
     */
67
    protected $id = null;
68
69
    /**
70
     * Ignore total row count functionality
71
     *
72
     * @var bool
73
     */
74
    protected $ignoreCount = false;
75
76
    /**
77
     * All parameters unsorted
78
     *
79
     * @var array
80
     */
81
    protected $unsortedParams = [];
82
83
    private $supportedLanguages;
84
85
86
    private $finalIncludeWhitelist;
87
    private $finalFilterWhitelist;
88
    private $finalOrderWhitelist;
89
90
    public function __construct($entityManager, $supportedLanguages = [])
91
    {
92
        $this->entityManager = $entityManager;
93
        $this->supportedLanguages = $supportedLanguages;
94
95
        //Cache customized whitelists merged with core whitelists
96
        $this->finalFilterWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
97
            static::class,
98
            'filterWhitelist'
99
        );
100
        $this->finalOrderWhitelist = $this->getStaticPropertyOfClassMergedWithParents(static::class, 'orderWhitelist');
101
        $this->finalIncludeWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
102
            static::class,
103
            'includeWhitelist'
104
        );
105
    }
106
107
    /**
108
     * @return \Doctrine\ORM\EntityManager
109
     */
110
    public function getEntityManager()
111
    {
112
        return $this->entityManager;
113
    }
114
115
    /**
116
     * @param \Doctrine\ORM\EntityManager $entityManager
117
     * @return $this
118
     */
119
    public function setEntityManager($entityManager)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
120
    {
121
        $this->entityManager = $entityManager;
122
        return $this;
123
    }
124
125
    private function isNotIncludableProperty($property)
126
    {
127
        return !isset($this->finalIncludeWhitelist[$property]);
128
    }
129
130
    private function isNotFilterableProperty($property)
131
    {
132
        return !isset($this->finalFilterWhitelist[$property]) && !isset($this->abstractFilterWhitelist[$property]);
133
    }
134
135
    private function isNotOrderableProperty($property)
136
    {
137
        return !isset($this->finalOrderWhitelist[$property]);
138
    }
139
140
    public function getTranslatableIncludeNames()
141
    {
142
        return array_keys(array_filter($this->finalIncludeWhitelist, function ($inc) {
143
            return isset($inc['translatable']) && $inc['translatable'];
144
        }));
145
    }
146
147
    /**
148
     * @return array
149
     */
150
    private function getPlatformOptions()
151
    {
152
        return \Zend_Registry::get('platformOptions');
153
    }
154
155
    /**
156
     * @return bool
157
     */
158
    protected function arePlatformUsersPublic()
159
    {
160
        return (bool)$this->getPlatformOptions()['user']['public'];
161
    }
162
163
    /**
164
     * @return bool
165
     */
166
    protected function arePlatformUsersPrivate()
167
    {
168
        return !$this->arePlatformUsersPublic();
169
    }
170
171
    /**
172
     * @param \Doctrine\ORM\QueryBuilder $query
173
     * @param string $alias
174
     * @param \UnserAller_Model_User $currentUser
175
     * @param array $additionalParams
176
     * @param $language
177
     * @param $filtername
178
     * @return \Doctrine\ORM\Query\Expr\Orx
179
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
180
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
181
     */
182
    private function filterAny($query, $alias, $currentUser, $additionalParams, $language, $filtername)
183
    {
184
        return $this->abstractFilterMultipleFields(
185
            $query,
186
            'orX',
187
            $currentUser,
188
            $additionalParams,
189
            $language,
190
            $filtername
191
        );
192
    }
193
194
    /**
195
     * @param \Doctrine\ORM\QueryBuilder $query
196
     * @param string $alias
197
     * @param \UnserAller_Model_User $currentUser
198
     * @param array $additionalParams
199
     * @param $language
200
     * @param $filtername
201
     * @return \Doctrine\ORM\Query\Expr\Orx
202
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
203
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
204
     */
205
    private function filterAll($query, $alias, $currentUser, $additionalParams, $language, $filtername)
206
    {
207
        return $this->abstractFilterMultipleFields(
208
            $query,
209
            'andX',
210
            $currentUser,
211
            $additionalParams,
212
            $language,
213
            $filtername
214
        );
215
    }
216
217
    /**
218
     * @param \Doctrine\ORM\QueryBuilder $query
219
     * @param string $expressionType
220
     * @param \UnserAller_Model_User $currentUser
221
     * @param array $additionalParams
222
     * @param $language
223
     * @param $filtername
224
     * @return \Doctrine\ORM\Query\Expr\Orx
225
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
226
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
227
     */
228
    private function abstractFilterMultipleFields(
229
        $query,
230
        $expressionType,
231
        $currentUser,
232
        $additionalParams,
233
        $language,
234
        $filtername
235
    ) {
236
        if (!isset($additionalParams[self::DIRECTIVE_FIELDS])) {
237
            throw new \UnserAllerLib_Api_V4_Exception_MissingFilterDirective(
238
                $filtername,
239
                self::DIRECTIVE_FIELDS,
240
                ['fieldname1', 'fieldname2'],
241
                ':someFilterDirective(params):maySomeMoreDirectives...'
242
            );
243
        }
244
245
        $fields = $additionalParams[self::DIRECTIVE_FIELDS];
246
        if (count(array_intersect_key($this->finalFilterWhitelist, array_flip($fields))) !== count($fields)) {
247
            throw new \UnserAllerLib_Api_V4_Exception_SafeForPrinting('Wrong use of "' . $filtername . '" filter. One of your specified fields is not filterable. Try using fields that are filterable.');
248
        }
249
250
        unset($additionalParams[self::DIRECTIVE_FIELDS]);
251
252
        $expression = call_user_func([$query->expr(), $expressionType]);
253
        foreach ($fields as $field) {
254
            $filterMethod = $this->decodeMethodFromRequestedFilter($field);
255
            $expression->add($this->$filterMethod(
256
                $query, $filterMethod, $currentUser, $additionalParams, $language, $field,
257
                $this->finalFilterWhitelist[$field]
258
            ));
259
        }
260
261
        return $expression;
262
    }
263
264
    /**
265
     * Executes include methods driven by a include string. See API docs to know how this string looks like
266
     *
267
     * @param \Doctrine\ORM\QueryBuilder $query
268
     * @param \UnserAller_Model_User $currentUser
269
     * @param $language
270
     * @param string $includeString
271
     * @param array $meta
272
     * @return \Doctrine\ORM\QueryBuilder
273
     */
274
    protected function addIncludeStatements($query, $currentUser, $language, $includeString, &$meta = [])
275
    {
276
        $requestedIncludes = $this->parseIncludeString($includeString, $this->finalIncludeWhitelist);
277
278
        $requestedIncludes = $requestedIncludes + static::$defaultIncludes;
279
        foreach ($requestedIncludes as $requestedInclude => $additionalParams) {
280
            if ($this->isNotIncludableProperty($requestedInclude)) {
281
                continue;
282
            }
283
284
            $includeMethod = $this->decodeMethodFromRequestedInclude($requestedInclude);
285
            $postProcessDirections = $this->$includeMethod($query, $includeMethod, $currentUser, $additionalParams,
286
                $language);
287
288
            if ($postProcessDirections) {
289
                $this->schedulePostProcessingDirections($postProcessDirections);
290
            }
291
292
            $this->updateMetaOnInclude($meta, $requestedInclude);
293
        }
294
        return $query;
295
    }
296
297
    /**
298
     * Collecting whitelist not just for current class but merged with all whitelists from parent classes.
299
     * So when we overwrite whitelists locally they are still including all the elements from core adapters.
300
     *
301
     * @param null|string $class
302
     * @param $propertyname
303
     * @return array
304
     */
305
    private function getStaticPropertyOfClassMergedWithParents($class, $propertyname)
306
    {
307
        $class = $class ? $class : static::class;
308
        $parent = get_parent_class($class);
309
        return $parent ? $this->getStaticPropertyOfClassOrArray(
310
            $class,
311
            $propertyname
312
        ) + $this->getStaticPropertyOfClassMergedWithParents(
313
            $parent,
314
            $propertyname
315
        ) : $this->getStaticPropertyOfClassOrArray($class, $propertyname);
316
    }
317
318
    private function getStaticPropertyOfClassOrArray($class, $propertyname)
319
    {
320
        return isset($class::$$propertyname) ? $class::$$propertyname : [];
321
    }
322
323
    private function updateMetaOnInclude(&$meta, $includeName)
324
    {
325
        $include = $this->finalIncludeWhitelist[$includeName];
326
        if (isset($include['model'])) {
327
            echo $includeName;
328
            $meta['modelnameIndex']["{$include['model']}"][] = $includeName;
329
        }
330
    }
331
332
    /**
333
     * Calls methods that add where conditions to a query driven by a string (see api docs for string format)
334
     *
335
     * @param \Doctrine\ORM\QueryBuilder $query
336
     * @param \UnserAller_Model_User $currentUser
337
     * @param string $filterString
338
     * @param $language
339
     * @param string $joinFiltersWith
340
     * @return \Doctrine\ORM\QueryBuilder
341
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidFilter
342
     * @uses filterAny
343
     * @uses filterAll
344
     */
345
    protected function addFilterStatements($query, $currentUser, $filterString, $language, $joinFiltersWith = 'AND')
346
    {
347
        $requestedFilters = array_filter($this->parseRichParamString($filterString));
348
        if (!$requestedFilters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $requestedFilters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
349
            return $query;
350
        }
351
352
        $expression = mb_strtoupper($joinFiltersWith) === 'OR' ? $query->expr()->orX() : $query->expr()->andX();
353
        foreach ($requestedFilters as $requestedFilter => $additionalParams) {
354
            if ($this->isNotFilterableProperty($requestedFilter)) {
355
                throw new \UnserAllerLib_Api_V4_Exception_InvalidFilter($requestedFilter);
356
            }
357
358
            $filterMethod = $this->decodeMethodFromRequestedFilter($requestedFilter);
359
            $expression->add($this->$filterMethod(
360
                $query, $filterMethod, $currentUser, $additionalParams, $language, $requestedFilter,
361
                $this->finalFilterWhitelist[$requestedFilter]
362
            ));
363
        }
364
365
        return $query->andWhere($expression);
366
    }
367
368
369
    /**
370
     * Transforms additionalParams for included collections into params which are used
371
     * during post processing to call a findXForApi method
372
     *
373
     * @param array $additionalParams
374
     * @return array
375
     */
376
    protected function parseAdditionalIncludeParams($additionalParams)
377
    {
378
        $filter = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filter');
379
        $filter = is_array($filter) ? implode(',', $filter) : '';
380
381
        $include = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'include');
382
        $include = is_array($include) ? implode(',', $include) : '';
383
384
        $order = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'order');
385
        $order = is_array($order) ? implode(',', $order) : '';
386
387
        $limit = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'limit');
388
        $limit = is_array($limit) ? (int)array_shift($limit) : 0;
389
390
        $page = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'page');
391
        $page = is_array($page) ? (int)array_shift($page) : 1;
392
393
        $filterMode = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filterMode');
394
        $filterMode = is_array($filterMode) ? array_shift($filterMode) : 'AND';
395
396
        return [$filter, $include, $order, $limit, $page, $filterMode];
397
    }
398
399
    /**
400
     * Calls methods that add orderBy statements to a query driven by a string (see api docs for the string format)
401
     *
402
     * @param \Doctrine\ORM\QueryBuilder $query
403
     * @param \UnserAller_Model_User $currentUser
404
     * @param string $orderString
405
     * @return \Doctrine\ORM\QueryBuilder
406
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidOrder
407
     */
408
    private function addOrderStatements($query, $currentUser, $orderString)
409
    {
410
        $requestedOrders = array_filter($this->parseRichParamString($orderString));
411
412
        if (!$requestedOrders) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $requestedOrders of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
413
            $requestedOrders = static::$defaultOrder;
414
        }
415
416
        foreach ($requestedOrders as $field => $order) {
417
            if ($this->isNotOrderableProperty($field)) {
418
                throw new \UnserAllerLib_Api_V4_Exception_InvalidOrder($field);
419
            }
420
421
            $orderMethod = $this->decodeMethodFromRequestedOrder($field);
422
            $postProcessTasks = $this->$orderMethod($query, $orderMethod, $currentUser,
423
                isset($order['desc']) ? 'DESC' : 'ASC', $order);
424
            if ($postProcessTasks) {
425
                $this->schedulePostProcessingDirections($postProcessTasks);
426
            }
427
        }
428
429
        return $query;
430
    }
431
432
    /**
433
     * Knows how to append post processing directions to the post process schedule
434
     *
435
     * @param array $tasks
436
     */
437
    private function schedulePostProcessingDirections($tasks)
438
    {
439
        if (!$tasks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tasks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
440
            return;
441
        }
442
443
        if (!is_array($tasks[0])) {
444
            $tasks = [$tasks];
445
        }
446
447
        foreach ($tasks as $task) {
448
            $this->resultArrayFixSchedule[array_shift($task)] = $task;
449
        }
450
    }
451
452
    /**
453
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
454
     * uppercase first letter and every letter that comes after a dot, remove dots and prepend 'include'. Examples:
455
     *
456
     * returns includeProject when $requestedInclude = project
457
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
458
     *
459
     * @param string $requestedInclude
460
     * @return string
461
     */
462
    private function decodeMethodFromRequestedInclude($requestedInclude)
463
    {
464
        return 'include' . implode('', array_map('ucfirst', explode('.', str_replace('[]', '_cp', $requestedInclude))));
465
    }
466
467
    /**
468
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
469
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
470
     *
471
     * returns includeProject when $requestedInclude = project
472
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
473
     *
474
     * @param string $requestedInclude
475
     * @return string
476
     */
477
    private function decodeMethodFromRequestedFilter($requestedInclude)
478
    {
479
        return 'filter' . implode('', array_map('ucfirst', explode('.', $requestedInclude)));
480
    }
481
482
    /**
483
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
484
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
485
     *
486
     * returns includeProject when $requestedInclude = project
487
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
488
     *
489
     * @param string $field
490
     * @return string
491
     */
492
    private function decodeMethodFromRequestedOrder($field)
493
    {
494
        return 'orderBy' . implode('', array_map('ucfirst', explode('.', $field)));
495
    }
496
497
    /**
498
     * Calculates total pages for an $incompleteStatement. Incomplete statements are doctrine query builder instances
499
     * with all required conditions but no select statement and no additional includes.
500
     *
501
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
502
     * @param int $limit
503
     * @return float|int
504
     */
505
    private function calculateTotalPages($incompleteStatement, $limit)
506
    {
507
        $incompleteStatement = clone $incompleteStatement;
508
509
        if ($limit) {
510
            return (int)ceil($this->executeRowCountStatement($incompleteStatement) / $limit);
511
        }
512
        return 1;
513
    }
514
515
    /**
516
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
517
     * @return int
518
     */
519
    private function executeRowCountStatement($incompleteStatement)
520
    {
521
        if ($this->ignoreCount) {
522
            return 0;
523
        }
524
525
        $rootAlias = $this->getRootAlias($incompleteStatement);
526
        $primaryIndexCol = $rootAlias . '.' . $this->getPrimaryIndexCol();
527
        return (int)$incompleteStatement
528
            ->select("COUNT(DISTINCT $primaryIndexCol)")
529
            ->getQuery()
530
            ->getSingleScalarResult();
531
    }
532
533
    /**
534
     * Doctrine will throw errors if a table has a multi column primary index
535
     * http://stackoverflow.com/questions/18968963/select-countdistinct-error-on-multiple-columns
536
     * @return string
537
     */
538
    protected function getPrimaryIndexCol()
539
    {
540
        return 'id';
541
    }
542
543
    /**
544
     * Todo: Include collections by additional params and not by includes and adjust docs
545
     * Takes the include string and decodes it to an array with include names as keys and an array with additionalParams
546
     * as the value. Includes that are nested inside included collections are grouped and added as additional params
547
     * to the included collection.
548
     *
549
     * @param $string
550
     * @param $availableIncludes
551
     * @return array
552
     */
553
    private function parseIncludeString($string, $availableIncludes)
554
    {
555
        if ($string === '') {
556
            return [];
557
        }
558
559
        if (is_string($string)) {
560
            $string = explode(',', $string);
561
        }
562
563
        if (!is_array($string)) {
564
            return [];
565
        }
566
567
        $requestedIncludes = [];
568
        $implicitIncludes = [];
569
        foreach ($string as $include) {
570
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
571
572
            $pathToFirstRecursiveInclusion = $this->pathForNestedInclude($includeName, $availableIncludes);
573
            if ($pathToFirstRecursiveInclusion) {
574
                $requestedIncludes[$pathToFirstRecursiveInclusion]['include'][] = substr(
575
                    $include,
576
                    strlen($pathToFirstRecursiveInclusion) + 1
577
                );
578
                continue;
579
            }
580
581
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
582
583
            if ($allModifiersStr === null) {
584
                if (!isset($requestedIncludes[$includeName])) {
585
                    $requestedIncludes[$includeName] = [];
586
                }
587
                continue;
588
            }
589
590
            if (preg_match('~filter\(~u', $allModifiersStr)) {
591
                $modifierArr = $this->parseModifierArraySlowButAccurate($allModifiersStr);
592
            } else {
593
                $modifierArr = $this->parseModifierStringQuickButInaccurate($allModifiersStr);
594
            }
595
596
            if (isset($requestedIncludes[$includeName])) {
597
                $requestedIncludes[$includeName] = $requestedIncludes[$includeName] + $modifierArr;
598
            } else {
599
                $requestedIncludes[$includeName] = $modifierArr;
600
            }
601
        }
602
603
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
604
    }
605
606
    /**
607
     * creates an array out of string in this format:
608
     *
609
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3)
610
     *
611
     * Result:
612
     * [
613
     *  'modifierName1' => ['modifierParam1','modifierParam2'],
614
     *  'modifierName2' => ['modifierParam3']
615
     * ]
616
     *
617
     * But doesn't work when modifier params contain other modifiers with params themselves
618
     *
619
     * @param string $allModifiersStr
620
     * @return array
621
     */
622
    private function parseModifierStringQuickButInaccurate($allModifiersStr)
623
    {
624
        // Matches multiple instances of 'something(foo|bar|baz)' in the string
625
        // I guess it ignores : so you could use anything, but probably don't do that
626
        preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
627
        // [0] is full matched strings...
628
        $modifierCount = count($allModifiersArr[0]);
629
        $modifierArr = [];
630 View Code Duplication
        for ($modifierIt = 0; $modifierIt < $modifierCount; $modifierIt++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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.

Loading history...
631
            // [1] is the modifier
632
            $modifierName = $allModifiersArr[1][$modifierIt];
633
            // and [3] is delimited params
634
            $modifierParamStr = $allModifiersArr[3][$modifierIt];
635
            // Make modifier array key with an array of params as the value
636
            $modifierArr[$modifierName] = explode('|', $modifierParamStr);
637
        }
638
639
        return $modifierArr;
640
    }
641
642
    /**
643
     * creates an array out of string in this format:
644
     *
645
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3))
646
     *
647
     * Can also handle modifier params that contain other modifier with params
648
     *
649
     * @param string $s
650
     * @return array
651
     */
652 View Code Duplication
    private function parseModifierArraySlowButAccurate($s)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
653
    {
654
        $modifierArr = [];
655
        $modifierName = '';
656
        $modifierParamStr = '';
657
658
        $depth = 0;
659
        for ($i = 0; $i <= strlen($s); $i++) {
660
            switch ($s[$i]) {
661
                case '(':
662
                    if ($depth) {
663
                        $modifierParamStr .= $s[$i];
664
                    }
665
                    $depth++;
666
                    break;
667
668
                case ')':
669
                    $depth--;
670
                    if ($depth) {
671
                        $modifierParamStr .= $s[$i];
672
                    }
673
                    break;
674
                case ':':
675
                    if ($depth) {
676
                        $modifierParamStr .= $s[$i];
677
                    } else {
678
                        $modifierArr[$modifierName] = $this->parseModifierParamStringSlowButAccurate($modifierParamStr);
679
                        $modifierName = '';
680
                        $modifierParamStr = '';
681
                    }
682
                    break;
683
                default:
684
                    if ($depth) {
685
                        $modifierParamStr .= $s[$i];
686
                    } else {
687
                        $modifierName .= $s[$i];
688
                    }
689
            }
690
        }
691
692
        if ($modifierName) {
693
            $modifierArr[$modifierName] = $this->parseModifierParamStringSlowButAccurate($modifierParamStr);
694
        }
695
696
        return $modifierArr;
697
    }
698
699
    /**
700
     * Can make an array out of parameter string that looks like this:
701
     *
702
     * param1|param2|param3
703
     *
704
     * Can also handle params that contain other modifiers with params like this:
705
     * param1|modifier(innerParam1|innerParam2)|param3
706
     *
707
     * @param string $s
708
     * @return array
709
     */
710 View Code Duplication
    private function parseModifierParamStringSlowButAccurate($s)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
711
    {
712
        $paramArr = [];
713
        $tmpStr = '';
714
715
        $depth = 0;
716
        for ($i = 0; $i <= strlen($s); $i++) {
717
            switch ($s[$i]) {
718
                case '(':
719
                    $tmpStr .= $s[$i];
720
                    $depth++;
721
                    break;
722
723
                case ')':
724
                    $tmpStr .= $s[$i];
725
                    $depth--;
726
                    break;
727
728
                case '|':
729
                    if ($depth) {
730
                        $tmpStr .= $s[$i];
731
                    } else {
732
                        $paramArr[] = $tmpStr;
733
                        $tmpStr = '';
734
                    }
735
                    break;
736
737
                default:
738
                    $tmpStr .= $s[$i];
739
            }
740
        }
741
742
        if (strlen($tmpStr)) {
743
            $paramArr[] = $tmpStr;
744
        }
745
746
        return $paramArr;
747
    }
748
749
    /**
750
     * Checks if includeName is an include nested inside a recursive inclusion.
751
     * If yes, return the path to that item - false otherwise.
752
     *
753
     * Example:
754
     * For projects there can be an include for phases. Phases are included recursively in its own adapter. So you'd
755
     * want when you include phases.steps that the steps inclusion is executed in the phase adapter and not in the
756
     * project adapter. That's why we need to separate includes that need to be passed further here.
757
     *
758
     * "recursiveinclude" results to false
759
     * "normalprop1" results to false
760
     * "recursiveinclude.normalprop1.normalprop2" results to "recursiveinclude"
761
     * "normalprop1.recursiveinclude.normalprop2" results to "normalprop1.recursiveinclude"
762
     *
763
     * @param $includeName
764
     * @param $availableIncludes
765
     * @return bool|string
766
     */
767
    private function pathForNestedInclude($includeName, $availableIncludes)
768
    {
769
        $pathArray = explode('.', $includeName);
770
        if (!isset($pathArray[1])) {
771
            return false;
772
        }
773
774
        $pathArrayLength = count($pathArray);
775
        for ($i = 1; $i < $pathArrayLength; $i++) {
776
            $implicitPath = implode('.', array_slice($pathArray, 0, $i));
777
            if ($this->extractStrategyForInclude($availableIncludes[$implicitPath]) === self::INCLUDE_RECURSIVE) {
778
                return $implicitPath;
779
            }
780
        }
781
782
        return false;
783
    }
784
785
    /**
786
     * Include configuration can either have just the strategy or a configuration array with the strategy inside.
787
     *
788
     * @param mixed $include
789
     * @return integer
790
     */
791
    private function extractStrategyForInclude($include)
792
    {
793
        return is_array($include) ? $include['strategy'] : $include;
794
    }
795
796
    /**
797
     * Validates the include string and returns an array with requiredIncludes
798
     *
799
     * @param string $string
800
     * @return array
801
     */
802
    private function parseRichParamString($string)
803
    {
804
        if ($string === '') {
805
            return [];
806
        }
807
808
        if (is_string($string)) {
809
            $string = explode(',', $string);
810
        }
811
812
        if (!is_array($string)) {
813
            return [];
814
        }
815
816
        $requestedIncludes = [];
817
        $implicitIncludes = [];
818
        foreach ($string as $include) {
819
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
820
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
821
822
            if ($allModifiersStr === null) {
823
                $requestedIncludes[$includeName] = [];
824
                continue;
825
            }
826
827
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
828
            // I guess it ignores : so you could use anything, but probably don't do that
829
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
830
            // [0] is full matched strings...
831
            $modifierCount = count($allModifiersArr[0]);
832
            $modifierArr = [];
833 View Code Duplication
            for ($modifierIt = 0; $modifierIt < $modifierCount; $modifierIt++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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.

Loading history...
834
                // [1] is the modifier
835
                $modifierName = $allModifiersArr[1][$modifierIt];
836
                // and [3] is delimited params
837
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
838
                // Make modifier array key with an array of params as the value
839
                $modifierArr[$modifierName] = explode('|', $modifierParamStr);
840
            }
841
            $requestedIncludes[$includeName] = $modifierArr;
842
        }
843
844
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
845
    }
846
847
    private function mergeWithImplicitIncludes($includes, $implicitIncludes)
848
    {
849
        foreach ($implicitIncludes as $implicitInclude) {
850
            if (isset($includes[$implicitInclude])) {
851
                continue;
852
            }
853
854
            $includes[$implicitInclude] = [];
855
        }
856
857
        return $includes;
858
    }
859
860
    /**
861
     * @param \Doctrine\ORM\QueryBuilder $query
862
     * @param string $alias
863
     * @param \UnserAller_Model_User $currentUser
864
     * @param array $additionalParams
865
     * @return \Doctrine\ORM\Query\Expr\Andx
866
     */
867
    protected function filterId($query, $alias, $currentUser, $additionalParams)
868
    {
869
        $rootAlias = array_shift($query->getRootAliases());
0 ignored issues
show
Bug introduced by
$query->getRootAliases() cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
870
        return $this->createConditionsForEntityColumn("$rootAlias.id", $query, $alias, $currentUser, $additionalParams);
871
    }
872
873
    private function getImplicitIncludes($includeName)
874
    {
875
        $parts = explode('.', $includeName);
876
        $numberOfParts = count($parts);
877
878
        if ($numberOfParts < 2) {
879
            return [];
880
        }
881
882
        $implicitIncludes = [];
883
        for ($i = 1; $i < $numberOfParts; $i++) {
884
            $implicitIncludes[] = implode('.', array_slice($parts, 0, $i));
885
        }
886
887
        return $implicitIncludes;
888
    }
889
890
    /**
891
     * Creates fixed, paginated results from an $incompleteStatement and a requiredIncludes string. An incomplete
892
     * statement is a query builder instance with only froms, joins and where conditions (groupbys, havings not tested).
893
     *
894
     * @param \UnserAller_Model_User $currentUser
895
     * @param $language
896
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
897
     * @param string $filterString
898
     * @param string $includeString
899
     * @param int $limit
900
     * @param int $page
901
     * @return array
902
     */
903
    protected function createPaginatedResults(
904
        $currentUser,
905
        $language,
906
        $incompleteStatement,
907
        $filterString,
908
        $includeString,
909
        $limit,
910
        $page
911
    ) {
912
        $this->addFilterStatements($incompleteStatement, $currentUser, $filterString, $language);
913
        $completeStatement = $this->completeStatement(
914
            $currentUser,
915
            $language,
916
            $incompleteStatement,
917
            $includeString,
918
            ''
919
        );
920
921
        if ($limit > 0) {
922
            $completeStatement
923
                ->setFirstResult(($page - 1) * $limit)
924
                ->setMaxResults($limit);
925
        }
926
927
        return [
928
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
929
            'page' => $page,
930
            'filter' => $filterString,
931
            'include' => $includeString,
932
            'pageSize' => $limit,
933
            'data' => $this->applyScheduledFixes($this->getRawResult($completeStatement), $currentUser, $language)
934
        ];
935
    }
936
937
    /**
938
     * @param \UnserAller_Model_User $currentUser
939
     * @return \Doctrine\ORM\QueryBuilder
940
     */
941
    abstract protected function initIncompleteStatement($currentUser);
942
943
    private function createIncompleteStatement(
944
        $currentUser,
945
        $filterString,
946
        $language,
947
        $joinFiltersWith = 'AND',
948
        &$meta = []
949
    ) {
950
        return $this->addFilterStatements(
951
            $this->initIncompleteStatement($currentUser),
952
            $currentUser,
953
            $filterString,
954
            $language,
955
            $joinFiltersWith
956
        );
957
    }
958
959
    /**
960
     * @param \UnserAller_Model_User $currentUser
961
     * @param string $filterString
962
     * @param $language
963
     * @param string $joinFiltersWith
964
     * @return int
965
     */
966
    public function findTotalNumberOfRows($currentUser, $filterString = '', $language = '', $joinFiltersWith = 'AND')
967
    {
968
        return $this->executeRowCountStatement(
969
            $this->createIncompleteStatement($currentUser, $filterString, $language, $joinFiltersWith)
970
        );
971
    }
972
973
    /**
974
     * @param \UnserAller_Model_User $currentUser
975
     * @param $language
976
     * @param string $filterString
977
     * @param string $includeString
978
     * @param string $orderString
979
     * @param int $limit
980
     * @param int $page
981
     * @param string $joinFiltersWith
982
     * @return array
983
     */
984
    public function findMultipleForApi(
985
        $currentUser,
986
        $language = '',
987
        $filterString = '',
988
        $includeString = '',
989
        $orderString = '',
990
        $limit = 0,
991
        $page = 1,
992
        $joinFiltersWith = 'AND'
993
    ) {
994
        if ($page <= 0) {
995
            $page = 1;
996
        }
997
998
        $meta = $this->initMetaArray('', $language);
999
1000
        $incompleteStatement = $this->createIncompleteStatement(
1001
            $currentUser,
1002
            $filterString,
1003
            $language,
1004
            $joinFiltersWith,
1005
            $meta
1006
        );
1007
1008
        $completeStatement = $this->completeStatement(
1009
            $currentUser,
1010
            $language,
1011
            $incompleteStatement,
1012
            $includeString,
1013
            $orderString,
1014
            $meta
1015
        );
1016
        if ($limit > 0) {
1017
            $completeStatement
1018
                ->setFirstResult(($page - 1) * $limit)
1019
                ->setMaxResults($limit);
1020
        }
1021
1022
        return [
1023
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
1024
            'filter' => $filterString,
1025
            'include' => $includeString,
1026
            'page' => $page,
1027
            'pageSize' => $limit,
1028
            'data' => $this->applyScheduledFixes(
1029
                $this->getRawResult($completeStatement),
1030
                $currentUser,
1031
                $language,
1032
                $meta
1033
            ),
1034
            'meta' => $meta
1035
        ];
1036
    }
1037
1038
    private function initMetaArray($modelPathOffset = '', $language = '')
1039
    {
1040
        return [
1041
            'modelnameIndex' => [
1042
                $this->getModelForMeta() => [$modelPathOffset]
1043
            ],
1044
            'language' => $language
1045
        ];
1046
    }
1047
1048
    /**
1049
     * @param string $language
1050
     * @param string $filterString
1051
     * @param string $includeString
1052
     * @param string $orderString
1053
     * @param int $limit
1054
     * @param int $page
1055
     * @param string $filterMode
1056
     * @return array
1057
     */
1058
    public function findMultiple(
1059
        $language = '',
1060
        $filterString = '',
1061
        $includeString = '',
1062
        $orderString = '',
1063
        $limit = 0,
1064
        $page = 1,
1065
        $filterMode = 'AND'
1066
    ) {
1067
        return json_decode(json_encode($this->findMultipleForApi(
1068
            $this->getCurrentlyAuthenticatedUser(),
1069
            $language,
1070
            $filterString,
1071
            $includeString,
1072
            $orderString,
1073
            $limit,
1074
            $page,
1075
            $filterMode
1076
        )), true);
1077
    }
1078
1079
    /**
1080
     * @param \UnserAller_Model_User $currentUser
1081
     * @param string $language
1082
     * @param string $filterString
1083
     * @param string $includeString
1084
     * @param string $orderString
1085
     * @param int $limit
1086
     * @param string $filterMode
1087
     * @return \Generator
1088
     */
1089
    public function batchFindMultiple(
1090
        $currentUser,
1091
        $language = '',
1092
        $filterString = '',
1093
        $includeString = '',
1094
        $orderString = '',
1095
        $limit = 500,
1096
        $filterMode = 'AND'
1097
    ) {
1098
        $page = 1;
1099
1100
        $result = $this->findMultipleForApi(
1101
            $currentUser,
1102
            $language,
1103
            $filterString,
1104
            $includeString,
1105
            $orderString,
1106
            $limit,
1107
            $page,
1108
            $filterMode
1109
        );
1110
1111
        yield $result;
1112
1113
        $totalPages = $result['totalPages'];
1114
        unset($result);
1115
        $page++;
1116
        while ($page <= $totalPages) {
1117
            $result = $this->findMultipleForApi(
1118
                $currentUser,
1119
                $language,
1120
                $filterString,
1121
                $includeString,
1122
                $orderString,
1123
                $limit,
1124
                $page,
1125
                $filterMode
1126
            );
1127
            yield $result;
1128
            $page++;
1129
            unset($result);
1130
        }
1131
    }
1132
1133
    /**
1134
     * @param \UnserAller_Model_User $currentUser
1135
     * @param string $language
1136
     * @param string $filterString
1137
     * @param string $includeString
1138
     * @param string $orderString
1139
     * @param int $limit
1140
     * @param int $page
1141
     * @param string $filterMode
1142
     * @return array
1143
     */
1144
    public function getNativeSqlIngredientsForFindMultiple(
1145
        $currentUser,
1146
        $language = '',
1147
        $filterString = '',
1148
        $includeString = '',
1149
        $orderString = '',
1150
        $limit = 0,
1151
        $page = 1,
1152
        $filterMode = 'AND'
1153
    ) {
1154
        if ($page <= 0) {
1155
            $page = 1;
1156
        }
1157
1158
        $incompleteStatement = $this->createIncompleteStatement($currentUser, $filterString, $language, $filterMode);
1159
1160
        $completeStatement = $this->completeStatement(
1161
            $currentUser,
1162
            $language,
1163
            $incompleteStatement,
1164
            $includeString,
1165
            $orderString
1166
        );
1167
        if ($limit > 0) {
1168
            $completeStatement
1169
                ->setFirstResult(($page - 1) * $limit)
1170
                ->setMaxResults($limit);
1171
        }
1172
1173
        return $this->getNativeSqlIngredients($completeStatement->getQuery());
1174
    }
1175
1176
    /**
1177
     * @param \Doctrine\ORM\Query $query
1178
     * @return array
1179
     */
1180
    private function getNativeSqlIngredients($query)
1181
    {
1182
        $sql = $query->getSQL();
1183
        $c = new \ReflectionClass('Doctrine\ORM\Query');
1184
        $parser = $c->getProperty('_parserResult');
1185
        $parser->setAccessible(true);
1186
        /** @var \ReflectionProperty $parser */
1187
        $parser = $parser->getValue($query);
1188
        /** @var \Doctrine\ORM\Query\ParserResult $parser */
1189
        $resultSet = $parser->getResultSetMapping();
1190
1191
        // Change the aliases back to what was originally specified in the QueryBuilder.
1192
        $sql = preg_replace_callback('/AS\s([a-zA-Z0-9_]+)/', function ($matches) use ($resultSet) {
1193
            $ret = 'AS ';
1194
            if ($resultSet->isScalarResult($matches[1])) {
1195
                $ret .= $resultSet->getScalarAlias($matches[1]);
1196
            } else {
1197
                $ret .= $matches[1];
1198
            }
1199
            return $ret;
1200
        }, $sql);
1201
        $m = $c->getMethod('processParameterMappings');
1202
        $m->setAccessible(true);
1203
        list($params, $types) = $m->invoke($query, $parser->getParameterMappings());
1204
        return [$sql, $params, $types];
1205
    }
1206
1207
    /**
1208
     * @param \UnserAller_Model_User $currentUser
1209
     * @param string $language
1210
     * @param string $filterString
1211
     * @param string $includeString
1212
     * @param string $orderString
1213
     * @return array|null
1214
     */
1215
    public function findOneForApi(
1216
        $currentUser,
1217
        $language = '',
1218
        $filterString = '',
1219
        $includeString = '',
1220
        $orderString = ''
1221
    ) {
1222
        return $this->createSingleResult($currentUser, $language, $filterString, $includeString, $orderString);
1223
    }
1224
1225
    /**
1226
     * @param string $language
1227
     * @param string $filterString
1228
     * @param string $includeString
1229
     * @param string $orderString
1230
     * @return array|null
1231
     */
1232
    public function findOne($language = '', $filterString = '', $includeString = '', $orderString = '')
1233
    {
1234
        return json_decode(json_encode($this->findOneForApi(
1235
            $this->getCurrentlyAuthenticatedUser(),
1236
            $language,
1237
            $filterString,
1238
            $includeString,
1239
            $orderString
1240
        )), true);
1241
    }
1242
1243
    /**
1244
     * @param \UnserAller_Model_User $currentUser
1245
     * @param int $id
1246
     * @param string $language
1247
     * @param string $include
1248
     * @return array|null
1249
     */
1250
    public function findForApi($currentUser, $id, $language = '', $include)
1251
    {
1252
        $this->id = (int)$id;
1253
        return $this->findOneForApi($currentUser, $language, "id:is($id)", $include, '');
1254
    }
1255
1256
    /**
1257
     * @param int $id
1258
     * @param string $language
1259
     * @param string $include
1260
     * @return array|null
1261
     */
1262
    public function find($id, $language = '', $include = '')
1263
    {
1264
        return json_decode(json_encode($this->findForApi(
1265
            $this->getCurrentlyAuthenticatedUser(),
1266
            $id,
1267
            $language,
1268
            $include
1269
        )), true);
1270
    }
1271
1272
    /**
1273
     * @return null|\UnserAller_Model_User|object
1274
     */
1275
    private function getCurrentlyAuthenticatedUser()
1276
    {
1277
        return $this->getEntityManager()->find(
1278
            \UnserAller_Model_User::class,
1279
            (int)\Zend_Auth::getInstance()->getIdentity()
1280
        );
1281
    }
1282
1283
    /**
1284
     * @param \UnserAller_Model_User $currentUser
1285
     * @param string $language
1286
     * @param string $filterString
1287
     * @param string $includeString
1288
     * @param string $orderString
1289
     * @return array|null
1290
     */
1291
    protected function createSingleResult($currentUser, $language = '', $filterString, $includeString, $orderString)
1292
    {
1293
        $meta = $this->initMetaArray('', $language);
1294
1295
        $result = $this->getRawResult(
1296
            $this->completeStatement(
1297
                $currentUser,
1298
                $language,
1299
                $this->createIncompleteStatement($currentUser, $filterString, $language, 'AND', $meta),
1300
                $includeString,
1301
                $orderString,
1302
                $meta
1303
            )->setFirstResult(0)->setMaxResults(1)
1304
        );
1305
1306
        if (!isset($result[0])) {
1307
            return null;
1308
        }
1309
1310
        return $this->applyScheduledFixes($result, $currentUser, $language, $meta)[0] + ['meta' => $meta];
1311
    }
1312
1313
    protected function getDefaultPostProcessDirections()
1314
    {
1315
        return [];
1316
    }
1317
1318
    /**
1319
     * Adds the default select statement, all includes and order statements to the incomplete statement
1320
     * and returns the qurey builder instance
1321
     *
1322
     * @param \UnserAller_Model_User $currentUser
1323
     * @param $language
1324
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
1325
     * @param string $includeString
1326
     * @param string $orderString
1327
     * @param array $meta
1328
     * @return \Doctrine\ORM\QueryBuilder
1329
     */
1330
    private function completeStatement(
1331
        $currentUser,
1332
        $language,
1333
        $incompleteStatement,
1334
        $includeString,
1335
        $orderString,
1336
        &$meta = []
1337
    ) {
1338
        $statement = clone $incompleteStatement;
1339
1340
        $this->schedulePostProcessingDirections($this->getDefaultPostProcessDirections());
1341
1342
        return $this->addOrderStatements(
1343
            $this->addIncludeStatements(
1344
                $statement->select($this->getDefaultSelectStatement($statement)),
1345
                $currentUser,
1346
                $language,
1347
                $includeString,
1348
                $meta
1349
            ),
1350
            $currentUser,
1351
            $orderString
1352
        );
1353
    }
1354
1355
    /**
1356
     * Returns the default select statement. In this case it just returns the first root entity which means the
1357
     * entire root entity will be selected
1358
     *
1359
     * @param \Doctrine\ORM\QueryBuilder $query
1360
     * @return string
1361
     */
1362
    protected function getDefaultSelectStatement($query)
1363
    {
1364
        return 'DISTINCT ' . $this->getRootAlias($query);
1365
    }
1366
1367
    /**
1368
     * Returns first root alias from query builder
1369
     *
1370
     * @param \Doctrine\ORM\QueryBuilder $query
1371
     * @return string
1372
     */
1373
    protected function getRootAlias($query)
1374
    {
1375
        return array_shift($query->getRootAliases());
0 ignored issues
show
Bug introduced by
$query->getRootAliases() cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
1376
    }
1377
1378
    /**
1379
     * Returns true if result item has an additional layer in the hierarchy because of custom subselects
1380
     *
1381
     * @param array $item
1382
     * @return bool
1383
     */
1384
    private function mustFlattenResultItem($item)
1385
    {
1386
        return isset($item[0]);
1387
    }
1388
1389
    /**
1390
     * Returns doctrine array results with all fixes applied
1391
     *
1392
     * @param \Doctrine\ORM\QueryBuilder $statement
1393
     * @return array
1394
     */
1395
    private function getRawResult($statement)
1396
    {
1397
        //Output raw sql here if you like to debug hard
1398
        //echo $statement->getQuery()->getSQL(); die;
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1399
        return $statement->getQuery()->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
1400
    }
1401
1402
    /**
1403
     * Doctrine will create an additional result layer when values are selected that do not belong
1404
     * to the doctrine object model. This function removes this additional layer and merges custom values with
1405
     * the doctrine object model for a single result item - not the whole result array.
1406
     *
1407
     * @param array $item
1408
     * @return array
1409
     */
1410
    private function flattenResultItem($item)
1411
    {
1412
        if (!$this->mustFlattenResultItem($item)) {
1413
            return $item;
1414
        }
1415
1416
        return array_merge(array_shift($item), $item);
1417
    }
1418
1419
    /**
1420
     * Parses $string for orderBy statements and returns an array where order statements are values.
1421
     *
1422
     * When string is "latestFirst, longestFirst" result would be: ['latestFirst', 'longestFirst']
1423
     *
1424
     * @param string $string
1425
     * @return array
1426
     */
1427
    protected function parseOrderString($string)
1428
    {
1429
        return array_filter(array_map('trim', explode(',', $string)));
1430
    }
1431
1432
    /**
1433
     * Executes all operations that were scheduled for post processing
1434
     *
1435
     * @param array $result
1436
     * @param \UnserAller_Model_User $currentUser
1437
     * @param $language
1438
     * @param array $meta
1439
     * @return array
1440
     */
1441
    private function applyScheduledFixes($result, $currentUser, $language, &$meta = [])
1442
    {
1443
        $scheduledFixes = $this->flushResultArrayFixSchedule();
1444
1445
        if (!$result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1446
            return $result;
1447
        }
1448
1449
        $numberOfResults = count($result);
1450
1451
        $this->applyFixesToItem(
1452
            $result[0],
1453
            $scheduledFixes,
1454
            $currentUser,
1455
            $meta,
1456
            'retrieveNestedCollectionAndMergeMeta',
1457
            $language
1458
        );
1459
        for ($i = 1; $i < $numberOfResults; $i++) {
1460
            $this->applyFixesToItem(
1461
                $result[$i],
1462
                $scheduledFixes,
1463
                $currentUser,
1464
                $meta,
1465
                'retrieveNestedCollection',
1466
                $language
1467
            );
1468
        }
1469
1470
        return $result;
1471
    }
1472
1473
    private function retrieveNestedCollectionResult($value, $nestingOptions, $language = '')
1474
    {
1475
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1476
        list($filterString, $includeString, $orderString, $limit, $page, $filterMode) = $this->parseAdditionalIncludeParams($additionalParams);
1477
1478
        if ($filterString) {
1479
            $filterString = $filterString . ',';
1480
        }
1481
1482
        if (is_array($value)) {
1483
            $filterFunctionString = vsprintf($filterFunction, array_merge($value));
1484
        } else {
1485
            $filterFunctionString = sprintf($filterFunction, $value);
1486
        }
1487
1488
        return $this->getAdapter($model)->findMultipleForApi(
1489
            $currentUser,
1490
            $language,
1491
            $filterString . $filterFunctionString,
1492
            $includeString,
1493
            $orderString,
1494
            $limit,
1495
            $page,
1496
            $filterMode
1497
        );
1498
    }
1499
1500
    private function retrieveNestedCollection($value, $nestingOptions, $language, $finalPath, $meta)
1501
    {
1502
        return $this->retrieveNestedCollectionResult($value, $nestingOptions, $language)['data'];
1503
    }
1504
1505
    private function retrieveNestedCollectionAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1506
    {
1507
        $result = $this->retrieveNestedCollectionResult($value, $nestingOptions, $language);
1508
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1509
        return $result['data'];
1510
    }
1511
1512
    private function retrieveNestedSingleResult($value, $nestingOptions, $language = '')
1513
    {
1514
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1515
        list($filterString, $includeString, $orderString, , ,) = $this->parseAdditionalIncludeParams($additionalParams);
1516
1517
        if ($filterString) {
1518
            $filterString = $filterString . ',';
1519
        }
1520
1521
        return $this->getAdapter($model)->findOneForApi(
1522
            $currentUser,
1523
            $language,
1524
            $filterString . sprintf($filterFunction, $value),
1525
            $includeString,
1526
            $orderString
1527
        );
1528
    }
1529
1530
    private function retrieveNestedSingleAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1531
    {
1532
        $result = $this->retrieveNestedSingleResult($value, $nestingOptions, $language);
1533
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1534
        unset($result['meta']);
1535
        return $result;
1536
    }
1537
1538
    private function mergeNestedMeta(&$meta, $nestedMeta, $includeName)
1539
    {
1540
        foreach ($nestedMeta['modelnameIndex'] as $model => $paths) {
1541
            foreach ($paths as $path) {
1542
                $fullPath = $includeName . '.' . $path;
1543
                if ($path && !in_array($fullPath, $meta['modelnameIndex'][$model])) {
1544
                    $meta['modelnameIndex'][$model][] = $fullPath;
1545
                }
1546
            }
1547
        }
1548
    }
1549
1550
    /**
1551
     * @param $item
1552
     * @param $scheduledFixes
1553
     * @param $currentUser
1554
     * @param $meta
1555
     * @param $collectionNestingMethod
1556
     * @param $language
1557
     * @uses retrieveNestedCollection
1558
     * @uses retrieveNestedCollectionAndMergeMeta
1559
     */
1560
    private function applyFixesToItem(
1561
        &$item,
1562
        $scheduledFixes,
1563
        $currentUser,
1564
        &$meta,
1565
        $collectionNestingMethod,
1566
        $language
1567
    ) {
1568
        $item = $this->flattenResultItem($item);
1569
1570
        //Operations can depend on a prop that must be deleted and doing it too early causes errors, collect them and delete at the end
1571
        $scheduledDeletions = [];
1572
1573
        foreach ($scheduledFixes as $path => $fix) {
1574
            if (isset($fix['delete'])) {
1575
                $scheduledDeletions[$path] = $fix;
1576
                continue;
1577
            }
1578
1579
            if (isset($fix['cast'])) {
1580
                \UnserAllerLib_Tool_Array::castNestedValue($item, $path, $fix['cast']);
1581
            }
1582
1583
            $value = \UnserAllerLib_Tool_Array::readNestedValue($item, $path);
1584
1585
            if (isset($fix['additionalFilterValues'])) {
1586
                $value = [$value];
1587
1588
                foreach ($fix['additionalFilterValues'] as $additionalFilterValue) {
1589
                    $value[] = \UnserAllerLib_Tool_Array::readNestedValue($item, $additionalFilterValue);
1590
                }
1591
            }
1592
1593 View Code Duplication
            if (isset($fix['nestCollection'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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.

Loading history...
1594
                $value = $this->$collectionNestingMethod($value, $fix['nestCollection'], $language,
1595
                    $fix['move'] ? $fix['move'] : $path, $meta);
1596
            }
1597
1598 View Code Duplication
            if (isset($fix['nestSingle'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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.

Loading history...
1599
                $value = $this->retrieveNestedSingleAndMergeMeta(
1600
                    $value,
1601
                    $fix['nestSingle'],
1602
                    $language,
1603
                    $fix['move'] ? $fix['move'] : $path,
1604
                    $meta
1605
                );
1606
            }
1607
1608
            if (isset($fix['filter'])) {
1609
                $value = $this->filterValue($fix['filter'], $value, $currentUser);
1610
            }
1611
1612
            if (isset($fix['cFilter'])) {
1613
                $value = $this->filterValue($fix['cFilter'], $value, $currentUser);
1614
            }
1615
1616
            if (isset($fix['mFilter'])) {
1617
                $value = $this->filterValue($fix['mFilter'], $item, $currentUser);
1618
            }
1619
1620
            if (isset($fix['move'])) {
1621
                \UnserAllerLib_Tool_Array::integrateNestedValue($item, $fix['move'], $value);
1622
                if ($path != $fix['move']) {
1623
                    $scheduledDeletions[$path] = ['delete' => 1];
1624
                }
1625
            }
1626
        }
1627
1628
        foreach ($scheduledDeletions as $path => $fix) {
1629
            \UnserAllerLib_Tool_Array::unsetNestedValue($item, $path);
1630
        }
1631
    }
1632
1633
    /**
1634
     * Applies filter methods for $filterName to $value
1635
     * @uses filterJsonAfterwards
1636
     * @uses filterJsonIfNullSetEmptyObjectAfterwards
1637
     * @uses filterJsonOrNullAfterwards
1638
     * @uses filterDatetimeAfterwards
1639
     * @uses filterDatetimeOrNullAfterwards
1640
     * @uses filterIntOrNullAfterwards
1641
     * @uses filterNl2BrAfterwards
1642
     * @param string $filterName
1643
     * @param mixed $value
1644
     * @param $currentUser
1645
     * @return mixed
1646
     */
1647
    private function filterValue($filterName, $value, $currentUser)
1648
    {
1649
        if (!is_callable([$this, $filterName])) {
1650
            throw new \InvalidArgumentException('Post Processing Filter method not found: ' . $filterName);
1651
        }
1652
1653
        return call_user_func_array([$this, $filterName], [$value, $currentUser]);
1654
    }
1655
1656
    /**
1657
     * @param $field
1658
     * @param \Doctrine\ORM\QueryBuilder $query
1659
     * @param string $alias
1660
     * @param \UnserAller_Model_User $currentUser
1661
     * @param array $methods
1662
     * @return \Doctrine\ORM\Query\Expr\Andx
1663
     * @uses stringContainExpression
1664
     * @uses stringContainsExpression
1665
     * @uses stringIsExpression
1666
     * @uses stringNotExpression
1667
     * @uses stringFalseExpression
1668
     * @uses stringTrueExpression
1669
     */
1670
    protected function createConditionsForStringColumn($field, $query, $alias, $currentUser, $methods)
1671
    {
1672
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1673
            $methods,
1674
            ['contain', 'contains', 'is', 'not', 'false', 'true']
1675
        )
1676
        ) {
1677
            throw new \InvalidArgumentException('Invalid expression methods used');
1678
        }
1679
1680
        return $this->createExpression('string', $field, $query, $alias, $currentUser, $methods);
1681
    }
1682
1683
    /**
1684
     * @param \Doctrine\ORM\QueryBuilder $query
1685
     * @param $fallbackField
1686
     * @param $translationName
1687
     * @param $language
1688
     * @param string $alias
1689
     * @param \UnserAller_Model_User $currentUser
1690
     * @param $additionalParams
1691
     * @return \Doctrine\ORM\Query\Expr\Composite
1692
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidFilter
1693
     */
1694
    protected function createConditionsForMultilanguageStringColumn(
1695
        $query,
1696
        $fallbackField,
1697
        $translationName,
1698
        $language,
1699
        $alias,
1700
        $currentUser,
1701
        $additionalParams
1702
    ) {
1703
        if (isset($additionalParams['overAllTranslations'])) {
1704
            if (!$this->supportedLanguages) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->supportedLanguages of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1705
                throw new \UnserAllerLib_Api_V4_Exception_InvalidFilter('Supported languages are not set');
1706
            }
1707
1708
            unset($additionalParams['overAllTranslations']);
1709
1710
            $expr = $query->expr()->orX();
1711
            foreach ($this->supportedLanguages as $supportedLanguage) {
1712
                $expr->add($this->createConditionsForStringColumn(
1713
                    "COALESCE(" . $this->joinTranslationOnce(
1714
                        $query,
1715
                        $translationName,
1716
                        $supportedLanguage
1717
                    ) . ".translation, $fallbackField)",
1718
                    $query,
1719
                    $alias,
1720
                    $currentUser,
1721
                    $additionalParams
1722
                ));
1723
            }
1724
1725
            return $expr;
1726
        }
1727
1728
        return $this->createConditionsForStringColumn(
1729
            "COALESCE(" . $this->joinTranslationOnce(
1730
                $query,
1731
                $translationName,
1732
                $language
1733
            ) . ".translation, $fallbackField)",
1734
            $query,
1735
            $alias,
1736
            $currentUser,
1737
            $additionalParams
1738
        );
1739
    }
1740
1741
1742
    /**
1743
     * @param $field
1744
     * @param \Doctrine\ORM\QueryBuilder $query
1745
     * @param string $alias
1746
     * @param \UnserAller_Model_User $currentUser
1747
     * @param array $methods
1748
     * @return \Doctrine\ORM\Query\Expr\Andx
1749
     * @uses dateGtExpression
1750
     * @uses dateGteExpression
1751
     * @uses dateLtExpression
1752
     * @uses dateLteExpression
1753
     * @uses dateFalseExpression
1754
     * @uses dateTrueExpression
1755
     * @uses dateIsExpression
1756
     * @uses dateNotExpression
1757
     */
1758 View Code Duplication
    protected function createConditionsForDatetimeColumn($field, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1759
    {
1760
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1761
            $methods,
1762
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true']
1763
        )
1764
        ) {
1765
            throw new \InvalidArgumentException('Invalid expression methods used');
1766
        }
1767
1768
        return $this->createExpression('date', $field, $query, $alias, $currentUser, $methods);
1769
    }
1770
1771
    /**
1772
     * @param $field
1773
     * @param \Doctrine\ORM\QueryBuilder $query
1774
     * @param string $alias
1775
     * @param \UnserAller_Model_User $currentUser
1776
     * @param array $methods
1777
     * @return \Doctrine\ORM\Query\Expr\Andx
1778
     * @uses integerFalseExpression
1779
     * @uses integerTrueExpression
1780
     * @uses integerIsExpression
1781
     * @uses integerNotExpression
1782
     * @uses integerMeExpression
1783
     * @uses integerNotmeExpression
1784
     */
1785
    protected function createConditionsForEntityColumn($field, $query, $alias, $currentUser, $methods)
1786
    {
1787
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true', 'is', 'not', 'me', 'notme'])) {
1788
            throw new \InvalidArgumentException('Invalid expression methods used');
1789
        }
1790
1791
        return $this->createExpression('integer', $field, $query, $alias, $currentUser, $methods);
1792
    }
1793
1794
    /**
1795
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1796
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1797
     * on a subquery. And it seems to be impossible to not return null values either
1798
     * Todo: Needs research, for time being only true comparison is working as expected
1799
     *
1800
     *
1801
     * @param $subquery
1802
     * @param \Doctrine\ORM\QueryBuilder $query
1803
     * @param string $alias
1804
     * @param \UnserAller_Model_User $currentUser
1805
     * @param array $methods
1806
     * @return \Doctrine\ORM\Query\Expr\Andx
1807
     * @uses subqueryFalseExpression
1808
     * @uses subqueryTrueExpression
1809
     * @uses subqueryGtExpression
1810
     * @uses subqueryGteExpression
1811
     * @uses subqueryLtExpression
1812
     * @uses subqueryLteExpression
1813
     * @uses subqueryEqExpression
1814
     * @uses subqueryAnyExpression
1815
     * @uses subqueryNullExpression
1816
     */
1817 View Code Duplication
    protected function createConditionsForIntegerSubquery($subquery, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1818
    {
1819
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1820
            $methods,
1821
            ['false', 'true', 'gt', 'gte', 'lt', 'lte', 'eq', 'any', 'null']
1822
        )
1823
        ) {
1824
            throw new \InvalidArgumentException('Invalid expression methods used');
1825
        }
1826
1827
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1828
    }
1829
1830
    /**
1831
     * @param $subquery
1832
     * @param \Doctrine\ORM\QueryBuilder $query
1833
     * @param string $alias
1834
     * @param \UnserAller_Model_User $currentUser
1835
     * @param array $methods
1836
     * @return \Doctrine\ORM\Query\Expr\Andx
1837
     */
1838 View Code Duplication
    protected function createConditionsForIntegerCollectionSubquery($subquery, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1839
    {
1840
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
1841
            throw new \InvalidArgumentException('Invalid expression methods used');
1842
        }
1843
1844
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1845
    }
1846
1847
    /**
1848
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1849
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1850
     * on a subquery. And it seems to be impossible to not return null values either
1851
     * Todo: Needs research, for time being only true comparison is working as expected
1852
     *
1853
     *
1854
     * @param $subquery
1855
     * @param \Doctrine\ORM\QueryBuilder $query
1856
     * @param string $alias
1857
     * @param \UnserAller_Model_User $currentUser
1858
     * @param array $methods
1859
     * @return \Doctrine\ORM\Query\Expr\Andx
1860
     * @uses subqueryAnyisExpression
1861
     */
1862 View Code Duplication
    protected function createConditionsForStringCollectionSubquery($subquery, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1863
    {
1864
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
1865
            throw new \InvalidArgumentException('Invalid expression methods used');
1866
        }
1867
1868
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1869
    }
1870
1871
    /**
1872
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1873
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1874
     * on a subquery. And it seems to be impossible to not return null values either
1875
     * Todo: Needs research, for time being only true comparison is working as expected
1876
     *
1877
     *
1878
     * @param $subquery
1879
     * @param \Doctrine\ORM\QueryBuilder $query
1880
     * @param string $alias
1881
     * @param \UnserAller_Model_User $currentUser
1882
     * @param array $methods
1883
     * @return \Doctrine\ORM\Query\Expr\Andx
1884
     * @uses subqueryTrueExpression
1885
     * @uses subqueryFalseExpression
1886
     */
1887 View Code Duplication
    protected function createConditionsForDatetimeSubquery($subquery, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1888
    {
1889
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true'])) {
1890
            throw new \InvalidArgumentException('Invalid expression methods used');
1891
        }
1892
1893
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1894
    }
1895
1896
    /**
1897
     * Translates params into where conditions. Null values are handled as you would expect it.
1898
     *
1899
     * @param $col
1900
     * @param \Doctrine\ORM\QueryBuilder $query
1901
     * @param string $alias
1902
     * @param \UnserAller_Model_User $currentUser
1903
     * @param array $methods
1904
     * @return \Doctrine\ORM\Query\Expr\Andx
1905
     * @uses integerIsExpression
1906
     * @uses integerNotExpression
1907
     * @uses integerGtExpression
1908
     * @uses integerGteExpression
1909
     * @uses integerLtExpression
1910
     * @uses integerLteExpression
1911
     * @uses integerFalseExpression
1912
     * @uses integerTrueExpression
1913
     */
1914 View Code Duplication
    protected function createConditionsForIntegerColumn($col, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1915
    {
1916
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1917
            $methods,
1918
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true']
1919
        )
1920
        ) {
1921
            throw new \InvalidArgumentException('Invalid expression methods used');
1922
        }
1923
1924
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
1925
    }
1926
1927
    /**
1928
     * Todo: Whitelisting allowed subqueries for the any filter makes having this extra function unnecessary
1929
     *
1930
     * This one allows some filter directives that result to function calls on protected methods. Don't ever redirect
1931
     * user content here.
1932
     *
1933
     * Translates params into where conditions. Null values are handled as you would expect it.
1934
     *
1935
     * @param $col
1936
     * @param \Doctrine\ORM\QueryBuilder $query
1937
     * @param string $alias
1938
     * @param \UnserAller_Model_User $currentUser
1939
     * @param array $methods
1940
     * @return \Doctrine\ORM\Query\Expr\Andx
1941
     * @uses integerIsExpression
1942
     * @uses integerNotExpression
1943
     * @uses integerGtExpression
1944
     * @uses integerGteExpression
1945
     * @uses integerLtExpression
1946
     * @uses integerLteExpression
1947
     * @uses integerFalseExpression
1948
     * @uses integerTrueExpression
1949
     * @uses integerAnyExpression
1950
     */
1951 View Code Duplication
    protected function createConditionsForIntegerColumnInternal($col, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1952
    {
1953
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1954
            $methods,
1955
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true', 'any']
1956
        )
1957
        ) {
1958
            throw new \InvalidArgumentException('Invalid expression methods used');
1959
        }
1960
1961
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
1962
    }
1963
1964
    /**
1965
     * Knows how to create a callable from a subquery definition
1966
     *
1967
     * @param string $name of subquery
1968
     * @param mixed[] $params for subquerymethod
1969
     * @return callable
1970
     */
1971
    protected function locateCallableSubquery($name, $params)
1972
    {
1973
        return [$this, $name];
1974
    }
1975
1976
    /**
1977
     * @param array $subqueryDefinition
1978
     * @return string DQL
1979
     */
1980
    private function consumeSubquery($subqueryDefinition)
1981
    {
1982
        list($name, $params) = $subqueryDefinition;
1983
        return call_user_func_array(
1984
            $this->locateCallableSubquery($name, $params),
1985
            $params
1986
        );
1987
    }
1988
1989
    /**
1990
     * @param $prefix
1991
     * @param string $field
1992
     * @param \Doctrine\ORM\QueryBuilder $query
1993
     * @param string $alias
1994
     * @param \UnserAller_Model_User $currentUser
1995
     * @param array $methods
1996
     * @return \Doctrine\ORM\Query\Expr\Andx
1997
     */
1998
    private function createExpression($prefix, $field, $query, $alias, $currentUser, $methods)
1999
    {
2000
        $expression = $query->expr()->andX();
2001
        foreach ($methods as $method => $params) {
2002
            $expression->add(call_user_func_array(
2003
                [$this, $prefix . ucfirst($method) . 'Expression'],
2004
                [$query, $field, $params, $alias, $currentUser]
2005
            ));
2006
        }
2007
2008
        return $expression;
2009
    }
2010
2011
    /**
2012
     * @param \Doctrine\ORM\QueryBuilder $query
2013
     * @param array $field
2014
     * @param array $params
2015
     * @param string $alias
2016
     * @return mixed
2017
     */
2018
    private function subqueryFalseExpression($query, $field, $params, $alias)
2019
    {
2020
        return $query->expr()->orX(
2021
            $query->expr()->not($query->expr()->exists($this->consumeSubquery($field))),
2022
            $query->expr()->eq('(' . $this->consumeSubquery($field) . ')', 0)
2023
        );
2024
    }
2025
2026
    /**
2027
     * @param \Doctrine\ORM\QueryBuilder $query
2028
     * @param array $field
2029
     * @param array $params
2030
     * @param string $alias
2031
     * @return mixed
2032
     */
2033
    private function subqueryNullExpression($query, $field, $params, $alias)
2034
    {
2035
        return $query->expr()->not($query->expr()->exists($this->consumeSubquery($field)));
2036
    }
2037
2038
    /**
2039
     * @param \Doctrine\ORM\QueryBuilder $query
2040
     * @param array $subquery
2041
     * @param array $params
2042
     * @param string $alias
2043
     * @return mixed
2044
     */
2045 View Code Duplication
    private function subqueryTrueExpression($query, $subquery, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2046
    {
2047
        return $query->expr()->andX(
2048
            $query->expr()->exists($this->consumeSubquery($subquery)),
2049
            $query->expr()->neq('(' . $this->consumeSubquery($subquery) . ')', 0)
2050
        );
2051
    }
2052
2053
    /**
2054
     * @param \Doctrine\ORM\QueryBuilder $query
2055
     * @param array $subquery
2056
     * @param array $params
2057
     * @param string $alias
2058
     * @return mixed
2059
     */
2060
    private function subqueryAnyisExpression($query, $subquery, $params, $alias)
2061
    {
2062
        $expression = $query->expr()->orX();
2063
        foreach ($params as $param) {
2064
            $alias = uniqid();
2065
            $query->setParameter("param$alias", $param);
2066
            $expression->add(
2067
                $query->expr()->eq(":param$alias", $query->expr()->any($this->consumeSubquery($subquery)))
2068
            );
2069
        }
2070
        return $expression;
2071
    }
2072
2073
    /**
2074
     * @param \Doctrine\ORM\QueryBuilder $query
2075
     * @param array $subquery
2076
     * @param array $params
2077
     * @param string $alias
2078
     * @return mixed
2079
     */
2080 View Code Duplication
    private function subqueryGtExpression($query, $subquery, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2081
    {
2082
        return $query->expr()->andX(
2083
            $query->expr()->exists($this->consumeSubquery($subquery)),
2084
            $query->expr()->gt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2085
        );
2086
    }
2087
2088
    /**
2089
     * @param \Doctrine\ORM\QueryBuilder $query
2090
     * @param array $subquery
2091
     * @param array $params
2092
     * @param string $alias
2093
     * @return mixed
2094
     */
2095 View Code Duplication
    private function subqueryGteExpression($query, $subquery, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2096
    {
2097
        return $query->expr()->andX(
2098
            $query->expr()->exists($this->consumeSubquery($subquery)),
2099
            $query->expr()->gte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2100
        );
2101
    }
2102
2103
    /**
2104
     * @param \Doctrine\ORM\QueryBuilder $query
2105
     * @param array $subquery
2106
     * @param array $params
2107
     * @param string $alias
2108
     * @return mixed
2109
     */
2110 View Code Duplication
    private function subqueryLteExpression($query, $subquery, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2111
    {
2112
        return $query->expr()->andX(
2113
            $query->expr()->exists($this->consumeSubquery($subquery)),
2114
            $query->expr()->lte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2115
        );
2116
    }
2117
2118
    /**
2119
     * @param \Doctrine\ORM\QueryBuilder $query
2120
     * @param array $subquery
2121
     * @param array $params
2122
     * @param string $alias
2123
     * @return mixed
2124
     */
2125 View Code Duplication
    private function subqueryLtExpression($query, $subquery, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2126
    {
2127
        return $query->expr()->andX(
2128
            $query->expr()->exists($this->consumeSubquery($subquery)),
2129
            $query->expr()->lt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2130
        );
2131
    }
2132
2133
    /**
2134
     * @param \Doctrine\ORM\QueryBuilder $query
2135
     * @param array $subquery
2136
     * @param array $params
2137
     * @param string $alias
2138
     * @return mixed
2139
     */
2140 View Code Duplication
    private function subqueryEqExpression($query, $subquery, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2141
    {
2142
        return $query->expr()->andX(
2143
            $query->expr()->exists($this->consumeSubquery($subquery)),
2144
            $query->expr()->eq('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2145
        );
2146
    }
2147
2148
    /**
2149
     * @param \Doctrine\ORM\QueryBuilder $query
2150
     * @param string $field
2151
     * @param array $params
2152
     * @param string $alias
2153
     * @return mixed
2154
     */
2155
    private function dateIsExpression($query, $field, $params, $alias)
2156
    {
2157
        return $query->expr()->in($field, $params);
2158
    }
2159
2160
    /**
2161
     * @param \Doctrine\ORM\QueryBuilder $query
2162
     * @param string $field
2163
     * @param array $params
2164
     * @param string $alias
2165
     * @return mixed
2166
     */
2167
    private function integerIsExpression($query, $field, $params, $alias)
2168
    {
2169
        return $query->expr()->in($field, $params);
2170
    }
2171
2172
    /**
2173
     * @param \Doctrine\ORM\QueryBuilder $query
2174
     * @param string $field
2175
     * @param array $params
2176
     * @param string $alias
2177
     * @return mixed
2178
     */
2179
    private function stringIsExpression($query, $field, $params, $alias)
2180
    {
2181
        return $query->expr()->in($field, $params);
2182
    }
2183
2184
    /**
2185
     * @param \Doctrine\ORM\QueryBuilder $query
2186
     * @param string $field
2187
     * @param array $params
2188
     * @param string $alias
2189
     * @param \UnserAller_Model_User $currentUser
2190
     * @return mixed
2191
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2192
     */
2193
    private function integerMeExpression($query, $field, $params, $alias, $currentUser)
2194
    {
2195
        if (!$currentUser) {
2196
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
2197
        }
2198
        return $query->expr()->eq($field, $currentUser->getId());
2199
    }
2200
2201
    /**
2202
     * @param \Doctrine\ORM\QueryBuilder $query
2203
     * @param string $field
2204
     * @param array $params
2205
     * @param string $alias
2206
     * @return \Doctrine\ORM\Query\Expr\Comparison
2207
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2208
     */
2209
    private function integerAnyExpression($query, $field, $params, $alias)
2210
    {
2211
        return $query->expr()->eq($field, $query->expr()->any($this->consumeSubquery($params)));
2212
    }
2213
2214
    /**
2215
     * @param \Doctrine\ORM\QueryBuilder $query
2216
     * @param string $field
2217
     * @param array $params
2218
     * @param string $alias
2219
     * @param \UnserAller_Model_User $currentUser
2220
     * @return \Doctrine\ORM\Query\Expr\Comparison
2221
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2222
     */
2223
    private function integerNotmeExpression($query, $field, $params, $alias, $currentUser)
2224
    {
2225
        if (!$currentUser) {
2226
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
2227
        }
2228
        return $query->expr()->neq($field, $currentUser->getId());
2229
    }
2230
2231
    /**
2232
     * @param \Doctrine\ORM\QueryBuilder $query
2233
     * @param string $field
2234
     * @param array $params
2235
     * @param string $alias
2236
     * @return \Doctrine\ORM\Query\Expr\Func
2237
     */
2238
    private function integerNotExpression($query, $field, $params, $alias)
2239
    {
2240
        return $query->expr()->notIn($field, $params);
2241
    }
2242
2243
    /**
2244
     * @param \Doctrine\ORM\QueryBuilder $query
2245
     * @param string $field
2246
     * @param array $params
2247
     * @param string $alias
2248
     * @return \Doctrine\ORM\Query\Expr\Func
2249
     */
2250
    private function dateNotExpression($query, $field, $params, $alias)
2251
    {
2252
        return $query->expr()->notIn($field, $params);
2253
    }
2254
2255
    /**
2256
     * @param \Doctrine\ORM\QueryBuilder $query
2257
     * @param string $field
2258
     * @param array $params
2259
     * @param string $alias
2260
     * @return mixed
2261
     */
2262
    private function stringNotExpression($query, $field, $params, $alias)
2263
    {
2264
        return $query->expr()->notIn($field, $params);
2265
    }
2266
2267
    /**
2268
     * @param \Doctrine\ORM\QueryBuilder $query
2269
     * @param string $field
2270
     * @param array $params
2271
     * @param string $alias
2272
     * @return \Doctrine\ORM\Query\Expr\Comparison
2273
     */
2274
    private function integerFalseExpression($query, $field, $params, $alias)
2275
    {
2276
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2277
    }
2278
2279
    /**
2280
     * @param \Doctrine\ORM\QueryBuilder $query
2281
     * @param string $field
2282
     * @param array $params
2283
     * @param string $alias
2284
     * @return \Doctrine\ORM\Query\Expr\Comparison
2285
     */
2286
    private function dateFalseExpression($query, $field, $params, $alias)
2287
    {
2288
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2289
    }
2290
2291
    /**
2292
     * @param \Doctrine\ORM\QueryBuilder $query
2293
     * @param string $field
2294
     * @param array $params
2295
     * @param string $alias
2296
     * @return \Doctrine\ORM\Query\Expr\Base
2297
     */
2298
    private function stringFalseExpression($query, $field, $params, $alias)
2299
    {
2300
        return $query->expr()->orX(
2301
            $query->expr()->isNull($field),
2302
            $query->expr()->eq($field, "''")
2303
        );
2304
    }
2305
2306
    /**
2307
     * @param \Doctrine\ORM\QueryBuilder $query
2308
     * @param string $field
2309
     * @param array $params
2310
     * @param string $alias
2311
     * @return \Doctrine\ORM\Query\Expr\Comparison
2312
     */
2313
    private function integerTrueExpression($query, $field, $params, $alias)
2314
    {
2315
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2316
    }
2317
2318
    /**
2319
     * @param \Doctrine\ORM\QueryBuilder $query
2320
     * @param string $field
2321
     * @param array $params
2322
     * @param string $alias
2323
     * @return \Doctrine\ORM\Query\Expr\Comparison
2324
     */
2325
    private function dateTrueExpression($query, $field, $params, $alias)
2326
    {
2327
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2328
    }
2329
2330
    /**
2331
     * @param \Doctrine\ORM\QueryBuilder $query
2332
     * @param string $field
2333
     * @param array $params
2334
     * @param string $alias
2335
     * @return \Doctrine\ORM\Query\Expr\Base
2336
     */
2337
    private function stringTrueExpression($query, $field, $params, $alias)
2338
    {
2339
        return $query->expr()->andX(
2340
            $query->expr()->isNotNull($field),
2341
            $query->expr()->neq($field, "''")
2342
        );
2343
    }
2344
2345
    /**
2346
     * @param \Doctrine\ORM\QueryBuilder $query
2347
     * @param string $field
2348
     * @param array $params
2349
     * @param string $alias
2350
     * @return mixed
2351
     */
2352 View Code Duplication
    private function stringContainsExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2353
    {
2354
        $contains = $query->expr()->orX();
2355
2356
        $index = 0;
2357
        foreach ($params as $string) {
2358
            $contains->add($query->expr()->like($field, ":contains_{$alias}_{$index}"));
2359
            $query->setParameter("contains_{$alias}_{$index}", '%' . $string . '%');
2360
            $index++;
2361
        }
2362
2363
        return $contains;
2364
    }
2365
2366
    /**
2367
     * @param \Doctrine\ORM\QueryBuilder $query
2368
     * @param string $field
2369
     * @param array $params
2370
     * @param string $alias
2371
     * @return mixed
2372
     */
2373
    private function stringContainExpression($query, $field, $params, $alias)
2374
    {
2375
        return $this->stringContainsExpression($query, $field, $params, $alias);
2376
    }
2377
2378
    /**
2379
     * @param \Doctrine\ORM\QueryBuilder $query
2380
     * @param string $field
2381
     * @param array $params
2382
     * @param string $alias
2383
     * @return mixed
2384
     */
2385 View Code Duplication
    private function dateLtExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2386
    {
2387
        $lt = $query->expr()->orX();
2388
        $index = 0;
2389
        foreach ($params as $datetime) {
2390
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2391
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2392
            $index++;
2393
        }
2394
2395
        return $lt;
2396
    }
2397
2398
    /**
2399
     * @param \Doctrine\ORM\QueryBuilder $query
2400
     * @param string $field
2401
     * @param array $params
2402
     * @param string $alias
2403
     * @return mixed
2404
     */
2405 View Code Duplication
    private function integerLtExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2406
    {
2407
        $lt = $query->expr()->orX();
2408
        $index = 0;
2409
        foreach ($params as $datetime) {
2410
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2411
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2412
            $index++;
2413
        }
2414
2415
        return $lt;
2416
    }
2417
2418
    /**
2419
     * @param \Doctrine\ORM\QueryBuilder $query
2420
     * @param string $field
2421
     * @param array $params
2422
     * @param string $alias
2423
     * @return mixed
2424
     */
2425 View Code Duplication
    private function integerLteExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2426
    {
2427
        $lte = $query->expr()->orX();
2428
        $index = 0;
2429
        foreach ($params as $datetime) {
2430
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2431
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2432
            $index++;
2433
        }
2434
2435
        return $lte;
2436
    }
2437
2438
    /**
2439
     * @param \Doctrine\ORM\QueryBuilder $query
2440
     * @param string $field
2441
     * @param array $params
2442
     * @param string $alias
2443
     * @return mixed
2444
     */
2445 View Code Duplication
    private function dateLteExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2446
    {
2447
        $lte = $query->expr()->orX();
2448
        $index = 0;
2449
        foreach ($params as $datetime) {
2450
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2451
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2452
            $index++;
2453
        }
2454
2455
        return $lte;
2456
    }
2457
2458
    /**
2459
     * @param \Doctrine\ORM\QueryBuilder $query
2460
     * @param string $field
2461
     * @param array $params
2462
     * @param string $alias
2463
     * @return mixed
2464
     */
2465 View Code Duplication
    private function dateGtExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2466
    {
2467
        $gt = $query->expr()->orX();
2468
        $index = 0;
2469
        foreach ($params as $datetime) {
2470
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2471
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2472
            $index++;
2473
        }
2474
2475
        return $gt;
2476
    }
2477
2478
    /**
2479
     * @param \Doctrine\ORM\QueryBuilder $query
2480
     * @param string $field
2481
     * @param array $params
2482
     * @param string $alias
2483
     * @return mixed
2484
     */
2485 View Code Duplication
    private function integerGtExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2486
    {
2487
        $gt = $query->expr()->orX();
2488
        $index = 0;
2489
        foreach ($params as $datetime) {
2490
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2491
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2492
            $index++;
2493
        }
2494
2495
        return $gt;
2496
    }
2497
2498
    /**
2499
     * @return string
2500
     */
2501
    protected function getModelForMeta()
2502
    {
2503
        return uniqid('UnknownClass');
2504
    }
2505
2506
    /**
2507
     * @return string
2508
     */
2509
    public function getClassnameForRepresentedModel()
2510
    {
2511
        return $this->getModelForMeta();
2512
    }
2513
2514
    /**
2515
     * @param \Doctrine\ORM\QueryBuilder $query
2516
     * @param string $field
2517
     * @param array $params
2518
     * @param string $alias
2519
     * @return mixed
2520
     */
2521 View Code Duplication
    private function integerGteExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2522
    {
2523
        $gte = $query->expr()->orX();
2524
        $index = 0;
2525
        foreach ($params as $datetime) {
2526
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2527
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2528
            $index++;
2529
        }
2530
2531
        return $gte;
2532
    }
2533
2534
    /**
2535
     * @param \Doctrine\ORM\QueryBuilder $query
2536
     * @param string $field
2537
     * @param array $params
2538
     * @param string $alias
2539
     * @return mixed
2540
     */
2541 View Code Duplication
    private function dateGteExpression($query, $field, $params, $alias)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
2542
    {
2543
        $gte = $query->expr()->orX();
2544
        $index = 0;
2545
        foreach ($params as $datetime) {
2546
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2547
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2548
            $index++;
2549
        }
2550
2551
        return $gte;
2552
    }
2553
2554
    /**
2555
     * Does some crazy things
2556
     *
2557
     * @param string $value
2558
     * @return array
2559
     */
2560
    private function filterJsonAfterwards($value)
2561
    {
2562
        return json_decode($value, true);
2563
    }
2564
2565
    /**
2566
     * Does some crazy things
2567
     *
2568
     * @param string $value
2569
     * @return mixed
2570
     */
2571
    private function filterJsonIfNullSetEmptyObjectAfterwards($value)
2572
    {
2573
        return $value === null ? new \stdClass() : json_decode($value, true);
2574
    }
2575
2576
    /**
2577
     * Does some crazy things
2578
     *
2579
     * @param string $value
2580
     * @return string
2581
     */
2582
    private function filterNl2BrAfterwards($value)
2583
    {
2584
        return nl2br($value, false);
2585
    }
2586
2587
    /**
2588
     * Does some crazy things
2589
     *
2590
     * @param string $value
2591
     * @return array
2592
     */
2593
    private function filterJsonOrNullAfterwards($value)
2594
    {
2595
        return $value === null ? null : json_decode($value, true);
2596
    }
2597
2598
    /**
2599
     * Too complex to explain
2600
     *
2601
     * @param string $value
2602
     * @return \DateTime
2603
     */
2604
    private function filterDatetimeAfterwards($value)
2605
    {
2606
        return new \DateTime($value);
2607
    }
2608
2609
    /**
2610
     * Too complex to explain
2611
     *
2612
     * @param string $value
2613
     * @return \DateTime
2614
     */
2615
    private function filterDatetimeOrNullAfterwards($value)
2616
    {
2617
        return $value === null ? null : new \DateTime($value);
2618
    }
2619
2620
    /**
2621
     * Too complex to explain
2622
     *
2623
     * @param string|null $value
2624
     * @return int|null
2625
     */
2626
    private function filterIntOrNullAfterwards($value)
2627
    {
2628
        return $value === null ? null : (int)$value;
2629
    }
2630
2631
    /**
2632
     * Returns the current resultArrayFixSchedule. Afterwards the schedule will be empty again.
2633
     *
2634
     * @return array
2635
     */
2636
    private function flushResultArrayFixSchedule()
2637
    {
2638
        $scheduledFixes = $this->resultArrayFixSchedule;
2639
        $this->resultArrayFixSchedule = [];
2640
        return $scheduledFixes;
2641
    }
2642
2643
    /**
2644
     * Returns true if $alias was used in $query already - false otherwise
2645
     *
2646
     * @param \Doctrine\ORM\QueryBuilder $query
2647
     * @param string $alias
2648
     * @return bool
2649
     */
2650
    protected function wasAliasUsed($query, $alias)
2651
    {
2652
        return in_array($alias, $query->getAllAliases());
2653
    }
2654
2655
    /**
2656
     * Returns true if $alias was used in $query already - false otherwise
2657
     *
2658
     * @param \Doctrine\ORM\QueryBuilder $query
2659
     * @param string $alias
2660
     * @return bool
2661
     */
2662
    protected function wasntAliasUsed($query, $alias)
2663
    {
2664
        return !$this->wasAliasUsed($query, $alias);
2665
    }
2666
2667
    /**
2668
     * @return array
2669
     */
2670
    public function getUnsortedParams()
2671
    {
2672
        return $this->unsortedParams;
2673
    }
2674
2675
    /**
2676
     * @param array $unsortedParams
2677
     * @return $this
2678
     */
2679
    public function setUnsortedParams($unsortedParams)
2680
    {
2681
        $this->unsortedParams = $unsortedParams;
2682
2683
        return $this;
2684
    }
2685
2686
    /**
2687
     * @param \Doctrine\ORM\QueryBuilder $query
2688
     * @param string $translationName
2689
     * @param string $language
2690
     * @return string alias of joined translation table
2691
     */
2692
    protected function joinTranslationOnce($query, $translationName, $language)
2693
    {
2694
        $alias = 'translation' . $translationName . $language;
2695
2696
        if ($this->wasAliasUsed($query, $alias)) {
2697
            return $alias;
2698
        }
2699
2700
        $rootAlias = $this->getRootAlias($query);
2701
2702
        $query->setParameter("name$alias", $translationName);
2703
        $query->setParameter("target$alias", $language);
2704
2705
        $query->leftJoin(
2706
            'UnserAller_Model_Translation',
2707
            $alias,
2708
            'WITH',
2709
            "$alias.name = CONCAT(:name$alias,$rootAlias.id) AND $alias.target = :target$alias"
2710
        );
2711
2712
        return $alias;
2713
    }
2714
2715
    /**
2716
     * @param \Doctrine\ORM\QueryBuilder $query
2717
     * @param string $alias
2718
     * @param string $col
2719
     * @param string $name
2720
     * @param string $translationName
2721
     * @param string $language
2722
     * @return array|void
2723
     */
2724
    protected function abstractIncludeMultilanguageStringColumn(
2725
        $query,
2726
        $alias,
2727
        $col,
2728
        $name,
2729
        $translationName,
2730
        $language
2731
    ) {
2732
        if (!$language) {
2733
            $query->addSelect("($col) $alias");
2734
        } else {
2735
            $query->addSelect("(COALESCE(" . $this->joinTranslationOnce(
2736
                $query,
2737
                $translationName,
2738
                $language
2739
            ) . ".translation,$col)) $alias");
2740
        }
2741
2742
        return [
2743
            $alias,
2744
            'move' => $name
2745
        ];
2746
    }
2747
2748
    protected function getAdditionalUserParamOrFail(&$additionalParams)
2749
    {
2750
        if (!isset($additionalParams['user'][0])) {
2751
            throw new \InvalidArgumentException('User identifier required but not given');
2752
        }
2753
2754
        $param = $additionalParams['user'];
2755
        unset($additionalParams['user']);
2756
        return \UnserAllerLib_Validate_Helper::integerOrFail($param[0], 1);
2757
    }
2758
}
2759