[BUGFIX] Fallback to empty array if ExportController receives no input
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Query.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic;
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 TYPO3\CMS\Extbase\Persistence\QueryInterface;
18
19 /**
20 * The Query class used to run queries against the database
21 */
22 class Query implements QueryInterface
23 {
24 /**
25 * An inner join.
26 */
27 const JCR_JOIN_TYPE_INNER = '{http://www.jcp.org/jcr/1.0}joinTypeInner';
28
29 /**
30 * A left-outer join.
31 */
32 const JCR_JOIN_TYPE_LEFT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeLeftOuter';
33
34 /**
35 * A right-outer join.
36 */
37 const JCR_JOIN_TYPE_RIGHT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeRightOuter';
38
39 /**
40 * Charset of strings in QOM
41 */
42 const CHARSET = 'utf-8';
43
44 /**
45 * @var string
46 */
47 protected $type;
48
49 /**
50 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
51 */
52 protected $objectManager;
53
54 /**
55 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory
56 */
57 protected $dataMapFactory;
58
59 /**
60 * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
61 */
62 protected $persistenceManager;
63
64 /**
65 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
66 */
67 protected $qomFactory;
68
69 /**
70 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface
71 */
72 protected $source;
73
74 /**
75 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface
76 */
77 protected $constraint;
78
79 /**
80 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement
81 */
82 protected $statement;
83
84 /**
85 * @var int
86 */
87 protected $orderings = [];
88
89 /**
90 * @var int
91 */
92 protected $limit;
93
94 /**
95 * @var int
96 */
97 protected $offset;
98
99 /**
100 * The query settings.
101 *
102 * @var QuerySettingsInterface
103 */
104 protected $querySettings;
105
106 /**
107 * @var ?QueryInterface
108 * @internal
109 */
110 protected $parentQuery;
111
112 /**
113 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
114 */
115 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
116 {
117 $this->objectManager = $objectManager;
118 }
119
120 /**
121 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory
122 */
123 public function injectDataMapFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory)
124 {
125 $this->dataMapFactory = $dataMapFactory;
126 }
127
128 /**
129 * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
130 */
131 public function injectPersistenceManager(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager)
132 {
133 $this->persistenceManager = $persistenceManager;
134 }
135
136 /**
137 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory
138 */
139 public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory)
140 {
141 $this->qomFactory = $qomFactory;
142 }
143
144 /**
145 * Constructs a query object working on the given class name
146 *
147 * @param string $type
148 */
149 public function __construct($type)
150 {
151 $this->type = $type;
152 }
153
154 /**
155 * @return ?QueryInterface
156 * @internal
157 */
158 public function getParentQuery(): ?QueryInterface
159 {
160 return $this->parentQuery;
161 }
162
163 /**
164 * @param ?QueryInterface $parentQuery
165 * @internal
166 */
167 public function setParentQuery(?QueryInterface $parentQuery): void
168 {
169 $this->parentQuery = $parentQuery;
170 }
171
172 /**
173 * Sets the Query Settings. These Query settings must match the settings expected by
174 * the specific Storage Backend.
175 *
176 * @param QuerySettingsInterface $querySettings The Query Settings
177 */
178 public function setQuerySettings(QuerySettingsInterface $querySettings)
179 {
180 $this->querySettings = $querySettings;
181 }
182
183 /**
184 * Returns the Query Settings.
185 *
186 * @throws Exception
187 * @return QuerySettingsInterface $querySettings The Query Settings
188 */
189 public function getQuerySettings()
190 {
191 if (!$this->querySettings instanceof QuerySettingsInterface) {
192 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Tried to get the query settings without seting them before.', 1248689115);
193 }
194 return $this->querySettings;
195 }
196
197 /**
198 * Returns the type this query cares for.
199 *
200 * @return string
201 */
202 public function getType()
203 {
204 return $this->type;
205 }
206
207 /**
208 * Sets the source to fetch the result from
209 *
210 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source
211 */
212 public function setSource(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source)
213 {
214 $this->source = $source;
215 }
216
217 /**
218 * Returns the selectorn name or an empty string, if the source is not a selector
219 * @todo This has to be checked at another place
220 *
221 * @return string The selector name
222 */
223 protected function getSelectorName()
224 {
225 $source = $this->getSource();
226 if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
227 return $source->getSelectorName();
228 }
229 return '';
230 }
231
232 /**
233 * Gets the node-tuple source for this query.
234 *
235 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface the node-tuple source; non-null
236 */
237 public function getSource()
238 {
239 if ($this->source === null) {
240 $this->source = $this->qomFactory->selector($this->getType(), $this->dataMapFactory->buildDataMap($this->getType())->getTableName());
241 }
242 return $this->source;
243 }
244
245 /**
246 * Executes the query against the database and returns the result
247 *
248 * @param bool $returnRawQueryResult avoids the object mapping by the persistence
249 * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array The query result object or an array if $returnRawQueryResult is TRUE
250 */
251 public function execute($returnRawQueryResult = false)
252 {
253 if ($returnRawQueryResult) {
254 return $this->persistenceManager->getObjectDataByQuery($this);
255 }
256 return $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\QueryResultInterface::class, $this);
257 }
258
259 /**
260 * Sets the property names to order the result by. Expected like this:
261 * array(
262 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
263 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
264 * )
265 * where 'foo' and 'bar' are property names.
266 *
267 * @param array $orderings The property names to order by
268 * @return QueryInterface
269 */
270 public function setOrderings(array $orderings)
271 {
272 $this->orderings = $orderings;
273 return $this;
274 }
275
276 /**
277 * Returns the property names to order the result by. Like this:
278 * array(
279 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
280 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
281 * )
282 *
283 * @return int
284 */
285 public function getOrderings()
286 {
287 return $this->orderings;
288 }
289
290 /**
291 * Sets the maximum size of the result set to limit. Returns $this to allow
292 * for chaining (fluid interface)
293 *
294 * @param int $limit
295 * @throws \InvalidArgumentException
296 * @return QueryInterface
297 */
298 public function setLimit($limit)
299 {
300 if (!is_int($limit) || $limit < 1) {
301 throw new \InvalidArgumentException('The limit must be an integer >= 1', 1245071870);
302 }
303 $this->limit = $limit;
304 return $this;
305 }
306
307 /**
308 * Resets a previously set maximum size of the result set. Returns $this to allow
309 * for chaining (fluid interface)
310 *
311 * @return QueryInterface
312 */
313 public function unsetLimit()
314 {
315 unset($this->limit);
316 return $this;
317 }
318
319 /**
320 * Returns the maximum size of the result set to limit.
321 *
322 * @return int
323 */
324 public function getLimit()
325 {
326 return $this->limit;
327 }
328
329 /**
330 * Sets the start offset of the result set to offset. Returns $this to
331 * allow for chaining (fluid interface)
332 *
333 * @param int $offset
334 * @throws \InvalidArgumentException
335 * @return QueryInterface
336 */
337 public function setOffset($offset)
338 {
339 if (!is_int($offset) || $offset < 0) {
340 throw new \InvalidArgumentException('The offset must be a positive integer', 1245071872);
341 }
342 $this->offset = $offset;
343 return $this;
344 }
345
346 /**
347 * Returns the start offset of the result set.
348 *
349 * @return int
350 */
351 public function getOffset()
352 {
353 return $this->offset;
354 }
355
356 /**
357 * The constraint used to limit the result set. Returns $this to allow
358 * for chaining (fluid interface)
359 *
360 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint
361 * @return QueryInterface
362 */
363 public function matching($constraint)
364 {
365 $this->constraint = $constraint;
366 return $this;
367 }
368
369 /**
370 * Sets the statement of this query. If you use this, you will lose the abstraction from a concrete storage
371 * backend (database).
372 *
373 * @param string|\TYPO3\CMS\Core\Database\Query\QueryBuilder|\Doctrine\DBAL\Statement $statement The statement
374 * @param array $parameters An array of parameters. These will be bound to placeholders '?' in the $statement.
375 * @return QueryInterface
376 */
377 public function statement($statement, array $parameters = [])
378 {
379 $this->statement = $this->qomFactory->statement($statement, $parameters);
380 return $this;
381 }
382
383 /**
384 * Returns the statement of this query.
385 *
386 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement
387 */
388 public function getStatement()
389 {
390 return $this->statement;
391 }
392
393 /**
394 * Gets the constraint for this query.
395 *
396 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface|null the constraint, or null if none
397 */
398 public function getConstraint()
399 {
400 return $this->constraint;
401 }
402
403 /**
404 * Performs a logical conjunction of the given constraints. The method takes one or more constraints and concatenates them with a boolean AND.
405 * It also accepts a single array of constraints to be concatenated.
406 *
407 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
408 * @throws Exception\InvalidNumberOfConstraintsException
409 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
410 */
411 public function logicalAnd($constraint1)
412 {
413 if (is_array($constraint1)) {
414 $resultingConstraint = array_shift($constraint1);
415 $constraints = $constraint1;
416 } else {
417 $constraints = func_get_args();
418 $resultingConstraint = array_shift($constraints);
419 }
420 if ($resultingConstraint === null) {
421 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1268056288);
422 }
423 foreach ($constraints as $constraint) {
424 $resultingConstraint = $this->qomFactory->_and($resultingConstraint, $constraint);
425 }
426 return $resultingConstraint;
427 }
428
429 /**
430 * Performs a logical disjunction of the two given constraints
431 *
432 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
433 * @throws Exception\InvalidNumberOfConstraintsException
434 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface
435 */
436 public function logicalOr($constraint1)
437 {
438 if (is_array($constraint1)) {
439 $resultingConstraint = array_shift($constraint1);
440 $constraints = $constraint1;
441 } else {
442 $constraints = func_get_args();
443 $resultingConstraint = array_shift($constraints);
444 }
445 if ($resultingConstraint === null) {
446 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1268056289);
447 }
448 foreach ($constraints as $constraint) {
449 $resultingConstraint = $this->qomFactory->_or($resultingConstraint, $constraint);
450 }
451 return $resultingConstraint;
452 }
453
454 /**
455 * Performs a logical negation of the given constraint
456 *
457 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint Constraint to negate
458 * @throws \RuntimeException
459 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface
460 */
461 public function logicalNot(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint)
462 {
463 return $this->qomFactory->not($constraint);
464 }
465
466 /**
467 * Returns an equals criterion used for matching objects against a query
468 *
469 * @param string $propertyName The name of the property to compare against
470 * @param mixed $operand The value to compare with
471 * @param bool $caseSensitive Whether the equality test should be done case-sensitive
472 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
473 */
474 public function equals($propertyName, $operand, $caseSensitive = true)
475 {
476 if (is_object($operand) || $caseSensitive) {
477 $comparison = $this->qomFactory->comparison(
478 $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
479 QueryInterface::OPERATOR_EQUAL_TO,
480 $operand
481 );
482 } else {
483 $comparison = $this->qomFactory->comparison(
484 $this->qomFactory->lowerCase($this->qomFactory->propertyValue($propertyName, $this->getSelectorName())),
485 QueryInterface::OPERATOR_EQUAL_TO,
486 mb_strtolower($operand, \TYPO3\CMS\Extbase\Persistence\Generic\Query::CHARSET)
487 );
488 }
489 return $comparison;
490 }
491
492 /**
493 * Returns a like criterion used for matching objects against a query
494 *
495 * @param string $propertyName The name of the property to compare against
496 * @param mixed $operand The value to compare with
497 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
498 */
499 public function like($propertyName, $operand)
500 {
501 return $this->qomFactory->comparison(
502 $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
503 QueryInterface::OPERATOR_LIKE,
504 $operand
505 );
506 }
507
508 /**
509 * Returns a "contains" criterion used for matching objects against a query.
510 * It matches if the multivalued property contains the given operand.
511 *
512 * @param string $propertyName The name of the (multivalued) property to compare against
513 * @param mixed $operand The value to compare with
514 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
515 */
516 public function contains($propertyName, $operand)
517 {
518 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_CONTAINS, $operand);
519 }
520
521 /**
522 * Returns an "in" criterion used for matching objects against a query. It
523 * matches if the property's value is contained in the multivalued operand.
524 *
525 * @param string $propertyName The name of the property to compare against
526 * @param mixed $operand The value to compare with, multivalued
527 * @throws Exception\UnexpectedTypeException
528 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
529 */
530 public function in($propertyName, $operand)
531 {
532 if (!\TYPO3\CMS\Extbase\Utility\TypeHandlingUtility::isValidTypeForMultiValueComparison($operand)) {
533 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('The "in" operator must be given a multivalued operand (array, ArrayAccess, Traversable).', 1264678095);
534 }
535 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_IN, $operand);
536 }
537
538 /**
539 * Returns a less than criterion used for matching objects against a query
540 *
541 * @param string $propertyName The name of the property to compare against
542 * @param mixed $operand The value to compare with
543 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
544 */
545 public function lessThan($propertyName, $operand)
546 {
547 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN, $operand);
548 }
549
550 /**
551 * Returns a less or equal than criterion used for matching objects against a query
552 *
553 * @param string $propertyName The name of the property to compare against
554 * @param mixed $operand The value to compare with
555 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
556 */
557 public function lessThanOrEqual($propertyName, $operand)
558 {
559 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO, $operand);
560 }
561
562 /**
563 * Returns a greater than criterion used for matching objects against a query
564 *
565 * @param string $propertyName The name of the property to compare against
566 * @param mixed $operand The value to compare with
567 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
568 */
569 public function greaterThan($propertyName, $operand)
570 {
571 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN, $operand);
572 }
573
574 /**
575 * Returns a greater than or equal criterion used for matching objects against a query
576 *
577 * @param string $propertyName The name of the property to compare against
578 * @param mixed $operand The value to compare with
579 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
580 */
581 public function greaterThanOrEqual($propertyName, $operand)
582 {
583 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $operand);
584 }
585
586 /**
587 * Returns a greater than or equal criterion used for matching objects against a query
588 *
589 * @param string $propertyName The name of the property to compare against
590 * @param mixed $operandLower The value of the lower boundary to compare against
591 * @param mixed $operandUpper The value of the upper boundary to compare against
592 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
593 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException
594 */
595 public function between($propertyName, $operandLower, $operandUpper)
596 {
597 return $this->logicalAnd(
598 $this->greaterThanOrEqual($propertyName, $operandLower),
599 $this->lessThanOrEqual($propertyName, $operandUpper)
600 );
601 }
602
603 /**
604 * @internal only to be used within Extbase, not part of TYPO3 Core API.
605 */
606 public function __wakeup()
607 {
608 $this->objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
609 $this->persistenceManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface::class);
610 $this->dataMapFactory = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class);
611 $this->qomFactory = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class);
612 }
613
614 /**
615 * @return array
616 * @internal only to be used within Extbase, not part of TYPO3 Core API.
617 */
618 public function __sleep()
619 {
620 return ['type', 'source', 'constraint', 'statement', 'orderings', 'limit', 'offset', 'querySettings'];
621 }
622
623 /**
624 * Returns the query result count.
625 *
626 * @return int The query result count
627 */
628 public function count()
629 {
630 return $this->execute()->count();
631 }
632
633 /**
634 * Returns an "isEmpty" criterion used for matching objects against a query.
635 * It matches if the multivalued property contains no values or is NULL.
636 *
637 * @param string $propertyName The name of the multivalued property to compare against
638 * @throws Exception\NotImplementedException
639 */
640 public function isEmpty($propertyName)
641 {
642 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\NotImplementedException(__METHOD__, 1476122265);
643 }
644 }