| Total Complexity | 109 |
| Total Lines | 668 |
| 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 |
||
| 55 | class Typo3DbBackend implements BackendInterface, SingletonInterface |
||
| 56 | { |
||
| 57 | /** |
||
| 58 | * @var ConnectionPool |
||
| 59 | */ |
||
| 60 | protected $connectionPool; |
||
| 61 | |||
| 62 | /** |
||
| 63 | * @var ConfigurationManagerInterface |
||
| 64 | */ |
||
| 65 | protected $configurationManager; |
||
| 66 | |||
| 67 | /** |
||
| 68 | * @var CacheService |
||
| 69 | */ |
||
| 70 | protected $cacheService; |
||
| 71 | |||
| 72 | /** |
||
| 73 | * @var ObjectManagerInterface |
||
| 74 | */ |
||
| 75 | protected $objectManager; |
||
| 76 | |||
| 77 | /** |
||
| 78 | * As determining the table columns is a costly operation this is done only once per table during runtime and cached then |
||
| 79 | * |
||
| 80 | * @var array |
||
| 81 | * @see clearPageCache() |
||
| 82 | */ |
||
| 83 | protected $hasPidColumn = []; |
||
| 84 | |||
| 85 | /** |
||
| 86 | * @param ConfigurationManagerInterface $configurationManager |
||
| 87 | */ |
||
| 88 | public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void |
||
| 89 | { |
||
| 90 | $this->configurationManager = $configurationManager; |
||
| 91 | } |
||
| 92 | |||
| 93 | /** |
||
| 94 | * @param CacheService $cacheService |
||
| 95 | */ |
||
| 96 | public function injectCacheService(CacheService $cacheService): void |
||
| 97 | { |
||
| 98 | $this->cacheService = $cacheService; |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * @param ObjectManagerInterface $objectManager |
||
| 103 | */ |
||
| 104 | public function injectObjectManager(ObjectManagerInterface $objectManager): void |
||
| 105 | { |
||
| 106 | $this->objectManager = $objectManager; |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Constructor. |
||
| 111 | */ |
||
| 112 | public function __construct() |
||
| 113 | { |
||
| 114 | $this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); |
||
| 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 int The uid of the inserted row |
||
| 124 | * @throws SqlErrorException |
||
| 125 | */ |
||
| 126 | public function addRow(string $tableName, array $fieldValues, bool $isRelation = false): int |
||
| 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 | */ |
||
| 167 | public function updateRow(string $tableName, array $fieldValues, bool $isRelation = false): void |
||
| 168 | { |
||
| 169 | if (!isset($fieldValues['uid'])) { |
||
| 170 | throw new \InvalidArgumentException('The given row must contain a value for "uid".', 1476045164); |
||
| 171 | } |
||
| 172 | |||
| 173 | $uid = (int)$fieldValues['uid']; |
||
| 174 | unset($fieldValues['uid']); |
||
| 175 | |||
| 176 | try { |
||
| 177 | $connection = $this->connectionPool->getConnectionForTable($tableName); |
||
| 178 | |||
| 179 | $types = []; |
||
| 180 | $platform = $connection->getDatabasePlatform(); |
||
| 181 | if ($platform instanceof SQLServerPlatform) { |
||
| 182 | // mssql needs to set proper PARAM_LOB and others to update fields |
||
| 183 | $tableDetails = $connection->getSchemaManager()->listTableDetails($tableName); |
||
| 184 | foreach ($fieldValues as $columnName => $columnValue) { |
||
| 185 | $columnName = (string)$columnName; |
||
| 186 | $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); |
||
| 187 | } |
||
| 188 | } |
||
| 189 | |||
| 190 | $connection->update($tableName, $fieldValues, ['uid' => $uid], $types); |
||
| 191 | } catch (DBALException $e) { |
||
| 192 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230767, $e); |
||
| 193 | } |
||
| 194 | |||
| 195 | if (!$isRelation) { |
||
| 196 | $this->clearPageCache($tableName, $uid); |
||
| 197 | } |
||
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Updates a relation row in the storage. |
||
| 202 | * |
||
| 203 | * @param string $tableName The database relation table name |
||
| 204 | * @param array $fieldValues The row to be updated |
||
| 205 | * @throws SqlErrorException |
||
| 206 | * @throws \InvalidArgumentException |
||
| 207 | */ |
||
| 208 | public function updateRelationTableRow(string $tableName, array $fieldValues): void |
||
| 209 | { |
||
| 210 | if (!isset($fieldValues['uid_local']) && !isset($fieldValues['uid_foreign'])) { |
||
| 211 | throw new \InvalidArgumentException( |
||
| 212 | 'The given fieldValues must contain a value for "uid_local" and "uid_foreign".', |
||
| 213 | 1360500126 |
||
| 214 | ); |
||
| 215 | } |
||
| 216 | |||
| 217 | $where = []; |
||
| 218 | $where['uid_local'] = (int)$fieldValues['uid_local']; |
||
| 219 | $where['uid_foreign'] = (int)$fieldValues['uid_foreign']; |
||
| 220 | unset($fieldValues['uid_local']); |
||
| 221 | unset($fieldValues['uid_foreign']); |
||
| 222 | |||
| 223 | if (!empty($fieldValues['tablenames'])) { |
||
| 224 | $where['tablenames'] = $fieldValues['tablenames']; |
||
| 225 | unset($fieldValues['tablenames']); |
||
| 226 | } |
||
| 227 | if (!empty($fieldValues['fieldname'])) { |
||
| 228 | $where['fieldname'] = $fieldValues['fieldname']; |
||
| 229 | unset($fieldValues['fieldname']); |
||
| 230 | } |
||
| 231 | |||
| 232 | try { |
||
| 233 | $this->connectionPool->getConnectionForTable($tableName)->update($tableName, $fieldValues, $where); |
||
| 234 | } catch (DBALException $e) { |
||
| 235 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230768, $e); |
||
| 236 | } |
||
| 237 | } |
||
| 238 | |||
| 239 | /** |
||
| 240 | * Deletes a row in the storage |
||
| 241 | * |
||
| 242 | * @param string $tableName The database table name |
||
| 243 | * @param array $where An array of where array('fieldname' => value). |
||
| 244 | * @param bool $isRelation TRUE if we are currently manipulating a relation table, FALSE by default |
||
| 245 | * @throws SqlErrorException |
||
| 246 | */ |
||
| 247 | public function removeRow(string $tableName, array $where, bool $isRelation = false): void |
||
| 248 | { |
||
| 249 | try { |
||
| 250 | $this->connectionPool->getConnectionForTable($tableName)->delete($tableName, $where); |
||
| 251 | } catch (DBALException $e) { |
||
| 252 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230769, $e); |
||
| 253 | } |
||
| 254 | |||
| 255 | if (!$isRelation && isset($where['uid'])) { |
||
| 256 | $this->clearPageCache($tableName, (int)$where['uid']); |
||
| 257 | } |
||
| 258 | } |
||
| 259 | |||
| 260 | /** |
||
| 261 | * Returns the object data matching the $query. |
||
| 262 | * |
||
| 263 | * @param QueryInterface $query |
||
| 264 | * @return array |
||
| 265 | * @throws SqlErrorException |
||
| 266 | */ |
||
| 267 | public function getObjectDataByQuery(QueryInterface $query): array |
||
| 308 | } |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Returns the object data using a custom statement |
||
| 312 | * |
||
| 313 | * @param Qom\Statement $statement |
||
| 314 | * @return array |
||
| 315 | * @throws SqlErrorException when the raw SQL statement fails in the database |
||
| 316 | */ |
||
| 317 | protected function getObjectDataByRawQuery(Statement $statement): array |
||
| 318 | { |
||
| 319 | $realStatement = $statement->getStatement(); |
||
| 320 | $parameters = $statement->getBoundVariables(); |
||
| 321 | |||
| 322 | // The real statement is an instance of the Doctrine DBAL QueryBuilder, so fetching |
||
| 323 | // this directly is possible |
||
| 324 | if ($realStatement instanceof QueryBuilder) { |
||
| 325 | try { |
||
| 326 | $result = $realStatement->execute(); |
||
| 327 | } catch (DBALException $e) { |
||
| 328 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064721, $e); |
||
| 329 | } |
||
| 330 | $rows = $result->fetchAll(); |
||
| 331 | } elseif ($realStatement instanceof \Doctrine\DBAL\Statement) { |
||
| 332 | try { |
||
| 333 | $realStatement->execute($parameters); |
||
| 334 | } catch (DBALException $e) { |
||
| 335 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1481281404, $e); |
||
| 336 | } |
||
| 337 | $rows = $realStatement->fetchAll(); |
||
| 338 | } else { |
||
| 339 | // Do a real raw query. This is very stupid, as it does not allow to use DBAL's real power if |
||
| 340 | // several tables are on different databases, so this is used with caution and could be removed |
||
| 341 | // in the future |
||
| 342 | try { |
||
| 343 | $connection = $this->connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); |
||
| 344 | $statement = $connection->executeQuery($realStatement, $parameters); |
||
| 345 | } catch (DBALException $e) { |
||
| 346 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064775, $e); |
||
| 347 | } |
||
| 348 | |||
| 349 | $rows = $statement->fetchAll(); |
||
| 350 | } |
||
| 351 | |||
| 352 | return $rows; |
||
| 353 | } |
||
| 354 | |||
| 355 | /** |
||
| 356 | * Returns the number of tuples matching the query. |
||
| 357 | * |
||
| 358 | * @param QueryInterface $query |
||
| 359 | * @return int The number of matching tuples |
||
| 360 | * @throws BadConstraintException |
||
| 361 | * @throws SqlErrorException |
||
| 362 | */ |
||
| 363 | public function getObjectCountByQuery(QueryInterface $query): int |
||
| 364 | { |
||
| 365 | if ($query->getConstraint() instanceof Statement) { |
||
| 366 | throw new BadConstraintException('Could not execute count on queries with a constraint of type TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Qom\\Statement', 1256661045); |
||
| 367 | } |
||
| 368 | |||
| 369 | $statement = $query->getStatement(); |
||
| 370 | if ($statement instanceof Statement |
||
| 371 | && !$statement->getStatement() instanceof QueryBuilder |
||
| 372 | ) { |
||
| 373 | $rows = $this->getObjectDataByQuery($query); |
||
| 374 | $count = count($rows); |
||
| 375 | } else { |
||
| 376 | /** @var Typo3DbQueryParser $queryParser */ |
||
| 377 | $queryParser = $this->objectManager->get(Typo3DbQueryParser::class); |
||
| 378 | $queryBuilder = $queryParser |
||
| 379 | ->convertQueryToDoctrineQueryBuilder($query) |
||
| 380 | ->resetQueryPart('orderBy'); |
||
| 381 | |||
| 382 | if ($queryParser->isDistinctQuerySuggested()) { |
||
| 383 | $source = $queryBuilder->getQueryPart('from')[0]; |
||
| 384 | // Tablename is already quoted for the DBMS, we need to treat table and field names separately |
||
| 385 | $tableName = $source['alias'] ?: $source['table']; |
||
| 386 | $fieldName = $queryBuilder->quoteIdentifier('uid'); |
||
| 387 | $queryBuilder->resetQueryPart('groupBy') |
||
| 388 | ->selectLiteral(sprintf('COUNT(DISTINCT %s.%s)', $tableName, $fieldName)); |
||
| 389 | } else { |
||
| 390 | $queryBuilder->count('*'); |
||
| 391 | } |
||
| 392 | |||
| 393 | try { |
||
| 394 | $count = $queryBuilder->execute()->fetchColumn(0); |
||
| 395 | } catch (DBALException $e) { |
||
| 396 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1472074379, $e); |
||
| 397 | } |
||
| 398 | if ($query->getOffset()) { |
||
| 399 | $count -= $query->getOffset(); |
||
| 400 | } |
||
| 401 | if ($query->getLimit()) { |
||
| 402 | $count = min($count, $query->getLimit()); |
||
| 403 | } |
||
| 404 | } |
||
| 405 | return (int)max(0, $count); |
||
| 406 | } |
||
| 407 | |||
| 408 | /** |
||
| 409 | * Checks if a Value Object equal to the given Object exists in the database |
||
| 410 | * |
||
| 411 | * @param AbstractValueObject $object The Value Object |
||
| 412 | * @return int|null The matching uid if an object was found, else FALSE |
||
| 413 | * @throws SqlErrorException |
||
| 414 | */ |
||
| 415 | public function getUidOfAlreadyPersistedValueObject(AbstractValueObject $object): ?int |
||
| 416 | { |
||
| 417 | /** @var DataMapper $dataMapper */ |
||
| 418 | $dataMapper = $this->objectManager->get(DataMapper::class); |
||
| 419 | $dataMap = $dataMapper->getDataMap(get_class($object)); |
||
| 420 | $tableName = $dataMap->getTableName(); |
||
| 421 | $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName); |
||
| 422 | if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface |
||
| 423 | && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() |
||
| 424 | ) { |
||
| 425 | $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); |
||
| 426 | } |
||
| 427 | $whereClause = []; |
||
| 428 | // loop over all properties of the object to exactly set the values of each database field |
||
| 429 | $properties = $object->_getProperties(); |
||
| 430 | foreach ($properties as $propertyName => $propertyValue) { |
||
| 431 | $propertyName = (string)$propertyName; |
||
| 432 | |||
| 433 | // @todo We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method |
||
| 434 | if ($dataMap->isPersistableProperty($propertyName) && $propertyName !== 'uid' && $propertyName !== 'pid' && $propertyName !== 'isClone') { |
||
| 435 | $fieldName = $dataMap->getColumnMap($propertyName)->getColumnName(); |
||
| 436 | if ($propertyValue === null) { |
||
| 437 | $whereClause[] = $queryBuilder->expr()->isNull($fieldName); |
||
| 438 | } else { |
||
| 439 | $whereClause[] = $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($dataMapper->getPlainValue($propertyValue))); |
||
| 440 | } |
||
| 441 | } |
||
| 442 | } |
||
| 443 | $queryBuilder |
||
| 444 | ->select('uid') |
||
| 445 | ->from($tableName) |
||
| 446 | ->where(...$whereClause); |
||
| 447 | |||
| 448 | try { |
||
| 449 | $uid = (int)$queryBuilder |
||
| 450 | ->execute() |
||
| 451 | ->fetchColumn(0); |
||
| 452 | if ($uid > 0) { |
||
| 453 | return $uid; |
||
| 454 | } |
||
| 455 | return null; |
||
| 456 | } catch (DBALException $e) { |
||
| 457 | throw new SqlErrorException($e->getPrevious()->getMessage(), 1470231748, $e); |
||
| 458 | } |
||
| 459 | } |
||
| 460 | |||
| 461 | /** |
||
| 462 | * Performs workspace and language overlay on the given row array. The language and workspace id is automatically |
||
| 463 | * detected (depending on FE or BE context). You can also explicitly set the language/workspace id. |
||
| 464 | * |
||
| 465 | * @param Qom\SourceInterface $source The source (selector or join) |
||
| 466 | * @param array $rows |
||
| 467 | * @param QueryInterface $query |
||
| 468 | * @param int|null $workspaceUid |
||
| 469 | * @return array |
||
| 470 | * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException |
||
| 471 | */ |
||
| 472 | protected function overlayLanguageAndWorkspace(SourceInterface $source, array $rows, QueryInterface $query, int $workspaceUid = null): array |
||
| 501 | } |
||
| 502 | |||
| 503 | /** |
||
| 504 | * If the result is a plain SELECT (no JOIN) then the regular overlay process works for tables |
||
| 505 | * - overlay workspace |
||
| 506 | * - overlay language of versioned record again |
||
| 507 | */ |
||
| 508 | protected function overlayLanguageAndWorkspaceForSelect(string $tableName, array $rows, PageRepository $pageRepository, QueryInterface $query): array |
||
| 509 | { |
||
| 510 | $overlaidRows = []; |
||
| 511 | foreach ($rows as $row) { |
||
| 512 | $row = $this->overlayLanguageAndWorkspaceForSingleRecord($tableName, $row, $pageRepository, $query); |
||
| 513 | if (is_array($row)) { |
||
| 514 | $overlaidRows[] = $row; |
||
| 515 | } |
||
| 516 | } |
||
| 517 | return $overlaidRows; |
||
| 518 | } |
||
| 519 | |||
| 520 | /** |
||
| 521 | * If the result consists of a JOIN (usually happens if a property is a relation with a MM table) then it is necessary |
||
| 522 | * to only do overlays for the fields that are contained in the main database table, otherwise a SQL error is thrown. |
||
| 523 | * In order to make this happen, a single SQL query is made to fetch all possible field names (= array keys) of |
||
| 524 | * a record (TCA[$tableName][columns] does not contain all needed information), which is then used to compute |
||
| 525 | * a separate subset of the row which can be overlaid properly. |
||
| 526 | */ |
||
| 527 | protected function overlayLanguageAndWorkspaceForJoinedSelect(string $tableName, array $rows, PageRepository $pageRepository, QueryInterface $query): array |
||
| 528 | { |
||
| 529 | // No valid rows, so this is skipped |
||
| 530 | if (!isset($rows[0]['uid'])) { |
||
| 531 | return $rows; |
||
| 532 | } |
||
| 533 | // First, find out the fields that belong to the "main" selected table which is defined by TCA, and take the first |
||
| 534 | // record to find out all possible fields in this database table |
||
| 535 | $fieldsOfMainTable = $pageRepository->getRawRecord($tableName, $rows[0]['uid']); |
||
| 536 | $overlaidRows = []; |
||
| 537 | foreach ($rows as $row) { |
||
| 538 | $mainRow = array_intersect_key($row, $fieldsOfMainTable); |
||
|
|
|||
| 539 | $joinRow = array_diff_key($row, $mainRow); |
||
| 540 | $mainRow = $this->overlayLanguageAndWorkspaceForSingleRecord($tableName, $mainRow, $pageRepository, $query); |
||
| 541 | if (is_array($mainRow)) { |
||
| 542 | $overlaidRows[] = array_replace($joinRow, $mainRow); |
||
| 543 | } |
||
| 544 | } |
||
| 545 | return $overlaidRows; |
||
| 546 | } |
||
| 547 | |||
| 548 | /** |
||
| 549 | * Takes one specific row, as defined in TCA and does all overlays. |
||
| 550 | * |
||
| 551 | * @param string $tableName |
||
| 552 | * @param array $row |
||
| 553 | * @param PageRepository $pageRepository |
||
| 554 | * @param QueryInterface $query |
||
| 555 | * @return array|int|mixed|null the overlaid row or false or null if overlay failed. |
||
| 556 | */ |
||
| 557 | protected function overlayLanguageAndWorkspaceForSingleRecord(string $tableName, array $row, PageRepository $pageRepository, QueryInterface $query) |
||
| 558 | { |
||
| 559 | $querySettings = $query->getQuerySettings(); |
||
| 560 | // If current row is a translation select its parent |
||
| 561 | $languageOfCurrentRecord = 0; |
||
| 562 | if ($GLOBALS['TCA'][$tableName]['ctrl']['languageField'] ?? null |
||
| 563 | && $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] ?? 0 |
||
| 564 | ) { |
||
| 565 | $languageOfCurrentRecord = $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']]; |
||
| 566 | } |
||
| 567 | if ($querySettings->getLanguageOverlayMode() |
||
| 568 | && $languageOfCurrentRecord > 0 |
||
| 569 | && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) |
||
| 570 | && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0 |
||
| 571 | ) { |
||
| 572 | $row = $pageRepository->getRawRecord( |
||
| 573 | $tableName, |
||
| 574 | (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] |
||
| 575 | ); |
||
| 576 | } |
||
| 577 | // Handle workspace overlays |
||
| 578 | $pageRepository->versionOL($tableName, $row, true); |
||
| 579 | if (is_array($row) && $querySettings->getLanguageOverlayMode()) { |
||
| 580 | if ($tableName === 'pages') { |
||
| 581 | $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid()); |
||
| 582 | } else { |
||
| 583 | // todo: remove type cast once getLanguageUid strictly returns an int |
||
| 584 | $languageUid = (int)$querySettings->getLanguageUid(); |
||
| 585 | if (!$querySettings->getRespectSysLanguage() |
||
| 586 | && $languageOfCurrentRecord > 0 |
||
| 587 | && (!$query instanceof Query || !$query->getParentQuery()) |
||
| 588 | ) { |
||
| 589 | // No parent query means we're processing the aggregate root. |
||
| 590 | // respectSysLanguage is false which means that records returned by the query |
||
| 591 | // might be from different languages (which is desired). |
||
| 592 | // So we must set the language used for overlay to the language of the current record |
||
| 593 | $languageUid = $languageOfCurrentRecord; |
||
| 594 | } |
||
| 595 | if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) |
||
| 596 | && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0 |
||
| 597 | && $languageOfCurrentRecord > 0 |
||
| 598 | ) { |
||
| 599 | // Force overlay by faking default language record, as getRecordOverlay can only handle default language records |
||
| 600 | $row['uid'] = $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']]; |
||
| 601 | $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = 0; |
||
| 602 | } |
||
| 603 | $row = $pageRepository->getRecordOverlay($tableName, $row, $languageUid, (string)$querySettings->getLanguageOverlayMode()); |
||
| 604 | } |
||
| 605 | } |
||
| 606 | return $row; |
||
| 607 | } |
||
| 608 | |||
| 609 | /** |
||
| 610 | * Fetches the moved record in case it is supported |
||
| 611 | * by the table and if there's only one row in the result set |
||
| 612 | * (applying this to all rows does not work, since the sorting |
||
| 613 | * order would be destroyed and possible limits are not met anymore) |
||
| 614 | * The move pointers are later unset (see versionOL() last argument) |
||
| 615 | */ |
||
| 616 | protected function resolveMovedRecordsInWorkspace(string $tableName, array $rows, int $workspaceUid): array |
||
| 617 | { |
||
| 618 | if ($workspaceUid === 0) { |
||
| 619 | return $rows; |
||
| 620 | } |
||
| 621 | if (!BackendUtility::isTableWorkspaceEnabled($tableName)) { |
||
| 622 | return $rows; |
||
| 623 | } |
||
| 624 | if (count($rows) !== 1) { |
||
| 625 | return $rows; |
||
| 626 | } |
||
| 627 | $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName); |
||
| 628 | $queryBuilder->getRestrictions()->removeAll(); |
||
| 629 | $movedRecords = $queryBuilder |
||
| 630 | ->select('*') |
||
| 631 | ->from($tableName) |
||
| 632 | ->where( |
||
| 633 | $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(VersionState::MOVE_POINTER, \PDO::PARAM_INT)), |
||
| 634 | $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($workspaceUid, \PDO::PARAM_INT)), |
||
| 635 | $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($rows[0]['uid'], \PDO::PARAM_INT)) |
||
| 636 | ) |
||
| 637 | ->setMaxResults(1) |
||
| 638 | ->execute() |
||
| 639 | ->fetchAll(); |
||
| 640 | if (!empty($movedRecords)) { |
||
| 641 | $rows = $movedRecords; |
||
| 642 | } |
||
| 643 | return $rows; |
||
| 644 | } |
||
| 645 | |||
| 646 | /** |
||
| 647 | * Clear the TYPO3 page cache for the given record. |
||
| 648 | * If the record lies on a page, then we clear the cache of this page. |
||
| 649 | * If the record has no PID column, we clear the cache of the current page as best-effort. |
||
| 650 | * |
||
| 651 | * Much of this functionality is taken from DataHandler::clear_cache() which unfortunately only works with logged-in BE user. |
||
| 652 | * |
||
| 653 | * @param string $tableName Table name of the record |
||
| 654 | * @param int $uid UID of the record |
||
| 655 | */ |
||
| 656 | protected function clearPageCache(string $tableName, int $uid): void |
||
| 657 | { |
||
| 658 | $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK); |
||
| 659 | if (empty($frameworkConfiguration['persistence']['enableAutomaticCacheClearing'])) { |
||
| 660 | return; |
||
| 661 | } |
||
| 662 | $pageIdsToClear = []; |
||
| 663 | $storagePage = null; |
||
| 664 | |||
| 665 | // As determining the table columns is a costly operation this is done only once per table during runtime and cached then |
||
| 666 | if (!isset($this->hasPidColumn[$tableName])) { |
||
| 667 | $columns = $this->connectionPool |
||
| 668 | ->getConnectionForTable($tableName) |
||
| 669 | ->getSchemaManager() |
||
| 670 | ->listTableColumns($tableName); |
||
| 671 | $this->hasPidColumn[$tableName] = array_key_exists('pid', $columns); |
||
| 672 | } |
||
| 673 | |||
| 674 | $tsfe = $this->getTSFE(); |
||
| 675 | if ($this->hasPidColumn[$tableName]) { |
||
| 676 | $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName); |
||
| 677 | $queryBuilder->getRestrictions()->removeAll(); |
||
| 678 | $result = $queryBuilder |
||
| 679 | ->select('pid') |
||
| 680 | ->from($tableName) |
||
| 681 | ->where( |
||
| 682 | $queryBuilder->expr()->eq( |
||
| 683 | 'uid', |
||
| 684 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
| 685 | ) |
||
| 686 | ) |
||
| 687 | ->execute(); |
||
| 688 | if ($row = $result->fetch()) { |
||
| 689 | $storagePage = $row['pid']; |
||
| 690 | $pageIdsToClear[] = $storagePage; |
||
| 691 | } |
||
| 692 | } elseif (isset($tsfe)) { |
||
| 693 | // No PID column - we can do a best-effort to clear the cache of the current page if in FE |
||
| 694 | $storagePage = $tsfe->id; |
||
| 695 | $pageIdsToClear[] = $storagePage; |
||
| 696 | } |
||
| 697 | if ($storagePage === null) { |
||
| 698 | return; |
||
| 699 | } |
||
| 700 | |||
| 701 | $pageTS = BackendUtility::getPagesTSconfig($storagePage); |
||
| 702 | if (isset($pageTS['TCEMAIN.']['clearCacheCmd'])) { |
||
| 703 | $clearCacheCommands = GeneralUtility::trimExplode(',', strtolower($pageTS['TCEMAIN.']['clearCacheCmd']), true); |
||
| 704 | $clearCacheCommands = array_unique($clearCacheCommands); |
||
| 705 | foreach ($clearCacheCommands as $clearCacheCommand) { |
||
| 706 | if (MathUtility::canBeInterpretedAsInteger($clearCacheCommand)) { |
||
| 707 | $pageIdsToClear[] = $clearCacheCommand; |
||
| 708 | } |
||
| 709 | } |
||
| 710 | } |
||
| 711 | |||
| 712 | foreach ($pageIdsToClear as $pageIdToClear) { |
||
| 713 | $this->cacheService->getPageIdStack()->push($pageIdToClear); |
||
| 714 | } |
||
| 715 | } |
||
| 716 | |||
| 717 | /** |
||
| 718 | * @return TypoScriptFrontendController|null |
||
| 719 | */ |
||
| 720 | protected function getTSFE(): ?TypoScriptFrontendController |
||
| 723 | } |
||
| 724 | } |
||
| 725 |