Completed
Push — master ( f5e55a...9ac026 )
by
unknown
01:53
created

Vortex::getItemLanguageGetter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
nc 2
cc 2
eloc 2
nop 1
crap 6
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
     * All parameters unsorted
71
     *
72
     * @var array
73
     */
74
    protected $unsortedParams = [];
75
76
    protected $supportedLanguages;
77
78
79
    private $finalIncludeWhitelist;
80
    private $finalFilterWhitelist;
81
    private $finalOrderWhitelist;
82
83
    public function __construct($entityManager, $supportedLanguages = [])
84
    {
85
        $this->entityManager = $entityManager;
86
        $this->supportedLanguages = $supportedLanguages;
87
88
        //Cache customized whitelists merged with core whitelists
89
        $this->finalFilterWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
90
            static::class,
91
            'filterWhitelist'
92
        );
93
        $this->finalOrderWhitelist = $this->getStaticPropertyOfClassMergedWithParents(static::class, 'orderWhitelist');
94
        $this->finalIncludeWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
95
            static::class,
96
            'includeWhitelist'
97
        );
98
    }
99
100
    /**
101
     * @return \Doctrine\ORM\EntityManager
102
     */
103
    public function getEntityManager()
104
    {
105
        return $this->entityManager;
106
    }
107
108
    /**
109
     * @param \Doctrine\ORM\EntityManager $entityManager
110
     * @return $this
111
     */
112
    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...
113
    {
114
        $this->entityManager = $entityManager;
115
        return $this;
116
    }
117
118
    private function isNotIncludableProperty($property)
119
    {
120
        return !isset($this->finalIncludeWhitelist[$property]);
121
    }
122
123
    private function isNotFilterableProperty($property)
124
    {
125
        return !isset($this->finalFilterWhitelist[$property]) && !isset($this->abstractFilterWhitelist[$property]);
126
    }
127
128
    private function isNotOrderableProperty($property)
129
    {
130
        return !isset($this->finalOrderWhitelist[$property]);
131
    }
132
133
    public function getTranslatableIncludeNames()
134
    {
135
        return array_keys(array_filter($this->finalIncludeWhitelist, function ($inc) {
136
            return isset($inc['translatable']) && $inc['translatable'];
137
        }));
138
    }
139
140
    /**
141
     * @return array
142
     */
143
    private function getPlatformOptions()
144
    {
145
        return \Zend_Registry::get('platformOptions');
146
    }
147
148
    /**
149
     * @return bool
150
     */
151
    protected function arePlatformUsersPublic()
152
    {
153
        return (bool)$this->getPlatformOptions()['user']['public'];
154
    }
155
156
    /**
157
     * @return bool
158
     */
159
    protected function arePlatformUsersPrivate()
160
    {
161
        return !$this->arePlatformUsersPublic();
162
    }
163
164
    /**
165
     * @param \Doctrine\ORM\QueryBuilder $query
166
     * @param string $alias
167
     * @param \UnserAller_Model_User $currentUser
168
     * @param array $additionalParams
169
     * @param $language
170
     * @param $filtername
171
     * @return \Doctrine\ORM\Query\Expr\Orx
172
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
173
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
174
     */
175
    private function filterAny($query, $alias, $currentUser, $additionalParams, $language, $filtername)
176
    {
177
        return $this->abstractFilterMultipleFields(
178
            $query,
179
            'orX',
180
            $currentUser,
181
            $additionalParams,
182
            $language,
183
            $filtername
184
        );
185
    }
186
187
    /**
188
     * @param \Doctrine\ORM\QueryBuilder $query
189
     * @param string $alias
190
     * @param \UnserAller_Model_User $currentUser
191
     * @param array $additionalParams
192
     * @param $language
193
     * @param $filtername
194
     * @return \Doctrine\ORM\Query\Expr\Orx
195
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
196
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
197
     */
198
    private function filterAll($query, $alias, $currentUser, $additionalParams, $language, $filtername)
199
    {
200
        return $this->abstractFilterMultipleFields(
201
            $query,
202
            'andX',
203
            $currentUser,
204
            $additionalParams,
205
            $language,
206
            $filtername
207
        );
208
    }
209
210
    /**
211
     * @param \Doctrine\ORM\QueryBuilder $query
212
     * @param string $expressionType
213
     * @param \UnserAller_Model_User $currentUser
214
     * @param array $additionalParams
215
     * @param $language
216
     * @param $filtername
217
     * @return \Doctrine\ORM\Query\Expr\Orx
218
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
219
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
220
     */
221
    private function abstractFilterMultipleFields(
222
        $query,
223
        $expressionType,
224
        $currentUser,
225
        $additionalParams,
226
        $language,
227
        $filtername
228
    ) {
229
        if (!isset($additionalParams[self::DIRECTIVE_FIELDS])) {
230
            throw new \UnserAllerLib_Api_V4_Exception_MissingFilterDirective(
231
                $filtername,
232
                self::DIRECTIVE_FIELDS,
233
                ['fieldname1', 'fieldname2'],
234
                ':someFilterDirective(params):maySomeMoreDirectives...'
235
            );
236
        }
237
238
        $fields = $additionalParams[self::DIRECTIVE_FIELDS];
239
        if (count(array_intersect_key($this->finalFilterWhitelist, array_flip($fields))) !== count($fields)) {
240
            throw new \UnserAllerLib_Api_V4_Exception_SafeForPrinting(
241
                'Wrong use of "' . $filtername . '" filter. '.
242
                'One of your specified fields is not filterable. '.
243
                'Try using fields that are filterable.'
244
            );
245
        }
246
247
        unset($additionalParams[self::DIRECTIVE_FIELDS]);
248
249
        $expression = call_user_func([$query->expr(), $expressionType]);
250
        foreach ($fields as $field) {
251
            $filterMethod = $this->decodeMethodFromRequestedFilter($field);
252
            $expression->add($this->$filterMethod(
253
                $query, $filterMethod, $currentUser, $additionalParams, $language, $field,
254
                $this->finalFilterWhitelist[$field]
255
            ));
256
        }
257
258
        return $expression;
259
    }
260
261
    /**
262
     * Executes include methods driven by a include string. See API docs to know how this string looks like
263
     *
264
     * @param \Doctrine\ORM\QueryBuilder $query
265
     * @param \UnserAller_Model_User $currentUser
266
     * @param $language
267
     * @param string $includeString
268
     * @param array $meta
269
     * @return \Doctrine\ORM\QueryBuilder
270
     */
271
    protected function addIncludeStatements($query, $currentUser, $language, $includeString, &$meta = [])
272
    {
273
        $requestedIncludes = $this->parseIncludeString($includeString, $this->finalIncludeWhitelist);
274
275
        $requestedIncludes = $requestedIncludes + static::$defaultIncludes;
276
        foreach ($requestedIncludes as $requestedInclude => $additionalParams) {
277
            if ($this->isNotIncludableProperty($requestedInclude)) {
278
                continue;
279
            }
280
281
            $includeMethod = $this->decodeMethodFromRequestedInclude($requestedInclude);
282
            $postProcessDirections = $this->$includeMethod($query, $includeMethod, $currentUser, $additionalParams,
283
                $language);
284
285
            if ($postProcessDirections) {
286
                $this->schedulePostProcessingDirections($postProcessDirections);
287
            }
288
289
            $this->updateMetaOnInclude($meta, $requestedInclude);
290
        }
291
        return $query;
292
    }
293
294
    /**
295
     * Collecting whitelist not just for current class but merged with all whitelists from parent classes.
296
     * So when we overwrite whitelists locally they are still including all the elements from core adapters.
297
     *
298
     * @param null|string $class
299
     * @param $propertyname
300
     * @return array
301
     */
302
    private function getStaticPropertyOfClassMergedWithParents($class, $propertyname)
303
    {
304
        $class = $class ? $class : static::class;
305
        $parent = get_parent_class($class);
306
        return $parent ? $this->getStaticPropertyOfClassOrArray(
307
                $class,
308
                $propertyname
309
            ) + $this->getStaticPropertyOfClassMergedWithParents(
310
                $parent,
311
                $propertyname
312
            ) : $this->getStaticPropertyOfClassOrArray($class, $propertyname);
313
    }
314
315
    private function getStaticPropertyOfClassOrArray($class, $propertyname)
316
    {
317
        return isset($class::$$propertyname) ? $class::$$propertyname : [];
318
    }
319
320
    private function updateMetaOnInclude(&$meta, $includeName)
321
    {
322
        $include = $this->finalIncludeWhitelist[$includeName];
323
        if (isset($include['model'])) {
324
            $meta['modelnameIndex']["{$include['model']}"][] = $includeName;
325
        }
326
    }
327
328
    /**
329
     * Calls methods that add where conditions to a query driven by a string (see api docs for string format)
330
     *
331
     * @param \Doctrine\ORM\QueryBuilder $query
332
     * @param \UnserAller_Model_User $currentUser
333
     * @param string $filterString
334
     * @param $language
335
     * @param string $joinFiltersWith
336
     * @return \Doctrine\ORM\QueryBuilder
337
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidFilter
338
     * @uses filterAny
339
     * @uses filterAll
340
     */
341
    protected function addFilterStatements($query, $currentUser, $filterString, $language, $joinFiltersWith = 'AND')
342
    {
343
        $requestedFilters = array_filter($this->parseRichParamString($filterString));
344
        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...
345
            return $query;
346
        }
347
348
        $expression = mb_strtoupper($joinFiltersWith) === 'OR' ? $query->expr()->orX() : $query->expr()->andX();
349
        foreach ($requestedFilters as $requestedFilter => $additionalParams) {
350
            if ($this->isNotFilterableProperty($requestedFilter)) {
351
                throw new \UnserAllerLib_Api_V4_Exception_InvalidFilter($requestedFilter);
352
            }
353
354
            $filterMethod = $this->decodeMethodFromRequestedFilter($requestedFilter);
355
            $expression->add($this->$filterMethod(
356
                $query, $filterMethod, $currentUser, $additionalParams, $language, $requestedFilter,
357
                $this->finalFilterWhitelist[$requestedFilter]
358
            ));
359
        }
360
361
        return $query->andWhere($expression);
362
    }
363
364
    abstract protected function getFallbackLanguage($resultItem, $requestedLanguage);
365
366
    /**
367
     * Transforms additionalParams for included collections into params which are used
368
     * during post processing to call a findXForApi method
369
     *
370
     * @param array $additionalParams
371
     * @return array
372
     */
373
    protected function parseAdditionalIncludeParams($additionalParams)
374
    {
375
        $filter = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filter');
376
        $filter = is_array($filter) ? implode(',', $filter) : '';
377
378
        $include = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'include');
379
        $include = is_array($include) ? implode(',', $include) : '';
380
381
        $order = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'order');
382
        $order = is_array($order) ? implode(',', $order) : '';
383
384
        $limit = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'limit');
