Completed
Push — master ( a8ed33...7a754b )
by
unknown
01:32
created

Vortex::entityIsExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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 entityIsExpression($query, $field, $params, $alias)
1904
    {
1905
        return $query->expr()->in($field, $params);
1906
    }
1907
1908
    /**
1909
     * @param \Doctrine\ORM\QueryBuilder $query
1910
     * @param string $field
1911
     * @param array $params
1912
     * @param string $alias
1913
     * @return mixed
1914
     */
1915
    private function entityIsOrNullExpression($query, $field, $params, $alias)
1916
    {
1917
        return $query->expr()->orX(
1918
            $query->expr()->in($field, $params),
1919
            $query->expr()->isNull($field)
1920
        );
1921
    }
1922
1923
    /**
1924
     * @param \Doctrine\ORM\QueryBuilder $query
1925
     * @param string $field
1926
     * @param array $params
1927
     * @param string $alias
1928
     * @param \UnserAller_Model_User $currentUser
1929
     * @return mixed
1930
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
1931
     */
1932
    private function entityMeExpression($query, $field, $params, $alias, $currentUser)
1933
    {
1934
        if (!$currentUser) {
1935
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
1936
        }
1937
        return $query->expr()->eq($field, $currentUser->getId());
1938
    }
1939
1940
1941
    /**
1942
     * @param \Doctrine\ORM\QueryBuilder $query
1943
     * @param string $field
1944
     * @param array $params
1945
     * @param string $alias
1946
     * @param \UnserAller_Model_User $currentUser
1947
     * @return \Doctrine\ORM\Query\Expr\Comparison
1948
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
1949
     */
1950
    private function entityNotmeExpression($query, $field, $params, $alias, $currentUser)
