| Total Complexity | 106 |
| Total Lines | 623 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like Typo3DbBackend often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Typo3DbBackend, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 54 | class Typo3DbBackend implements BackendInterface, SingletonInterface |
||
| 55 | { |
||
| 56 | protected ConnectionPool $connectionPool; |
||
| 57 | protected ConfigurationManagerInterface $configurationManager; |
||
| 58 | protected CacheService $cacheService; |
||
| 59 | |||
| 60 | /** |
||
| 61 | * As determining the table columns is a costly operation this is done only once per table during runtime and cached then |
||
| 62 | * |
||
| 63 | * @see clearPageCache() |
||
| 64 | */ |
||
| 65 | protected array $hasPidColumn = []; |
||
| 66 | |||
| 67 | public function __construct(CacheService $cacheService, ConfigurationManagerInterface $configurationManager) |
||
| 68 | { |
||
| 69 | $this->cacheService = $cacheService; |
||
| 70 | $this->configurationManager = $configurationManager; |
||
| 71 | $this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); |
||
| 72 | } |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Adds a row to the storage |
||
| 76 | * |
||
| 77 | * @param string $tableName The database table name |
||
| 78 | * @param array $fieldValues The row to be inserted |
||
| 79 | * @param bool $isRelation TRUE if we are currently inserting into a relation table, FALSE by default |
||
| 80 | * @return int The uid of the inserted row |
||
| 81 | * @throws SqlErrorException |
||
| 82 | */ |
||
| 83 | public function addRow(string $tableName, array $fieldValues, bool $isRelation = false): int |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Updates a row in the storage |
||
| 117 | * |
||
| 118 | * @param string $tableName The database table name |
||
| 119 | * @param array $fieldValues The row to be updated |
||
| 120 | * @param bool $isRelation TRUE if we are currently inserting into a relation table, FALSE by default |
||
| 121 | * @throws \InvalidArgumentException |
||
| 122 | * @throws SqlErrorException |
||
| 123 | */ |
||
| 124 | public function updateRow(string $tableName, array $fieldValues, bool $isRelation = false): void |
||
| 125 | { |
||
| 126 | if (!isset($fieldValues['uid'])) { |
||
| 127 | throw new \InvalidArgumentException('The given row must contain a value for "uid".', 1476045164); |
||
| 128 | } |
||
| 129 | |||
| 130 | $uid = (int)$fieldValues['uid']; |
||
| 131 | unset($fieldValues['uid']); |
||
| 132 | |||
| 133 | try { |
||
| 134 | $connection = $this->connectionPool->getConnectionForTable($tableName); |
||
| 135 | |||
| 136 | $types = []; |
||
| 137 | $platform = $connection->getDatabasePlatform(); |
||
| 138 | if ($platform instanceof SQLServerPlatform) { |
||
| 139 | // mssql needs to set proper PARAM_LOB and others to update fields |
||
| 140 | $tableDetails = $connection->getSchemaManager()->listTableDetails($tableName); |
||
| 141 | foreach ($fieldValues as $columnName => $columnValue) { |
||
| 142 | $columnName = (string)$columnName; |
||
| 143 | $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); |
||
| 144 | } |
||
| 145 | } |
||
| 146 | |||
| 147 | $connection->update($tableName, $fieldValues, ['uid' => $uid], $types); |
||
| 148 | } catch (DBALException $e) { |
||
| 149 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230767, $e); |
||
| 150 | } |
||
| 151 | |||
| 152 | if (!$isRelation) { |
||
| 153 | $this->clearPageCache($tableName, $uid); |
||
| 154 | } |
||
| 155 | } |
||
| 156 | |||
| 157 | /** |
||
| 158 | * Updates a relation row in the storage. |
||
| 159 | * |
||
| 160 | * @param string $tableName The database relation table name |
||
| 161 | * @param array $fieldValues The row to be updated |
||
| 162 | * @throws SqlErrorException |
||
| 163 | * @throws \InvalidArgumentException |
||
| 164 | */ |
||
| 165 | public function updateRelationTableRow(string $tableName, array $fieldValues): void |
||
| 166 | { |
||
| 167 | if (!isset($fieldValues['uid_local']) && !isset($fieldValues['uid_foreign'])) { |
||
| 168 | throw new \InvalidArgumentException( |
||
| 169 | 'The given fieldValues must contain a value for "uid_local" and "uid_foreign".', |
||
| 170 | 1360500126 |
||
| 171 | ); |
||
| 172 | } |
||
| 173 | |||
| 174 | $where = []; |
||
| 175 | $where['uid_local'] = (int)$fieldValues['uid_local']; |
||
| 176 | $where['uid_foreign'] = (int)$fieldValues['uid_foreign']; |
||
| 177 | unset($fieldValues['uid_local']); |
||
| 178 | unset($fieldValues['uid_foreign']); |
||
| 179 | |||
| 180 | if (!empty($fieldValues['tablenames'])) { |
||
| 181 | $where['tablenames'] = $fieldValues['tablenames']; |
||
| 182 | unset($fieldValues['tablenames']); |
||
| 183 | } |
||
| 184 | if (!empty($fieldValues['fieldname'])) { |
||
| 185 | $where['fieldname'] = $fieldValues['fieldname']; |
||
| 186 | unset($fieldValues['fieldname']); |
||
| 187 | } |
||
| 188 | |||
| 189 | try { |
||
| 190 | $this->connectionPool->getConnectionForTable($tableName)->update($tableName, $fieldValues, $where); |
||
| 191 | } catch (DBALException $e) { |
||
| 192 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230768, $e); |
||
| 193 | } |
||
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Deletes a row in the storage |
||
| 198 | * |
||
| 199 | * @param string $tableName The database table name |
||
| 200 | * @param array $where An array of where array('fieldname' => value). |
||
| 201 | * @param bool $isRelation TRUE if we are currently manipulating a relation table, FALSE by default |
||
| 202 | * @throws SqlErrorException |
||
| 203 | */ |
||
| 204 | public function removeRow(string $tableName, array $where, bool $isRelation = false): void |
||
| 205 | { |
||
| 206 | try { |
||
| 207 | $this->connectionPool->getConnectionForTable($tableName)->delete($tableName, $where); |
||
| 208 | } catch (DBALException $e) { |
||
| 209 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230769, $e); |
||
| 210 | } |
||
| 211 | |||
| 212 | if (!$isRelation && isset($where['uid'])) { |
||
| 213 | $this->clearPageCache($tableName, (int)$where['uid']); |
||
| 214 | } |
||
| 215 | } |
||
| 216 | |||
| 217 | /** |
||
| 218 | * Returns the object data matching the $query. |
||
| 219 | * |
||
| 220 | * @param QueryInterface $query |
||
| 221 | * @return array |
||
| 222 | * @throws SqlErrorException |
||
| 223 | */ |
||
| 224 | public function getObjectDataByQuery(QueryInterface $query): array |
||
| 264 | } |
||
| 265 | |||
| 266 | /** |
||
| 267 | * Returns the object data using a custom statement |
||
| 268 | * |
||
| 269 | * @param Qom\Statement $statement |
||
| 270 | * @return array |
||
| 271 | * @throws SqlErrorException when the raw SQL statement fails in the database |
||
| 272 | */ |
||
| 273 | protected function getObjectDataByRawQuery(Statement $statement): array |
||
| 274 | { |
||
| 275 | $realStatement = $statement->getStatement(); |
||
| 276 | $parameters = $statement->getBoundVariables(); |
||
| 277 | |||
| 278 | // The real statement is an instance of the Doctrine DBAL QueryBuilder, so fetching |
||
| 279 | // this directly is possible |
||
| 280 | if ($realStatement instanceof QueryBuilder) { |
||
| 281 | try { |
||
| 282 | $result = $realStatement->execute(); |
||
| 283 | } catch (DBALException $e) { |
||
| 284 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064721, $e); |
||
| 285 | } |
||
| 286 | $rows = $result->fetchAll(); |
||
| 287 | } elseif ($realStatement instanceof \Doctrine\DBAL\Statement) { |
||
| 288 | try { |
||
| 289 | $realStatement->execute($parameters); |
||
| 290 | } catch (DBALException $e) { |
||
| 291 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1481281404, $e); |
||
| 292 | } |
||
| 293 | $rows = $realStatement->fetchAll(); |
||
| 294 | } else { |
||
| 295 | // Do a real raw query. This is very stupid, as it does not allow to use DBAL's real power if |
||
| 296 | // several tables are on different databases, so this is used with caution and could be removed |
||
| 297 | // in the future |
||
| 298 | try { |
||
| 299 | $connection = $this->connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); |
||
| 300 | $statement = $connection->executeQuery($realStatement, $parameters); |
||
| 301 | } catch (DBALException $e) { |
||
| 302 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064775, $e); |
||
| 303 | } |
||
| 304 | |||
| 305 | $rows = $statement->fetchAll(); |
||
| 306 | } |
||
| 307 | |||
| 308 | return $rows; |
||
| 309 | } |
||
| 310 | |||
| 311 | /** |
||
| 312 | * Returns the number of tuples matching the query. |
||
| 313 | * |
||
| 314 | * @param QueryInterface $query |
||
| 315 | * @return int The number of matching tuples |
||
| 316 | * @throws BadConstraintException |
||
| 317 | * @throws SqlErrorException |
||
| 318 | */ |
||
| 319 | public function getObjectCountByQuery(QueryInterface $query): int |
||
| 320 | { |
||
| 321 | if ($query->getConstraint() instanceof Statement) { |
||
| 322 | throw new BadConstraintException('Could not execute count on queries with a constraint of type TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Qom\\Statement', 1256661045); |
||
| 323 | } |
||
| 324 | |||
| 325 | $statement = $query->getStatement(); |
||
| 326 | if ($statement instanceof Statement |
||
| 327 | && !$statement->getStatement() instanceof QueryBuilder |
||
| 328 | ) { |
||
| 329 | $rows = $this->getObjectDataByQuery($query); |
||
| 330 | $count = count($rows); |
||
| 331 | } else { |
||
| 332 | $queryParser = GeneralUtility::makeInstance(Typo3DbQueryParser::class); |
||
| 333 | $queryBuilder = $queryParser |
||
| 334 | ->convertQueryToDoctrineQueryBuilder($query) |
||
| 335 | ->resetQueryPart('orderBy'); |
||
| 336 | |||
| 337 | if ($queryParser->isDistinctQuerySuggested()) { |
||
| 338 | $source = $queryBuilder->getQueryPart('from')[0]; |
||
| 339 | // Tablename is already quoted for the DBMS, we need to treat table and field names separately |
||
| 340 | $tableName = $source['alias'] ?: $source['table']; |
||
| 341 | $fieldName = $queryBuilder->quoteIdentifier('uid'); |
||
| 342 | $queryBuilder->resetQueryPart('groupBy') |
||
| 343 | ->selectLiteral(sprintf('COUNT(DISTINCT %s.%s)', $tableName, $fieldName)); |
||
| 344 | } else { |
||
| 345 | $queryBuilder->count('*'); |
||
| 346 | } |
||
| 347 | |||
| 348 | try { |
||
| 349 | $count = $queryBuilder->execute()->fetchColumn(0); |
||
| 350 | } catch (DBALException $e) { |
||
| 351 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1472074379, $e); |
||
| 352 | } |
||
| 353 | if ($query->getOffset()) { |
||
| 354 | $count -= $query->getOffset(); |
||
| 355 | } |
||
| 356 | if ($query->getLimit()) { |
||
| 357 | $count = min($count, $query->getLimit()); |
||
| 358 | } |
||
| 359 | } |
||
| 360 | return (int)max(0, $count); |
||
| 361 | } |
||
| 362 | |||
| 363 | /** |
||
| 364 | * Checks if a Value Object equal to the given Object exists in the database |
||
| 365 | * |
||
| 366 | * @param AbstractValueObject $object The Value Object |
||
| 367 | * @return int|null The matching uid if an object was found, else FALSE |
||
| 368 | * @throws SqlErrorException |
||
| 369 | */ |
||
| 370 | public function getUidOfAlreadyPersistedValueObject(AbstractValueObject $object): ?int |
||
| 371 | { |
||
| 372 | $dataMapper = GeneralUtility::makeInstance(DataMapper::class); |
||
| 373 | $dataMap = $dataMapper->getDataMap(get_class($object)); |
||
| 374 | $tableName = $dataMap->getTableName(); |
||
| 375 | $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName); |
||
| 376 | if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface |
||
| 377 | && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() |
||
| 378 | ) { |
||
| 379 | $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); |
||
| 380 | } |
||
| 381 | $whereClause = []; |
||
| 382 | // loop over all properties of the object to exactly set the values of each database field |
||
| 383 | $properties = $object->_getProperties(); |
||
| 384 | foreach ($properties as $propertyName => $propertyValue) { |
||
| 385 | $propertyName = (string)$propertyName; |
||
| 386 | |||
| 387 | // @todo We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method |
||
| 388 | if ($dataMap->isPersistableProperty($propertyName) && $propertyName !== 'uid' && $propertyName !== 'pid' && $propertyName !== 'isClone') { |
||
| 389 | $fieldName = $dataMap->getColumnMap($propertyName)->getColumnName(); |
||
| 390 | if ($propertyValue === null) { |
||
| 391 | $whereClause[] = $queryBuilder->expr()->isNull($fieldName); |
||
| 392 | } else { |
||
| 393 | $whereClause[] = $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($dataMapper->getPlainValue($propertyValue))); |
||
| 394 | } |
||
| 395 | } |
||
| 396 | } |
||
| 397 | $queryBuilder |
||
| 398 | ->select('uid') |
||
| 399 | ->from($tableName) |
||
| 400 | ->where(...$whereClause); |
||
| 401 | |||
| 402 | try { |
||
| 403 | $uid = (int)$queryBuilder |
||
| 404 | ->execute() |
||
| 405 | ->fetchColumn(0); |
||
| 406 | if ($uid > 0) { |
||
| 407 | return $uid; |
||
| 408 | } |
||
| 409 | return null; |
||
| 410 | } catch (DBALException $e) { |
||
| 411 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470231748, $e); |
||
| 412 | } |
||
| 413 | } |
||
| 414 | |||
| 415 | /** |
||
| 416 | * Performs workspace and language overlay on the given row array. The language and workspace id is automatically |
||
| 417 | * detected (depending on FE or BE context). You can also explicitly set the language/workspace id. |
||
| 418 | * |
||
| 419 | * @param Qom\SourceInterface $source The source (selector or join) |
||
| 420 | * @param array $rows |
||
| 421 | * @param QueryInterface $query |
||
| 422 | * @param int|null $workspaceUid |
||
| 423 | * @return array |
||
| 424 | * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException |
||
| 425 | */ |
||
| 426 | protected function overlayLanguageAndWorkspace(SourceInterface $source, array $rows, QueryInterface $query, int $workspaceUid = null): array |
||
| 455 | } |
||
| 456 | |||
| 457 | /** |
||
| 458 | * If the result is a plain SELECT (no JOIN) then the regular overlay process works for tables |
||
| 459 | * - overlay workspace |
||
| 460 | * - overlay language of versioned record again |
||
| 461 | */ |
||
| 462 | protected function overlayLanguageAndWorkspaceForSelect(string $tableName, array $rows, PageRepository $pageRepository, QueryInterface $query): array |
||
| 463 | { |
||
| 464 | $overlaidRows = []; |
||
| 465 | foreach ($rows as $row) { |
||
| 466 | $row = $this->overlayLanguageAndWorkspaceForSingleRecord($tableName, $row, $pageRepository, $query); |
||
| 467 | if (is_array($row)) { |
||
| 468 | $overlaidRows[] = $row; |
||
| 469 | } |
||
| 470 | } |
||
| 471 | return $overlaidRows; |
||
| 472 | } |
||
| 473 | |||
| 474 | /** |
||
| 475 | * If the result consists of a JOIN (usually happens if a property is a relation with a MM table) then it is necessary |
||
| 476 | * to only do overlays for the fields that are contained in the main database table, otherwise a SQL error is thrown. |
||
| 477 | * In order to make this happen, a single SQL query is made to fetch all possible field names (= array keys) of |
||
| 478 | * a record (TCA[$tableName][columns] does not contain all needed information), which is then used to compute |
||
| 479 | * a separate subset of the row which can be overlaid properly. |
||
| 480 | */ |
||
| 481 | protected function overlayLanguageAndWorkspaceForJoinedSelect(string $tableName, array $rows, PageRepository $pageRepository, QueryInterface $query): array |
||
| 482 | { |
||
| 483 | // No valid rows, so this is skipped |
||
| 484 | if (!isset($rows[0]['uid'])) { |
||
| 485 | return $rows; |
||
| 486 | } |
||
| 487 | // First, find out the fields that belong to the "main" selected table which is defined by TCA, and take the first |
||
| 488 | // record to find out all possible fields in this database table |
||
| 489 | $fieldsOfMainTable = $pageRepository->getRawRecord($tableName, $rows[0]['uid']); |
||
| 490 | $overlaidRows = []; |
||
| 491 | foreach ($rows as $row) { |
||
| 492 | $mainRow = array_intersect_key($row, $fieldsOfMainTable); |
||
|
|
|||
| 493 | $joinRow = array_diff_key($row, $mainRow); |
||
| 494 | $mainRow = $this->overlayLanguageAndWorkspaceForSingleRecord($tableName, $mainRow, $pageRepository, $query); |
||
| 495 | if (is_array($mainRow)) { |
||
| 496 | $overlaidRows[] = array_replace($joinRow, $mainRow); |
||
| 497 | } |
||
| 498 | } |
||
| 499 | return $overlaidRows; |
||
| 500 | } |
||
| 501 | |||
| 502 | /** |
||
| 503 | * Takes one specific row, as defined in TCA and does all overlays. |
||
| 504 | * |
||
| 505 | * @param string $tableName |
||
| 506 | * @param array $row |
||
| 507 | * @param PageRepository $pageRepository |
||
| 508 | * @param QueryInterface $query |
||
| 509 | * @return array|int|mixed|null the overlaid row or false or null if overlay failed. |
||
| 510 | */ |
||
| 511 | protected function overlayLanguageAndWorkspaceForSingleRecord(string $tableName, array $row, PageRepository $pageRepository, QueryInterface $query) |
||
| 512 | { |
||
| 513 | $querySettings = $query->getQuerySettings(); |
||
| 514 | // If current row is a translation select its parent |
||
| 515 | $languageOfCurrentRecord = 0; |
||
| 516 | if ($GLOBALS['TCA'][$tableName]['ctrl']['languageField'] ?? null |
||
| 517 | && $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] ?? 0 |
||
| 518 | ) { |
||
| 519 | $languageOfCurrentRecord = $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']]; |
||
| 520 | } |
||
| 521 | if ($querySettings->getLanguageOverlayMode() |
||
| 522 | && $languageOfCurrentRecord > 0 |
||
| 523 | && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) |
||
| 524 | && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0 |
||
| 525 | ) { |
||
| 526 | $row = $pageRepository->getRawRecord( |
||
| 527 | $tableName, |
||
| 528 | (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] |
||
| 529 | ); |
||
| 530 | } |
||
| 531 | // Handle workspace overlays |
||
| 532 | $pageRepository->versionOL($tableName, $row, true); |
||
| 533 | if (is_array($row) && $querySettings->getLanguageOverlayMode()) { |
||
| 534 | if ($tableName === 'pages') { |
||
| 535 | $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid()); |
||
| 536 | } else { |
||
| 537 | // todo: remove type cast once getLanguageUid strictly returns an int |
||
| 538 | $languageUid = (int)$querySettings->getLanguageUid(); |
||
| 539 | if (!$querySettings->getRespectSysLanguage() |
||
| 540 | && $languageOfCurrentRecord > 0 |
||
| 541 | && (!$query instanceof Query || !$query->getParentQuery()) |
||
| 542 | ) { |
||
| 543 | // No parent query means we're processing the aggregate root. |
||
| 544 | // respectSysLanguage is false which means that records returned by the query |
||
| 545 | // might be from different languages (which is desired). |
||
| 546 | // So we must set the language used for overlay to the language of the current record |
||
| 547 | $languageUid = $languageOfCurrentRecord; |
||
| 548 | } |
||
| 549 | if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) |
||
| 550 | && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0 |
||
| 551 | && $languageOfCurrentRecord > 0 |
||
| 552 | ) { |
||
| 553 | // Force overlay by faking default language record, as getRecordOverlay can only handle default language records |
||
| 554 | $row['uid'] = $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']]; |
||
| 555 | $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = 0; |
||
| 556 | } |
||
| 557 | $row = $pageRepository->getRecordOverlay($tableName, $row, $languageUid, (string)$querySettings->getLanguageOverlayMode()); |
||
| 558 | } |
||
| 559 | } |
||
| 560 | return $row; |
||
| 561 | } |
||
| 562 | |||
| 563 | /** |
||
| 564 | * Fetches the moved record in case it is supported |
||
| 565 | * by the table and if there's only one row in the result set |
||
| 566 | * (applying this to all rows does not work, since the sorting |
||
| 567 | * order would be destroyed and possible limits are not met anymore) |
||
| 568 | * The move pointers are later unset (see versionOL() last argument) |
||
| 569 | */ |
||
| 570 | protected function resolveMovedRecordsInWorkspace(string $tableName, array $rows, int $workspaceUid): array |
||
| 571 | { |
||
| 572 | if ($workspaceUid === 0) { |
||
| 573 | return $rows; |
||
| 574 | } |
||
| 575 | if (!BackendUtility::isTableWorkspaceEnabled($tableName)) { |
||
| 576 | return $rows; |
||
| 577 | } |
||
| 578 | if (count($rows) !== 1) { |
||
| 579 | return $rows; |
||
| 580 | } |
||
| 581 | $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName); |
||
| 582 | $queryBuilder->getRestrictions()->removeAll(); |
||
| 583 | $movedRecords = $queryBuilder |
||
| 584 | ->select('*') |
||
| 585 | ->from($tableName) |
||
| 586 | ->where( |
||
| 587 | $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(VersionState::MOVE_POINTER, \PDO::PARAM_INT)), |
||
| 588 | $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($workspaceUid, \PDO::PARAM_INT)), |
||
| 589 | $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($rows[0]['uid'], \PDO::PARAM_INT)) |
||
| 590 | ) |
||
| 591 | ->setMaxResults(1) |
||
| 592 | ->execute() |
||
| 593 | ->fetchAll(); |
||
| 594 | if (!empty($movedRecords)) { |
||
| 595 | $rows = $movedRecords; |
||
| 596 | } |
||
| 597 | return $rows; |
||
| 598 | } |
||
| 599 | |||
| 600 | /** |
||
| 601 | * Clear the TYPO3 page cache for the given record. |
||
| 602 | * If the record lies on a page, then we clear the cache of this page. |
||
| 603 | * If the record has no PID column, we clear the cache of the current page as best-effort. |
||
| 604 | * |
||
| 605 | * Much of this functionality is taken from DataHandler::clear_cache() which unfortunately only works with logged-in BE user. |
||
| 606 | * |
||
| 607 | * @param string $tableName Table name of the record |
||
| 608 | * @param int $uid UID of the record |
||
| 609 | */ |
||
| 610 | protected function clearPageCache(string $tableName, int $uid): void |
||
| 611 | { |
||
| 612 | $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK); |
||
| 613 | if (empty($frameworkConfiguration['persistence']['enableAutomaticCacheClearing'])) { |
||
| 614 | return; |
||
| 615 | } |
||
| 616 | $pageIdsToClear = []; |
||
| 617 | $storagePage = null; |
||
| 618 | |||
| 619 | // As determining the table columns is a costly operation this is done only once per table during runtime and cached then |
||
| 620 | if (!isset($this->hasPidColumn[$tableName])) { |
||
| 621 | $columns = $this->connectionPool |
||
| 622 | ->getConnectionForTable($tableName) |
||
| 623 | ->getSchemaManager() |
||
| 624 | ->listTableColumns($tableName); |
||
| 625 | $this->hasPidColumn[$tableName] = array_key_exists('pid', $columns); |
||
| 626 | } |
||
| 627 | |||
| 628 | $tsfe = $this->getTSFE(); |
||
| 629 | if ($this->hasPidColumn[$tableName]) { |
||
| 630 | $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName); |
||
| 631 | $queryBuilder->getRestrictions()->removeAll(); |
||
| 632 | $result = $queryBuilder |
||
| 633 | ->select('pid') |
||
| 634 | ->from($tableName) |
||
| 635 | ->where( |
||
| 636 | $queryBuilder->expr()->eq( |
||
| 637 | 'uid', |
||
| 638 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
| 639 | ) |
||
| 640 | ) |
||
| 641 | ->execute(); |
||
| 642 | if ($row = $result->fetch()) { |
||
| 643 | $storagePage = $row['pid']; |
||
| 644 | $pageIdsToClear[] = $storagePage; |
||
| 645 | } |
||
| 646 | } elseif (isset($tsfe)) { |
||
| 647 | // No PID column - we can do a best-effort to clear the cache of the current page if in FE |
||
| 648 | $storagePage = $tsfe->id; |
||
| 649 | $pageIdsToClear[] = $storagePage; |
||
| 650 | } |
||
| 651 | if ($storagePage === null) { |
||
| 652 | return; |
||
| 653 | } |
||
| 654 | |||
| 655 | $pageTS = BackendUtility::getPagesTSconfig($storagePage); |
||
| 656 | if (isset($pageTS['TCEMAIN.']['clearCacheCmd'])) { |
||
| 657 | $clearCacheCommands = GeneralUtility::trimExplode(',', strtolower($pageTS['TCEMAIN.']['clearCacheCmd']), true); |
||
| 658 | $clearCacheCommands = array_unique($clearCacheCommands); |
||
| 659 | foreach ($clearCacheCommands as $clearCacheCommand) { |
||
| 660 | if (MathUtility::canBeInterpretedAsInteger($clearCacheCommand)) { |
||
| 661 | $pageIdsToClear[] = $clearCacheCommand; |
||
| 662 | } |
||
| 663 | } |
||
| 664 | } |
||
| 665 | |||
| 666 | foreach ($pageIdsToClear as $pageIdToClear) { |
||
| 667 | $this->cacheService->getPageIdStack()->push($pageIdToClear); |
||
| 668 | } |
||
| 669 | } |
||
| 670 | |||
| 671 | /** |
||
| 672 | * @return TypoScriptFrontendController|null |
||
| 673 | */ |
||
| 674 | protected function getTSFE(): ?TypoScriptFrontendController |
||
| 677 | } |
||
| 678 | } |
||
| 679 |