‪TYPO3CMS  ‪main
PlainDataResolver.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
25 
34 {
38  protected ‪$tableName;
39 
43  protected ‪$liveIds;
44 
48  protected ‪$sortingStatement;
49 
53  protected ‪$workspaceId;
54 
58  protected ‪$keepLiveIds = false;
59 
63  protected ‪$keepDeletePlaceholder = false;
64 
68  protected ‪$keepMovePlaceholder = true;
69 
75  public function ‪__construct(‪$tableName, array ‪$liveIds, array ‪$sortingStatement = null)
76  {
77  $this->tableName = ‪$tableName;
78  $this->liveIds = $this->‪reindex($this->‪sanitizeIds($liveIds));
79  $this->sortingStatement = ‪$sortingStatement;
80  }
81 
87  public function ‪setWorkspaceId(‪$workspaceId)
88  {
89  $this->workspaceId = (int)‪$workspaceId;
90  }
91 
98  public function ‪setKeepLiveIds(‪$keepLiveIds)
99  {
100  $this->keepLiveIds = (bool)‪$keepLiveIds;
101  return $this;
102  }
103 
111  {
112  $this->keepDeletePlaceholder = (bool)‪$keepDeletePlaceholder;
113  return $this;
114  }
115 
123  {
124  $this->keepMovePlaceholder = (bool)‪$keepMovePlaceholder;
125  return $this;
126  }
127 
131  public function get()
132  {
133  $resolvedIds = $this->‪processVersionOverlays($this->liveIds);
134  if ($resolvedIds !== $this->liveIds) {
135  $resolvedIds = $this->‪reindex($resolvedIds);
136  }
137 
138  $tempIds = $this->‪processSorting($resolvedIds);
139  if ($tempIds !== $resolvedIds) {
140  $resolvedIds = $this->‪reindex($tempIds);
141  }
142 
143  $tempIds = $this->‪applyLiveIds($resolvedIds);
144  if ($tempIds !== $resolvedIds) {
145  $resolvedIds = $this->‪reindex($tempIds);
146  }
147 
148  return $resolvedIds;
149  }
150 
158  public function ‪processVersionOverlays(array $ids)
159  {
160  $ids = $this->‪sanitizeIds($ids);
161  if (empty($this->workspaceId) || !$this->‪isWorkspaceEnabled() || empty($ids)) {
162  return $ids;
163  }
164 
165  $ids = $this->‪reindex(
167  );
168  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
169  ->getQueryBuilderForTable($this->tableName);
170 
171  $queryBuilder->getRestrictions()->removeAll()
172  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
173 
174  $result = $queryBuilder
175  ->select('uid', 't3ver_oid', 't3ver_state')
176  ->from($this->tableName)
177  ->where(
178  $queryBuilder->expr()->in(
179  't3ver_oid',
180  $queryBuilder->createNamedParameter($ids, ‪Connection::PARAM_INT_ARRAY)
181  ),
182  $queryBuilder->expr()->eq(
183  't3ver_wsid',
184  $queryBuilder->createNamedParameter($this->workspaceId, ‪Connection::PARAM_INT)
185  )
186  )
187  ->executeQuery();
188 
189  while ($version = $result->fetchAssociative()) {
190  $liveReferenceId = $version['t3ver_oid'];
191  $versionId = $version['uid'];
192  if (isset($ids[$liveReferenceId])) {
193  if (!$this->keepDeletePlaceholder
194  && VersionState::tryFrom($version['t3ver_state'] ?? 0) === VersionState::DELETE_PLACEHOLDER
195  ) {
196  unset($ids[$liveReferenceId]);
197  } else {
198  $ids[$liveReferenceId] = $versionId;
199  }
200  }
201  }
202 
203  return $ids;
204  }
205 
213  public function ‪processVersionMovePlaceholders(array $ids)
214  {
215  $ids = $this->‪sanitizeIds($ids);
216  // Early return on insufficient data-set
217  if (empty($this->workspaceId) || !$this->‪isWorkspaceEnabled() || empty($ids)) {
218  return $ids;
219  }
220 
221  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
222  ->getQueryBuilderForTable($this->tableName);
223 
224  $queryBuilder->getRestrictions()->removeAll()
225  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
226 
227  $result = $queryBuilder
228  ->select('uid', 't3ver_oid')
229  ->from($this->tableName)
230  ->where(
231  $queryBuilder->expr()->eq(
232  't3ver_state',
233  $queryBuilder->createNamedParameter(VersionState::MOVE_POINTER->value, ‪Connection::PARAM_INT)
234  ),
235  $queryBuilder->expr()->eq(
236  't3ver_wsid',
237  $queryBuilder->createNamedParameter($this->workspaceId, ‪Connection::PARAM_INT)
238  ),
239  $queryBuilder->expr()->in(
240  't3ver_oid',
241  $queryBuilder->createNamedParameter($ids, ‪Connection::PARAM_INT_ARRAY)
242  )
243  )
244  ->executeQuery();
245 
246  while ($movedRecord = $result->fetchAssociative()) {
247  $liveReferenceId = (int)$movedRecord['t3ver_oid'];
248  $movedVersionId = (int)$movedRecord['uid'];
249  // Substitute moved record and purge live reference
250  if (isset($ids[$movedVersionId])) {
251  $ids[$movedVersionId] = $liveReferenceId;
252  unset($ids[$liveReferenceId]);
253  } elseif (!$this->keepMovePlaceholder) {
254  // Just purge live reference
255  unset($ids[$liveReferenceId]);
256  }
257  }
258 
259  return $ids;
260  }
261 
270  public function ‪processSorting(array $ids)
271  {
272  $ids = $this->‪sanitizeIds($ids);
273  // Early return on missing sorting statement or insufficient data-set
274  if (empty($this->sortingStatement) || count($ids) < 2) {
275  return $ids;
276  }
277 
278  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->tableName);
279  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
280  $queryBuilder
281  ->select('uid')
282  ->from($this->tableName)
283  ->where(
284  $queryBuilder->expr()->in(
285  'uid',
286  // do not use named parameter here as the list can get too long
287  array_map(intval(...), $ids)
288  )
289  );
290 
291  if (!empty($this->sortingStatement)) {
292  foreach ($this->sortingStatement as ‪$sortingStatement) {
293  $queryBuilder->getConcreteQueryBuilder()->addOrderBy(‪$sortingStatement);
294  }
295  }
296  // Always add explicit order by uid to have deterministic rows from dbms like postgres.
297  // Scenario (see workspace FAL/Modify/ActionTest modifyContentAndDeleteFileReference):
298  // A content element with two images - sys_file_reference uid=23 with sorting_foreign=2 (!)
299  // and sys_file_reference uid=42 with sorting_foreign=1. The references have been added
300  // and later changed their sorting that uid 42 is before 23.
301  // Then, in workspaces, image reference 42 is deleted and 23 is changed (eg. title). This
302  // creates two overlays: a 'delete placeholder' t3ver_state=2 with sorting_foreign=1 for 42,
303  // and a 'changed' record t3ver_state=0 with sorting_foreign=1 for 23.
304  // So both overlay records end up with sorting_foreign=1. This is technically ok since the
305  // 'delete placeholder' "does not exist" from a live relation point of view, so the next
306  // "real" record starts with 1 when published.
307  // BUT, this scenario makes the order of returned rows non-deterministic for dbms that
308  // do not implicitly order by uid (mysql does, postgres does not): The usual orderBy
309  // is 'sorting_foreign' but both are 1 now.
310  // We thus add a general explicit order by uid here to force deterministic row returns.
311  $queryBuilder->addOrderBy('uid');
312 
313  $sortedIds = $queryBuilder->executeQuery()->fetchAllAssociative();
314 
315  return array_map(intval(...), array_column($sortedIds, 'uid'));
316  }
317 
327  public function ‪applyLiveIds(array $ids)
328  {
329  $ids = $this->‪sanitizeIds($ids);
330  if (!$this->keepLiveIds || !$this->‪isWorkspaceEnabled() || empty($ids)) {
331  return $ids;
332  }
333 
334  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
335  ->getQueryBuilderForTable($this->tableName);
336 
337  $queryBuilder->getRestrictions()->removeAll()
338  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
339 
340  $result = $queryBuilder
341  ->select('uid', 't3ver_oid')
342  ->from($this->tableName)
343  ->where(
344  $queryBuilder->expr()->in(
345  'uid',
346  $queryBuilder->createNamedParameter($ids, ‪Connection::PARAM_INT_ARRAY)
347  )
348  )
349  ->executeQuery();
350 
351  $versionIds = [];
352  while (‪$record = $result->fetchAssociative()) {
353  $liveId = ‪$record['uid'];
354  $versionIds[$liveId] = ‪$record['t3ver_oid'];
355  }
356 
357  foreach ($ids as $id) {
358  if (!empty($versionIds[$id])) {
359  $ids[$id] = $versionIds[$id];
360  }
361  }
362 
363  return $ids;
364  }
365 
372  protected function ‪reindex(array $ids)
373  {
374  if (empty($ids)) {
375  return $ids;
376  }
377  $ids = array_values($ids);
378  $ids = array_combine($ids, $ids);
379  return $ids;
380  }
381 
387  protected function ‪sanitizeIds(array $ids): array
388  {
389  return array_filter($ids);
390  }
391 
395  protected function ‪isWorkspaceEnabled()
396  {
397  if (‪ExtensionManagementUtility::isLoaded('workspaces')) {
398  return BackendUtility::isTableWorkspaceEnabled($this->tableName);
399  }
400  return false;
401  }
402 }
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\processVersionOverlays
‪int[] processVersionOverlays(array $ids)
Definition: PlainDataResolver.php:151
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver
Definition: PlainDataResolver.php:34
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\__construct
‪__construct($tableName, array $liveIds, array $sortingStatement=null)
Definition: PlainDataResolver.php:68
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\$keepDeletePlaceholder
‪bool $keepDeletePlaceholder
Definition: PlainDataResolver.php:57
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\$keepMovePlaceholder
‪bool $keepMovePlaceholder
Definition: PlainDataResolver.php:61
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\$sortingStatement
‪array null $sortingStatement
Definition: PlainDataResolver.php:45
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\setKeepMovePlaceholder
‪PlainDataResolver setKeepMovePlaceholder($keepMovePlaceholder)
Definition: PlainDataResolver.php:115
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\processSorting
‪int[] processSorting(array $ids)
Definition: PlainDataResolver.php:263
‪TYPO3\CMS\Core\DataHandling
Definition: DataHandler.php:16
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\applyLiveIds
‪int[] applyLiveIds(array $ids)
Definition: PlainDataResolver.php:320
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\processVersionMovePlaceholders
‪int[] processVersionMovePlaceholders(array $ids)
Definition: PlainDataResolver.php:206
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\isLoaded
‪static isLoaded(string $key)
Definition: ExtensionManagementUtility.php:55
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:32
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\sanitizeIds
‪sanitizeIds(array $ids)
Definition: PlainDataResolver.php:380
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\$tableName
‪string $tableName
Definition: PlainDataResolver.php:37
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\setKeepDeletePlaceholder
‪PlainDataResolver setKeepDeletePlaceholder($keepDeletePlaceholder)
Definition: PlainDataResolver.php:103
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\reindex
‪int[] reindex(array $ids)
Definition: PlainDataResolver.php:365
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\setKeepLiveIds
‪PlainDataResolver setKeepLiveIds($keepLiveIds)
Definition: PlainDataResolver.php:91
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\$workspaceId
‪int $workspaceId
Definition: PlainDataResolver.php:49
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\isWorkspaceEnabled
‪bool isWorkspaceEnabled()
Definition: PlainDataResolver.php:388
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\$liveIds
‪int[] $liveIds
Definition: PlainDataResolver.php:41
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\setWorkspaceId
‪setWorkspaceId($workspaceId)
Definition: PlainDataResolver.php:80
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\$keepLiveIds
‪bool $keepLiveIds
Definition: PlainDataResolver.php:53