Completed
Push — master ( 6b52f2...a8ed33 )
by
unknown
07:49
created

Vortex::integerIsOrNullExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 4
crap 2
1
<?php
2
3
namespace Macroparts\Vortex;
4
5
use Doctrine\ORM\Query;
6
7
abstract class Vortex
8
{
9
    use \UnserAllerLib_Api_V4_AdapterProvider;
10
11
    const DIRECTIVE_FIELDS = 'fields';
12
    const META_ON_OBJECT_LEVEL_ENABLED = 1;
13
    const META_ON_OBJECT_LEVEL_DISABLED = 0;
14
15
    /**
16
     * @var \Doctrine\ORM\EntityManager
17
     */
18
    private $entityManager;
19
20
    /**
21
     * Defines data that can be included with an api call. You'd try to return only the most frequently used data
22
     * in default payload. Other available raw and aggregated data you'd make includable. Every availableInclude
23
     * has it's own include method and has to be mentioned in this configuration.
24
     *
25
     * Go to the Project Repository for an example configuration
26
     *
27
     * @var array
28
     */
29
    protected static $includeWhitelist = [];
30
    protected static $filterWhitelist = [];
31
    protected static $orderWhitelist = [];
32
33
    private $abstractFilterWhitelist = [
34
        'id' => '',
35
        'any' => '',
36
        'all' => ''
37
    ];
38
39
    /**
40
     * Used in the include configuration to tell if a property is a recursive inclusion or not
41
     */
42
    const INCLUDE_DIRECT = 0;
43
    const INCLUDE_RECURSIVE = 1;
44
45
    /**
46
     * Collects operations that have to be run on the doctrine result after query execution
47
     *
48
     * @var array
49
     */
50
    private $resultArrayFixSchedule = [];
51
52
    /**
53
     * Repositories can define a set of properties that are included by default
54
     *
55
     * @var array
56
     */
57
    protected static $defaultIncludes = [];
58
59
    /**
60
     * Repositories can define a default order which is taken by default
61
     *
62
     * @var array
63
     */
64
    protected static $defaultOrder = [];
65
66
    /**
67
     * If an ID is specified in the URL, it will be saved here for usage in child adapters.
68
     *
69
     * @var int|null
70
     */
71
    protected $id = null;
72
73
    /**
74
     * All parameters unsorted
75
     *
76
     * @var array
77
     */
78
    protected $unsortedParams = [];
79
80
    protected $supportedLanguages;
81
82
83
    private $finalIncludeWhitelist;
84
    private $finalFilterWhitelist;
85
    private $finalOrderWhitelist;
86
87
    public function __construct($entityManager, $supportedLanguages = [])
88
    {
89
        $this->entityManager = $entityManager;
90
        $this->supportedLanguages = $supportedLanguages;
91
92
        //Cache customized whitelists merged with core whitelists
93
        $this->finalFilterWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
94
            static::class,
95
            'filterWhitelist'
96
        );
97
        $this->finalOrderWhitelist = $this->getStaticPropertyOfClassMergedWithParents(static::class, 'orderWhitelist');
98
        $this->finalIncludeWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
99
            static::class,
100
            'includeWhitelist'
101
        );
102
    }
103
104
    /**
105
     * @return \Doctrine\ORM\EntityManager
106
     */
107
    public function getEntityManager()
108
    {
109
        return $this->entityManager;
110
    }
111
112
    /**
113
     * @param \Doctrine\ORM\EntityManager $entityManager
114
     * @return $this
115
     */
116
    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...
117
    {
118
        $this->entityManager = $entityManager;
119
        return $this;
120
    }
121
122
    private function isNotIncludableProperty($property)
123
    {
124
        return !isset($this->finalIncludeWhitelist[$property]);
125
    }
126
127
    private function isNotFilterableProperty($property)
128
    {
129
        return !isset($this->finalFilterWhitelist[$property]) && !isset($this->abstractFilterWhitelist[$property]);
130
    }
131
132
    private function isNotOrderableProperty($property)
133
    {
134
        return !isset($this->finalOrderWhitelist[$property]);
135
    }
136
137
    public function getTranslatableIncludeNames()
138
    {
139
        return array_keys(array_filter($this->finalIncludeWhitelist, function ($inc) {
140
            return isset($inc['translatable']) && $inc['translatable'];
141
        }));
142
    }
143
144
    /**
145
     * @return array
146
     */
147
    private function getPlatformOptions()
148
    {
149
        return \Zend_Registry::get('platformOptions');
150
    }
151
152
    /**
153
     * @return bool
154
     */
155
    protected function arePlatformUsersPublic()
156
    {
157
        return (bool)$this->getPlatformOptions()['user']['public'];
158
    }
159
160
    /**
161
     * @return bool
162
     */
163
    protected function arePlatformUsersPrivate()
164
    {
165
        return !$this->arePlatformUsersPublic();
166
    }
167
168
    /**
169
     * @param \Doctrine\ORM\QueryBuilder $query
170
     * @param string $alias
171
     * @param \UnserAller_Model_User $currentUser
172
     * @param array $additionalParams
173
     * @param $language
174
     * @param $filtername
175
     * @return \Doctrine\ORM\Query\Expr\Orx
176
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
177
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
178
     */
179
    private function filterAny($query, $alias, $currentUser, $additionalParams, $language, $filtername)
180
    {
181
        return $this->abstractFilterMultipleFields(
182
            $query,
183
            'orX',
184
            $currentUser,
185
            $additionalParams,
186
            $language,
187
            $filtername
188
        );
189
    }
190
191
    /**
192
     * @param \Doctrine\ORM\QueryBuilder $query
193
     * @param string $alias
194
     * @param \UnserAller_Model_User $currentUser
195
     * @param array $additionalParams
196
     * @param $language
197
     * @param $filtername
198
     * @return \Doctrine\ORM\Query\Expr\Orx
199
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
200
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
201
     */
202
    private function filterAll($query, $alias, $currentUser, $additionalParams, $language, $filtername)
203
    {
204
        return $this->abstractFilterMultipleFields(
205
            $query,
206
            'andX',
207
            $currentUser,
208
            $additionalParams,
209
            $language,
210
            $filtername
211
        );
212
    }
213
214
    /**
215
     * @param \Doctrine\ORM\QueryBuilder $query
216
     * @param string $expressionType
217
     * @param \UnserAller_Model_User $currentUser
218
     * @param array $additionalParams
219
     * @param $language
220
     * @param $filtername
221
     * @return \Doctrine\ORM\Query\Expr\Orx
222
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
223
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
224
     */
225
    private function abstractFilterMultipleFields(
226
        $query,
227
        $expressionType,
228
        $currentUser,
229
        $additionalParams,
230
        $language,
231
        $filtername
232
    ) {
233
        if (!isset($additionalParams[self::DIRECTIVE_FIELDS])) {
234
            throw new \UnserAllerLib_Api_V4_Exception_MissingFilterDirective(
235
                $filtername,
236
                self::DIRECTIVE_FIELDS,
237
                ['fieldname1', 'fieldname2'],
238
                ':someFilterDirective(params):maySomeMoreDirectives...'
239
            );
240
        }
241
242
        $fields = $additionalParams[self::DIRECTIVE_FIELDS];
243
        if (count(array_intersect_key($this->finalFilterWhitelist, array_flip($fields))) !== count($fields)) {
244
            throw new \UnserAllerLib_Api_V4_Exception_SafeForPrinting(
245
                'Wrong use of "' . $filtername . '" filter. '.
246
                'One of your specified fields is not filterable. '.
247
                'Try using fields that are filterable.'
248
            );
249
        }
250
251
        unset($additionalParams[self::DIRECTIVE_FIELDS]);
252
253
        $expression = call_user_func([$query->expr(), $expressionType]);
254
        foreach ($fields as $field) {
255
            $filterMethod = $this->decodeMethodFromRequestedFilter($field);
256
            $expression->add($this->$filterMethod(
257
                $query, $filterMethod, $currentUser, $additionalParams, $language, $field,
258
                $this->finalFilterWhitelist[$field]
259
            ));
260
        }
261
262
        return $expression;
263
    }
264
265
    /**
266
     * Executes include methods driven by a include string. See API docs to know how this string looks like
267
     *
268
     * @param \Doctrine\ORM\QueryBuilder $query
269
     * @param \UnserAller_Model_User $currentUser
270
     * @param $language
271
     * @param string $includeString
272
     * @param array $meta
273
     * @return \Doctrine\ORM\QueryBuilder
274
     */
275
    protected function addIncludeStatements($query, $currentUser, $language, $includeString, &$meta = [])
276
    {
277
        $requestedIncludes = $this->parseIncludeString($includeString, $this->finalIncludeWhitelist);
278
279
        $requestedIncludes = $requestedIncludes + static::$defaultIncludes;
280
        foreach ($requestedIncludes as $requestedInclude => $additionalParams) {
281
            if ($this->isNotIncludableProperty($requestedInclude)) {
282
                continue;
283
            }
284
285
            $includeMethod = $this->decodeMethodFromRequestedInclude($requestedInclude);
286
            $postProcessDirections = $this->$includeMethod($query, $includeMethod, $currentUser, $additionalParams,
287
                $language);
288
289
            if ($postProcessDirections) {
290
                $this->schedulePostProcessingDirections($postProcessDirections);
291
            }
292
293
            $this->updateMetaOnInclude($meta, $requestedInclude);
294
        }
295
        return $query;
296
    }
297
298
    /**
299
     * Collecting whitelist not just for current class but merged with all whitelists from parent classes.
300
     * So when we overwrite whitelists locally they are still including all the elements from core adapters.
301
     *
302
     * @param null|string $class
303
     * @param $propertyname
304
     * @return array
305
     */
306
    private function getStaticPropertyOfClassMergedWithParents($class, $propertyname)
307
    {
308
        $class = $class ? $class : static::class;
309
        $parent = get_parent_class($class);
310
        return $parent ? $this->getStaticPropertyOfClassOrArray(
311
            $class,
312
            $propertyname
313
        ) + $this->getStaticPropertyOfClassMergedWithParents(
314
            $parent,
315
            $propertyname
316
        ) : $this->getStaticPropertyOfClassOrArray($class, $propertyname);
317
    }
318
319
    private function getStaticPropertyOfClassOrArray($class, $propertyname)
320
    {
321
        return isset($class::$$propertyname) ? $class::$$propertyname : [];
322
    }
323
324
    private function updateMetaOnInclude(&$meta, $includeName)
325
    {
326
        $include = $this->finalIncludeWhitelist[$includeName];
327
        if (isset($include['model'])) {
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
    abstract protected function getFallbackLanguage($resultItem, $requestedLanguage);
369
370
    /**
371
     * Transforms additionalParams for included collections into params which are used
372
     * during post processing to call a findXForApi method
373
     *
374
     * @param array $additionalParams
375
     * @return array
376
     */
377
    protected function parseAdditionalIncludeParams($additionalParams)
378
    {
379
        $filter = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filter');
380
        $filter = is_array($filter) ? implode(',', $filter) : '';
381
382
        $include = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'include');
383
        $include = is_array($include) ? implode(',', $include) : '';
384
385
        $order = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'order');
386
        $order = is_array($order) ? implode(',', $order) : '';
387
388
        $limit = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'limit');
389
        $limit = is_array($limit) ? (int)array_shift($limit) : 0;
390
391
        $page = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'page');
392
        $page = is_array($page) ? (int)array_shift($page) : 1;
393
394
        $filterMode = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filterMode');
395
        $filterMode = is_array($filterMode) ? array_shift($filterMode) : 'AND';
