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