385
        $limit = is_array($limit) ? (int)array_shift($limit) : 0;
386
387
        $page = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'page');
388
        $page = is_array($page) ? (int)array_shift($page) : 1;
389
390
        $filterMode = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filterMode');
391
        $filterMode = is_array($filterMode) ? array_shift($filterMode) : 'AND';
392
393
        return [$filter, $include, $order, $limit, $page, $filterMode];
394
    }
395
396
    /**
397
     * Calls methods that add orderBy statements to a query driven by a string (see api docs for the string format)
398
     *
399
     * @param \Doctrine\ORM\QueryBuilder $query
400
     * @param \UnserAller_Model_User $currentUser
401
     * @param string $orderString
402
     * @return \Doctrine\ORM\QueryBuilder
403
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidOrder
404
     */
405
    private function addOrderStatements($query, $currentUser, $orderString)
406
    {
407
        $requestedOrders = array_filter($this->parseRichParamString($orderString));
408
409
        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...
410
            $requestedOrders = static::$defaultOrder;
411
        }
412
413
        foreach ($requestedOrders as $field => $order) {
414
            if ($this->isNotOrderableProperty($field)) {
415
                throw new \UnserAllerLib_Api_V4_Exception_InvalidOrder($field);
416
            }
417
418
            $orderMethod = $this->decodeMethodFromRequestedOrder($field);
419
            $postProcessTasks = $this->$orderMethod($query, $orderMethod, $currentUser,
420
                isset($order['desc']) ? 'DESC' : 'ASC', $order);
421
            if ($postProcessTasks) {
422
                $this->schedulePostProcessingDirections($postProcessTasks);
423
            }
424
        }
425
426
        return $query;
427
    }
428
429
    /**
430
     * Knows how to append post processing directions to the post process schedule
431
     *
432
     * @param array $tasks
433
     */
434
    private function schedulePostProcessingDirections($tasks)
435
    {
436
        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...
437
            return;
438
        }
439
440
        if (!is_array($tasks[0])) {
441
            $tasks = [$tasks];
442
        }
443
444
        foreach ($tasks as $task) {
445
            $this->resultArrayFixSchedule[array_shift($task)] = $task;
446
        }
447
    }
448
449
    /**
450
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
451
     * uppercase first letter and every letter that comes after a dot, remove dots and prepend 'include'. Examples:
452
     *
453
     * returns includeProject when $requestedInclude = project
454
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
455
     *
456
     * @param string $requestedInclude
457
     * @return string
458
     */
459
    private function decodeMethodFromRequestedInclude($requestedInclude)
460
    {
461
        return 'include' . implode('', array_map('ucfirst', explode('.', str_replace('[]', '_cp', $requestedInclude))));
462
    }
463
464
    /**
465
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
466
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
467
     *
468
     * returns includeProject when $requestedInclude = project
469
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
470
     *
471
     * @param string $requestedInclude
472
     * @return string
473
     */
474
    private function decodeMethodFromRequestedFilter($requestedInclude)
475
    {
476
        return 'filter' . implode('', array_map('ucfirst', explode('.', $requestedInclude)));
477
    }
478
479
    /**
480
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
481
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
482
     *
483
     * returns includeProject when $requestedInclude = project
484
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
485
     *
486
     * @param string $field
487
     * @return string
488
     */
489
    private function decodeMethodFromRequestedOrder($field)
490
    {
491
        return 'orderBy' . implode('', array_map('ucfirst', explode('.', $field)));
492
    }
493
494
    /**
495
     * Calculates total pages for an $incompleteStatement. Incomplete statements are doctrine query builder instances
496
     * with all required conditions but no select statement and no additional includes.
497
     *
498
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
499
     * @param int $limit
500
     * @return float|int
501
     */
502
    private function calculateTotalPages($incompleteStatement, $limit)
503
    {
504
        $incompleteStatement = clone $incompleteStatement;
505
506
        if ($limit) {
507
            return (int)ceil($this->executeRowCountStatement($incompleteStatement) / $limit);
508
        }
509
        return 1;
510
    }
511
512
    /**
513
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
514
     * @return int
515
     */
516
    private function executeRowCountStatement($incompleteStatement)
517
    {
518
        $rootAlias = $this->getRootAlias($incompleteStatement);
519
        $primaryIndexCol = $rootAlias . '.' . $this->getPrimaryIndexCol();
520
521
        if ($incompleteStatement->getDQLPart('having')) {
522
            $rootEntities = $incompleteStatement->getRootEntities();
523
            return (int)$incompleteStatement->getEntityManager()->createQueryBuilder()
524
                ->select('COUNT(x)')
525
                ->from(array_shift($rootEntities), 'x')
526
                ->where(
527
                    $incompleteStatement->expr()->in(
528
                        'x.'.$this->getPrimaryIndexCol(),
529
                        $incompleteStatement->select($primaryIndexCol)->getDQL()
530
                    )
531
                )->setParameters($incompleteStatement->getParameters())->getQuery()->getSingleScalarResult();
532
        }
533
534
        return (int)$incompleteStatement
535
            ->select("COUNT(DISTINCT $primaryIndexCol)")
536
            ->getQuery()
537
            ->getSingleScalarResult();
538
    }
539
540
    /**
541
     * Doctrine will throw errors if a table has a multi column primary index
542
     * http://stackoverflow.com/questions/18968963/select-countdistinct-error-on-multiple-columns
543
     * @return string
544
     */
545
    protected function getPrimaryIndexCol()
546
    {
547
        return 'id';
548
    }
549
550
    /**
551
     * Todo: Include collections by additional params and not by includes and adjust docs
552
     * Takes the include string and decodes it to an array with include names as keys and an array with additionalParams
553
     * as the value. Includes that are nested inside included collections are grouped and added as additional params
554
     * to the included collection.
555
     *
556
     * @param $string
557
     * @param $availableIncludes
558
     * @return array
559
     */
560
    private function parseIncludeString($string, $availableIncludes)
561
    {
562
        if ($string === '') {
563
            return [];
564
        }
565
566
        if (is_string($string)) {
567
            $string = explode(',', $string);
568
        }
569
570
        if (!is_array($string)) {
571
            return [];
572
        }
573
574
        $requestedIncludes = [];
575
        $implicitIncludes = [];
576
        foreach ($string as $include) {
577
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
578
579
            $pathToFirstRecursiveInclusion = $this->pathForNestedInclude($includeName, $availableIncludes);
580
            if ($pathToFirstRecursiveInclusion) {
581
                $requestedIncludes[$pathToFirstRecursiveInclusion]['include'][] = substr(
582
                    $include,
583
                    strlen($pathToFirstRecursiveInclusion) + 1
584
                );
585
                continue;
586
            }
587
588
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
589
590
            if ($allModifiersStr === null) {
591
                if (!isset($requestedIncludes[$includeName])) {
592
                    $requestedIncludes[$includeName] = [];
593
                }
594
                continue;
595
            }
596
597
            if (preg_match('~filter\(~u', $allModifiersStr)) {
598
                $modifierArr = $this->parseModifierArraySlowButAccurate($allModifiersStr);
599
            } else {
600
                $modifierArr = $this->parseModifierStringQuickButInaccurate($allModifiersStr);
601
            }
602
603
            if (isset($requestedIncludes[$includeName])) {
604
                $requestedIncludes[$includeName] = $requestedIncludes[$includeName] + $modifierArr;
605
            } else {
606
                $requestedIncludes[$includeName] = $modifierArr;
607
            }
608
        }
609
610
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
611
    }
612
613
    /**
614
     * creates an array out of string in this format:
615
     *
616
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3)
617
     *
618
     * Result:
619
     * [
620
     *  'modifierName1' => ['modifierParam1','modifierParam2'],
621
     *  'modifierName2' => ['modifierParam3']
622
     * ]
623
     *
624
     * But doesn't work when modifier params contain other modifiers with params themselves
625
     *
626
     * @param string $allModifiersStr
627
     * @return array
628
     */
629
    private function parseModifierStringQuickButInaccurate($allModifiersStr)
630
    {
631
        // Matches multiple instances of 'something(foo|bar|baz)' in the string
632
        // I guess it ignores : so you could use anything, but probably don't do that
633
        preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
634
        // [0] is full matched strings...
635
        $modifierCount = count($allModifiersArr[0]);
636
        $modifierArr = [];
637 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...
638
            // [1] is the modifier
639
            $modifierName = $allModifiersArr[1][$modifierIt];
640
            // and [3] is delimited params
641
            $modifierParamStr = $allModifiersArr[3][$modifierIt];
642
            // Make modifier array key with an array of params as the value
643
            $modifierArr[$modifierName] = explode('|', $modifierParamStr);
644
        }
645
646
        return $modifierArr;
647
    }
648
649
    /**
650
     * creates an array out of string in this format:
651
     *
652
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3))
653
     *
654
     * Can also handle modifier params that contain other modifier with params
655
     *
656
     * @param string $s
657
     * @return array
658
     */
659 View Code Duplication
    private function parseModifierArraySlowButAccurate($s)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
718
    {
719
        $paramArr = [];
720
        $tmpStr = '';
721
722
        $depth = 0;
723
        for ($i = 0; $i <= strlen($s); $i++) {
724
            switch ($s[$i]) {
725
                case '(':
726
                    $tmpStr .= $s[$i];
727
                    $depth++;
728
                    break;
729
730
                case ')':
731
                    $tmpStr .= $s[$i];
732
                    $depth--;
733
                    break;
734
735
                case '|':
736
                    if ($depth) {
737
                        $tmpStr .= $s[$i];
738
                    } else {
739
                        $paramArr[] = $tmpStr;
740
                        $tmpStr = '';
741
                    }
742
                    break;
743
744
                default:
745
                    $tmpStr .= $s[$i];
746
            }
747
        }
748
749
        if (strlen($tmpStr)) {
750
            $paramArr[] = $tmpStr;
751
        }
752
753
        return $paramArr;
754
    }
755
756
    /**
757
     * Checks if includeName is an include nested inside a recursive inclusion.
758
     * If yes, return the path to that item - false otherwise.
759
     *
760
     * Example:
761
     * For projects there can be an include for phases. Phases are included recursively in its own adapter. So you'd
762
     * want when you include phases.steps that the steps inclusion is executed in the phase adapter and not in the
763
     * project adapter. That's why we need to separate includes that need to be passed further here.
764
     *
765
     * "recursiveinclude" results to false
766
     * "normalprop1" results to false
767
     * "recursiveinclude.normalprop1.normalprop2" results to "recursiveinclude"
768
     * "normalprop1.recursiveinclude.normalprop2" results to "normalprop1.recursiveinclude"
769
     *
770
     * @param $includeName
771
     * @param $availableIncludes
772
     * @return bool|string
773
     */
774
    private function pathForNestedInclude($includeName, $availableIncludes)
