Completed
Push — master ( c6a299...3ff228 )
by Fabien
07:05 queued 03:28
created

ContentRepository::resetDefaultQuerySettings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
namespace Fab\Vidi\Domain\Repository;
3
4
/**
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Fab\Vidi\DataHandler\DataHandlerFactory;
18
use Fab\Vidi\Domain\Validator\ContentValidator;
19
use Fab\Vidi\Domain\Validator\LanguageValidator;
20
use Fab\Vidi\Persistence\QuerySettings;
21
use Fab\Vidi\Resolver\FieldPathResolver;
22
use TYPO3\CMS\Core\DataHandling\DataHandler;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Core\Utility\MathUtility;
25
use TYPO3\CMS\Extbase\Object\ObjectManager;
26
use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedMethodException;
27
use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;
28
use TYPO3\CMS\Extbase\Persistence\RepositoryInterface;
29
use Fab\Vidi\Converter\Property;
30
use Fab\Vidi\DataHandler\ProcessAction;
31
use Fab\Vidi\Domain\Model\Content;
32
use Fab\Vidi\Persistence\Matcher;
33
use Fab\Vidi\Persistence\Order;
34
use Fab\Vidi\Persistence\Query;
35
use Fab\Vidi\Tca\Tca;
36
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface;
37
38
/**
39
 * Repository for accessing Content
40
 */
