05cc5a087dc5b651528214de67c043fc3caa96b9
[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 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Extbase\Persistence\Generic\Qom;
19 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
20
21 /**
22 * A Storage backend
23 */
24 class Typo3DbBackend implements BackendInterface, \TYPO3\CMS\Core\SingletonInterface {
25
26 /**
27 * The TYPO3 database object
28 *
29 * @var \TYPO3\CMS\Core\Database\DatabaseConnection
30 */
31 protected $databaseHandle;
32
33 /**
34 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
35 * @inject
36 */
37 protected $dataMapper;
38
39 /**
40 * The TYPO3 page repository. Used for language and workspace overlay
41 *
42 * @var \TYPO3\CMS\Frontend\Page\PageRepository
43 */
44 protected $pageRepository;
45
46 /**
47 * A first-level TypoScript configuration cache
48 *
49 * @var array
50 */
51 protected $pageTSConfigCache = array();
52
53 /**
54 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
55 * @inject
56 */
57 protected $configurationManager;
58
59 /**
60 * @var \TYPO3\CMS\Extbase\Service\CacheService
61 * @inject
62 */
63 protected $cacheService;
64
65 /**
66 * @var \TYPO3\CMS\Core\Cache\CacheManager
67 * @inject
68 */
69 protected $cacheManager;
70
71 /**
72 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
73 */
74 protected $tableColumnCache;
75
76 /**
77 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
78 */
79 protected $queryCache;
80
81 /**
82 * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
83 * @inject
84 */
85 protected $environmentService;
86
87 /**
88 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser
89 * @inject
90 */
91 protected $queryParser;
92
93 /**
94 * A first level cache for queries during runtime
95 *
96 * @var array
97 */
98 protected $queryRuntimeCache = array();
99
100 /**
101 * Constructor. takes the database handle from $GLOBALS['TYPO3_DB']
102 */
103 public function __construct() {
104 $this->databaseHandle = $GLOBALS['TYPO3_DB'];
105 }
106
107 /**
108 * Lifecycle method
109 *
110 * @return void
111 */
112 public function initializeObject() {
113 $this->tableColumnCache = $this->cacheManager->getCache('extbase_typo3dbbackend_tablecolumns');
114 $this->queryCache = $this->cacheManager->getCache('extbase_typo3dbbackend_queries');
115 }
116
117 /**
118 * Adds a row to the storage
119 *
120 * @param string $tableName The database table name
121 * @param array $fieldValues The row to be inserted
122 * @param bool $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
123 * @return integer The uid of the inserted row
124 */
125 public function addRow($tableName, array $fieldValues, $isRelation = FALSE) {
126 if (isset($fieldValues['uid'])) {
127 unset($fieldValues['uid']);
128 }
129
130 $this->databaseHandle->exec_INSERTquery($tableName, $fieldValues);
131 $this->checkSqlErrors();
132 $uid = $this->databaseHandle->sql_insert_id();
133
134 if (!$isRelation) {
135 $this->clearPageCache($tableName, $uid);
136 }
137 return (int)$uid;
138 }
139
140 /**
141 * Updates a row in the storage
142 *
143 * @param string $tableName The database table name
144 * @param array $fieldValues The row to be updated
145 * @param bool $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
146 * @throws \InvalidArgumentException
147 * @return bool
148 */
149 public function updateRow($tableName, array $fieldValues, $isRelation = FALSE) {
150 if (!isset($fieldValues['uid'])) {
151 throw new \InvalidArgumentException('The given row must contain a value for "uid".');
152 }
153
154 $uid = (int)$fieldValues['uid'];
155 unset($fieldValues['uid']);
156
157 $updateSuccessful = $this->databaseHandle->exec_UPDATEquery($tableName, 'uid = ' . $uid, $fieldValues);
158 $this->checkSqlErrors();
159
160 if (!$isRelation) {
161 $this->clearPageCache($tableName, $uid);
162 }
163
164 return $updateSuccessful;
165 }
166
167 /**
168 * Updates a relation row in the storage.
169 *
170 * @param string $tableName The database relation table name
171 * @param array $fieldValues The row to be updated
172 * @throws \InvalidArgumentException
173 * @return bool
174 */
175 public function updateRelationTableRow($tableName, array $fieldValues) {
176 if (!isset($fieldValues['uid_local']) && !isset($fieldValues['uid_foreign'])) {
177 throw new \InvalidArgumentException(
178 'The given fieldValues must contain a value for "uid_local" and "uid_foreign".', 1360500126
179 );
180 }
181
182 $where['uid_local'] = (int)$fieldValues['uid_local'];
183 $where['uid_foreign'] = (int)$fieldValues['uid_foreign'];
184 unset($fieldValues['uid_local']);
185 unset($fieldValues['uid_foreign']);
186
187 if (!empty($fieldValues['tablenames'])) {
188 $where['tablenames'] = $fieldValues['tablenames'];
189 unset($fieldValues['tablenames']);
190 }
191 if (!empty($fieldValues['fieldname'])) {
192 $where['fieldname'] = $fieldValues['fieldname'];
193 unset($fieldValues['fieldname']);
194 }
195
196 $updateSuccessful = $this->databaseHandle->exec_UPDATEquery(
197 $tableName,
198 $this->resolveWhereStatement($where, $tableName),
199 $fieldValues
200 );
201 $this->checkSqlErrors();
202
203 return $updateSuccessful;
204 }
205
206 /**
207 * Deletes a row in the storage
208 *
209 * @param string $tableName The database table name
210 * @param array $where An array of where array('fieldname' => value).
211 * @param bool $isRelation TRUE if we are currently manipulating a relation table, FALSE by default
212 * @return bool
213 */
214 public function removeRow($tableName, array $where, $isRelation = FALSE) {
215 $deleteSuccessful = $this->databaseHandle->exec_DELETEquery(
216 $tableName,
217 $this->resolveWhereStatement($where, $tableName)
218 );
219 $this->checkSqlErrors();
220
221 if (!$isRelation && isset($where['uid'])) {
222 $this->clearPageCache($tableName, $where['uid']);
223 }
224
225 return $deleteSuccessful;
226 }
227
228 /**
229 * Fetches maximal value for given table column from database.
230 *
231 * @param string $tableName The database table name
232 * @param array $where An array of where array('fieldname' => value).
233 * @param string $columnName column name to get the max value from
234 * @return mixed the max value
235 */
236 public function getMaxValueFromTable($tableName, array $where, $columnName) {
237 $result = $this->databaseHandle->exec_SELECTgetSingleRow(
238 $columnName,
239 $tableName,
240 $this->resolveWhereStatement($where, $tableName),
241 '',
242 $columnName . ' DESC',
243 TRUE
244 );
245 $this->checkSqlErrors();
246
247 return $result[0];
248 }
249
250 /**
251 * Fetches row data from the database
252 *
253 * @param string $tableName
254 * @param array $where An array of where array('fieldname' => value).
255 * @return array|bool
256 */
257 public function getRowByIdentifier($tableName, array $where) {
258 $row = $this->databaseHandle->exec_SELECTgetSingleRow(
259 '*',
260 $tableName,
261 $this->resolveWhereStatement($where, $tableName)
262 );
263 $this->checkSqlErrors();
264
265 return $row ?: FALSE;
266 }
267
268 /**
269 * Converts an array to an AND concatenated where statement
270 *
271 * @param array $where array('fieldName' => 'fieldValue')
272 * @param string $tableName table to use for escaping config
273 *
274 * @return string
275 */
276 protected function resolveWhereStatement(array $where, $tableName = 'foo') {
277 $whereStatement = array();
278
279 foreach ($where as $fieldName => $fieldValue) {
280 $whereStatement[] = $fieldName . ' = ' . $this->databaseHandle->fullQuoteStr($fieldValue, $tableName);
281 }
282
283 return implode(' AND ', $whereStatement);
284 }
285
286 /**
287 * Returns the object data matching the $query.
288 *
289 * @param QueryInterface $query
290 * @return array
291 */
292 public function getObjectDataByQuery(QueryInterface $query) {
293 $statement = $query->getStatement();
294 if ($statement instanceof Qom\Statement) {
295 $rows = $this->getObjectDataByRawQuery($statement);
296 } else {
297 $rows = $this->getRowsByStatementParts($query);
298 }
299
300 $rows = $this->doLanguageAndWorkspaceOverlay($query->getSource(), $rows, $query->getQuerySettings());
301 return $rows;
302 }
303
304 /**
305 * Creates the parameters for the query methods of the database methods in the TYPO3 core, from an array
306 * that came from a parsed query.
307 *
308 * @param array $statementParts
309 * @return array
310 */
311 protected function createQueryCommandParametersFromStatementParts(array $statementParts) {
312 return array(
313 'selectFields' => implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']),
314 'fromTable' => implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']),
315 'whereClause' => (!empty($statementParts['where']) ? implode('', $statementParts['where']) : '1')
316 . (!empty($statementParts['additionalWhereClause'])
317 ? ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'])
318 : ''
319 ),
320 'orderBy' => (!empty($statementParts['orderings']) ? implode(', ', $statementParts['orderings']) : ''),
321 'limit' => ($statementParts['offset'] ? $statementParts['offset'] . ', ' : '')
322 . ($statementParts['limit'] ? $statementParts['limit'] : '')
323 );
324 }
325
326 /**
327 * Determines whether to use prepared statement or not and returns the rows from the corresponding method
328 *
329 * @param QueryInterface $query
330 * @return array
331 */
332 protected function getRowsByStatementParts(QueryInterface $query) {
333 if ($query->getQuerySettings()->getUsePreparedStatement()) {
334 list($statementParts, $parameters) = $this->getStatementParts($query, FALSE);
335 $rows = $this->getRowsFromPreparedDatabase($statementParts, $parameters);
336 } else {
337 list($statementParts) = $this->getStatementParts($query);
338 $rows = $this->getRowsFromDatabase($statementParts);
339 }
340
341 return $rows;
342 }
343
344 /**
345 * Fetches the rows directly from the database, not using prepared statement
346 *
347 * @param array $statementParts
348 * @return array the result
349 */
350 protected function getRowsFromDatabase(array $statementParts) {
351 $queryCommandParameters = $this->createQueryCommandParametersFromStatementParts($statementParts);
352 $rows = $this->databaseHandle->exec_SELECTgetRows(
353 $queryCommandParameters['selectFields'],
354 $queryCommandParameters['fromTable'],
355 $queryCommandParameters['whereClause'],
356 '',
357 $queryCommandParameters['orderBy'],
358 $queryCommandParameters['limit']
359 );
360 $this->checkSqlErrors();
361
362 return $rows;
363 }
364
365 /**
366 * Fetches the rows from the database, using prepared statement
367 *
368 * @param array $statementParts
369 * @param array $parameters
370 * @return array the result
371 */
372 protected function getRowsFromPreparedDatabase(array $statementParts, array $parameters) {
373 $queryCommandParameters = $this->createQueryCommandParametersFromStatementParts($statementParts);
374 $preparedStatement = $this->databaseHandle->prepare_SELECTquery(
375 $queryCommandParameters['selectFields'],
376 $queryCommandParameters['fromTable'],
377 $queryCommandParameters['whereClause'],
378 '',
379 $queryCommandParameters['orderBy'],
380 $queryCommandParameters['limit']
381 );
382
383 $preparedStatement->execute($parameters);
384 $rows = $preparedStatement->fetchAll();
385
386 $preparedStatement->free();
387 return $rows;
388 }
389
390 /**
391 * Returns the object data using a custom statement
392 *
393 * @param Qom\Statement $statement
394 * @return array
395 */
396 protected function getObjectDataByRawQuery(Qom\Statement $statement) {
397 $realStatement = $statement->getStatement();
398 $parameters = $statement->getBoundVariables();
399
400 if ($realStatement instanceof \TYPO3\CMS\Core\Database\PreparedStatement) {
401 $realStatement->execute($parameters);
402 $rows = $realStatement->fetchAll();
403
404 $realStatement->free();
405 } else {
406 /**
407 * @deprecated since 6.2, this block will be removed in two versions
408 * the deprecation log is in Qom\Statement
409 */
410 if (!empty($parameters)) {
411 $this->replacePlaceholders($realStatement, $parameters);
412 }
413
414 $result = $this->databaseHandle->sql_query($realStatement);
415 $this->checkSqlErrors();
416
417 $rows = array();
418 while ($row = $this->databaseHandle->sql_fetch_assoc($result)) {
419 if (is_array($row)) {
420 $rows[] = $row;
421 }
422 }
423 $this->databaseHandle->sql_free_result($result);
424 }
425
426 return $rows;
427 }
428
429 /**
430 * Returns the number of tuples matching the query.
431 *
432 * @param QueryInterface $query
433 * @throws Exception\BadConstraintException
434 * @return integer The number of matching tuples
435 */
436 public function getObjectCountByQuery(QueryInterface $query) {
437 if ($query->getConstraint() instanceof Qom\Statement) {
438 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\\Statement', 1256661045);
439 }
440
441 list($statementParts) = $this->getStatementParts($query);
442
443 $fields = '*';
444 if (isset($statementParts['keywords']['distinct'])) {
445 $fields = 'DISTINCT ' . reset($statementParts['tables']) . '.uid';
446 }
447
448 $queryCommandParameters = $this->createQueryCommandParametersFromStatementParts($statementParts);
449 $count = $this->databaseHandle->exec_SELECTcountRows(
450 $fields,
451 $queryCommandParameters['fromTable'],
452 $queryCommandParameters['whereClause']
453 );
454 $this->checkSqlErrors();
455
456 if ($statementParts['offset']) {
457 $count -= $statementParts['offset'];
458 }
459
460 if ($statementParts['limit']) {
461 $count = min($count, $statementParts['limit']);
462 }
463
464 return (int)max(0, $count);
465 }
466
467 /**
468 * Looks for the query in cache or builds it up otherwise
469 *
470 * @param QueryInterface $query
471 * @param bool $resolveParameterPlaceholders whether to resolve the parameters or leave the placeholders
472 * @return array
473 * @throws \RuntimeException
474 */
475 protected function getStatementParts($query, $resolveParameterPlaceholders = TRUE) {
476 /**
477 * The queryParser will preparse the query to get the query's hash and parameters.
478 * If the hash is found in the cache and useQueryCaching is enabled, extbase will
479 * then take the string representation from cache and build a prepared query with
480 * the parameters found.
481 *
482 * Otherwise extbase will parse the complete query, build the string representation
483 * and run a usual query.
484 */
485 list($queryHash, $parameters) = $this->queryParser->preparseQuery($query);
486
487 if ($query->getQuerySettings()->getUseQueryCache()) {
488 $statementParts = $this->getQueryCacheEntry($queryHash);
489 if ($queryHash && !$statementParts) {
490 $statementParts = $this->queryParser->parseQuery($query);
491 $this->setQueryCacheEntry($queryHash, $statementParts);
492 }
493 } else {
494 $statementParts = $this->queryParser->parseQuery($query);
495 }
496
497 if (!$statementParts) {
498 throw new \RuntimeException('Your query could not be built.', 1394453197);
499 }
500
501 $this->queryParser->addDynamicQueryParts($query->getQuerySettings(), $statementParts);
502
503 // Limit and offset are not cached to allow caching of pagebrowser queries.
504 $statementParts['limit'] = ((int)$query->getLimit() ?: NULL);
505 $statementParts['offset'] = ((int)$query->getOffset() ?: NULL);
506
507 if ($resolveParameterPlaceholders === TRUE) {
508 $statementParts = $this->resolveParameterPlaceholders($statementParts, $parameters);
509 }
510
511 return array($statementParts, $parameters);
512 }
513
514 /**
515 * Replaces the parameters in the queryStructure with given values
516 *
517 * @param array $statementParts
518 * @param array $parameters
519 * @return array
520 */
521 protected function resolveParameterPlaceholders(array $statementParts, array $parameters) {
522 $tableName = reset($statementParts['tables']) ?: 'foo';
523
524 foreach ($parameters as $parameterPlaceholder => $parameter) {
525 $parameter = $this->dataMapper->getPlainValue($parameter, NULL, array($this, 'quoteTextValueCallback'), array('tablename' => $tableName));
526 $statementParts['where'] = str_replace($parameterPlaceholder, $parameter, $statementParts['where']);
527 }
528
529 return $statementParts;
530 }
531
532 /**
533 * Will be called by the data mapper to quote string values.
534 *
535 * @param string $value The value to be quoted.
536 * @param array $parameters Additional parameters array currently containing the "tablename" key.
537 * @return string The quoted string.
538 */
539 public function quoteTextValueCallback($value, $parameters) {
540 return $this->databaseHandle->fullQuoteStr($value, $parameters['tablename']);
541 }
542
543 /**
544 * Checks if a Value Object equal to the given Object exists in the data base
545 *
546 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object The Value Object
547 * @return mixed The matching uid if an object was found, else FALSE
548 * @todo this is the last monster in this persistence series. refactor!
549 */
550 public function getUidOfAlreadyPersistedValueObject(\TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object) {
551 $fields = array();
552 $parameters = array();
553 $dataMap = $this->dataMapper->getDataMap(get_class($object));
554 $properties = $object->_getProperties();
555 foreach ($properties as $propertyName => $propertyValue) {
556 // FIXME We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method
557 if ($dataMap->isPersistableProperty($propertyName) && $propertyName !== 'uid' && $propertyName !== 'pid' && $propertyName !== 'isClone') {
558 if ($propertyValue === NULL) {
559 $fields[] = $dataMap->getColumnMap($propertyName)->getColumnName() . ' IS NULL';
560 } else {
561 $fields[] = $dataMap->getColumnMap($propertyName)->getColumnName() . '=?';
562 $parameters[] = $this->dataMapper->getPlainValue($propertyValue);
563 }
564 }
565 }
566 $sql = array();
567 $sql['additionalWhereClause'] = array();
568 $tableName = $dataMap->getTableName();
569 $this->addVisibilityConstraintStatement(new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings(), $tableName, $sql);
570 $statement = 'SELECT * FROM ' . $tableName;
571 $statement .= ' WHERE ' . implode(' AND ', $fields);
572 if (!empty($sql['additionalWhereClause'])) {
573 $statement .= ' AND ' . implode(' AND ', $sql['additionalWhereClause']);
574 }
575 $this->replacePlaceholders($statement, $parameters, $tableName);
576 // debug($statement,-2);
577 $res = $this->databaseHandle->sql_query($statement);
578 $this->checkSqlErrors($statement);
579 $row = $this->databaseHandle->sql_fetch_assoc($res);
580 if ($row !== FALSE) {
581 return (int)$row['uid'];
582 } else {
583 return FALSE;
584 }
585 }
586
587 /**
588 * Replace query placeholders in a query part by the given
589 * parameters.
590 *
591 * @param string &$sqlString The query part with placeholders
592 * @param array $parameters The parameters
593 * @param string $tableName
594 *
595 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception
596 * @deprecated since 6.2, will be removed two versions later
597 * @todo add deprecation notice after getUidOfAlreadyPersistedValueObject is adjusted
598 */
599 protected function replacePlaceholders(&$sqlString, array $parameters, $tableName = 'foo') {
600 // TODO profile this method again
601 if (substr_count($sqlString, '?') !== count($parameters)) {
602 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('The number of question marks to replace must be equal to the number of parameters.', 1242816074);
603 }
604 $offset = 0;
605 foreach ($parameters as $parameter) {
606 $markPosition = strpos($sqlString, '?', $offset);
607 if ($markPosition !== FALSE) {
608 if ($parameter === NULL) {
609 $parameter = 'NULL';
610 } elseif (is_array($parameter) || $parameter instanceof \ArrayAccess || $parameter instanceof \Traversable) {
611 $items = array();
612 foreach ($parameter as $item) {
613 $items[] = $this->databaseHandle->fullQuoteStr($item, $tableName);
614 }
615 $parameter = '(' . implode(',', $items) . ')';
616 } else {
617 $parameter = $this->databaseHandle->fullQuoteStr($parameter, $tableName);
618 }
619 $sqlString = substr($sqlString, 0, $markPosition) . $parameter . substr($sqlString, ($markPosition + 1));
620 }
621 $offset = $markPosition + strlen($parameter);
622 }
623 }
624
625 /**
626 * Adds enableFields and deletedClause to the query if necessary
627 *
628 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings
629 * @param string $tableName The database table name
630 * @param array &$sql The query parts
631 * @return void
632 * @todo remove after getUidOfAlreadyPersistedValueObject is adjusted, this was moved to queryParser
633 */
634 protected function addVisibilityConstraintStatement(\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings, $tableName, array &$sql) {
635 $statement = '';
636 if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
637 $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
638 $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
639 $includeDeleted = $querySettings->getIncludeDeleted();
640 if ($this->environmentService->isEnvironmentInFrontendMode()) {
641 $statement .= $this->getFrontendConstraintStatement($tableName, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
642 } else {
643 // TYPO3_MODE === 'BE'
644 $statement .= $this->getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted);
645 }
646 if (!empty($statement)) {
647 $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
648 $sql['additionalWhereClause'][] = $statement;
649 }
650 }
651 }
652
653 /**
654 * Returns constraint statement for frontend context
655 *
656 * @param string $tableName
657 * @param bool $ignoreEnableFields A flag indicating whether the enable fields should be ignored
658 * @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.
659 * @param bool $includeDeleted A flag indicating whether deleted records should be included
660 * @return string
661 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException
662 * @todo remove after getUidOfAlreadyPersistedValueObject is adjusted, this was moved to queryParser
663 */
664 protected function getFrontendConstraintStatement($tableName, $ignoreEnableFields, array $enableFieldsToBeIgnored = array(), $includeDeleted) {
665 $statement = '';
666 if ($ignoreEnableFields && !$includeDeleted) {
667 if (count($enableFieldsToBeIgnored)) {
668 // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
669 $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
670 } else {
671 $statement .= $this->getPageRepository()->deleteClause($tableName);
672 }
673 } elseif (!$ignoreEnableFields && !$includeDeleted) {
674 $statement .= $this->getPageRepository()->enableFields($tableName);
675 } elseif (!$ignoreEnableFields && $includeDeleted) {
676 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);
677 }
678 return $statement;
679 }
680
681 /**
682 * Returns constraint statement for backend context
683 *
684 * @param string $tableName
685 * @param bool $ignoreEnableFields A flag indicating whether the enable fields should be ignored
686 * @param bool $includeDeleted A flag indicating whether deleted records should be included
687 * @return string
688 * @todo remove after getUidOfAlreadyPersistedValueObject is adjusted, this was moved to queryParser
689 */
690 protected function getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted) {
691 $statement = '';
692 if (!$ignoreEnableFields) {
693 $statement .= BackendUtility::BEenableFields($tableName);
694 }
695 if (!$includeDeleted) {
696 $statement .= BackendUtility::deleteClause($tableName);
697 }
698 return $statement;
699 }
700
701 /**
702 * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
703 * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
704 *
705 * @param Qom\SourceInterface $source The source (selector od join)
706 * @param array $rows
707 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
708 * @param null|integer $workspaceUid
709 * @return array
710 */
711 protected function doLanguageAndWorkspaceOverlay(Qom\SourceInterface $source, array $rows, \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings, $workspaceUid = NULL) {
712 if ($source instanceof Qom\SelectorInterface) {
713 $tableName = $source->getSelectorName();
714 } elseif ($source instanceof Qom\JoinInterface) {
715 $tableName = $source->getRight()->getSelectorName();
716 } else {
717 // No proper source, so we do not have a table name here
718 // we cannot do an overlay and return the original rows instead.
719 return $rows;
720 }
721
722 $pageRepository = $this->getPageRepository();
723 if (is_object($GLOBALS['TSFE'])) {
724 if ($workspaceUid !== NULL) {
725 $pageRepository->versioningWorkspaceId = $workspaceUid;
726 }
727 } else {
728 if ($workspaceUid === NULL) {
729 $workspaceUid = $GLOBALS['BE_USER']->workspace;
730 }
731 $pageRepository->versioningWorkspaceId = $workspaceUid;
732 }
733
734 $overlaidRows = array();
735 foreach ($rows as $row) {
736 // If current row is a translation select its parent
737 if (isset($tableName) && isset($GLOBALS['TCA'][$tableName])
738 && isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
739 && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
740 && !isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerTable'])
741 ) {
742 if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
743 && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
744 ) {
745 $row = $this->databaseHandle->exec_SELECTgetSingleRow(
746 $tableName . '.*',
747 $tableName,
748 $tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] .
749 ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0'
750 );
751 }
752 }
753 $pageRepository->versionOL($tableName, $row, TRUE);
754 if ($pageRepository->versioningPreview && isset($row['_ORIG_uid'])) {
755 $row['uid'] = $row['_ORIG_uid'];
756 }
757 if ($tableName == 'pages') {
758 $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
759 } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
760 && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
761 && !isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerTable'])
762 ) {
763 if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], array(-1, 0))) {
764 $overlayMode = $querySettings->getLanguageMode() === 'strict' ? 'hideNonTranslated' : '';
765 $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
766 }
767 }
768 if ($row !== NULL && is_array($row)) {
769 $overlaidRows[] = $row;
770 }
771 }
772 return $overlaidRows;
773 }
774
775 /**
776 * @return \TYPO3\CMS\Frontend\Page\PageRepository
777 */
778 protected function getPageRepository() {
779 if (!$this->pageRepository instanceof \TYPO3\CMS\Frontend\Page\PageRepository) {
780 if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
781 $this->pageRepository = $GLOBALS['TSFE']->sys_page;
782 } else {
783 $this->pageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository');
784 }
785 }
786
787 return $this->pageRepository;
788 }
789
790 /**
791 * Checks if there are SQL errors in the last query, and if yes, throw an exception.
792 *
793 * @return void
794 * @param string $sql The SQL statement
795 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\SqlErrorException
796 */
797 protected function checkSqlErrors($sql = '') {
798 $error = $this->databaseHandle->sql_error();
799 if ($error !== '') {
800 $error .= $sql ? ': ' . $sql : '';
801 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\SqlErrorException($error, 1247602160);
802 }
803 }
804
805 /**
806 * Clear the TYPO3 page cache for the given record.
807 * If the record lies on a page, then we clear the cache of this page.
808 * If the record has no PID column, we clear the cache of the current page as best-effort.
809 *
810 * Much of this functionality is taken from t3lib_tcemain::clear_cache() which unfortunately only works with logged-in BE user.
811 *
812 * @param string $tableName Table name of the record
813 * @param integer $uid UID of the record
814 * @return void
815 */
816 protected function clearPageCache($tableName, $uid) {
817 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
818 if (isset($frameworkConfiguration['persistence']['enableAutomaticCacheClearing']) && $frameworkConfiguration['persistence']['enableAutomaticCacheClearing'] === '1') {
819 } else {
820 // if disabled, return
821 return;
822 }
823 $pageIdsToClear = array();
824 $storagePage = NULL;
825 $columns = $this->databaseHandle->admin_get_fields($tableName);
826 if (array_key_exists('pid', $columns)) {
827 $result = $this->databaseHandle->exec_SELECTquery('pid', $tableName, 'uid=' . (int)$uid);
828 if ($row = $this->databaseHandle->sql_fetch_assoc($result)) {
829 $storagePage = $row['pid'];
830 $pageIdsToClear[] = $storagePage;
831 }
832 } elseif (isset($GLOBALS['TSFE'])) {
833 // No PID column - we can do a best-effort to clear the cache of the current page if in FE
834 $storagePage = $GLOBALS['TSFE']->id;
835 $pageIdsToClear[] = $storagePage;
836 }
837 if ($storagePage === NULL) {
838 return;
839 }
840 if (!isset($this->pageTSConfigCache[$storagePage])) {
841 $this->pageTSConfigCache[$storagePage] = BackendUtility::getPagesTSconfig($storagePage);
842 }
843 if (isset($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd'])) {
844 $clearCacheCommands = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', strtolower($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd']), TRUE);
845 $clearCacheCommands = array_unique($clearCacheCommands);
846 foreach ($clearCacheCommands as $clearCacheCommand) {
847 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($clearCacheCommand)) {
848 $pageIdsToClear[] = $clearCacheCommand;
849 }
850 }
851 }
852
853 foreach ($pageIdsToClear as $pageIdToClear) {
854 $this->cacheService->getPageIdStack()->push($pageIdToClear);
855 }
856 }
857
858 /**
859 * Finds and returns a variable value from the query cache.
860 *
861 * @param string $entryIdentifier Identifier of the cache entry to fetch
862 * @return mixed The value
863 */
864 protected function getQueryCacheEntry($entryIdentifier) {
865 if (!isset($this->queryRuntimeCache[$entryIdentifier])) {
866 $this->queryRuntimeCache[$entryIdentifier] = $this->queryCache->get($entryIdentifier);
867 }
868 return $this->queryRuntimeCache[$entryIdentifier];
869 }
870
871 /**
872 * Saves the value of a PHP variable in the query cache.
873 *
874 * @param string $entryIdentifier An identifier used for this cache entry
875 * @param mixed $variable The query to cache
876 * @return void
877 */
878 protected function setQueryCacheEntry($entryIdentifier, $variable) {
879 $this->queryRuntimeCache[$entryIdentifier] = $variable;
880 $this->queryCache->set($entryIdentifier, $variable, array(), 0);
881 }
882 }