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