Completed
Push — master ( 9ac026...6b52f2 )
by
unknown
46:08
created

Vortex::createSingleResult()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 0
cts 28
cp 0
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 23
nc 3
nop 6
crap 12
1
<?php
2
3
namespace Macroparts\Vortex;
4
5
use Doctrine\ORM\Query;
6
7
abstract class Vortex
8
{
9
    use \UnserAllerLib_Api_V4_AdapterProvider;
10
11
    const DIRECTIVE_FIELDS = 'fields';
12
    const META_ON_OBJECT_LEVEL_ENABLED = 1;
13
    const META_ON_OBJECT_LEVEL_DISABLED = 0;
14
15
    /**
16
     * @var \Doctrine\ORM\EntityManager
17
     */
18
    private $entityManager;
19
20
    /**
21
     * Defines data that can be included with an api call. You'd try to return only the most frequently used data
22
     * in default payload. Other available raw and aggregated data you'd make includable. Every availableInclude
23
     * has it's own include method and has to be mentioned in this configuration.
24
     *
25
     * Go to the Project Repository for an example configuration
26
     *
27
     * @var array
28
     */
29
    protected static $includeWhitelist = [];
30
    protected static $filterWhitelist = [];
31
    protected static $orderWhitelist = [];
32
33
    private $abstractFilterWhitelist = [
34
        'id' => '',
35
        'any' => '',
36
        'all' => ''
37
    ];
38
39
    /**
40
     * Used in the include configuration to tell if a property is a recursive inclusion or not
41
     */
42
    const INCLUDE_DIRECT = 0;
43
    const INCLUDE_RECURSIVE = 1;
44
45
    /**
46
     * Collects operations that have to be run on the doctrine result after query execution
47
     *
48
     * @var array
49
     */
50
    private $resultArrayFixSchedule = [];
51
52
    /**
53
     * Repositories can define a set of properties that are included by default
54
     *
55
     * @var array
56
     */
57
    protected static $defaultIncludes = [];
58
59
    /**
60
     * Repositories can define a default order which is taken by default
61
     *
62
     * @var array
63
     */
64
    protected static $defaultOrder = [];
65
66
    /**
67
     * If an ID is specified in the URL, it will be saved here for usage in child adapters.
68
     *
69
     * @var int|null
70
     */
71
    protected $id = null;
72
73
    /**
74
     * All parameters unsorted
75
     *
76
     * @var array
77
     */
78
    protected $unsortedParams = [];
79
80
    protected $supportedLanguages;
81
82
83
    private $finalIncludeWhitelist;
84
    private $finalFilterWhitelist;
85
    private $finalOrderWhitelist;
86
87
    public function __construct($entityManager, $supportedLanguages = [])
88
    {
89
        $this->entityManager = $entityManager;
90
        $this->supportedLanguages = $supportedLanguages;
91
92
        //Cache customized whitelists merged with core whitelists
93
        $this->finalFilterWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
94
            static::class,
95
            'filterWhitelist'
96
        );
97
        $this->finalOrderWhitelist = $this->getStaticPropertyOfClassMergedWithParents(static::class, 'orderWhitelist');
98
        $this->finalIncludeWhitelist = $this->getStaticPropertyOfClassMergedWithParents(
99
            static::class,
100
            'includeWhitelist'
101
        );
102
    }
103
104
    /**
105
     * @return \Doctrine\ORM\EntityManager
106
     */
107
    public function getEntityManager()
108
    {
109
        return $this->entityManager;
110
    }
111
112
    /**
113
     * @param \Doctrine\ORM\EntityManager $entityManager
114
     * @return $this
115
     */
116
    public function setEntityManager($entityManager)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
117
    {
118
        $this->entityManager = $entityManager;
119
        return $this;
120
    }
121
122
    private function isNotIncludableProperty($property)
123
    {
124
        return !isset($this->finalIncludeWhitelist[$property]);
125
    }
126
127
    private function isNotFilterableProperty($property)
128
    {
129
        return !isset($this->finalFilterWhitelist[$property]) && !isset($this->abstractFilterWhitelist[$property]);
130
    }
131
132
    private function isNotOrderableProperty($property)
133
    {
134
        return !isset($this->finalOrderWhitelist[$property]);
135
    }
136
137
    public function getTranslatableIncludeNames()
138
    {
139
        return array_keys(array_filter($this->finalIncludeWhitelist, function ($inc) {
140
            return isset($inc['translatable']) && $inc['translatable'];
141
        }));
142
    }
143
144
    /**
145
     * @return array
146
     */
147
    private function getPlatformOptions()
148
    {
149
        return \Zend_Registry::get('platformOptions');
150
    }
151
152
    /**
153
     * @return bool
154
     */
155
    protected function arePlatformUsersPublic()
156
    {
157
        return (bool)$this->getPlatformOptions()['user']['public'];
158
    }
159
160
    /**
161
     * @return bool
162
     */
163
    protected function arePlatformUsersPrivate()
164
    {
165
        return !$this->arePlatformUsersPublic();
166
    }
167
168
    /**
169
     * @param \Doctrine\ORM\QueryBuilder $query
170
     * @param string $alias
171
     * @param \UnserAller_Model_User $currentUser
172
     * @param array $additionalParams
173
     * @param $language
174
     * @param $filtername
175
     * @return \Doctrine\ORM\Query\Expr\Orx
176
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
177
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
178
     */
179
    private function filterAny($query, $alias, $currentUser, $additionalParams, $language, $filtername)
180
    {
181
        return $this->abstractFilterMultipleFields(
182
            $query,
183
            'orX',
184
            $currentUser,
185
            $additionalParams,
186
            $language,
187
            $filtername
188
        );
189
    }
190
191
    /**
192
     * @param \Doctrine\ORM\QueryBuilder $query
193
     * @param string $alias
194
     * @param \UnserAller_Model_User $currentUser
195
     * @param array $additionalParams
196
     * @param $language
197
     * @param $filtername
198
     * @return \Doctrine\ORM\Query\Expr\Orx
199
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
200
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
201
     */
202
    private function filterAll($query, $alias, $currentUser, $additionalParams, $language, $filtername)
203
    {
204
        return $this->abstractFilterMultipleFields(
205
            $query,
206
            'andX',
207
            $currentUser,
208
            $additionalParams,
209
            $language,
210
            $filtername
211
        );
212
    }
213
214
    /**
215
     * @param \Doctrine\ORM\QueryBuilder $query
216
     * @param string $expressionType
217
     * @param \UnserAller_Model_User $currentUser
218
     * @param array $additionalParams
219
     * @param $language
220
     * @param $filtername
221
     * @return \Doctrine\ORM\Query\Expr\Orx
222
     * @throws \UnserAllerLib_Api_V4_Exception_MissingFilterDirective
223
     * @throws \UnserAllerLib_Api_V4_Exception_SafeForPrinting
224
     */