1951
    {
1952
        if (!$currentUser) {
1953
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
1954
        }
1955
        return $query->expr()->neq($field, $currentUser->getId());
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\Func
1964
     */
1965
    private function entityNotExpression($query, $field, $params, $alias)
1966
    {
1967
        return $query->expr()->notIn($field, $params);
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 entityFalseExpression($query, $field, $params, $alias)
1978
    {
1979
        return $query->expr()->eq("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 \Doctrine\ORM\Query\Expr\Comparison
1988
     */
1989
    private function entityTrueExpression($query, $field, $params, $alias)
1990
    {
1991
        return $query->expr()->neq("COALESCE(IDENTITY($field),0)", 0);
1992
    }
1993
1994
    /**
1995
     * @param \Doctrine\ORM\QueryBuilder $query
1996
     * @param string $field
1997
     * @param array $params
1998
     * @param string $alias
1999
     * @return mixed
2000
     */
2001
    private function entityLtExpression($query, $field, $params, $alias)
2002
    {
2003
        $lt = $query->expr()->orX();
2004
        $index = 0;
2005
        foreach ($params as $datetime) {
2006
            $lt->add($query->expr()->lt("IDENTITY($field)", ":lt_{$alias}_{$index}"));
2007
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2008
            $index++;
2009
        }
2010
2011
        return $lt;
2012
    }
2013
2014
    /**
2015
     * @param \Doctrine\ORM\QueryBuilder $query
2016
     * @param string $field
2017
     * @param array $params
2018
     * @param string $alias
2019
     * @return mixed
2020
     */
2021
    private function entityLteExpression($query, $field, $params, $alias)
2022
    {
2023
        $lte = $query->expr()->orX();
2024
        $index = 0;
2025
        foreach ($params as $datetime) {
2026
            $lte->add($query->expr()->lte("IDENTITY($field)", ":lte_{$alias}_{$index}"));
2027
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2028
            $index++;
2029
        }
2030
2031
        return $lte;
2032
    }
2033
2034
    /**
2035
     * @param \Doctrine\ORM\QueryBuilder $query
2036
     * @param string $field
2037
     * @param array $params
2038
     * @param string $alias
2039
     * @return mixed
2040
     */
2041
    private function entityGtExpression($query, $field, $params, $alias)
2042
    {
2043
        $gt = $query->expr()->orX();
2044
        $index = 0;
2045
        foreach ($params as $datetime) {
2046
            $gt->add($query->expr()->gt("IDENTITY($field)", ":gt_{$alias}_{$index}"));
2047
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2048
            $index++;
2049
        }
2050
2051
        return $gt;
2052
    }
2053
2054
    /**
2055
     * @param \Doctrine\ORM\QueryBuilder $query
2056
     * @param string $field
2057
     * @param array $params
2058
     * @param string $alias
2059
     * @return mixed
2060
     */
2061
    private function entityGteExpression($query, $field, $params, $alias)
2062
    {
2063
        $gte = $query->expr()->orX();
2064
        $index = 0;
2065
        foreach ($params as $datetime) {
2066
            $gte->add($query->expr()->gte("IDENTITY($field)", ":gte_{$alias}_{$index}"));
2067
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2068
            $index++;
2069
        }
2070
2071
        return $gte;
2072
    }
2073
2074
    /**
2075
     * Translates params into where conditions. The subquery must really return an integer for it to work!
2076
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
2077
     * on a subquery. And it seems to be impossible to not return null values either
2078
     * Todo: Needs research, for time being only true comparison is working as expected
2079
     *
2080
     *
2081
     * @param $subquery
2082
     * @param \Doctrine\ORM\QueryBuilder $query
2083
     * @param string $alias
2084
     * @param \UnserAller_Model_User $currentUser
2085
     * @param array $methods
2086
     * @return \Doctrine\ORM\Query\Expr\Andx
2087
     * @uses subqueryFalseExpression
2088
     * @uses subqueryTrueExpression
2089
     * @uses subqueryGtExpression
2090
     * @uses subqueryGteExpression
2091
     * @uses subqueryLtExpression
2092
     * @uses subqueryLteExpression
2093
     * @uses subqueryEqExpression
2094
     * @uses subqueryAnyExpression
2095
     * @uses subqueryNullExpression
2096
     */
2097 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...
2098
    {
2099
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
2100
            $methods,
2101
            ['false', 'true', 'gt', 'gte', 'lt', 'lte', 'eq', 'any', 'null']
2102
        )
2103
        ) {
2104
            throw new \InvalidArgumentException('Invalid expression methods used');
2105
        }
2106
2107
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2108
    }
2109
2110
    /**
2111
     * @param $subquery
2112
     * @param \Doctrine\ORM\QueryBuilder $query
2113
     * @param string $alias
2114
     * @param \UnserAller_Model_User $currentUser
2115
     * @param array $methods
2116
     * @return \Doctrine\ORM\Query\Expr\Andx
2117
     */
2118 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...
2119
    {
2120
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
2121
            throw new \InvalidArgumentException('Invalid expression methods used');
2122
        }
2123
2124
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2125
    }
2126
2127
    /**
2128
     * Translates params into where conditions. The subquery must really return an integer for it to work!
2129
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
2130
     * on a subquery. And it seems to be impossible to not return null values either
2131
     * Todo: Needs research, for time being only true comparison is working as expected
2132
     *
2133
     *
2134
     * @param $subquery
2135
     * @param \Doctrine\ORM\QueryBuilder $query
2136
     * @param string $alias
2137
     * @param \UnserAller_Model_User $currentUser
2138
     * @param array $methods
2139
     * @return \Doctrine\ORM\Query\Expr\Andx
2140
     * @uses subqueryAnyisExpression
2141
     */
2142 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...
2143
    {
2144
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
2145
            throw new \InvalidArgumentException('Invalid expression methods used');
2146
        }
2147
2148
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2149
    }
2150
2151
    /**
2152
     * Translates params into where conditions. The subquery must really return an integer for it to work!
2153
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
2154
     * on a subquery. And it seems to be impossible to not return null values either
2155
     * Todo: Needs research, for time being only true comparison is working as expected
2156
     *
2157
     *
2158
     * @param $subquery
2159
     * @param \Doctrine\ORM\QueryBuilder $query
2160
     * @param string $alias
2161
     * @param \UnserAller_Model_User $currentUser
2162
     * @param array $methods
2163
     * @return \Doctrine\ORM\Query\Expr\Andx
2164
     * @uses subqueryTrueExpression
2165
     * @uses subqueryFalseExpression
2166
     */
2167 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...
2168
    {
2169
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true'])) {
2170
            throw new \InvalidArgumentException('Invalid expression methods used');
2171
        }
2172
2173
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
2174
    }
