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

Mapper::extractLanguageCodesFromMask()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 16
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 16
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Mapper 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\CreateStruct;
13
use eZ\Publish\SPI\Persistence\Content\Field;
14
use eZ\Publish\SPI\Persistence\Content\FieldValue;
15
use eZ\Publish\SPI\Persistence\Content\Relation;
16
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
17
use eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\ConverterRegistry as Registry;
18
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
19
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
20
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
21
22
/**
23
 * Mapper for Content Handler.
24
 *
25
 * Performs mapping of Content objects.
26
 */
27
class Mapper
28
{
29
    /**
30
     * FieldValue converter registry.
31
     *
32
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\ConverterRegistry
33
     */
34
    protected $converterRegistry;
35
36
    /**
37
     * Caching language handler.
38
     *
39
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
40
     */
41
    protected $languageHandler;
42
43
    /**
44
     * Creates a new mapper.
45
     *
46
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\ConverterRegistry $converterRegistry
47
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
48
     */
49
    public function __construct(Registry $converterRegistry, LanguageHandler $languageHandler)
50
    {
51
        $this->converterRegistry = $converterRegistry;
52
        $this->languageHandler = $languageHandler;
53
    }
54
55
    /**
56
     * Creates a Content from the given $struct and $currentVersionNo.
57
     *
58
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
59
     * @param mixed $currentVersionNo
60
     *
61
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
62
     */
63
    private function createContentInfoFromCreateStruct(CreateStruct $struct, $currentVersionNo = 1)
64
    {
65
        $contentInfo = new ContentInfo();
66
67
        $contentInfo->id = null;
68
        $contentInfo->contentTypeId = $struct->typeId;
69
        $contentInfo->sectionId = $struct->sectionId;
70
        $contentInfo->ownerId = $struct->ownerId;
71
        $contentInfo->alwaysAvailable = $struct->alwaysAvailable;
72
        $contentInfo->remoteId = $struct->remoteId;
73
        $contentInfo->mainLanguageCode = $this->languageHandler->load($struct->initialLanguageId)->languageCode;
74
        $contentInfo->name = isset($struct->name[$contentInfo->mainLanguageCode])
75
            ? $struct->name[$contentInfo->mainLanguageCode]
76
            : '';
77
        // For drafts published and modified timestamps should be 0
78
        $contentInfo->publicationDate = 0;
79
        $contentInfo->modificationDate = 0;
80
        $contentInfo->currentVersionNo = $currentVersionNo;
81
        $contentInfo->isPublished = false;
82
83
        return $contentInfo;
84
    }
85
86
    /**
87
     * Creates a new version for the given $struct and $versionNo.
88
     *
89
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
90
     * @param mixed $versionNo
91
     *
92
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo
93
     */
94
    public function createVersionInfoFromCreateStruct(CreateStruct $struct, $versionNo)
95
    {
96
        $versionInfo = new VersionInfo();
97
98
        $versionInfo->id = null;
99
        $versionInfo->contentInfo = $this->createContentInfoFromCreateStruct($struct, $versionNo);
100
        $versionInfo->versionNo = $versionNo;
101
        $versionInfo->creatorId = $struct->ownerId;
102
        $versionInfo->status = VersionInfo::STATUS_DRAFT;
103
        $versionInfo->initialLanguageCode = $this->languageHandler->load($struct->initialLanguageId)->languageCode;
104
        $versionInfo->creationDate = $struct->modified;
105
        $versionInfo->modificationDate = $struct->modified;
106
        $versionInfo->names = $struct->name;
107
108
        $languages = [];
109 View Code Duplication
        foreach ($struct->fields as $field) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
110
            if (!isset($languages[$field->languageCode])) {
111
                $languages[$field->languageCode] = true;
112
            }
113
        }
114
        $versionInfo->languageCodes = array_keys($languages);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_keys($languages) of type array<integer,integer|string> is incompatible with the declared type array<integer,string> of property $languageCodes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
115
116
        return $versionInfo;
117
    }
118
119
    /**
120
     * Creates a new version for the given $content.
121
     *
122
     * @param \eZ\Publish\SPI\Persistence\Content $content
123
     * @param mixed $versionNo
124
     * @param mixed $userId
125
     *
126
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo
127
     */