396
397
        return [$filter, $include, $order, $limit, $page, $filterMode];
398
    }
399
400
    /**
401
     * Calls methods that add orderBy statements to a query driven by a string (see api docs for the string format)
402
     *
403
     * @param \Doctrine\ORM\QueryBuilder $query
404
     * @param \UnserAller_Model_User $currentUser
405
     * @param string $orderString
406
     * @return \Doctrine\ORM\QueryBuilder
407
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidOrder
408
     */
409
    private function addOrderStatements($query, $currentUser, $orderString)
410
    {
411
        $requestedOrders = array_filter($this->parseRichParamString($orderString));
412
413
        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...
414
            $requestedOrders = static::$defaultOrder;
415
        }
416
417
        foreach ($requestedOrders as $field => $order) {
418
            if ($this->isNotOrderableProperty($field)) {
419
                throw new \UnserAllerLib_Api_V4_Exception_InvalidOrder($field);
420
            }
421
422
            $orderMethod = $this->decodeMethodFromRequestedOrder($field);
423
            $postProcessTasks = $this->$orderMethod($query, $orderMethod, $currentUser,
424
                isset($order['desc']) ? 'DESC' : 'ASC', $order);
425
            if ($postProcessTasks) {
426
                $this->schedulePostProcessingDirections($postProcessTasks);
427
            }
428
        }
429
430
        return $query;
431
    }
432
433
    /**
434
     * Knows how to append post processing directions to the post process schedule
435
     *
436
     * @param array $tasks
437
     */
438
    private function schedulePostProcessingDirections($tasks)
439
    {
440
        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...
441
            return;
442
        }
443
444
        if (!is_array($tasks[0])) {
445
            $tasks = [$tasks];
446
        }
447
448
        foreach ($tasks as $task) {
449
            $this->resultArrayFixSchedule[array_shift($task)] = $task;
450
        }
451
    }
452
453
    /**
454
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
455
     * uppercase first letter and every letter that comes after a dot, remove dots and prepend 'include'. Examples:
456
     *
457
     * returns includeProject when $requestedInclude = project
458
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
459
     *
460
     * @param string $requestedInclude
461
     * @return string
462
     */
463
    private function decodeMethodFromRequestedInclude($requestedInclude)
464
    {
465
        return 'include' . implode('', array_map('ucfirst', explode('.', str_replace('[]', '_cp', $requestedInclude))));
466
    }
467
468
    /**
469
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
470
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
471
     *
472
     * returns includeProject when $requestedInclude = project
473
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
474
     *
475
     * @param string $requestedInclude
476
     * @return string
477
     */
478
    private function decodeMethodFromRequestedFilter($requestedInclude)
479
    {
480
        return 'filter' . implode('', array_map('ucfirst', explode('.', $requestedInclude)));
481
    }
482
483
    /**
484
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
485
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
486
     *
487
     * returns includeProject when $requestedInclude = project
488
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
489
     *
490
     * @param string $field
491
     * @return string
492
     */
493
    private function decodeMethodFromRequestedOrder($field)
494
    {
495
        return 'orderBy' . implode('', array_map('ucfirst', explode('.', $field)));
496
    }
497
498
    /**
499
     * Calculates total pages for an $incompleteStatement. Incomplete statements are doctrine query builder instances
500
     * with all required conditions but no select statement and no additional includes.
501
     *
502
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
503
     * @param int $limit
504
     * @return float|int
505
     */
506
    private function calculateTotalPages($incompleteStatement, $limit)
507
    {
508
        $incompleteStatement = clone $incompleteStatement;
509
510
        if ($limit) {
511
            return (int)ceil($this->executeRowCountStatement($incompleteStatement) / $limit);
512
        }
513
        return 1;
514
    }
515
516
    /**
517
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
518
     * @return int
519
     */
520
    private function executeRowCountStatement($incompleteStatement)
521
    {
522
        $rootAlias = $this->getRootAlias($incompleteStatement);
523
        $primaryIndexCol = $rootAlias . '.' . $this->getPrimaryIndexCol();
524
525
        if ($incompleteStatement->getDQLPart('having')) {
526
            $rootEntities = $incompleteStatement->getRootEntities();
527
            return (int)$incompleteStatement->getEntityManager()->createQueryBuilder()
528
                ->select('COUNT(x)')
529
                ->from(array_shift($rootEntities), 'x')
530
                ->where(
531
                    $incompleteStatement->expr()->in(
532
                        'x.'.$this->getPrimaryIndexCol(),
533
                        $incompleteStatement->select($primaryIndexCol)->getDQL()
534
                    )
535
                )->setParameters($incompleteStatement->getParameters())->getQuery()->getSingleScalarResult();
536
        }
537
538
        return (int)$incompleteStatement
539
            ->select("COUNT(DISTINCT $primaryIndexCol)")
540
            ->getQuery()
541
            ->getSingleScalarResult();
542
    }
543
544
    /**
545
     * Doctrine will throw errors if a table has a multi column primary index
546
     * http://stackoverflow.com/questions/18968963/select-countdistinct-error-on-multiple-columns
547
     * @return string
548
     */
549
    protected function getPrimaryIndexCol()
550
    {
551
        return 'id';
552
    }
553
554
    /**
555
     * Todo: Include collections by additional params and not by includes and adjust docs
556
     * Takes the include string and decodes it to an array with include names as keys and an array with additionalParams
557
     * as the value. Includes that are nested inside included collections are grouped and added as additional params
558
     * to the included collection.
559
     *
560
     * @param $string
561
     * @param $availableIncludes
562
     * @return array
563
     */
564
    private function parseIncludeString($string, $availableIncludes)
565
    {
566
        if ($string === '') {
567
            return [];
568
        }
569
570
        if (is_string($string)) {
571
            $string = explode(',', $string);
572
        }
573
574
        if (!is_array($string)) {
575
            return [];
576
        }
577
578
        $requestedIncludes = [];
579
        $implicitIncludes = [];
580
        foreach ($string as $include) {
581
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
582
583
            $pathToFirstRecursiveInclusion = $this->pathForNestedInclude($includeName, $availableIncludes);
584
            if ($pathToFirstRecursiveInclusion) {
585
                $requestedIncludes[$pathToFirstRecursiveInclusion]['include'][] = substr(
586
                    $include,
587
                    strlen($pathToFirstRecursiveInclusion) + 1
588
                );
589
                continue;
590
            }
591
592
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
593
594
            if ($allModifiersStr === null) {
595
                if (!isset($requestedIncludes[$includeName])) {
596
                    $requestedIncludes[$includeName] = [];
597
                }
598
                continue;
599
            }
600
601
            if (preg_match('~filter\(~u', $allModifiersStr)) {
602
                $modifierArr = $this->parseModifierArraySlowButAccurate($allModifiersStr);
603
            } else {
604
                $modifierArr = $this->parseModifierStringQuickButInaccurate($allModifiersStr);
605
            }
606
607
            if (isset($requestedIncludes[$includeName])) {
608
                $requestedIncludes[$includeName] = $requestedIncludes[$includeName] + $modifierArr;
609
            } else {
610
                $requestedIncludes[$includeName] = $modifierArr;
611
            }
612
        }
613
614
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
615
    }
616
617
    /**
618
     * creates an array out of string in this format:
619
     *
620
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3)
621
     *
622
     * Result:
623
     * [
624
     *  'modifierName1' => ['modifierParam1','modifierParam2'],
625
     *  'modifierName2' => ['modifierParam3']
626
     * ]
627
     *
628
     * But doesn't work when modifier params contain other modifiers with params themselves
629
     *
630
     * @param string $allModifiersStr
631
     * @return array
632
     */
633
    private function parseModifierStringQuickButInaccurate($allModifiersStr)
634
    {
635
        // Matches multiple instances of 'something(foo|bar|baz)' in the string
636
        // I guess it ignores : so you could use anything, but probably don't do that
637
        preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
638
        // [0] is full matched strings...
639
        $modifierCount = count($allModifiersArr[0]);
640
        $modifierArr = [];
641 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...
642
            // [1] is the modifier
643
            $modifierName = $allModifiersArr[1][$modifierIt];
644
            // and [3] is delimited params
645
            $modifierParamStr = $allModifiersArr[3][$modifierIt];
646
            // Make modifier array key with an array of params as the value
647
            $modifierArr[$modifierName] = explode('|', $modifierParamStr);
648
        }
649
650
        return $modifierArr;
651
    }
652
653
    /**
654
     * creates an array out of string in this format:
655
     *
656
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3))
657
     *
658
     * Can also handle modifier params that contain other modifier with params
659
     *
660
     * @param string $s
661
     * @return array
662
     */
663 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...
664
    {
665
        $modifierArr = [];
666
        $modifierName = '';
667
        $modifierParamStr = '';
668
669
        $depth = 0;
670
        for ($i = 0; $i <= strlen($s); $i++) {
671
            switch ($s[$i]) {
672
                case '(':
673
                    if ($depth) {
674
                        $modifierParamStr .= $s[$i];
675
                    }
676
                    $depth++;
677
                    break;
678
679
                case ')':
680
                    $depth--;
681
                    if ($depth) {
682
                        $modifierParamStr .= $s[$i];
683
                    }
684
                    break;
685
                case ':':
686
                    if ($depth) {
687
                        $modifierParamStr .= $s[$i];
688
                    } else {
689
                        $modifierArr[$modifierName] = $this->parseModifierParamStringSlowButAccurate($modifierParamStr);
690
                        $modifierName = '';
691
                        $modifierParamStr = '';
692
                    }
693
                    break;
694
                default:
695
                    if ($depth) {
696
                        $modifierParamStr .= $s[$i];
697
                    } else {
698
                        $modifierName .= $s[$i];
699
                    }
700
            }
701
        }
702
703
        if ($modifierName) {
704
            $modifierArr[$modifierName] = $this->parseModifierParamStringSlowButAccurate($modifierParamStr);
705
        }
706
707
        return $modifierArr;
708
    }
709
710
    /**
711
     * Can make an array out of parameter string that looks like this:
712
     *
713
     * param1|param2|param3
714
     *
715
     * Can also handle params that contain other modifiers with params like this:
716
     * param1|modifier(innerParam1|innerParam2)|param3
717
     *
718
     * @param string $s
719
     * @return array
720
     */
721 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...
722
    {
723
        $paramArr = [];
724
        $tmpStr = '';
725
726
        $depth = 0;
727
        for ($i = 0; $i <= strlen($s); $i++) {
728
            switch ($s[$i]) {
729
                case '(':
730
                    $tmpStr .= $s[$i];
731
                    $depth++;
732
                    break;
733
734
                case ')':
735
                    $tmpStr .= $s[$i];
736
                    $depth--;
737
                    break;
738
739
                case '|':
740
                    if ($depth) {
741
                        $tmpStr .= $s[$i];
742
                    } else {
743
                        $paramArr[] = $tmpStr;
744
                        $tmpStr = '';
745
                    }
746
                    break;
747
748
                default:
749
                    $tmpStr .= $s[$i];
750
            }
751
        }
752
753
        if (strlen($tmpStr)) {
754
            $paramArr[] = $tmpStr;
755
        }
756
757
        return $paramArr;
758
    }
759
760
    /**
761
     * Checks if includeName is an include nested inside a recursive inclusion.
762
     * If yes, return the path to that item - false otherwise.
763
     *
764
     * Example:
765
     * For projects there can be an include for phases. Phases are included recursively in its own adapter. So you'd
766
     * want when you include phases.steps that the steps inclusion is executed in the phase adapter and not in the
767
     * project adapter. That's why we need to separate includes that need to be passed further here.
768
     *
769
     * "recursiveinclude" results to false
770
     * "normalprop1" results to false
771
     * "recursiveinclude.normalprop1.normalprop2" results to "recursiveinclude"
772
     * "normalprop1.recursiveinclude.normalprop2" results to "normalprop1.recursiveinclude"
773
     *
774
     * @param $includeName
775
     * @param $availableIncludes
776
     * @return bool|string
777
     */
