[CLEANUP] Alwas put null at the last position
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / DataHandling / Localization / DataMapItem.php
1 <?php
2 namespace TYPO3\CMS\Core\DataHandling\Localization;
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\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Utility\MathUtility;
20
21 /**
22 * Entity for data-map item.
23 */
24 class DataMapItem
25 {
26 const TYPE_PARENT = 'parent';
27 const TYPE_DIRECT_CHILD = 'directChild';
28 const TYPE_GRAND_CHILD = 'grandChild';
29
30 const SCOPE_PARENT = State::STATE_PARENT;
31 const SCOPE_SOURCE = State::STATE_SOURCE;
32 const SCOPE_EXCLUDE = 'exclude';
33
34 /**
35 * @var string
36 */
37 protected $tableName;
38
39 /**
40 * @var string|int
41 */
42 protected $id;
43
44 /**
45 * @var array
46 */
47 protected $suggestedValues;
48
49 /**
50 * @var array
51 */
52 protected $persistedValues;
53
54 /**
55 * @var array
56 */
57 protected $configurationFieldNames;
58
59 /**
60 * @var bool
61 */
62 protected $new;
63
64 /**
65 * @var string
66 */
67 protected $type;
68
69 /**
70 * @var State
71 */
72 protected $state;
73
74 /**
75 * @var string|int
76 */
77 protected $language;
78
79 /**
80 * @var string|int
81 */
82 protected $parent;
83
84 /**
85 * @var string|int
86 */
87 protected $source;
88
89 /**
90 * @var DataMapItem[][]
91 */
92 protected $dependencies = [];
93
94 /**
95 * Builds a data-map item. In addition to the constructor, the values
96 * for language, parent and source record pointers are assigned as well.
97 *
98 * @param string $tableName
99 * @param string|int $id
100 * @param array $suggestedValues
101 * @param array $persistedValues
102 * @param array $configurationFieldNames
103 * @return object|DataMapItem
104 */
105 public static function build(
106 string $tableName,
107 $id,
108 array $suggestedValues,
109 array $persistedValues,
110 array $configurationFieldNames
111 ) {
112 $item = GeneralUtility::makeInstance(
113 static::class,
114 $tableName,
115 $id,
116 $suggestedValues,
117 $persistedValues,
118 $configurationFieldNames
119 );
120
121 $item->language = (int)($suggestedValues[$item->getLanguageFieldName()] ?? $persistedValues[$item->getLanguageFieldName()]);
122 $item->setParent($suggestedValues[$item->getParentFieldName()] ?? $persistedValues[$item->getParentFieldName()]);
123 if ($item->getSourceFieldName() !== null) {
124 $item->setSource($suggestedValues[$item->getSourceFieldName()] ?? $persistedValues[$item->getSourceFieldName()]);
125 }
126
127 return $item;
128 }
129
130 /**
131 * @param string $tableName
132 * @param string|int $id
133 * @param array $suggestedValues
134 * @param array $persistedValues
135 * @param array $configurationFieldNames
136 */
137 public function __construct(
138 string $tableName,
139 $id,
140 array $suggestedValues,
141 array $persistedValues,
142 array $configurationFieldNames
143 ) {
144 $this->tableName = $tableName;
145 $this->id = $id;
146
147 $this->suggestedValues = $suggestedValues;
148 $this->persistedValues = $persistedValues;
149 $this->configurationFieldNames = $configurationFieldNames;
150
151 $this->new = !MathUtility::canBeInterpretedAsInteger($id);
152 }
153
154 /**
155 * Gets the current table name of this data-map item.
156 *
157 * @return string
158 */
159 public function getTableName(): string
160 {
161 return $this->tableName;
162 }
163
164 /**
165 * Gets the table name used to resolve the language parent record.
166 *
167 * @return string
168 */
169 public function getFromTableName(): string
170 {
171 if ($this->tableName === 'pages_language_overlay') {
172 return 'pages';
173 }
174 return $this->tableName;
175 }
176
177 /**
178 * Gets the table name used to resolve any kind of translations.
179 *
180 * @return string
181 */
182 public function getForTableName(): string
183 {
184 if ($this->tableName === 'pages') {
185 return 'pages_language_overlay';
186 }
187 return $this->tableName;
188 }
189
190 /**
191 * Gets the id of this data-map item.
192 *
193 * @return mixed
194 */
195 public function getId()
196 {
197 return $this->id;
198 }
199
200 /**
201 * Gets the suggested values that were initially
202 * submitted as the whole data-map to the DataHandler.
203 *
204 * @return array
205 */
206 public function getSuggestedValues(): array
207 {
208 return $this->suggestedValues;
209 }
210
211 /**
212 * Gets the persisted values that represent the persisted state
213 * of the record this data-map item is a surrogate for - does only
214 * contain relevant field values.
215 *
216 * @return array
217 */
218 public function getPersistedValues(): array
219 {
220 return $this->persistedValues;
221 }
222
223 /**
224 * @return array
225 */
226 public function getConfigurationFieldNames(): array
227 {
228 return $this->configurationFieldNames;
229 }
230
231 /**
232 * @return string
233 */
234 public function getLanguageFieldName(): string
235 {
236 return $this->configurationFieldNames['language'];
237 }
238
239 /**
240 * @return string
241 */
242 public function getParentFieldName(): string
243 {
244 return $this->configurationFieldNames['parent'];
245 }
246
247 /**
248 * @return string|null
249 */
250 public function getSourceFieldName()
251 {
252 return $this->configurationFieldNames['source'];
253 }
254
255 /**
256 * @return bool
257 */
258 public function isNew(): bool
259 {
260 return $this->new;
261 }
262
263 /**
264 * @return string
265 */
266 public function getType(): string
267 {
268 if ($this->type === null) {
269 // implicit: default language, it's a parent
270 if ($this->language === 0) {
271 $this->type = static::TYPE_PARENT;
272 } elseif (
273 // implicit: having source value different to parent value, it's a 2nd or higher level translation
274 $this->source !== null
275 && $this->source !== $this->parent
276 ) {
277 $this->type = static::TYPE_GRAND_CHILD;
278 } else {
279 // implicit: otherwise, it's a 1st level translation
280 $this->type = static::TYPE_DIRECT_CHILD;
281 }
282 }
283 return $this->type;
284 }
285
286 /**
287 * @return bool
288 */
289 public function isParentType(): bool
290 {
291 return $this->getType() === static::TYPE_PARENT;
292 }
293
294 /**
295 * @return bool
296 */
297 public function isDirectChildType(): bool
298 {
299 return $this->getType() === static::TYPE_DIRECT_CHILD;
300 }
301
302 /**
303 * @return bool
304 */
305 public function isGrandChildType(): bool
306 {
307 return $this->getType() === static::TYPE_GRAND_CHILD;
308 }
309
310 /**
311 * @return State
312 */
313 public function getState(): State
314 {
315 if ($this->state === null && !$this->isParentType()) {
316 $this->state = $this->buildState();
317 }
318 return $this->state;
319 }
320
321 /**
322 * @return string|int
323 */
324 public function getLanguage()
325 {
326 return $this->language;
327 }
328
329 /**
330 * @param string|int $language
331 */
332 public function setLanguage($language)
333 {
334 $this->language = $language;
335 }
336
337 /**
338 * @return string|int
339 */
340 public function getParent()
341 {
342 return $this->parent;
343 }
344
345 /**
346 * @param string|int $parent
347 */
348 public function setParent($parent)
349 {
350 $this->parent = $this->extractId($parent);
351 }
352
353 /**
354 * @return string|int
355 */
356 public function getSource()
357 {
358 return $this->source;
359 }
360
361 /**
362 * @param string|int $source
363 */
364 public function setSource($source)
365 {
366 $this->source = $this->extractId($source);
367 }
368
369 /**
370 * @param string $scope
371 * @return int|string
372 */
373 public function getIdForScope($scope)
374 {
375 if (
376 $scope === static::SCOPE_PARENT
377 || $scope === static::SCOPE_EXCLUDE
378 ) {
379 return $this->getParent();
380 }
381 if ($scope === static::SCOPE_SOURCE) {
382 return $this->getSource();
383 }
384 throw new \RuntimeException('Invalid scope', 1486325248);
385 }
386
387 /**
388 * @return DataMapItem[][]
389 */
390 public function getDependencies(): array
391 {
392 return $this->dependencies;
393 }
394
395 /**
396 * @param DataMapItem[][] $dependencies
397 */
398 public function setDependencies(array $dependencies)
399 {
400 $this->dependencies = $dependencies;
401 }
402
403 /**
404 * @param string $scope
405 * @return DataMapItem[]
406 */
407 public function findDependencies(string $scope)
408 {
409 return $this->dependencies[$scope] ?? [];
410 }
411
412 /**
413 * @return string[]
414 */
415 public function getApplicableScopes()
416 {
417 $scopes = [];
418 if (!empty($this->getSourceFieldName())) {
419 $scopes[] = static::SCOPE_SOURCE;
420 }
421 $scopes[] = static::SCOPE_PARENT;
422 $scopes[] = static::SCOPE_EXCLUDE;
423 return $scopes;
424 }
425
426 /**
427 * Extracts real id from provided id-value, which can either be a real
428 * integer value, a 'NEW...' id, or a combined identifier 'tt_content_13'.
429 *
430 * @param int|string $idValue
431 * @return int|string
432 */
433 protected function extractId($idValue)
434 {
435 if (MathUtility::canBeInterpretedAsInteger($idValue)) {
436 return $idValue;
437 }
438 if (strpos($idValue, 'NEW') === 0) {
439 return $idValue;
440 }
441 // @todo Handle if $tableName does not match $this->tableName
442 list($tableName, $id) = BackendUtility::splitTable_Uid($idValue);
443 return $id;
444 }
445
446 /**
447 * @return State|null
448 */
449 protected function buildState()
450 {
451 // build from persisted states
452 if (!$this->isNew()) {
453 $state = State::fromJSON(
454 $this->tableName,
455 $this->persistedValues['l10n_state'] ?? null
456 );
457 } elseif (is_string($this->suggestedValues['l10n_state'] ?? null)) {
458 // use provided states for a new and copied element
459 $state = State::fromJSON(
460 $this->tableName,
461 $this->suggestedValues['l10n_state']
462 );
463 } else {
464 // provide the default states
465 $state = State::create($this->tableName);
466 }
467 // switch "custom" to "source" state for 2nd level translations
468 if ($this->isNew() && $this->isGrandChildType()) {
469 $state->updateStates(State::STATE_CUSTOM, State::STATE_SOURCE);
470 }
471 // apply any provided updates to the states
472 if (is_array($this->suggestedValues['l10n_state'] ?? null)) {
473 $state->update(
474 $this->suggestedValues['l10n_state'] ?? []
475 );
476 }
477 return $state;
478 }
479 }