225
    private function abstractFilterMultipleFields(
226
        $query,
227
        $expressionType,
228
        $currentUser,
229
        $additionalParams,
230
        $language,
231
        $filtername
232
    ) {
233
        if (!isset($additionalParams[self::DIRECTIVE_FIELDS])) {
234
            throw new \UnserAllerLib_Api_V4_Exception_MissingFilterDirective(
235
                $filtername,
236
                self::DIRECTIVE_FIELDS,
237
                ['fieldname1', 'fieldname2'],
238
                ':someFilterDirective(params):maySomeMoreDirectives...'
239
            );
240
        }
241
242
        $fields = $additionalParams[self::DIRECTIVE_FIELDS];
243
        if (count(array_intersect_key($this->finalFilterWhitelist, array_flip($fields))) !== count($fields)) {
244
            throw new \UnserAllerLib_Api_V4_Exception_SafeForPrinting(
245
                'Wrong use of "' . $filtername . '" filter. '.
246
                'One of your specified fields is not filterable. '.
247
                'Try using fields that are filterable.'
248
            );
249
        }
250
251
        unset($additionalParams[self::DIRECTIVE_FIELDS]);
252
253
        $expression = call_user_func([$query->expr(), $expressionType]);
254
        foreach ($fields as $field) {
255
            $filterMethod = $this->decodeMethodFromRequestedFilter($field);
256
            $expression->add($this->$filterMethod(
257
                $query, $filterMethod, $currentUser, $additionalParams, $language, $field,
258
                $this->finalFilterWhitelist[$field]
259
            ));
260
        }
261
262
        return $expression;
263
    }
264
265
    /**
266
     * Executes include methods driven by a include string. See API docs to know how this string looks like
267
     *
268
     * @param \Doctrine\ORM\QueryBuilder $query
269
     * @param \UnserAller_Model_User $currentUser
270
     * @param $language
271
     * @param string $includeString
272
     * @param array $meta
273
     * @return \Doctrine\ORM\QueryBuilder
274
     */
275
    protected function addIncludeStatements($query, $currentUser, $language, $includeString, &$meta = [])
276
    {
277
        $requestedIncludes = $this->parseIncludeString($includeString, $this->finalIncludeWhitelist);
278
279
        $requestedIncludes = $requestedIncludes + static::$defaultIncludes;
280
        foreach ($requestedIncludes as $requestedInclude => $additionalParams) {
281
            if ($this->isNotIncludableProperty($requestedInclude)) {
282
                continue;
283
            }
284
285
            $includeMethod = $this->decodeMethodFromRequestedInclude($requestedInclude);
286
            $postProcessDirections = $this->$includeMethod($query, $includeMethod, $currentUser, $additionalParams,
287
                $language);
288
289
            if ($postProcessDirections) {
290
                $this->schedulePostProcessingDirections($postProcessDirections);
291
            }
292
293
            $this->updateMetaOnInclude($meta, $requestedInclude);
294
        }
295
        return $query;
296
    }
297
298
    /**
299
     * Collecting whitelist not just for current class but merged with all whitelists from parent classes.
300
     * So when we overwrite whitelists locally they are still including all the elements from core adapters.
301
     *
302
     * @param null|string $class
303
     * @param $propertyname
304
     * @return array
305
     */
306
    private function getStaticPropertyOfClassMergedWithParents($class, $propertyname)
307
    {
308
        $class = $class ? $class : static::class;
309
        $parent = get_parent_class($class);
310
        return $parent ? $this->getStaticPropertyOfClassOrArray(
311
                $class,
312
                $propertyname
313
            ) + $this->getStaticPropertyOfClassMergedWithParents(
314
                $parent,
315
                $propertyname
316
            ) : $this->getStaticPropertyOfClassOrArray($class, $propertyname);
317
    }
318
319
    private function getStaticPropertyOfClassOrArray($class, $propertyname)
320
    {
321
        return isset($class::$$propertyname) ? $class::$$propertyname : [];
322
    }
323
324
    private function updateMetaOnInclude(&$meta, $includeName)
325
    {
326
        $include = $this->finalIncludeWhitelist[$includeName];
327
        if (isset($include['model'])) {
328
            $meta['modelnameIndex']["{$include['model']}"][] = $includeName;
329
        }
330
    }
331
332
    /**
333
     * Calls methods that add where conditions to a query driven by a string (see api docs for string format)
334
     *
335
     * @param \Doctrine\ORM\QueryBuilder $query
336
     * @param \UnserAller_Model_User $currentUser
337
     * @param string $filterString
338
     * @param $language
339
     * @param string $joinFiltersWith
340
     * @return \Doctrine\ORM\QueryBuilder
341
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidFilter
342
     * @uses filterAny
343
     * @uses filterAll
344
     */
345
    protected function addFilterStatements($query, $currentUser, $filterString, $language, $joinFiltersWith = 'AND')
346
    {
347
        $requestedFilters = array_filter($this->parseRichParamString($filterString));
348
        if (!$requestedFilters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $requestedFilters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
349
            return $query;
350
        }
351
352
        $expression = mb_strtoupper($joinFiltersWith) === 'OR' ? $query->expr()->orX() : $query->expr()->andX();
353
        foreach ($requestedFilters as $requestedFilter => $additionalParams) {
354
            if ($this->isNotFilterableProperty($requestedFilter)) {
355
                throw new \UnserAllerLib_Api_V4_Exception_InvalidFilter($requestedFilter);
356
            }
357
358
            $filterMethod = $this->decodeMethodFromRequestedFilter($requestedFilter);
359
            $expression->add($this->$filterMethod(
360
                $query, $filterMethod, $currentUser, $additionalParams, $language, $requestedFilter,
361
                $this->finalFilterWhitelist[$requestedFilter]
362
            ));
363
        }
364
365
        return $query->andWhere($expression);
366
    }
367
368
    abstract protected function getFallbackLanguage($resultItem, $requestedLanguage);
369
370
    /**
371
     * Transforms additionalParams for included collections into params which are used
372
     * during post processing to call a findXForApi method
373
     *
374
     * @param array $additionalParams
375
     * @return array
376
     */
377
    protected function parseAdditionalIncludeParams($additionalParams)
378
    {
379
        $filter = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filter');
380
        $filter = is_array($filter) ? implode(',', $filter) : '';
381
382
        $include = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'include');
383
        $include = is_array($include) ? implode(',', $include) : '';
384
385
        $order = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'order');
386
        $order = is_array($order) ? implode(',', $order) : '';
387
388
        $limit = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'limit');
389
        $limit = is_array($limit) ? (int)array_shift($limit) : 0;
390
391
        $page = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'page');
392
        $page = is_array($page) ? (int)array_shift($page) : 1;
393
394
        $filterMode = \UnserAllerLib_Tool_Array::spliceElemOrNull($additionalParams, 'filterMode');
395
        $filterMode = is_array($filterMode) ? array_shift($filterMode) : 'AND';
396
397
        return [$filter, $include, $order, $limit, $page, $filterMode];
398
    }
399
400
    /**
401
     * Calls methods that add orderBy statements to a query driven by a string (see api docs for the string format)
402
     *
403
     * @param \Doctrine\ORM\QueryBuilder $query
404
     * @param \UnserAller_Model_User $currentUser
405
     * @param string $orderString
406
     * @return \Doctrine\ORM\QueryBuilder
407
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidOrder
408
     */
409
    private function addOrderStatements($query, $currentUser, $orderString)
410
    {
411
        $requestedOrders = array_filter($this->parseRichParamString($orderString));
412
413
        if (!$requestedOrders) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $requestedOrders of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
414
            $requestedOrders = static::$defaultOrder;
415
        }
416
417
        foreach ($requestedOrders as $field => $order) {
418
            if ($this->isNotOrderableProperty($field)) {
419
                throw new \UnserAllerLib_Api_V4_Exception_InvalidOrder($field);
420
            }
421
422
            $orderMethod = $this->decodeMethodFromRequestedOrder($field);
423
            $postProcessTasks = $this->$orderMethod($query, $orderMethod, $currentUser,
424
                isset($order['desc']) ? 'DESC' : 'ASC', $order);
425
            if ($postProcessTasks) {
426
                $this->schedulePostProcessingDirections($postProcessTasks);
427
            }
428
        }
