Completed
Push — master ( b22f08...115366 )
by André
34:29 queued 20:05
created

FieldHandler::getLanguageCodes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Content FieldHandler class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Legacy\Content;
10
11
use eZ\Publish\SPI\Persistence\Content;
12
use eZ\Publish\SPI\Persistence\Content\Type;
13
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
14
use eZ\Publish\SPI\Persistence\Content\Field;
15
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
16
use eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition;
17
use eZ\Publish\Core\Persistence\FieldTypeRegistry;
18
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
19
20
/**
21
 * Field Handler.
22
 */
23
class FieldHandler
24
{
25
    /**
26
     * Content Gateway.
27
     *
28
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway
29
     */
30
    protected $contentGateway;
31
32
    /**
33
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\Handler
34
     */
35
    protected $languageHandler;
36
37
    /**
38
     * Content Mapper.
39
     *
40
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Mapper
41
     */
42
    protected $mapper;
43
44
    /**
45
     * Storage Handler.
46
     *
47
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\StorageHandler
48
     */
49
    protected $storageHandler;
50
51
    /**
52
     * FieldType registry.
53
     *
54
     * @var \eZ\Publish\Core\Persistence\FieldTypeRegistry
55
     */
56
    protected $fieldTypeRegistry;
57
58
    /**
59
     * Hash of SPI FieldTypes or callable callbacks to generate one.
60
     *
61
     * @var array
62
     */
63
    protected $fieldTypes;
64
65
    /**
66
     * Creates a new Field Handler.
67
     *
68
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway $contentGateway
69
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Mapper $mapper
70
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageHandler $storageHandler
71
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
72
     * @param \eZ\Publish\Core\Persistence\FieldTypeRegistry $fieldTypeRegistry
73
     */
74
    public function __construct(
75
        Gateway $contentGateway,
76
        Mapper $mapper,
77
        StorageHandler $storageHandler,
78
        LanguageHandler $languageHandler,
79
        FieldTypeRegistry $fieldTypeRegistry
80
    ) {
81
        $this->contentGateway = $contentGateway;
82
        $this->mapper = $mapper;
83
        $this->storageHandler = $storageHandler;
84
        $this->languageHandler = $languageHandler;
0 ignored issues
show
Documentation Bug introduced by
$languageHandler is of type object<eZ\Publish\SPI\Pe...ntent\Language\Handler>, but the property $languageHandler was declared to be of type object<eZ\Publish\Core\P...ntent\Language\Handler>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
85
        $this->fieldTypeRegistry = $fieldTypeRegistry;
86
    }
87
88
    /**
89
     * Creates new fields in the database from $content of $contentType.
90
     *
91
     * @param \eZ\Publish\SPI\Persistence\Content $content
92
     * @param \eZ\Publish\SPI\Persistence\Content\Type $contentType
93
     */
94
    public function createNewFields(Content $content, Type $contentType)
95
    {
96
        $fieldsToCopy = array();
97
        $languageCodes = array();
98
        $fields = $this->getFieldMap($content->fields, $languageCodes);
99
        $languageCodes[$content->versionInfo->contentInfo->mainLanguageCode] = true;
100
101
        foreach ($contentType->fieldDefinitions as $fieldDefinition) {
102
            foreach (array_keys($languageCodes) as $languageCode) {
103
                // Create fields passed from struct
104
                if (isset($fields[$fieldDefinition->id][$languageCode])) {
105
                    $field = $fields[$fieldDefinition->id][$languageCode];
106
                    $this->createNewField($field, $content);
107
                } elseif (
108
                    !$fieldDefinition->isTranslatable
109
                    && isset($fields[$fieldDefinition->id][$content->versionInfo->contentInfo->mainLanguageCode])
110
                ) {
111
                    // Copy only for untranslatable field and when field in main language exists
112
                    // Only register here, process later as field copied should be already stored
113
                    $fieldsToCopy[$fieldDefinition->id][$languageCode] =
114
                        $fields[$fieldDefinition->id][$content->versionInfo->contentInfo->mainLanguageCode];
115
                } else { // In all other cases create empty field
116
                    $field = $this->getEmptyField($fieldDefinition, $languageCode);
117
                    $content->fields[] = $field;
118
                    $this->createNewField($field, $content);
119
                }
120
            }
121
        }
122
123
        $this->copyFields($fieldsToCopy, $content);
124
    }
125
126
    /**
127
     * Returns empty Field object for given field definition and language code.
128
     *
129
     * Uses FieldType to create empty field value.
130
     *
131
     * @param \eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition $fieldDefinition
132
     * @param string $languageCode
133
     *
134
     * @return \eZ\Publish\SPI\Persistence\Content\Field
135
     */
136
    protected function getEmptyField(FieldDefinition $fieldDefinition, $languageCode)
137
    {
138
        $fieldType = $this->fieldTypeRegistry->getFieldType($fieldDefinition->fieldType);
139
140
        return new Field(
141
            array(
142
                'fieldDefinitionId' => $fieldDefinition->id,
143
                'type' => $fieldDefinition->fieldType,
144
                'value' => $fieldType->getEmptyValue(),
145
                'languageCode' => $languageCode,
146
            )
147
        );
148
    }
149
150
    /**
151
     * Creates existing fields in a new version for $content.
152
     *
153
     * @param \eZ\Publish\SPI\Persistence\Content $content
154
     */
155
    public function createExistingFieldsInNewVersion(Content $content)
156
    {
157
        foreach ($content->fields as $field) {
158
            $this->createExistingFieldInNewVersion($field, $content);
159
        }
160
    }
161
162
    /**
163
     * Creates a new field in the database.
164
     *
165
     * Used by self::createNewFields() and self::updateFields()
166
     *
167
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
168
     * @param \eZ\Publish\SPI\Persistence\Content $content
169
     */
170 View Code Duplication
    protected function createNewField(Field $field, Content $content)
171
    {
172
        $field->versionNo = $content->versionInfo->versionNo;
173
174
        $field->id = $this->contentGateway->insertNewField(
175
            $content,
176
            $field,
177
            $this->mapper->convertToStorageValue($field)
178
        );
179
180
        // If the storage handler returns true, it means that $field value has been modified
181
        // So we need to update it in order to store those modifications
182
        // Field converter is called once again via the Mapper
183
        if ($this->storageHandler->storeFieldData($content->versionInfo, $field) === true) {
184
            $this->contentGateway->updateField(
185
                $field,
186
                $this->mapper->convertToStorageValue($field)
187
            );
188
        }
189
    }
190
191
    /**
192
     * @param array $fields
193
     * @param \eZ\Publish\SPI\Persistence\Content $content
194
     */
195
    protected function copyFields(array $fields, Content $content)
196
    {
197
        foreach ($fields as $languageFields) {
198
            foreach ($languageFields as $languageCode => $field) {
199
                $this->copyField($field, $languageCode, $content);
200
            }
201
        }
202
    }
203
204
    /**
205
     * Copies existing field to new field for given $languageCode.
206
     *
207
     * Used by self::createNewFields() and self::updateFields()
208
     *
209
     * @param \eZ\Publish\SPI\Persistence\Content\Field $originalField
210
     * @param string $languageCode
211
     * @param \eZ\Publish\SPI\Persistence\Content $content
212
     */
213
    protected function copyField(Field $originalField, $languageCode, Content $content)
214
    {
215
        $originalField->versionNo = $content->versionInfo->versionNo;
216
        $field = clone $originalField;
217
        $field->languageCode = $languageCode;
218
219
        $field->id = $this->contentGateway->insertNewField(
220
            $content,
221
            $field,
222
            $this->mapper->convertToStorageValue($field)
223
        );
224
225
        // If the storage handler returns true, it means that $field value has been modified
226
        // So we need to update it in order to store those modifications
227
        // Field converter is called once again via the Mapper
228
        if ($this->storageHandler->copyFieldData($content->versionInfo, $field, $originalField) === true) {
229
            $this->contentGateway->updateField(
230
                $field,
231
                $this->mapper->convertToStorageValue($field)
232
            );
233
        }
234
235
        $content->fields[] = $field;
236
    }
237
238
    /**
239
     * Updates an existing field in the database.
240
     *
241
     * Used by self::createNewFields() and self::updateFields()
242
     *
243
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
244
     * @param \eZ\Publish\SPI\Persistence\Content $content
245
     */
246
    protected function updateField(Field $field, Content $content)
247
    {
248
        $this->contentGateway->updateField(
249
            $field,
250
            $this->mapper->convertToStorageValue($field)
251
        );
252
253
        // If the storage handler returns true, it means that $field value has been modified
254
        // So we need to update it in order to store those modifications
255
        // Field converter is called once again via the Mapper
256
        if ($this->storageHandler->storeFieldData($content->versionInfo, $field) === true) {
257
            $this->contentGateway->updateField(
258
                $field,
259
                $this->mapper->convertToStorageValue($field)
260
            );
261
        }
262
    }
263
264
    /**
265
     * Creates an existing field in a new version, no new ID is generated.
266
     *
267
     * Used to insert a field with an existing ID but a new version number.
268
     * $content is used for new version data, needed by Content gateway and external storage.
269
     *
270
     * External data is being copied here as some FieldTypes require original field external data.
271
     * By default copying falls back to storing, it is upon external storage implementation to override
272
     * the behaviour as needed.
273
     *
274
     * @param Field $field
275
     * @param Content $content
276
     */
277 View Code Duplication
    protected function createExistingFieldInNewVersion(Field $field, Content $content)
278
    {
279
        $originalField = clone $field;
280
        $field->versionNo = $content->versionInfo->versionNo;
281
282
        $this->contentGateway->insertExistingField(
283
            $content,
284
            $field,
285
            $this->mapper->convertToStorageValue($field)
286
        );
287
288
        // If the storage handler returns true, it means that $field value has been modified
289
        // So we need to update it in order to store those modifications
290
        // Field converter is called once again via the Mapper
291
        if ($this->storageHandler->copyFieldData($content->versionInfo, $field, $originalField) === true) {
292
            $this->contentGateway->updateField(
293
                $field,
294
                $this->mapper->convertToStorageValue($field)
295
            );
296
        }
297
    }
298
299
    /**
300
     * Performs external loads for the fields in $content.
301
     *
302
     * @param Content $content
303
     */
304
    public function loadExternalFieldData(Content $content)
305
    {
306
        foreach ($content->fields as $field) {
307
            $this->storageHandler->getFieldData($content->versionInfo, $field);
308
        }
309
    }
310
311
    /**
312
     * Updates the fields in for content identified by $contentId and $versionNo in the database in respect to $updateStruct.
313
     *
314
     * @param \eZ\Publish\SPI\Persistence\Content $content
315
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $updateStruct
316
     * @param \eZ\Publish\SPI\Persistence\Content\Type $contentType
317
     */
318
    public function updateFields(Content $content, UpdateStruct $updateStruct, Type $contentType)
319
    {
320
        $updatedFields = array();
321
        $fieldsToCopy = array();
322
        $nonTranslatableCopiesUpdateSet = array();
323
        $mainLanguageCode = $content->versionInfo->contentInfo->mainLanguageCode;
324
        $languageCodes = $existingLanguageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
325
        $contentFieldMap = $this->getFieldMap($content->fields);
326
        $updateFieldMap = $this->getFieldMap($updateStruct->fields, $languageCodes);
327
        $initialLanguageCode = $this->languageHandler->load($updateStruct->initialLanguageId)->languageCode;
328
        $languageCodes[$initialLanguageCode] = true;
329
330
        foreach ($contentType->fieldDefinitions as $fieldDefinition) {
331
            foreach (array_keys($languageCodes) as $languageCode) {
332
                if (isset($updateFieldMap[$fieldDefinition->id][$languageCode])) {
333
                    $field = clone $updateFieldMap[$fieldDefinition->id][$languageCode];
334
                    $field->versionNo = $content->versionInfo->versionNo;
335
                    if (isset($field->id)) {
336
                        $this->updateField($field, $content);
337
                        $updatedFields[$fieldDefinition->id][$languageCode] = $field;
338
                    } else {
339
                        $this->createNewField($field, $content);
340
                    }
341
                } elseif (!isset($existingLanguageCodes[$languageCode])) {
342
                    // If field is not set for new language
343
                    if ($fieldDefinition->isTranslatable) {
344
                        // Use empty value for translatable field
345
                        $field = $this->getEmptyField($fieldDefinition, $languageCode);
346
                        $this->createNewField($field, $content);
347
                    } else {
348
                        // Use value from main language code for untranslatable field
349
                        $fieldsToCopy[$fieldDefinition->id][$languageCode] =
350
                            isset($updateFieldMap[$fieldDefinition->id][$mainLanguageCode])
351
                                ? $updateFieldMap[$fieldDefinition->id][$mainLanguageCode]
352
                                : $contentFieldMap[$fieldDefinition->id][$mainLanguageCode];
353
                    }
354
                } elseif (!$fieldDefinition->isTranslatable
355
                    && isset($updateFieldMap[$fieldDefinition->id][$mainLanguageCode])
356
                ) {
357
                    // If field is not set for existing language and is untranslatable and main language is updated,
358
                    // also update copied field data
359
                    // Register for processing after all given fields are updated
360
                    $nonTranslatableCopiesUpdateSet[$fieldDefinition->id][] = $languageCode;
361
                }
362
363
                // If no above conditions were met - do nothing
364
            }
365
        }
366
367
        foreach ($nonTranslatableCopiesUpdateSet as $fieldDefinitionId => $languageCodes) {
368
            foreach ($languageCodes as $languageCode) {
369
                $this->updateCopiedField(
370
                    $contentFieldMap[$fieldDefinitionId][$languageCode],
371
                    $updateFieldMap[$fieldDefinitionId][$mainLanguageCode],
372
                    $updatedFields[$fieldDefinitionId][$mainLanguageCode],
373
                    $content
374
                );
375
            }
376
        }
377
378
        $this->copyFields($fieldsToCopy, $content);
379
    }
380
381
    /**
382
     * Updates a language copy of a non-translatable field.
383
     *
384
     * External data is being copied here as some FieldTypes require original field external data.
385
     * By default copying falls back to storing, it is upon external storage implementation to override
386
     * the behaviour as needed.
387
     *
388
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
389
     * @param \eZ\Publish\SPI\Persistence\Content\Field $updateField
390
     * @param \eZ\Publish\SPI\Persistence\Content\Field $originalField
391
     * @param \eZ\Publish\SPI\Persistence\Content $content
392
     */
393 View Code Duplication
    protected function updateCopiedField(Field $field, Field $updateField, Field $originalField, Content $content)
394
    {
395
        $field->versionNo = $content->versionInfo->versionNo;
396
        $field->value = clone $updateField->value;
397
398
        $this->contentGateway->updateField(
399
            $field,
400
            $this->mapper->convertToStorageValue($field)
401
        );
402
403
        // If the storage handler returns true, it means that $field value has been modified
404
        // So we need to update it in order to store those modifications
405
        // Field converter is called once again via the Mapper
406
        if ($this->storageHandler->copyFieldData($content->versionInfo, $field, $originalField) === true) {
407
            $this->contentGateway->updateField(
408
                $field,
409
                $this->mapper->convertToStorageValue($field)
410
            );
411
        }
412
    }
413
414
    /**
415
     * Returns given $fields structured in hash array with field definition ids and language codes as keys.
416
     *
417
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
418
     * @param array $languageCodes
419
     *
420
     * @return \eZ\Publish\SPI\Persistence\Content\Field[][]
421
     */
422
    protected function getFieldMap(array $fields, &$languageCodes = null)
423
    {
424
        $fieldMap = array();
425
        foreach ($fields as $field) {
426
            if (isset($languageCodes)) {
427
                $languageCodes[$field->languageCode] = true;
428
            }
429
            $fieldMap[$field->fieldDefinitionId][$field->languageCode] = $field;
430
        }
431
432
        return $fieldMap;
433
    }
434
435
    /**
436
     * Deletes the fields for $contentId in $versionInfo from the database.
437
     *
438
     * @param int $contentId
439
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo
440
     */
441
    public function deleteFields($contentId, VersionInfo $versionInfo)
442
    {
443
        foreach ($this->contentGateway->getFieldIdsByType($contentId, $versionInfo->versionNo) as $fieldType => $ids) {
444
            $this->storageHandler->deleteFieldData($fieldType, $versionInfo, $ids);
445
        }
446
        $this->contentGateway->deleteFields($contentId, $versionInfo->versionNo);
447
    }
448
}
449