128
    public function createVersionInfoForContent(Content $content, $versionNo, $userId)
129
    {
130
        $versionInfo = new VersionInfo();
131
132
        $versionInfo->contentInfo = $content->versionInfo->contentInfo;
133
        $versionInfo->versionNo = $versionNo;
134
        $versionInfo->creatorId = $userId;
135
        $versionInfo->status = VersionInfo::STATUS_DRAFT;
136
        $versionInfo->initialLanguageCode = $content->versionInfo->initialLanguageCode;
137
        $versionInfo->creationDate = time();
138
        $versionInfo->modificationDate = $versionInfo->creationDate;
139
        $versionInfo->names = is_object($content->versionInfo) ? $content->versionInfo->names : array();
140
        $versionInfo->languageCodes = $content->versionInfo->languageCodes;
141
142
        return $versionInfo;
143
    }
144
145
    /**
146
     * Converts value of $field to storage value.
147
     *
148
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
149
     *
150
     * @return \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue
151
     */
152
    public function convertToStorageValue(Field $field)
153
    {
154
        $converter = $this->converterRegistry->getConverter(
155
            $field->type
156
        );
157
        $storageValue = new StorageFieldValue();
158
        $converter->toStorageValue(
159
            $field->value,
160
            $storageValue
161
        );
162
163
        return $storageValue;
164
    }
165
166
    /**
167
     * Extracts Content objects (and nested) from database result $rows.
168
     *
169
     * Expects database rows to be indexed by keys of the format
170
     *
171
     *      "$tableName_$columnName"
172
     *
173
     * @param array $rows
174
     * @param array $nameRows
175
     *
176
     * @return \eZ\Publish\SPI\Persistence\Content[]
177
     */
178
    public function extractContentFromRows(array $rows, array $nameRows)
179
    {
180
        $versionedNameData = array();
181
        foreach ($nameRows as $row) {
182
            $contentId = (int)$row['ezcontentobject_name_contentobject_id'];
183
            $versionNo = (int)$row['ezcontentobject_name_content_version'];
184
            $versionedNameData[$contentId][$versionNo][$row['ezcontentobject_name_content_translation']] = $row['ezcontentobject_name_name'];
185
        }
186
187
        $contentInfos = array();
188
        $versionInfos = array();
189
        $fields = array();
190
191
        foreach ($rows as $row) {
192
            $contentId = (int)$row['ezcontentobject_id'];
193
            if (!isset($contentInfos[$contentId])) {
194
                $contentInfos[$contentId] = $this->extractContentInfoFromRow($row, 'ezcontentobject_');
195
            }
196
            if (!isset($versionInfos[$contentId])) {
197
                $versionInfos[$contentId] = array();
198
            }
199
200
            $versionId = (int)$row['ezcontentobject_version_id'];
201
            if (!isset($versionInfos[$contentId][$versionId])) {
202
                $versionInfos[$contentId][$versionId] = $this->extractVersionInfoFromRow($row);
203
            }
204
205
            $fieldId = (int)$row['ezcontentobject_attribute_id'];
206 View Code Duplication
            if (!isset($fields[$contentId][$versionId][$fieldId])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207
                $fields[$contentId][$versionId][$fieldId] = $this->extractFieldFromRow($row);
208
            }
209
        }
210
211
        $results = array();
212
        foreach ($contentInfos as $contentId => $contentInfo) {
213
            foreach ($versionInfos[$contentId] as $versionId => $versionInfo) {
214
                // Fallback to just main language name if versioned name data is missing
215 View Code Duplication
                if (isset($versionedNameData[$contentId][$versionInfo->versionNo])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
                    $names = $versionedNameData[$contentId][$versionInfo->versionNo];
217
                } else {
218
                    $names = [$contentInfo->mainLanguageCode => $contentInfo->name];
219
                }
220
221
                $content = new Content();
222
                $content->versionInfo = $versionInfo;
223
                $content->versionInfo->names = $names;
224
                $content->versionInfo->contentInfo = $contentInfo;
225
                $content->fields = array_values($fields[$contentId][$versionId]);
226
                $results[] = $content;
227
            }
228
        }
229
230
        return $results;
231
    }