775
    {
776
        $pathArray = explode('.', $includeName);
777
        if (!isset($pathArray[1])) {
778
            return false;
779
        }
780
781
        $pathArrayLength = count($pathArray);
782
        for ($i = 1; $i < $pathArrayLength; $i++) {
783
            $implicitPath = implode('.', array_slice($pathArray, 0, $i));
784
            if ($this->extractStrategyForInclude($availableIncludes[$implicitPath]) === self::INCLUDE_RECURSIVE) {
785
                return $implicitPath;
786
            }
787
        }
788
789
        return false;
790
    }
791
792
    /**
793
     * Include configuration can either have just the strategy or a configuration array with the strategy inside.
794
     *
795
     * @param mixed $include
796
     * @return integer
797
     */
798
    private function extractStrategyForInclude($include)
799
    {
800
        return is_array($include) ? $include['strategy'] : $include;
801
    }
802
803
    /**
804
     * Validates the include string and returns an array with requiredIncludes
805
     *
806
     * @param string $string
807
     * @return array
808
     */
809
    private function parseRichParamString($string)
810
    {
811
        if ($string === '') {
812
            return [];
813
        }
814
815
        if (is_string($string)) {
816
            $string = explode(',', $string);
817
        }
818
819
        if (!is_array($string)) {
820
            return [];
821
        }
822
823
        $requestedIncludes = [];
824
        $implicitIncludes = [];
825
        foreach ($string as $include) {
826
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
827
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
828
829
            if ($allModifiersStr === null) {
830
                $requestedIncludes[$includeName] = [];
831
                continue;
832
            }
833
834
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
835
            // I guess it ignores : so you could use anything, but probably don't do that
836
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
837
            // [0] is full matched strings...
838
            $modifierCount = count($allModifiersArr[0]);
839
            $modifierArr = [];
840 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...
841
                // [1] is the modifier
842
                $modifierName = $allModifiersArr[1][$modifierIt];
843
                // and [3] is delimited params
844
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
845
                // Make modifier array key with an array of params as the value
846
                $modifierArr[$modifierName] = explode('|', $modifierParamStr);
847
            }
848
            $requestedIncludes[$includeName] = $modifierArr;
849
        }
850
851
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
852
    }
853
854
    private function mergeWithImplicitIncludes($includes, $implicitIncludes)
855
    {
856
        foreach ($implicitIncludes as $implicitInclude) {
857
            if (isset($includes[$implicitInclude])) {
858
                continue;
859
            }
860
861
            $includes[$implicitInclude] = [];
862
        }
863
864
        return $includes;
865
    }
866
867
    /**
868
     * @param \Doctrine\ORM\QueryBuilder $query
869
     * @param string $alias
870
     * @param \UnserAller_Model_User $currentUser
871
     * @param array $additionalParams
872
     * @return \Doctrine\ORM\Query\Expr\Andx
873
     */
874
    protected function filterId($query, $alias, $currentUser, $additionalParams)
875
    {
876
        $rootAliasArr = $query->getRootAliases();
877
        $rootAlias = array_shift($rootAliasArr);
878
        return $this->createConditionsForEntityColumn("$rootAlias.id", $query, $alias, $currentUser, $additionalParams);
879
    }
880
881
    private function getImplicitIncludes($includeName)
882
    {
883
        $parts = explode('.', $includeName);
884
        $numberOfParts = count($parts);
885
886
        if ($numberOfParts < 2) {
887
            return [];
888
        }
889
890
        $implicitIncludes = [];
891
        for ($i = 1; $i < $numberOfParts; $i++) {
892
            $implicitIncludes[] = implode('.', array_slice($parts, 0, $i));
893
        }
894
895
        return $implicitIncludes;
896
    }
897
898
    /**
899
     * Creates fixed, paginated results from an $incompleteStatement and a requiredIncludes string. An incomplete
900
     * statement is a query builder instance with only froms, joins and where conditions (groupbys, havings not tested).
901
     *
902
     * @param \UnserAller_Model_User $currentUser
903
     * @param $language
904
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
905
     * @param string $filterString
906
     * @param string $includeString
907
     * @param int $limit
908
     * @param int $page
909
     * @return array
910
     */
911
    protected function createPaginatedResults(
912
        $currentUser,
913
        $language,
914
        $incompleteStatement,
915
        $filterString,
916
        $includeString,
917
        $limit,
918
        $page
919
    ) {
920
        $this->addFilterStatements($incompleteStatement, $currentUser, $filterString, $language);
921
        $completeStatement = $this->completeStatement(
922
            $currentUser,
923
            $language,
924
            $incompleteStatement,
925
            $includeString,
926
            ''
927
        );
928
929
        if ($limit > 0) {
930
            $completeStatement
931
                ->setFirstResult(($page - 1) * $limit)
932
                ->setMaxResults($limit);
933
        }
934
935
        return [
936
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
937
            'page' => $page,
938
            'filter' => $filterString,
939
            'include' => $includeString,
940
            'pageSize' => $limit,
941
            'data' => $this->applyScheduledFixes($this->getRawResult($completeStatement), $currentUser, $language)
942
        ];
943
    }
944
945
    /**
946
     * @param \UnserAller_Model_User $currentUser
947
     * @return \Doctrine\ORM\QueryBuilder
948
     */
949
    abstract protected function initIncompleteStatement($currentUser);
950
951
    private function createIncompleteStatement(
952
        $currentUser,
953
        $filterString,
954
        $language,
955
        $joinFiltersWith = 'AND',
956
        &$meta = []
957
    ) {
958
        return $this->addFilterStatements(
959
            $this->initIncompleteStatement($currentUser),
960
            $currentUser,
961
            $filterString,
962
            $language,
963
            $joinFiltersWith
964
        );
965
    }
966
967
    /**
968
     * @param \UnserAller_Model_User $currentUser
969
     * @param string $filterString
970
     * @param $language
971
     * @param string $joinFiltersWith
972
     * @return int
973
     */
974
    public function findTotalNumberOfRows($currentUser, $filterString = '', $language = '', $joinFiltersWith = 'AND')
975
    {
976
        return $this->executeRowCountStatement(
977
            $this->createIncompleteStatement($currentUser, $filterString, $language, $joinFiltersWith)
978
        );
979
    }
980
981
    /**
982
     * @param \UnserAller_Model_User $currentUser
983
     * @param $language
984
     * @param string $filterString
985
     * @param string $includeString
986
     * @param string $orderString
987
     * @param int $limit
988
     * @param int $page
989
     * @param string $joinFiltersWith
990
     * @return array
991
     */
992
    public function findMultipleForApi(
993
        $currentUser,
994
        $language = '',
995
        $filterString = '',
996
        $includeString = '',
997
        $orderString = '',
998
        $limit = 0,
999
        $page = 1,
1000
        $joinFiltersWith = 'AND'
1001
    ) {
1002
        if ($page <= 0) {
1003
            $page = 1;
1004
        }
1005
1006
        $meta = $this->initMetaArray('', $language);
1007
1008
        $incompleteStatement = $this->createIncompleteStatement(
1009
            $currentUser,
1010
            $filterString,
1011
            $language,
1012
            $joinFiltersWith,
1013
            $meta
1014
        );
1015
1016
        $completeStatement = $this->completeStatement(
1017
            $currentUser,
1018
            $language,
1019
            $incompleteStatement,
1020
            $includeString,
1021
            $orderString,
1022
            $meta
1023
        );
1024
        if ($limit > 0) {
1025
            $completeStatement
1026
                ->setFirstResult(($page - 1) * $limit)
1027
                ->setMaxResults($limit);
1028
        }
1029
1030
        return [
1031
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
1032
            'filter' => $filterString,
1033
            'include' => $includeString,
1034
            'page' => $page,
1035
            'pageSize' => $limit,
1036
            'data' => $this->applyScheduledFixes(
1037
                $this->getRawResult($completeStatement),
1038
                $currentUser,
1039
                $language,
1040
                $meta
1041
            ),
1042
            'meta' => $meta
1043
        ];
1044
    }
1045
1046
    private function initMetaArray($modelPathOffset = '', $language = '')
1047
    {
1048
        return [
1049
            'modelnameIndex' => [
1050
                $this->getModelForMeta() => [$modelPathOffset]
1051
            ],
1052
            'language' => $language
1053
        ];
1054
    }
1055
1056
    /**
1057
     * @param string $language
1058
     * @param string $filterString
1059
     * @param string $includeString
1060
     * @param string $orderString
1061
     * @param int $limit
1062
     * @param int $page
1063
     * @param string $filterMode
1064
     * @return array
1065
     */
1066
    public function findMultiple(
1067
        $language = '',
1068
        $filterString = '',
1069
        $includeString = '',
1070
        $orderString = '',
1071
        $limit = 0,
1072
        $page = 1,
1073
        $filterMode = 'AND'
1074
    ) {
1075
        return json_decode(json_encode($this->findMultipleForApi(
1076
            $this->getCurrentlyAuthenticatedUser(),
1077
            $language,
1078
            $filterString,
1079
            $includeString,
1080
            $orderString,
1081
            $limit,
1082
            $page,
1083
            $filterMode
1084
        )), true);
1085
    }
1086
1087
    /**
1088
     * @param \UnserAller_Model_User $currentUser
1089
     * @param string $language
1090
     * @param string $filterString
1091
     * @param string $includeString
1092
     * @param string $orderString
1093
     * @param int $limit
1094
     * @param string $filterMode
1095
     * @return \Generator
1096
     */
1097
    public function batchFindMultiple(
1098
        $currentUser,
1099
        $language = '',
1100
        $filterString = '',
1101
        $includeString = '',
1102
        $orderString = '',
1103
        $limit = 500,
1104
        $filterMode = 'AND'
1105
    ) {
1106
        $page = 1;
1107
1108
        $result = $this->findMultipleForApi(
1109
            $currentUser,
1110
            $language,
1111
            $filterString,
1112
            $includeString,
1113
            $orderString,
1114
            $limit,
1115
            $page,
1116
            $filterMode
1117
        );
1118
1119
        yield $result;
1120
1121
        $totalPages = $result['totalPages'];
1122
        unset($result);
1123
        $page++;
1124
        while ($page <= $totalPages) {
1125
            $result = $this->findMultipleForApi(
1126
                $currentUser,
1127
                $language,
1128
                $filterString,
1129
                $includeString,
1130
                $orderString,
1131
                $limit,
1132
                $page,
1133
                $filterMode
1134
            );
1135
            yield $result;
1136
            $page++;
1137
            unset($result);
1138
        }
1139
    }
1140
1141
    /**
1142
     * @param \UnserAller_Model_User $currentUser
1143
     * @param string $language
1144
     * @param string $filterString
1145
     * @param string $includeString
1146
     * @param string $orderString
1147
     * @param int $limit
1148
     * @param int $page
1149
     * @param string $filterMode
1150
     * @return array
1151
     */