2175
2176
    /**
2177
     * Translates params into where conditions. Null values are handled as you would expect it.
2178
     *
2179
     * @param $col
2180
     * @param \Doctrine\ORM\QueryBuilder $query
2181
     * @param string $alias
2182
     * @param \UnserAller_Model_User $currentUser
2183
     * @param array $methods
2184
     * @return \Doctrine\ORM\Query\Expr\Andx
2185
     * @uses integerIsExpression
2186
     * @uses integerNotExpression
2187
     * @uses integerGtExpression
2188
     * @uses integerGteExpression
2189
     * @uses integerLtExpression
2190
     * @uses integerLteExpression
2191
     * @uses integerFalseExpression
2192
     * @uses integerTrueExpression
2193
     */
2194 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...
2195
    {
2196
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
2197
            $methods,
2198
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true']
2199
        )
2200
        ) {
2201
            throw new \InvalidArgumentException('Invalid expression methods used');
2202
        }
2203
2204
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
2205
    }
2206
2207
    /**
2208
     * Todo: Whitelisting allowed subqueries for the any filter makes having this extra function unnecessary
2209
     *
2210
     * This one allows some filter directives that result to function calls on protected methods. Don't ever redirect
2211
     * user content here.
2212
     *
2213
     * Translates params into where conditions. Null values are handled as you would expect it.
2214
     *
2215
     * @param $col
2216
     * @param \Doctrine\ORM\QueryBuilder $query
2217
     * @param string $alias
2218
     * @param \UnserAller_Model_User $currentUser
2219
     * @param array $methods
2220
     * @return \Doctrine\ORM\Query\Expr\Andx
2221
     * @uses integerIsExpression
2222
     * @uses integerNotExpression
2223
     * @uses integerGtExpression
2224
     * @uses integerGteExpression
2225
     * @uses integerLtExpression
2226
     * @uses integerLteExpression
2227
     * @uses integerFalseExpression
2228
     * @uses integerTrueExpression
2229
     * @uses integerAnyExpression
2230
     */
2231 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...
2232
    {
2233
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
2234
            $methods,
2235
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true', 'any']
2236
        )
2237
        ) {
2238
            throw new \InvalidArgumentException('Invalid expression methods used');
2239
        }
2240
2241
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
2242
    }
2243
2244
    /**
2245
     * Knows how to create a callable from a subquery definition
2246
     *
2247
     * @param string $name of subquery
2248
     * @param mixed[] $params for subquerymethod
2249
     * @return callable
2250
     */
2251
    protected function locateCallableSubquery($name, $params)
2252
    {
2253
        return [$this, $name];
2254
    }
2255
2256
    /**
2257
     * @param array $subqueryDefinition
2258
     * @return string DQL
2259
     */
2260
    private function consumeSubquery($subqueryDefinition)
2261
    {
2262
        list($name, $params) = $subqueryDefinition;
2263
        return call_user_func_array(
2264
            $this->locateCallableSubquery($name, $params),
2265
            $params
2266
        );
2267
    }
2268
2269
    /**
2270
     * @param $prefix
2271
     * @param string $field
2272
     * @param \Doctrine\ORM\QueryBuilder $query
2273
     * @param string $alias
2274
     * @param \UnserAller_Model_User $currentUser
2275
     * @param array $methods
2276
     * @return \Doctrine\ORM\Query\Expr\Andx
2277
     */
2278
    private function createExpression($prefix, $field, $query, $alias, $currentUser, $methods)
2279
    {
2280
        $expression = $query->expr()->andX();
2281
        foreach ($methods as $method => $params) {
2282
            $expression->add(call_user_func_array(
2283
                [$this, $prefix . ucfirst($method) . 'Expression'],
2284
                [$query, $field, $params, $alias, $currentUser]
2285
            ));
2286
        }
2287
2288
        return $expression;
2289
    }
2290
2291
    /**
2292
     * @param \Doctrine\ORM\QueryBuilder $query
2293
     * @param array $field
2294
     * @param array $params
2295
     * @param string $alias
2296
     * @return mixed
2297
     */
2298
    private function subqueryFalseExpression($query, $field, $params, $alias)
2299
    {
2300
        return $query->expr()->orX(
2301
            $query->expr()->not($query->expr()->exists($this->consumeSubquery($field))),
2302
            $query->expr()->eq('(' . $this->consumeSubquery($field) . ')', 0)
2303
        );
2304
    }
