37df075d5f06c0230d3d85a8e21b9af906412172
[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 Doctrine\DBAL\DBALException;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
21 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
22 use TYPO3\CMS\Core\SingletonInterface;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
26 use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
27 use TYPO3\CMS\Extbase\Persistence\Generic\Qom;
28 use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\SqlErrorException;
29 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
30
31 /**
32 * A Storage backend
33 */
34 class Typo3DbBackend implements BackendInterface, SingletonInterface
35 {
36 /**
37 * @var ConnectionPool
38 */
39 protected $connectionPool;
40
41 /**
42 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
43 */
44 protected $dataMapper;
45
46 /**
47 * The TYPO3 page repository. Used for language and workspace overlay
48 *
49 * @var \TYPO3\CMS\Frontend\Page\PageRepository
50 */
51 protected $pageRepository;
52
53 /**
54 * A first-level TypoScript configuration cache
55 *
56 * @var array
57 */
58 protected $pageTSConfigCache = [];
59
60 /**
61 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
62 */
63 protected $configurationManager;
64
65 /**
66 * @var \TYPO3\CMS\Extbase\Service\CacheService
67 */
68 protected $cacheService;
69
70 /**
71 * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
72 */
73 protected $environmentService;
74
75 /**
76 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
77 */
78 protected $objectManager;
79
80 /**
81 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper
82 */
83 public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
84 {
85 $this->dataMapper = $dataMapper;
86 }
87
88 /**
89 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
90 */
91 public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
92 {
93 $this->configurationManager = $configurationManager;
94 }
95
96 /**
97 * @param \TYPO3\CMS\Extbase\Service\CacheService $cacheService
98 */
99 public function injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService)
100 {
101 $this->cacheService = $cacheService;
102 }
103
104 /**
105 * @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
106 */
107 public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
108 {
109 $this->environmentService = $environmentService;
110 }
111
112 /**
113 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
114 */
115 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
116 {
117 $this->objectManager = $objectManager;
118 }
119
120 /**
121 * Constructor.
122 */
123 public function __construct()
124 {
125 $this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
126 }
127
128 /**
129 * Adds a row to the storage
130 *
131 * @param string $tableName The database table name
132 * @param array $fieldValues The row to be inserted
133 * @param bool $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
134 * @return int The uid of the inserted row
135 * @throws SqlErrorException
136 */
137 public function addRow($tableName, array $fieldValues, $isRelation = false)
138 {
139 if (isset($fieldValues['uid'])) {
140 unset($fieldValues['uid']);
141 }
142 try {
143 $connection = $this->connectionPool->getConnectionForTable($tableName);
144 $connection->insert($tableName, $fieldValues);
145 } catch (DBALException $e) {
146 throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230766);
147 }
148
149 $uid = 0;
150 if (!$isRelation) {
151 // Relation tables have no auto_increment column, so no retrieval must be tried.
152 $uid = $connection->lastInsertId($tableName);
153 $this->clearPageCache($tableName, $uid);
154 }
155 return (int)$uid;
156 }
157
158 /**
159 * Updates a row in the storage
160 *
161 * @param string $tableName The database table name
162 * @param array $fieldValues The row to be updated
163 * @param bool $isRelation TRUE if we are currently inserting into a relation table, FALSE by default
164 * @throws \InvalidArgumentException
165 * @throws SqlErrorException
166 * @return bool
167 */
168 public function updateRow($tableName, array $fieldValues, $isRelation = false)
169 {
170 if (!isset($fieldValues['uid'])) {
171 throw new \InvalidArgumentException('The given row must contain a value for "uid".', 1476045164);
172 }
173
174 $uid = (int)$fieldValues['uid'];
175 unset($fieldValues['uid']);
176
177 try {
178 $this->connectionPool->getConnectionForTable($tableName)
179 ->update($tableName, $fieldValues, ['uid' => $uid]);
180 } catch (DBALException $e) {
181 throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230767);
182 }
183
184 if (!$isRelation) {
185 $this->clearPageCache($tableName, $uid);
186 }
187
188 // always returns true
189 return true;
190 }
191
192 /**
193 * Updates a relation row in the storage.
194 *
195 * @param string $tableName The database relation table name
196 * @param array $fieldValues The row to be updated
197 * @throws \InvalidArgumentException
198 * @return bool
199 * @throws SqlErrorException
200 */
201 public function updateRelationTableRow($tableName, array $fieldValues)
202 {
203 if (!isset($fieldValues['uid_local']) && !isset($fieldValues['uid_foreign'])) {
204 throw new \InvalidArgumentException(
205 'The given fieldValues must contain a value for "uid_local" and "uid_foreign".', 1360500126
206 );
207 }
208
209 $where['uid_local'] = (int)$fieldValues['uid_local'];
210 $where['uid_foreign'] = (int)$fieldValues['uid_foreign'];
211 unset($fieldValues['uid_local']);
212 unset($fieldValues['uid_foreign']);
213
214 if (!empty($fieldValues['tablenames'])) {
215 $where['tablenames'] = $fieldValues['tablenames'];
216 unset($fieldValues['tablenames']);
217 }
218 if (!empty($fieldValues['fieldname'])) {
219 $where['fieldname'] = $fieldValues['fieldname'];
220 unset($fieldValues['fieldname']);
221 }
222
223 try {
224 $this->connectionPool->getConnectionForTable($tableName)
225 ->update($tableName, $fieldValues, $where);
226 } catch (DBALException $e) {
227 throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230768);
228 }
229
230 // always returns true
231 return true;
232 }
233
234 /**
235 * Deletes a row in the storage
236 *
237 * @param string $tableName The database table name
238 * @param array $where An array of where array('fieldname' => value).
239 * @param bool $isRelation TRUE if we are currently manipulating a relation table, FALSE by default
240 * @return bool
241 * @throws SqlErrorException
242 */
243 public function removeRow($tableName, array $where, $isRelation = false)
244 {
245 try {
246 $this->connectionPool->getConnectionForTable($tableName)->delete($tableName, $where);
247 } catch (DBALException $e) {
248 throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230769);
249 }
250
251 if (!$isRelation && isset($where['uid'])) {
252 $this->clearPageCache($tableName, $where['uid']);
253 }
254
255 // always returns true
256 return true;
257 }
258
259 /**
260 * Fetches maximal value for given table column from database.
261 *
262 * @param string $tableName The database table name
263 * @param array $where An array of where array('fieldname' => value).
264 * @param string $columnName column name to get the max value from
265 * @return mixed the max value
266 * @throws SqlErrorException
267 */
268 public function getMaxValueFromTable($tableName, array $where, $columnName)
269 {
270 try {
271 $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
272 $queryBuilder->getRestrictions()->removeAll();
273 $queryBuilder
274 ->select($columnName)
275 ->from($tableName)
276 ->orderBy($columnName, 'DESC')
277 ->setMaxResults(1);
278
279 foreach ($where as $fieldName => $value) {
280 $queryBuilder->andWhere(
281 $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
282 );
283 }
284
285 $result = $queryBuilder->execute()->fetchColumn(0);
286 } catch (DBALException $e) {
287 throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230770);
288 }
289 return $result;
290 }
291
292 /**
293 * Fetches row data from the database
294 *
295 * @param string $tableName
296 * @param array $where An array of where array('fieldname' => value).
297 * @return array|bool
298 * @throws SqlErrorException
299 */
300 public function getRowByIdentifier($tableName, array $where)
301 {
302 try {
303 $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
304 $queryBuilder->getRestrictions()->removeAll();
305 $queryBuilder
306 ->select('*')
307 ->from($tableName);
308
309 foreach ($where as $fieldName => $value) {
310 $queryBuilder->andWhere(
311 $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
312 );
313 }
314
315 $row = $queryBuilder->execute()->fetch();
316 } catch (DBALException $e) {
317 throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230771);
318 }
319 return $row ?: false;
320 }
321
322 /**
323 * Returns the object data matching the $query.
324 *
325 * @param QueryInterface $query
326 * @return array
327 * @throws SqlErrorException
328 */
329 public function getObjectDataByQuery(QueryInterface $query)
330 {
331 $statement = $query->getStatement();
332 if ($statement instanceof Qom\Statement) {
333 $rows = $this->getObjectDataByRawQuery($statement);
334 } else {
335 $queryBuilder = $this->objectManager->get(Typo3DbQueryParser::class)
336 ->convertQueryToDoctrineQueryBuilder($query);
337 if ($query->getOffset()) {
338 $queryBuilder->setFirstResult($query->getOffset());
339 }
340 if ($query->getLimit()) {
341 $queryBuilder->setMaxResults($query->getLimit());
342 }
343 try {
344 $rows = $queryBuilder->execute()->fetchAll();
345 } catch (DBALException $e) {
346 throw new SqlErrorException($e->getPrevious()->getMessage(), 1472074485);
347 }
348 }
349
350 $rows = $this->doLanguageAndWorkspaceOverlay($query->getSource(), $rows, $query->getQuerySettings());
351 return $rows;
352 }
353
354 /**
355 * Returns the object data using a custom statement
356 *
357 * @param Qom\Statement $statement
358 * @return array
359 * @throws SqlErrorException when the raw SQL statement fails in the database
360 */
361 protected function getObjectDataByRawQuery(Qom\Statement $statement)
362 {
363 $realStatement = $statement->getStatement();
364 $parameters = $statement->getBoundVariables();
365
366 // The real statement is an instance of the Doctrine DBAL QueryBuilder, so fetching
367 // this directly is possible
368 if ($realStatement instanceof QueryBuilder) {
369 try {
370 $result = $realStatement->execute();
371 } catch (DBALException $e) {
372 throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064721);
373 }
374 $rows = $result->fetchAll();
375 } elseif ($realStatement instanceof \Doctrine\DBAL\Statement) {
376 try {
377 $realStatement->execute($parameters);
378 } catch (DBALException $e) {
379 throw new SqlErrorException($e->getPrevious()->getMessage(), 1481281404);
380 }
381 $rows = $realStatement->fetchAll();
382 } else {
383 // Do a real raw query. This is very stupid, as it does not allow to use DBAL's real power if
384 // several tables are on different databases, so this is used with caution and could be removed
385 // in the future
386 try {
387 $connection = $this->connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
388 $statement = $connection->executeQuery($realStatement, $parameters);
389 } catch (DBALException $e) {
390 throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064775);
391 }
392
393 $rows = $statement->fetchAll();
394 }
395
396 return $rows;
397 }
398
399 /**
400 * Returns the number of tuples matching the query.
401 *
402 * @param QueryInterface $query
403 * @throws Exception\BadConstraintException
404 * @return int The number of matching tuples
405 * @throws SqlErrorException
406 */
407 public function getObjectCountByQuery(QueryInterface $query)
408 {
409 if ($query->getConstraint() instanceof Qom\Statement) {
410 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);
411 }
412
413 $queryBuilder = $this->objectManager->get(Typo3DbQueryParser::class)
414 ->convertQueryToDoctrineQueryBuilder($query)
415 ->resetQueryPart('orderBy');
416
417 if (count($queryBuilder->getQueryPart('groupBy')) !== 0) {
418 $source = $queryBuilder->getQueryPart('from')[0];
419 // Tablename is already quoted for the DBMS, we need to treat table and field names separately
420 $tableName = $source['alias'] ?: $source['table'];
421 $fieldName = $queryBuilder->quoteIdentifier('uid');
422 $queryBuilder->resetQueryPart('groupBy')
423 ->selectLiteral(sprintf('COUNT(DISTINCT %s.%s)', $tableName, $fieldName));
424 } else {
425 $queryBuilder->count('*');
426 }
427
428 try {
429 $count = $queryBuilder->execute()->fetchColumn(0);
430 } catch (DBALException $e) {
431 throw new SqlErrorException($e->getPrevious()->getMessage(), 1472074379);
432 }
433 if ($query->getOffset()) {
434 $count -= $query->getOffset();
435 }
436 if ($query->getLimit()) {
437 $count = min($count, $query->getLimit());
438 }
439 return (int)max(0, $count);
440 }
441
442 /**
443 * Checks if a Value Object equal to the given Object exists in the database
444 *
445 * @param AbstractValueObject $object The Value Object
446 * @return mixed The matching uid if an object was found, else FALSE
447 * @throws SqlErrorException
448 */
449 public function getUidOfAlreadyPersistedValueObject(AbstractValueObject $object)
450 {
451 $dataMap = $this->dataMapper->getDataMap(get_class($object));
452 $tableName = $dataMap->getTableName();
453 $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
454 if ($this->environmentService->isEnvironmentInFrontendMode()) {
455 $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
456 }
457 $whereClause = [];
458 // loop over all properties of the object to exactly set the values of each database field
459 $properties = $object->_getProperties();
460 foreach ($properties as $propertyName => $propertyValue) {
461 // @todo We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method
462 if ($dataMap->isPersistableProperty($propertyName) && $propertyName !== 'uid' && $propertyName !== 'pid' && $propertyName !== 'isClone') {
463 $fieldName = $dataMap->getColumnMap($propertyName)->getColumnName();
464 if ($propertyValue === null) {
465 $whereClause[] = $queryBuilder->expr()->isNull($fieldName);
466 } else {
467 $whereClause[] = $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($propertyValue)));
468 }
469 }
470 }
471 $queryBuilder
472 ->select('uid')
473 ->from($tableName)
474 ->where(...$whereClause);
475
476 try {
477 $uid = (int)$queryBuilder
478 ->execute()
479 ->fetchColumn(0);
480 if ($uid > 0) {
481 return $uid;
482 } else {
483 return false;
484 }
485 } catch (DBALException $e) {
486 throw new SqlErrorException($e->getPrevious()->getMessage(), 1470231748);
487 }
488 }
489
490 /**
491 * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
492 * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
493 *
494 * @param Qom\SourceInterface $source The source (selector od join)
495 * @param array $rows
496 * @param \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
497 * @param null|int $workspaceUid
498 * @return array
499 */
500 protected function doLanguageAndWorkspaceOverlay(Qom\SourceInterface $source, array $rows, \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings, $workspaceUid = null)
501 {
502 if ($source instanceof Qom\SelectorInterface) {
503 $tableName = $source->getSelectorName();
504 } elseif ($source instanceof Qom\JoinInterface) {
505 $tableName = $source->getRight()->getSelectorName();
506 } else {
507 // No proper source, so we do not have a table name here
508 // we cannot do an overlay and return the original rows instead.
509 return $rows;
510 }
511
512 $pageRepository = $this->getPageRepository();
513 if (is_object($GLOBALS['TSFE'])) {
514 if ($workspaceUid !== null) {
515 $pageRepository->versioningWorkspaceId = $workspaceUid;
516 }
517 } else {
518 if ($workspaceUid === null) {
519 $workspaceUid = $GLOBALS['BE_USER']->workspace;
520 }
521 $pageRepository->versioningWorkspaceId = $workspaceUid;
522 }
523
524 // Fetches the move-placeholder in case it is supported
525 // by the table and if there's only one row in the result set
526 // (applying this to all rows does not work, since the sorting
527 // order would be destroyed and possible limits not met anymore)
528 if (!empty($pageRepository->versioningWorkspaceId)
529 && BackendUtility::isTableWorkspaceEnabled($tableName)
530 && count($rows) === 1
531 ) {
532 $versionId = $pageRepository->versioningWorkspaceId;
533 $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
534 $queryBuilder->getRestrictions()->removeAll();
535 $movePlaceholder = $queryBuilder->select($tableName . '.*')
536 ->from($tableName)
537 ->where(
538 $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(3, \PDO::PARAM_INT)),
539 $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($versionId, \PDO::PARAM_INT)),
540 $queryBuilder->expr()->eq('t3ver_move_id', $queryBuilder->createNamedParameter($rows[0]['uid'], \PDO::PARAM_INT))
541 )
542 ->setMaxResults(1)
543 ->execute()
544 ->fetch();
545 if (!empty($movePlaceholder)) {
546 $rows = [$movePlaceholder];
547 }
548 }
549
550 $overlaidRows = [];
551 foreach ($rows as $row) {
552 // If current row is a translation select its parent
553 if (isset($tableName) && isset($GLOBALS['TCA'][$tableName])
554 && isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
555 && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
556 && $tableName !== 'pages_language_overlay'
557 ) {
558 if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
559 && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
560 ) {
561 $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
562 $queryBuilder->getRestrictions()->removeAll();
563 $row = $queryBuilder->select($tableName . '.*')
564 ->from($tableName)
565 ->where(
566 $queryBuilder->expr()->eq(
567 $tableName . '.uid',
568 $queryBuilder->createNamedParameter(
569 $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
570 \PDO::PARAM_INT
571 )
572 ),
573 $queryBuilder->expr()->eq(
574 $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
575 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
576 )
577 )
578 ->setMaxResults(1)
579 ->execute()
580 ->fetch();
581 }
582 }
583 $pageRepository->versionOL($tableName, $row, true);
584 if ($tableName === 'pages') {
585 $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
586 } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
587 && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
588 && $tableName !== 'pages_language_overlay'
589 ) {
590 if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], [-1, 0])) {
591 $overlayMode = $querySettings->getLanguageMode() === 'strict' ? 'hideNonTranslated' : '';
592 $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
593 }
594 }
595 if ($row !== null && is_array($row)) {
596 $overlaidRows[] = $row;
597 }
598 }
599 return $overlaidRows;
600 }
601
602 /**
603 * @return \TYPO3\CMS\Frontend\Page\PageRepository
604 */
605 protected function getPageRepository()
606 {
607 if (!$this->pageRepository instanceof \TYPO3\CMS\Frontend\Page\PageRepository) {
608 if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
609 $this->pageRepository = $GLOBALS['TSFE']->sys_page;
610 } else {
611 $this->pageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
612 }
613 }
614
615 return $this->pageRepository;
616 }
617
618 /**
619 * Clear the TYPO3 page cache for the given record.
620 * If the record lies on a page, then we clear the cache of this page.
621 * If the record has no PID column, we clear the cache of the current page as best-effort.
622 *
623 * Much of this functionality is taken from DataHandler::clear_cache() which unfortunately only works with logged-in BE user.
624 *
625 * @param string $tableName Table name of the record
626 * @param int $uid UID of the record
627 */
628 protected function clearPageCache($tableName, $uid)
629 {
630 $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
631 if (isset($frameworkConfiguration['persistence']['enableAutomaticCacheClearing']) && $frameworkConfiguration['persistence']['enableAutomaticCacheClearing'] === '1') {
632 } else {
633 // if disabled, return
634 return;
635 }
636 $pageIdsToClear = [];
637 $storagePage = null;
638 $columns = GeneralUtility::makeInstance(ConnectionPool::class)
639 ->getConnectionForTable($tableName)
640 ->getSchemaManager()
641 ->listTableColumns($tableName);
642 if (array_key_exists('pid', $columns)) {
643 $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
644 $queryBuilder->getRestrictions()->removeAll();
645 $result = $queryBuilder
646 ->select('pid')
647 ->from($tableName)
648 ->where(
649 $queryBuilder->expr()->eq(
650 'uid',
651 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
652 )
653 )
654 ->execute();
655 if ($row = $result->fetch()) {
656 $storagePage = $row['pid'];
657 $pageIdsToClear[] = $storagePage;
658 }
659 } elseif (isset($GLOBALS['TSFE'])) {
660 // No PID column - we can do a best-effort to clear the cache of the current page if in FE
661 $storagePage = $GLOBALS['TSFE']->id;
662 $pageIdsToClear[] = $storagePage;
663 }
664 if ($storagePage === null) {
665 return;
666 }
667 if (!isset($this->pageTSConfigCache[$storagePage])) {
668 $this->pageTSConfigCache[$storagePage] = BackendUtility::getPagesTSconfig($storagePage);
669 }
670 if (isset($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd'])) {
671 $clearCacheCommands = GeneralUtility::trimExplode(',', strtolower($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd']), true);
672 $clearCacheCommands = array_unique($clearCacheCommands);
673 foreach ($clearCacheCommands as $clearCacheCommand) {
674 if (MathUtility::canBeInterpretedAsInteger($clearCacheCommand)) {
675 $pageIdsToClear[] = $clearCacheCommand;
676 }
677 }
678 }
679
680 foreach ($pageIdsToClear as $pageIdToClear) {
681 $this->cacheService->getPageIdStack()->push($pageIdToClear);
682 }
683 }
684 }