Completed
Push — master ( 51c4ea...fe594a )
by
unknown
08:19
created

Vortex::parseModifierParamStringSlowButAccurate()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 38
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 26
nc 12
nop 1
1
<?php
2
3
namespace Macroparts\Vortex;
4
5
abstract class Vortex
6
{
7
    use UnserAllerLib_Api_V4_AdapterProvider;
8
9
    const DIRECTIVE_FIELDS = 'fields';
10
11
    /**
12
     * @var \Doctrine\ORM\EntityManager
13
     */
14
    private $entityManager;
15
16
    /**
17
     * Defines data that can be included with an api call. You'd try to return only the most frequently used data
18
     * in default payload. Other available raw and aggregated data you'd make includable. Every availableInclude
19
     * has it's own include method and has to be mentioned in this configuration.
20
     *
21
     * Go to the Project Repository for an example configuration
22
     *
23
     * @var array
24
     */
25
    protected static $includeWhitelist = [];
26
    protected static $filterWhitelist = [];
27
    protected static $orderWhitelist = [];
28
29
    private $abstractFilterWhitelist = [
30
        'id' => '',
31
        'any' => '',
32
        'all' => ''
33
    ];
34
35
    /**
36
     * Used in the include configuration to tell if a property is a recursive inclusion or not
37
     */
38
    const INCLUDE_DIRECT = 0;
39
    const INCLUDE_RECURSIVE = 1;
40
41
    /**
42
     * Collects operations that have to be run on the doctrine result after query execution
43
     *
44
     * @var array
45
     */
46
    private $resultArrayFixSchedule = [];
47
48
    /**
49
     * Repositories can define a set of properties that are included by default
50
     *
51
     * @var array
52
     */
53
    protected static $defaultIncludes = [];
54
55
    /**
56
     * Repositories can define a default order which is taken by default
57
     *
58
     * @var array
59
     */
60
    protected static $defaultOrder = [];
61
62
    /**
63
     * If an ID is specified in the URL, it will be saved here for usage in child adapters.
64
     *
65
     * @var int|null
66
     */
67
    protected $id = null;
68
69
    /**
70
     * Ignore total row count functionality
71
     *
72
     * @var bool
73
     */
74
    protected $ignoreCount = false;
75
76
    /**
77
     * All parameters unsorted
78
     *
79
     * @var array
80
     */
81
    protected $unsortedParams = [];
82
83
    private $supportedLanguages;
84
85
86
    private $finalIncludeWhitelist;
87
    private $finalFilterWhitelist;
88
    private $finalOrderWhitelist;
89
90
    public function __construct($entityManager, $supportedLanguages = [])
91
    {
92
        $this->entityManager = $entityManager;
93
        $this->supportedLanguages = $supportedLanguages;
94
95
        //Cache customized whitelists merged with core whitelists
96
        $this->finalFilterWhitelist = $this->getStaticPropertyOfClassMergedWithParents(static::class,
97
            'filterWhitelist');
98
        $this->finalOrderWhitelist = $this->getStaticPropertyOfClassMergedWithParents(static::class, 'orderWhitelist');
99
        $this->finalIncludeWhitelist = $this->getStaticPropertyOfClassMergedWithParents(static::class,
100
            'includeWhitelist');
101
    }
102
103
    /**
104
     * @return \Doctrine\ORM\EntityManager
105
     */
106
    public function getEntityManager()
107
    {
108
        return $this->entityManager;
109
    }
110
111
    /**
112
     * @param \Doctrine\ORM\EntityManager $entityManager
113
     * @return $this
114
     */
115
    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...
116
    {
117
        $this->entityManager = $entityManager;
118
        return $this;
119
    }
120
121
    private function isNotIncludableProperty($property)
122
    {
123
        return !isset($this->finalIncludeWhitelist[$property]);
124
    }
125
126
    private function isNotFilterableProperty($property)
127
    {
128
        return !isset($this->finalFilterWhitelist[$property]) && !isset($this->abstractFilterWhitelist[$property]);
129
    }
130
131
    private function isNotOrderableProperty($property)
132
    {
133
        return !isset($this->finalOrderWhitelist[$property]);
134
    }
135
136
    public function getTranslatableIncludeNames()
137
    {
138
        return array_keys(array_filter($this->finalIncludeWhitelist, function ($inc) {
139
            return isset($inc['translatable']) && $inc['translatable'];
140
        }));
141
    }
142
143
    /**
144
     * @return array
145
     */
146
    private function getPlatformOptions()
147
    {
148
        return Zend_Registry::get('platformOptions');
149
    }
150
151
    /**
152
     * @return bool
153
     */
154
    protected function arePlatformUsersPublic()
155
    {
156
        return (bool)$this->getPlatformOptions()['user']['public'];
157
    }
158
159
    /**
160
     * @return bool
161
     */
162
    protected function arePlatformUsersPrivate()
163
    {
164
        return !$this->arePlatformUsersPublic();
165
    }
166
167
    /**
168
     * @param \Doctrine\ORM\QueryBuilder $query
169
     * @param string $alias
170
     * @param UnserAller_Model_User $currentUser
171
     * @param array $additionalParams
172
     * @param $language
173
     * @param $filtername
174
     * @return \Doctrine\ORM\Query\Expr\Orx
175
     * @throws UnserAllerLib_Api_V4_Exception_MissingFilterDirective
176
     * @throws UnserAllerLib_Api_V4_Exception_SafeForPrinting
177
     */
178
    private function filterAny($query, $alias, $currentUser, $additionalParams, $language, $filtername)
179
    {
180
        return $this->abstractFilterMultipleFields($query, 'orX', $currentUser, $additionalParams, $language, $filtername);
181
    }
182
183
    /**
184
     * @param \Doctrine\ORM\QueryBuilder $query
185
     * @param string $alias
186
     * @param UnserAller_Model_User $currentUser
187
     * @param array $additionalParams
188
     * @param $language
189
     * @param $filtername
190
     * @return \Doctrine\ORM\Query\Expr\Orx
191
     * @throws UnserAllerLib_Api_V4_Exception_MissingFilterDirective
192
     * @throws UnserAllerLib_Api_V4_Exception_SafeForPrinting
193
     */
194
    private function filterAll($query, $alias, $currentUser, $additionalParams, $language, $filtername)
195
    {
196
        return $this->abstractFilterMultipleFields($query, 'andX', $currentUser, $additionalParams, $language,
197
            $filtername);
198
    }
199
200
    /**
201
     * @param \Doctrine\ORM\QueryBuilder $query
202
     * @param string $expressionType
203
     * @param UnserAller_Model_User $currentUser
204
     * @param array $additionalParams
205
     * @param $language
206
     * @param $filtername
207
     * @return \Doctrine\ORM\Query\Expr\Orx
208
     * @throws UnserAllerLib_Api_V4_Exception_MissingFilterDirective
209
     * @throws UnserAllerLib_Api_V4_Exception_SafeForPrinting
210
     */
211
    private function abstractFilterMultipleFields(
212
        $query,
213
        $expressionType,
214
        $currentUser,
215
        $additionalParams,
216
        $language,
217
        $filtername
218
    ) {
219
        if (!isset($additionalParams[self::DIRECTIVE_FIELDS])) {
220
            throw new UnserAllerLib_Api_V4_Exception_MissingFilterDirective(
221
                $filtername, self::DIRECTIVE_FIELDS, ['fieldname1', 'fieldname2'],
222
                ':someFilterDirective(params):maySomeMoreDirectives...'
223
            );
224
        }
225
226
        $fields = $additionalParams[self::DIRECTIVE_FIELDS];
227
        if (count(array_intersect_key($this->finalFilterWhitelist, array_flip($fields))) !== count($fields)) {
228
            throw new UnserAllerLib_Api_V4_Exception_SafeForPrinting('Wrong use of "' . $filtername . '" filter. One of your specified fields is not filterable. Try using fields that are filterable.');
229
        }
230
231
        unset($additionalParams[self::DIRECTIVE_FIELDS]);
232
233
        $expression = call_user_func([$query->expr(), $expressionType]);
234
        foreach ($fields as $field) {
235
            $filterMethod = $this->decodeMethodFromRequestedFilter($field);
236
            $expression->add($this->$filterMethod(
237
                $query, $filterMethod, $currentUser, $additionalParams, $language, $field,
238
                $this->finalFilterWhitelist[$field]
239
            ));
240
        }
241
242
        return $expression;
243
    }
244
245
    /**
246
     * Executes include methods driven by a include string. See API docs to know how this string looks like
247
     *
248
     * @param \Doctrine\ORM\QueryBuilder $query
249
     * @param UnserAller_Model_User $currentUser
250
     * @param $language
251
     * @param string $includeString
252
     * @param array $meta
253
     * @return \Doctrine\ORM\QueryBuilder
254
     */
255
    protected function addIncludeStatements($query, $currentUser, $language, $includeString, &$meta = [])
256
    {
257
        $requestedIncludes = $this->parseIncludeString($includeString, $this->finalIncludeWhitelist);
258
259
        $requestedIncludes = $requestedIncludes + static::$defaultIncludes;
260
        foreach ($requestedIncludes as $requestedInclude => $additionalParams) {
261
            if ($this->isNotIncludableProperty($requestedInclude)) {
262
                continue;
263
            }
264
265
            $includeMethod = $this->decodeMethodFromRequestedInclude($requestedInclude);
266
            $postProcessDirections = $this->$includeMethod($query, $includeMethod, $currentUser, $additionalParams,
267
                $language);
268
269
            if ($postProcessDirections) {
270
                $this->schedulePostProcessingDirections($postProcessDirections);
271
            }
272
273
            $this->updateMetaOnInclude($meta, $requestedInclude);
274
        }
275
        return $query;
276
    }
277
278
    /**
279
     * Collecting whitelist not just for current class but merged with all whitelists from parent classes.
280
     * So when we overwrite whitelists locally they are still including all the elements from core adapters.
281
     *
282
     * @param null|string $class
283
     * @return array
284
     */
285
    private function getStaticPropertyOfClassMergedWithParents($class, $propertyname)
286
    {
287
        $class = $class ? $class : static::class;
288
        $parent = get_parent_class($class);
289
        return $parent ? $this->getStaticPropertyOfClassOrArray($class,
290
                $propertyname) + $this->getStaticPropertyOfClassMergedWithParents($parent,
291
                $propertyname) : $this->getStaticPropertyOfClassOrArray($class, $propertyname);
292
    }
293
294
    private function getStaticPropertyOfClassOrArray($class, $propertyname)
295
    {
296
        return isset($class::$$propertyname) ? $class::$$propertyname : [];
297
    }
298
299
    private function updateMetaOnInclude(&$meta, $includeName)
300
    {
301
        $include = static::$includeWhitelist[$includeName];
302
        if (isset($include['model'])) {
303
            $meta['modelnameIndex']["{$include['model']}"][] = $includeName;
304
        }
305
    }
306
307
    /**
308
     * Calls methods that add where conditions to a query driven by a string (see api docs for string format)
309
     *
310
     * @param \Doctrine\ORM\QueryBuilder $query
311
     * @param UnserAller_Model_User $currentUser
312
     * @param string $filterString
313
     * @param string $joinFiltersWith
314
     * @return \Doctrine\ORM\QueryBuilder
315
     * @throws UnserAllerLib_Api_V4_Exception_InvalidFilter
316
     */
317
    protected function addFilterStatements($query, $currentUser, $filterString, $language, $joinFiltersWith = 'AND')
318
    {
319
        $requestedFilters = array_filter($this->parseRichParamString($filterString));
320
        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...
321
            return $query;
322
        }
323
324
        $expression = mb_strtoupper($joinFiltersWith) === 'OR' ? $query->expr()->orX() : $query->expr()->andX();
325
        foreach ($requestedFilters as $requestedFilter => $additionalParams) {
326
            if ($this->isNotFilterableProperty($requestedFilter)) {
327
                throw new UnserAllerLib_Api_V4_Exception_InvalidFilter($requestedFilter);
328
            }
329
330
            $filterMethod = $this->decodeMethodFromRequestedFilter($requestedFilter);
331
            $expression->add($this->$filterMethod(
332
                $query, $filterMethod, $currentUser, $additionalParams, $language, $requestedFilter,
333
                $this->finalFilterWhitelist[$requestedFilter]
334
            ));
335
        }
336
337
        return $query->andWhere($expression);
338
    }