778
    private function pathForNestedInclude($includeName, $availableIncludes)
779
    {
780
        $pathArray = explode('.', $includeName);
781
        if (!isset($pathArray[1])) {
782
            return false;
783
        }
784
785
        $pathArrayLength = count($pathArray);
786
        for ($i = 1; $i < $pathArrayLength; $i++) {
787
            $implicitPath = implode('.', array_slice($pathArray, 0, $i));
788
            if ($this->extractStrategyForInclude($availableIncludes[$implicitPath]) === self::INCLUDE_RECURSIVE) {
789
                return $implicitPath;
790
            }
791
        }
792
793
        return false;
794
    }
795
796
    /**
797
     * Include configuration can either have just the strategy or a configuration array with the strategy inside.
798
     *
799
     * @param mixed $include
800
     * @return integer
801
     */
802
    private function extractStrategyForInclude($include)
803
    {
804
        return is_array($include) ? $include['strategy'] : $include;
805
    }
806
807
    /**
808
     * Validates the include string and returns an array with requiredIncludes
809
     *
810
     * @param string $string
811
     * @return array
812
     */
813
    private function parseRichParamString($string)
814
    {
815
        if ($string === '') {
816
            return [];
817
        }
818
819
        if (is_string($string)) {
820
            $string = explode(',', $string);
821
        }
822
823
        if (!is_array($string)) {
824
            return [];
825
        }
826
827
        $requestedIncludes = [];
828
        $implicitIncludes = [];
829
        foreach ($string as $include) {
830
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
831
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
832
833
            if ($allModifiersStr === null) {
834
                $requestedIncludes[$includeName] = [];
835
                continue;
836
            }
837
838
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
839
            // I guess it ignores : so you could use anything, but probably don't do that
840
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
841
            // [0] is full matched strings...
842
            $modifierCount = count($allModifiersArr[0]);
843
            $modifierArr = [];
844 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...
845
                // [1] is the modifier
846
                $modifierName = $allModifiersArr[1][$modifierIt];
847
                // and [3] is delimited params
848
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
849
                // Make modifier array key with an array of params as the value
850
                $modifierArr[$modifierName] = explode('|', $modifierParamStr);
851
            }
852
            $requestedIncludes[$includeName] = $modifierArr;
853
        }
854
855
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
856
    }
857
858
    private function mergeWithImplicitIncludes($includes, $implicitIncludes)
859
    {
860
        foreach ($implicitIncludes as $implicitInclude) {
861
            if (isset($includes[$implicitInclude])) {
862
                continue;
863
            }
864
865
            $includes[$implicitInclude] = [];
866
        }
867
868
        return $includes;
869
    }
870
871
    /**
872
     * @param \Doctrine\ORM\QueryBuilder $query
873
     * @param string $alias
874
     * @param \UnserAller_Model_User $currentUser
875
     * @param array $additionalParams
876
     * @return \Doctrine\ORM\Query\Expr\Andx
877
     */
878
    protected function filterId($query, $alias, $currentUser, $additionalParams)
879
    {
880
        $rootAliasArr = $query->getRootAliases();
881
        $rootAlias = array_shift($rootAliasArr);
882
        return $this->createConditionsForEntityColumn("$rootAlias.id", $query, $alias, $currentUser, $additionalParams);
883
    }
884
885
    private function getImplicitIncludes($includeName)
886
    {
887
        $parts = explode('.', $includeName);
888
        $numberOfParts = count($parts);
889
890
        if ($numberOfParts < 2) {
891
            return [];
892
        }
893
894
        $implicitIncludes = [];
895
        for ($i = 1; $i < $numberOfParts; $i++) {
896
            $implicitIncludes[] = implode('.', array_slice($parts, 0, $i));
897
        }
898
899
        return $implicitIncludes;
900
    }
901
902
    /**
903
     * @param \UnserAller_Model_User $currentUser
904
     * @return \Doctrine\ORM\QueryBuilder
905
     */
906
    abstract protected function initIncompleteStatement($currentUser);
907
908
    private function createIncompleteStatement(
909
        $currentUser,
910
        $filterString,
911
        $language,
912
        $joinFiltersWith = 'AND',
913
        &$meta = []
914
    ) {
915
        return $this->addFilterStatements(
916
            $this->initIncompleteStatement($currentUser),
917
            $currentUser,
918
            $filterString,
919
            $language,
920
            $joinFiltersWith
921
        );
922
    }
923
924
    /**
925
     * @param \UnserAller_Model_User $currentUser
926
     * @param string $filterString
927
     * @param $language
928
     * @param string $joinFiltersWith
929
     * @return int
930
     */
931
    public function findTotalNumberOfRows($currentUser, $filterString = '', $language = '', $joinFiltersWith = 'AND')
932
    {
933
        return $this->executeRowCountStatement(
934
            $this->createIncompleteStatement($currentUser, $filterString, $language, $joinFiltersWith)
935
        );
936
    }
937
938
    /**
939
     * @param \UnserAller_Model_User $currentUser
940
     * @param string $language
941
     * @param string $filterString
942
     * @param string $includeString
943
     * @param string $orderString
944
     * @param int $limit
945
     * @param int $page
946
     * @param string $joinFiltersWith
947
     * @param int $metaOnObjectLevelOption
948
     * @return array
949
     */
950
    public function findMultipleForApi(
951
        $currentUser,
952
        $language = '',
953
        $filterString = '',
954
        $includeString = '',
955
        $orderString = '',
956
        $limit = 0,
957
        $page = 1,
958
        $joinFiltersWith = 'AND',
959
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
960
    ) {
961
        if ($page <= 0) {
962
            $page = 1;
963
        }
964
965
        $meta = $this->initMetaArray('', $language);
966
967
        $incompleteStatement = $this->createIncompleteStatement(
968
            $currentUser,
969
            $filterString,
970
            $language,
971
            $joinFiltersWith,
972
            $meta
973
        );
974
975
        $completeStatement = $this->completeStatement(
976
            $currentUser,
977
            $language,
978
            $incompleteStatement,
979
            $includeString,
980
            $orderString,
981
            $meta
982
        );
983
        if ($limit > 0) {
984
            $completeStatement
985
                ->setFirstResult(($page - 1) * $limit)
986
                ->setMaxResults($limit);
987
        }
988
989
        return [
990
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
991
            'filter' => $filterString,
992
            'include' => $includeString,
993
            'page' => $page,
994
            'pageSize' => $limit,
995
            'data' => $this->applyScheduledFixes(
996
                $this->getRawResult($completeStatement),
997
                $currentUser,
998
                $language,
999
                $meta,
1000
                $metaOnObjectLevelOption
1001
            ),
1002
            'meta' => $meta
1003
        ];
1004
    }
1005
1006
    private function initMetaArray($modelPathOffset = '', $language = '')
1007
    {
1008
        return [
1009
            'modelnameIndex' => [
1010
                $this->getModelForMeta() => [$modelPathOffset]
1011
            ],
1012
            'language' => $language
1013
        ];
1014
    }
1015
1016
    /**
1017
     * @param string $language
1018
     * @param string $filterString
1019
     * @param string $includeString
1020
     * @param string $orderString
1021
     * @param int $limit
1022
     * @param int $page
1023
     * @param string $filterMode
1024
     * @return array
1025
     */
1026
    public function findMultiple(
1027
        $language = '',
1028
        $filterString = '',
1029
        $includeString = '',
1030
        $orderString = '',
1031
        $limit = 0,
1032
        $page = 1,
1033
        $filterMode = 'AND'
1034
    ) {
1035
        return json_decode(json_encode($this->findMultipleForApi(
1036
            $this->getCurrentlyAuthenticatedUser(),
1037
            $language,
1038
            $filterString,
1039
            $includeString,
1040
            $orderString,
1041
            $limit,
1042
            $page,
1043
            $filterMode,
1044
            self::META_ON_OBJECT_LEVEL_DISABLED
1045
        )), true);
1046
    }
1047
1048
    /**
1049
     * @param \UnserAller_Model_User $currentUser
1050
     * @param string $language
1051
     * @param string $filterString
1052
     * @param string $includeString
1053
     * @param string $orderString
1054
     * @param int $limit
1055
     * @param string $filterMode
1056
     * @return \Generator
1057
     */
1058
    public function batchFindMultiple(
1059
        $currentUser,
1060
        $language = '',
1061
        $filterString = '',
1062
        $includeString = '',
1063
        $orderString = '',
1064
        $limit = 500,
1065
        $filterMode = 'AND'
1066
    ) {
1067
        $page = 1;
1068
1069
        $result = $this->findMultipleForApi(
1070
            $currentUser,
1071
            $language,
1072
            $filterString,
1073
            $includeString,
1074
            $orderString,
1075
            $limit,
1076
            $page,
1077
            $filterMode
1078
        );
1079
1080
        yield $result;
1081
1082
        $totalPages = $result['totalPages'];
1083
        unset($result);
1084
        $page++;
1085
        while ($page <= $totalPages) {
1086
            $result = $this->findMultipleForApi(
1087
                $currentUser,
1088
                $language,
1089
                $filterString,
1090
                $includeString,
1091
                $orderString,
1092
                $limit,
1093
                $page,
1094
                $filterMode
1095
            );
1096
            yield $result;
1097
            $page++;
1098
            unset($result);
1099
        }
1100
    }
1101
1102
    /**
1103
     * @param \UnserAller_Model_User $currentUser
1104
     * @param string $language
1105
     * @param string $filterString
1106
     * @param string $includeString
1107
     * @param string $orderString
1108
     * @param int $limit
1109
     * @param int $page
1110
     * @param string $filterMode
1111
     * @return array
1112
     */
1113
    public function getNativeSqlIngredientsForFindMultiple(
1114
        $currentUser,
1115
        $language = '',
1116
        $filterString = '',
1117
        $includeString = '',
1118
        $orderString = '',
1119
        $limit = 0,
1120
        $page = 1,
1121
        $filterMode = 'AND'
1122
    ) {
1123
        if ($page <= 0) {
1124
            $page = 1;
1125
        }
1126
1127
        $incompleteStatement = $this->createIncompleteStatement($currentUser, $filterString, $language, $filterMode);
1128
1129
        $completeStatement = $this->completeStatement(
1130
            $currentUser,
1131
            $language,
1132
            $incompleteStatement,
1133
            $includeString,
1134
            $orderString
1135
        );
1136
        if ($limit > 0) {
1137
            $completeStatement
1138
                ->setFirstResult(($page - 1) * $limit)
1139
                ->setMaxResults($limit);
1140
        }
1141
1142
        return $this->getNativeSqlIngredients($completeStatement->getQuery());
1143
    }
1144
1145
    /**
1146
     * @param Query $query
1147
     * @return array
1148
     */
1149
    private function getNativeSqlIngredients($query)