1152
    public function getNativeSqlIngredientsForFindMultiple(
1153
        $currentUser,
1154
        $language = '',
1155
        $filterString = '',
1156
        $includeString = '',
1157
        $orderString = '',
1158
        $limit = 0,
1159
        $page = 1,
1160
        $filterMode = 'AND'
1161
    ) {
1162
        if ($page <= 0) {
1163
            $page = 1;
1164
        }
1165
1166
        $incompleteStatement = $this->createIncompleteStatement($currentUser, $filterString, $language, $filterMode);
1167
1168
        $completeStatement = $this->completeStatement(
1169
            $currentUser,
1170
            $language,
1171
            $incompleteStatement,
1172
            $includeString,
1173
            $orderString
1174
        );
1175
        if ($limit > 0) {
1176
            $completeStatement
1177
                ->setFirstResult(($page - 1) * $limit)
1178
                ->setMaxResults($limit);
1179
        }
1180
1181
        return $this->getNativeSqlIngredients($completeStatement->getQuery());
1182
    }
1183
1184
    /**
1185
     * @param \Doctrine\ORM\Query $query
1186
     * @return array
1187
     */
1188
    private function getNativeSqlIngredients($query)
1189
    {
1190
        $sql = $query->getSQL();
1191
        $c = new \ReflectionClass('Doctrine\ORM\Query');
1192
        $parser = $c->getProperty('_parserResult');
1193
        $parser->setAccessible(true);
1194
        /** @var \ReflectionProperty $parser */
1195
        $parser = $parser->getValue($query);
1196
        /** @var \Doctrine\ORM\Query\ParserResult $parser */
1197
        $resultSet = $parser->getResultSetMapping();
1198
1199
        // Change the aliases back to what was originally specified in the QueryBuilder.
1200
        $sql = preg_replace_callback('/AS\s([a-zA-Z0-9_]+)/', function ($matches) use ($resultSet) {
1201
            $ret = 'AS ';
1202
            if ($resultSet->isScalarResult($matches[1])) {
1203
                $ret .= $resultSet->getScalarAlias($matches[1]);
1204
            } else {
1205
                $ret .= $matches[1];
1206
            }
1207
            return $ret;
1208
        }, $sql);
1209
        $m = $c->getMethod('processParameterMappings');
1210
        $m->setAccessible(true);
1211
        list($params, $types) = $m->invoke($query, $parser->getParameterMappings());
1212
        return [$sql, $params, $types];
1213
    }
1214
1215
    /**
1216
     * @param \UnserAller_Model_User $currentUser
1217
     * @param string $language
1218
     * @param string $filterString
1219
     * @param string $includeString
1220
     * @param string $orderString
1221
     * @return array|null
1222
     */
1223
    public function findOneForApi(
1224
        $currentUser,
1225
        $language = '',
1226
        $filterString = '',
1227
        $includeString = '',
1228
        $orderString = ''
1229
    ) {
1230
        return $this->createSingleResult($currentUser, $language, $filterString, $includeString, $orderString);
1231
    }
1232
1233
    /**
1234
     * @param string $language
1235
     * @param string $filterString
1236
     * @param string $includeString
1237
     * @param string $orderString
1238
     * @return array|null
1239
     */
1240
    public function findOne($language = '', $filterString = '', $includeString = '', $orderString = '')
1241
    {
1242
        return json_decode(json_encode($this->findOneForApi(
1243
            $this->getCurrentlyAuthenticatedUser(),
1244
            $language,
1245
            $filterString,
1246
            $includeString,
1247
            $orderString
1248
        )), true);
1249
    }
1250
1251
    /**
1252
     * @param \UnserAller_Model_User $currentUser
1253
     * @param int $id
1254
     * @param string $language
1255
     * @param string $include
1256
     * @return array|null
1257
     */
1258
    public function findForApi($currentUser, $id, $language = '', $include = '')
1259
    {
1260
        $this->id = (int)$id;
1261
        return $this->findOneForApi($currentUser, $language, "id:is($id)", $include, '');
1262
    }
1263
1264
    /**
1265
     * @param int $id
1266
     * @param string $language
1267
     * @param string $include
1268
     * @return array|null
1269
     */
1270
    public function find($id, $language = '', $include = '')
1271
    {
1272
        return json_decode(json_encode($this->findForApi(
1273
            $this->getCurrentlyAuthenticatedUser(),
1274
            $id,
1275
            $language,
1276
            $include
1277
        )), true);
1278
    }
1279
1280
    /**
1281
     * @return null|\UnserAller_Model_User|object
1282
     */
1283
    private function getCurrentlyAuthenticatedUser()
1284
    {
1285
        return $this->getEntityManager()->find(
1286
            \UnserAller_Model_User::class,
1287
            (int)\Zend_Auth::getInstance()->getIdentity()
1288
        );
1289
    }
1290
1291
    /**
1292
     * @param \UnserAller_Model_User $currentUser
1293
     * @param string $language
1294
     * @param string $filterString
1295
     * @param string $includeString
1296
     * @param string $orderString
1297
     * @return array|null
1298
     */
1299
    protected function createSingleResult($currentUser, $language, $filterString, $includeString, $orderString)
1300
    {
1301
        $meta = $this->initMetaArray('', $language);
1302
1303
        $result = $this->getRawResult(
1304
            $this->completeStatement(
1305
                $currentUser,
1306
                $language,
1307
                $this->createIncompleteStatement($currentUser, $filterString, $language, 'AND', $meta),
1308
                $includeString,
1309
                $orderString,
1310
                $meta
1311
            )->setFirstResult(0)->setMaxResults(1)
1312
        );
1313
1314
        if (!isset($result[0])) {
1315
            return null;
1316
        }
1317
1318
        $result = $this->applyScheduledFixes($result, $currentUser, $language, $meta)[0];
1319
        $result['meta'] = $result['meta'] + $meta;
1320
        return $result;
1321
    }
1322
1323
    protected function getDefaultPostProcessDirections()
1324
    {
1325
        return [];
1326
    }
1327
1328
    /**
1329
     * Adds the default select statement, all includes and order statements to the incomplete statement
1330
     * and returns the qurey builder instance
1331
     *
1332
     * @param \UnserAller_Model_User $currentUser
1333
     * @param $language
1334
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
1335
     * @param string $includeString
1336
     * @param string $orderString
1337
     * @param array $meta
1338
     * @return \Doctrine\ORM\QueryBuilder
1339
     */
1340
    private function completeStatement(
1341
        $currentUser,
1342
        $language,
1343
        $incompleteStatement,
1344
        $includeString,
1345
        $orderString,
1346
        &$meta = []
1347
    ) {
1348
        $statement = clone $incompleteStatement;
1349
1350
        $this->schedulePostProcessingDirections($this->getDefaultPostProcessDirections());
1351
1352
        return $this->addOrderStatements(
1353
            $this->addIncludeStatements(
1354
                $statement->select($this->getDefaultSelectStatement($statement)),
1355
                $currentUser,
1356
                $language,
1357
                $includeString,
1358
                $meta
1359
            ),
1360
            $currentUser,
1361
            $orderString
1362
        );
1363
    }
1364
1365
    /**
1366
     * Returns the default select statement. In this case it just returns the first root entity which means the
1367
     * entire root entity will be selected
1368
     *
1369
     * @param \Doctrine\ORM\QueryBuilder $query
1370
     * @return string
1371
     */
1372
    protected function getDefaultSelectStatement($query)
1373
    {
1374
        return 'DISTINCT ' . $this->getRootAlias($query);
1375
    }
1376
1377
    /**
1378
     * Returns first root alias from query builder
1379
     *
1380
     * @param \Doctrine\ORM\QueryBuilder $query
1381
     * @return string
1382
     */
1383
    protected function getRootAlias($query)
1384
    {
1385
        $rootAliasArr = $query->getRootAliases();
1386
        return array_shift($rootAliasArr);
1387
    }
1388
1389
    /**
1390
     * Returns true if result item has an additional layer in the hierarchy because of custom subselects
1391
     *
1392
     * @param array $item
1393
     * @return bool
1394
     */
1395
    private function mustFlattenResultItem($item)
1396
    {
1397
        return isset($item[0]);
1398
    }
1399
1400
    /**
1401
     * Returns doctrine array results with all fixes applied
1402
     *
1403
     * @param \Doctrine\ORM\QueryBuilder $statement
1404
     * @return array
1405
     */
1406
    private function getRawResult($statement)
1407
    {
1408
        //Output raw sql here if you like to debug hard
1409
        //echo $statement->getQuery()->getSQL(); die;
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1410
        return $statement->getQuery()->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
1411
    }
1412
1413
    /**
1414
     * Doctrine will create an additional result layer when values are selected that do not belong
1415
     * to the doctrine object model. This function removes this additional layer and merges custom values with
1416
     * the doctrine object model for a single result item - not the whole result array.
1417
     *
1418
     * @param array $item
1419
     * @return array
1420
     */
1421
    private function flattenResultItem($item)
1422
    {
1423
        if (!$this->mustFlattenResultItem($item)) {
1424
            return $item;
1425
        }
1426
1427
        return array_merge(array_shift($item), $item);
1428
    }
1429
1430
    /**
1431
     * Parses $string for orderBy statements and returns an array where order statements are values.
1432
     *
1433
     * When string is "latestFirst, longestFirst" result would be: ['latestFirst', 'longestFirst']
1434
     *
1435
     * @param string $string
1436
     * @return array
1437
     */
1438
    protected function parseOrderString($string)
1439
    {
1440
        return array_filter(array_map('trim', explode(',', $string)));
1441
    }
1442
1443
    /**
1444
     * @uses getFallbackLanguage
1445
     * @uses getRequestedLanguage
1446
     * @return callable
1447
     */
1448
    private function getItemLanguageGetter($language)
1449
    {
1450
        return $language === '' ? 'getFallbackLanguage' : 'getRequestedLanguage';
1451
    }
1452
1453
    /**
1454
     * @param mixed[] $resultItem
1455
     * @param string $requestedLanguage
1456
     * @return string
1457
     */
1458
    private function getRequestedLanguage($resultItem, $requestedLanguage)
1459
    {
1460
        return $requestedLanguage;
1461
    }
1462
1463
    /**
1464
     * Executes all operations that were scheduled for post processing
1465
     *
1466
     * @param array $result
1467
     * @param \UnserAller_Model_User $currentUser
1468
     * @param $language
1469
     * @param array $meta
1470
     * @return array
1471
     */
1472
    private function applyScheduledFixes($result, $currentUser, $language, &$meta = [])
1473
    {
1474
        $scheduledFixes = $this->flushResultArrayFixSchedule();
1475
1476
        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...
1477
            return $result;
1478
        }
1479
1480
        $numberOfResults = count($result);