339
340
341
    /**
342
     * Transforms additionalParams for included collections into params which are used
343
     * during post processing to call a findXForApi method
344
     *
345
     * @param array $additionalParams
346
     * @return array
347
     */
348
    protected function parseAdditionalIncludeParams($additionalParams)
349
    {
350
        $filter = UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filter');
351
        $filter = is_array($filter) ? implode(',', $filter) : '';
352
353
        $include = UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'include');
354
        $include = is_array($include) ? implode(',', $include) : '';
355
356
        $order = UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'order');
357
        $order = is_array($order) ? implode(',', $order) : '';
358
359
        $limit = UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'limit');
360
        $limit = is_array($limit) ? (int)array_shift($limit) : 0;
361
362
        $page = UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'page');
363
        $page = is_array($page) ? (int)array_shift($page) : 1;
364
365
        $filterMode = UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filterMode');
366
        $filterMode = is_array($filterMode) ? array_shift($filterMode) : 'AND';
367
368
        return [$filter, $include, $order, $limit, $page, $filterMode];
369
    }
370
371
    /**
372
     * Calls methods that add orderBy statements to a query driven by a string (see api docs for the string format)
373
     *
374
     * @param \Doctrine\ORM\QueryBuilder $query
375
     * @param UnserAller_Model_User $currentUser
376
     * @param string $orderString
377
     * @return \Doctrine\ORM\QueryBuilder
378
     */
379
    private function addOrderStatements($query, $currentUser, $orderString)
380
    {
381
        $requestedOrders = array_filter($this->parseRichParamString($orderString));
382
383
        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...
384
            $requestedOrders = static::$defaultOrder;
385
        }
386
387
        foreach ($requestedOrders as $field => $order) {
388
            if ($this->isNotOrderableProperty($field)) {
389
                throw new UnserAllerLib_Api_V4_Exception_InvalidOrder($field);
390
            }
391
392
            $orderMethod = $this->decodeMethodFromRequestedOrder($field);
393
            $postProcessTasks = $this->$orderMethod($query, $orderMethod, $currentUser,
394
                isset($order['desc']) ? 'DESC' : 'ASC', $order);
395
            if ($postProcessTasks) {
396
                $this->schedulePostProcessingDirections($postProcessTasks);
397
            }
398
        }
399
400
        return $query;
401
    }
402
403
    /**
404
     * Knows how to append post processing directions to the post process schedule
405
     *
406
     * @param array $tasks
407
     */
408
    private function schedulePostProcessingDirections($tasks)
409
    {
410
        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...
411
            return;
412
        }
413
414
        if (!is_array($tasks[0])) {
415
            $tasks = [$tasks];
416
        }
417
418
        foreach ($tasks as $task) {
419
            $this->resultArrayFixSchedule[array_shift($task)] = $task;
420
        }
421
    }
422
423
    /**
424
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
425
     * uppercase first letter and every letter that comes after a dot, remove dots and prepend 'include'. Examples:
426
     *
427
     * returns includeProject when $requestedInclude = project
428
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
429
     *
430
     * @param string $requestedInclude
431
     * @return string
432
     */
433
    private function decodeMethodFromRequestedInclude($requestedInclude)
434
    {
435
        return 'include' . implode('', array_map('ucfirst', explode('.', str_replace('[]', '_cp', $requestedInclude))));
436
    }
437
438
    /**
439
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
440
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
441
     *
442
     * returns includeProject when $requestedInclude = project
443
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
444
     *
445
     * @param string $requestedInclude
446
     * @return string
447
     */
448
    private function decodeMethodFromRequestedFilter($requestedInclude)
449
    {
450
        return 'filter' . implode('', array_map('ucfirst', explode('.', $requestedInclude)));
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, prepend 'include'. Examples:
456
     *
457
     * returns includeProject when $requestedInclude = project
458
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
459
     *
460
     * @param string $field
461
     * @return string
462
     */
463
    private function decodeMethodFromRequestedOrder($field)
464
    {
465
        return 'orderBy' . implode('', array_map('ucfirst', explode('.', $field)));
466
    }
467
468
    /**
469
     * Calculates total pages for an $incompleteStatement. Incomplete statements are doctrine query builder instances
470
     * with all required conditions but no select statement and no additional includes.
471
     *
472
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
473
     * @param int $limit
474
     * @return float|int
475
     */
476
    private function calculateTotalPages($incompleteStatement, $limit)
477
    {
478
        $incompleteStatement = clone $incompleteStatement;
479
480
        if ($limit) {
481
            return (int)ceil($this->executeRowCountStatement($incompleteStatement) / $limit);
482
        }
483
        return 1;
484
    }
485
486
    /**
487
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
488
     * @return int
489
     */
490
    private function executeRowCountStatement($incompleteStatement)
491
    {
492
        if ($this->ignoreCount) {
493
            return 0;
494
        }
495
496
        $rootAlias = $this->getRootAlias($incompleteStatement);
497
        $primaryIndexCol = $rootAlias . '.' . $this->getPrimaryIndexCol();
498
        return (int)$incompleteStatement
499
            ->select("COUNT(DISTINCT $primaryIndexCol)")
500
            ->getQuery()
501
            ->getSingleScalarResult();
502
    }
503
504
    /**
505
     * Doctrine will throw errors if a table has a multi column primary index
506
     * http://stackoverflow.com/questions/18968963/select-countdistinct-error-on-multiple-columns
507
     * @return array
508
     */
509
    protected function getPrimaryIndexCol()
510
    {
511
        return 'id';
512
    }
513
514
    /**
515
     * Todo: Include collections by additional params and not by includes and adjust docs
516
     * Takes the include string and decodes it to an array with include names as keys and an array with additionalParams
517
     * as the value. Includes that are nested inside included collections are grouped and added as additional params
518
     * to the included collection.
519
     *
520
     * @param $string
521
     * @param $availableIncludes
522
     * @return array
523
     */
524
    private function parseIncludeString($string, $availableIncludes)
525
    {
526
        if ($string === '') {
527
            return [];
528
        }
529
530
        if (is_string($string)) {
531
            $string = explode(',', $string);
532
        }
533
534
        if (!is_array($string)) {
535
            return [];
536
        }
537
538
        $requestedIncludes = [];
539
        $implicitIncludes = [];
540
        foreach ($string as $include) {
541
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
542
543
            $pathToFirstRecursiveInclusion = $this->pathForNestedInclude($includeName, $availableIncludes);
544
            if ($pathToFirstRecursiveInclusion) {
545
                $requestedIncludes[$pathToFirstRecursiveInclusion]['include'][] = substr($include,
546
                    strlen($pathToFirstRecursiveInclusion) + 1);
547
                continue;
548
            }
549
550
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
551
552
            if ($allModifiersStr === null) {
553
                if (!isset($requestedIncludes[$includeName])) {
554
                    $requestedIncludes[$includeName] = [];
555
                }
556
                continue;
557
            }
558
559
            if (preg_match('~filter\(~u', $allModifiersStr)) {
560
                $modifierArr = $this->parseModifierArraySlowButAccurate($allModifiersStr);
561
            } else {
562
                $modifierArr = $this->parseModifierStringQuickButInaccurate($allModifiersStr);
563
            }
564
565
            if (isset($requestedIncludes[$includeName])) {
566
                $requestedIncludes[$includeName] = $requestedIncludes[$includeName] + $modifierArr;
567
            } else {
568
                $requestedIncludes[$includeName] = $modifierArr;
569
            }
570
        }
571
572
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
573
    }
574
575
    /**
576
     * creates an array out of string in this format:
577
     *
578
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3)
579
     *
580
     * Result:
581
     * [
582
     *  'modifierName1' => ['modifierParam1','modifierParam2'],
583
     *  'modifierName2' => ['modifierParam3']
584
     * ]
585
     *
586
     * But doesn't work when modifier params contain other modifiers with params themselves
587
     *
588
     * @param string $allModifiersStr
589
     * @return array
590
     */
591
    private function parseModifierStringQuickButInaccurate($allModifiersStr)
592
    {
593
        // Matches multiple instances of 'something(foo|bar|baz)' in the string
594
        // I guess it ignores : so you could use anything, but probably don't do that
595
        preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
596
        // [0] is full matched strings...
597
        $modifierCount = count($allModifiersArr[0]);
598
        $modifierArr = [];
599 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...
600
            // [1] is the modifier
601
            $modifierName = $allModifiersArr[1][$modifierIt];
602
            // and [3] is delimited params
603
            $modifierParamStr = $allModifiersArr[3][$modifierIt];
604
            // Make modifier array key with an array of params as the value
605
            $modifierArr[$modifierName] = explode('|', $modifierParamStr);
606
        }
607
608
        return $modifierArr;
609
    }
