[BUGFIX] MM references are not transformed to versioned entities
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / DataHandling / PlainDataResolver.php
1 <?php
2 namespace TYPO3\CMS\Core\DataHandling;
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 TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Versioning\VersionState;
21
22 /**
23 * Plain data resolving.
24 *
25 * This component resolves data constraints for given IDs of a
26 * particular table on a plain/raw database level. Thus, workspaces
27 * placeholders and overlay related resorting is applied automatically.
28 */
29 class PlainDataResolver
30 {
31 /**
32 * @var string
33 */
34 protected $tableName;
35
36 /**
37 * @var int[]
38 */
39 protected $liveIds;
40
41 /**
42 * @var array
43 */
44 protected $sortingStatement;
45
46 /**
47 * @var int
48 */
49 protected $workspaceId;
50
51 /**
52 * @var bool
53 */
54 protected $keepLiveIds = false;
55
56 /**
57 * @var bool
58 */
59 protected $keepDeletePlaceholder = false;
60
61 /**
62 * @var bool
63 */
64 protected $keepMovePlaceholder = true;
65
66 /**
67 * @var int[]
68 */
69 protected $resolvedIds;
70
71 /**
72 * @param string $tableName
73 * @param int[] $liveIds
74 * @param NULL|array $sortingStatement
75 */
76 public function __construct($tableName, array $liveIds, array $sortingStatement = null)
77 {
78 $this->tableName = $tableName;
79 $this->liveIds = $this->reindex($liveIds);
80 $this->sortingStatement = $sortingStatement;
81 }
82
83 /**
84 * Sets the target workspace ID the final result shall use.
85 *
86 * @param int $workspaceId
87 */
88 public function setWorkspaceId($workspaceId)
89 {
90 $this->workspaceId = (int)$workspaceId;
91 }
92
93 /**
94 * Sets whether live IDs shall be kept in the final result set.
95 *
96 * @param bool $keepLiveIds
97 * @return PlainDataResolver
98 */
99 public function setKeepLiveIds($keepLiveIds)
100 {
101 $this->keepLiveIds = (bool)$keepLiveIds;
102 return $this;
103 }
104
105 /**
106 * Sets whether delete placeholders shall be kept in the final result set.
107 *
108 * @param bool $keepDeletePlaceholder
109 * @return PlainDataResolver
110 */
111 public function setKeepDeletePlaceholder($keepDeletePlaceholder)
112 {
113 $this->keepDeletePlaceholder = (bool)$keepDeletePlaceholder;
114 return $this;
115 }
116
117 /**
118 * Sets whether move placeholders shall be kept in case they cannot be substituted.
119 *
120 * @param bool $keepMovePlaceholder
121 * @return PlainDataResolver
122 */
123 public function setKeepMovePlaceholder($keepMovePlaceholder)
124 {
125 $this->keepMovePlaceholder = (bool)$keepMovePlaceholder;
126 return $this;
127 }
128
129 /**
130 * @return int[]
131 */
132 public function get()
133 {
134 if (isset($this->resolvedIds)) {
135 return $this->resolvedIds;
136 }
137
138 $ids = $this->reindex(
139 $this->processVersionOverlays($this->liveIds)
140 );
141 $ids = $this->reindex(
142 $this->processSorting($ids)
143 );
144 $ids = $this->reindex(
145 $this->applyLiveIds($ids)
146 );
147
148 $this->resolvedIds = $ids;
149 return $this->resolvedIds;
150 }
151
152 /**
153 * Processes version overlays on the final result set.
154 *
155 * @param int[] $ids
156 * @return int[]
157 * @internal
158 */
159 public function processVersionOverlays(array $ids)
160 {
161 if (empty($this->workspaceId) || !$this->isWorkspaceEnabled() || empty($ids)) {
162 return $ids;
163 }
164
165 $ids = $this->reindex(
166 $this->processVersionMovePlaceholders($ids)
167 );
168 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
169 ->getQueryBuilderForTable($this->tableName);
170
171 $queryBuilder->getRestrictions()->removeAll();
172
173 $result = $queryBuilder
174 ->select('uid', 't3ver_oid', 't3ver_state')
175 ->from($this->tableName)
176 ->where(
177 $queryBuilder->expr()->eq('pid', -1),
178 $queryBuilder->expr()->in('t3ver_oid', array_map('intval', $ids)),
179 $queryBuilder->expr()->eq('t3ver_wsid', $this->workspaceId)
180 )
181 ->execute();
182
183 while ($version = $result->fetch()) {
184 $liveReferenceId = $version['t3ver_oid'];
185 $versionId = $version['uid'];
186 if (isset($ids[$liveReferenceId])) {
187 if (!$this->keepDeletePlaceholder
188 && VersionState::cast($version['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
189 ) {
190 unset($ids[$liveReferenceId]);
191 } else {
192 $ids[$liveReferenceId] = $versionId;
193 }
194 }
195 }
196
197 return $ids;
198 }
199
200 /**
201 * Processes and resolves move placeholders on the final result set.
202 *
203 * @param int[] $ids
204 * @return int[]
205 * @internal
206 */
207 public function processVersionMovePlaceholders(array $ids)
208 {
209 // Early return on insufficient data-set
210 if (empty($this->workspaceId) || !$this->isWorkspaceEnabled() || empty($ids)) {
211 return $ids;
212 }
213
214 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
215 ->getQueryBuilderForTable($this->tableName);
216
217 $queryBuilder->getRestrictions()->removeAll();
218
219 $result = $queryBuilder
220 ->select('uid', 't3ver_move_id')
221 ->from($this->tableName)
222 ->where(
223 $queryBuilder->expr()->neq('pid', -1),
224 $queryBuilder->expr()->eq('t3ver_state', VersionState::MOVE_PLACEHOLDER),
225 $queryBuilder->expr()->eq('t3ver_wsid', $this->workspaceId),
226 $queryBuilder->expr()->in('t3ver_move_id', array_map('intval', $ids))
227 )
228 ->execute();
229
230 while ($movePlaceholder = $result->fetch()) {
231 $liveReferenceId = $movePlaceholder['t3ver_move_id'];
232 $movePlaceholderId = $movePlaceholder['uid'];
233 // Substitute MOVE_PLACEHOLDER and purge live reference
234 if (isset($ids[$movePlaceholderId])) {
235 $ids[$movePlaceholderId] = $liveReferenceId;
236 unset($ids[$liveReferenceId]);
237 // Just purge live reference
238 } elseif (!$this->keepMovePlaceholder) {
239 unset($ids[$liveReferenceId]);
240 }
241 }
242
243 return $ids;
244 }
245
246 /**
247 * Processes sorting of the final result set, if
248 * a sorting statement (table column/expression) is given.
249 *
250 * @param int[] $ids
251 * @return int[]
252 * @internal
253 */
254 public function processSorting(array $ids)
255 {
256 // Early return on missing sorting statement or insufficient data-set
257 if (empty($this->sortingStatement) || count($ids) < 2) {
258 return $ids;
259 }
260
261 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
262 ->getQueryBuilderForTable($this->tableName);
263
264 $queryBuilder->getRestrictions()->removeAll();
265
266 $queryBuilder
267 ->select('uid')
268 ->from($this->tableName)
269 ->where(
270 $queryBuilder->expr()->in('uid', array_map('intval', $ids))
271 );
272
273 if (!empty($this->sortingStatement)) {
274 foreach ($this->sortingStatement as $sortingStatement) {
275 $queryBuilder->add('orderBy', $sortingStatement, true);
276 }
277 }
278
279 $sortedIds = $queryBuilder->execute()->fetchAll();
280
281 return array_column($sortedIds, 'uid');
282 }
283
284 /**
285 * Applies live IDs to the final result set, if
286 * the current table is enabled for workspaces and
287 * the keepLiveIds class member is enabled.
288 *
289 * @param int[] $ids
290 * @return int[]
291 * @internal
292 */
293 public function applyLiveIds(array $ids)
294 {
295 if (!$this->keepLiveIds || !$this->isWorkspaceEnabled() || empty($ids)) {
296 return $ids;
297 }
298
299 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
300 ->getQueryBuilderForTable($this->tableName);
301
302 $queryBuilder->getRestrictions()->removeAll();
303
304 $result = $queryBuilder
305 ->select('uid', 't3ver_oid')
306 ->from($this->tableName)
307 ->where(
308 $queryBuilder->expr()->in('uid', array_map('intval', $ids))
309 )
310 ->execute();
311
312 $versionIds = [];
313 while ($record = $result->fetch()) {
314 $liveId = $record['uid'];
315 $versionIds[$liveId] = $record['t3ver_oid'];
316 }
317
318 foreach ($ids as $id) {
319 if (!empty($versionIds[$id])) {
320 $ids[$id] = $versionIds[$id];
321 }
322 }
323
324 return $ids;
325 }
326
327 /**
328 * Re-indexes the given IDs.
329 *
330 * @param int[] $ids
331 * @return int[]
332 */
333 protected function reindex(array $ids)
334 {
335 if (empty($ids)) {
336 return $ids;
337 }
338 $ids = array_values($ids);
339 $ids = array_combine($ids, $ids);
340 return $ids;
341 }
342
343 /**
344 * @return bool
345 */
346 protected function isWorkspaceEnabled()
347 {
348 return BackendUtility::isTableWorkspaceEnabled($this->tableName);
349 }
350
351 /**
352 * @return bool
353 */
354 protected function isLocalizationEnabled()
355 {
356 return BackendUtility::isTableLocalizable($this->tableName);
357 }
358 }