1150
    {
1151
        $sql = $query->getSQL();
1152
        $c = new \ReflectionClass('Doctrine\ORM\Query');
1153
        $parser = $c->getProperty('_parserResult');
1154
        $parser->setAccessible(true);
1155
        /** @var \ReflectionProperty $parser */
1156
        $parser = $parser->getValue($query);
1157
        /** @var \Doctrine\ORM\Query\ParserResult $parser */
1158
        $resultSet = $parser->getResultSetMapping();
1159
1160
        // Change the aliases back to what was originally specified in the QueryBuilder.
1161
        $sql = preg_replace_callback('/AS\s([a-zA-Z0-9_]+)/', function ($matches) use ($resultSet) {
1162
            $ret = 'AS ';
1163
            if ($resultSet->isScalarResult($matches[1])) {
1164
                $ret .= $resultSet->getScalarAlias($matches[1]);
1165
            } else {
1166
                $ret .= $matches[1];
1167
            }
1168
            return $ret;
1169
        }, $sql);
1170
        $m = $c->getMethod('processParameterMappings');
1171
        $m->setAccessible(true);
1172
        list($params, $types) = $m->invoke($query, $parser->getParameterMappings());
1173
        return [$sql, $params, $types];
1174
    }
1175
1176
    /**
1177
     * @param \UnserAller_Model_User $currentUser
1178
     * @param string $language
1179
     * @param string $filterString
1180
     * @param string $includeString
1181
     * @param string $orderString
1182
     * @param int $metaOnObjectLevelOption
1183
     * @return array|null
1184
     */
1185
    public function findOneForApi(
1186
        $currentUser,
1187
        $language = '',
1188
        $filterString = '',
1189
        $includeString = '',
1190
        $orderString = '',
1191
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1192
    ) {
1193
        return $this->createSingleResult(
1194
            $currentUser,
1195
            $language,
1196
            $filterString,
1197
            $includeString,
1198
            $orderString,
1199
            $metaOnObjectLevelOption
1200
        );
1201
    }
1202
1203
    /**
1204
     * @param string $language
1205
     * @param string $filterString
1206
     * @param string $includeString
1207
     * @param string $orderString
1208
     * @return array|null
1209
     */
1210
    public function findOne($language = '', $filterString = '', $includeString = '', $orderString = '')
1211
    {
1212
        return json_decode(json_encode($this->findOneForApi(
1213
            $this->getCurrentlyAuthenticatedUser(),
1214
            $language,
1215
            $filterString,
1216
            $includeString,
1217
            $orderString,
1218
            self::META_ON_OBJECT_LEVEL_DISABLED
1219
        )), true);
1220
    }
1221
1222
    /**
1223
     * @param \UnserAller_Model_User $currentUser
1224
     * @param int $id
1225
     * @param string $language
1226
     * @param string $include
1227
     * @param int $metaOnObjectLevelOption
1228
     * @return array|null
1229
     */
1230
    public function findForApi(
1231
        $currentUser,
1232
        $id,
1233
        $language = '',
1234
        $include = '',
1235
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1236
    ) {
1237
        $this->id = (int)$id;
1238
        return $this->findOneForApi(
1239
            $currentUser,
1240
            $language,
1241
            "id:is($id)",
1242
            $include,
1243
            '',
1244
            $metaOnObjectLevelOption
1245
        );
1246
    }
1247
1248
    /**
1249
     * @param int $id
1250
     * @param string $language
1251
     * @param string $include
1252
     * @return array|null
1253
     */
1254
    public function find($id, $language = '', $include = '')
1255
    {
1256
        return json_decode(json_encode($this->findForApi(
1257
            $this->getCurrentlyAuthenticatedUser(),
1258
            $id,
1259
            $language,
1260
            $include,
1261
            self::META_ON_OBJECT_LEVEL_DISABLED
1262
        )), true);
1263
    }
1264
1265
    /**
1266
     * @return null|\UnserAller_Model_User|object
1267
     */
1268
    private function getCurrentlyAuthenticatedUser()
1269
    {
1270
        return $this->getEntityManager()->find(
1271
            \UnserAller_Model_User::class,
1272
            (int)\Zend_Auth::getInstance()->getIdentity()
1273
        );
1274
    }
1275
1276
    /**
1277
     * @param \UnserAller_Model_User $currentUser
1278
     * @param string $language
1279
     * @param string $filterString
1280
     * @param string $includeString
1281
     * @param string $orderString
1282
     * @param int $metaOnObjectLevelOption
1283
     * @return array|null
1284
     */
1285
    protected function createSingleResult(
1286
        $currentUser,
1287
        $language,
1288
        $filterString,
1289
        $includeString,
1290
        $orderString,
1291
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
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
        $result = $this->applyScheduledFixes($result, $currentUser, $language, $meta, $metaOnObjectLevelOption)[0];
1311
        if ($metaOnObjectLevelOption === self::META_ON_OBJECT_LEVEL_ENABLED) {
1312
            $result['meta'] = $result['meta'] + $meta;
1313
        }
1314
        return $result;
1315
    }
1316
1317
    protected function getDefaultPostProcessDirections()
1318
    {
1319
        return [];
1320
    }
1321
1322
    /**
1323
     * Adds the default select statement, all includes and order statements to the incomplete statement
1324
     * and returns the qurey builder instance
1325
     *
1326
     * @param \UnserAller_Model_User $currentUser
1327
     * @param $language
1328
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
1329
     * @param string $includeString
1330
     * @param string $orderString
1331
     * @param array $meta
1332
     * @return \Doctrine\ORM\QueryBuilder
1333
     */
1334
    private function completeStatement(
1335
        $currentUser,
1336
        $language,
1337
        $incompleteStatement,
1338
        $includeString,
1339
        $orderString,
1340
        &$meta = []
1341
    ) {
1342
        $statement = clone $incompleteStatement;
1343
1344
        $this->schedulePostProcessingDirections($this->getDefaultPostProcessDirections());
1345
1346
        return $this->addOrderStatements(
1347
            $this->addIncludeStatements(
1348
                $statement->select($this->getDefaultSelectStatement($statement)),
1349
                $currentUser,
1350
                $language,
1351
                $includeString,
1352
                $meta
1353
            ),
1354
            $currentUser,
1355
            $orderString
1356
        );
1357
    }
1358
1359
    /**
1360
     * Returns the default select statement. In this case it just returns the first root entity which means the
1361
     * entire root entity will be selected
1362
     *
1363
     * @param \Doctrine\ORM\QueryBuilder $query
1364
     * @return string
1365
     */
1366
    protected function getDefaultSelectStatement($query)
1367
    {
1368
        return 'DISTINCT ' . $this->getRootAlias($query);
1369
    }
1370
1371
    /**
1372
     * Returns first root alias from query builder
1373
     *
1374
     * @param \Doctrine\ORM\QueryBuilder $query
1375
     * @return string
1376
     */
1377
    protected function getRootAlias($query)
1378
    {
1379
        $rootAliasArr = $query->getRootAliases();
1380
        return array_shift($rootAliasArr);
1381
    }
1382
1383
    /**
1384
     * Returns true if result item has an additional layer in the hierarchy because of custom subselects
1385
     *
1386
     * @param array $item
1387
     * @return bool
1388
     */
1389
    private function mustFlattenResultItem($item)
1390
    {
1391
        return isset($item[0]);
1392
    }
1393
1394
    /**
1395
     * Returns doctrine array results with all fixes applied
1396
     *
1397
     * @param \Doctrine\ORM\QueryBuilder $statement
1398
     * @return array
1399
     */
1400
    private function getRawResult($statement)
1401
    {
1402
        //Output raw sql here if you like to debug hard
1403
        //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...
1404
        return $statement->getQuery()->getResult(Query::HYDRATE_ARRAY);
1405
    }
1406
1407
    /**
1408
     * Doctrine will create an additional result layer when values are selected that do not belong
1409
     * to the doctrine object model. This function removes this additional layer and merges custom values with
1410
     * the doctrine object model for a single result item - not the whole result array.
1411
     *
1412
     * @param array $item
1413
     * @return array
1414
     */
1415
    private function flattenResultItem($item)
1416
    {
1417
        if (!$this->mustFlattenResultItem($item)) {
1418
            return $item;
1419
        }
1420
1421
        return array_merge(array_shift($item), $item);
1422
    }
1423
1424
    /**
1425
     * Parses $string for orderBy statements and returns an array where order statements are values.
1426
     *
1427
     * When string is "latestFirst, longestFirst" result would be: ['latestFirst', 'longestFirst']
1428
     *
1429
     * @param string $string
1430
     * @return array
1431
     */
1432
    protected function parseOrderString($string)
1433
    {
1434
        return array_filter(array_map('trim', explode(',', $string)));
1435
    }
1436
1437
    /**
1438
     * @uses getFallbackLanguage
1439
     * @uses getRequestedLanguage
1440
     * @param $language
1441
     * @return callable
1442
     */
1443
    private function getItemLanguageGetter($language)
1444
    {
1445
        return $language === '' ? 'getFallbackLanguage' : 'getRequestedLanguage';
1446
    }
1447
1448
    /**
1449
     * @param mixed[] $resultItem
1450
     * @param string $requestedLanguage
1451
     * @return string
1452
     */
1453
    private function getRequestedLanguage($resultItem, $requestedLanguage)
1454
    {
1455
        return $requestedLanguage;
1456
    }
1457
1458
    /**
1459
     * Executes all operations that were scheduled for post processing
1460
     *
1461
     * @param array $result
1462
     * @param \UnserAller_Model_User $currentUser
1463
     * @param $language
1464
     * @param array $meta
1465
     * @param int $metaOnObjectLevelOption
1466
     * @return array
1467
     */
1468
    private function applyScheduledFixes(
1469
        $result,
1470
        $currentUser,
1471
        $language,
1472
        &$meta = [],
1473
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1474
    ) {
1475
        $scheduledFixes = $this->flushResultArrayFixSchedule();
1476
1477
        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...
1478
            return $result;
1479
        }
1480
1481
        $numberOfResults = count($result);
1482
1483
        $this->applyFixesToItem(
1484
            $result[0],
1485
            $scheduledFixes,
1486
            $currentUser,
1487
            $meta,
1488
            'retrieveNestedCollectionAndMergeMeta',
1489
            $language,
1490
            $this->getItemLanguageGetter($language),
1491
            $metaOnObjectLevelOption
1492
        );
1493
        for ($i = 1; $i < $numberOfResults; $i++) {
1494
            $this->applyFixesToItem(
1495
                $result[$i],
1496
                $scheduledFixes,
1497
                $currentUser,
1498
                $meta,
1499
                'retrieveNestedCollection',
1500
                $language,
1501
                $this->getItemLanguageGetter($language),
1502
                $metaOnObjectLevelOption
1503
            );
1504
        }
1505
1506
        return $result;
1507
    }
1508
1509
    private function retrieveNestedCollectionResult($value, $nestingOptions, $language = '')
1510
    {
1511
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1512
        list(
1513
            $filterString,
1514
            $includeString,
1515
            $orderString,
1516
            $limit,
1517
            $page,
1518
            $filterMode
1519
            ) = $this->parseAdditionalIncludeParams($additionalParams);
1520
1521
        if ($filterString) {
1522
            $filterString = $filterString . ',';
1523
        }
1524
1525
        if (is_array($value)) {
1526
            $filterFunctionString = vsprintf($filterFunction, array_merge($value));
1527
        } else {
1528
            $filterFunctionString = sprintf($filterFunction, $value);
1529
        }
1530
1531
        return $this->getAdapter($model)->findMultipleForApi(
1532
            $currentUser,
1533
            $language,
1534
            $filterString . $filterFunctionString,
1535
            $includeString,
1536
            $orderString,
1537
            $limit,
1538
            $page,
1539
            $filterMode
1540
        );
1541
    }
1542
1543
    private function retrieveNestedCollection($value, $nestingOptions, $language, $finalPath, $meta)
1544
    {
1545
        return $this->retrieveNestedCollectionResult($value, $nestingOptions, $language)['data'];
1546
    }
