cbd749e18c726afd72c06a33c6297c2140fd7135
[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 */
98 public function setKeepLiveIds($keepLiveIds)
99 {
100 $this->keepLiveIds = (bool)$keepLiveIds;
101 }
102
103 /**
104 * Sets whether delete placeholders shall be kept in the final result set.
105 *
106 * @param bool $keepDeletePlaceholder
107 */
108 public function setKeepDeletePlaceholder($keepDeletePlaceholder)
109 {
110 $this->keepDeletePlaceholder = (bool)$keepDeletePlaceholder;
111 }
112
113 /**
114 * Sets whether move placeholders shall be kept in case they cannot be substituted.
115 *
116 * @param bool $keepMovePlaceholder
117 */
118 public function setKeepMovePlaceholder($keepMovePlaceholder)
119 {
120 $this->keepMovePlaceholder = (bool)$keepMovePlaceholder;
121 }
122
123 /**
124 * @return int[]
125 */
126 public function get()
127 {
128 if (isset($this->resolvedIds)) {
129 return $this->resolvedIds;
130 }
131
132 $ids = $this->processVersionOverlays($this->liveIds);
133 $ids = $this->processSorting($ids);
134 $ids = $this->applyLiveIds($ids);
135
136 $this->resolvedIds = $ids;
137 return $this->resolvedIds;
138 }
139
140 /**
141 * Processes version overlays on the final result set.
142 *
143 * @param int[] $ids
144 * @return int[]
145 */
146 protected function processVersionOverlays(array $ids)
147 {
148 if (empty($this->workspaceId) || !$this->isWorkspaceEnabled() || empty($ids)) {
149 return $ids;
150 }
151
152 $ids = $this->processVersionMovePlaceholders($ids);
153 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
154 ->getQueryBuilderForTable($this->tableName);
155
156 $queryBuilder->getRestrictions()->removeAll();
157
158 $result = $queryBuilder
159 ->select('uid', 't3ver_oid', 't3ver_state')
160 ->from($this->tableName)
161 ->where(
162 $queryBuilder->expr()->eq('pid', -1),
163 $queryBuilder->expr()->in('t3ver_oid', array_map('intval', $ids)),
164 $queryBuilder->expr()->eq('t3ver_wsid', $this->workspaceId)
165 )
166 ->execute();
167
168 while ($version = $result->fetch()) {
169 $liveReferenceId = $version['t3ver_oid'];
170 $versionId = $version['uid'];
171 if (isset($ids[$liveReferenceId])) {
172 if (!$this->keepDeletePlaceholder
173 && VersionState::cast($version['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
174 ) {
175 unset($ids[$liveReferenceId]);
176 } else {
177 $ids[$liveReferenceId] = $versionId;
178 }
179 }
180 }
181 $ids = $this->reindex($ids);
182
183 return $ids;
184 }
185
186 /**
187 * Processes and resolves move placeholders on the final result set.
188 *
189 * @param int[] $ids
190 * @return int[]
191 */
192 protected function processVersionMovePlaceholders(array $ids)
193 {
194 // Early return on insufficient data-set
195 if (empty($this->workspaceId) || !$this->isWorkspaceEnabled() || empty($ids)) {
196 return $ids;
197 }
198
199 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
200 ->getQueryBuilderForTable($this->tableName);
201
202 $queryBuilder->getRestrictions()->removeAll();
203
204 $result = $queryBuilder
205 ->select('uid', 't3ver_move_id')
206 ->from($this->tableName)
207 ->where(
208 $queryBuilder->expr()->neq('pid', -1),
209 $queryBuilder->expr()->eq('t3ver_state', VersionState::MOVE_PLACEHOLDER),
210 $queryBuilder->expr()->eq('t3ver_wsid', $this->workspaceId),
211 $queryBuilder->expr()->in('t3ver_move_id', array_map('intval', $ids))
212 )
213 ->execute();
214
215 while ($movePlaceholder = $result->fetch()) {
216 $liveReferenceId = $movePlaceholder['t3ver_move_id'];
217 $movePlaceholderId = $movePlaceholder['uid'];
218 // Substitute MOVE_PLACEHOLDER and purge live reference
219 if (isset($ids[$movePlaceholderId])) {
220 $ids[$movePlaceholderId] = $liveReferenceId;
221 unset($ids[$liveReferenceId]);
222 // Just purge live reference
223 } elseif (!$this->keepMovePlaceholder) {
224 unset($ids[$liveReferenceId]);
225 }
226 }
227
228 $ids = $this->reindex($ids);
229
230 return $ids;
231 }
232
233 /**
234 * Processes sorting of the final result set, if
235 * a sorting statement (table column/expression) is given.
236 *
237 * @param int[] $ids
238 * @return int[]
239 */
240 protected function processSorting(array $ids)
241 {
242 // Early return on missing sorting statement or insufficient data-set
243 if (empty($this->sortingStatement) || count($ids) < 2) {
244 return $ids;
245 }
246
247 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
248 ->getQueryBuilderForTable($this->tableName);
249
250 $queryBuilder->getRestrictions()->removeAll();
251
252 $queryBuilder
253 ->select('uid')
254 ->from($this->tableName)
255 ->where(
256 $queryBuilder->expr()->in('uid', array_map('intval', $ids))
257 );
258
259 if (!empty($this->sortingStatement)) {
260 foreach ($this->sortingStatement as $sortingStatement) {
261 $queryBuilder->add('orderBy', $sortingStatement, true);
262 }
263 }
264
265 $sortedIds = $queryBuilder->execute()->fetchAll();
266
267 return $this->reindex(array_column($sortedIds, 'uid'));
268 }
269
270 /**
271 * Applies live IDs to the final result set, if
272 * the current table is enabled for workspaces and
273 * the keepLiveIds class member is enabled.
274 *
275 * @param int[] $ids
276 * @return int[]
277 */
278 protected function applyLiveIds(array $ids)
279 {
280 if (!$this->keepLiveIds || !$this->isWorkspaceEnabled() || empty($ids)) {
281 return $ids;
282 }
283
284 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
285 ->getQueryBuilderForTable($this->tableName);
286
287 $queryBuilder->getRestrictions()->removeAll();
288
289 $result = $queryBuilder
290 ->select('uid', 't3ver_oid')
291 ->from($this->tableName)
292 ->where(
293 $queryBuilder->expr()->in('uid', array_map('intval', $ids))
294 )
295 ->execute();
296
297 $versionIds = [];
298 while ($record = $result->fetch()) {
299 $liveId = $record['uid'];
300 $versionIds[$liveId] = $record['t3ver_oid'];
301 }
302
303 foreach ($ids as $id) {
304 if (!empty($versionIds[$id])) {
305 $ids[$id] = $versionIds[$id];
306 }
307 }
308
309 return $this->reindex($ids);
310 }
311
312 /**
313 * Re-indexes the given IDs.
314 *
315 * @param int[] $ids
316 * @return int[]
317 */
318 protected function reindex(array $ids)
319 {
320 if (empty($ids)) {
321 return $ids;
322 }
323 $ids = array_values($ids);
324 $ids = array_combine($ids, $ids);
325 return $ids;
326 }
327
328 /**
329 * @return bool
330 */
331 protected function isWorkspaceEnabled()
332 {
333 return BackendUtility::isTableWorkspaceEnabled($this->tableName);
334 }
335
336 /**
337 * @return bool
338 */
339 protected function isLocalizationEnabled()
340 {
341 return BackendUtility::isTableLocalizable($this->tableName);
342 }
343 }