41
class ContentRepository implements RepositoryInterface
42
{
43
44
    /**
45
     * Tell whether it is a raw result (array) or object being returned.
46
     *
47
     * @var bool
48
     */
49
    protected $rawResult = false;
50
51
    /**
52
     * The data type to be returned, e.g fe_users, fe_groups, tt_content, etc...
53
     *
54
     * @var string
55
     */
56
    protected $dataType;
57
58
    /**
59
     * The source field is useful in the context of MM relations to know who is the caller
60
     * e.g findByItems which eventually corresponds to a field name.
61
     *
62
     * @var string
63
     */
64
    protected $sourceFieldName = '';
65
66
    /**
67
     * @var array
68
     */
69
    protected $errorMessages = array();
70
71
    /**
72
     * @var QuerySettingsInterface
73
     */
74
    protected $defaultQuerySettings;
75
76
    /**
77
     * Constructor
78
     *
79
     * @param string $dataType
80
     */
81
    public function __construct($dataType)
82
    {
83
        $this->dataType = $dataType;
84
    }
85
86
    /**
87
     * Returns all objects of this repository.
88
     *
89
     * @return Content[]
90
     */
91
    public function findAll()
92
    {
93
        $query = $this->createQuery();
94
        return $query->execute();
95
    }
96
97
    /**
98
     * Returns all "distinct" values for a given property.
99
     *
100
     * @param string $propertyName
101
     * @param Matcher $matcher
102
     * @return Content[]
103
     */
104 View Code Duplication
    public function findDistinctValues($propertyName, Matcher $matcher = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
105
    {
106
        $query = $this->createQuery();
107
        $query->setDistinct($propertyName);
108
109
        // Remove empty values from selection.
110
        $constraint = $query->logicalNot($query->equals($propertyName, ''));
111
112
        // Add some additional constraints from the Matcher object.
113
        $matcherConstraint = null;
114
        if (!is_null($matcher)) {
115
            $matcherConstraint = $this->computeConstraints($query, $matcher);
116
        }
117
118
        // Assemble the final constraints or not.
119
        if ($matcherConstraint) {
120
            $query->logicalAnd($matcherConstraint, $constraint);
121
            $query->matching($query->logicalAnd($matcherConstraint, $constraint));
122
        } else {
123
            $query->matching($constraint);
124
        }
125
126
        return $query->execute();
127
    }
128
129
    /**
130
     * Returns all "distinct" values for a given property.
131
     *
132
     * @param string $propertyName
133
     * @param Matcher $matcher
134
     * @return int
135
     */
136 View Code Duplication
    public function countDistinctValues($propertyName, Matcher $matcher = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
137
    {
138
        $query = $this->createQuery();
139
        $query->setDistinct($propertyName);
140
141
        // Remove empty values from selection.
142
        $constraint = $query->logicalNot($query->equals($propertyName, ''));
143
144
        // Add some additional constraints from the Matcher object.
145
        $matcherConstraint = null;
146
        if (!is_null($matcher)) {
147
            $matcherConstraint = $this->computeConstraints($query, $matcher);
148
        }
149
150
        // Assemble the final constraints or not.
151
        if ($matcherConstraint) {
152
            $query->logicalAnd($matcherConstraint, $constraint);
153
            $query->matching($query->logicalAnd($matcherConstraint, $constraint));
154
        } else {
155
            $query->matching($constraint);
156
        }
157
158
        return $query->count();
159
    }
160
161
    /**
162
     * Finds an object matching the given identifier.
163
     *
164
     * @param int $uid The identifier of the object to find
165
     * @return Content|null
166
     * @api
167
     */
168
    public function findByUid($uid)
169
    {
170
        return $this->findByIdentifier($uid);
171
    }
172
173
    /**
174
     * Finds all Contents given specified matches.
175
     *
176
     * @param string $propertyName
177
     * @param array $values
178
     * @return Content[]
179
     */
180
    public function findIn($propertyName, array $values)
181
    {
182
        $query = $this->createQuery();
183
        $query->matching($query->in($propertyName, $values));
184
        return $query->execute();
185
    }
186
187
    /**
188
     * Finds all Contents given specified matches.
189
     *
190
     * @param Matcher $matcher
191
     * @param Order $order The order
192
     * @param int $limit
193
     * @param int $offset
194
     * @return Content[]
195
     */
196
    public function findBy(Matcher $matcher, Order $order = null, $limit = null, $offset = null)
197
    {
198
199
        $query = $this->createQuery();
200
201
        $limit = (int)$limit; // make sure to cast
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $limit. This often makes code more readable.
Loading history...
202
        if ($limit > 0) {
203
            $query->setLimit($limit);
204
        }
205
206
        if ($order) {
207
            $query->setOrderings($order->getOrderings());
208
209
            // Loops around the orderings adding if necessary a dummy condition
210
            // to make sure the relations can be resolved when transforming the query to plain SQL.
211
            foreach ($order->getOrderings() as $ordering => $direction) {
212
                if ($this->hasForeignRelationIn($ordering)) {
213
                    $relationalField = $this->getForeignRelationFrom($ordering);
214
                    $matcher->like($relationalField . '.uid', '');
215
                }
216
            }
217
        }
218
219
        if ($offset) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $offset of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
220
            $query->setOffset($offset);
221
        }
222
223
        $constraints = $this->computeConstraints($query, $matcher);
224
225
        if ($constraints) {
226
            $query->matching($constraints);
227
        }
228
229
        return $query->execute();
230
    }
231
232
    /**
233
     * Find one Content object given specified matches.
234
     *
235
     * @param Matcher $matcher
236
     * @return Content
237
     */
238
    public function findOneBy(Matcher $matcher)
239
    {
240
241
        $query = $this->createQuery();
242
243
        $constraints = $this->computeConstraints($query, $matcher);
244
245
        if ($constraints) {
246
            $query->matching($constraints);
247
        }
248
249
        $query->setLimit(1); // only take one!
250
251
        $resultSet = $query->execute();
252
        if ($resultSet) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resultSet 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...
253
            $resultSet = current($resultSet);
254
        }
255
        return $resultSet;
256
    }
257
258
    /**
259
     * Count all Contents given specified matches.
260
     *
261
     * @param Matcher $matcher
262
     * @return int
263
     */
264
    public function countBy(Matcher $matcher)
265
    {
266
267
        $query = $this->createQuery();
268
269
        $constraints = $this->computeConstraints($query, $matcher);
270
271
        if ($constraints) {
272
            $query->matching($constraints);
273
        }
274
275
        return $query->count();
276
    }
277
278
    /**
279
     * Update a content with new information.
280
     *
281
     * @param Content $content
282
     * @param $language
283
     * @return bool
284
     */
285 View Code Duplication
    public function localize($content, $language)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
286
    {
287
288
        // Security check
289
        $this->getContentValidator()->validate($content);
290
        $this->getLanguageValidator()->validate($language);
291
292
        $dataType = $content->getDataType();
293
        $handler = $this->getDataHandlerFactory()->action(ProcessAction::LOCALIZE)->forType($dataType)->getDataHandler();
294
295
        $handlerResult = $handler->processLocalize($content, $language);
296
        $this->errorMessages = $handler->getErrorMessages();
297
        return $handlerResult;
298
    }
299
300
    /**
301
     * Update a content with new information.
302
     *
303
     * @param Content $content
304
     * @return bool
305
     */
306
    public function update($content)
307
    {
308
309
        // Security check.
310
        $this->getContentValidator()->validate($content);
311
312
        $dataType = $content->getDataType();
313
        $handler = $this->getDataHandlerFactory()->action(ProcessAction::UPDATE)->forType($dataType)->getDataHandler();
314
315
        $handlerResult = $handler->processUpdate($content);
316
        $this->errorMessages = $handler->getErrorMessages();
317
        return $handlerResult;
318
    }
319
320
    /**
321
     * Removes an object from this repository.
322
     *
323
     * @param Content $content
324
     * @return boolean
325
     */
326
    public function remove($content)
327
    {
328
        $dataType = $content->getDataType();
329
        $handler = $this->getDataHandlerFactory()->action(ProcessAction::REMOVE)->forType($dataType)->getDataHandler();
330
331
        $handlerResult = $handler->processRemove($content);
332
        $this->errorMessages = $handler->getErrorMessages();
333
        return $handlerResult;
334
    }
335
336
    /**
337
     * Move a content within this repository.
338
     * The $target corresponds to the pid to move the records to.
339
     * It can also be a negative value in case of sorting. The negative value would be the uid of its predecessor.
340
     *
341
     * @param Content $content
342
     * @param string $target
343
     * @return bool
344
     */
345 View Code Duplication
    public function move($content, $target)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
346
    {
347
348
        // Security check.
349
        $this->getContentValidator()->validate($content);
350
351
        $dataType = $content->getDataType();
352
        $handler = $this->getDataHandlerFactory()->action(ProcessAction::MOVE)->forType($dataType)->getDataHandler();
353
354
        $handlerResult = $handler->processMove($content, $target);
355
        $this->errorMessages = $handler->getErrorMessages();
356
        return $handlerResult;
357
    }
358
359
    /**
360
     * Copy a content within this repository.
361
     *
362
     * @param Content $content
363
     * @return bool
364
     */
365 View Code Duplication
    public function copy($content, $target)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
366
    {
367
368
        // Security check.
369
        $this->getContentValidator()->validate($content);
370
371
        $dataType = $content->getDataType();
372
        $handler = $this->getDataHandlerFactory()->action(ProcessAction::COPY)->forType($dataType)->getDataHandler();
373
374
        $handlerResult = $handler->processCopy($content, $target);
375
        $this->errorMessages = $handler->getErrorMessages();
376
        return $handlerResult;
377
    }
378
379
    /**
380
     * Adds an object to this repository.
381
     *
382
     * @param object $object The object to add
383
     * @throws \BadMethodCallException
384
     * @return void
385
     * @api
386
     */
387
    public function add($object)
388
    {
389
        throw new \BadMethodCallException('Repository does not support the add() method.', 1375805599);
390
    }
391
392
    /**
393
     * Returns the total number objects of this repository.
394
     *
395
     * @return integer The object count
396
     * @api
397
     */
398
    public function countAll()
399
    {
400
        $query = $this->createQuery();
401
        return $query->count();
402
    }
403
404
    /**
405
     * Removes all objects of this repository as if remove() was called for
406
     * all of them.
407
     *
408
     * @return void
409
     * @api
410
     */
411
    public function removeAll()
412
    {
413
        // TODO: Implement removeAll() method.
414
    }
415
416
    /**
417
     * Finds an object matching the given identifier.
418
     *
419
     * @param mixed $identifier The identifier of the object to find
420
     * @return Content|null
421
     * @api
422
     */
423
    public function findByIdentifier($identifier)
424
    {
425
        $query = $this->createQuery();
426
427
        $result = $query->matching($query->equals('uid', $identifier))
428
            ->execute();
429
430
        if (is_array($result)) {
431
            $result = current($result);
432
        }
433
434
        return $result;
435
    }
436
437
    /**
438
     * Dispatches magic methods (findBy[Property]())
439
     *
440
     * @param string $methodName The name of the magic method
441
     * @param string $arguments The arguments of the magic method
442
     * @throws UnsupportedMethodException
443
     * @return mixed
444
     * @api
445
     */
446
    public function __call($methodName, $arguments)
447
    {
448
        if (substr($methodName, 0, 6) === 'findBy' && strlen($methodName) > 7) {
449
            $propertyName = strtolower(substr(substr($methodName, 6), 0, 1)) . substr(substr($methodName, 6), 1);
450
            $result = $this->processMagicCall($propertyName, $arguments[0]);
451 View Code Duplication
        } elseif (substr($methodName, 0, 9) === 'findOneBy' && strlen($methodName) > 10) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
452
            $propertyName = strtolower(substr(substr($methodName, 9), 0, 1)) . substr(substr($methodName, 9), 1);
453
            $result = $this->processMagicCall($propertyName, $arguments[0], 'one');
454
        } elseif (substr($methodName, 0, 7) === 'countBy' && strlen($methodName) > 8) {
455
            $propertyName = strtolower(substr(substr($methodName, 7), 0, 1)) . substr(substr($methodName, 7), 1);
456
            $result = $this->processMagicCall($propertyName, $arguments[0], 'count');
457
        } else {
458
            throw new UnsupportedMethodException('The method "' . $methodName . '" is not supported by the repository.', 1360838010);
459
        }
460
        return $result;
461
    }