1547
1548
    private function retrieveNestedCollectionAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1549
    {
1550
        $result = $this->retrieveNestedCollectionResult($value, $nestingOptions, $language);
1551
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1552
        return $result['data'];
1553
    }
1554
1555
    private function retrieveNestedSingleResult($value, $nestingOptions, $language = '')
1556
    {
1557
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1558
        list($filterString, $includeString, $orderString, , ,) = $this->parseAdditionalIncludeParams($additionalParams);
1559
1560
        if ($filterString) {
1561
            $filterString = $filterString . ',';
1562
        }
1563
1564
        return $this->getAdapter($model)->findOneForApi(
1565
            $currentUser,
1566
            $language,
1567
            $filterString . sprintf($filterFunction, $value),
1568
            $includeString,
1569
            $orderString
1570
        );
1571
    }
1572
1573
    private function retrieveNestedSingleAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1574
    {
1575
        $result = $this->retrieveNestedSingleResult($value, $nestingOptions, $language);
1576
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1577
        unset($result['meta']);
1578
        return $result;
1579
    }
1580
1581
    private function mergeNestedMeta(&$meta, $nestedMeta, $includeName)
1582
    {
1583
        foreach ($nestedMeta['modelnameIndex'] as $model => $paths) {
1584
            foreach ($paths as $path) {
1585
                $fullPath = $includeName . '.' . $path;
1586
                if ($path && !in_array($fullPath, $meta['modelnameIndex'][$model])) {
1587
                    $meta['modelnameIndex'][$model][] = $fullPath;
1588
                }
1589
            }
1590
        }
1591
    }
1592
1593
    /**
1594
     * @param $item
1595
     * @param $scheduledFixes
1596
     * @param $currentUser
1597
     * @param $meta
1598
     * @param $collectionNestingMethod
1599
     * @param $language
1600
     * @param $itemLanguageGetter
1601
     * @param int $metaOnObjectLevelOption
1602
     * @uses retrieveNestedCollection
1603
     * @uses retrieveNestedCollectionAndMergeMeta
1604
     */
1605
    private function applyFixesToItem(
1606
        &$item,
1607
        $scheduledFixes,
1608
        $currentUser,
1609
        &$meta,
1610
        $collectionNestingMethod,
1611
        $language,
1612
        $itemLanguageGetter,
1613
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1614
    ) {
1615
        $item = $this->flattenResultItem($item);
1616
1617
        //If deleteion is not scheduled and done at the end, multiple tasks on same field can fail
1618
        $scheduledDeletions = [];
1619
1620
        foreach ($scheduledFixes as $path => $fix) {
1621
            if (isset($fix['delete'])) {
1622
                $scheduledDeletions[$path] = $fix;
1623
                continue;
1624
            }
1625
1626
            if (isset($fix['cast'])) {
1627
                \UnserAllerLib_Tool_Array::castNestedValue($item, $path, $fix['cast']);
1628
            }
1629
1630
            $value = \UnserAllerLib_Tool_Array::readNestedValue($item, $path);
1631
1632
            if (isset($fix['additionalFilterValues'])) {
1633
                $value = [$value];
1634
1635
                foreach ($fix['additionalFilterValues'] as $additionalFilterValue) {
1636
                    $value[] = \UnserAllerLib_Tool_Array::readNestedValue($item, $additionalFilterValue);
1637
                }
1638
            }
1639
1640 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...
1641
                $value = $this->$collectionNestingMethod(
1642
                    $value,
1643
                    $fix['nestCollection'],
1644
                    $this->$itemLanguageGetter($item, $language),
1645
                    $fix['move'] ? $fix['move'] : $path,
1646
                    $meta
1647
                );
1648
            }
1649
1650 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...
1651
                $value = $this->retrieveNestedSingleAndMergeMeta(
1652
                    $value,
1653
                    $fix['nestSingle'],
1654
                    $this->$itemLanguageGetter($item, $language),
1655
                    $fix['move'] ? $fix['move'] : $path,
1656
                    $meta
1657
                );
1658
            }
1659
1660
            if (isset($fix['filter'])) {
1661
                $value = $this->filterValue($fix['filter'], $value, $currentUser);
1662
            }
1663
1664
            if (isset($fix['cFilter'])) {
1665
                $value = $this->filterValue($fix['cFilter'], $value, $currentUser);
1666
            }
1667
1668
            if (isset($fix['mFilter'])) {
1669
                $value = $this->filterValue($fix['mFilter'], $item, $currentUser);
1670
            }
1671
1672
            if (isset($fix['move'])) {
1673
                \UnserAllerLib_Tool_Array::integrateNestedValue($item, $fix['move'], $value);
1674
                if ($path != $fix['move']) {
1675
                    $scheduledDeletions[$path] = ['delete' => 1];
1676
                }
1677
            }
1678
        }
1679
1680
        foreach ($scheduledDeletions as $path => $fix) {
1681
            \UnserAllerLib_Tool_Array::unsetNestedValue($item, $path);
1682
        }
1683
1684
        if ($metaOnObjectLevelOption === self::META_ON_OBJECT_LEVEL_ENABLED) {
1685
            $item['meta'] = $this->createMetadataForResultItem($item, $language, $itemLanguageGetter);
1686
        }
1687
    }
1688
1689
    /**
1690
     * @param mixed[] $resultItem
1691
     * @param string $requestedLanguage
1692
     * @param callable $itemLanguageGetter
1693
     * @return array
1694
     */
1695
    private function createMetadataForResultItem($resultItem, $requestedLanguage, $itemLanguageGetter)
1696
    {
1697
        return [
1698
            'language' => $this->$itemLanguageGetter($resultItem, $requestedLanguage),
1699
            'model' => $this->getModelForMeta()
1700
        ];
1701
    }
1702
1703
    /**
1704
     * Applies filter methods for $filterName to $value
1705
     * @uses filterJsonAfterwards
1706
     * @uses filterJsonIfNullSetEmptyObjectAfterwards
1707
     * @uses filterJsonOrNullAfterwards
1708
     * @uses filterDatetimeAfterwards
1709
     * @uses filterDatetimeOrNullAfterwards
1710
     * @uses filterIntOrNullAfterwards
1711
     * @uses filterNl2BrAfterwards
1712
     * @param string $filterName
1713
     * @param mixed $value
1714
     * @param $currentUser
1715
     * @return mixed
1716
     */
1717
    private function filterValue($filterName, $value, $currentUser)
1718
    {
1719
        if (!is_callable([$this, $filterName])) {
1720
            throw new \InvalidArgumentException('Post Processing Filter method not found: ' . $filterName);
1721
        }
1722
1723
        return call_user_func_array([$this, $filterName], [$value, $currentUser]);
1724
    }
1725
1726
    /**
1727
     * @param $field
1728
     * @param \Doctrine\ORM\QueryBuilder $query
1729
     * @param string $alias
1730
     * @param \UnserAller_Model_User $currentUser
1731
     * @param array $methods
1732
     * @return \Doctrine\ORM\Query\Expr\Andx
1733
     * @uses stringContainExpression
1734
     * @uses stringContainsExpression
1735
     * @uses stringIsExpression
1736
     * @uses stringNotExpression
1737
     * @uses stringFalseExpression
1738
     * @uses stringTrueExpression
1739
     */
1740
    protected function createConditionsForStringColumn($field, $query, $alias, $currentUser, $methods)
1741
    {
1742
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1743
            $methods,
1744
            ['contain', 'contains', 'is', 'not', 'false', 'true']
1745
        )
1746
        ) {
1747
            throw new \InvalidArgumentException('Invalid expression methods used');
1748
        }
1749
1750
        return $this->createExpression('string', $field, $query, $alias, $currentUser, $methods);
1751
    }
1752
1753
    /**
1754
     * @param \Doctrine\ORM\QueryBuilder $query
1755
     * @param $fallbackField
1756
     * @param $translationName
1757
     * @param $language
1758
     * @param string $alias
1759
     * @param \UnserAller_Model_User $currentUser
1760
     * @param $additionalParams
1761
     * @return \Doctrine\ORM\Query\Expr\Composite
1762
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidFilter
1763
     */
1764
    protected function createConditionsForMultilanguageStringColumn(
1765
        $query,
1766
        $fallbackField,
1767
        $translationName,
1768
        $language,
1769
        $alias,
1770
        $currentUser,
1771
        $additionalParams
1772
    ) {
1773
        if (isset($additionalParams['overAllTranslations'])) {
1774
            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...
1775
                throw new \UnserAllerLib_Api_V4_Exception_InvalidFilter('Supported languages are not set');
1776
            }
1777
1778
            unset($additionalParams['overAllTranslations']);
1779
1780
            $expr = $query->expr()->orX();
1781
            foreach ($this->supportedLanguages as $supportedLanguage) {
1782
                $expr->add($this->createConditionsForStringColumn(
1783
                    "COALESCE(" . $this->joinTranslationOnce(
1784
                        $query,
1785
                        $translationName,
1786
                        $supportedLanguage
1787
                    ) . ".translation, $fallbackField)",
1788
                    $query,
1789
                    $alias,
1790
                    $currentUser,
1791
                    $additionalParams
1792
                ));
1793
            }
1794
1795
            return $expr;
1796
        }
1797
1798
        return $this->createConditionsForStringColumn(
1799
            "COALESCE(" . $this->joinTranslationOnce(
1800
                $query,
1801
                $translationName,
1802
                $language
1803
            ) . ".translation, $fallbackField)",
1804
            $query,
1805
            $alias,
1806
            $currentUser,
1807
            $additionalParams
1808
        );
1809
    }
1810
1811
1812
    /**
1813
     * @param $field
1814
     * @param \Doctrine\ORM\QueryBuilder $query
1815
     * @param string $alias
1816
     * @param \UnserAller_Model_User $currentUser
1817
     * @param array $methods
1818
     * @return \Doctrine\ORM\Query\Expr\Andx
1819
     * @uses dateGtExpression
1820
     * @uses dateGteExpression
1821
     * @uses dateLtExpression
1822
     * @uses dateLteExpression
1823
     * @uses dateFalseExpression
1824
     * @uses dateTrueExpression
1825
     * @uses dateIsExpression
1826
     * @uses dateNotExpression
1827
     */
1828 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...
1829
    {
1830
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1831
            $methods,
1832
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true']
1833
        )
1834
        ) {
1835
            throw new \InvalidArgumentException('Invalid expression methods used');
1836
        }
1837
1838
        return $this->createExpression('date', $field, $query, $alias, $currentUser, $methods);
1839
    }
1840
1841
    /**
1842
     * @param $field
1843
     * @param \Doctrine\ORM\QueryBuilder $query
1844
     * @param string $alias
1845
     * @param \UnserAller_Model_User $currentUser
1846
     * @param array $methods
1847
     * @return \Doctrine\ORM\Query\Expr\Andx
1848
     * @uses entityFalseExpression
1849
     * @uses entityTrueExpression
1850
     * @uses entityIsExpression
1851
     * @uses entityIsOrNullExpression
1852
     * @uses entityNotExpression
1853
     * @uses entityMeExpression
1854
     * @uses entityNotmeExpression
1855
     * @uses entityLtExpression
1856
     * @uses entityLteExpression
1857
     * @uses entityGtExpression
1858
     * @uses entityGteExpression
1859
     */
1860 View Code Duplication
    protected function createConditionsForEntityColumn($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...
1861
    {
1862
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1863
            $methods,
1864
            ['false', 'true', 'is', 'not', 'me', 'notme', 'isOrNull', 'lt', 'lte', 'gt', 'gte']
1865
        )) {
1866
            throw new \InvalidArgumentException('Invalid expression methods used');
1867
        }