2305
2306
    /**
2307
     * @param \Doctrine\ORM\QueryBuilder $query
2308
     * @param array $field
2309
     * @param array $params
2310
     * @param string $alias
2311
     * @return mixed
2312
     */
2313
    private function subqueryNullExpression($query, $field, $params, $alias)
2314
    {
2315
        return $query->expr()->not($query->expr()->exists($this->consumeSubquery($field)));
2316
    }
2317
2318
    /**
2319
     * @param \Doctrine\ORM\QueryBuilder $query
2320
     * @param array $subquery
2321
     * @param array $params
2322
     * @param string $alias
2323
     * @return mixed
2324
     */
2325 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...
2326
    {
2327
        return $query->expr()->andX(
2328
            $query->expr()->exists($this->consumeSubquery($subquery)),
2329
            $query->expr()->neq('(' . $this->consumeSubquery($subquery) . ')', 0)
2330
        );
2331
    }
2332
2333
    /**
2334
     * @param \Doctrine\ORM\QueryBuilder $query
2335
     * @param array $subquery
2336
     * @param array $params
2337
     * @param string $alias
2338
     * @return mixed
2339
     */
2340
    private function subqueryAnyisExpression($query, $subquery, $params, $alias)
2341
    {
2342
        $expression = $query->expr()->orX();
2343
        foreach ($params as $param) {
2344
            $alias = uniqid();
2345
            $query->setParameter("param$alias", $param);
2346
            $expression->add(
2347
                $query->expr()->eq(":param$alias", $query->expr()->any($this->consumeSubquery($subquery)))
2348
            );
2349
        }
2350
        return $expression;
2351
    }
2352
2353
    /**
2354
     * @param \Doctrine\ORM\QueryBuilder $query
2355
     * @param array $subquery
2356
     * @param array $params
2357
     * @param string $alias
2358
     * @return mixed
2359
     */
2360 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...
2361
    {
2362
        return $query->expr()->andX(
2363
            $query->expr()->exists($this->consumeSubquery($subquery)),
2364
            $query->expr()->gt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2365
        );
2366
    }
2367
2368
    /**
2369
     * @param \Doctrine\ORM\QueryBuilder $query
2370
     * @param array $subquery
2371
     * @param array $params
2372
     * @param string $alias
2373
     * @return mixed
2374
     */
2375 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...
2376
    {
2377
        return $query->expr()->andX(
2378
            $query->expr()->exists($this->consumeSubquery($subquery)),
2379
            $query->expr()->gte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2380
        );
2381
    }
2382
2383
    /**
2384
     * @param \Doctrine\ORM\QueryBuilder $query
2385
     * @param array $subquery
2386
     * @param array $params
2387
     * @param string $alias
2388
     * @return mixed
2389
     */
2390 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...
2391
    {
2392
        return $query->expr()->andX(
2393
            $query->expr()->exists($this->consumeSubquery($subquery)),
2394
            $query->expr()->lte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2395
        );
2396
    }
2397
2398
    /**
2399
     * @param \Doctrine\ORM\QueryBuilder $query
2400
     * @param array $subquery
2401
     * @param array $params
2402
     * @param string $alias
2403
     * @return mixed
2404
     */
2405 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...
2406
    {
2407
        return $query->expr()->andX(
2408
            $query->expr()->exists($this->consumeSubquery($subquery)),
2409
            $query->expr()->lt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2410
        );
2411
    }
2412
2413
    /**
2414
     * @param \Doctrine\ORM\QueryBuilder $query
2415
     * @param array $subquery
2416
     * @param array $params
2417
     * @param string $alias
2418
     * @return mixed
2419
     */