462
463
    /**
464
     * Returns a query for objects of this repository
465
     *
466
     * @return Query
467
     * @api
468
     */
469
    public function createQuery()
470
    {
471
        /** @var Query $query */
472
        $query = $this->getObjectManager()->get(Query::class, $this->dataType);
473
        $query->setSourceFieldName($this->sourceFieldName);
474
475
        if ($this->defaultQuerySettings) {
476
            $query->setQuerySettings($this->defaultQuerySettings);
477
        } else {
478
479
            // Initialize and pass the query settings at this level.
480
            /** @var QuerySettings $querySettings */
481
            $querySettings = $this->getObjectManager()->get(QuerySettings::class);
482
483
            // Default choice for the BE.
484
            if ($this->isBackendMode()) {
485
                $querySettings->setIgnoreEnableFields(true);
486
            }
487
488
            $query->setQuerySettings($querySettings);
489
        }
490
491
        return $query;
492
    }
493
494
    /**
495
     * Sets the property names to order the result by per default.
496
     * Expected like this:
497
     * array(
498
     * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
499
     * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
500
     * )
501
     *
502
     * @param array $defaultOrderings The property names to order by
503
     * @throws \BadMethodCallException
504
     * @return void
505
     * @api
506
     */
507
    public function setDefaultOrderings(array $defaultOrderings)