232
233
    /**
234
     * Extracts a ContentInfo object from $row.
235
     *
236
     * @param array $row
237
     * @param string $prefix Prefix for row keys, which are initially mapped by ezcontentobject fields
238
     * @param string $treePrefix Prefix for tree row key, which are initially mapped by ezcontentobject_tree_ fields
239
     *
240
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
241
     */
242
    public function extractContentInfoFromRow(array $row, $prefix = '', $treePrefix = 'ezcontentobject_tree_')
243
    {
244
        $contentInfo = new ContentInfo();
245
        $contentInfo->id = (int)$row["{$prefix}id"];
246
        $contentInfo->name = $row["{$prefix}name"];
247
        $contentInfo->contentTypeId = (int)$row["{$prefix}contentclass_id"];
248
        $contentInfo->sectionId = (int)$row["{$prefix}section_id"];
249
        $contentInfo->currentVersionNo = (int)$row["{$prefix}current_version"];
250
        $contentInfo->isPublished = (bool)($row["{$prefix}status"] == ContentInfo::STATUS_PUBLISHED);
251
        $contentInfo->ownerId = (int)$row["{$prefix}owner_id"];
252
        $contentInfo->publicationDate = (int)$row["{$prefix}published"];
253
        $contentInfo->modificationDate = (int)$row["{$prefix}modified"];
254
        $contentInfo->alwaysAvailable = (int)$row["{$prefix}language_mask"] & 1;
0 ignored issues
show
Documentation Bug introduced by
The property $alwaysAvailable was declared of type boolean, but (int) $row["{$prefix}language_mask"] & 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
255
        $contentInfo->mainLanguageCode = $this->languageHandler->load($row["{$prefix}initial_language_id"])->languageCode;
256
        $contentInfo->remoteId = $row["{$prefix}remote_id"];
257
        $contentInfo->mainLocationId = ($row["{$treePrefix}main_node_id"] !== null ? (int)$row["{$treePrefix}main_node_id"] : null);
258
259
        return $contentInfo;
260
    }
261
262
    /**
263
     * Extracts ContentInfo objects from $rows.
264
     *
265
     * @param array $rows
266
     * @param string $prefix Prefix for row keys, which are initially mapped by ezcontentobject fields
267
     * @param string $treePrefix Prefix for tree row key, which are initially mapped by ezcontentobject_tree_ fields
268
     *
269
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo[]
270
     */
271
    public function extractContentInfoFromRows(array $rows, $prefix = '', $treePrefix = 'ezcontentobject_tree_')
272
    {
273
        $contentInfoObjects = array();
274
        foreach ($rows as $row) {
275
            $contentInfoObjects[] = $this->extractContentInfoFromRow($row, $prefix, $treePrefix);
276
        }
277
278
        return $contentInfoObjects;
279
    }
280
281
    /**
282
     * Extracts a VersionInfo object from $row.
283
     *
284
     * This method will return VersionInfo with incomplete data. It is intended to be used only by
285
     * {@link self::extractContentFromRows} where missing data will be filled in.
286
     *
287
     * @param array $row
288
     * @param array $names
289
     *
290
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo
291
     */
292
    private function extractVersionInfoFromRow(array $row, array $names = array())
293
    {
294
        $versionInfo = new VersionInfo();
295
        $versionInfo->id = (int)$row['ezcontentobject_version_id'];
296
        $versionInfo->contentInfo = null;
297
        $versionInfo->versionNo = (int)$row['ezcontentobject_version_version'];
298
        $versionInfo->creatorId = (int)$row['ezcontentobject_version_creator_id'];
299
        $versionInfo->creationDate = (int)$row['ezcontentobject_version_created'];
300
        $versionInfo->modificationDate = (int)$row['ezcontentobject_version_modified'];
301
        $versionInfo->initialLanguageCode = $this->languageHandler->load($row['ezcontentobject_version_initial_language_id'])->languageCode;
302
        $versionInfo->languageCodes = $this->extractLanguageCodesFromMask($row['ezcontentobject_version_language_mask']);
303
        $versionInfo->status = (int)$row['ezcontentobject_version_status'];
304
        $versionInfo->names = $names;
305
306
        return $versionInfo;
307
    }