1868
1869
        return $this->createExpression('entity', $field, $query, $alias, $currentUser, $methods);
1870
    }
1871
1872
    /**
1873
     * @param $subquery
1874
     * @param $query
1875
     * @param $alias
1876
     * @param $currentUser
1877
     * @param $methods
1878
     * @return \Doctrine\ORM\Query\Expr\Andx
1879
     * @uses subqueryIsExpression
1880
     * @uses subqueryFalseExpression
1881
     * @uses subqueryTrueExpression
1882
     * @uses subqueryNotExpression
1883
     * @uses subqueryMeExpression
1884
     * @uses subqueryNotmeExpression
1885
     * @uses subqueryEqExpression
1886
     */
1887
    protected function createConditionsForEntitySubquery($subquery, $query, $alias, $currentUser, $methods)
1888
    {
1889
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true', 'is', 'not', 'me', 'notme', 'eq'])) {
1890
            throw new \InvalidArgumentException('Invalid expression methods used');
1891
        }
1892
1893
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1894
    }
1895
1896
    /**
1897
     * @param \Doctrine\ORM\QueryBuilder $query
1898
     * @param string $field
1899
     * @param array $params
1900
     * @param string $alias
1901
     * @return mixed
1902
     */
1903
    private function entityIsOrNullExpression($query, $field, $params, $alias)
1904
    {
1905
        return $query->expr()->orX(
1906
            $query->expr()->in($field, $params),
1907
            $query->expr()->isNull($field)
1908
        );
1909
    }
1910
1911
    /**
1912
     * @param \Doctrine\ORM\QueryBuilder $query
1913
     * @param string $field
1914
     * @param array $params
1915
     * @param string $alias
1916
     * @param \UnserAller_Model_User $currentUser
1917
     * @return mixed
1918
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
1919
     */
1920
    private function entityMeExpression($query, $field, $params, $alias, $currentUser)
1921
    {
1922
        if (!$currentUser) {
1923
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
1924
        }
1925
        return $query->expr()->eq($field, $currentUser->getId());
1926
    }
1927
1928
1929
    /**
1930
     * @param \Doctrine\ORM\QueryBuilder $query
1931
     * @param string $field
1932
     * @param array $params
1933
     * @param string $alias
1934
     * @param \UnserAller_Model_User $currentUser
1935
     * @return \Doctrine\ORM\Query\Expr\Comparison
1936
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
1937
     */
1938
    private function entityNotmeExpression($query, $field, $params, $alias, $currentUser)
1939
    {
1940
        if (!$currentUser) {
1941
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
1942
        }
1943
        return $query->expr()->neq($field, $currentUser->getId());
1944
    }
1945
1946
    /**
1947
     * @param \Doctrine\ORM\QueryBuilder $query
1948
     * @param string $field
1949
     * @param array $params
1950
     * @param string $alias
1951
     * @return \Doctrine\ORM\Query\Expr\Func
1952
     */
1953
    private function entityNotExpression($query, $field, $params, $alias)
1954
    {
1955
        return $query->expr()->notIn($field, $params);
1956
    }
1957
1958
    /**
1959
     * @param \Doctrine\ORM\QueryBuilder $query
1960
     * @param string $field
1961
     * @param array $params
1962
     * @param string $alias
1963
     * @return \Doctrine\ORM\Query\Expr\Comparison
1964
     */
1965
    private function entityFalseExpression($query, $field, $params, $alias)
1966
    {
1967
        return $query->expr()->eq("COALESCE(IDENTITY($field),0)", 0);
1968
    }
1969
1970
    /**
1971
     * @param \Doctrine\ORM\QueryBuilder $query
1972
     * @param string $field
1973
     * @param array $params
1974
     * @param string $alias
1975
     * @return \Doctrine\ORM\Query\Expr\Comparison
1976
     */
1977
    private function entityTrueExpression($query, $field, $params, $alias)
1978
    {
1979
        return $query->expr()->neq("COALESCE(IDENTITY($field),0)", 0);
1980
    }
1981
1982
    /**
1983
     * @param \Doctrine\ORM\QueryBuilder $query
1984
     * @param string $field
1985
     * @param array $params
1986
     * @param string $alias
1987
     * @return mixed
1988
     */
1989
    private function entityLtExpression($query, $field, $params, $alias)
1990
    {
1991
        $lt = $query->expr()->orX();
1992
        $index = 0;
1993
        foreach ($params as $datetime) {
1994
            $lt->add($query->expr()->lt("IDENTITY($field)", ":lt_{$alias}_{$index}"));
1995
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
1996
            $index++;
1997
        }
1998
1999
        return $lt;
2000
    }
2001
2002
    /**
2003
     * @param \Doctrine\ORM\QueryBuilder $query
2004
     * @param string $field
2005
     * @param array $params
2006
     * @param string $alias
2007
     * @return mixed
2008
     */
2009
    private function entityLteExpression($query, $field, $params, $alias)
2010
    {
2011
        $lte = $query->expr()->orX();
2012
        $index = 0;
2013
        foreach ($params as $datetime) {
2014
            $lte->add($query->expr()->lte("IDENTITY($field)", ":lte_{$alias}_{$index}"));
2015
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2016
            $index++;
2017
        }
2018
2019
        return $lte;
2020
    }
2021
2022
    /**
2023
     * @param \Doctrine\ORM\QueryBuilder $query
2024
     * @param string $field
2025
     * @param array $params
2026
     * @param string $alias
2027
     * @return mixed
2028
     */
2029
    private function entityGtExpression($query, $field, $params, $alias)
2030
    {
2031
        $gt = $query->expr()->orX();
2032
        $index = 0;
2033
        foreach ($params as $datetime) {
2034
            $gt->add($query->expr()->gt("IDENTITY($field)", ":gt_{$alias}_{$index}"));
2035
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2036
            $index++;
2037
        }
2038
2039
        return $gt;
2040
    }
2041
2042
    /**
2043
     * @param \Doctrine\ORM\QueryBuilder $query
2044
     * @param string $field
2045
     * @param array $params
2046
     * @param string $alias
2047
     * @return mixed
2048
     */
2049
    private function entityGteExpression($query, $field, $params, $alias)
2050
    {
2051
        $gte = $query->expr()->orX();
2052
        $index = 0;
2053
        foreach ($params as $datetime) {
2054
            $gte->add($query->expr()->gte("IDENTITY($field)", ":gte_{$alias}_{$index}"));
2055
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2056
            $index++;
2057
        }
2058
2059
        return $gte;
2060
    }
2061
2062
    /**
2063
     * Translates params into where conditions. The subquery must really return an integer for it to work!
2064
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
2065
     * on a subquery. And it seems to be impossible to not return null values either
2066
     * Todo: Needs research, for time being only true comparison is working as expected
2067
     *
2068
     *
2069
     * @param $subquery
2070
     * @param \Doctrine\ORM\QueryBuilder $query
2071
     * @param string $alias
2072
     * @param \UnserAller_Model_User $currentUser
2073
     * @param array $methods
2074
     * @return \Doctrine\ORM\Query\Expr\Andx
2075
     * @uses subqueryFalseExpression
2076
     * @uses subqueryTrueExpression
2077
     * @uses subqueryGtExpression
2078
     * @uses subqueryGteExpression
2079
     * @uses subqueryLtExpression
2080
     * @uses subqueryLteExpression
2081
     * @uses subqueryEqExpression
2082
     * @uses subqueryAnyExpression
2083
     * @uses subqueryNullExpression
2084
     */
2085 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...
2086
    {
2087
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
2088
            $methods,
2089
            ['false', 'true', 'gt', 'gte', 'lt', 'lte', 'eq', 'any', 'null']
2090
        )
2091
        ) {
2092
            throw new \InvalidArgumentException('Invalid expression methods used');
2093
        }
2094
2095
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2096
    }
2097
2098
    /**
2099
     * @param $subquery
2100
     * @param \Doctrine\ORM\QueryBuilder $query
2101
     * @param string $alias
2102
     * @param \UnserAller_Model_User $currentUser
2103
     * @param array $methods
2104
     * @return \Doctrine\ORM\Query\Expr\Andx
2105
     */
2106 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...
2107
    {
2108
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
2109
            throw new \InvalidArgumentException('Invalid expression methods used');
2110
        }
2111
2112
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2113
    }
2114
2115
    /**
2116
     * Translates params into where conditions. The subquery must really return an integer for it to work!
2117
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
2118
     * on a subquery. And it seems to be impossible to not return null values either
2119
     * Todo: Needs research, for time being only true comparison is working as expected
2120
     *
2121
     *
2122
     * @param $subquery
2123
     * @param \Doctrine\ORM\QueryBuilder $query
2124
     * @param string $alias
2125
     * @param \UnserAller_Model_User $currentUser
2126
     * @param array $methods
2127
     * @return \Doctrine\ORM\Query\Expr\Andx
2128
     * @uses subqueryAnyisExpression
2129
     */
2130 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...
2131
    {
2132
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
2133
            throw new \InvalidArgumentException('Invalid expression methods used');
2134
        }
2135
2136
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2137
    }
2138
2139
    /**
2140
     * Translates params into where conditions. The subquery must really return an integer for it to work!
2141
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
2142
     * on a subquery. And it seems to be impossible to not return null values either
2143
     * Todo: Needs research, for time being only true comparison is working as expected
2144
     *
2145
     *
2146
     * @param $subquery
2147
     * @param \Doctrine\ORM\QueryBuilder $query
2148
     * @param string $alias
2149
     * @param \UnserAller_Model_User $currentUser
2150
     * @param array $methods
2151
     * @return \Doctrine\ORM\Query\Expr\Andx
2152
     * @uses subqueryTrueExpression
2153
     * @uses subqueryFalseExpression
2154
     */
2155 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...
2156
    {
2157
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true'])) {
2158
            throw new \InvalidArgumentException('Invalid expression methods used');
2159
        }
2160
2161
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2162
    }
2163
2164
    /**
2165
     * Translates params into where conditions. Null values are handled as you would expect it.
2166
     *
2167
     * @param $col
2168
     * @param \Doctrine\ORM\QueryBuilder $query
2169
     * @param string $alias
2170
     * @param \UnserAller_Model_User $currentUser
2171
     * @param array $methods
2172
     * @return \Doctrine\ORM\Query\Expr\Andx
2173
     * @uses integerIsExpression
2174
     * @uses integerNotExpression
2175
     * @uses integerGtExpression
2176
     * @uses integerGteExpression
2177
     * @uses integerLtExpression
2178
     * @uses integerLteExpression
2179
     * @uses integerFalseExpression
2180
     * @uses integerTrueExpression
2181
     */
2182 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...
2183
    {
2184
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
2185
            $methods,
2186
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true']
2187
        )
2188
        ) {
2189
            throw new \InvalidArgumentException('Invalid expression methods used');
2190
        }
2191
2192
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
2193
    }
2194
2195
    /**
2196
     * Todo: Whitelisting allowed subqueries for the any filter makes having this extra function unnecessary
2197
     *
2198
     * This one allows some filter directives that result to function calls on protected methods. Don't ever redirect
2199
     * user content here.
2200
     *
2201
     * Translates params into where conditions. Null values are handled as you would expect it.
2202
     *
2203
     * @param $col
2204
     * @param \Doctrine\ORM\QueryBuilder $query
2205
     * @param string $alias
2206
     * @param \UnserAller_Model_User $currentUser
2207
     * @param array $methods
2208
     * @return \Doctrine\ORM\Query\Expr\Andx
2209
     * @uses integerIsExpression
2210
     * @uses integerNotExpression
2211
     * @uses integerGtExpression
2212
     * @uses integerGteExpression
2213
     * @uses integerLtExpression
2214
     * @uses integerLteExpression
2215
     * @uses integerFalseExpression
2216
     * @uses integerTrueExpression
2217
     * @uses integerAnyExpression
2218
     */