429
430
        return $query;
431
    }
432
433
    /**
434
     * Knows how to append post processing directions to the post process schedule
435
     *
436
     * @param array $tasks
437
     */
438
    private function schedulePostProcessingDirections($tasks)
439
    {
440
        if (!$tasks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tasks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
441
            return;
442
        }
443
444
        if (!is_array($tasks[0])) {
445
            $tasks = [$tasks];
446
        }
447
448
        foreach ($tasks as $task) {
449
            $this->resultArrayFixSchedule[array_shift($task)] = $task;
450
        }
451
    }
452
453
    /**
454
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
455
     * uppercase first letter and every letter that comes after a dot, remove dots and prepend 'include'. Examples:
456
     *
457
     * returns includeProject when $requestedInclude = project
458
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
459
     *
460
     * @param string $requestedInclude
461
     * @return string
462
     */
463
    private function decodeMethodFromRequestedInclude($requestedInclude)
464
    {
465
        return 'include' . implode('', array_map('ucfirst', explode('.', str_replace('[]', '_cp', $requestedInclude))));
466
    }
467
468
    /**
469
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
470
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
471
     *
472
     * returns includeProject when $requestedInclude = project
473
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
474
     *
475
     * @param string $requestedInclude
476
     * @return string
477
     */
478
    private function decodeMethodFromRequestedFilter($requestedInclude)
479
    {
480
        return 'filter' . implode('', array_map('ucfirst', explode('.', $requestedInclude)));
481
    }
482
483
    /**
484
     * Returns the name of the appropriate include method for $requestedInclude. The rule is simple:
485
     * uppercase first letter and every letter that comes after a dot, remove dots, prepend 'include'. Examples:
486
     *
487
     * returns includeProject when $requestedInclude = project
488
     * returns includePhaseProjectNumberOfLikes when $requestedInclude = phase.project.numerOfLikes
489
     *
490
     * @param string $field
491
     * @return string
492
     */
493
    private function decodeMethodFromRequestedOrder($field)
494
    {
495
        return 'orderBy' . implode('', array_map('ucfirst', explode('.', $field)));
496
    }
497
498
    /**
499
     * Calculates total pages for an $incompleteStatement. Incomplete statements are doctrine query builder instances
500
     * with all required conditions but no select statement and no additional includes.
501
     *
502
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
503
     * @param int $limit
504
     * @return float|int
505
     */
506
    private function calculateTotalPages($incompleteStatement, $limit)
507
    {
508
        $incompleteStatement = clone $incompleteStatement;
509
510
        if ($limit) {
511
            return (int)ceil($this->executeRowCountStatement($incompleteStatement) / $limit);
512
        }
513
        return 1;
514
    }
515
516
    /**
517
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
518
     * @return int
519
     */
520
    private function executeRowCountStatement($incompleteStatement)
521
    {
522
        $rootAlias = $this->getRootAlias($incompleteStatement);
523
        $primaryIndexCol = $rootAlias . '.' . $this->getPrimaryIndexCol();
524
525
        if ($incompleteStatement->getDQLPart('having')) {
526
            $rootEntities = $incompleteStatement->getRootEntities();
527
            return (int)$incompleteStatement->getEntityManager()->createQueryBuilder()
528
                ->select('COUNT(x)')
529
                ->from(array_shift($rootEntities), 'x')
530
                ->where(
531
                    $incompleteStatement->expr()->in(
532
                        'x.'.$this->getPrimaryIndexCol(),
533
                        $incompleteStatement->select($primaryIndexCol)->getDQL()
534
                    )
535
                )->setParameters($incompleteStatement->getParameters())->getQuery()->getSingleScalarResult();
536
        }
537
538
        return (int)$incompleteStatement
539
            ->select("COUNT(DISTINCT $primaryIndexCol)")
540
            ->getQuery()
541
            ->getSingleScalarResult();
542
    }
543
544
    /**
545
     * Doctrine will throw errors if a table has a multi column primary index
546
     * http://stackoverflow.com/questions/18968963/select-countdistinct-error-on-multiple-columns
547
     * @return string
548
     */
549
    protected function getPrimaryIndexCol()
550
    {
551
        return 'id';
552
    }
553
554
    /**
555
     * Todo: Include collections by additional params and not by includes and adjust docs
556
     * Takes the include string and decodes it to an array with include names as keys and an array with additionalParams
557
     * as the value. Includes that are nested inside included collections are grouped and added as additional params
558
     * to the included collection.
559
     *
560
     * @param $string
561
     * @param $availableIncludes
562
     * @return array
563
     */
564
    private function parseIncludeString($string, $availableIncludes)
565
    {
566
        if ($string === '') {
567
            return [];
568
        }
569
570
        if (is_string($string)) {
571
            $string = explode(',', $string);
572
        }
573
574
        if (!is_array($string)) {
575
            return [];
576
        }
577
578
        $requestedIncludes = [];
579
        $implicitIncludes = [];
580
        foreach ($string as $include) {
581
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
582
583
            $pathToFirstRecursiveInclusion = $this->pathForNestedInclude($includeName, $availableIncludes);
584
            if ($pathToFirstRecursiveInclusion) {
585
                $requestedIncludes[$pathToFirstRecursiveInclusion]['include'][] = substr(
586
                    $include,
587
                    strlen($pathToFirstRecursiveInclusion) + 1
588
                );
589
                continue;
590
            }
591
592
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
593
594
            if ($allModifiersStr === null) {
595
                if (!isset($requestedIncludes[$includeName])) {
596
                    $requestedIncludes[$includeName] = [];
597
                }
598
                continue;
599
            }
600
601
            if (preg_match('~filter\(~u', $allModifiersStr)) {
602
                $modifierArr = $this->parseModifierArraySlowButAccurate($allModifiersStr);
603
            } else {
604
                $modifierArr = $this->parseModifierStringQuickButInaccurate($allModifiersStr);
605
            }
606
607
            if (isset($requestedIncludes[$includeName])) {
608
                $requestedIncludes[$includeName] = $requestedIncludes[$includeName] + $modifierArr;
609
            } else {
610
                $requestedIncludes[$includeName] = $modifierArr;
611
            }
612
        }
613
614
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
615
    }
616
617
    /**
618
     * creates an array out of string in this format:
619
     *
620
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3)
621
     *
622
     * Result:
623
     * [
624
     *  'modifierName1' => ['modifierParam1','modifierParam2'],
625
     *  'modifierName2' => ['modifierParam3']
626
     * ]
627
     *
628
     * But doesn't work when modifier params contain other modifiers with params themselves
629
     *
630
     * @param string $allModifiersStr
631
     * @return array
632
     */
633
    private function parseModifierStringQuickButInaccurate($allModifiersStr)
634
    {
635
        // Matches multiple instances of 'something(foo|bar|baz)' in the string
636
        // I guess it ignores : so you could use anything, but probably don't do that
637
        preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
638
        // [0] is full matched strings...
639
        $modifierCount = count($allModifiersArr[0]);
640
        $modifierArr = [];
641 View Code Duplication
        for ($modifierIt = 0; $modifierIt < $modifierCount; $modifierIt++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
642
            // [1] is the modifier
643
            $modifierName = $allModifiersArr[1][$modifierIt];
644
            // and [3] is delimited params
645
            $modifierParamStr = $allModifiersArr[3][$modifierIt];
646
            // Make modifier array key with an array of params as the value
647
            $modifierArr[$modifierName] = explode('|', $modifierParamStr);
648
        }
649
650
        return $modifierArr;
651
    }