1481
1482
        $this->applyFixesToItem(
1483
            $result[0],
1484
            $scheduledFixes,
1485
            $currentUser,
1486
            $meta,
1487
            'retrieveNestedCollectionAndMergeMeta',
1488
            $language,
1489
            $this->getItemLanguageGetter($language)
1490
        );
1491
        for ($i = 1; $i < $numberOfResults; $i++) {
1492
            $this->applyFixesToItem(
1493
                $result[$i],
1494
                $scheduledFixes,
1495
                $currentUser,
1496
                $meta,
1497
                'retrieveNestedCollection',
1498
                $language,
1499
                $this->getItemLanguageGetter($language)
1500
            );
1501
        }
1502
1503
        return $result;
1504
    }
1505
1506
    private function retrieveNestedCollectionResult($value, $nestingOptions, $language = '')
1507
    {
1508
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1509
        list(
1510
            $filterString,
1511
            $includeString,
1512
            $orderString,
1513
            $limit,
1514
            $page,
1515
            $filterMode
1516
            ) = $this->parseAdditionalIncludeParams($additionalParams);
1517
1518
        if ($filterString) {
1519
            $filterString = $filterString . ',';
1520
        }
1521
1522
        if (is_array($value)) {
1523
            $filterFunctionString = vsprintf($filterFunction, array_merge($value));
1524
        } else {
1525
            $filterFunctionString = sprintf($filterFunction, $value);
1526
        }
1527
1528
        return $this->getAdapter($model)->findMultipleForApi(
1529
            $currentUser,
1530
            $language,
1531
            $filterString . $filterFunctionString,
1532
            $includeString,
1533
            $orderString,
1534
            $limit,
1535
            $page,
1536
            $filterMode
1537
        );
1538
    }
1539
1540
    private function retrieveNestedCollection($value, $nestingOptions, $language, $finalPath, $meta)
1541
    {
1542
        return $this->retrieveNestedCollectionResult($value, $nestingOptions, $language)['data'];
1543
    }
1544
1545
    private function retrieveNestedCollectionAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1546
    {
1547
        $result = $this->retrieveNestedCollectionResult($value, $nestingOptions, $language);
1548
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1549
        return $result['data'];
1550
    }
1551
1552
    private function retrieveNestedSingleResult($value, $nestingOptions, $language = '')
1553
    {
1554
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1555
        list($filterString, $includeString, $orderString, , ,) = $this->parseAdditionalIncludeParams($additionalParams);
1556
1557
        if ($filterString) {
1558
            $filterString = $filterString . ',';
1559
        }
1560
1561
        return $this->getAdapter($model)->findOneForApi(
1562
            $currentUser,
1563
            $language,
1564
            $filterString . sprintf($filterFunction, $value),
1565
            $includeString,
1566
            $orderString
1567
        );
1568
    }
1569
1570
    private function retrieveNestedSingleAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1571
    {
1572
        $result = $this->retrieveNestedSingleResult($value, $nestingOptions, $language);
1573
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1574
        unset($result['meta']);
1575
        return $result;
1576
    }
1577
1578
    private function mergeNestedMeta(&$meta, $nestedMeta, $includeName)
1579
    {
1580
        foreach ($nestedMeta['modelnameIndex'] as $model => $paths) {
1581
            foreach ($paths as $path) {
1582
                $fullPath = $includeName . '.' . $path;
1583
                if ($path && !in_array($fullPath, $meta['modelnameIndex'][$model])) {
1584
                    $meta['modelnameIndex'][$model][] = $fullPath;
1585
                }
1586
            }
1587
        }
1588
    }
1589
1590
    /**
1591
     * @param $item
1592
     * @param $scheduledFixes
1593
     * @param $currentUser
1594
     * @param $meta
1595
     * @param $collectionNestingMethod
1596
     * @param $language
1597
     * @uses retrieveNestedCollection
1598
     * @uses retrieveNestedCollectionAndMergeMeta
1599
     */
1600
    private function applyFixesToItem(
1601
        &$item,
1602
        $scheduledFixes,
1603
        $currentUser,
1604
        &$meta,
1605
        $collectionNestingMethod,
1606
        $language,
1607
        $itemLanguageGetter
1608
    ) {
1609
        $item = $this->flattenResultItem($item);
1610
1611
        //If deleteion is not scheduled and done at the end, multiple tasks on same field can fail
1612
        $scheduledDeletions = [];
1613
1614
        foreach ($scheduledFixes as $path => $fix) {
1615
            if (isset($fix['delete'])) {
1616
                $scheduledDeletions[$path] = $fix;
1617
                continue;
1618
            }
1619
1620
            if (isset($fix['cast'])) {
1621
                \UnserAllerLib_Tool_Array::castNestedValue($item, $path, $fix['cast']);
1622
            }
1623
1624
            $value = \UnserAllerLib_Tool_Array::readNestedValue($item, $path);
1625
1626
            if (isset($fix['additionalFilterValues'])) {
1627
                $value = [$value];
1628
1629
                foreach ($fix['additionalFilterValues'] as $additionalFilterValue) {
1630
                    $value[] = \UnserAllerLib_Tool_Array::readNestedValue($item, $additionalFilterValue);
1631
                }
1632
            }
1633
1634 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...
1635
                $value = $this->$collectionNestingMethod(
1636
                    $value,
1637
                    $fix['nestCollection'],
1638
                    $this->$itemLanguageGetter($item, $language),
1639
                    $fix['move'] ? $fix['move'] : $path,
1640
                    $meta
1641
                );
1642
            }
1643
1644 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...
1645
                $value = $this->retrieveNestedSingleAndMergeMeta(
1646
                    $value,
1647
                    $fix['nestSingle'],
1648
                    $this->$itemLanguageGetter($item, $language),
1649
                    $fix['move'] ? $fix['move'] : $path,
1650
                    $meta
1651
                );
1652
            }
1653
1654
            if (isset($fix['filter'])) {
1655
                $value = $this->filterValue($fix['filter'], $value, $currentUser);
1656
            }
1657
1658
            if (isset($fix['cFilter'])) {
1659
                $value = $this->filterValue($fix['cFilter'], $value, $currentUser);
1660
            }
1661
1662
            if (isset($fix['mFilter'])) {
1663
                $value = $this->filterValue($fix['mFilter'], $item, $currentUser);
1664
            }
1665
1666
            if (isset($fix['move'])) {
1667
                \UnserAllerLib_Tool_Array::integrateNestedValue($item, $fix['move'], $value);
1668
                if ($path != $fix['move']) {
1669
                    $scheduledDeletions[$path] = ['delete' => 1];
1670
                }
1671
            }
1672
        }
1673
1674
        foreach ($scheduledDeletions as $path => $fix) {
1675
            \UnserAllerLib_Tool_Array::unsetNestedValue($item, $path);
1676
        }
1677
1678
        $item['meta'] = $this->createMetadataForResultItem($item, $language, $itemLanguageGetter);
1679
    }
1680
1681
    /**
1682
     * @param mixed[] $resultItem
1683
     * @param string $requestedLanguage
1684
     * @param callable $itemLanguageGetter
1685
     * @return array
1686
     */
1687
    private function createMetadataForResultItem($resultItem, $requestedLanguage, $itemLanguageGetter)
1688
    {
1689
        return [
1690
            'language' => $this->$itemLanguageGetter($resultItem, $requestedLanguage),
1691
            'model' => $this->getModelForMeta()
1692
        ];
1693
    }
1694
1695
    /**
1696
     * Applies filter methods for $filterName to $value
1697
     * @uses filterJsonAfterwards
1698
     * @uses filterJsonIfNullSetEmptyObjectAfterwards
1699
     * @uses filterJsonOrNullAfterwards
1700
     * @uses filterDatetimeAfterwards
1701
     * @uses filterDatetimeOrNullAfterwards
1702
     * @uses filterIntOrNullAfterwards
1703
     * @uses filterNl2BrAfterwards
1704
     * @param string $filterName
1705
     * @param mixed $value
1706
     * @param $currentUser
1707
     * @return mixed
1708
     */
1709
    private function filterValue($filterName, $value, $currentUser)
1710
    {
1711
        if (!is_callable([$this, $filterName])) {
1712
            throw new \InvalidArgumentException('Post Processing Filter method not found: ' . $filterName);
1713
        }
1714
1715
        return call_user_func_array([$this, $filterName], [$value, $currentUser]);
1716
    }
1717
1718
    /**
1719
     * @param $field
1720
     * @param \Doctrine\ORM\QueryBuilder $query
1721
     * @param string $alias
1722
     * @param \UnserAller_Model_User $currentUser
1723
     * @param array $methods
1724
     * @return \Doctrine\ORM\Query\Expr\Andx
1725
     * @uses stringContainExpression
1726
     * @uses stringContainsExpression
1727
     * @uses stringIsExpression
1728
     * @uses stringNotExpression
1729
     * @uses stringFalseExpression
1730
     * @uses stringTrueExpression
1731
     */
1732
    protected function createConditionsForStringColumn($field, $query, $alias, $currentUser, $methods)
1733
    {
1734
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1735
            $methods,
1736
            ['contain', 'contains', 'is', 'not', 'false', 'true']
1737
        )
1738
        ) {
1739
            throw new \InvalidArgumentException('Invalid expression methods used');
1740
        }
1741
1742
        return $this->createExpression('string', $field, $query, $alias, $currentUser, $methods);
1743
    }
1744
1745
    /**
1746
     * @param \Doctrine\ORM\QueryBuilder $query
1747
     * @param $fallbackField
1748
     * @param $translationName
1749
     * @param $language
1750
     * @param string $alias
1751
     * @param \UnserAller_Model_User $currentUser
1752
     * @param $additionalParams
1753
     * @return \Doctrine\ORM\Query\Expr\Composite
1754
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidFilter
1755
     */
1756
    protected function createConditionsForMultilanguageStringColumn(
1757
        $query,
1758
        $fallbackField,
1759
        $translationName,
1760
        $language,
1761
        $alias,
1762
        $currentUser,
1763
        $additionalParams
1764
    ) {
1765
        if (isset($additionalParams['overAllTranslations'])) {
1766
            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...
1767
                throw new \UnserAllerLib_Api_V4_Exception_InvalidFilter('Supported languages are not set');
1768
            }
1769
1770
            unset($additionalParams['overAllTranslations']);
1771
1772
            $expr = $query->expr()->orX();
1773
            foreach ($this->supportedLanguages as $supportedLanguage) {
1774
                $expr->add($this->createConditionsForStringColumn(
1775
                    "COALESCE(" . $this->joinTranslationOnce(
1776
                        $query,
1777
                        $translationName,
1778
                        $supportedLanguage
1779
                    ) . ".translation, $fallbackField)",
1780
                    $query,
1781
                    $alias,
1782
                    $currentUser,
1783
                    $additionalParams
1784
                ));
1785
            }
1786
1787
            return $expr;
1788
        }