508
    {
509
        throw new \BadMethodCallException('Repository does not support the setDefaultOrderings() method.', 1375805598);
510
    }
511
512
    /**
513
     * Sets the default query settings to be used in this repository
514
     *
515
     * @param QuerySettingsInterface $defaultQuerySettings The query settings to be used by default
516
     * @throws \BadMethodCallException
517
     * @return void
518
     * @api
519
     */
520
    public function setDefaultQuerySettings(QuerySettingsInterface $defaultQuerySettings)
521
    {
522
        $this->defaultQuerySettings = $defaultQuerySettings;
523
    }
524
525
    /**
526
     * @return void
527
     */
528
    public function resetDefaultQuerySettings()
529
    {
530
        $this->defaultQuerySettings = null;
531
    }
532
533
534
    /**
535
     * @return array
536
     */
537
    public function getErrorMessages()
538
    {
539
        return $this->errorMessages;
540
    }
541
542
    /**
543
     * @param string $sourceFieldName
544
     * @return $this
545
     */
546
    public function setSourceFieldName($sourceFieldName)
547
    {
548
        $this->sourceFieldName = $sourceFieldName;
549
        return $this;
550
    }
551
552
    /**
553
     * @return string
554
     */
555
    public function getDataType()
556
    {
557
        return $this->dataType;
558
    }
559
560
    /**
561
     * Tell whether the order has a foreign table in its expression, e.g. "metadata.title".
562
     *
563
     * @param string $ordering
564
     * @return bool
565
     */
566
    protected function hasForeignRelationIn($ordering)
567
    {
568
        return strpos($ordering, '.') !== false;
569
    }
570
571
    /**
572
     * Extract the foreign relation of the ordering "metadata.title" -> "metadata"
573
     *
574
     * @param string $ordering
575
     * @return string
576
     */
577
    protected function getForeignRelationFrom($ordering)
578
    {
579
        $parts = explode('.', $ordering);
580
        return $parts[0];
581
    }
582
583
    /**
584
     * Get the constraints
585
     *
586
     * @param Query $query
587
     * @param Matcher $matcher
588
     * @return ConstraintInterface|null
589
     */
590
    protected function computeConstraints(Query $query, Matcher $matcher)
