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