[TASK] phpdoc: Use boolean/integer instead of bool/int
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Storage / Typo3DbBackend.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic\Storage;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * A Storage backend
32 */
33 class Typo3DbBackend implements \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface, \TYPO3\CMS\Core\SingletonInterface {
34
35 const OPERATOR_EQUAL_TO_NULL = 'operatorEqualToNull';
36 const OPERATOR_NOT_EQUAL_TO_NULL = 'operatorNotEqualToNull';
37
38 /**
39 * The TYPO3 database object
40 *
41 * @var \TYPO3\CMS\Core\Database\DatabaseConnection
42 */
43 protected $databaseHandle;
44
45 /**
46 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
47 * @inject
48 */
49 protected $dataMapper;
50
51 /**
52 * The TYPO3 page repository. Used for language and workspace overlay
53 *
54 * @var \TYPO3\CMS\Frontend\Page\PageRepository
55 */
56 protected $pageRepository;
57
58 /**
59 * A first-level TypoScript configuration cache
60 *
61 * @var array
62 */
63 protected $pageTSConfigCache = array();
64
65 /**
66 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
67 * @inject
68 */
69 protected $configurationManager;
70
71 /**
72 * @var \TYPO3\CMS\Extbase\Service\CacheService
73 * @inject
74 */
75 protected $cacheService;
76
77 /**
78 * @var \TYPO3\CMS\Core\Cache\CacheManager
79 * @inject
80 */
81 protected $cacheManager;
82
83 /**
84 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
85 */
86 protected $tableColumnCache;
87
88 /**
89 * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
90 * @inject
91 */
92 protected $environmentService;
93
94 /**
95 * Constructor. takes the database handle from $GLOBALS['TYPO3_DB']
96 */
97 public function __construct() {
98 $this->databaseHandle = $GLOBALS['TYPO3_DB'];
99 }
100
101 /**
102 * Lifecycle method
103 *
104 * @return void
105 */
106 public function initializeObject() {
107 $this->tableColumnCache = $this->cacheManager->getCache('extbase_typo3dbbackend_tablecolumns');
108 }
109
110 /**
111 * Adds a row to the storage
112 *
113 * @param string $tableName The database table name
114 * @param array $row The row to be inserted
115 * @param boolean $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
116 * @return integer The uid of the inserted row
117 */
118 public function addRow($tableName, array $row, $isRelation = FALSE) {
119 $fields = array();
120 $values = array();
121 $parameters = array();
122 if (isset($row['uid'])) {
123 unset($row['uid']);
124 }
125 foreach ($row as $columnName => $value) {
126 $fields[] = $columnName;
127 $values[] = '?';
128 $parameters[] = $value;
129 }
130 $sqlString = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')';
131 $this->replacePlaceholders($sqlString, $parameters, $tableName);
132 // debug($sqlString,-2);
133 $this->databaseHandle->sql_query($sqlString);
134 $this->checkSqlErrors($sqlString);
135 $uid = $this->databaseHandle->sql_insert_id();
136 if (!$isRelation) {
137 $this->clearPageCache($tableName, $uid);
138 }
139 return (integer) $uid;
140 }
141
142 /**
143 * Updates a row in the storage
144 *
145 * @param string $tableName The database table name
146 * @param array $row The row to be updated
147 * @param boolean $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
148 * @throws \InvalidArgumentException
149 * @return boolean
150 */
151 public function updateRow($tableName, array $row, $isRelation = FALSE) {
152 if (!isset($row['uid'])) {
153 throw new \InvalidArgumentException('The given row must contain a value for "uid".');
154 }
155 $uid = (integer) $row['uid'];
156 unset($row['uid']);
157 $fields = array();
158 $parameters = array();
159 foreach ($row as $columnName => $value) {
160 $fields[] = $columnName . '=?';
161 $parameters[] = $value;
162 }
163 $parameters[] = $uid;
164 $sqlString = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $fields) . ' WHERE uid=?';
165 $this->replacePlaceholders($sqlString, $parameters, $tableName);
166 // debug($sqlString,-2);
167 $returnValue = $this->databaseHandle->sql_query($sqlString);
168 $this->checkSqlErrors($sqlString);
169 if (!$isRelation) {
170 $this->clearPageCache($tableName, $uid);
171 }
172 return $returnValue;
173 }
174
175 /**
176 * Updates a relation row in the storage.
177 *
178 * @param string $tableName The database relation table name
179 * @param array $row The row to be updated
180 * @throws \InvalidArgumentException
181 * @return boolean
182 */
183 public function updateRelationTableRow($tableName, array $row) {
184 if (!isset($row['uid_local']) && !isset($row['uid_foreign'])) {
185 throw new \InvalidArgumentException(
186 'The given row must contain a value for "uid_local" and "uid_foreign".', 1360500126
187 );
188 }
189 $uidLocal = (int) $row['uid_local'];
190 $uidForeign = (int) $row['uid_foreign'];
191 unset($row['uid_local']);
192 unset($row['uid_foreign']);
193 $fields = array();
194 $parameters = array();
195 foreach ($row as $columnName => $value) {
196 $fields[] = $columnName . '=?';
197 $parameters[] = $value;
198 }
199 $parameters[] = $uidLocal;
200 $parameters[] = $uidForeign;
201
202 $sqlString = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $fields) . ' WHERE uid_local=? AND uid_foreign=?';
203 $this->replacePlaceholders($sqlString, $parameters);
204
205 $returnValue = $this->databaseHandle->sql_query($sqlString);
206 $this->checkSqlErrors($sqlString);
207
208 return $returnValue;
209 }
210
211 /**
212 * Deletes a row in the storage
213 *
214 * @param string $tableName The database table name
215 * @param array $identifier An array of identifier array('fieldname' => value). This array will be transformed to a WHERE clause
216 * @param boolean $isRelation TRUE if we are currently manipulating a relation table, FALSE by default
217 * @return boolean
218 */
219 public function removeRow($tableName, array $identifier, $isRelation = FALSE) {
220 $statement = 'DELETE FROM ' . $tableName . ' WHERE ' . $this->parseIdentifier($identifier);
221 $this->replacePlaceholders($statement, $identifier, $tableName);
222 if (!$isRelation && isset($identifier['uid'])) {
223 $this->clearPageCache($tableName, $identifier['uid'], $isRelation);
224 }
225 // debug($statement, -2);
226 $returnValue = $this->databaseHandle->sql_query($statement);
227 $this->checkSqlErrors($statement);
228 return $returnValue;
229 }
230
231 /**
232 * Fetches maximal value for given table column from database.
233 *
234 * @param string $tableName The database table name
235 * @param array $identifier An array of identifier array('fieldname' => value). This array will be transformed to a WHERE clause
236 * @param string $columnName column name to get the max value from
237 * @return mixed the max value
238 */
239 public function getMaxValueFromTable($tableName, $identifier, $columnName) {
240 $sqlString = 'SELECT ' . $columnName . ' FROM ' . $tableName . ' WHERE ' . $this->parseIdentifier($identifier) . ' ORDER BY ' . $columnName . ' DESC LIMIT 1';
241 $this->replacePlaceholders($sqlString, $identifier);
242
243 $result = $this->databaseHandle->sql_query($sqlString);
244 $row = $this->databaseHandle->sql_fetch_assoc($result);
245 $this->checkSqlErrors($sqlString);
246 return $row[$columnName];
247 }
248
249 /**
250 * Fetches row data from the database
251 *
252 * @param string $tableName
253 * @param array $identifier The Identifier of the row to fetch
254 * @return array|boolean
255 */
256 public function getRowByIdentifier($tableName, array $identifier) {
257 $statement = 'SELECT * FROM ' . $tableName . ' WHERE ' . $this->parseIdentifier($identifier);
258 $this->replacePlaceholders($statement, $identifier, $tableName);
259 // debug($statement,-2);
260 $res = $this->databaseHandle->sql_query($statement);
261 $this->checkSqlErrors($statement);
262 $row = $this->databaseHandle->sql_fetch_assoc($res);
263 if ($row !== FALSE) {
264 return $row;
265 } else {
266 return FALSE;
267 }
268 }
269
270 /**
271 * @param array $identifier
272 * @return string
273 */
274 protected function parseIdentifier(array $identifier) {
275 $fieldNames = array_keys($identifier);
276 $suffixedFieldNames = array();
277 foreach ($fieldNames as $fieldName) {
278 $suffixedFieldNames[] = $fieldName . '=?';
279 }
280 return implode(' AND ', $suffixedFieldNames);
281 }
282
283 /**
284 * Returns the object data matching the $query.
285 *
286 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
287 * @return array
288 */
289 public function getObjectDataByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query) {
290 $statement = $query->getStatement();
291 if ($statement instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement) {
292 $sql = $statement->getStatement();
293 $parameters = $statement->getBoundVariables();
294 } else {
295 $parameters = array();
296 $statementParts = $this->parseQuery($query, $parameters);
297 $sql = $this->buildQuery($statementParts, $parameters);
298 }
299 $tableName = 'foo';
300 if (is_array($statementParts) && !empty($statementParts['tables'][0])) {
301 $tableName = $statementParts['tables'][0];
302 }
303 $this->replacePlaceholders($sql, $parameters, $tableName);
304 // debug($sql,-2);
305 $result = $this->databaseHandle->sql_query($sql);
306 $this->checkSqlErrors($sql);
307 $rows = $this->getRowsFromResult($result);
308 $this->databaseHandle->sql_free_result($result);
309 // Get language uid from querySettings.
310 // Ensure the backend handling is not broken (fallback to Get parameter 'L' if needed)
311 $rows = $this->doLanguageAndWorkspaceOverlay($query->getSource(), $rows, $query->getQuerySettings());
312 // TODO: implement $objectData = $this->processObjectRecords($statementHandle);
313 return $rows;
314 }
315
316 /**
317 * Returns the number of tuples matching the query.
318 *
319 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
320 * @throws Exception\BadConstraintException
321 * @return integer The number of matching tuples
322 */
323 public function getObjectCountByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query) {
324 $constraint = $query->getConstraint();
325 if ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement) {
326 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\BadConstraintException('Could not execute count on queries with a constraint of type TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Qom\\StatementInterface', 1256661045);
327 }
328 $parameters = array();
329 $statementParts = $this->parseQuery($query, $parameters);
330 // Reset $statementParts for valid table return
331 reset($statementParts);
332 // if limit is set, we need to count the rows "manually" as COUNT(*) ignores LIMIT constraints
333 if (!empty($statementParts['limit'])) {
334 $statement = $this->buildQuery($statementParts, $parameters);
335 $this->replacePlaceholders($statement, $parameters, current($statementParts['tables']));
336 $result = $this->databaseHandle->sql_query($statement);
337 $this->checkSqlErrors($statement);
338 $count = $this->databaseHandle->sql_num_rows($result);
339 } else {
340 $statementParts['fields'] = array('COUNT(*)');
341 // having orderings without grouping is not compatible with non-MySQL DBMS
342 $statementParts['orderings'] = array();
343 if (isset($statementParts['keywords']['distinct'])) {
344 unset($statementParts['keywords']['distinct']);
345 $statementParts['fields'] = array('COUNT(DISTINCT ' . reset($statementParts['tables']) . '.uid)');
346 }
347 $statement = $this->buildQuery($statementParts, $parameters);
348 $this->replacePlaceholders($statement, $parameters, current($statementParts['tables']));
349 $result = $this->databaseHandle->sql_query($statement);
350 $this->checkSqlErrors($statement);
351 $rows = $this->getRowsFromResult($result);
352 $count = current(current($rows));
353 }
354 $this->databaseHandle->sql_free_result($result);
355 return (integer) $count;
356 }
357
358 /**
359 * Parses the query and returns the SQL statement parts.
360 *
361 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query The query
362 * @param array &$parameters
363 * @return array The SQL statement parts
364 */
365 public function parseQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query, array &$parameters) {
366 $sql = array();
367 $sql['keywords'] = array();
368 $sql['tables'] = array();
369 $sql['unions'] = array();
370 $sql['fields'] = array();
371 $sql['where'] = array();
372 $sql['additionalWhereClause'] = array();
373 $sql['orderings'] = array();
374 $sql['limit'] = array();
375 $source = $query->getSource();
376 $this->parseSource($source, $sql);
377 $this->parseConstraint($query->getConstraint(), $source, $sql, $parameters);
378 $this->parseOrderings($query->getOrderings(), $source, $sql);
379 $this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $sql);
380 $tableNames = array_unique(array_keys($sql['tables'] + $sql['unions']));
381 foreach ($tableNames as $tableName) {
382 if (is_string($tableName) && strlen($tableName) > 0) {
383 $this->addAdditionalWhereClause($query->getQuerySettings(), $tableName, $sql);
384 }
385 }
386 return $sql;
387 }
388
389 /**
390 * Returns the statement, ready to be executed.
391 *
392 * @param array $sql The SQL statement parts
393 * @return string The SQL statement
394 */
395 public function buildQuery(array $sql) {
396 $statement = 'SELECT ' . implode(' ', $sql['keywords']) . ' ' . implode(',', $sql['fields']) . ' FROM ' . implode(' ', $sql['tables']) . ' ' . implode(' ', $sql['unions']);
397 if (!empty($sql['where'])) {
398 $statement .= ' WHERE ' . implode('', $sql['where']);
399 if (!empty($sql['additionalWhereClause'])) {
400 $statement .= ' AND ' . implode(' AND ', $sql['additionalWhereClause']);
401 }
402 } elseif (!empty($sql['additionalWhereClause'])) {
403 $statement .= ' WHERE ' . implode(' AND ', $sql['additionalWhereClause']);
404 }
405 if (!empty($sql['orderings'])) {
406 $statement .= ' ORDER BY ' . implode(', ', $sql['orderings']);
407 }
408 if (!empty($sql['limit'])) {
409 $statement .= ' LIMIT ' . $sql['limit'];
410 }
411 return $statement;
412 }
413
414 /**
415 * Checks if a Value Object equal to the given Object exists in the data base
416 *
417 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object The Value Object
418 * @return mixed The matching uid if an object was found, else FALSE
419 */
420 public function getUidOfAlreadyPersistedValueObject(\TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object) {
421 $fields = array();
422 $parameters = array();
423 $dataMap = $this->dataMapper->getDataMap(get_class($object));
424 $properties = $object->_getProperties();
425 foreach ($properties as $propertyName => $propertyValue) {
426 // FIXME We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method
427 if ($dataMap->isPersistableProperty($propertyName) && $propertyName !== 'uid' && $propertyName !== 'pid' && $propertyName !== 'isClone') {
428 if ($propertyValue === NULL) {
429 $fields[] = $dataMap->getColumnMap($propertyName)->getColumnName() . ' IS NULL';
430 } else {
431 $fields[] = $dataMap->getColumnMap($propertyName)->getColumnName() . '=?';
432 $parameters[] = $this->getPlainValue($propertyValue);
433 }
434 }
435 }
436 $sql = array();
437 $sql['additionalWhereClause'] = array();
438 $tableName = $dataMap->getTableName();
439 $this->addVisibilityConstraintStatement(new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings(), $tableName, $sql);
440 $statement = 'SELECT * FROM ' . $tableName;
441 $statement .= ' WHERE ' . implode(' AND ', $fields);
442 if (!empty($sql['additionalWhereClause'])) {
443 $statement .= ' AND ' . implode(' AND ', $sql['additionalWhereClause']);
444 }
445 $this->replacePlaceholders($statement, $parameters, $tableName);
446 // debug($statement,-2);
447 $res = $this->databaseHandle->sql_query($statement);
448 $this->checkSqlErrors($statement);
449 $row = $this->databaseHandle->sql_fetch_assoc($res);
450 if ($row !== FALSE) {
451 return (integer) $row['uid'];
452 } else {
453 return FALSE;
454 }
455 }
456
457 /**
458 * Transforms a Query Source into SQL and parameter arrays
459 *
460 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
461 * @param array &$sql
462 * @return void
463 */
464 protected function parseSource(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql) {
465 if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
466 $className = $source->getNodeTypeName();
467 $tableName = $this->dataMapper->getDataMap($className)->getTableName();
468 $this->addRecordTypeConstraint($className, $sql);
469 $sql['fields'][$tableName] = $tableName . '.*';
470 $sql['tables'][$tableName] = $tableName;
471 } elseif ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
472 $this->parseJoin($source, $sql);
473 }
474 }
475
476 /**
477 * Add a constraint to ensure that the record type of the returned tuples is matching the data type of the repository.
478 *
479 * @param string $className The class name
480 * @param array &$sql The query parts
481 * @return void
482 */
483 protected function addRecordTypeConstraint($className, &$sql) {
484 if ($className !== NULL) {
485 $dataMap = $this->dataMapper->getDataMap($className);
486 if ($dataMap->getRecordTypeColumnName() !== NULL) {
487 $recordTypes = array();
488 if ($dataMap->getRecordType() !== NULL) {
489 $recordTypes[] = $dataMap->getRecordType();
490 }
491 foreach ($dataMap->getSubclasses() as $subclassName) {
492 $subclassDataMap = $this->dataMapper->getDataMap($subclassName);
493 if ($subclassDataMap->getRecordType() !== NULL) {
494 $recordTypes[] = $subclassDataMap->getRecordType();
495 }
496 }
497 if (count($recordTypes) > 0) {
498 $recordTypeStatements = array();
499 foreach ($recordTypes as $recordType) {
500 $tableName = $dataMap->getTableName();
501 $recordTypeStatements[] = $tableName . '.' . $dataMap->getRecordTypeColumnName() . '=' . $this->databaseHandle->fullQuoteStr($recordType, $tableName);
502 }
503 $sql['additionalWhereClause'][] = '(' . implode(' OR ', $recordTypeStatements) . ')';
504 }
505 }
506 }
507 }
508
509 /**
510 * Transforms a Join into SQL and parameter arrays
511 *
512 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface $join The join
513 * @param array &$sql The query parts
514 * @return void
515 */
516 protected function parseJoin(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface $join, array &$sql) {
517 $leftSource = $join->getLeft();
518 $leftClassName = $leftSource->getNodeTypeName();
519 $this->addRecordTypeConstraint($leftClassName, $sql);
520 $leftTableName = $leftSource->getSelectorName();
521 // $sql['fields'][$leftTableName] = $leftTableName . '.*';
522 $rightSource = $join->getRight();
523 if ($rightSource instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
524 $rightClassName = $rightSource->getLeft()->getNodeTypeName();
525 $rightTableName = $rightSource->getLeft()->getSelectorName();
526 } else {
527 $rightClassName = $rightSource->getNodeTypeName();
528 $rightTableName = $rightSource->getSelectorName();
529 $sql['fields'][$leftTableName] = $rightTableName . '.*';
530 }
531 $this->addRecordTypeConstraint($rightClassName, $sql);
532 $sql['tables'][$leftTableName] = $leftTableName;
533 $sql['unions'][$rightTableName] = 'LEFT JOIN ' . $rightTableName;
534 $joinCondition = $join->getJoinCondition();
535 if ($joinCondition instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\EquiJoinCondition) {
536 $column1Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty1Name(), $leftClassName);
537 $column2Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty2Name(), $rightClassName);
538 $sql['unions'][$rightTableName] .= ' ON ' . $joinCondition->getSelector1Name() . '.' . $column1Name . ' = ' . $joinCondition->getSelector2Name() . '.' . $column2Name;
539 }
540 if ($rightSource instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
541 $this->parseJoin($rightSource, $sql);
542 }
543 }
544
545 /**
546 * Transforms a constraint into SQL and parameter arrays
547 *
548 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint The constraint
549 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
550 * @param array &$sql The query parts
551 * @param array &$parameters The parameters that will replace the markers
552 * @return void
553 */
554 protected function parseConstraint(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint = NULL, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql, array &$parameters) {
555 if ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface) {
556 $sql['where'][] = '(';
557 $this->parseConstraint($constraint->getConstraint1(), $source, $sql, $parameters);
558 $sql['where'][] = ' AND ';
559 $this->parseConstraint($constraint->getConstraint2(), $source, $sql, $parameters);
560 $sql['where'][] = ')';
561 } elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface) {
562 $sql['where'][] = '(';
563 $this->parseConstraint($constraint->getConstraint1(), $source, $sql, $parameters);
564 $sql['where'][] = ' OR ';
565 $this->parseConstraint($constraint->getConstraint2(), $source, $sql, $parameters);
566 $sql['where'][] = ')';
567 } elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface) {
568 $sql['where'][] = 'NOT (';
569 $this->parseConstraint($constraint->getConstraint(), $source, $sql, $parameters);
570 $sql['where'][] = ')';
571 } elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface) {
572 $this->parseComparison($constraint, $source, $sql, $parameters);
573 }
574 }
575
576 /**
577 * Parse a Comparison into SQL and parameter arrays.
578 *
579 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface $comparison The comparison to parse
580 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
581 * @param array &$sql SQL query parts to add to
582 * @param array &$parameters Parameters to bind to the SQL
583 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException
584 * @return void
585 */
586 protected function parseComparison(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface $comparison, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql, array &$parameters) {
587 $operand1 = $comparison->getOperand1();
588 $operator = $comparison->getOperator();
589 $operand2 = $comparison->getOperand2();
590 if ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_IN) {
591 $items = array();
592 $hasValue = FALSE;
593 foreach ($operand2 as $value) {
594 $value = $this->getPlainValue($value);
595 if ($value !== NULL) {
596 $items[] = $value;
597 $hasValue = TRUE;
598 }
599 }
600 if ($hasValue === FALSE) {
601 $sql['where'][] = '1<>1';
602 } else {
603 $this->parseDynamicOperand($operand1, $operator, $source, $sql, $parameters, NULL, $operand2);
604 $parameters[] = $items;
605 }
606 } elseif ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_CONTAINS) {
607 if ($operand2 === NULL) {
608 $sql['where'][] = '1<>1';
609 } else {
610 $className = $source->getNodeTypeName();
611 $tableName = $this->dataMapper->convertClassNameToTableName($className);
612 $propertyName = $operand1->getPropertyName();
613 while (strpos($propertyName, '.') !== FALSE) {
614 $this->addUnionStatement($className, $tableName, $propertyName, $sql);
615 }
616 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
617 $dataMap = $this->dataMapper->getDataMap($className);
618 $columnMap = $dataMap->getColumnMap($propertyName);
619 $typeOfRelation = $columnMap instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap ? $columnMap->getTypeOfRelation() : NULL;
620 if ($typeOfRelation === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
621 $relationTableName = $columnMap->getRelationTableName();
622 $sql['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=?)';
623 $parameters[] = intval($this->getPlainValue($operand2));
624 } elseif ($typeOfRelation === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
625 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
626 if (isset($parentKeyFieldName)) {
627 $childTableName = $columnMap->getChildTableName();
628 $sql['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=?)';
629 $parameters[] = intval($this->getPlainValue($operand2));
630 } else {
631 $sql['where'][] = 'FIND_IN_SET(?,' . $tableName . '.' . $columnName . ')';
632 $parameters[] = intval($this->getPlainValue($operand2));
633 }
634 } else {
635 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
636 }
637 }
638 } else {
639 if ($operand2 === NULL) {
640 if ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_EQUAL_TO) {
641 $operator = self::OPERATOR_EQUAL_TO_NULL;
642 } elseif ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_NOT_EQUAL_TO) {
643 $operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
644 }
645 }
646 $this->parseDynamicOperand($operand1, $operator, $source, $sql, $parameters);
647 $parameters[] = $this->getPlainValue($operand2);
648 }
649 }
650
651 /**
652 * Returns a plain value, i.e. objects are flattened out if possible.
653 *
654 * @param mixed $input
655 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException
656 * @return mixed
657 */
658 protected function getPlainValue($input) {
659 if (is_array($input)) {
660 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An array could not be converted to a plain value.', 1274799932);
661 }
662 if ($input instanceof \DateTime) {
663 return $input->format('U');
664 } elseif (is_object($input)) {
665 if ($input instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
666 $realInput = $input->_loadRealInstance();
667 } else {
668 $realInput = $input;
669 }
670 if ($realInput instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
671 return $realInput->getUid();
672 } else {
673 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An object of class "' . get_class($realInput) . '" could not be converted to a plain value.', 1274799934);
674 }
675 } elseif (is_bool($input)) {
676 return $input === TRUE ? 1 : 0;
677 } else {
678 return $input;
679 }
680 }
681
682 /**
683 * Parse a DynamicOperand into SQL and parameter arrays.
684 *
685 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\DynamicOperandInterface $operand
686 * @param string $operator One of the JCR_OPERATOR_* constants
687 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
688 * @param array &$sql The query parts
689 * @param array &$parameters The parameters that will replace the markers
690 * @param string $valueFunction an optional SQL function to apply to the operand value
691 * @param null $operand2
692 * @return void
693 */
694 protected function parseDynamicOperand(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\DynamicOperandInterface $operand, $operator, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql, array &$parameters, $valueFunction = NULL, $operand2 = NULL) {
695 if ($operand instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\LowerCaseInterface) {
696 $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, $parameters, 'LOWER');
697 } elseif ($operand instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\UpperCaseInterface) {
698 $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, $parameters, 'UPPER');
699 } elseif ($operand instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\PropertyValueInterface) {
700 $propertyName = $operand->getPropertyName();
701 if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
702 // FIXME Only necessary to differ from Join
703 $className = $source->getNodeTypeName();
704 $tableName = $this->dataMapper->convertClassNameToTableName($className);
705 while (strpos($propertyName, '.') !== FALSE) {
706 $this->addUnionStatement($className, $tableName, $propertyName, $sql);
707 }
708 } elseif ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
709 $tableName = $source->getJoinCondition()->getSelector1Name();
710 }
711 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
712 $operator = $this->resolveOperator($operator);
713 $constraintSQL = '';
714 if ($valueFunction === NULL) {
715 $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $operator . ' ?';
716 } else {
717 $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $operator . ' ?';
718 }
719 $sql['where'][] = $constraintSQL;
720 }
721 }
722
723 /**
724 * @param string &$className
725 * @param string &$tableName
726 * @param array &$propertyPath
727 * @param array &$sql
728 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception
729 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException
730 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\MissingColumnMapException
731 */
732 protected function addUnionStatement(&$className, &$tableName, &$propertyPath, array &$sql) {
733 $explodedPropertyPath = explode('.', $propertyPath, 2);
734 $propertyName = $explodedPropertyPath[0];
735 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
736 $tableName = $this->dataMapper->convertClassNameToTableName($className);
737 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
738
739 if ($columnMap === NULL) {
740 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\MissingColumnMapException('The ColumnMap for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1355142232);
741 }
742
743 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
744 $childTableName = $columnMap->getChildTableName();
745
746 if ($childTableName === NULL) {
747 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException('The relation information for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1353170925);
748 }
749
750 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_ONE) {
751 if (isset($parentKeyFieldName)) {
752 $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
753 } else {
754 $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $columnName . '=' . $childTableName . '.uid';
755 }
756 $className = $this->dataMapper->getType($className, $propertyName);
757 } elseif ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
758 if (isset($parentKeyFieldName)) {
759 $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
760 } else {
761 $onStatement = '(FIND_IN_SET(' . $childTableName . '.uid, ' . $tableName . '.' . $columnName . '))';
762 $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $onStatement;
763 }
764 $className = $this->dataMapper->getType($className, $propertyName);
765 } elseif ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
766 $relationTableName = $columnMap->getRelationTableName();
767 $sql['unions'][$relationTableName] = 'LEFT JOIN ' . $relationTableName . ' ON ' . $tableName . '.uid=' . $relationTableName . '.' . $columnMap->getParentKeyFieldName();
768 $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $relationTableName . '.' . $columnMap->getChildKeyFieldName() . '=' . $childTableName . '.uid';
769 $className = $this->dataMapper->getType($className, $propertyName);
770 } else {
771 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Could not determine type of relation.', 1252502725);
772 }
773 // TODO check if there is another solution for this
774 $sql['keywords']['distinct'] = 'DISTINCT';
775 $propertyPath = $explodedPropertyPath[1];
776 $tableName = $childTableName;
777 }
778
779 /**
780 * Returns the SQL operator for the given JCR operator type.
781 *
782 * @param string $operator One of the JCR_OPERATOR_* constants
783 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception
784 * @return string an SQL operator
785 */
786 protected function resolveOperator($operator) {
787 switch ($operator) {
788 case self::OPERATOR_EQUAL_TO_NULL:
789 $operator = 'IS';
790 break;
791 case self::OPERATOR_NOT_EQUAL_TO_NULL:
792 $operator = 'IS NOT';
793 break;
794 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_IN:
795 $operator = 'IN';
796 break;
797 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_EQUAL_TO:
798 $operator = '=';
799 break;
800 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_NOT_EQUAL_TO:
801 $operator = '!=';
802 break;
803 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_LESS_THAN:
804 $operator = '<';
805 break;
806 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO:
807 $operator = '<=';
808 break;
809 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_GREATER_THAN:
810 $operator = '>';
811 break;
812 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO:
813 $operator = '>=';
814 break;
815 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_LIKE:
816 $operator = 'LIKE';
817 break;
818 default:
819 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Unsupported operator encountered.', 1242816073);
820 }
821 return $operator;
822 }
823
824 /**
825 * Replace query placeholders in a query part by the given
826 * parameters.
827 *
828 * @param string &$sqlString The query part with placeholders
829 * @param array $parameters The parameters
830 * @param string $tableName
831 *
832 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception
833 */
834 protected function replacePlaceholders(&$sqlString, array $parameters, $tableName = 'foo') {
835 // TODO profile this method again
836 if (substr_count($sqlString, '?') !== count($parameters)) {
837 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('The number of question marks to replace must be equal to the number of parameters.', 1242816074);
838 }
839 $offset = 0;
840 foreach ($parameters as $parameter) {
841 $markPosition = strpos($sqlString, '?', $offset);
842 if ($markPosition !== FALSE) {
843 if ($parameter === NULL) {
844 $parameter = 'NULL';
845 } elseif (is_array($parameter) || $parameter instanceof \ArrayAccess || $parameter instanceof \Traversable) {
846 $items = array();
847 foreach ($parameter as $item) {
848 $items[] = $this->databaseHandle->fullQuoteStr($item, $tableName);
849 }
850 $parameter = '(' . implode(',', $items) . ')';
851 } else {
852 $parameter = $this->databaseHandle->fullQuoteStr($parameter, $tableName);
853 }
854 $sqlString = substr($sqlString, 0, $markPosition) . $parameter . substr($sqlString, ($markPosition + 1));
855 }
856 $offset = $markPosition + strlen($parameter);
857 }
858 }
859
860 /**
861 * Adds additional WHERE statements according to the query settings.
862 *
863 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
864 * @param string $tableName The table name to add the additional where clause for
865 * @param string &$sql
866 * @return void
867 */
868 protected function addAdditionalWhereClause(\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings, $tableName, &$sql) {
869 $this->addVisibilityConstraintStatement($querySettings, $tableName, $sql);
870 if ($querySettings->getRespectSysLanguage()) {
871 $this->addSysLanguageStatement($tableName, $sql, $querySettings);
872 }
873 if ($querySettings->getRespectStoragePage()) {
874 $this->addPageIdStatement($tableName, $sql, $querySettings->getStoragePageIds());
875 }
876 }
877
878 /**
879 * Builds the enable fields statement
880 *
881 * @param string $tableName The database table name
882 * @param array &$sql The query parts
883 * @return void
884 * @deprecated since Extbase 6.0, will be removed in Extbase 6.2.
885 */
886 protected function addEnableFieldsStatement($tableName, array &$sql) {
887 \TYPO3\CMS\Core\Utility\GeneralUtility::logDeprecatedFunction();
888 if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
889 if ($this->environmentService->isEnvironmentInFrontendMode()) {
890 $statement = $this->getPageRepository()->enableFields($tableName);
891 } else {
892 // TYPO3_MODE === 'BE'
893 $statement = \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($tableName);
894 $statement .= \TYPO3\CMS\Backend\Utility\BackendUtility::BEenableFields($tableName);
895 }
896 if (!empty($statement)) {
897 $statement = substr($statement, 5);
898 $sql['additionalWhereClause'][] = $statement;
899 }
900 }
901 }
902
903 /**
904 * Adds enableFields and deletedClause to the query if necessary
905 *
906 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings
907 * @param string $tableName The database table name
908 * @param array &$sql The query parts
909 * @return void
910 */
911 protected function addVisibilityConstraintStatement(\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings, $tableName, array &$sql) {
912 $statement = '';
913 if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
914 $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
915 $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
916 $includeDeleted = $querySettings->getIncludeDeleted();
917 if ($this->environmentService->isEnvironmentInFrontendMode()) {
918 $statement .= $this->getFrontendConstraintStatement($tableName, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
919 } else {
920 // TYPO3_MODE === 'BE'
921 $statement .= $this->getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted);
922 }
923 if (!empty($statement)) {
924 $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
925 $sql['additionalWhereClause'][] = $statement;
926 }
927 }
928 }
929
930 /**
931 * Returns constraint statement for frontend context
932 *
933 * @param string $tableName
934 * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
935 * @param array $enableFieldsToBeIgnored If $ignoreEnableFields is true, this array specifies enable fields to be ignored. If it is NULL or an empty array (default) all enable fields are ignored.
936 * @param boolean $includeDeleted A flag indicating whether deleted records should be included
937 * @return string
938 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException
939 */
940 protected function getFrontendConstraintStatement($tableName, $ignoreEnableFields, $enableFieldsToBeIgnored = array(), $includeDeleted) {
941 $statement = '';
942 if ($ignoreEnableFields && !$includeDeleted) {
943 if (count($enableFieldsToBeIgnored)) {
944 // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
945 $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
946 } else {
947 $statement .= $this->getPageRepository()->deleteClause($tableName);
948 }
949 } elseif (!$ignoreEnableFields && !$includeDeleted) {
950 $statement .= $this->getPageRepository()->enableFields($tableName);
951 } elseif (!$ignoreEnableFields && $includeDeleted) {
952 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException('Query setting "ignoreEnableFields=FALSE" can not be used together with "includeDeleted=TRUE" in frontend context.', 1327678173);
953 }
954 return $statement;
955 }
956
957 /**
958 * Returns constraint statement for backend context
959 *
960 * @param string $tableName
961 * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
962 * @param boolean $includeDeleted A flag indicating whether deleted records should be included
963 * @return string
964 */
965 protected function getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted) {
966 $statement = '';
967 if (!$ignoreEnableFields) {
968 $statement .= \TYPO3\CMS\Backend\Utility\BackendUtility::BEenableFields($tableName);
969 }
970 if (!$includeDeleted) {
971 $statement .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($tableName);
972 }
973 return $statement;
974 }
975
976 /**
977 * Builds the language field statement
978 *
979 * @param string $tableName The database table name
980 * @param array &$sql The query parts
981 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
982 * @return void
983 */
984 protected function addSysLanguageStatement($tableName, array &$sql, $querySettings) {
985 if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
986 if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
987 // Select all entries for the current language
988 $additionalWhereClause = $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getSysLanguageUid()) . ',-1)';
989 // If any language is set -> get those entries which are not translated yet
990 // They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
991 if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
992 && $querySettings->getSysLanguageUid() > 0
993 ) {
994 $additionalWhereClause .= ' OR (' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
995 ' AND ' . $tableName . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
996 ' FROM ' . $tableName .
997 ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
998 ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
999
1000 // Add delete clause to ensure all entries are loaded
1001 if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
1002 $additionalWhereClause .= ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
1003 }
1004 $additionalWhereClause .= '))';
1005 }
1006 $sql['additionalWhereClause'][] = '(' . $additionalWhereClause . ')';
1007 }
1008 }
1009 }
1010
1011 /**
1012 * Builds the page ID checking statement
1013 *
1014 * @param string $tableName The database table name
1015 * @param array &$sql The query parts
1016 * @param array $storagePageIds list of storage page ids
1017 * @return void
1018 */
1019 protected function addPageIdStatement($tableName, array &$sql, array $storagePageIds) {
1020 $tableColumns = $this->tableColumnCache->get($tableName);
1021 if ($tableColumns === FALSE) {
1022 $tableColumns = $this->databaseHandle->admin_get_fields($tableName);
1023 $this->tableColumnCache->set($tableName, $tableColumns);
1024 }
1025 if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('pid', $tableColumns)) {
1026 $rootLevel = (int)$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'];
1027 if ($rootLevel) {
1028 if ($rootLevel === 1) {
1029 $sql['additionalWhereClause'][] = $tableName . '.pid = 0';
1030 }
1031 } else {
1032 if (empty($storagePageIds)) {
1033 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException('Missing storage page ids.', 1365779762);
1034 }
1035 $sql['additionalWhereClause'][] = $tableName . '.pid IN (' . implode(', ', $storagePageIds) . ')';
1036 }
1037 }
1038 }
1039
1040 /**
1041 * Transforms orderings into SQL.
1042 *
1043 * @param array $orderings An array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
1044 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
1045 * @param array &$sql The query parts
1046 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException
1047 * @return void
1048 */
1049 protected function parseOrderings(array $orderings, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql) {
1050 foreach ($orderings as $propertyName => $order) {
1051 switch ($order) {
1052 case \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelConstantsInterface::JCR_ORDER_ASCENDING:
1053
1054 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING:
1055 $order = 'ASC';
1056 break;
1057 case \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelConstantsInterface::JCR_ORDER_DESCENDING:
1058
1059 case \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING:
1060 $order = 'DESC';
1061 break;
1062 default:
1063 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException('Unsupported order encountered.', 1242816074);
1064 }
1065 $className = '';
1066 $tableName = '';
1067 if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
1068 $className = $source->getNodeTypeName();
1069 $tableName = $this->dataMapper->convertClassNameToTableName($className);
1070 while (strpos($propertyName, '.') !== FALSE) {
1071 $this->addUnionStatement($className, $tableName, $propertyName, $sql);
1072 }
1073 } elseif ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
1074 $tableName = $source->getLeft()->getSelectorName();
1075 }
1076 $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
1077 if (strlen($tableName) > 0) {
1078 $sql['orderings'][] = $tableName . '.' . $columnName . ' ' . $order;
1079 } else {
1080 $sql['orderings'][] = $columnName . ' ' . $order;
1081 }
1082 }
1083 }
1084
1085 /**
1086 * Transforms limit and offset into SQL
1087 *
1088 * @param integer $limit
1089 * @param integer $offset
1090 * @param array &$sql
1091 * @return void
1092 */
1093 protected function parseLimitAndOffset($limit, $offset, array &$sql) {
1094 if ($limit !== NULL && $offset !== NULL) {
1095 $sql['limit'] = intval($offset) . ', ' . intval($limit);
1096 } elseif ($limit !== NULL) {
1097 $sql['limit'] = intval($limit);
1098 }
1099 }
1100
1101 /**
1102 * Transforms a Resource from a database query to an array of rows.
1103 *
1104 * @param resource $result The result
1105 * @return array The result as an array of rows (tuples)
1106 */
1107 protected function getRowsFromResult($result) {
1108 $rows = array();
1109 while ($row = $this->databaseHandle->sql_fetch_assoc($result)) {
1110 if (is_array($row)) {
1111 $rows[] = $row;
1112 }
1113 }
1114 return $rows;
1115 }
1116
1117 /**
1118 * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
1119 * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
1120 *
1121 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source (selector od join)
1122 * @param array $rows
1123 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
1124 * @param null|integer $workspaceUid
1125 * @return array
1126 */
1127 protected function doLanguageAndWorkspaceOverlay(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array $rows, $querySettings, $workspaceUid = NULL) {
1128 if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
1129 $tableName = $source->getSelectorName();
1130 } elseif ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
1131 $tableName = $source->getRight()->getSelectorName();
1132 }
1133 // If we do not have a table name here, we cannot do an overlay and return the original rows instead.
1134 if (isset($tableName)) {
1135 $pageRepository = $this->getPageRepository();
1136 if (is_object($GLOBALS['TSFE'])) {
1137 $languageMode = $GLOBALS['TSFE']->sys_language_mode;
1138 if ($workspaceUid !== NULL) {
1139 $pageRepository->versioningWorkspaceId = $workspaceUid;
1140 }
1141 } else {
1142 $languageMode = '';
1143 if ($workspaceUid === NULL) {
1144 $workspaceUid = $GLOBALS['BE_USER']->workspace;
1145 }
1146 $pageRepository->versioningWorkspaceId = $workspaceUid;
1147 }
1148
1149 $overlayedRows = array();
1150 foreach ($rows as $row) {
1151 // If current row is a translation select its parent
1152 if ($querySettings->getRespectSysLanguage()
1153 && isset($tableName) && isset($GLOBALS['TCA'][$tableName])
1154 && isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
1155 && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
1156 ) {
1157 if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
1158 && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
1159 ) {
1160 $row = $this->databaseHandle->exec_SELECTgetSingleRow(
1161 $tableName . '.*',
1162 $tableName,
1163 $tableName . '.uid=' . (integer) $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] .
1164 ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0'
1165 );
1166 }
1167 }
1168 $pageRepository->versionOL($tableName, $row, TRUE);
1169 if ($pageRepository->versioningPreview && isset($row['_ORIG_uid'])) {
1170 $row['uid'] = $row['_ORIG_uid'];
1171 }
1172 if ($querySettings->getRespectSysLanguage()) {
1173 if ($tableName == 'pages') {
1174 $row = $pageRepository->getPageOverlay($row, $querySettings->getSysLanguageUid());
1175 } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
1176 && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
1177 ) {
1178 if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], array(-1, 0))) {
1179 $overlayMode = $languageMode === 'strict' ? 'hideNonTranslated' : '';
1180 $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getSysLanguageUid(), $overlayMode);
1181 }
1182 }
1183 }
1184 if ($row !== NULL && is_array($row)) {
1185 $overlayedRows[] = $row;
1186 }
1187 }
1188 } else {
1189 $overlayedRows = $rows;
1190 }
1191 return $overlayedRows;
1192 }
1193
1194 /**
1195 * @return \TYPO3\CMS\Frontend\Page\PageRepository
1196 */
1197 protected function getPageRepository() {
1198 if (!$this->pageRepository instanceof \TYPO3\CMS\Frontend\Page\PageRepository) {
1199 if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
1200 $this->pageRepository = $GLOBALS['TSFE']->sys_page;
1201 } else {
1202 $this->pageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository');
1203 }
1204 }
1205
1206 return $this->pageRepository;
1207 }
1208
1209 /**
1210 * Checks if there are SQL errors in the last query, and if yes, throw an exception.
1211 *
1212 * @return void
1213 * @param string $sql The SQL statement
1214 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\SqlErrorException
1215 */
1216 protected function checkSqlErrors($sql = '') {
1217 $error = $this->databaseHandle->sql_error();
1218 if ($error !== '') {
1219 $error .= $sql ? ': ' . $sql : '';
1220 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\SqlErrorException($error, 1247602160);
1221 }
1222 }
1223
1224 /**
1225 * Clear the TYPO3 page cache for the given record.
1226 * If the record lies on a page, then we clear the cache of this page.
1227 * If the record has no PID column, we clear the cache of the current page as best-effort.
1228 *
1229 * Much of this functionality is taken from t3lib_tcemain::clear_cache() which unfortunately only works with logged-in BE user.
1230 *
1231 * @param string $tableName Table name of the record
1232 * @param integer $uid UID of the record
1233 * @return void
1234 */
1235 protected function clearPageCache($tableName, $uid) {
1236 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1237 if (isset($frameworkConfiguration['persistence']['enableAutomaticCacheClearing']) && $frameworkConfiguration['persistence']['enableAutomaticCacheClearing'] === '1') {
1238 } else {
1239 // if disabled, return
1240 return;
1241 }
1242 $pageIdsToClear = array();
1243 $storagePage = NULL;
1244 $columns = $this->databaseHandle->admin_get_fields($tableName);
1245 if (array_key_exists('pid', $columns)) {
1246 $result = $this->databaseHandle->exec_SELECTquery('pid', $tableName, 'uid=' . intval($uid));
1247 if ($row = $this->databaseHandle->sql_fetch_assoc($result)) {
1248 $storagePage = $row['pid'];
1249 $pageIdsToClear[] = $storagePage;
1250 }
1251 } elseif (isset($GLOBALS['TSFE'])) {
1252 // No PID column - we can do a best-effort to clear the cache of the current page if in FE
1253 $storagePage = $GLOBALS['TSFE']->id;
1254 $pageIdsToClear[] = $storagePage;
1255 }
1256 if ($storagePage === NULL) {
1257 return;
1258 }
1259 if (!isset($this->pageTSConfigCache[$storagePage])) {
1260 $this->pageTSConfigCache[$storagePage] = \TYPO3\CMS\Backend\Utility\BackendUtility::getPagesTSconfig($storagePage);
1261 }
1262 if (isset($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd'])) {
1263 $clearCacheCommands = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', strtolower($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd']), 1);
1264 $clearCacheCommands = array_unique($clearCacheCommands);
1265 foreach ($clearCacheCommands as $clearCacheCommand) {
1266 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($clearCacheCommand)) {
1267 $pageIdsToClear[] = $clearCacheCommand;
1268 }
1269 }
1270 }
1271
1272 foreach ($pageIdsToClear as $pageIdToClear) {
1273 $this->cacheService->getPageIdStack()->push($pageIdToClear);
1274 }
1275 }
1276 }
1277
1278 ?>