610
611
    /**
612
     * creates an array out of string in this format:
613
     *
614
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3))
615
     *
616
     * Can also handle modifier params that contain other modifier with params
617
     *
618
     * @param string $s
619
     * @return array
620
     */
621
    private function parseModifierArraySlowButAccurate($s)
622
    {
623
        $modifierArr = [];
624
        $modifierName = '';
625
        $modifierParamStr = '';
626
627
        $depth = 0;
628
        for ($i = 0; $i <= strlen($s); $i++) {
629
            switch ($s[$i]) {
630
                case '(':
631
                    if ($depth) {
632
                        $modifierParamStr .= $s[$i];
633
                    }
634
                    $depth++;
635
                    break;
636
637
                case ')':
638
                    $depth--;
639
                    if ($depth) {
640
                        $modifierParamStr .= $s[$i];
641
                    }
642
                    break;
643
                case ':':
644
                    if ($depth) {
645
                        $modifierParamStr .= $s[$i];
646
                    } else {
647
                        $modifierArr[$modifierName] = $this->parseModifierParamStringSlowButAccurate($modifierParamStr);
648
                        $modifierName = '';
649
                        $modifierParamStr = '';
650
                    }
651
                    break;
652
                default:
653
                    if ($depth) {
654
                        $modifierParamStr .= $s[$i];
655
                    } else {
656
                        $modifierName .= $s[$i];
657
                    }
658
            }
659
        }
660
661
        if ($modifierName) {
662
            $modifierArr[$modifierName] = $this->parseModifierParamStringSlowButAccurate($modifierParamStr);
663
        }
664
665
        return $modifierArr;
666
    }
667
668
    /**
669
     * Can make an array out of parameter string that looks like this:
670
     *
671
     * param1|param2|param3
672
     *
673
     * Can also handle params that contain other modifiers with params like this:
674
     * param1|modifier(innerParam1|innerParam2)|param3
675
     *
676
     * @param string $s
677
     * @return array
678
     */
679
    private function parseModifierParamStringSlowButAccurate($s)
680
    {
681
        $paramArr = [];
682
        $tmpStr = '';
683
684
        $depth = 0;
685
        for ($i = 0; $i <= strlen($s); $i++) {
686
            switch ($s[$i]) {
687
                case '(':
688
                    $tmpStr .= $s[$i];
689
                    $depth++;
690
                    break;
691
692
                case ')':
693
                    $tmpStr .= $s[$i];
694
                    $depth--;
695
                    break;
696
697
                case '|':
698
                    if ($depth) {
699
                        $tmpStr .= $s[$i];
700
                    } else {
701
                        $paramArr[] = $tmpStr;
702
                        $tmpStr = '';
703
                    }
704
                    break;
705
706
                default:
707
                    $tmpStr .= $s[$i];
708
            }
709
        }
710
711
        if (strlen($tmpStr)) {
712
            $paramArr[] = $tmpStr;
713
        }
714
715
        return $paramArr;
716
    }
717
718
    /**
719
     * Checks if includeName is an include nested inside a recursive inclusion.
720
     * If yes, return the path to that item - false otherwise.
721
     *
722
     * Example:
723
     * For projects there can be an include for phases. Phases are included recursively in its own adapter. So you'd
724
     * want when you include phases.steps that the steps inclusion is executed in the phase adapter and not in the
725
     * project adapter. That's why we need to separate includes that need to be passed further here.
726
     *
727
     * "recursiveinclude" results to false
728
     * "normalprop1" results to false
729
     * "recursiveinclude.normalprop1.normalprop2" results to "recursiveinclude"
730
     * "normalprop1.recursiveinclude.normalprop2" results to "normalprop1.recursiveinclude"
731
     *
732
     * @param $includeName
733
     * @param $availableIncludes
734
     * @return bool|string
735
     */
736
    private function pathForNestedInclude($includeName, $availableIncludes)
737
    {
738
        $pathArray = explode('.', $includeName);
739
        if (!isset($pathArray[1])) {
740
            return false;
741
        }
742
743
        $pathArrayLength = count($pathArray);
744
        for ($i = 1; $i < $pathArrayLength; $i++) {
745
            $implicitPath = implode('.', array_slice($pathArray, 0, $i));
746
            if ($this->extractStrategyForInclude($availableIncludes[$implicitPath]) === self::INCLUDE_RECURSIVE) {
747
                return $implicitPath;
748
            }
749
        }
750
751
        return false;
752
    }
753
754
    /**
755
     * Include configuration can either have just the strategy or a configuration array with the strategy inside.
756
     *
757
     * @param mixed $include
758
     * @return integer
759
     */
760
    private function extractStrategyForInclude($include)
761
    {
762
        return is_array($include) ? $include['strategy'] : $include;
763
    }
764
765
    /**
766
     * Validates the include string and returns an array with requiredIncludes
767
     *
768
     * @param string $string
769
     * @return array
770
     */
771
    private function parseRichParamString($string)
772
    {
773
        if ($string === '') {
774
            return [];
775
        }
776
777
        if (is_string($string)) {
778
            $string = explode(',', $string);
779
        }
780
781
        if (!is_array($string)) {
782
            return [];
783
        }
784
785
        $requestedIncludes = [];
786
        $implicitIncludes = [];
787
        foreach ($string as $include) {
788
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
789
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
790
791
            if ($allModifiersStr === null) {
792
                $requestedIncludes[$includeName] = [];
793
                continue;
794
            }
795
796
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
797
            // I guess it ignores : so you could use anything, but probably don't do that
798
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
799
            // [0] is full matched strings...
800
            $modifierCount = count($allModifiersArr[0]);
801
            $modifierArr = [];
802 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...
803
                // [1] is the modifier
804
                $modifierName = $allModifiersArr[1][$modifierIt];
805
                // and [3] is delimited params
806
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
807
                // Make modifier array key with an array of params as the value
808
                $modifierArr[$modifierName] = explode('|', $modifierParamStr);
809
            }
810
            $requestedIncludes[$includeName] = $modifierArr;
811
        }
812
813
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
814
    }
815
816
    private function mergeWithImplicitIncludes($includes, $implicitIncludes)
817
    {
818
        foreach ($implicitIncludes as $implicitInclude) {
819
            if (isset($includes[$implicitInclude])) {
820
                continue;
821
            }
822
823
            $includes[$implicitInclude] = [];
824
        }
825
826
        return $includes;
827
    }
828
829
    /**
830
     * @param \Doctrine\ORM\QueryBuilder $query
831
     * @param string $alias
832
     * @param UnserAller_Model_User $currentUser
833
     * @param array $additionalParams
834
     * @return array
835
     */
836
    protected function filterId($query, $alias, $currentUser, $additionalParams)
837
    {
838
        $rootAlias = array_shift($query->getRootAliases());
0 ignored issues
show
Bug introduced by
$query->getRootAliases() cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
839
        return $this->createConditionsForEntityColumn("$rootAlias.id", $query, $alias, $currentUser, $additionalParams);
840
    }
841
842
    private function getImplicitIncludes($includeName)
843
    {
844
        $parts = explode('.', $includeName);
845
        $numberOfParts = count($parts);
846
847
        if ($numberOfParts < 2) {
848
            return [];
849
        }
850
851
        $implicitIncludes = [];
852
        for ($i = 1; $i < $numberOfParts; $i++) {
853
            $implicitIncludes[] = implode('.', array_slice($parts, 0, $i));
854
        }
855
856
        return $implicitIncludes;
857
    }
858
859
    /**
860
     * Creates fixed, paginated results from an $incompleteStatement and a requiredIncludes string. An incomplete
861
     * statement is a query builder instance with only froms, joins and where conditions (groupbys, havings not tested).
862
     *
863
     * @param UnserAller_Model_User $currentUser
864
     * @param $language
865
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
866
     * @param string $filterString
867
     * @param string $includeString
868
     * @param int $limit
869
     * @param int $page
870
     * @return array
871
     */
872
    protected function createPaginatedResults(
873
        $currentUser,
874
        $language,
875
        $incompleteStatement,
876
        $filterString,
877
        $includeString,
878
        $limit,
879
        $page
880
    ) {
881
        $this->addFilterStatements($incompleteStatement, $currentUser, $filterString, $language);
882
        $completeStatement = $this->completeStatement($currentUser, $language, $incompleteStatement, $includeString,
883
            '');
884
885
        if ($limit > 0) {
886
            $completeStatement
887
                ->setFirstResult(($page - 1) * $limit)
888
                ->setMaxResults($limit);
889
        }
890
891
        return [
892
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
893
            'page' => $page,
894
            'filter' => $filterString,
895
            'include' => $includeString,
896
            'pageSize' => $limit,
897
            'data' => $this->applyScheduledFixes($this->getRawResult($completeStatement), $currentUser, $language)
898
        ];
899
    }
900
901
    /**
902
     * @param UnserAller_Model_User $currentUser
903
     * @return \Doctrine\ORM\QueryBuilder
904
     */
905
    protected abstract function initIncompleteStatement($currentUser);
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
906
907
    private function createIncompleteStatement(
908
        $currentUser,
909
        $filterString,
910
        $language,
911
        $joinFiltersWith = 'AND',
912
        &$meta = []
913
    ) {
914
        return $this->addFilterStatements($this->initIncompleteStatement($currentUser), $currentUser, $filterString,
915
            $language, $joinFiltersWith);
916
    }
917
918
    /**
919
     * @param UnserAller_Model_User $currentUser
920
     * @param string $filterString
921
     * @param $language
922
     * @param string $joinFiltersWith
923
     * @return int
924
     */
925
    public function findTotalNumberOfRows($currentUser, $filterString = '', $language = '', $joinFiltersWith = 'AND')
926
    {
927
        return $this->executeRowCountStatement(
928
            $this->createIncompleteStatement($currentUser, $filterString, $language, $joinFiltersWith)
929
        );
930
    }