308
309
    /**
310
     * Extracts a VersionInfo object from $row.
311
     *
312
     * @param array $rows
313
     * @param array $nameRows
314
     *
315
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[]
316
     */
317
    public function extractVersionInfoListFromRows(array $rows, array $nameRows)
318
    {
319
        $nameData = array();
320
        foreach ($nameRows as $row) {
321
            $versionId = $row['ezcontentobject_name_contentobject_id'] . '_' . $row['ezcontentobject_name_content_version'];
322
            $nameData[$versionId][$row['ezcontentobject_name_content_translation']] = $row['ezcontentobject_name_name'];
323
        }
324
325
        $versionInfoList = array();
326
        foreach ($rows as $row) {
327
            $versionId = $row['ezcontentobject_id'] . '_' . $row['ezcontentobject_version_version'];
328
            if (!isset($versionInfoList[$versionId])) {
329
                $versionInfo = new VersionInfo();
330
                $versionInfo->id = (int)$row['ezcontentobject_version_id'];
331
                $versionInfo->contentInfo = $this->extractContentInfoFromRow($row, 'ezcontentobject_');
332
                $versionInfo->versionNo = (int)$row['ezcontentobject_version_version'];
333
                $versionInfo->creatorId = (int)$row['ezcontentobject_version_creator_id'];
334
                $versionInfo->creationDate = (int)$row['ezcontentobject_version_created'];
335
                $versionInfo->modificationDate = (int)$row['ezcontentobject_version_modified'];
336
                $versionInfo->initialLanguageCode = $this->languageHandler->load($row['ezcontentobject_version_initial_language_id'])->languageCode;
337
                $versionInfo->languageCodes = $this->extractLanguageCodesFromMask((int)$row['ezcontentobject_version_language_mask']);
338
                $versionInfo->status = (int)$row['ezcontentobject_version_status'];
339
                $versionInfo->names = $nameData[$versionId];
340
                $versionInfoList[$versionId] = $versionInfo;
341
            }
342
        }
343
344
        return array_values($versionInfoList);
345
    }
346
347
    /**
348
     * @param int $languageMask
349
     *
350
     * @return string[]
351
     */
352 View Code Duplication
    public function extractLanguageCodesFromMask($languageMask)
353
    {
354
        $exp = 2;
355
        $result = [];
356
357
        // Decomposition of $languageMask into its binary components.
358
        while ($exp <= $languageMask) {
359
            if ($languageMask & $exp) {
360
                $result[] = $this->languageHandler->load($exp)->languageCode;
361
            }
362
363
            $exp *= 2;
364
        }
365
366
        return $result;
367
    }
368
369
    /**
370
     * Extracts a Field from $row.
371
     *
372
     * @param array $row
373
     *
374
     * @return Field
375
     */
376
    protected function extractFieldFromRow(array $row)
377
    {
378
        $field = new Field();
379
380
        $field->id = (int)$row['ezcontentobject_attribute_id'];
381
        $field->fieldDefinitionId = (int)$row['ezcontentobject_attribute_contentclassattribute_id'];
382
        $field->type = $row['ezcontentobject_attribute_data_type_string'];
383
        $field->value = $this->extractFieldValueFromRow($row, $field->type);
384
        $field->languageCode = $row['ezcontentobject_attribute_language_code'];
385
        $field->versionNo = (int)$row['ezcontentobject_attribute_version'];
386
387
        return $field;
388
    }
389
390
    /**
391
     * Extracts a FieldValue of $type from $row.
392
     *
393
     * @param array $row
394
     * @param string $type
395
     *
396
     * @return \eZ\Publish\SPI\Persistence\Content\FieldValue
397
     *
398
     * @throws \eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\Converter\Exception\NotFound
399
     *         if the necessary converter for $type could not be found.
400
     */
401
    protected function extractFieldValueFromRow(array $row, $type)