591
    {
592
593
        $constraints = null;
594
595
        $collectedConstraints = array();
596
597
        // Search term
598
        $constraint = $this->computeSearchTermConstraint($query, $matcher);
599
        if ($constraint) {
600
            $collectedConstraints[] = $constraint;
601
        }
602
603
        foreach ($matcher->getSupportedOperators() as $operator) {
604
            $constraint = $this->computeConstraint($query, $matcher, $operator);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $constraint is correct as $this->computeConstraint...y, $matcher, $operator) (which targets Fab\Vidi\Domain\Reposito...ry::computeConstraint()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
605
            if ($constraint) {
606
                $collectedConstraints[] = $constraint;
607
            }
608
        }
609
610
        if (count($collectedConstraints) > 1) {
611
            $logical = $matcher->getDefaultLogicalSeparator();
612
            $constraints = $query->$logical($collectedConstraints);
613
        } elseif (!empty($collectedConstraints)) {
614
615
            // true means there is one constraint only and should become the result
616
            $constraints = current($collectedConstraints);
617
        }
618
619
        // Trigger signal for post processing the computed constraints object.
620
        $constraints = $this->emitPostProcessConstraintsSignal($query, $constraints);
621
622
        return $constraints;
623
    }
624
625
    /**
626
     * Computes the search constraint and returns it.
627
     *
628
     * @param Query $query
629
     * @param Matcher $matcher
630
     * @return ConstraintInterface|null
631
     */
632
    protected function computeSearchTermConstraint(Query $query, Matcher $matcher)
633
    {
634
635
        $result = null;
636
637
        // Search term case
638
        if ($matcher->getSearchTerm()) {
639
640
            $fields = GeneralUtility::trimExplode(',', Tca::table($this->dataType)->getSearchFields(), true);
641
642
            $constraints = array();
643
            $likeClause = sprintf('%%%s%%', $matcher->getSearchTerm());
644
            foreach ($fields as $fieldNameAndPath) {
645
                if ($this->isSuitableForLike($fieldNameAndPath, $matcher->getSearchTerm())) {
646
647
                    $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
648
                    $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
649
650
                    if (Tca::table($dataType)->hasField($fieldName) && Tca::table($dataType)->field($fieldName)->hasRelation()) {
651
                        $foreignTable = Tca::table($dataType)->field($fieldName)->getForeignTable();
652
                        $fieldNameAndPath = $fieldNameAndPath . '.' . Tca::table($foreignTable)->getLabelField();
653
                    }
654
                    $constraints[] = $query->like($fieldNameAndPath, $likeClause);
655
                }
656
            }
657
            $logical = $matcher->getLogicalSeparatorForSearchTerm();
658
            $result = $query->$logical($constraints);
659
        }
660
661
        return $result;
662
    }
663
664
    /**
665
     * It does not make sense to have a "like" in presence of numerical field, e.g "uid".
666
     * Tell whether the given value makes sense for a "like" clause.
667
     *
668
     * @param string $fieldNameAndPath
669
     * @param string $value
670
     * @return bool
671
     */
672
    protected function isSuitableForLike($fieldNameAndPath, $value)
673
    {
674
        $isSuitable = true;
675
676
        // true means it is a string
677
        if (!MathUtility::canBeInterpretedAsInteger($value)) {
678
679
            $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
680
            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
681
682
            if (Tca::table($dataType)->field($fieldName)->isNumerical()
683
                && !Tca::table($dataType)->field($fieldName)->hasRelation()
684
            ) {
685
                $isSuitable = false;
686
            }
687
        }
688
689
        return $isSuitable;
690
    }
691
692
    /**
693
     * Computes the constraint for matches and returns it.
694
     *
695
     * @param Query $query
696
     * @param Matcher $matcher
697
     * @param string $operator
698
     * @return ConstraintInterface|null
699
     */
700
    protected function computeConstraint(Query $query, Matcher $matcher, $operator)
701
    {
702
        $result = null;
703
704
        $operatorName = ucfirst($operator);
705
        $getCriteria = sprintf('get%s', $operatorName);
706
        $criteria = $matcher->$getCriteria();
707
708
        if (!empty($criteria)) {
709
            $constraints = array();
710
711
            foreach ($criteria as $criterion) {
712
713
                $fieldNameAndPath = $criterion['fieldNameAndPath'];
714
                $operand = $criterion['operand'];
715
716
                // Compute a few variables...
717
                // $dataType is generally equals to $this->dataType but not always... if fieldName is a path.
718
                $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
719
                $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
720
                $fieldPath = $this->getFieldPathResolver()->stripFieldName($fieldNameAndPath, $this->dataType);
721
722
                if (Tca::table($dataType)->field($fieldName)->hasRelation()) {
723
                    if (MathUtility::canBeInterpretedAsInteger($operand)) {
724
                        $fieldNameAndPath = $fieldName . '.uid';
725
                    } else {
726
                        $foreignTableName = Tca::table($dataType)->field($fieldName)->getForeignTable();
727
                        $foreignTable = Tca::table($foreignTableName);
728
                        $fieldNameAndPath = $fieldName . '.' . $foreignTable->getLabelField();
729
                    }
730
731
                    // If different means we should restore the prepended path segment for proper SQL parser.
732
                    // This is true for a composite field, e.g items.sys_file_metadata for categories.
733
                    if ($fieldName !== $fieldPath) {
734
                        $fieldNameAndPath = $fieldPath . '.' . $fieldNameAndPath;
735
                    }
736
                }
737
738
                $constraints[] = $query->$operator($fieldNameAndPath, $criterion['operand']);
739
            }
740
741
            $getLogicalSeparator = sprintf('getLogicalSeparatorFor%s', $operatorName);
742
            $logical = $matcher->$getLogicalSeparator();
743
            $result = $query->$logical($constraints);
744
        }
745
746
        return $result;
747
    }
