Issues (42)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Vortex.php (35 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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