652
653
    /**
654
     * creates an array out of string in this format:
655
     *
656
     * modifierName1(modifierParam1|modifierParam2):modifierName2(modifierParam3))
657
     *
658
     * Can also handle modifier params that contain other modifier with params
659
     *
660
     * @param string $s
661
     * @return array
662
     */
663 View Code Duplication
    private function parseModifierArraySlowButAccurate($s)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

Loading history...
722
    {
723
        $paramArr = [];
724
        $tmpStr = '';
725
726
        $depth = 0;
727
        for ($i = 0; $i <= strlen($s); $i++) {
728
            switch ($s[$i]) {
729
                case '(':
730
                    $tmpStr .= $s[$i];
731
                    $depth++;
732
                    break;
733
734
                case ')':
735
                    $tmpStr .= $s[$i];
736
                    $depth--;
737
                    break;
738
739
                case '|':
740
                    if ($depth) {
741
                        $tmpStr .= $s[$i];
742
                    } else {
743
                        $paramArr[] = $tmpStr;
744
                        $tmpStr = '';
745
                    }
746
                    break;
747
748
                default:
749
                    $tmpStr .= $s[$i];
750
            }
751
        }
752
753
        if (strlen($tmpStr)) {
754
            $paramArr[] = $tmpStr;
755
        }
756
757
        return $paramArr;
758
    }
759
760
    /**
761
     * Checks if includeName is an include nested inside a recursive inclusion.
762
     * If yes, return the path to that item - false otherwise.
763
     *
764
     * Example:
765
     * For projects there can be an include for phases. Phases are included recursively in its own adapter. So you'd
766
     * want when you include phases.steps that the steps inclusion is executed in the phase adapter and not in the
767
     * project adapter. That's why we need to separate includes that need to be passed further here.
768
     *
769
     * "recursiveinclude" results to false
770
     * "normalprop1" results to false
771
     * "recursiveinclude.normalprop1.normalprop2" results to "recursiveinclude"
772
     * "normalprop1.recursiveinclude.normalprop2" results to "normalprop1.recursiveinclude"
773
     *
774
     * @param $includeName
775
     * @param $availableIncludes
776
     * @return bool|string
777
     */
778
    private function pathForNestedInclude($includeName, $availableIncludes)
779
    {
780
        $pathArray = explode('.', $includeName);
781
        if (!isset($pathArray[1])) {
782
            return false;
783
        }
784
785
        $pathArrayLength = count($pathArray);
786
        for ($i = 1; $i < $pathArrayLength; $i++) {
787
            $implicitPath = implode('.', array_slice($pathArray, 0, $i));
788
            if ($this->extractStrategyForInclude($availableIncludes[$implicitPath]) === self::INCLUDE_RECURSIVE) {
789
                return $implicitPath;
790
            }
791
        }
792
793
        return false;
794
    }
795
796
    /**
797
     * Include configuration can either have just the strategy or a configuration array with the strategy inside.
798
     *
799
     * @param mixed $include
800
     * @return integer
801
     */
802
    private function extractStrategyForInclude($include)
803
    {
804
        return is_array($include) ? $include['strategy'] : $include;
805
    }
806
807
    /**
808
     * Validates the include string and returns an array with requiredIncludes
809
     *
810
     * @param string $string
811
     * @return array
812
     */
813
    private function parseRichParamString($string)
814
    {
815
        if ($string === '') {
816
            return [];
817
        }
818
819
        if (is_string($string)) {
820
            $string = explode(',', $string);
821
        }
822
823
        if (!is_array($string)) {
824
            return [];
825
        }
826
827
        $requestedIncludes = [];
828
        $implicitIncludes = [];
829
        foreach ($string as $include) {
830
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
831
            $implicitIncludes = array_merge($implicitIncludes, $this->getImplicitIncludes($includeName));
832
833
            if ($allModifiersStr === null) {
834
                $requestedIncludes[$includeName] = [];
835
                continue;
836
            }
837
838
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
839
            // I guess it ignores : so you could use anything, but probably don't do that
840
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
841
            // [0] is full matched strings...
842
            $modifierCount = count($allModifiersArr[0]);
843
            $modifierArr = [];
844 View Code Duplication
            for ($modifierIt = 0; $modifierIt < $modifierCount; $modifierIt++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
845
                // [1] is the modifier
846
                $modifierName = $allModifiersArr[1][$modifierIt];
847
                // and [3] is delimited params
848
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
849
                // Make modifier array key with an array of params as the value
850
                $modifierArr[$modifierName] = explode('|', $modifierParamStr);
851
            }
852
            $requestedIncludes[$includeName] = $modifierArr;
853
        }
854
855
        return $this->mergeWithImplicitIncludes($requestedIncludes, $implicitIncludes);
856
    }
857
858
    private function mergeWithImplicitIncludes($includes, $implicitIncludes)
859
    {
860
        foreach ($implicitIncludes as $implicitInclude) {
861
            if (isset($includes[$implicitInclude])) {
862
                continue;
863
            }
864
865
            $includes[$implicitInclude] = [];
866
        }
867
868
        return $includes;
869
    }
870
871
    /**
872
     * @param \Doctrine\ORM\QueryBuilder $query
873
     * @param string $alias
874
     * @param \UnserAller_Model_User $currentUser
875
     * @param array $additionalParams
876
     * @return \Doctrine\ORM\Query\Expr\Andx
877
     */
878
    protected function filterId($query, $alias, $currentUser, $additionalParams)
879
    {
880
        $rootAliasArr = $query->getRootAliases();
881
        $rootAlias = array_shift($rootAliasArr);
882
        return $this->createConditionsForEntityColumn("$rootAlias.id", $query, $alias, $currentUser, $additionalParams);
883
    }
884
885
    private function getImplicitIncludes($includeName)
886
    {
887
        $parts = explode('.', $includeName);
888
        $numberOfParts = count($parts);
889
890
        if ($numberOfParts < 2) {
891
            return [];
892
        }
893
894
        $implicitIncludes = [];
895
        for ($i = 1; $i < $numberOfParts; $i++) {
896
            $implicitIncludes[] = implode('.', array_slice($parts, 0, $i));
897
        }
898
899
        return $implicitIncludes;
900
    }
901
902
    /**
903
     * @param \UnserAller_Model_User $currentUser
904
     * @return \Doctrine\ORM\QueryBuilder
905
     */
906
    abstract protected function initIncompleteStatement($currentUser);
907
908
    private function createIncompleteStatement(
909
        $currentUser,
910
        $filterString,
911
        $language,
912
        $joinFiltersWith = 'AND',
913
        &$meta = []
914
    ) {
915
        return $this->addFilterStatements(
916
            $this->initIncompleteStatement($currentUser),
917
            $currentUser,
918
            $filterString,
919
            $language,
920
            $joinFiltersWith
921
        );
922
    }
923
924
    /**
925
     * @param \UnserAller_Model_User $currentUser
926
     * @param string $filterString
927
     * @param $language
928
     * @param string $joinFiltersWith
929
     * @return int
930
     */
931
    public function findTotalNumberOfRows($currentUser, $filterString = '', $language = '', $joinFiltersWith = 'AND')
932
    {
933
        return $this->executeRowCountStatement(
934
            $this->createIncompleteStatement($currentUser, $filterString, $language, $joinFiltersWith)
935
        );
936
    }