931
932
    /**
933
     * @param UnserAller_Model_User $currentUser
934
     * @param $language
935
     * @param string $filterString
936
     * @param string $includeString
937
     * @param string $orderString
938
     * @param int $limit
939
     * @param int $page
940
     * @param string $joinFiltersWith
941
     * @return array
942
     */
943
    public function findMultipleForApi(
944
        $currentUser,
945
        $language = '',
946
        $filterString = '',
947
        $includeString = '',
948
        $orderString = '',
949
        $limit = 0,
950
        $page = 1,
951
        $joinFiltersWith = 'AND'
952
    ) {
953
        if ($page <= 0) {
954
            $page = 1;
955
        }
956
957
        $meta = $this->initMetaArray('', $language);
958
959
        $incompleteStatement = $this->createIncompleteStatement($currentUser, $filterString, $language,
960
            $joinFiltersWith, $meta);
961
962
        $completeStatement = $this->completeStatement($currentUser, $language, $incompleteStatement, $includeString,
963
            $orderString, $meta);
964
        if ($limit > 0) {
965
            $completeStatement
966
                ->setFirstResult(($page - 1) * $limit)
967
                ->setMaxResults($limit);
968
        }
969
970
        return [
971
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
972
            'filter' => $filterString,
973
            'include' => $includeString,
974
            'page' => $page,
975
            'pageSize' => $limit,
976
            'data' => $this->applyScheduledFixes($this->getRawResult($completeStatement), $currentUser, $language,
977
                $meta),
978
            'meta' => $meta
979
        ];
980
    }
981
982
    private function initMetaArray($modelPathOffset = '', $language = '')
983
    {
984
        return [
985
            'modelnameIndex' => [
986
                $this->getModelForMeta() => [$modelPathOffset]
987
            ],
988
            'language' => $language
989
        ];
990
    }
991
992
    /**
993
     * @param string $language
994
     * @param string $filterString
995
     * @param string $includeString
996
     * @param string $orderString
997
     * @param int $limit
998
     * @param int $page
999
     * @param string $filterMode
1000
     * @return array
1001
     */
1002
    public function findMultiple(
1003
        $language = '',
1004
        $filterString = '',
1005
        $includeString = '',
1006
        $orderString = '',
1007
        $limit = 0,
1008
        $page = 1,
1009
        $filterMode = 'AND'
1010
    ) {
1011
        return json_decode(json_encode($this->findMultipleForApi($this->getCurrentlyAuthenticatedUser(), $language,
1012
            $filterString, $includeString, $orderString, $limit, $page, $filterMode)), true);
1013
    }
1014
1015
    /**
1016
     * @param UnserAller_Model_User $currentUser
1017
     * @param string $language
1018
     * @param string $filterString
1019
     * @param string $includeString
1020
     * @param string $orderString
1021
     * @param int $limit
1022
     * @param string $filterMode
1023
     * @return Generator
1024
     */
1025
    public function batchFindMultiple(
1026
        $currentUser,
1027
        $language = '',
1028
        $filterString = '',
1029
        $includeString = '',
1030
        $orderString = '',
1031
        $limit = 500,
1032
        $filterMode = 'AND'
1033
    ) {
1034
        $page = 1;
1035
1036
        $result = $this->findMultipleForApi($currentUser, $language, $filterString, $includeString, $orderString,
1037
            $limit, $page, $filterMode);
1038
1039
        yield $result;
1040
1041
        $totalPages = $result['totalPages'];
1042
        unset($result);
1043
        $page++;
1044
        while ($page <= $totalPages) {
1045
            $result = $this->findMultipleForApi($currentUser, $language, $filterString, $includeString, $orderString,
1046
                $limit, $page, $filterMode);
1047
            yield $result;
1048
            $page++;
1049
            unset($result);
1050
        }
1051
    }
1052
1053
    /**
1054
     * @param UnserAller_Model_User $currentUser
1055
     * @param string $language
1056
     * @param string $filterString
1057
     * @param string $includeString
1058
     * @param string $orderString
1059
     * @param int $limit
1060
     * @param int $page
1061
     * @param string $filterMode
1062
     * @return array
1063
     */
1064
    public function getNativeSqlIngredientsForFindMultiple(
1065
        $currentUser,
1066
        $language = '',
1067
        $filterString = '',
1068
        $includeString = '',
1069
        $orderString = '',
1070
        $limit = 0,
1071
        $page = 1,
1072
        $filterMode = 'AND'
1073
    ) {
1074
        if ($page <= 0) {
1075
            $page = 1;
1076
        }
1077
1078
        $incompleteStatement = $this->createIncompleteStatement($currentUser, $filterString, $language, $filterMode);
1079
1080
        $completeStatement = $this->completeStatement($currentUser, $language, $incompleteStatement, $includeString,
1081
            $orderString);
1082
        if ($limit > 0) {
1083
            $completeStatement
1084
                ->setFirstResult(($page - 1) * $limit)
1085
                ->setMaxResults($limit);
1086
        }
1087
1088
        return $this->getNativeSqlIngredients($completeStatement->getQuery());
1089
    }
1090
1091
    /**
1092
     * @param \Doctrine\ORM\Query $query
1093
     * @return array
1094
     */
1095
    private function getNativeSqlIngredients($query)
1096
    {
1097
        $sql = $query->getSQL();
1098
        $c = new \ReflectionClass('Doctrine\ORM\Query');
1099
        $parser = $c->getProperty('_parserResult');
1100
        $parser->setAccessible(true);
1101
        /** @var \Doctrine\ORM\Query\ParserResult $parser */
1102
        $parser = $parser->getValue($query);
0 ignored issues
show
Bug introduced by
The method getValue() does not seem to exist on object<Doctrine\ORM\Query\ParserResult>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1103
        $resultSet = $parser->getResultSetMapping();
1104
1105
        // Change the aliases back to what was originally specified in the QueryBuilder.
1106
        $sql = preg_replace_callback('/AS\s([a-zA-Z0-9_]+)/', function ($matches) use ($resultSet) {
1107
            $ret = 'AS ';
1108
            if ($resultSet->isScalarResult($matches[1])) {
1109
                $ret .= $resultSet->getScalarAlias($matches[1]);
1110
            } else {
1111
                $ret .= $matches[1];
1112
            }
1113
            return $ret;
1114
        }, $sql);
1115
        $m = $c->getMethod('processParameterMappings');
1116
        $m->setAccessible(true);
1117
        list($params, $types) = $m->invoke($query, $parser->getParameterMappings());
1118
        return [$sql, $params, $types];
1119
    }
1120
1121
    /**
1122
     * @param UnserAller_Model_User $currentUser
1123
     * @param string $language
1124
     * @param string $filterString
1125
     * @param string $includeString
1126
     * @param string $orderString
1127
     * @return array|null
1128
     */
1129
    public function findOneForApi(
1130
        $currentUser,
1131
        $language = '',
1132
        $filterString = '',
1133
        $includeString = '',
1134
        $orderString = ''
1135
    ) {
1136
        return $this->createSingleResult($currentUser, $language, $filterString, $includeString, $orderString);
1137
    }
1138
1139
    /**
1140
     * @param string $language
1141
     * @param string $filterString
1142
     * @param string $includeString
1143
     * @param string $orderString
1144
     * @return array|null
1145
     */
1146
    public function findOne($language = '', $filterString = '', $includeString = '', $orderString = '')
1147
    {
1148
        return json_decode(json_encode($this->findOneForApi($this->getCurrentlyAuthenticatedUser(), $language,
1149
            $filterString, $includeString, $orderString)), true);
1150
    }
1151
1152
    /**
1153
     * @param UnserAller_Model_User $currentUser
1154
     * @param int $id
1155
     * @param string $language
1156
     * @param string $include
1157
     * @return array|null
1158
     */
1159
    public function findForApi($currentUser, $id, $language = '', $include)
1160
    {
1161
        $this->id = (int)$id;
1162
        return $this->findOneForApi($currentUser, $language, "id:is($id)", $include, '');
1163
    }
1164
1165
    /**
1166
     * @param int $id
1167
     * @param string $language
1168
     * @param string $include
1169
     * @return array|null
1170
     */
1171
    public function find($id, $language = '', $include = '')
1172
    {
1173
        return json_decode(json_encode($this->findForApi($this->getCurrentlyAuthenticatedUser(), $id, $language,
1174
            $include)), true);
1175
    }
1176
1177
    private function getCurrentlyAuthenticatedUser()
1178
    {
1179
        return $this->getEntityManager()->find(UnserAller_Model_User::class,
1180
            (int)Zend_Auth::getInstance()->getIdentity());
1181
    }
1182
1183
    /**
1184
     * @param UnserAller_Model_User $currentUser
1185
     * @param string $language
1186
     * @param string $filterString
1187
     * @param string $includeString
1188
     * @param string $orderString
1189
     * @return array|null
1190
     */
1191
    protected function createSingleResult($currentUser, $language = '', $filterString, $includeString, $orderString)
1192
    {
1193
        $meta = $this->initMetaArray('', $language);
1194
1195
        $result = $this->getRawResult(
1196
            $this->completeStatement(
1197
                $currentUser, $language,
1198
                $this->createIncompleteStatement($currentUser, $filterString, $language, 'AND', $meta), $includeString,
1199
                $orderString, $meta
1200
            )->setFirstResult(0)->setMaxResults(1)
1201
        );
1202
1203
        if (!isset($result[0])) {
1204
            return null;
1205
        }
1206
1207
        return $this->applyScheduledFixes($result, $currentUser, $language, $meta)[0] + ['meta' => $meta];
1208
    }
1209
1210
    protected function getDefaultPostProcessDirections()
1211
    {
1212
        return [];
1213
    }
1214
1215
    /**
1216
     * Adds the default select statement, all includes and order statements to the incomplete statement
1217
     * and returns the qurey builder instance
1218
     *
1219
     * @param UnserAller_Model_User $currentUser
1220
     * @param $language
1221
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
1222
     * @param string $includeString
1223
     * @param string $orderString
1224
     * @param array $meta
1225
     * @return \Doctrine\ORM\QueryBuilder
1226
     */