748
749
    /**
750
     * @return DataHandler
751
     */
752
    protected function getDataHandler()
753
    {
754
        if (!$this->dataHandler) {
755
            $this->dataHandler = GeneralUtility::makeInstance(DataHandler::class);
756
        }
757
        return $this->dataHandler;
758
    }
759
760
    /**
761
     * Handle the magic call by properly creating a Query object and returning its result.
762
     *
763
     * @param string $propertyName
764
     * @param string $value
765
     * @param string $flag
766
     * @return array
767
     */
768
    protected function processMagicCall($propertyName, $value, $flag = '')
769
    {
770
771
        $fieldName = Property::name($propertyName)->of($this->dataType)->toFieldName();
772
773
        /** @var $matcher Matcher */
774
        $matcher = GeneralUtility::makeInstance(Matcher::class, array(), $this->getDataType());
775
776
        $table = Tca::table($this->dataType);
777
        if ($table->field($fieldName)->isGroup()) {
778
779
            $valueParts = explode('.', $value, 2);
780
            $fieldName = $fieldName . '.' . $valueParts[0];
781
            $value = $valueParts[1];
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $value. This often makes code more readable.
Loading history...
782
        }
783
784
        $matcher->equals($fieldName, $value);
785
786
        if ($flag == 'count') {
787
            $result = $this->countBy($matcher);
788
        } else {
789
            $result = $this->findBy($matcher);
790
        }
791
        return $flag == 'one' && !empty($result) ? reset($result) : $result;
792
    }
793
794
    /**
795
     * @return DataHandlerFactory
796
     */
797
    protected function getDataHandlerFactory()
798
    {
799
        return GeneralUtility::makeInstance(DataHandlerFactory::class);
800
    }
801
802
    /**
803
     * Returns whether the current mode is Backend
804
     *
805
     * @return bool
806
     */
807
    protected function isBackendMode()
808
    {
809
        return TYPO3_MODE == 'BE';
810
    }
811
812
    /**
813
     * @return FieldPathResolver
814
     */
815
    protected function getFieldPathResolver()
816
    {
817
        return GeneralUtility::makeInstance(FieldPathResolver::class);
818
    }
819
820
    /**
821
     * @return ObjectManager
822
     */
823
    protected function getObjectManager()
824
    {
825
        return GeneralUtility::makeInstance(ObjectManager::class);
826
    }
827
828
    /**
829
     * @return ContentValidator
830
     */
831
    protected function getContentValidator()
832
    {
833
        return GeneralUtility::makeInstance(ContentValidator::class);
834
    }
835
836
    /**
837
     * @return LanguageValidator
838
     */
839
    protected function getLanguageValidator()
840
    {
841
        return GeneralUtility::makeInstance(LanguageValidator::class);
842
    }
843
844
    /**
845
     * Signal that is called for post-processing the computed constraints object.
846
     *
847
     * @param Query $query
848
     * @param ConstraintInterface|null $constraints
849
     * @return ConstraintInterface|null $constraints
850
     * @signal
851
     */
852
    protected function emitPostProcessConstraintsSignal(Query $query, $constraints)
853
    {
854
        $result = $this->getSignalSlotDispatcher()->dispatch(
855
            self::class,
856
            'postProcessConstraintsObject',
857
            array(
858
                $query,
859
                $constraints
860
            )
861
        );
862
863
        return $result[1];
864
    }
865
866
    /**
867
     * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
868
     */
869
    protected function getSignalSlotDispatcher()
870
    {
871
        return $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
872
    }
873
874
}
875