937
938
    /**
939
     * @param \UnserAller_Model_User $currentUser
940
     * @param string $language
941
     * @param string $filterString
942
     * @param string $includeString
943
     * @param string $orderString
944
     * @param int $limit
945
     * @param int $page
946
     * @param string $joinFiltersWith
947
     * @param int $metaOnObjectLevelOption
948
     * @return array
949
     */
950
    public function findMultipleForApi(
951
        $currentUser,
952
        $language = '',
953
        $filterString = '',
954
        $includeString = '',
955
        $orderString = '',
956
        $limit = 0,
957
        $page = 1,
958
        $joinFiltersWith = 'AND',
959
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
960
    ) {
961
        if ($page <= 0) {
962
            $page = 1;
963
        }
964
965
        $meta = $this->initMetaArray('', $language);
966
967
        $incompleteStatement = $this->createIncompleteStatement(
968
            $currentUser,
969
            $filterString,
970
            $language,
971
            $joinFiltersWith,
972
            $meta
973
        );
974
975
        $completeStatement = $this->completeStatement(
976
            $currentUser,
977
            $language,
978
            $incompleteStatement,
979
            $includeString,
980
            $orderString,
981
            $meta
982
        );
983
        if ($limit > 0) {
984
            $completeStatement
985
                ->setFirstResult(($page - 1) * $limit)
986
                ->setMaxResults($limit);
987
        }
988
989
        return [
990
            'totalPages' => $this->calculateTotalPages($incompleteStatement, $limit),
991
            'filter' => $filterString,
992
            'include' => $includeString,
993
            'page' => $page,
994
            'pageSize' => $limit,
995
            'data' => $this->applyScheduledFixes(
996
                $this->getRawResult($completeStatement),
997
                $currentUser,
998
                $language,
999
                $meta,
1000
                $metaOnObjectLevelOption
1001
            ),
1002
            'meta' => $meta
1003
        ];
1004
    }
1005
1006
    private function initMetaArray($modelPathOffset = '', $language = '')
1007
    {
1008
        return [
1009
            'modelnameIndex' => [
1010
                $this->getModelForMeta() => [$modelPathOffset]
1011
            ],
1012
            'language' => $language
1013
        ];
1014
    }
1015
1016
    /**
1017
     * @param string $language
1018
     * @param string $filterString
1019
     * @param string $includeString
1020
     * @param string $orderString
1021
     * @param int $limit
1022
     * @param int $page
1023
     * @param string $filterMode
1024
     * @return array
1025
     */
1026
    public function findMultiple(
1027
        $language = '',
1028
        $filterString = '',
1029
        $includeString = '',
1030
        $orderString = '',
1031
        $limit = 0,
1032
        $page = 1,
1033
        $filterMode = 'AND'
1034
    ) {
1035
        return json_decode(json_encode($this->findMultipleForApi(
1036
            $this->getCurrentlyAuthenticatedUser(),
1037
            $language,
1038
            $filterString,
1039
            $includeString,
1040
            $orderString,
1041
            $limit,
1042
            $page,
1043
            $filterMode,
1044
            self::META_ON_OBJECT_LEVEL_DISABLED
1045
        )), true);
1046
    }
1047
1048
    /**
1049
     * @param \UnserAller_Model_User $currentUser
1050
     * @param string $language
1051
     * @param string $filterString
1052
     * @param string $includeString
1053
     * @param string $orderString
1054
     * @param int $limit
1055
     * @param string $filterMode
1056
     * @return \Generator
1057
     */
1058
    public function batchFindMultiple(
1059
        $currentUser,
1060
        $language = '',
1061
        $filterString = '',
1062
        $includeString = '',
1063
        $orderString = '',
1064
        $limit = 500,
1065
        $filterMode = 'AND'
1066
    ) {
1067
        $page = 1;
1068
1069
        $result = $this->findMultipleForApi(
1070
            $currentUser,
1071
            $language,
1072
            $filterString,
1073
            $includeString,
1074
            $orderString,
1075
            $limit,
1076
            $page,
1077
            $filterMode
1078
        );
1079
1080
        yield $result;
1081
1082
        $totalPages = $result['totalPages'];
1083
        unset($result);
1084
        $page++;
1085
        while ($page <= $totalPages) {
1086
            $result = $this->findMultipleForApi(
1087
                $currentUser,
1088
                $language,
1089
                $filterString,
1090
                $includeString,
1091
                $orderString,
1092
                $limit,
1093
                $page,
1094
                $filterMode
1095
            );
1096
            yield $result;
1097
            $page++;
1098
            unset($result);
1099
        }
1100
    }
1101
1102
    /**
1103
     * @param \UnserAller_Model_User $currentUser
1104
     * @param string $language
1105
     * @param string $filterString
1106
     * @param string $includeString
1107
     * @param string $orderString
1108
     * @param int $limit
1109
     * @param int $page
1110
     * @param string $filterMode
1111
     * @return array
1112
     */
1113
    public function getNativeSqlIngredientsForFindMultiple(
1114
        $currentUser,
1115
        $language = '',
1116
        $filterString = '',
1117
        $includeString = '',
1118
        $orderString = '',
1119
        $limit = 0,
1120
        $page = 1,
1121
        $filterMode = 'AND'
1122
    ) {
1123
        if ($page <= 0) {
1124
            $page = 1;
1125
        }
1126
1127
        $incompleteStatement = $this->createIncompleteStatement($currentUser, $filterString, $language, $filterMode);
1128
1129
        $completeStatement = $this->completeStatement(
1130
            $currentUser,
1131
            $language,
1132
            $incompleteStatement,
1133
            $includeString,
1134
            $orderString
1135
        );
1136
        if ($limit > 0) {
1137
            $completeStatement
1138
                ->setFirstResult(($page - 1) * $limit)
1139
                ->setMaxResults($limit);
1140
        }
1141
1142
        return $this->getNativeSqlIngredients($completeStatement->getQuery());
1143
    }
1144
1145
    /**
1146
     * @param Query $query
1147
     * @return array
1148
     */
1149
    private function getNativeSqlIngredients($query)
1150
    {
1151
        $sql = $query->getSQL();
1152
        $c = new \ReflectionClass('Doctrine\ORM\Query');
1153
        $parser = $c->getProperty('_parserResult');
1154
        $parser->setAccessible(true);
1155
        /** @var \ReflectionProperty $parser */
1156
        $parser = $parser->getValue($query);
1157
        /** @var \Doctrine\ORM\Query\ParserResult $parser */
1158
        $resultSet = $parser->getResultSetMapping();
1159
1160
        // Change the aliases back to what was originally specified in the QueryBuilder.
1161
        $sql = preg_replace_callback('/AS\s([a-zA-Z0-9_]+)/', function ($matches) use ($resultSet) {
1162
            $ret = 'AS ';
1163
            if ($resultSet->isScalarResult($matches[1])) {
1164
                $ret .= $resultSet->getScalarAlias($matches[1]);
1165
            } else {
1166
                $ret .= $matches[1];
1167
            }
1168
            return $ret;
1169
        }, $sql);
1170
        $m = $c->getMethod('processParameterMappings');
1171
        $m->setAccessible(true);
1172
        list($params, $types) = $m->invoke($query, $parser->getParameterMappings());
1173
        return [$sql, $params, $types];
1174
    }
1175
1176
    /**
1177
     * @param \UnserAller_Model_User $currentUser
1178
     * @param string $language
1179
     * @param string $filterString
1180
     * @param string $includeString
1181
     * @param string $orderString
1182
     * @param int $metaOnObjectLevelOption
1183
     * @return array|null
1184
     */