1227
    private function completeStatement(
1228
        $currentUser,
1229
        $language,
1230
        $incompleteStatement,
1231
        $includeString,
1232
        $orderString,
1233
        &$meta = []
1234
    ) {
1235
        $statement = clone $incompleteStatement;
1236
1237
        $this->schedulePostProcessingDirections($this->getDefaultPostProcessDirections());
1238
1239
        return $this->addOrderStatements(
1240
            $this->addIncludeStatements(
1241
                $statement->select($this->getDefaultSelectStatement($statement)), $currentUser, $language,
1242
                $includeString, $meta
1243
            ),
1244
            $currentUser,
1245
            $orderString
1246
        );
1247
    }
1248
1249
    /**
1250
     * Returns the default select statement. In this case it just returns the first root entity which means the
1251
     * entire root entity will be selected
1252
     *
1253
     * @param \Doctrine\ORM\QueryBuilder $query
1254
     * @return string
1255
     */
1256
    protected function getDefaultSelectStatement($query)
1257
    {
1258
        return 'DISTINCT ' . $this->getRootAlias($query);
1259
    }
1260
1261
    /**
1262
     * Returns first root alias from query builder
1263
     *
1264
     * @param \Doctrine\ORM\QueryBuilder $query
1265
     * @return string
1266
     */
1267
    protected function getRootAlias($query)
1268
    {
1269
        return array_shift($query->getRootAliases());
0 ignored issues
show
Bug introduced by
$query->getRootAliases() cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
1270
    }
1271
1272
    /**
1273
     * Returns true if result item has an additional layer in the hierarchy because of custom subselects
1274
     *
1275
     * @param array $item
1276
     * @return bool
1277
     */
1278
    private function mustFlattenResultItem($item)
1279
    {
1280
        return isset($item[0]);
1281
    }
1282
1283
    /**
1284
     * Returns doctrine array results with all fixes applied
1285
     *
1286
     * @param \Doctrine\ORM\QueryBuilder $statement
1287
     * @return array
1288
     */
1289
    private function getRawResult($statement)
1290
    {
1291
        //Output raw sql here if you like to debug hard
1292
        //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...
1293
        return $statement->getQuery()->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
1294
    }
1295
1296
    /**
1297
     * Doctrine will create an additional result layer when values are selected that do not belong
1298
     * to the doctrine object model. This function removes this additional layer and merges custom values with
1299
     * the doctrine object model for a single result item - not the whole result array.
1300
     *
1301
     * @param array $item
1302
     * @return array
1303
     */
1304
    private function flattenResultItem($item)
1305
    {
1306
        if (!$this->mustFlattenResultItem($item)) {
1307
            return $item;
1308
        }
1309
1310
        return array_merge(array_shift($item), $item);
1311
    }
1312
1313
    /**
1314
     * Parses $string for orderBy statements and returns an array where order statements are values.
1315
     *
1316
     * When string is "latestFirst, longestFirst" result would be: ['latestFirst', 'longestFirst']
1317
     *
1318
     * @param string $string
1319
     * @return array
1320
     */
1321
    protected function parseOrderString($string)
1322
    {
1323
        return array_filter(array_map('trim', explode(',', $string)));
1324
    }
1325
1326
    /**
1327
     * Executes all operations that were scheduled for post processing
1328
     *
1329
     * @param array $result
1330
     * @param UnserAller_Model_User $currentUser
1331
     * @param $language
1332
     * @param array $meta
1333
     * @return array
1334
     */
1335
    private function applyScheduledFixes($result, $currentUser, $language, &$meta = [])
1336
    {
1337
        $scheduledFixes = $this->flushResultArrayFixSchedule();
1338
1339
        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...
1340
            return $result;
1341
        }
1342
1343
        $numberOfResults = count($result);
1344
1345
        $this->applyFixesToItem($result[0], $scheduledFixes, $currentUser, $meta,
1346
            'retrieveNestedCollectionAndMergeMeta', $language);
1347
        for ($i = 1; $i < $numberOfResults; $i++) {
1348
            $this->applyFixesToItem($result[$i], $scheduledFixes, $currentUser, $meta, 'retrieveNestedCollection',
1349
                $language);
1350
        }
1351
1352
        return $result;
1353
    }
1354
1355
    private function retrieveNestedCollectionResult($value, $nestingOptions, $language = '')
1356
    {
1357
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1358
        list($filterString, $includeString, $orderString, $limit, $page, $filterMode) = $this->parseAdditionalIncludeParams($additionalParams);
1359
1360
        if ($filterString) {
1361
            $filterString = $filterString . ',';
1362
        }
1363
1364
        if (is_array($value)) {
1365
            $filterFunctionString = vsprintf($filterFunction, array_merge($value));
1366
        } else {
1367
            $filterFunctionString = sprintf($filterFunction, $value);
1368
        }
1369
1370
        return $this->getAdapter($model)->findMultipleForApi(
1371
            $currentUser, $language, $filterString . $filterFunctionString, $includeString, $orderString, $limit, $page,
1372
            $filterMode
1373
        );
1374
    }
1375
1376
    private function retrieveNestedCollection($value, $nestingOptions, $language, $finalPath, $meta)
1377
    {
1378
        return $this->retrieveNestedCollectionResult($value, $nestingOptions, $language)['data'];
1379
    }
1380
1381
    private function retrieveNestedCollectionAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1382
    {
1383
        $result = $this->retrieveNestedCollectionResult($value, $nestingOptions, $language);
1384
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1385
        return $result['data'];
1386
    }
1387
1388
    private function retrieveNestedSingleResult($value, $nestingOptions, $language = '')
1389
    {
1390
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1391
        list($filterString, $includeString, $orderString, $limit, $page, $filterMode) = $this->parseAdditionalIncludeParams($additionalParams);
0 ignored issues
show
Unused Code introduced by
The assignment to $limit is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $page is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $filterMode is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1392
1393
        if ($filterString) {
1394
            $filterString = $filterString . ',';
1395
        }
1396
1397
        return $this->getAdapter($model)->findOneForApi(
1398
            $currentUser, $language, $filterString . sprintf($filterFunction, $value), $includeString, $orderString
1399
        );
1400
    }
1401
1402
    private function retrieveNestedSingleAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1403
    {
1404
        $result = $this->retrieveNestedSingleResult($value, $nestingOptions, $language);
1405
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1406
        unset($result['meta']);
1407
        return $result;
1408
    }
1409
1410
    private function mergeNestedMeta(&$meta, $nestedMeta, $includeName)
1411
    {
1412
        foreach ($nestedMeta['modelnameIndex'] as $model => $paths) {
1413
            foreach ($paths as $path) {
1414
                $fullPath = $includeName . '.' . $path;
1415
                if ($path && !in_array($fullPath, $meta['modelnameIndex'][$model])) {
1416
                    $meta['modelnameIndex'][$model][] = $fullPath;
1417
                }
1418
            }
1419
        }
1420
    }
1421
1422
    private function applyFixesToItem(
1423
        &$item,
1424
        $scheduledFixes,
1425
        $currentUser,
1426
        &$meta,
1427
        $collectionNestingMethod,
1428
        $language
1429
    ) {
1430
        $item = $this->flattenResultItem($item);
1431
1432
        //Operations can depend on a prop that must be deleted and doing it too early causes errors, collect them and delete at the end
1433
        $scheduledDeletions = [];
1434
1435
        foreach ($scheduledFixes as $path => $fix) {
1436
            if (isset($fix['delete'])) {
1437
                $scheduledDeletions[$path] = $fix;
1438
                continue;
1439
            }
1440
1441
            if (isset($fix['cast'])) {
1442
                UnserAllerLib_Tool_Array::castNestedValue($item, $path, $fix['cast']);
1443
            }
1444
1445
            $value = UnserAllerLib_Tool_Array::readNestedValue($item, $path);
1446
1447
            if (isset($fix['additionalFilterValues'])) {
1448
                $value = [$value];
1449
1450
                foreach ($fix['additionalFilterValues'] AS $additionalFilterValue) {
1451
                    $value[] = UnserAllerLib_Tool_Array::readNestedValue($item, $additionalFilterValue);
1452
                }
1453
            }
1454
1455 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...
1456
                $value = $this->$collectionNestingMethod($value, $fix['nestCollection'], $language,
1457
                    $fix['move'] ? $fix['move'] : $path, $meta);
1458
            }
1459
1460 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...
1461
                $value = $this->retrieveNestedSingleAndMergeMeta($value, $fix['nestSingle'], $language,
1462
                    $fix['move'] ? $fix['move'] : $path, $meta);
1463
            }
1464
1465
            if (isset($fix['filter'])) {
1466
                $value = $this->filterValue($fix['filter'], $value, $currentUser);
1467
            }
1468
1469
            if (isset($fix['cFilter'])) {
1470
                $value = $this->filterValue($fix['cFilter'], $value, $currentUser);
1471
            }
1472
1473
            if (isset($fix['mFilter'])) {
1474
                $value = $this->filterValue($fix['mFilter'], $item, $currentUser);
1475
            }
1476
1477
            if (isset($fix['move'])) {
1478
                UnserAllerLib_Tool_Array::integrateNestedValue($item, $fix['move'], $value);
1479
                if ($path != $fix['move']) {
1480
                    $scheduledDeletions[$path] = ['delete' => 1];
1481
                }
1482
            }
1483
        }
1484
1485
        foreach ($scheduledDeletions as $path => $fix) {
1486
            UnserAllerLib_Tool_Array::unsetNestedValue($item, $path);
1487
        }
1488
    }
1489
1490
    /**
1491
     * Applies filter methods for $filterName to $value
1492
     * @uses filterJsonAfterwards
1493
     * @uses filterJsonOrNullAfterwards
1494
     * @uses filterDatetimeAfterwards
1495
     * @uses filterDatetimeOrNullAfterwards
1496
     * @param string $filterName
1497
     * @param mixed $value
1498
     * @return mixed
1499
     */
1500
    private function filterValue($filterName, $value, $currentUser)
1501
    {
1502
        if (!is_callable([$this, $filterName])) {
1503
            throw new InvalidArgumentException('Post Processing Filter method not found: ' . $filterName);
1504
        }
1505
1506
        return call_user_func_array([$this, $filterName], [$value, $currentUser]);
1507
    }
1508
1509
    /**
1510
     * @param \Doctrine\ORM\QueryBuilder $query
1511
     * @param string $alias
1512
     * @param UnserAller_Model_User $currentUser
1513
     * @param array $methods
1514
     * @return \Doctrine\ORM\Query\Expr\Andx
1515
     */