2219 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...
2220
    {
2221
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
2222
            $methods,
2223
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true', 'any']
2224
        )
2225
        ) {
2226
            throw new \InvalidArgumentException('Invalid expression methods used');
2227
        }
2228
2229
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
2230
    }
2231
2232
    /**
2233
     * Knows how to create a callable from a subquery definition
2234
     *
2235
     * @param string $name of subquery
2236
     * @param mixed[] $params for subquerymethod
2237
     * @return callable
2238
     */
2239
    protected function locateCallableSubquery($name, $params)
2240
    {
2241
        return [$this, $name];
2242
    }
2243
2244
    /**
2245
     * @param array $subqueryDefinition
2246
     * @return string DQL
2247
     */
2248
    private function consumeSubquery($subqueryDefinition)
2249
    {
2250
        list($name, $params) = $subqueryDefinition;
2251
        return call_user_func_array(
2252
            $this->locateCallableSubquery($name, $params),
2253
            $params
2254
        );
2255
    }
2256
2257
    /**
2258
     * @param $prefix
2259
     * @param string $field
2260
     * @param \Doctrine\ORM\QueryBuilder $query
2261
     * @param string $alias
2262
     * @param \UnserAller_Model_User $currentUser
2263
     * @param array $methods
2264
     * @return \Doctrine\ORM\Query\Expr\Andx
2265
     */
2266
    private function createExpression($prefix, $field, $query, $alias, $currentUser, $methods)
2267
    {
2268
        $expression = $query->expr()->andX();
2269
        foreach ($methods as $method => $params) {
2270
            $expression->add(call_user_func_array(
2271
                [$this, $prefix . ucfirst($method) . 'Expression'],
2272
                [$query, $field, $params, $alias, $currentUser]
2273
            ));
2274
        }
2275
2276
        return $expression;
2277
    }
2278
2279
    /**
2280
     * @param \Doctrine\ORM\QueryBuilder $query
2281
     * @param array $field
2282
     * @param array $params
2283
     * @param string $alias
2284
     * @return mixed
2285
     */
2286
    private function subqueryFalseExpression($query, $field, $params, $alias)
2287
    {
2288
        return $query->expr()->orX(
2289
            $query->expr()->not($query->expr()->exists($this->consumeSubquery($field))),
2290
            $query->expr()->eq('(' . $this->consumeSubquery($field) . ')', 0)
2291
        );
2292
    }
2293
2294
    /**
2295
     * @param \Doctrine\ORM\QueryBuilder $query
2296
     * @param array $field
2297
     * @param array $params
2298
     * @param string $alias
2299
     * @return mixed
2300
     */
2301
    private function subqueryNullExpression($query, $field, $params, $alias)
2302
    {
2303
        return $query->expr()->not($query->expr()->exists($this->consumeSubquery($field)));
2304
    }
2305
2306
    /**
2307
     * @param \Doctrine\ORM\QueryBuilder $query
2308
     * @param array $subquery
2309
     * @param array $params
2310
     * @param string $alias
2311
     * @return mixed
2312
     */
2313 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...
2314
    {
2315
        return $query->expr()->andX(
2316
            $query->expr()->exists($this->consumeSubquery($subquery)),
2317
            $query->expr()->neq('(' . $this->consumeSubquery($subquery) . ')', 0)
2318
        );
2319
    }
2320
2321
    /**
2322
     * @param \Doctrine\ORM\QueryBuilder $query
2323
     * @param array $subquery
2324
     * @param array $params
2325
     * @param string $alias
2326
     * @return mixed
2327
     */
2328
    private function subqueryAnyisExpression($query, $subquery, $params, $alias)
2329
    {
2330
        $expression = $query->expr()->orX();
2331
        foreach ($params as $param) {
2332
            $alias = uniqid();
2333
            $query->setParameter("param$alias", $param);
2334
            $expression->add(
2335
                $query->expr()->eq(":param$alias", $query->expr()->any($this->consumeSubquery($subquery)))
2336
            );
2337
        }
2338
        return $expression;
2339
    }
2340
2341
    /**
2342
     * @param \Doctrine\ORM\QueryBuilder $query
2343
     * @param array $subquery
2344
     * @param array $params
2345
     * @param string $alias
2346
     * @return mixed
2347
     */
2348 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...
2349
    {
2350
        return $query->expr()->andX(
2351
            $query->expr()->exists($this->consumeSubquery($subquery)),
2352
            $query->expr()->gt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2353
        );
2354
    }
2355
2356
    /**
2357
     * @param \Doctrine\ORM\QueryBuilder $query
2358
     * @param array $subquery
2359
     * @param array $params
2360
     * @param string $alias
2361
     * @return mixed
2362
     */
2363 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...
2364
    {
2365
        return $query->expr()->andX(
2366
            $query->expr()->exists($this->consumeSubquery($subquery)),
2367
            $query->expr()->gte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2368
        );
2369
    }
2370
2371
    /**
2372
     * @param \Doctrine\ORM\QueryBuilder $query
2373
     * @param array $subquery
2374
     * @param array $params
2375
     * @param string $alias
2376
     * @return mixed
2377
     */
2378 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...
2379
    {
2380
        return $query->expr()->andX(
2381
            $query->expr()->exists($this->consumeSubquery($subquery)),
2382
            $query->expr()->lte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2383
        );
2384
    }
2385
2386
    /**
2387
     * @param \Doctrine\ORM\QueryBuilder $query
2388
     * @param array $subquery
2389
     * @param array $params
2390
     * @param string $alias
2391
     * @return mixed
2392
     */
2393 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...
2394
    {
2395
        return $query->expr()->andX(
2396
            $query->expr()->exists($this->consumeSubquery($subquery)),
2397
            $query->expr()->lt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2398
        );
2399
    }
2400
2401
    /**
2402
     * @param \Doctrine\ORM\QueryBuilder $query
2403
     * @param array $subquery
2404
     * @param array $params
2405
     * @param string $alias
2406
     * @return mixed
2407
     */