1185
    public function findOneForApi(
1186
        $currentUser,
1187
        $language = '',
1188
        $filterString = '',
1189
        $includeString = '',
1190
        $orderString = '',
1191
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1192
    ) {
1193
        return $this->createSingleResult(
1194
            $currentUser,
1195
            $language,
1196
            $filterString,
1197
            $includeString,
1198
            $orderString,
1199
            $metaOnObjectLevelOption
1200
        );
1201
    }
1202
1203
    /**
1204
     * @param string $language
1205
     * @param string $filterString
1206
     * @param string $includeString
1207
     * @param string $orderString
1208
     * @return array|null
1209
     */
1210
    public function findOne($language = '', $filterString = '', $includeString = '', $orderString = '')
1211
    {
1212
        return json_decode(json_encode($this->findOneForApi(
1213
            $this->getCurrentlyAuthenticatedUser(),
1214
            $language,
1215
            $filterString,
1216
            $includeString,
1217
            $orderString,
1218
            self::META_ON_OBJECT_LEVEL_DISABLED
1219
        )), true);
1220
    }
1221
1222
    /**
1223
     * @param \UnserAller_Model_User $currentUser
1224
     * @param int $id
1225
     * @param string $language
1226
     * @param string $include
1227
     * @param int $metaOnObjectLevelOption
1228
     * @return array|null
1229
     */
1230
    public function findForApi(
1231
        $currentUser,
1232
        $id,
1233
        $language = '',
1234
        $include = '',
1235
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1236
    ) {
1237
        $this->id = (int)$id;
1238
        return $this->findOneForApi(
1239
            $currentUser,
1240
            $language,
1241
            "id:is($id)",
1242
            $include,
1243
            '',
1244
            $metaOnObjectLevelOption
1245
        );
1246
    }
1247
1248
    /**
1249
     * @param int $id
1250
     * @param string $language
1251
     * @param string $include
1252
     * @return array|null
1253
     */
1254
    public function find($id, $language = '', $include = '')
1255
    {
1256
        return json_decode(json_encode($this->findForApi(
1257
            $this->getCurrentlyAuthenticatedUser(),
1258
            $id,
1259
            $language,
1260
            $include,
1261
            self::META_ON_OBJECT_LEVEL_DISABLED
1262
        )), true);
1263
    }
1264
1265
    /**
1266
     * @return null|\UnserAller_Model_User|object
1267
     */
1268
    private function getCurrentlyAuthenticatedUser()
1269
    {
1270
        return $this->getEntityManager()->find(
1271
            \UnserAller_Model_User::class,
1272
            (int)\Zend_Auth::getInstance()->getIdentity()
1273
        );
1274
    }
1275
1276
    /**
1277
     * @param \UnserAller_Model_User $currentUser
1278
     * @param string $language
1279
     * @param string $filterString
1280
     * @param string $includeString
1281
     * @param string $orderString
1282
     * @param int $metaOnObjectLevelOption
1283
     * @return array|null
1284
     */
1285
    protected function createSingleResult(
1286
        $currentUser,
1287
        $language,
1288
        $filterString,
1289
        $includeString,
1290
        $orderString,
1291
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1292
    ) {
1293
        $meta = $this->initMetaArray('', $language);
1294
1295
        $result = $this->getRawResult(
1296
            $this->completeStatement(
1297
                $currentUser,
1298
                $language,
1299
                $this->createIncompleteStatement($currentUser, $filterString, $language, 'AND', $meta),
1300
                $includeString,
1301
                $orderString,
1302
                $meta
1303
            )->setFirstResult(0)->setMaxResults(1)
1304
        );
1305
1306
        if (!isset($result[0])) {
1307
            return null;
1308
        }
1309
1310
        $result = $this->applyScheduledFixes($result, $currentUser, $language, $meta, $metaOnObjectLevelOption)[0];
1311
        if ($metaOnObjectLevelOption === self::META_ON_OBJECT_LEVEL_ENABLED) {
1312
            $result['meta'] = $result['meta'] + $meta;
1313
        }
1314
        return $result;
1315
    }
1316
1317
    protected function getDefaultPostProcessDirections()
1318
    {
1319
        return [];
1320
    }
1321
1322
    /**
1323
     * Adds the default select statement, all includes and order statements to the incomplete statement
1324
     * and returns the qurey builder instance
1325
     *
1326
     * @param \UnserAller_Model_User $currentUser
1327
     * @param $language
1328
     * @param \Doctrine\ORM\QueryBuilder $incompleteStatement
1329
     * @param string $includeString
1330
     * @param string $orderString
1331
     * @param array $meta
1332
     * @return \Doctrine\ORM\QueryBuilder
1333
     */
1334
    private function completeStatement(
1335
        $currentUser,
1336
        $language,
1337
        $incompleteStatement,
1338
        $includeString,
1339
        $orderString,
1340
        &$meta = []
1341
    ) {
1342
        $statement = clone $incompleteStatement;
1343
1344
        $this->schedulePostProcessingDirections($this->getDefaultPostProcessDirections());
1345
1346
        return $this->addOrderStatements(
1347
            $this->addIncludeStatements(
1348
                $statement->select($this->getDefaultSelectStatement($statement)),
1349
                $currentUser,
1350
                $language,
1351
                $includeString,
1352
                $meta
1353
            ),
1354
            $currentUser,
1355
            $orderString
1356
        );
1357
    }
1358
1359
    /**
1360
     * Returns the default select statement. In this case it just returns the first root entity which means the
1361
     * entire root entity will be selected
1362
     *
1363
     * @param \Doctrine\ORM\QueryBuilder $query
1364
     * @return string
1365
     */
1366
    protected function getDefaultSelectStatement($query)
1367
    {
1368
        return 'DISTINCT ' . $this->getRootAlias($query);
1369
    }
1370
1371
    /**
1372
     * Returns first root alias from query builder
1373
     *
1374
     * @param \Doctrine\ORM\QueryBuilder $query
1375
     * @return string
1376
     */
1377
    protected function getRootAlias($query)
1378
    {
1379
        $rootAliasArr = $query->getRootAliases();
1380
        return array_shift($rootAliasArr);
1381
    }
1382
1383
    /**
1384
     * Returns true if result item has an additional layer in the hierarchy because of custom subselects
1385
     *
1386
     * @param array $item
1387
     * @return bool
1388
     */
1389
    private function mustFlattenResultItem($item)
1390
    {
1391
        return isset($item[0]);
1392
    }
1393
1394
    /**
1395
     * Returns doctrine array results with all fixes applied
1396
     *
1397
     * @param \Doctrine\ORM\QueryBuilder $statement
1398
     * @return array
1399
     */
1400
    private function getRawResult($statement)
1401
    {
1402
        //Output raw sql here if you like to debug hard
1403
        //echo $statement->getQuery()->getSQL(); die;
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1404
        return $statement->getQuery()->getResult(Query::HYDRATE_ARRAY);
1405
    }
1406
1407
    /**
1408
     * Doctrine will create an additional result layer when values are selected that do not belong
1409
     * to the doctrine object model. This function removes this additional layer and merges custom values with
1410
     * the doctrine object model for a single result item - not the whole result array.
1411
     *
1412
     * @param array $item
1413
     * @return array
1414
     */
1415
    private function flattenResultItem($item)
1416
    {
1417
        if (!$this->mustFlattenResultItem($item)) {
1418
            return $item;
1419
        }
1420
1421
        return array_merge(array_shift($item), $item);
1422
    }
