Completed
Push — master ( 7a754b...b8f67b )
by
unknown
03:48
created

Vortex::selectTotalNumberOfRows()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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