1516
    protected function createConditionsForStringColumn($field, $query, $alias, $currentUser, $methods)
1517
    {
1518
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods,
1519
            ['contain', 'contains', 'is', 'not', 'false', 'true'])
1520
        ) {
1521
            throw new InvalidArgumentException('Invalid expression methods used');
1522
        }
1523
1524
        return $this->createExpression('string', $field, $query, $alias, $currentUser, $methods);
1525
    }
1526
1527
    /**
1528
     * @param \Doctrine\ORM\QueryBuilder $query
1529
     * @param $fallbackField
1530
     * @param $translationName
1531
     * @param $language
1532
     * @param string $alias
1533
     * @param UnserAller_Model_User $currentUser
1534
     * @param $additionalParams
1535
     * @return \Doctrine\ORM\Query\Expr\Composite
1536
     * @throws UnserAllerLib_Api_V4_Exception_InvalidFilter
1537
     */
1538
    protected function createConditionsForMultilanguageStringColumn(
1539
        $query,
1540
        $fallbackField,
1541
        $translationName,
1542
        $language,
1543
        $alias,
1544
        $currentUser,
1545
        $additionalParams
1546
    ) {
1547
        if (isset($additionalParams['overAllTranslations'])) {
1548
            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...
1549
                throw new UnserAllerLib_Api_V4_Exception_InvalidFilter('Supported languages are not set');
1550
            }
1551
1552
            unset($additionalParams['overAllTranslations']);
1553
1554
            $expr = $query->expr()->orX();
1555
            foreach ($this->supportedLanguages as $supportedLanguage) {
1556
                $expr->add($this->createConditionsForStringColumn(
1557
                    "COALESCE(" . $this->joinTranslationOnce($query, $translationName,
1558
                        $supportedLanguage) . ".translation, $fallbackField)",
1559
                    $query,
1560
                    $alias,
1561
                    $currentUser,
1562
                    $additionalParams
1563
                ));
1564
            }
1565
1566
            return $expr;
1567
        }
1568
1569
        return $this->createConditionsForStringColumn(
1570
            "COALESCE(" . $this->joinTranslationOnce($query, $translationName,
1571
                $language) . ".translation, $fallbackField)",
1572
            $query,
1573
            $alias,
1574
            $currentUser,
1575
            $additionalParams
1576
        );
1577
    }
1578
1579
1580
    /**
1581
     * @param \Doctrine\ORM\QueryBuilder $query
1582
     * @param string $alias
1583
     * @param UnserAller_Model_User $currentUser
1584
     * @param array $methods
1585
     * @return \Doctrine\ORM\Query\Expr\Andx
1586
     */
1587
    protected function createConditionsForDatetimeColumn($field, $query, $alias, $currentUser, $methods)
1588
    {
1589
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['gt', 'gte', 'lt', 'lte', 'false', 'true'])) {
1590
            throw new InvalidArgumentException('Invalid expression methods used');
1591
        }
1592
1593
        return $this->createExpression('date', $field, $query, $alias, $currentUser, $methods);
1594
    }
1595
1596
    /**
1597
     * @param \Doctrine\ORM\QueryBuilder $query
1598
     * @param string $alias
1599
     * @param UnserAller_Model_User $currentUser
1600
     * @param array $methods
1601
     * @return \Doctrine\ORM\Query\Expr\Andx
1602
     */
1603
    protected function createConditionsForEntityColumn($field, $query, $alias, $currentUser, $methods)
1604
    {
1605
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true', 'is', 'not', 'me', 'notme'])) {
1606
            throw new InvalidArgumentException('Invalid expression methods used');
1607
        }
1608
1609
        return $this->createExpression('integer', $field, $query, $alias, $currentUser, $methods);
1610
    }
1611
1612
    /**
1613
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1614
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1615
     * on a subquery. And it seems to be impossible to not return null values either
1616
     * Todo: Needs research, for time being only true comparison is working as expected
1617
     *
1618
     *
1619
     * @param \Doctrine\ORM\QueryBuilder $query
1620
     * @param string $alias
1621
     * @param UnserAller_Model_User $currentUser
1622
     * @param array $methods
1623
     * @return \Doctrine\ORM\Query\Expr\Andx
1624
     */
1625 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...
1626
    {
1627
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods,
1628
            ['false', 'true', 'gt', 'gte', 'lt', 'lte', 'eq', 'any', 'null'])
1629
        ) {
1630
            throw new InvalidArgumentException('Invalid expression methods used');
1631
        }
1632
1633
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1634
    }
1635
1636
    /**
1637
     * @param \Doctrine\ORM\QueryBuilder $query
1638
     * @param string $alias
1639
     * @param UnserAller_Model_User $currentUser
1640
     * @param array $methods
1641
     * @return \Doctrine\ORM\Query\Expr\Andx
1642
     */
1643 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...
1644
    {
1645
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
1646
            throw new InvalidArgumentException('Invalid expression methods used');
1647
        }
1648
1649
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1650
    }
1651
1652
    /**
1653
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1654
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1655
     * on a subquery. And it seems to be impossible to not return null values either
1656
     * Todo: Needs research, for time being only true comparison is working as expected
1657
     *
1658
     *
1659
     * @param \Doctrine\ORM\QueryBuilder $query
1660
     * @param string $alias
1661
     * @param UnserAller_Model_User $currentUser
1662
     * @param array $methods
1663
     * @return \Doctrine\ORM\Query\Expr\Andx
1664
     */
1665 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...
1666
    {
1667
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
1668
            throw new InvalidArgumentException('Invalid expression methods used');
1669
        }
1670
1671
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1672
    }
1673
1674
    /**
1675
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1676
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1677
     * on a subquery. And it seems to be impossible to not return null values either
1678
     * Todo: Needs research, for time being only true comparison is working as expected
1679
     *
1680
     *
1681
     * @param \Doctrine\ORM\QueryBuilder $query
1682
     * @param string $alias
1683
     * @param UnserAller_Model_User $currentUser
1684
     * @param array $methods
1685
     * @return \Doctrine\ORM\Query\Expr\Andx
1686
     */
1687 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...
1688
    {
1689
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true'])) {
1690
            throw new InvalidArgumentException('Invalid expression methods used');
1691
        }
1692
1693
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1694
    }
1695
1696
    /**
1697
     * Translates params into where conditions. Null values are handled as you would expect it.
1698
     *
1699
     * @param \Doctrine\ORM\QueryBuilder $query
1700
     * @param string $alias
1701
     * @param UnserAller_Model_User $currentUser
1702
     * @param array $methods
1703
     * @return \Doctrine\ORM\Query\Expr\Andx
1704
     */
1705 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...
1706
    {
1707
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods,
1708
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true'])
1709
        ) {
1710
            throw new InvalidArgumentException('Invalid expression methods used');
1711
        }
1712
1713
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
1714
    }
1715
1716
    /**
1717
     * Todo: Whitelisting allowed subqueries for the any filter makes having this extra function unnecessary
1718
     *
1719
     * This one allows some filter directives that result to function calls on protected methods. Don't ever redirect
1720
     * user content here.
1721
     *
1722
     * Translates params into where conditions. Null values are handled as you would expect it.
1723
     *
1724
     * @param \Doctrine\ORM\QueryBuilder $query
1725
     * @param string $alias
1726
     * @param UnserAller_Model_User $currentUser
1727
     * @param array $methods
1728
     * @return \Doctrine\ORM\Query\Expr\Andx
1729
     */