1423
1424
    /**
1425
     * Parses $string for orderBy statements and returns an array where order statements are values.
1426
     *
1427
     * When string is "latestFirst, longestFirst" result would be: ['latestFirst', 'longestFirst']
1428
     *
1429
     * @param string $string
1430
     * @return array
1431
     */
1432
    protected function parseOrderString($string)
1433
    {
1434
        return array_filter(array_map('trim', explode(',', $string)));
1435
    }
1436
1437
    /**
1438
     * @uses getFallbackLanguage
1439
     * @uses getRequestedLanguage
1440
     * @param $language
1441
     * @return callable
1442
     */
1443
    private function getItemLanguageGetter($language)
1444
    {
1445
        return $language === '' ? 'getFallbackLanguage' : 'getRequestedLanguage';
1446
    }
1447
1448
    /**
1449
     * @param mixed[] $resultItem
1450
     * @param string $requestedLanguage
1451
     * @return string
1452
     */
1453
    private function getRequestedLanguage($resultItem, $requestedLanguage)
1454
    {
1455
        return $requestedLanguage;
1456
    }
1457
1458
    /**
1459
     * Executes all operations that were scheduled for post processing
1460
     *
1461
     * @param array $result
1462
     * @param \UnserAller_Model_User $currentUser
1463
     * @param $language
1464
     * @param array $meta
1465
     * @param int $metaOnObjectLevelOption
1466
     * @return array
1467
     */
1468
    private function applyScheduledFixes(
1469
        $result,
1470
        $currentUser,
1471
        $language,
1472
        &$meta = [],
1473
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1474
    ) {
1475
        $scheduledFixes = $this->flushResultArrayFixSchedule();
1476
1477
        if (!$result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1478
            return $result;
1479
        }
1480
1481
        $numberOfResults = count($result);
1482
1483
        $this->applyFixesToItem(
1484
            $result[0],
1485
            $scheduledFixes,
1486
            $currentUser,
1487
            $meta,
1488
            'retrieveNestedCollectionAndMergeMeta',
1489
            $language,
1490
            $this->getItemLanguageGetter($language),
1491
            $metaOnObjectLevelOption
1492
        );
1493
        for ($i = 1; $i < $numberOfResults; $i++) {
1494
            $this->applyFixesToItem(
1495
                $result[$i],
1496
                $scheduledFixes,
1497
                $currentUser,
1498
                $meta,
1499
                'retrieveNestedCollection',
1500
                $language,
1501
                $this->getItemLanguageGetter($language),
1502
                $metaOnObjectLevelOption
1503
            );
1504
        }
1505
1506
        return $result;
1507
    }
1508
1509
    private function retrieveNestedCollectionResult($value, $nestingOptions, $language = '')
1510
    {
1511
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1512
        list(
1513
            $filterString,
1514
            $includeString,
1515
            $orderString,
1516
            $limit,
1517
            $page,
1518
            $filterMode
1519
            ) = $this->parseAdditionalIncludeParams($additionalParams);
1520
1521
        if ($filterString) {
1522
            $filterString = $filterString . ',';
1523
        }
1524
1525
        if (is_array($value)) {
1526
            $filterFunctionString = vsprintf($filterFunction, array_merge($value));
1527
        } else {
1528
            $filterFunctionString = sprintf($filterFunction, $value);
1529
        }
1530
1531
        return $this->getAdapter($model)->findMultipleForApi(
1532
            $currentUser,
1533
            $language,
1534
            $filterString . $filterFunctionString,
1535
            $includeString,
1536
            $orderString,
1537
            $limit,
1538
            $page,
1539
            $filterMode
1540
        );
1541
    }
1542
1543
    private function retrieveNestedCollection($value, $nestingOptions, $language, $finalPath, $meta)
1544
    {
1545
        return $this->retrieveNestedCollectionResult($value, $nestingOptions, $language)['data'];
1546
    }
1547
1548
    private function retrieveNestedCollectionAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1549
    {
1550
        $result = $this->retrieveNestedCollectionResult($value, $nestingOptions, $language);
1551
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1552
        return $result['data'];
1553
    }
1554
1555
    private function retrieveNestedSingleResult($value, $nestingOptions, $language = '')
1556
    {
1557
        list($model, $filterFunction, $currentUser, $additionalParams) = $nestingOptions;
1558
        list($filterString, $includeString, $orderString, , ,) = $this->parseAdditionalIncludeParams($additionalParams);
1559
1560
        if ($filterString) {
1561
            $filterString = $filterString . ',';
1562
        }
1563
1564
        return $this->getAdapter($model)->findOneForApi(
1565
            $currentUser,
1566
            $language,
1567
            $filterString . sprintf($filterFunction, $value),
1568
            $includeString,
1569
            $orderString
1570
        );
1571
    }
1572
1573
    private function retrieveNestedSingleAndMergeMeta($value, $nestingOptions, $language, $finalPath, &$meta)
1574
    {
1575
        $result = $this->retrieveNestedSingleResult($value, $nestingOptions, $language);
1576
        $this->mergeNestedMeta($meta, $result['meta'], $finalPath);
1577
        unset($result['meta']);
1578
        return $result;
1579
    }
1580
1581
    private function mergeNestedMeta(&$meta, $nestedMeta, $includeName)
1582
    {
1583
        foreach ($nestedMeta['modelnameIndex'] as $model => $paths) {
1584
            foreach ($paths as $path) {
1585
                $fullPath = $includeName . '.' . $path;
1586
                if ($path && !in_array($fullPath, $meta['modelnameIndex'][$model])) {
1587
                    $meta['modelnameIndex'][$model][] = $fullPath;
1588
                }
1589
            }
1590
        }
1591
    }
1592
1593
    /**
1594
     * @param $item
1595
     * @param $scheduledFixes
1596
     * @param $currentUser
1597
     * @param $meta
1598
     * @param $collectionNestingMethod
1599
     * @param $language
1600
     * @param $itemLanguageGetter
1601
     * @param int $metaOnObjectLevelOption
1602
     * @uses retrieveNestedCollection
1603
     * @uses retrieveNestedCollectionAndMergeMeta
1604
     */