2420 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...
2421
    {
2422
        return $query->expr()->andX(
2423
            $query->expr()->exists($this->consumeSubquery($subquery)),
2424
            $query->expr()->eq('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2425
        );
2426
    }
2427
2428
    /**
2429
     * @param \Doctrine\ORM\QueryBuilder $query
2430
     * @param array $subquery
2431
     * @param array $params
2432
     * @param string $alias
2433
     * @return mixed
2434
     */
2435
    private function subqueryIsExpression($query, $subquery, $params, $alias)
2436
    {
2437
        return $query->expr()->in('(' . $this->consumeSubquery($subquery) . ')', $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 dateIsExpression($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 integerIsExpression($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 mixed
2470
     */
2471
    private function stringIsExpression($query, $field, $params, $alias)
2472
    {
2473
        return $query->expr()->in($field, $params);
2474
    }
2475
2476
    /**
2477
     * @param \Doctrine\ORM\QueryBuilder $query
2478
     * @param string $field
2479
     * @param array $params
2480
     * @param string $alias
2481
     * @return \Doctrine\ORM\Query\Expr\Comparison
2482
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2483
     */
2484
    private function integerAnyExpression($query, $field, $params, $alias)
2485
    {
2486
        return $query->expr()->eq($field, $query->expr()->any($this->consumeSubquery($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 integerNotExpression($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 \Doctrine\ORM\Query\Expr\Func
2507
     */
2508
    private function dateNotExpression($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 mixed
2519
     */
2520
    private function stringNotExpression($query, $field, $params, $alias)
2521
    {
2522
        return $query->expr()->notIn($field, $params);
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 integerFalseExpression($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\Comparison
2543
     */
2544
    private function dateFalseExpression($query, $field, $params, $alias)
2545
    {
2546
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2547
    }
2548
2549
    /**
2550
     * @param \Doctrine\ORM\QueryBuilder $query
2551
     * @param string $field
2552
     * @param array $params
2553
     * @param string $alias
2554
     * @return \Doctrine\ORM\Query\Expr\Base
2555
     */
2556
    private function stringFalseExpression($query, $field, $params, $alias)
2557
    {
2558
        return $query->expr()->orX(
2559
            $query->expr()->isNull($field),
2560
            $query->expr()->eq($field, "''")
2561
        );
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 integerTrueExpression($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\Comparison
2582
     */
2583
    private function dateTrueExpression($query, $field, $params, $alias)
2584
    {
2585
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2586
    }
2587
2588
    /**
2589
     * @param \Doctrine\ORM\QueryBuilder $query
2590
     * @param string $field
2591
     * @param array $params
2592
     * @param string $alias
2593
     * @return \Doctrine\ORM\Query\Expr\Base
2594
     */
2595
    private function stringTrueExpression($query, $field, $params, $alias)
2596
    {
2597
        return $query->expr()->andX(
2598
            $query->expr()->isNotNull($field),
2599
            $query->expr()->neq($field, "''")
2600
        );
2601
    }
2602
2603
    /**
2604
     * @param \Doctrine\ORM\QueryBuilder $query
2605
     * @param string $field
2606
     * @param array $params
2607
     * @param string $alias
2608
     * @return mixed
2609
     */
2610 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...
2611
    {
2612
        $contains = $query->expr()->orX();
2613
2614
        $index = 0;
2615
        foreach ($params as $string) {
2616
            $contains->add($query->expr()->like($field, ":contains_{$alias}_{$index}"));
2617
            $query->setParameter("contains_{$alias}_{$index}", '%' . $string . '%');
2618
            $index++;
2619
        }
2620
2621
        return $contains;
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
    private function stringContainExpression($query, $field, $params, $alias)
2632
    {
2633
        return $this->stringContainsExpression($query, $field, $params, $alias);
2634
    }
2635
2636
    /**
2637
     * @param \Doctrine\ORM\QueryBuilder $query
2638
     * @param string $field
2639
     * @param array $params
2640
     * @param string $alias
2641
     * @return mixed
2642
     */
2643 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...
2644
    {
2645
        $lt = $query->expr()->orX();
2646
        $index = 0;
2647
        foreach ($params as $datetime) {
2648
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2649
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2650
            $index++;
2651
        }
2652
2653
        return $lt;
2654
    }
2655
2656
    /**
2657
     * @param \Doctrine\ORM\QueryBuilder $query
2658
     * @param string $field
2659
     * @param array $params
2660
     * @param string $alias
2661
     * @return mixed
2662
     */
2663 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...
2664
    {
2665
        $lt = $query->expr()->orX();
2666
        $index = 0;
2667
        foreach ($params as $datetime) {
2668
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2669
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2670
            $index++;
2671
        }
2672
2673
        return $lt;
2674
    }
2675
2676
    /**
2677
     * @param \Doctrine\ORM\QueryBuilder $query
2678
     * @param string $field
2679
     * @param array $params
2680
     * @param string $alias
2681
     * @return mixed
2682
     */
2683 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...
2684
    {
2685
        $lte = $query->expr()->orX();
2686
        $index = 0;
2687
        foreach ($params as $datetime) {
2688
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2689
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2690
            $index++;
2691
        }
2692
2693
        return $lte;
2694
    }
2695
2696
    /**
2697
     * @param \Doctrine\ORM\QueryBuilder $query
2698
     * @param string $field
2699
     * @param array $params
2700
     * @param string $alias
2701
     * @return mixed
2702
     */
2703 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...
2704
    {
2705
        $lte = $query->expr()->orX();
2706
        $index = 0;
2707
        foreach ($params as $datetime) {
2708
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2709
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2710
            $index++;
2711
        }
2712
2713
        return $lte;
2714
    }
2715
2716
    /**
2717
     * @param \Doctrine\ORM\QueryBuilder $query
2718
     * @param string $field
2719
     * @param array $params
2720
     * @param string $alias
2721
     * @return mixed
2722
     */
2723 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...
2724
    {
2725
        $gt = $query->expr()->orX();
2726
        $index = 0;
2727
        foreach ($params as $datetime) {
2728
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2729
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2730
            $index++;
2731
        }
2732
2733
        return $gt;
2734
    }
2735
2736
    /**
2737
     * @param \Doctrine\ORM\QueryBuilder $query
2738
     * @param string $field
2739
     * @param array $params
2740
     * @param string $alias
2741
     * @return mixed
2742
     */
2743 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...
2744
    {
2745
        $gt = $query->expr()->orX();
2746
        $index = 0;
2747
        foreach ($params as $datetime) {
2748
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2749
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2750
            $index++;
2751
        }
2752
2753
        return $gt;
2754
    }
2755
2756
    /**
2757
     * @return string
2758
     */
2759
    protected function getModelForMeta()
2760
    {
2761
        return uniqid('UnknownClass');
2762
    }
2763
2764
    /**
2765
     * @return string
2766
     */
2767
    public function getClassnameForRepresentedModel()
2768
    {
2769
        return $this->getModelForMeta();
2770
    }
2771
2772
    /**
2773
     * @param \Doctrine\ORM\QueryBuilder $query
2774
     * @param string $field
2775
     * @param array $params
2776
     * @param string $alias
2777
     * @return mixed
2778
     */
2779 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...
2780
    {
2781
        $gte = $query->expr()->orX();
2782
        $index = 0;
2783
        foreach ($params as $datetime) {
2784
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2785
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2786
            $index++;
2787
        }
2788
2789
        return $gte;
2790
    }
2791
2792
    /**
2793
     * @param \Doctrine\ORM\QueryBuilder $query
2794
     * @param string $field
2795
     * @param array $params
2796
     * @param string $alias
2797
     * @return mixed
2798
     */
2799 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...
2800
    {
2801
        $gte = $query->expr()->orX();
2802
        $index = 0;
2803
        foreach ($params as $datetime) {
2804
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2805
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2806
            $index++;
2807
        }
2808
2809
        return $gte;
2810
    }
2811
2812
    /**
2813
     * Does some crazy things
2814
     *
2815
     * @param string $value
2816
     * @return array
2817
     */
2818
    private function filterJsonAfterwards($value)
2819
    {
2820
        return json_decode($value, true);
2821
    }
2822
2823
    /**
2824
     * Does some crazy things
2825
     *
2826
     * @param string $value
2827
     * @return mixed
2828
     */
2829
    private function filterJsonIfNullSetEmptyObjectAfterwards($value)
2830
    {
2831
        return $value === null ? new \stdClass() : json_decode($value, true);
2832
    }
2833
2834
    /**
2835
     * Does some crazy things
2836
     *
2837
     * @param string $value
2838
     * @return string
2839
     */
2840
    private function filterNl2BrAfterwards($value)
2841
    {
2842
        return nl2br($value, false);
2843
    }
2844
2845
    /**
2846
     * Does some crazy things
2847
     *
2848
     * @param string $value
2849
     * @return array
2850
     */
2851
    private function filterJsonOrNullAfterwards($value)
2852
    {
2853
        return $value === null ? null : json_decode($value, true);
2854
    }
2855
2856
    /**
2857
     * Too complex to explain
2858
     *
2859
     * @param string $value
2860
     * @return \DateTime
2861
     */
2862
    private function filterDatetimeAfterwards($value)
2863
    {
2864
        return new \DateTime($value);
2865
    }
2866
2867
    /**
2868
     * Too complex to explain
2869
     *
2870
     * @param string $value
2871
     * @return \DateTime
2872
     */
2873
    private function filterDatetimeOrNullAfterwards($value)
2874
    {
2875
        return $value === null ? null : new \DateTime($value);
2876
    }
2877
2878
    /**
2879
     * Too complex to explain
2880
     *
2881
     * @param string|null $value
2882
     * @return int|null
2883
     */
2884
    private function filterIntOrNullAfterwards($value)
2885
    {
2886
        return $value === null ? null : (int)$value;
2887
    }
2888
2889
    /**
2890
     * Returns the current resultArrayFixSchedule. Afterwards the schedule will be empty again.
2891
     *
2892
     * @return array
2893
     */
2894
    private function flushResultArrayFixSchedule()
2895
    {
2896
        $scheduledFixes = $this->resultArrayFixSchedule;
2897
        $this->resultArrayFixSchedule = [];
2898
        return $scheduledFixes;
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 wasAliasUsed($query, $alias)
2909
    {
2910
        return in_array($alias, $query->getAllAliases());
2911
    }
2912
2913
    /**
2914
     * Returns true if $alias was used in $query already - false otherwise
2915
     *
2916
     * @param \Doctrine\ORM\QueryBuilder $query
2917
     * @param string $alias
2918
     * @return bool
2919
     */
2920
    protected function wasntAliasUsed($query, $alias)
2921
    {
2922
        return !$this->wasAliasUsed($query, $alias);
2923
    }
2924
2925
    /**
2926
     * @return array
2927
     */
2928
    public function getUnsortedParams()
2929
    {
2930
        return $this->unsortedParams;
2931
    }
2932
2933
    /**
2934
     * @param array $unsortedParams
2935
     * @return $this
2936
     */
2937
    public function setUnsortedParams($unsortedParams)
2938
    {
2939
        $this->unsortedParams = $unsortedParams;
2940
2941
        return $this;
2942
    }
2943
2944
    /**
2945
     * @param \Doctrine\ORM\QueryBuilder $query
2946
     * @param string $translationName
2947
     * @param string $language
2948
     * @return string alias of joined translation table
2949
     */
2950
    protected function joinTranslationOnce($query, $translationName, $language)
2951
    {
2952
        $alias = 'translation' . $translationName . $language;
2953
2954
        if ($this->wasAliasUsed($query, $alias)) {
2955
            return $alias;
2956
        }
2957
2958
        $rootAlias = $this->getRootAlias($query);
2959
2960
        $query->setParameter("name$alias", $translationName);
2961
        $query->setParameter("target$alias", $language);
2962
2963
        $query->leftJoin(
2964
            'UnserAller_Model_Translation',
2965
            $alias,
2966
            'WITH',
2967
            "$alias.name = CONCAT(:name$alias,$rootAlias.id) AND $alias.target = :target$alias"
2968
        );
2969
2970
        return $alias;
2971
    }
2972
2973
    /**
2974
     * @param \Doctrine\ORM\QueryBuilder $query
2975
     * @param string $alias
2976
     * @param string $col
2977
     * @param string $name
2978
     * @param string $translationName
2979
     * @param string $language
2980
     * @return array
2981
     */
2982
    protected function abstractIncludeMultilanguageStringColumn(
2983
        $query,
2984
        $alias,
2985
        $col,
2986
        $name,
2987
        $translationName,
2988
        $language
2989
    ) {
2990
        if (!$language) {
2991
            $query->addSelect("($col) $alias");
2992
        } else {
2993
            $query->addSelect("(COALESCE(" . $this->joinTranslationOnce(
2994
                $query,
2995
                $translationName,
2996
                $language
2997
            ) . ".translation,$col)) $alias");
2998
        }
2999
3000
        return [
3001
            $alias,
3002
            'move' => $name
3003
        ];
3004
    }
3005
3006
    protected function getAdditionalUserParamOrFail(&$additionalParams)
3007
    {
3008
        if (!isset($additionalParams['user'][0])) {
3009
            throw new \InvalidArgumentException('User identifier required but not given');
3010
        }
3011
3012
        $param = $additionalParams['user'];
3013
        unset($additionalParams['user']);
3014
        return \UnserAllerLib_Validate_Helper::integerOrFail($param[0], 1);
3015
    }
3016
}
3017