1789
1790
        return $this->createConditionsForStringColumn(
1791
            "COALESCE(" . $this->joinTranslationOnce(
1792
                $query,
1793
                $translationName,
1794
                $language
1795
            ) . ".translation, $fallbackField)",
1796
            $query,
1797
            $alias,
1798
            $currentUser,
1799
            $additionalParams
1800
        );
1801
    }
1802
1803
1804
    /**
1805
     * @param $field
1806
     * @param \Doctrine\ORM\QueryBuilder $query
1807
     * @param string $alias
1808
     * @param \UnserAller_Model_User $currentUser
1809
     * @param array $methods
1810
     * @return \Doctrine\ORM\Query\Expr\Andx
1811
     * @uses dateGtExpression
1812
     * @uses dateGteExpression
1813
     * @uses dateLtExpression
1814
     * @uses dateLteExpression
1815
     * @uses dateFalseExpression
1816
     * @uses dateTrueExpression
1817
     * @uses dateIsExpression
1818
     * @uses dateNotExpression
1819
     */
1820 View Code Duplication
    protected function createConditionsForDatetimeColumn($field, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1821
    {
1822
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1823
            $methods,
1824
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true']
1825
        )
1826
        ) {
1827
            throw new \InvalidArgumentException('Invalid expression methods used');
1828
        }
1829
1830
        return $this->createExpression('date', $field, $query, $alias, $currentUser, $methods);
1831
    }
1832
1833
    /**
1834
     * @param $field
1835
     * @param \Doctrine\ORM\QueryBuilder $query
1836
     * @param string $alias
1837
     * @param \UnserAller_Model_User $currentUser
1838
     * @param array $methods
1839
     * @return \Doctrine\ORM\Query\Expr\Andx
1840
     * @uses integerFalseExpression
1841
     * @uses integerTrueExpression
1842
     * @uses integerIsExpression
1843
     * @uses integerNotExpression
1844
     * @uses integerMeExpression
1845
     * @uses integerNotmeExpression
1846
     */
1847
    protected function createConditionsForEntityColumn($field, $query, $alias, $currentUser, $methods)
1848
    {
1849
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true', 'is', 'not', 'me', 'notme'])) {
1850
            throw new \InvalidArgumentException('Invalid expression methods used');
1851
        }
1852
1853
        return $this->createExpression('integer', $field, $query, $alias, $currentUser, $methods);
1854
    }
1855
1856
    /**
1857
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1858
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1859
     * on a subquery. And it seems to be impossible to not return null values either
1860
     * Todo: Needs research, for time being only true comparison is working as expected
1861
     *
1862
     *
1863
     * @param $subquery
1864
     * @param \Doctrine\ORM\QueryBuilder $query
1865
     * @param string $alias
1866
     * @param \UnserAller_Model_User $currentUser
1867
     * @param array $methods
1868
     * @return \Doctrine\ORM\Query\Expr\Andx
1869
     * @uses subqueryFalseExpression
1870
     * @uses subqueryTrueExpression
1871
     * @uses subqueryGtExpression
1872
     * @uses subqueryGteExpression
1873
     * @uses subqueryLtExpression
1874
     * @uses subqueryLteExpression
1875
     * @uses subqueryEqExpression
1876
     * @uses subqueryAnyExpression
1877
     * @uses subqueryNullExpression
1878
     */
1879 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...
1880
    {
1881
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1882
            $methods,
1883
            ['false', 'true', 'gt', 'gte', 'lt', 'lte', 'eq', 'any', 'null']
1884
        )
1885
        ) {
1886
            throw new \InvalidArgumentException('Invalid expression methods used');
1887
        }
1888
1889
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1890
    }
1891
1892
    /**
1893
     * @param $subquery
1894
     * @param \Doctrine\ORM\QueryBuilder $query
1895
     * @param string $alias
1896
     * @param \UnserAller_Model_User $currentUser
1897
     * @param array $methods
1898
     * @return \Doctrine\ORM\Query\Expr\Andx
1899
     */
1900 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...
1901
    {
1902
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
1903
            throw new \InvalidArgumentException('Invalid expression methods used');
1904
        }
1905
1906
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1907
    }
1908
1909
    /**
1910
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1911
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1912
     * on a subquery. And it seems to be impossible to not return null values either
1913
     * Todo: Needs research, for time being only true comparison is working as expected
1914
     *
1915
     *
1916
     * @param $subquery
1917
     * @param \Doctrine\ORM\QueryBuilder $query
1918
     * @param string $alias
1919
     * @param \UnserAller_Model_User $currentUser
1920
     * @param array $methods
1921
     * @return \Doctrine\ORM\Query\Expr\Andx
1922
     * @uses subqueryAnyisExpression
1923
     */
1924 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...
1925
    {
1926
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['anyis'])) {
1927
            throw new \InvalidArgumentException('Invalid expression methods used');
1928
        }
1929
1930
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1931
    }
1932
1933
    /**
1934
     * Translates params into where conditions. The subquery must really return an integer for it to work!
1935
     * Returning null will cause wrong bahvior!!! In DQL it seems to be impossible to do an IS NULL comparison
1936
     * on a subquery. And it seems to be impossible to not return null values either
1937
     * Todo: Needs research, for time being only true comparison is working as expected
1938
     *
1939
     *
1940
     * @param $subquery
1941
     * @param \Doctrine\ORM\QueryBuilder $query
1942
     * @param string $alias
1943
     * @param \UnserAller_Model_User $currentUser
1944
     * @param array $methods
1945
     * @return \Doctrine\ORM\Query\Expr\Andx
1946
     * @uses subqueryTrueExpression
1947
     * @uses subqueryFalseExpression
1948
     */
1949 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...
1950
    {
1951
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan($methods, ['false', 'true'])) {
1952
            throw new \InvalidArgumentException('Invalid expression methods used');
1953
        }
1954
1955
        return $this->createExpression('subquery', $subquery, $query, $alias, $currentUser, $methods);
1956
    }
1957
1958
    /**
1959
     * Translates params into where conditions. Null values are handled as you would expect it.
1960
     *
1961
     * @param $col
1962
     * @param \Doctrine\ORM\QueryBuilder $query
1963
     * @param string $alias
1964
     * @param \UnserAller_Model_User $currentUser
1965
     * @param array $methods
1966
     * @return \Doctrine\ORM\Query\Expr\Andx
1967
     * @uses integerIsExpression
1968
     * @uses integerNotExpression
1969
     * @uses integerGtExpression
1970
     * @uses integerGteExpression
1971
     * @uses integerLtExpression
1972
     * @uses integerLteExpression
1973
     * @uses integerFalseExpression
1974
     * @uses integerTrueExpression
1975
     */
1976 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...
1977
    {
1978
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1979
            $methods,
1980
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true']
1981
        )
1982
        ) {
1983
            throw new \InvalidArgumentException('Invalid expression methods used');
1984
        }
1985
1986
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
1987
    }
1988
1989
    /**
1990
     * Todo: Whitelisting allowed subqueries for the any filter makes having this extra function unnecessary
1991
     *
1992
     * This one allows some filter directives that result to function calls on protected methods. Don't ever redirect
1993
     * user content here.
1994
     *
1995
     * Translates params into where conditions. Null values are handled as you would expect it.
1996
     *
1997
     * @param $col
1998
     * @param \Doctrine\ORM\QueryBuilder $query
1999
     * @param string $alias
2000
     * @param \UnserAller_Model_User $currentUser
2001
     * @param array $methods
2002
     * @return \Doctrine\ORM\Query\Expr\Andx
2003
     * @uses integerIsExpression
2004
     * @uses integerNotExpression
2005
     * @uses integerGtExpression
2006
     * @uses integerGteExpression
2007
     * @uses integerLtExpression
2008
     * @uses integerLteExpression
2009
     * @uses integerFalseExpression
2010
     * @uses integerTrueExpression
2011
     * @uses integerAnyExpression
2012
     */
2013 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...
2014
    {
2015
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
2016
            $methods,
2017
            ['is', 'not', 'gt', 'gte', 'lt', 'lte', 'false', 'true', 'any']
2018
        )
2019
        ) {
2020
            throw new \InvalidArgumentException('Invalid expression methods used');
2021
        }
2022
2023
        return $this->createExpression('integer', $col, $query, $alias, $currentUser, $methods);
2024
    }
2025
2026
    /**
2027
     * Knows how to create a callable from a subquery definition
2028
     *
2029
     * @param string $name of subquery
2030
     * @param mixed[] $params for subquerymethod
2031
     * @return callable
2032
     */
2033
    protected function locateCallableSubquery($name, $params)
2034
    {
2035
        return [$this, $name];
2036
    }
2037
2038
    /**
2039
     * @param array $subqueryDefinition
2040
     * @return string DQL
2041
     */
2042
    private function consumeSubquery($subqueryDefinition)
2043
    {
2044
        list($name, $params) = $subqueryDefinition;
2045
        return call_user_func_array(
2046
            $this->locateCallableSubquery($name, $params),
2047
            $params
2048
        );
2049
    }
2050
2051
    /**
2052
     * @param $prefix
2053
     * @param string $field
2054
     * @param \Doctrine\ORM\QueryBuilder $query
2055
     * @param string $alias
2056
     * @param \UnserAller_Model_User $currentUser
2057
     * @param array $methods
2058
     * @return \Doctrine\ORM\Query\Expr\Andx
2059
     */
2060
    private function createExpression($prefix, $field, $query, $alias, $currentUser, $methods)
2061
    {
2062
        $expression = $query->expr()->andX();
2063
        foreach ($methods as $method => $params) {
2064
            $expression->add(call_user_func_array(
2065
                [$this, $prefix . ucfirst($method) . 'Expression'],
2066
                [$query, $field, $params, $alias, $currentUser]
2067
            ));
2068
        }
2069
2070
        return $expression;
2071
    }
2072
2073
    /**
2074
     * @param \Doctrine\ORM\QueryBuilder $query
2075
     * @param array $field
2076
     * @param array $params
2077
     * @param string $alias
2078
     * @return mixed
2079
     */
2080
    private function subqueryFalseExpression($query, $field, $params, $alias)
2081
    {
2082
        return $query->expr()->orX(
2083
            $query->expr()->not($query->expr()->exists($this->consumeSubquery($field))),
2084
            $query->expr()->eq('(' . $this->consumeSubquery($field) . ')', 0)
2085
        );
2086
    }
2087
2088
    /**
2089
     * @param \Doctrine\ORM\QueryBuilder $query
2090
     * @param array $field
2091
     * @param array $params
2092
     * @param string $alias
2093
     * @return mixed
2094
     */
2095
    private function subqueryNullExpression($query, $field, $params, $alias)
2096
    {
2097
        return $query->expr()->not($query->expr()->exists($this->consumeSubquery($field)));
2098
    }
2099
2100
    /**
2101
     * @param \Doctrine\ORM\QueryBuilder $query
2102
     * @param array $subquery
2103
     * @param array $params
2104
     * @param string $alias
2105
     * @return mixed
2106
     */