1605
    private function applyFixesToItem(
1606
        &$item,
1607
        $scheduledFixes,
1608
        $currentUser,
1609
        &$meta,
1610
        $collectionNestingMethod,
1611
        $language,
1612
        $itemLanguageGetter,
1613
        $metaOnObjectLevelOption = self::META_ON_OBJECT_LEVEL_ENABLED
1614
    ) {
1615
        $item = $this->flattenResultItem($item);
1616
1617
        //If deleteion is not scheduled and done at the end, multiple tasks on same field can fail
1618
        $scheduledDeletions = [];
1619
1620
        foreach ($scheduledFixes as $path => $fix) {
1621
            if (isset($fix['delete'])) {
1622
                $scheduledDeletions[$path] = $fix;
1623
                continue;
1624
            }
1625
1626
            if (isset($fix['cast'])) {
1627
                \UnserAllerLib_Tool_Array::castNestedValue($item, $path, $fix['cast']);
1628
            }
1629
1630
            $value = \UnserAllerLib_Tool_Array::readNestedValue($item, $path);
1631
1632
            if (isset($fix['additionalFilterValues'])) {
1633
                $value = [$value];
1634
1635
                foreach ($fix['additionalFilterValues'] as $additionalFilterValue) {
1636
                    $value[] = \UnserAllerLib_Tool_Array::readNestedValue($item, $additionalFilterValue);
1637
                }
1638
            }
1639
1640 View Code Duplication
            if (isset($fix['nestCollection'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1641
                $value = $this->$collectionNestingMethod(
1642
                    $value,
1643
                    $fix['nestCollection'],
1644
                    $this->$itemLanguageGetter($item, $language),
1645
                    $fix['move'] ? $fix['move'] : $path,
1646
                    $meta
1647
                );
1648
            }
1649
1650 View Code Duplication
            if (isset($fix['nestSingle'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1651
                $value = $this->retrieveNestedSingleAndMergeMeta(
1652
                    $value,
1653
                    $fix['nestSingle'],
1654
                    $this->$itemLanguageGetter($item, $language),
1655
                    $fix['move'] ? $fix['move'] : $path,
1656
                    $meta
1657
                );
1658
            }
1659
1660
            if (isset($fix['filter'])) {
1661
                $value = $this->filterValue($fix['filter'], $value, $currentUser);
1662
            }
1663
1664
            if (isset($fix['cFilter'])) {
1665
                $value = $this->filterValue($fix['cFilter'], $value, $currentUser);
1666
            }
1667
1668
            if (isset($fix['mFilter'])) {
1669
                $value = $this->filterValue($fix['mFilter'], $item, $currentUser);
1670
            }
1671
1672
            if (isset($fix['move'])) {
1673
                \UnserAllerLib_Tool_Array::integrateNestedValue($item, $fix['move'], $value);
1674
                if ($path != $fix['move']) {
1675
                    $scheduledDeletions[$path] = ['delete' => 1];
1676
                }
1677
            }
1678
        }
1679
1680
        foreach ($scheduledDeletions as $path => $fix) {
1681
            \UnserAllerLib_Tool_Array::unsetNestedValue($item, $path);
1682
        }
1683
1684
        if ($metaOnObjectLevelOption === self::META_ON_OBJECT_LEVEL_ENABLED) {
1685
            $item['meta'] = $this->createMetadataForResultItem($item, $language, $itemLanguageGetter);
1686
        }
1687
    }
1688
1689
    /**
1690
     * @param mixed[] $resultItem
1691
     * @param string $requestedLanguage
1692
     * @param callable $itemLanguageGetter
1693
     * @return array
1694
     */
1695
    private function createMetadataForResultItem($resultItem, $requestedLanguage, $itemLanguageGetter)
1696
    {
1697
        return [
1698
            'language' => $this->$itemLanguageGetter($resultItem, $requestedLanguage),
1699
            'model' => $this->getModelForMeta()
1700
        ];
1701
    }
1702
1703
    /**
1704
     * Applies filter methods for $filterName to $value
1705
     * @uses filterJsonAfterwards
1706
     * @uses filterJsonIfNullSetEmptyObjectAfterwards
1707
     * @uses filterJsonOrNullAfterwards
1708
     * @uses filterDatetimeAfterwards
1709
     * @uses filterDatetimeOrNullAfterwards
1710
     * @uses filterIntOrNullAfterwards
1711
     * @uses filterNl2BrAfterwards
1712
     * @param string $filterName
1713
     * @param mixed $value
1714
     * @param $currentUser
1715
     * @return mixed
1716
     */
1717
    private function filterValue($filterName, $value, $currentUser)
1718
    {
1719
        if (!is_callable([$this, $filterName])) {
1720
            throw new \InvalidArgumentException('Post Processing Filter method not found: ' . $filterName);
1721
        }
1722
1723
        return call_user_func_array([$this, $filterName], [$value, $currentUser]);
1724
    }
1725
1726
    /**
1727
     * @param $field
1728
     * @param \Doctrine\ORM\QueryBuilder $query
1729
     * @param string $alias
1730
     * @param \UnserAller_Model_User $currentUser
1731
     * @param array $methods
1732
     * @return \Doctrine\ORM\Query\Expr\Andx
1733
     * @uses stringContainExpression
1734
     * @uses stringContainsExpression
1735
     * @uses stringIsExpression
1736
     * @uses stringNotExpression
1737
     * @uses stringFalseExpression
1738
     * @uses stringTrueExpression
1739
     */
1740
    protected function createConditionsForStringColumn($field, $query, $alias, $currentUser, $methods)
1741
    {
1742
        if (\UnserAllerLib_Tool_Array::hasMoreKeysThan(
1743
            $methods,
1744
            ['contain', 'contains', 'is', 'not', 'false', 'true']
1745
        )
1746
        ) {
1747
            throw new \InvalidArgumentException('Invalid expression methods used');
1748
        }
1749
1750
        return $this->createExpression('string', $field, $query, $alias, $currentUser, $methods);
1751
    }
1752
1753
    /**
1754
     * @param \Doctrine\ORM\QueryBuilder $query
1755
     * @param $fallbackField
1756
     * @param $translationName
1757
     * @param $language
1758
     * @param string $alias
1759
     * @param \UnserAller_Model_User $currentUser
1760
     * @param $additionalParams
1761
     * @return \Doctrine\ORM\Query\Expr\Composite
1762
     * @throws \UnserAllerLib_Api_V4_Exception_InvalidFilter
1763
     */
1764
    protected function createConditionsForMultilanguageStringColumn(
1765
        $query,
1766
        $fallbackField,
1767
        $translationName,
1768
        $language,
1769
        $alias,
1770
        $currentUser,
1771
        $additionalParams
1772
    ) {
1773
        if (isset($additionalParams['overAllTranslations'])) {
1774
            if (!$this->supportedLanguages) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->supportedLanguages of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1775
                throw new \UnserAllerLib_Api_V4_Exception_InvalidFilter('Supported languages are not set');
1776
            }
1777
1778
            unset($additionalParams['overAllTranslations']);
1779
1780
            $expr = $query->expr()->orX();
1781
            foreach ($this->supportedLanguages as $supportedLanguage) {
1782
                $expr->add($this->createConditionsForStringColumn(
1783
                    "COALESCE(" . $this->joinTranslationOnce(
1784
                        $query,
1785
                        $translationName,
1786
                        $supportedLanguage
1787
                    ) . ".translation, $fallbackField)",
1788
                    $query,
1789
                    $alias,
1790
                    $currentUser,
1791
                    $additionalParams
1792
                ));
1793
            }
1794
1795
            return $expr;
1796
        }
1797
1798
        return $this->createConditionsForStringColumn(
1799
            "COALESCE(" . $this->joinTranslationOnce(
1800
                $query,
1801
                $translationName,
1802
                $language
1803
            ) . ".translation, $fallbackField)",
1804
            $query,
1805
            $alias,
1806
            $currentUser,
1807
            $additionalParams
1808
        );
1809
    }
1810
1811
1812
    /**
1813
     * @param $field
1814
     * @param \Doctrine\ORM\QueryBuilder $query
1815
     * @param string $alias
1816
     * @param \UnserAller_Model_User $currentUser
1817
     * @param array $methods
1818
     * @return \Doctrine\ORM\Query\Expr\Andx
1819
     * @uses dateGtExpression
1820
     * @uses dateGteExpression
1821
     * @uses dateLtExpression
1822
     * @uses dateLteExpression
1823
     * @uses dateFalseExpression
1824
     * @uses dateTrueExpression
1825
     * @uses dateIsExpression
1826
     * @uses dateNotExpression
1827
     */
1828 View Code Duplication
    protected function createConditionsForDatetimeColumn($field, $query, $alias, $currentUser, $methods)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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