1730 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...
1731
    {
1732
        if (UnserAllerLib_Tool_Array::hasMoreKeysThan($methods,
1733
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true', 'any'])
1734
        ) {
1735
            throw new InvalidArgumentException('Invalid expression methods used');
1736
        }
1737
1738
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
1739
    }
1740
1741
    /**
1742
     * Knows how to create a callable from a subquery definition
1743
     *
1744
     * @param string $name of subquery
1745
     * @param mixed[] $params for subquerymethod
1746
     * @return callable
1747
     */
1748
    protected function locateCallableSubquery($name, $params)
1749
    {
1750
        return [$this, $name];
1751
    }
1752
1753
    /**
1754
     * @param array $subqueryDefinition
1755
     * @return string DQL
1756
     */
1757
    private function consumeSubquery($subqueryDefinition)
1758
    {
1759
        list($name, $params) = $subqueryDefinition;
1760
        return call_user_func_array(
1761
            $this->locateCallableSubquery($name, $params),
1762
            $params
1763
        );
1764
    }
1765
1766
    /**
1767
     * @param string $field
1768
     * @param \Doctrine\ORM\QueryBuilder $query
1769
     * @param string $alias
1770
     * @param UnserAller_Model_User $currentUser
1771
     * @param array $methods
1772
     * @return \Doctrine\ORM\Query\Expr\Andx
1773
     */
1774
    private function createExpression($prefix, $field, $query, $alias, $currentUser, $methods)
1775
    {
1776
        $expression = $query->expr()->andX();
1777
        foreach ($methods as $method => $params) {
1778
            $expression->add(call_user_func_array([$this, $prefix . ucfirst($method) . 'Expression'],
1779
                [$query, $field, $params, $alias, $currentUser]));
1780
        }
1781
1782
        return $expression;
1783
    }
1784
1785
    /**
1786
     * @param \Doctrine\ORM\QueryBuilder $query
1787
     * @param string $field
1788
     * @param array $params
1789
     * @param string $alias
1790
     * @return mixed
1791
     */
1792
    private function subqueryFalseExpression($query, $field, $params, $alias)
1793
    {
1794
        return $query->expr()->orX(
1795
            $query->expr()->not($query->expr()->exists($this->consumeSubquery($field))),
1796
            $query->expr()->eq('(' . $this->consumeSubquery($field) . ')', 0)
1797
        );
1798
    }
1799
1800
    /**
1801
     * @param \Doctrine\ORM\QueryBuilder $query
1802
     * @param string $field
1803
     * @param array $params
1804
     * @param string $alias
1805
     * @return mixed
1806
     */
1807
    private function subqueryNullExpression($query, $field, $params, $alias)
1808
    {
1809
        return $query->expr()->not($query->expr()->exists($this->consumeSubquery($field)));
1810
    }
1811
1812
    /**
1813
     * @param \Doctrine\ORM\QueryBuilder $query
1814
     * @param string $subquery
1815
     * @param array $params
1816
     * @param string $alias
1817
     * @return mixed
1818
     */
1819 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...
1820
    {
1821
        return $query->expr()->andX(
1822
            $query->expr()->exists($this->consumeSubquery($subquery)),
1823
            $query->expr()->neq('(' . $this->consumeSubquery($subquery) . ')', 0)
1824
        );
1825
    }
1826
1827
    /**
1828
     * @param \Doctrine\ORM\QueryBuilder $query
1829
     * @param string $subquery
1830
     * @param array $params
1831
     * @param string $alias
1832
     * @return mixed
1833
     */
1834
    private function subqueryAnyisExpression($query, $subquery, $params, $alias)
1835
    {
1836
        $expression = $query->expr()->orX();
1837
        foreach ($params as $param) {
1838
            $alias = uniqid();
1839
            $query->setParameter("param$alias", $param);
1840
            $expression->add(
1841
                $query->expr()->eq(":param$alias", $query->expr()->any($this->consumeSubquery($subquery)))
1842
            );
1843
        }
1844
        return $expression;
1845
    }
1846
1847
    /**
1848
     * @param \Doctrine\ORM\QueryBuilder $query
1849
     * @param string $subquery
1850
     * @param array $params
1851
     * @param string $alias
1852
     * @return mixed
1853
     */
1854 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...
1855
    {
1856
        return $query->expr()->andX(
1857
            $query->expr()->exists($this->consumeSubquery($subquery)),
1858
            $query->expr()->gt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
1859
        );
1860
    }
1861
1862
    /**
1863
     * @param \Doctrine\ORM\QueryBuilder $query
1864
     * @param string $subquery
1865
     * @param array $params
1866
     * @param string $alias
1867
     * @return mixed
1868
     */
1869 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...
1870
    {
1871
        return $query->expr()->andX(
1872
            $query->expr()->exists($this->consumeSubquery($subquery)),
1873
            $query->expr()->gte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
1874
        );
1875
    }
1876
1877
    /**
1878
     * @param \Doctrine\ORM\QueryBuilder $query
1879
     * @param string $subquery
1880
     * @param array $params
1881
     * @param string $alias
1882
     * @return mixed
1883
     */
1884 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...
1885
    {
1886
        return $query->expr()->andX(
1887
            $query->expr()->exists($this->consumeSubquery($subquery)),
1888
            $query->expr()->lte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
1889
        );
1890
    }
1891
1892
    /**
1893
     * @param \Doctrine\ORM\QueryBuilder $query
1894
     * @param string $subquery
1895
     * @param array $params
1896
     * @param string $alias
1897
     * @return mixed
1898
     */
1899 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...
1900
    {
1901
        return $query->expr()->andX(
1902
            $query->expr()->exists($this->consumeSubquery($subquery)),
1903
            $query->expr()->lt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
1904
        );
1905
    }
1906
1907
    /**
1908
     * @param \Doctrine\ORM\QueryBuilder $query
1909
     * @param string $subquery
1910
     * @param array $params
1911
     * @param string $alias
1912
     * @return mixed
1913
     */
1914 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...
1915
    {
1916
        return $query->expr()->andX(
1917
            $query->expr()->exists($this->consumeSubquery($subquery)),
1918
            $query->expr()->eq('(' . $this->consumeSubquery($subquery) . ')', $params[0])
1919
        );
1920
    }
1921
1922
    /**
1923
     * @param \Doctrine\ORM\QueryBuilder $query
1924
     * @param string $field
1925
     * @param array $params
1926
     * @param string $alias
1927
     * @return mixed
1928
     */
1929
    private function dateIsExpression($query, $field, $params, $alias)
1930
    {
1931
        return $query->expr()->in($field, $params);
1932
    }
1933
1934
    /**
1935
     * @param \Doctrine\ORM\QueryBuilder $query
1936
     * @param string $field
1937
     * @param array $params
1938
     * @param string $alias
1939
     * @return mixed
1940
     */
1941
    private function integerIsExpression($query, $field, $params, $alias)
1942
    {
1943
        return $query->expr()->in($field, $params);
1944
    }
1945
1946
    /**
1947
     * @param \Doctrine\ORM\QueryBuilder $query
1948
     * @param string $field
1949
     * @param array $params
1950
     * @param string $alias
1951
     * @return mixed
1952
     */
1953
    private function stringIsExpression($query, $field, $params, $alias)
1954
    {
1955
        return $query->expr()->in($field, $params);
1956
    }
1957
1958
    /**
1959
     * @param \Doctrine\ORM\QueryBuilder $query
1960
     * @param string $field
1961
     * @param array $params
1962
     * @param string $alias
1963
     * @param UnserAller_Model_User $currentUser
1964
     * @return mixed
1965
     * @throws UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
1966
     */
1967
    private function integerMeExpression($query, $field, $params, $alias, $currentUser)
1968
    {
1969
        if (!$currentUser) {
1970
            throw new UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
1971
        }
1972
        return $query->expr()->eq($field, $currentUser->getId());
1973
    }
1974
1975
    /**
1976
     * @param \Doctrine\ORM\QueryBuilder $query
1977
     * @param string $field
1978
     * @param array $params
1979
     * @param string $alias
1980
     * @return \Doctrine\ORM\Query\Expr\Comparison
1981
     * @throws UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
1982
     */
1983
    private function integerAnyExpression($query, $field, $params, $alias)
1984
    {
1985
        return $query->expr()->eq($field, $query->expr()->any($this->consumeSubquery($params)));
1986
    }
1987
1988
    /**
1989
     * @param \Doctrine\ORM\QueryBuilder $query
1990
     * @param string $field
1991
     * @param array $params
1992
     * @param string $alias
1993
     * @param UnserAller_Model_User $currentUser
1994
     * @return \Doctrine\ORM\Query\Expr\Comparison
1995
     * @throws UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
1996
     */
1997
    private function integerNotmeExpression($query, $field, $params, $alias, $currentUser)
1998
    {
1999
        if (!$currentUser) {
2000
            throw new UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
2001
        }
2002
        return $query->expr()->neq($field, $currentUser->getId());
2003
    }
2004
2005
    /**
2006
     * @param \Doctrine\ORM\QueryBuilder $query
2007
     * @param string $field
2008
     * @param array $params
2009
     * @param string $alias
2010
     * @return \Doctrine\ORM\Query\Expr\Func
2011
     */
2012
    private function integerNotExpression($query, $field, $params, $alias)
2013
    {
2014
        return $query->expr()->notIn($field, $params);
2015
    }
2016
2017
    /**
2018
     * @param \Doctrine\ORM\QueryBuilder $query
2019
     * @param string $field
2020
     * @param array $params
2021
     * @param string $alias
2022
     * @return \Doctrine\ORM\Query\Expr\Func
2023
     */
2024
    private function dateNotExpression($query, $field, $params, $alias)
2025
    {
2026
        return $query->expr()->notIn($field, $params);
2027
    }
2028
2029
    /**
2030
     * @param \Doctrine\ORM\QueryBuilder $query
2031
     * @param string $field
2032
     * @param array $params
2033
     * @param string $alias
2034
     * @return mixed
2035
     */
2036
    private function stringNotExpression($query, $field, $params, $alias)
2037
    {
2038
        return $query->expr()->notIn($field, $params);
2039
    }
2040
2041
    /**
2042
     * @param \Doctrine\ORM\QueryBuilder $query
2043
     * @param string $field
2044
     * @param array $params
2045
     * @param string $alias
2046
     * @return \Doctrine\ORM\Query\Expr\Comparison
2047
     */
2048
    private function integerFalseExpression($query, $field, $params, $alias)
2049
    {
2050
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2051
    }
2052
2053
    /**
2054
     * @param \Doctrine\ORM\QueryBuilder $query
2055
     * @param string $field
2056
     * @param array $params
2057
     * @param string $alias
2058
     * @return \Doctrine\ORM\Query\Expr\Comparison
2059
     */
2060
    private function dateFalseExpression($query, $field, $params, $alias)
2061
    {
2062
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2063
    }
2064
2065
    /**
2066
     * @param \Doctrine\ORM\QueryBuilder $query
2067
     * @param string $field
2068
     * @param array $params
2069
     * @param string $alias
2070
     * @return \Doctrine\ORM\Query\Expr\Base
2071
     */
2072
    private function stringFalseExpression($query, $field, $params, $alias)
2073
    {
2074
        return $query->expr()->orX(
2075
            $query->expr()->isNull($field),
2076
            $query->expr()->eq($field, "''")
2077
        );
2078
    }
2079
2080
    /**
2081
     * @param \Doctrine\ORM\QueryBuilder $query
2082
     * @param string $field
2083
     * @param array $params
2084
     * @param string $alias
2085
     * @return \Doctrine\ORM\Query\Expr\Comparison
2086
     */
2087
    private function integerTrueExpression($query, $field, $params, $alias)
2088
    {
2089
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2090
    }
2091
2092
    /**
2093
     * @param \Doctrine\ORM\QueryBuilder $query
2094
     * @param string $field
2095
     * @param array $params
2096
     * @param string $alias
2097
     * @return \Doctrine\ORM\Query\Expr\Comparison
2098
     */
2099
    private function dateTrueExpression($query, $field, $params, $alias)
2100
    {
2101
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2102
    }
2103
2104
    /**
2105
     * @param \Doctrine\ORM\QueryBuilder $query
2106
     * @param string $field
2107
     * @param array $params
2108
     * @param string $alias
2109
     * @return \Doctrine\ORM\Query\Expr\Base
2110
     */
2111
    private function stringTrueExpression($query, $field, $params, $alias)
2112
    {
2113
        return $query->expr()->andX(
2114
            $query->expr()->isNotNull($field),
2115
            $query->expr()->neq($field, "''")
2116
        );
2117
    }
2118
2119
    /**
2120
     * @param \Doctrine\ORM\QueryBuilder $query
2121
     * @param string $field
2122
     * @param array $params
2123
     * @param string $alias
2124
     * @return mixed
2125
     */
2126 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...
2127
    {
2128
        $contains = $query->expr()->orX();
2129
2130
        $index = 0;
2131
        foreach ($params as $string) {
2132
            $contains->add($query->expr()->like($field, ":contains_{$alias}_{$index}"));
2133
            $query->setParameter("contains_{$alias}_{$index}", '%' . $string . '%');
2134
            $index++;
2135
        }
2136
2137
        return $contains;
2138
    }
2139
2140
    /**
2141
     * @param \Doctrine\ORM\QueryBuilder $query
2142
     * @param string $field
2143
     * @param array $params
2144
     * @param string $alias
2145
     * @return mixed
2146
     */
2147
    private function stringContainExpression($query, $field, $params, $alias)
2148
    {
2149
        return $this->stringContainsExpression($query, $field, $params, $alias);
2150
    }
2151
2152
    /**
2153
     * @param \Doctrine\ORM\QueryBuilder $query
2154
     * @param string $field
2155
     * @param array $params
2156
     * @param string $alias
2157
     * @return mixed
2158
     */
2159 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...
2160
    {
2161
        $lt = $query->expr()->orX();
2162
        $index = 0;
2163
        foreach ($params as $datetime) {
2164
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2165
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2166
            $index++;
2167
        }
2168
2169
        return $lt;
2170
    }
2171
2172
    /**
2173
     * @param \Doctrine\ORM\QueryBuilder $query
2174
     * @param string $field
2175
     * @param array $params
2176
     * @param string $alias
2177
     * @return mixed
2178
     */
2179 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...
2180
    {
2181
        $lt = $query->expr()->orX();
2182
        $index = 0;
2183
        foreach ($params as $datetime) {
2184
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2185
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2186
            $index++;
2187
        }
2188
2189
        return $lt;
2190
    }
2191
2192
    /**
2193
     * @param \Doctrine\ORM\QueryBuilder $query
2194
     * @param string $field
2195
     * @param array $params
2196
     * @param string $alias
2197
     * @return mixed
2198
     */
2199 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...
2200
    {
2201
        $lte = $query->expr()->orX();
2202
        $index = 0;
2203
        foreach ($params as $datetime) {
2204
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2205
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2206
            $index++;
2207
        }
2208
2209
        return $lte;
2210
    }
2211
2212
    /**
2213
     * @param \Doctrine\ORM\QueryBuilder $query
2214
     * @param string $field
2215
     * @param array $params
2216
     * @param string $alias
2217
     * @return mixed
2218
     */
2219 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...
2220
    {
2221
        $lte = $query->expr()->orX();
2222
        $index = 0;
2223
        foreach ($params as $datetime) {
2224
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2225
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2226
            $index++;
2227
        }
2228
2229
        return $lte;
2230
    }
2231
2232
    /**
2233
     * @param \Doctrine\ORM\QueryBuilder $query
2234
     * @param string $field
2235
     * @param array $params
2236
     * @param string $alias
2237
     * @return mixed
2238
     */
2239 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...
2240
    {
2241
        $gt = $query->expr()->orX();
2242
        $index = 0;
2243
        foreach ($params as $datetime) {
2244
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2245
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2246
            $index++;
2247
        }
2248
2249
        return $gt;
2250
    }
2251
2252
    /**
2253
     * @param \Doctrine\ORM\QueryBuilder $query
2254
     * @param string $field
2255
     * @param array $params
2256
     * @param string $alias
2257
     * @return mixed
2258
     */
2259 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...
2260
    {
2261
        $gt = $query->expr()->orX();
2262
        $index = 0;
2263
        foreach ($params as $datetime) {
2264
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2265
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2266
            $index++;
2267
        }
2268
2269
        return $gt;
2270
    }
2271
2272
    /**
2273
     * @return string
2274
     */
2275
    protected function getModelForMeta()
2276
    {
2277
        return uniqid('UnknownClass');
2278
    }
2279
2280
    /**
2281
     * @return string
2282
     */
2283
    public function getClassnameForRepresentedModel()
2284
    {
2285
        return $this->getModelForMeta();
2286
    }
2287
2288
    /**
2289
     * @param \Doctrine\ORM\QueryBuilder $query
2290
     * @param string $field
2291
     * @param array $params
2292
     * @param string $alias
2293
     * @return mixed
2294
     */
2295 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...
2296
    {
2297
        $gte = $query->expr()->orX();
2298
        $index = 0;
2299
        foreach ($params as $datetime) {
2300
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2301
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2302
            $index++;
2303
        }
2304
2305
        return $gte;
2306
    }
2307
2308
    /**
2309
     * @param \Doctrine\ORM\QueryBuilder $query
2310
     * @param string $field
2311
     * @param array $params
2312
     * @param string $alias
2313
     * @return mixed
2314
     */
2315 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...
2316
    {
2317
        $gte = $query->expr()->orX();
2318
        $index = 0;
2319
        foreach ($params as $datetime) {
2320
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2321
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2322
            $index++;
2323
        }
2324
2325
        return $gte;
2326
    }
2327
2328
    /**
2329
     * Does some crazy things
2330
     *
2331
     * @param string $value
2332
     * @return array
2333
     */
2334
    private function filterJsonAfterwards($value)
2335
    {
2336
        return json_decode($value, true);
2337
    }
2338
2339
    /**
2340
     * Does some crazy things
2341
     *
2342
     * @param string $value
2343
     * @return mixed
2344
     */
2345
    private function filterJsonIfNullSetEmptyObjectAfterwards($value)
2346
    {
2347
        return $value === null ? new stdClass() : json_decode($value, true);
2348
    }
2349
2350
    /**
2351
     * Does some crazy things
2352
     *
2353
     * @param string $value
2354
     * @return string
2355
     */
2356
    private function filterNl2BrAfterwards($value)
2357
    {
2358
        return nl2br($value, false);
2359
    }
2360
2361
    /**
2362
     * Does some crazy things
2363
     *
2364
     * @param string $value
2365
     * @return array
2366
     */
2367
    private function filterJsonOrNullAfterwards($value)
2368
    {
2369
        return $value === null ? null : json_decode($value, true);
2370
    }
2371
2372
    /**
2373
     * Too complex to explain
2374
     *
2375
     * @param string $value
2376
     * @return DateTime
2377
     */
2378
    private function filterDatetimeAfterwards($value)
2379
    {
2380
        return new DateTime($value);
2381
    }
2382
2383
    /**
2384
     * Too complex to explain
2385
     *
2386
     * @param string $value
2387
     * @return DateTime
2388
     */
2389
    private function filterDatetimeOrNullAfterwards($value)
2390
    {
2391
        return $value === null ? null : new DateTime($value);
2392
    }
2393
2394
    /**
2395
     * Too complex to explain
2396
     *
2397
     * @param string|null $value
2398
     * @return int|null
2399
     */
2400
    private function filterIntOrNullAfterwards($value)
2401
    {
2402
        return $value === null ? null : (int)$value;
2403
    }
2404
2405
    /**
2406
     * Returns the current resultArrayFixSchedule. Afterwards the schedule will be empty again.
2407
     *
2408
     * @return array
2409
     */
2410
    private function flushResultArrayFixSchedule()
2411
    {
2412
        $scheduledFixes = $this->resultArrayFixSchedule;
2413
        $this->resultArrayFixSchedule = [];
2414
        return $scheduledFixes;
2415
    }
2416
2417
    /**
2418
     * Returns true if $alias was used in $query already - false otherwise
2419
     *
2420
     * @param \Doctrine\ORM\QueryBuilder $query
2421
     * @param string $alias
2422
     * @return bool
2423
     */
2424
    protected function wasAliasUsed($query, $alias)
2425
    {
2426
        return in_array($alias, $query->getAllAliases());
2427
    }
2428
2429
    /**
2430
     * Returns true if $alias was used in $query already - false otherwise
2431
     *
2432
     * @param \Doctrine\ORM\QueryBuilder $query
2433
     * @param string $alias
2434
     * @return bool
2435
     */
2436
    protected function wasntAliasUsed($query, $alias)
2437
    {
2438
        return !$this->wasAliasUsed($query, $alias);
2439
    }
2440
2441
    /**
2442
     * @return array
2443
     */
2444
    public function getUnsortedParams()
2445
    {
2446
        return $this->unsortedParams;
2447
    }
2448
2449
    /**
2450
     * @param array $unsortedParams
2451
     * @return $this
2452
     */
2453
    public function setUnsortedParams($unsortedParams)
2454
    {
2455
        $this->unsortedParams = $unsortedParams;
2456
2457
        return $this;
2458
    }
2459
2460
    /**
2461
     * @param \Doctrine\ORM\QueryBuilder $query
2462
     * @param string $translationName
2463
     * @param string $language
2464
     * @return string alias of joined translation table
2465
     */
2466
    protected function joinTranslationOnce($query, $translationName, $language)
2467
    {
2468
        $alias = 'translation' . $translationName . $language;
2469
2470
        if ($this->wasAliasUsed($query, $alias)) {
2471
            return $alias;
2472
        }
2473
2474
        $rootAlias = $this->getRootAlias($query);
2475
2476
        $query->setParameter("name$alias", $translationName);
2477
        $query->setParameter("target$alias", $language);
2478
2479
        $query->leftJoin(
2480
            'UnserAller_Model_Translation',
2481
            $alias,
2482
            'WITH',
2483
            "$alias.name = CONCAT(:name$alias,$rootAlias.id) AND $alias.target = :target$alias"
2484
        );
2485
2486
        return $alias;
2487
    }
2488
2489
    /**
2490
     * @param \Doctrine\ORM\QueryBuilder $query
2491
     * @param string $alias
2492
     * @param string $col
2493
     * @param string $name
2494
     * @param string $translationName
2495
     * @param string $language
2496
     * @return array|void
2497
     */
2498
    protected function abstractIncludeMultilanguageStringColumn($query, $alias, $col, $name, $translationName, $language)
2499
    {
2500
        if (!$language) {
2501
            $query->addSelect("($col) $alias");
2502
        } else {
2503
            $query->addSelect("(COALESCE(" . $this->joinTranslationOnce($query, $translationName,
2504
                    $language) . ".translation,$col)) $alias");
2505
        }
2506
2507
        return [
2508
            $alias,
2509
            'move' => $name
2510
        ];
2511
    }
2512
2513
    protected function getAdditionalUserParamOrFail(&$additionalParams)
2514
    {
2515
        if (!isset($additionalParams['user'][0])) {
2516
            throw new InvalidArgumentException('User identifier required but not given');
2517
        }
2518
2519
        $param = $additionalParams['user'];
2520
        unset($additionalParams['user']);
2521
        return UnserAllerLib_Validate_Helper::integerOrFail($param[0], 1);
2522
    }
2523
}
2524