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