2107 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...
2108
    {
2109
        return $query->expr()->andX(
2110
            $query->expr()->exists($this->consumeSubquery($subquery)),
2111
            $query->expr()->neq('(' . $this->consumeSubquery($subquery) . ')', 0)
2112
        );
2113
    }
2114
2115
    /**
2116
     * @param \Doctrine\ORM\QueryBuilder $query
2117
     * @param array $subquery
2118
     * @param array $params
2119
     * @param string $alias
2120
     * @return mixed
2121
     */
2122
    private function subqueryAnyisExpression($query, $subquery, $params, $alias)
2123
    {
2124
        $expression = $query->expr()->orX();
2125
        foreach ($params as $param) {
2126
            $alias = uniqid();
2127
            $query->setParameter("param$alias", $param);
2128
            $expression->add(
2129
                $query->expr()->eq(":param$alias", $query->expr()->any($this->consumeSubquery($subquery)))
2130
            );
2131
        }
2132
        return $expression;
2133
    }
2134
2135
    /**
2136
     * @param \Doctrine\ORM\QueryBuilder $query
2137
     * @param array $subquery
2138
     * @param array $params
2139
     * @param string $alias
2140
     * @return mixed
2141
     */
2142 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...
2143
    {
2144
        return $query->expr()->andX(
2145
            $query->expr()->exists($this->consumeSubquery($subquery)),
2146
            $query->expr()->gt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2147
        );
2148
    }
2149
2150
    /**
2151
     * @param \Doctrine\ORM\QueryBuilder $query
2152
     * @param array $subquery
2153
     * @param array $params
2154
     * @param string $alias
2155
     * @return mixed
2156
     */
2157 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...
2158
    {
2159
        return $query->expr()->andX(
2160
            $query->expr()->exists($this->consumeSubquery($subquery)),
2161
            $query->expr()->gte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2162
        );
2163
    }
2164
2165
    /**
2166
     * @param \Doctrine\ORM\QueryBuilder $query
2167
     * @param array $subquery
2168
     * @param array $params
2169
     * @param string $alias
2170
     * @return mixed
2171
     */