2408 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...
2409
    {
2410
        return $query->expr()->andX(
2411
            $query->expr()->exists($this->consumeSubquery($subquery)),
2412
            $query->expr()->eq('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2413
        );
2414
    }
2415
2416
    /**
2417
     * @param \Doctrine\ORM\QueryBuilder $query
2418
     * @param array $subquery
2419
     * @param array $params
2420
     * @param string $alias
2421
     * @return mixed
2422
     */
2423
    private function subqueryIsExpression($query, $subquery, $params, $alias)
2424
    {
2425
        return $query->expr()->in('(' . $this->consumeSubquery($subquery) . ')', $params);
2426
    }
2427
2428
    /**
2429
     * @param \Doctrine\ORM\QueryBuilder $query
2430
     * @param string $field
2431
     * @param array $params
2432
     * @param string $alias
2433
     * @return mixed
2434
     */
2435
    private function dateIsExpression($query, $field, $params, $alias)
2436
    {
2437
        return $query->expr()->in($field, $params);
2438
    }
2439
2440
    /**
2441
     * @param \Doctrine\ORM\QueryBuilder $query
2442
     * @param string $field
2443
     * @param array $params
2444
     * @param string $alias
2445
     * @return mixed
2446
     */
2447
    private function integerIsExpression($query, $field, $params, $alias)
2448
    {
2449
        return $query->expr()->in($field, $params);
2450
    }
2451
2452
    /**
2453
     * @param \Doctrine\ORM\QueryBuilder $query
2454
     * @param string $field
2455
     * @param array $params
2456
     * @param string $alias
2457
     * @return mixed
2458
     */
2459
    private function stringIsExpression($query, $field, $params, $alias)
2460
    {
2461
        return $query->expr()->in($field, $params);
2462
    }
2463
2464
    /**
2465
     * @param \Doctrine\ORM\QueryBuilder $query
2466
     * @param string $field
2467
     * @param array $params
2468
     * @param string $alias
2469
     * @return \Doctrine\ORM\Query\Expr\Comparison
2470
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2471
     */
2472
    private function integerAnyExpression($query, $field, $params, $alias)
2473
    {
2474
        return $query->expr()->eq($field, $query->expr()->any($this->consumeSubquery($params)));
2475
    }
2476
2477
    /**
2478
     * @param \Doctrine\ORM\QueryBuilder $query
2479
     * @param string $field
2480
     * @param array $params
2481
     * @param string $alias
2482
     * @return \Doctrine\ORM\Query\Expr\Func
2483
     */
2484
    private function integerNotExpression($query, $field, $params, $alias)
2485
    {
2486
        return $query->expr()->notIn($field, $params);
2487
    }
2488
2489
    /**
2490
     * @param \Doctrine\ORM\QueryBuilder $query
2491
     * @param string $field
2492
     * @param array $params
2493
     * @param string $alias
2494
     * @return \Doctrine\ORM\Query\Expr\Func
2495
     */
2496
    private function dateNotExpression($query, $field, $params, $alias)
2497
    {
2498
        return $query->expr()->notIn($field, $params);
2499
    }
2500
2501
    /**
2502
     * @param \Doctrine\ORM\QueryBuilder $query
2503
     * @param string $field
2504
     * @param array $params
2505
     * @param string $alias
2506
     * @return mixed
2507
     */
2508
    private function stringNotExpression($query, $field, $params, $alias)
2509
    {
2510
        return $query->expr()->notIn($field, $params);
2511
    }
2512
2513
    /**
2514
     * @param \Doctrine\ORM\QueryBuilder $query
2515
     * @param string $field
2516
     * @param array $params
2517
     * @param string $alias
2518
     * @return \Doctrine\ORM\Query\Expr\Comparison
2519
     */
2520
    private function integerFalseExpression($query, $field, $params, $alias)
2521
    {
2522
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2523
    }
2524
2525
    /**
2526
     * @param \Doctrine\ORM\QueryBuilder $query
2527
     * @param string $field
2528
     * @param array $params
2529
     * @param string $alias
2530
     * @return \Doctrine\ORM\Query\Expr\Comparison
2531
     */
2532
    private function dateFalseExpression($query, $field, $params, $alias)
2533
    {
2534
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2535
    }
2536
2537
    /**
2538
     * @param \Doctrine\ORM\QueryBuilder $query
2539
     * @param string $field
2540
     * @param array $params
2541
     * @param string $alias
2542
     * @return \Doctrine\ORM\Query\Expr\Base
2543
     */
2544
    private function stringFalseExpression($query, $field, $params, $alias)
2545
    {
2546
        return $query->expr()->orX(
2547
            $query->expr()->isNull($field),
2548
            $query->expr()->eq($field, "''")
2549
        );
2550
    }
2551
2552
    /**
2553
     * @param \Doctrine\ORM\QueryBuilder $query
2554
     * @param string $field
2555
     * @param array $params
2556
     * @param string $alias
2557
     * @return \Doctrine\ORM\Query\Expr\Comparison
2558
     */
2559
    private function integerTrueExpression($query, $field, $params, $alias)
2560
    {
2561
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2562
    }
2563
2564
    /**
2565
     * @param \Doctrine\ORM\QueryBuilder $query
2566
     * @param string $field
2567
     * @param array $params
2568
     * @param string $alias
2569
     * @return \Doctrine\ORM\Query\Expr\Comparison
2570
     */
2571
    private function dateTrueExpression($query, $field, $params, $alias)
2572
    {
2573
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2574
    }
2575
2576
    /**
2577
     * @param \Doctrine\ORM\QueryBuilder $query
2578
     * @param string $field
2579
     * @param array $params
2580
     * @param string $alias
2581
     * @return \Doctrine\ORM\Query\Expr\Base
2582
     */
2583
    private function stringTrueExpression($query, $field, $params, $alias)
2584
    {
2585
        return $query->expr()->andX(
2586
            $query->expr()->isNotNull($field),
2587
            $query->expr()->neq($field, "''")
2588
        );
2589
    }
2590
2591
    /**
2592
     * @param \Doctrine\ORM\QueryBuilder $query
2593
     * @param string $field
2594
     * @param array $params
2595
     * @param string $alias
2596
     * @return mixed
2597
     */
2598 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...
2599
    {
2600
        $contains = $query->expr()->orX();
2601
2602
        $index = 0;
2603
        foreach ($params as $string) {
2604
            $contains->add($query->expr()->like($field, ":contains_{$alias}_{$index}"));
2605
            $query->setParameter("contains_{$alias}_{$index}", '%' . $string . '%');
2606
            $index++;
2607
        }
2608
2609
        return $contains;
2610
    }
2611
2612
    /**
2613
     * @param \Doctrine\ORM\QueryBuilder $query
2614
     * @param string $field
2615
     * @param array $params
2616
     * @param string $alias
2617
     * @return mixed
2618
     */
2619
    private function stringContainExpression($query, $field, $params, $alias)
2620
    {
2621
        return $this->stringContainsExpression($query, $field, $params, $alias);
2622
    }
2623
2624
    /**
2625
     * @param \Doctrine\ORM\QueryBuilder $query
2626
     * @param string $field
2627
     * @param array $params
2628
     * @param string $alias
2629
     * @return mixed
2630
     */
2631 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...
2632
    {
2633
        $lt = $query->expr()->orX();
2634
        $index = 0;
2635
        foreach ($params as $datetime) {
2636
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2637
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2638
            $index++;
2639
        }
2640
2641
        return $lt;
2642
    }
2643
2644
    /**
2645
     * @param \Doctrine\ORM\QueryBuilder $query
2646
     * @param string $field
2647
     * @param array $params
2648
     * @param string $alias
2649
     * @return mixed
2650
     */
2651 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...
2652
    {
2653
        $lt = $query->expr()->orX();
2654
        $index = 0;
2655
        foreach ($params as $datetime) {
2656
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2657
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2658
            $index++;
2659
        }
2660
2661
        return $lt;
2662
    }
2663
2664
    /**
2665
     * @param \Doctrine\ORM\QueryBuilder $query
2666
     * @param string $field
2667
     * @param array $params
2668
     * @param string $alias
2669
     * @return mixed
2670
     */
2671 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...
2672
    {
2673
        $lte = $query->expr()->orX();
2674
        $index = 0;
2675
        foreach ($params as $datetime) {
2676
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2677
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2678
            $index++;
2679
        }
2680
2681
        return $lte;
2682
    }
2683
2684
    /**
2685
     * @param \Doctrine\ORM\QueryBuilder $query
2686
     * @param string $field
2687
     * @param array $params
2688
     * @param string $alias
2689
     * @return mixed
2690
     */
2691 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...
2692
    {
2693
        $lte = $query->expr()->orX();
2694
        $index = 0;
2695
        foreach ($params as $datetime) {
2696
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2697
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2698
            $index++;
2699
        }
2700
2701
        return $lte;
2702
    }
2703
2704
    /**
2705
     * @param \Doctrine\ORM\QueryBuilder $query
2706
     * @param string $field
2707
     * @param array $params
2708
     * @param string $alias
2709
     * @return mixed
2710
     */
2711 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...
2712
    {
2713
        $gt = $query->expr()->orX();
2714
        $index = 0;
2715
        foreach ($params as $datetime) {
2716
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2717
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2718
            $index++;
2719
        }
2720
2721
        return $gt;
2722
    }
2723
2724
    /**
2725
     * @param \Doctrine\ORM\QueryBuilder $query
2726
     * @param string $field
2727
     * @param array $params
2728
     * @param string $alias
2729
     * @return mixed
2730
     */
2731 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...
2732
    {
2733
        $gt = $query->expr()->orX();
2734
        $index = 0;
2735
        foreach ($params as $datetime) {
2736
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2737
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2738
            $index++;
2739
        }
2740
2741
        return $gt;
2742
    }
2743
2744
    /**
2745
     * @return string
2746
     */
2747
    protected function getModelForMeta()
2748
    {
2749
        return uniqid('UnknownClass');
2750
    }
2751
2752
    /**
2753
     * @return string
2754
     */
2755
    public function getClassnameForRepresentedModel()
2756
    {
2757
        return $this->getModelForMeta();
2758
    }
2759
2760
    /**
2761
     * @param \Doctrine\ORM\QueryBuilder $query
2762
     * @param string $field
2763
     * @param array $params
2764
     * @param string $alias
2765
     * @return mixed
2766
     */
2767 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...
2768
    {
2769
        $gte = $query->expr()->orX();
2770
        $index = 0;
2771
        foreach ($params as $datetime) {
2772
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2773
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2774
            $index++;
2775
        }
2776
2777
        return $gte;
2778
    }
2779
2780
    /**
2781
     * @param \Doctrine\ORM\QueryBuilder $query
2782
     * @param string $field
2783
     * @param array $params
2784
     * @param string $alias
2785
     * @return mixed
2786
     */
2787 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...
2788
    {
2789
        $gte = $query->expr()->orX();
2790
        $index = 0;
2791
        foreach ($params as $datetime) {
2792
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2793
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2794
            $index++;
2795
        }
2796
2797
        return $gte;
2798
    }
2799
2800
    /**
2801
     * Does some crazy things
2802
     *
2803
     * @param string $value
2804
     * @return array
2805
     */
2806
    private function filterJsonAfterwards($value)
2807
    {
2808
        return json_decode($value, true);
2809
    }
2810
2811
    /**
2812
     * Does some crazy things
2813
     *
2814
     * @param string $value
2815
     * @return mixed
2816
     */
2817
    private function filterJsonIfNullSetEmptyObjectAfterwards($value)
2818
    {
2819
        return $value === null ? new \stdClass() : json_decode($value, true);
2820
    }
2821
2822
    /**
2823
     * Does some crazy things
2824
     *
2825
     * @param string $value
2826
     * @return string
2827
     */
2828
    private function filterNl2BrAfterwards($value)
2829
    {
2830
        return nl2br($value, false);
2831
    }
2832
2833
    /**
2834
     * Does some crazy things
2835
     *
2836
     * @param string $value
2837
     * @return array
2838
     */
2839
    private function filterJsonOrNullAfterwards($value)
2840
    {
2841
        return $value === null ? null : json_decode($value, true);
2842
    }
2843
2844
    /**
2845
     * Too complex to explain
2846
     *
2847
     * @param string $value
2848
     * @return \DateTime
2849
     */
2850
    private function filterDatetimeAfterwards($value)
2851
    {
2852
        return new \DateTime($value);
2853
    }
2854
2855
    /**
2856
     * Too complex to explain
2857
     *
2858
     * @param string $value
2859
     * @return \DateTime
2860
     */
2861
    private function filterDatetimeOrNullAfterwards($value)
2862
    {
2863
        return $value === null ? null : new \DateTime($value);
2864
    }
2865
2866
    /**
2867
     * Too complex to explain
2868
     *
2869
     * @param string|null $value
2870
     * @return int|null
2871
     */
2872
    private function filterIntOrNullAfterwards($value)
2873
    {
2874
        return $value === null ? null : (int)$value;
2875
    }
2876
2877
    /**
2878
     * Returns the current resultArrayFixSchedule. Afterwards the schedule will be empty again.
2879
     *
2880
     * @return array
2881
     */
2882
    private function flushResultArrayFixSchedule()
2883
    {
2884
        $scheduledFixes = $this->resultArrayFixSchedule;
2885
        $this->resultArrayFixSchedule = [];
2886
        return $scheduledFixes;
2887
    }
2888
2889
    /**
2890
     * Returns true if $alias was used in $query already - false otherwise
2891
     *
2892
     * @param \Doctrine\ORM\QueryBuilder $query
2893
     * @param string $alias
2894
     * @return bool
2895
     */
2896
    protected function wasAliasUsed($query, $alias)
2897
    {
2898
        return in_array($alias, $query->getAllAliases());
2899
    }
2900
2901
    /**
2902
     * Returns true if $alias was used in $query already - false otherwise
2903
     *
2904
     * @param \Doctrine\ORM\QueryBuilder $query
2905
     * @param string $alias
2906
     * @return bool
2907
     */
2908
    protected function wasntAliasUsed($query, $alias)
2909
    {
2910
        return !$this->wasAliasUsed($query, $alias);
2911
    }
2912
2913
    /**
2914
     * @return array
2915
     */
2916
    public function getUnsortedParams()
2917
    {
2918
        return $this->unsortedParams;
2919
    }
2920
2921
    /**
2922
     * @param array $unsortedParams
2923
     * @return $this
2924
     */
2925
    public function setUnsortedParams($unsortedParams)
2926
    {
2927
        $this->unsortedParams = $unsortedParams;
2928
2929
        return $this;
2930
    }
2931
2932
    /**
2933
     * @param \Doctrine\ORM\QueryBuilder $query
2934
     * @param string $translationName
2935
     * @param string $language
2936
     * @return string alias of joined translation table
2937
     */
2938
    protected function joinTranslationOnce($query, $translationName, $language)
2939
    {
2940
        $alias = 'translation' . $translationName . $language;
2941
2942
        if ($this->wasAliasUsed($query, $alias)) {
2943
            return $alias;
2944
        }
2945
2946
        $rootAlias = $this->getRootAlias($query);
2947
2948
        $query->setParameter("name$alias", $translationName);
2949
        $query->setParameter("target$alias", $language);
2950
2951
        $query->leftJoin(
2952
            'UnserAller_Model_Translation',
2953
            $alias,
2954
            'WITH',
2955
            "$alias.name = CONCAT(:name$alias,$rootAlias.id) AND $alias.target = :target$alias"
2956
        );
2957
2958
        return $alias;
2959
    }
2960
2961
    /**
2962
     * @param \Doctrine\ORM\QueryBuilder $query
2963
     * @param string $alias
2964
     * @param string $col
2965
     * @param string $name
2966
     * @param string $translationName
2967
     * @param string $language
2968
     * @return array
2969
     */
2970
    protected function abstractIncludeMultilanguageStringColumn(
2971
        $query,
2972
        $alias,
2973
        $col,
2974
        $name,
2975
        $translationName,
2976
        $language
2977
    ) {
2978
        if (!$language) {
2979
            $query->addSelect("($col) $alias");
2980
        } else {
2981
            $query->addSelect("(COALESCE(" . $this->joinTranslationOnce(
2982
                $query,
2983
                $translationName,
2984
                $language
2985
            ) . ".translation,$col)) $alias");
2986
        }
2987
2988
        return [
2989
            $alias,
2990
            'move' => $name
2991
        ];
2992
    }
2993
2994
    protected function getAdditionalUserParamOrFail(&$additionalParams)
2995
    {
2996
        if (!isset($additionalParams['user'][0])) {
2997
            throw new \InvalidArgumentException('User identifier required but not given');
2998
        }
2999
3000
        $param = $additionalParams['user'];
3001
        unset($additionalParams['user']);
3002
        return \UnserAllerLib_Validate_Helper::integerOrFail($param[0], 1);
3003
    }
3004
}
3005