402
    {
403
        $storageValue = new StorageFieldValue();
404
405
        // Nullable field
406
        $storageValue->dataFloat = isset($row['ezcontentobject_attribute_data_float'])
407
            ? (float)$row['ezcontentobject_attribute_data_float']
408
            : null;
409
        // Nullable field
410
        $storageValue->dataInt = isset($row['ezcontentobject_attribute_data_int'])
411
            ? (int)$row['ezcontentobject_attribute_data_int']
412
            : null;
413
        $storageValue->dataText = $row['ezcontentobject_attribute_data_text'];
414
        // Not nullable field
415
        $storageValue->sortKeyInt = (int)$row['ezcontentobject_attribute_sort_key_int'];
416
        $storageValue->sortKeyString = $row['ezcontentobject_attribute_sort_key_string'];
417
418
        $fieldValue = new FieldValue();
419
420
        $converter = $this->converterRegistry->getConverter($type);
421
        $converter->toFieldValue($storageValue, $fieldValue);
422
423
        return $fieldValue;
424
    }
425
426
    /**
427
     * Creates CreateStruct from $content.
428
     *
429
     * @param \eZ\Publish\SPI\Persistence\Content $content
430
     *
431
     * @return \eZ\Publish\SPI\Persistence\Content\CreateStruct
432
     */
433
    public function createCreateStructFromContent(Content $content)
434
    {
435
        $struct = new CreateStruct();
436
        $struct->name = $content->versionInfo->names;
437
        $struct->typeId = $content->versionInfo->contentInfo->contentTypeId;
438
        $struct->sectionId = $content->versionInfo->contentInfo->sectionId;
439
        $struct->ownerId = $content->versionInfo->contentInfo->ownerId;
440
        $struct->locations = array();
441
        $struct->alwaysAvailable = $content->versionInfo->contentInfo->alwaysAvailable;
442
        $struct->remoteId = md5(uniqid(get_class($this), true));
443
        $struct->initialLanguageId = $this->languageHandler->loadByLanguageCode($content->versionInfo->initialLanguageCode)->id;
444
        $struct->modified = time();
445
446
        foreach ($content->fields as $field) {
447
            $newField = clone $field;
448
            $newField->id = null;
449
            $struct->fields[] = $newField;
450
        }
451
452
        return $struct;
453
    }
454
455
    /**
456
     * Extracts relation objects from $rows.
457
     */
458
    public function extractRelationsFromRows(array $rows)
459
    {
460
        $relations = array();
461
462
        foreach ($rows as $row) {
463
            $id = (int)$row['ezcontentobject_link_id'];
464
            if (!isset($relations[$id])) {
465
                $relations[$id] = $this->extractRelationFromRow($row);
466
            }
467
        }
468
469
        return $relations;
470
    }
471
472
    /**
473
     * Extracts a Relation object from a $row.
474
     *
475
     * @param array $row Associative array representing a relation
476
     *
477
     * @return \eZ\Publish\SPI\Persistence\Content\Relation
478
     */
479
    protected function extractRelationFromRow(array $row)
480
    {
481
        $relation = new Relation();
482
        $relation->id = (int)$row['ezcontentobject_link_id'];
483
        $relation->sourceContentId = (int)$row['ezcontentobject_link_from_contentobject_id'];
484
        $relation->sourceContentVersionNo = (int)$row['ezcontentobject_link_from_contentobject_version'];
485
        $relation->destinationContentId = (int)$row['ezcontentobject_link_to_contentobject_id'];
486
        $relation->type = (int)$row['ezcontentobject_link_relation_type'];
487
488
        $contentClassAttributeId = (int)$row['ezcontentobject_link_contentclassattribute_id'];
489
        if ($contentClassAttributeId > 0) {
490
            $relation->sourceFieldDefinitionId = $contentClassAttributeId;
491
        }
492
493
        return $relation;
494
    }
495
496
    /**
497
     * Creates a Content from the given $struct.
498
     *
499
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $struct
500
     *
501
     * @return \eZ\Publish\SPI\Persistence\Content\Relation
502
     */
503
    public function createRelationFromCreateStruct(RelationCreateStruct $struct)
504
    {
505
        $relation = new Relation();
506
507
        $relation->destinationContentId = $struct->destinationContentId;
508
        $relation->sourceContentId = $struct->sourceContentId;
509
        $relation->sourceContentVersionNo = $struct->sourceContentVersionNo;
510
        $relation->sourceFieldDefinitionId = $struct->sourceFieldDefinitionId;
511
        $relation->type = $struct->type;
512
513
        return $relation;
514
    }
515
}
516