2172 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...
2173
    {
2174
        return $query->expr()->andX(
2175
            $query->expr()->exists($this->consumeSubquery($subquery)),
2176
            $query->expr()->lte('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2177
        );
2178
    }
2179
2180
    /**
2181
     * @param \Doctrine\ORM\QueryBuilder $query
2182
     * @param array $subquery
2183
     * @param array $params
2184
     * @param string $alias
2185
     * @return mixed
2186
     */
2187 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...
2188
    {
2189
        return $query->expr()->andX(
2190
            $query->expr()->exists($this->consumeSubquery($subquery)),
2191
            $query->expr()->lt('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2192
        );
2193
    }
2194
2195
    /**
2196
     * @param \Doctrine\ORM\QueryBuilder $query
2197
     * @param array $subquery
2198
     * @param array $params
2199
     * @param string $alias
2200
     * @return mixed
2201
     */
2202 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...
2203
    {
2204
        return $query->expr()->andX(
2205
            $query->expr()->exists($this->consumeSubquery($subquery)),
2206
            $query->expr()->eq('(' . $this->consumeSubquery($subquery) . ')', $params[0])
2207
        );
2208
    }
2209
2210
    /**
2211
     * @param \Doctrine\ORM\QueryBuilder $query
2212
     * @param string $field
2213
     * @param array $params
2214
     * @param string $alias
2215
     * @return mixed
2216
     */
2217
    private function dateIsExpression($query, $field, $params, $alias)
2218
    {
2219
        return $query->expr()->in($field, $params);
2220
    }
2221
2222
    /**
2223
     * @param \Doctrine\ORM\QueryBuilder $query
2224
     * @param string $field
2225
     * @param array $params
2226
     * @param string $alias
2227
     * @return mixed
2228
     */
2229
    private function integerIsExpression($query, $field, $params, $alias)
2230
    {
2231
        return $query->expr()->in($field, $params);
2232
    }
2233
2234
    /**
2235
     * @param \Doctrine\ORM\QueryBuilder $query
2236
     * @param string $field
2237
     * @param array $params
2238
     * @param string $alias
2239
     * @return mixed
2240
     */
2241
    private function stringIsExpression($query, $field, $params, $alias)
2242
    {
2243
        return $query->expr()->in($field, $params);
2244
    }
2245
2246
    /**
2247
     * @param \Doctrine\ORM\QueryBuilder $query
2248
     * @param string $field
2249
     * @param array $params
2250
     * @param string $alias
2251
     * @param \UnserAller_Model_User $currentUser
2252
     * @return mixed
2253
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2254
     */
2255
    private function integerMeExpression($query, $field, $params, $alias, $currentUser)
2256
    {
2257
        if (!$currentUser) {
2258
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
2259
        }
2260
        return $query->expr()->eq($field, $currentUser->getId());
2261
    }
2262
2263
    /**
2264
     * @param \Doctrine\ORM\QueryBuilder $query
2265
     * @param string $field
2266
     * @param array $params
2267
     * @param string $alias
2268
     * @return \Doctrine\ORM\Query\Expr\Comparison
2269
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2270
     */
2271
    private function integerAnyExpression($query, $field, $params, $alias)
2272
    {
2273
        return $query->expr()->eq($field, $query->expr()->any($this->consumeSubquery($params)));
2274
    }
2275
2276
    /**
2277
     * @param \Doctrine\ORM\QueryBuilder $query
2278
     * @param string $field
2279
     * @param array $params
2280
     * @param string $alias
2281
     * @param \UnserAller_Model_User $currentUser
2282
     * @return \Doctrine\ORM\Query\Expr\Comparison
2283
     * @throws \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated
2284
     */
2285
    private function integerNotmeExpression($query, $field, $params, $alias, $currentUser)
2286
    {
2287
        if (!$currentUser) {
2288
            throw new \UnserAllerLib_Api_V4_Exception_UserRequiredButNotAuthenticated();
2289
        }
2290
        return $query->expr()->neq($field, $currentUser->getId());
2291
    }
2292
2293
    /**
2294
     * @param \Doctrine\ORM\QueryBuilder $query
2295
     * @param string $field
2296
     * @param array $params
2297
     * @param string $alias
2298
     * @return \Doctrine\ORM\Query\Expr\Func
2299
     */
2300
    private function integerNotExpression($query, $field, $params, $alias)
2301
    {
2302
        return $query->expr()->notIn($field, $params);
2303
    }
2304
2305
    /**
2306
     * @param \Doctrine\ORM\QueryBuilder $query
2307
     * @param string $field
2308
     * @param array $params
2309
     * @param string $alias
2310
     * @return \Doctrine\ORM\Query\Expr\Func
2311
     */
2312
    private function dateNotExpression($query, $field, $params, $alias)
2313
    {
2314
        return $query->expr()->notIn($field, $params);
2315
    }
2316
2317
    /**
2318
     * @param \Doctrine\ORM\QueryBuilder $query
2319
     * @param string $field
2320
     * @param array $params
2321
     * @param string $alias
2322
     * @return mixed
2323
     */
2324
    private function stringNotExpression($query, $field, $params, $alias)
2325
    {
2326
        return $query->expr()->notIn($field, $params);
2327
    }
2328
2329
    /**
2330
     * @param \Doctrine\ORM\QueryBuilder $query
2331
     * @param string $field
2332
     * @param array $params
2333
     * @param string $alias
2334
     * @return \Doctrine\ORM\Query\Expr\Comparison
2335
     */
2336
    private function integerFalseExpression($query, $field, $params, $alias)
2337
    {
2338
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2339
    }
2340
2341
    /**
2342
     * @param \Doctrine\ORM\QueryBuilder $query
2343
     * @param string $field
2344
     * @param array $params
2345
     * @param string $alias
2346
     * @return \Doctrine\ORM\Query\Expr\Comparison
2347
     */
2348
    private function dateFalseExpression($query, $field, $params, $alias)
2349
    {
2350
        return $query->expr()->eq('COALESCE(' . $field . ',0)', 0);
2351
    }
2352
2353
    /**
2354
     * @param \Doctrine\ORM\QueryBuilder $query
2355
     * @param string $field
2356
     * @param array $params
2357
     * @param string $alias
2358
     * @return \Doctrine\ORM\Query\Expr\Base
2359
     */
2360
    private function stringFalseExpression($query, $field, $params, $alias)
2361
    {
2362
        return $query->expr()->orX(
2363
            $query->expr()->isNull($field),
2364
            $query->expr()->eq($field, "''")
2365
        );
2366
    }
2367
2368
    /**
2369
     * @param \Doctrine\ORM\QueryBuilder $query
2370
     * @param string $field
2371
     * @param array $params
2372
     * @param string $alias
2373
     * @return \Doctrine\ORM\Query\Expr\Comparison
2374
     */
2375
    private function integerTrueExpression($query, $field, $params, $alias)
2376
    {
2377
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2378
    }
2379
2380
    /**
2381
     * @param \Doctrine\ORM\QueryBuilder $query
2382
     * @param string $field
2383
     * @param array $params
2384
     * @param string $alias
2385
     * @return \Doctrine\ORM\Query\Expr\Comparison
2386
     */
2387
    private function dateTrueExpression($query, $field, $params, $alias)
2388
    {
2389
        return $query->expr()->neq('COALESCE(' . $field . ',0)', 0);
2390
    }
2391
2392
    /**
2393
     * @param \Doctrine\ORM\QueryBuilder $query
2394
     * @param string $field
2395
     * @param array $params
2396
     * @param string $alias
2397
     * @return \Doctrine\ORM\Query\Expr\Base
2398
     */
2399
    private function stringTrueExpression($query, $field, $params, $alias)
2400
    {
2401
        return $query->expr()->andX(
2402
            $query->expr()->isNotNull($field),
2403
            $query->expr()->neq($field, "''")
2404
        );
2405
    }
2406
2407
    /**
2408
     * @param \Doctrine\ORM\QueryBuilder $query
2409
     * @param string $field
2410
     * @param array $params
2411
     * @param string $alias
2412
     * @return mixed
2413
     */
2414 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...
2415
    {
2416
        $contains = $query->expr()->orX();
2417
2418
        $index = 0;
2419
        foreach ($params as $string) {
2420
            $contains->add($query->expr()->like($field, ":contains_{$alias}_{$index}"));
2421
            $query->setParameter("contains_{$alias}_{$index}", '%' . $string . '%');
2422
            $index++;
2423
        }
2424
2425
        return $contains;
2426
    }
2427
2428
    /**
2429
     * @param \Doctrine\ORM\QueryBuilder $query
2430
     * @param string $field
2431
     * @param array $params
2432
     * @param string $alias
2433
     * @return mixed
2434
     */
2435
    private function stringContainExpression($query, $field, $params, $alias)
2436
    {
2437
        return $this->stringContainsExpression($query, $field, $params, $alias);
2438
    }
2439
2440
    /**
2441
     * @param \Doctrine\ORM\QueryBuilder $query
2442
     * @param string $field
2443
     * @param array $params
2444
     * @param string $alias
2445
     * @return mixed
2446
     */
2447 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...
2448
    {
2449
        $lt = $query->expr()->orX();
2450
        $index = 0;
2451
        foreach ($params as $datetime) {
2452
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2453
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2454
            $index++;
2455
        }
2456
2457
        return $lt;
2458
    }
2459
2460
    /**
2461
     * @param \Doctrine\ORM\QueryBuilder $query
2462
     * @param string $field
2463
     * @param array $params
2464
     * @param string $alias
2465
     * @return mixed
2466
     */
2467 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...
2468
    {
2469
        $lt = $query->expr()->orX();
2470
        $index = 0;
2471
        foreach ($params as $datetime) {
2472
            $lt->add($query->expr()->lt($field, ":lt_{$alias}_{$index}"));
2473
            $query->setParameter("lt_{$alias}_{$index}", $datetime);
2474
            $index++;
2475
        }
2476
2477
        return $lt;
2478
    }
2479
2480
    /**
2481
     * @param \Doctrine\ORM\QueryBuilder $query
2482
     * @param string $field
2483
     * @param array $params
2484
     * @param string $alias
2485
     * @return mixed
2486
     */
2487 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...
2488
    {
2489
        $lte = $query->expr()->orX();
2490
        $index = 0;
2491
        foreach ($params as $datetime) {
2492
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2493
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2494
            $index++;
2495
        }
2496
2497
        return $lte;
2498
    }
2499
2500
    /**
2501
     * @param \Doctrine\ORM\QueryBuilder $query
2502
     * @param string $field
2503
     * @param array $params
2504
     * @param string $alias
2505
     * @return mixed
2506
     */
2507 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...
2508
    {
2509
        $lte = $query->expr()->orX();
2510
        $index = 0;
2511
        foreach ($params as $datetime) {
2512
            $lte->add($query->expr()->lte($field, ":lte_{$alias}_{$index}"));
2513
            $query->setParameter("lte_{$alias}_{$index}", $datetime);
2514
            $index++;
2515
        }
2516
2517
        return $lte;
2518
    }
2519
2520
    /**
2521
     * @param \Doctrine\ORM\QueryBuilder $query
2522
     * @param string $field
2523
     * @param array $params
2524
     * @param string $alias
2525
     * @return mixed
2526
     */
2527 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...
2528
    {
2529
        $gt = $query->expr()->orX();
2530
        $index = 0;
2531
        foreach ($params as $datetime) {
2532
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2533
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2534
            $index++;
2535
        }
2536
2537
        return $gt;
2538
    }
2539
2540
    /**
2541
     * @param \Doctrine\ORM\QueryBuilder $query
2542
     * @param string $field
2543
     * @param array $params
2544
     * @param string $alias
2545
     * @return mixed
2546
     */
2547 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...
2548
    {
2549
        $gt = $query->expr()->orX();
2550
        $index = 0;
2551
        foreach ($params as $datetime) {
2552
            $gt->add($query->expr()->gt($field, ":gt_{$alias}_{$index}"));
2553
            $query->setParameter("gt_{$alias}_{$index}", $datetime);
2554
            $index++;
2555
        }
2556
2557
        return $gt;
2558
    }
2559
2560
    /**
2561
     * @return string
2562
     */
2563
    protected function getModelForMeta()
2564
    {
2565
        return uniqid('UnknownClass');
2566
    }
2567
2568
    /**
2569
     * @return string
2570
     */
2571
    public function getClassnameForRepresentedModel()
2572
    {
2573
        return $this->getModelForMeta();
2574
    }
2575
2576
    /**
2577
     * @param \Doctrine\ORM\QueryBuilder $query
2578
     * @param string $field
2579
     * @param array $params
2580
     * @param string $alias
2581
     * @return mixed
2582
     */
2583 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...
2584
    {
2585
        $gte = $query->expr()->orX();
2586
        $index = 0;
2587
        foreach ($params as $datetime) {
2588
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2589
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2590
            $index++;
2591
        }
2592
2593
        return $gte;
2594
    }
2595
2596
    /**
2597
     * @param \Doctrine\ORM\QueryBuilder $query
2598
     * @param string $field
2599
     * @param array $params
2600
     * @param string $alias
2601
     * @return mixed
2602
     */
2603 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...
2604
    {
2605
        $gte = $query->expr()->orX();
2606
        $index = 0;
2607
        foreach ($params as $datetime) {
2608
            $gte->add($query->expr()->gte($field, ":gte_{$alias}_{$index}"));
2609
            $query->setParameter("gte_{$alias}_{$index}", $datetime);
2610
            $index++;
2611
        }
2612
2613
        return $gte;
2614
    }
2615
2616
    /**
2617
     * Does some crazy things
2618
     *
2619
     * @param string $value
2620
     * @return array
2621
     */
2622
    private function filterJsonAfterwards($value)
2623
    {
2624
        return json_decode($value, true);
2625
    }
2626
2627
    /**
2628
     * Does some crazy things
2629
     *
2630
     * @param string $value
2631
     * @return mixed
2632
     */
2633
    private function filterJsonIfNullSetEmptyObjectAfterwards($value)
2634
    {
2635
        return $value === null ? new \stdClass() : json_decode($value, true);
2636
    }
2637
2638
    /**
2639
     * Does some crazy things
2640
     *
2641
     * @param string $value
2642
     * @return string
2643
     */
2644
    private function filterNl2BrAfterwards($value)
2645
    {
2646
        return nl2br($value, false);
2647
    }
2648
2649
    /**
2650
     * Does some crazy things
2651
     *
2652
     * @param string $value
2653
     * @return array
2654
     */
2655
    private function filterJsonOrNullAfterwards($value)
2656
    {
2657
        return $value === null ? null : json_decode($value, true);
2658
    }
2659
2660
    /**
2661
     * Too complex to explain
2662
     *
2663
     * @param string $value
2664
     * @return \DateTime
2665
     */
2666
    private function filterDatetimeAfterwards($value)
2667
    {
2668
        return new \DateTime($value);
2669
    }
2670
2671
    /**
2672
     * Too complex to explain
2673
     *
2674
     * @param string $value
2675
     * @return \DateTime
2676
     */
2677
    private function filterDatetimeOrNullAfterwards($value)
2678
    {
2679
        return $value === null ? null : new \DateTime($value);
2680
    }
2681
2682
    /**
2683
     * Too complex to explain
2684
     *
2685
     * @param string|null $value
2686
     * @return int|null
2687
     */
2688
    private function filterIntOrNullAfterwards($value)
2689
    {
2690
        return $value === null ? null : (int)$value;
2691
    }
2692
2693
    /**
2694
     * Returns the current resultArrayFixSchedule. Afterwards the schedule will be empty again.
2695
     *
2696
     * @return array
2697
     */
2698
    private function flushResultArrayFixSchedule()
2699
    {
2700
        $scheduledFixes = $this->resultArrayFixSchedule;
2701
        $this->resultArrayFixSchedule = [];
2702
        return $scheduledFixes;
2703
    }
2704
2705
    /**
2706
     * Returns true if $alias was used in $query already - false otherwise
2707
     *
2708
     * @param \Doctrine\ORM\QueryBuilder $query
2709
     * @param string $alias
2710
     * @return bool
2711
     */
2712
    protected function wasAliasUsed($query, $alias)
2713
    {
2714
        return in_array($alias, $query->getAllAliases());
2715
    }
2716
2717
    /**
2718
     * Returns true if $alias was used in $query already - false otherwise
2719
     *
2720
     * @param \Doctrine\ORM\QueryBuilder $query
2721
     * @param string $alias
2722
     * @return bool
2723
     */
2724
    protected function wasntAliasUsed($query, $alias)
2725
    {
2726
        return !$this->wasAliasUsed($query, $alias);
2727
    }
2728
2729
    /**
2730
     * @return array
2731
     */
2732
    public function getUnsortedParams()
2733
    {
2734
        return $this->unsortedParams;
2735
    }
2736
2737
    /**
2738
     * @param array $unsortedParams
2739
     * @return $this
2740
     */
2741
    public function setUnsortedParams($unsortedParams)
2742
    {
2743
        $this->unsortedParams = $unsortedParams;
2744
2745
        return $this;
2746
    }
2747
2748
    /**
2749
     * @param \Doctrine\ORM\QueryBuilder $query
2750
     * @param string $translationName
2751
     * @param string $language
2752
     * @return string alias of joined translation table
2753
     */
2754
    protected function joinTranslationOnce($query, $translationName, $language)
2755
    {
2756
        $alias = 'translation' . $translationName . $language;
2757
2758
        if ($this->wasAliasUsed($query, $alias)) {
2759
            return $alias;
2760
        }
2761
2762
        $rootAlias = $this->getRootAlias($query);
2763
2764
        $query->setParameter("name$alias", $translationName);
2765
        $query->setParameter("target$alias", $language);
2766
2767
        $query->leftJoin(
2768
            'UnserAller_Model_Translation',
2769
            $alias,
2770
            'WITH',
2771
            "$alias.name = CONCAT(:name$alias,$rootAlias.id) AND $alias.target = :target$alias"
2772
        );
2773
2774
        return $alias;
2775
    }
2776
2777
    /**
2778
     * @param \Doctrine\ORM\QueryBuilder $query
2779
     * @param string $alias
2780
     * @param string $col
2781
     * @param string $name
2782
     * @param string $translationName
2783
     * @param string $language
2784
     * @return array|void
2785
     */
2786
    protected function abstractIncludeMultilanguageStringColumn(
2787
        $query,
2788
        $alias,
2789
        $col,
2790
        $name,
2791
        $translationName,
2792
        $language
2793
    ) {
2794
        if (!$language) {
2795
            $query->addSelect("($col) $alias");
2796
        } else {
2797
            $query->addSelect("(COALESCE(" . $this->joinTranslationOnce(
2798
                    $query,
2799
                    $translationName,
2800
                    $language
2801
                ) . ".translation,$col)) $alias");
2802
        }
2803
2804
        return [
2805
            $alias,
2806
            'move' => $name
2807
        ];
2808
    }
2809
2810
    protected function getAdditionalUserParamOrFail(&$additionalParams)
2811
    {
2812
        if (!isset($additionalParams['user'][0])) {
2813
            throw new \InvalidArgumentException('User identifier required but not given');
2814
        }
2815
2816
        $param = $additionalParams['user'];
2817
        unset($additionalParams['user']);
2818
        return \UnserAllerLib_Validate_Helper::integerOrFail($param[0], 1);
2819
    }
2820
}
2821