Completed
Push — fix_ezp29385 ( 36b56f...415bc3 )
by
unknown
56:19 queued 22:25
created

DomainMapper::buildRelationDomainObject()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 3
dl 0
loc 28
rs 9.472
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the DomainMapper 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\Repository\Helper;
10
11
use eZ\Publish\SPI\Persistence\Content\Handler as ContentHandler;
12
use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandler;
13
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
14
use eZ\Publish\SPI\Persistence\Content\Type\Handler as TypeHandler;
15
use eZ\Publish\Core\Repository\Values\Content\Content;
16
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
17
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
18
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
19
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
20
use eZ\Publish\API\Repository\Values\Content\Field;
21
use eZ\Publish\Core\Repository\Values\Content\Relation;
22
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
23
use eZ\Publish\Core\Repository\Values\Content\Location;
24
use eZ\Publish\SPI\Persistence\Content as SPIContent;
25
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
26
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo;
27
use eZ\Publish\SPI\Persistence\Content\ContentInfo as SPIContentInfo;
28
use eZ\Publish\SPI\Persistence\Content\Relation as SPIRelation;
29
use eZ\Publish\SPI\Persistence\Content\Type as SPIType;
30
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct as SPILocationCreateStruct;
31
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
32
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
33
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
34
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
35
use DateTime;
36
37
/**
38
 * DomainMapper is an internal service.
39
 *
40
 * @internal Meant for internal use by Repository.
41
 */
42
class DomainMapper
43
{
44
    /**
45
     * @var \eZ\Publish\SPI\Persistence\Content\Handler
46
     */
47
    protected $contentHandler;
48
49
    /**
50
     * @var \eZ\Publish\SPI\Persistence\Content\Location\Handler
51
     */
52
    protected $locationHandler;
53
54
    /**
55
     * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
56
     */
57
    protected $contentTypeHandler;
58
59
    /**
60
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
61
     */
62
    protected $contentLanguageHandler;
63
64
    /**
65
     * @var FieldTypeRegistry
66
     */
67
    protected $fieldTypeRegistry;
68
69
    /**
70
     * Setups service with reference to repository.
71
     *
72
     * @param \eZ\Publish\SPI\Persistence\Content\Handler $contentHandler
73
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler
74
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
75
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $contentLanguageHandler
76
     * @param FieldTypeRegistry $fieldTypeRegistry
77
     */
78
    public function __construct(
79
        ContentHandler $contentHandler,
80
        LocationHandler $locationHandler,
81
        TypeHandler $contentTypeHandler,
82
        LanguageHandler $contentLanguageHandler,
83
        FieldTypeRegistry $fieldTypeRegistry
84
    ) {
85
        $this->contentHandler = $contentHandler;
86
        $this->locationHandler = $locationHandler;
87
        $this->contentTypeHandler = $contentTypeHandler;
88
        $this->contentLanguageHandler = $contentLanguageHandler;
89
        $this->fieldTypeRegistry = $fieldTypeRegistry;
90
    }
91
92
    /**
93
     * Builds a Content domain object from value object returned from persistence.
94
     *
95
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
96
     * @param ContentType|SPIType $contentType
97
     * @param array|null $fieldLanguages Language codes to filter fields on
98
     * @param string|null $fieldAlwaysAvailableLanguage Language code fallback if a given field is not found in $fieldLanguages
99
     *
100
     * @return \eZ\Publish\Core\Repository\Values\Content\Content
101
     */
102
    public function buildContentDomainObject(SPIContent $spiContent, $contentType = null, array $fieldLanguages = null, $fieldAlwaysAvailableLanguage = null)
103
    {
104
        if ($contentType === null) {
105
            $contentType = $this->contentTypeHandler->load(
106
                $spiContent->versionInfo->contentInfo->contentTypeId
107
            );
108
        }
109
110
        return new Content(
111
            array(
112
                'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $fieldLanguages, $fieldAlwaysAvailableLanguage),
113
                'versionInfo' => $this->buildVersionInfoDomainObject($spiContent->versionInfo),
114
            )
115
        );
116
    }
117
118
    /**
119
     * Returns an array of domain fields created from given array of SPI fields.
120
     *
121
     * @throws InvalidArgumentType On invalid $contentType
122
     *
123
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
124
     * @param ContentType|SPIType $contentType
125
     * @param array|null $languages Language codes to filter fields on
126
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $languages
127
     *
128
     * @return array
129
     */
130
    public function buildDomainFields(array $spiFields, $contentType, array $languages = null, $alwaysAvailableLanguage = null)
131
    {
132
        if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) {
133
            throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType');
134
        }
135
136
        $fieldIdentifierMap = array();
137
        foreach ($contentType->fieldDefinitions as $fieldDefinitions) {
138
            $fieldIdentifierMap[$fieldDefinitions->id] = $fieldDefinitions->identifier;
139
        }
140
141
        $fieldInFilterLanguagesMap = array();
142
        if ($languages !== null && $alwaysAvailableLanguage !== null) {
143
            foreach ($spiFields as $spiField) {
144
                if (in_array($spiField->languageCode, $languages)) {
145
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
146
                }
147
            }
148
        }
149
150
        $fields = array();
151
        foreach ($spiFields as $spiField) {
152
            // We ignore fields in content not part of the content type
153
            if (!isset($fieldIdentifierMap[$spiField->fieldDefinitionId])) {
154
                continue;
155
            }
156
157
            if ($languages !== null && !in_array($spiField->languageCode, $languages)) {
158
                // If filtering is enabled we ignore fields in other languages then $fieldLanguages, if:
159
                if ($alwaysAvailableLanguage === null) {
160
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
161
                    continue;
162
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
163
                    // Ignore field if it exists in one of the filtered languages
164
                    continue;
165
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
166
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
167
                    continue;
168
                }
169
            }
170
171
            $fields[] = new Field(
172
                array(
173
                    'id' => $spiField->id,
174
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
175
                        ->fromPersistenceValue($spiField->value),
176
                    'languageCode' => $spiField->languageCode,
177
                    'fieldDefIdentifier' => $fieldIdentifierMap[$spiField->fieldDefinitionId],
178
                )
179
            );
180
        }
181
182
        return $fields;
183
    }
184
185
    /**
186
     * Builds a VersionInfo domain object from value object returned from persistence.
187
     *
188
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo
189
     *
190
     * @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo
191
     */
192
    public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo)
193
    {
194
        $languageCodes = array();
195
        foreach ($spiVersionInfo->languageIds as $languageId) {
196
            $languageCodes[] = $this->contentLanguageHandler->load($languageId)->languageCode;
197
        }
198
199
        // Map SPI statuses to API
200
        switch ($spiVersionInfo->status) {
201
            case SPIVersionInfo::STATUS_ARCHIVED:
202
                $status = APIVersionInfo::STATUS_ARCHIVED;
203
                break;
204
205
            case SPIVersionInfo::STATUS_PUBLISHED:
206
                $status = APIVersionInfo::STATUS_PUBLISHED;
207
                break;
208
209
            case SPIVersionInfo::STATUS_DRAFT:
210
            default:
211
                $status = APIVersionInfo::STATUS_DRAFT;
212
        }
213
214
        return new VersionInfo(
215
            array(
216
                'id' => $spiVersionInfo->id,
217
                'versionNo' => $spiVersionInfo->versionNo,
218
                'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate),
219
                'creatorId' => $spiVersionInfo->creatorId,
220
                'creationDate' => $this->getDateTime($spiVersionInfo->creationDate),
221
                'status' => $status,
222
                'initialLanguageCode' => $spiVersionInfo->initialLanguageCode,
223
                'languageCodes' => $languageCodes,
224
                'names' => $spiVersionInfo->names,
225
                'contentInfo' => $this->buildContentInfoDomainObject($spiVersionInfo->contentInfo),
226
            )
227
        );
228
    }
229
230
    /**
231
     * Builds a ContentInfo domain object from value object returned from persistence.
232
     *
233
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo
234
     *
235
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
236
     */
237
    public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo)
238
    {
239
        // Map SPI statuses to API
240
        switch ($spiContentInfo->status) {
241
            case SPIContentInfo::STATUS_TRASHED:
242
                $status = ContentInfo::STATUS_TRASHED;
243
                break;
244
245
            case SPIContentInfo::STATUS_PUBLISHED:
246
                $status = ContentInfo::STATUS_PUBLISHED;
247
                break;
248
249
            case SPIContentInfo::STATUS_DRAFT:
250
            default:
251
                $status = ContentInfo::STATUS_DRAFT;
252
        }
253
254
        return new ContentInfo(
255
            array(
256
                'id' => $spiContentInfo->id,
257
                'contentTypeId' => $spiContentInfo->contentTypeId,
258
                'name' => $spiContentInfo->name,
259
                'sectionId' => $spiContentInfo->sectionId,
260
                'currentVersionNo' => $spiContentInfo->currentVersionNo,
261
                'published' => $spiContentInfo->isPublished,
0 ignored issues
show
Deprecated Code introduced by
The property eZ\Publish\SPI\Persisten...ntentInfo::$isPublished has been deprecated with message: Use SPI\ContentInfo::$status (with value ContentInfo::STATUS_PUBLISHED) Flag indicating if content is currently published.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
262
                'ownerId' => $spiContentInfo->ownerId,
263
                'modificationDate' => $spiContentInfo->modificationDate == 0 ?
264
                    null :
265
                    $this->getDateTime($spiContentInfo->modificationDate),
266
                'publishedDate' => $spiContentInfo->publicationDate == 0 ?
267
                    null :
268
                    $this->getDateTime($spiContentInfo->publicationDate),
269
                'alwaysAvailable' => $spiContentInfo->alwaysAvailable,
270
                'remoteId' => $spiContentInfo->remoteId,
271
                'mainLanguageCode' => $spiContentInfo->mainLanguageCode,
272
                'mainLocationId' => $spiContentInfo->mainLocationId,
273
                'status' => $status,
274
            )
275
        );
276
    }
277
278
    /**
279
     * Builds API Relation object from provided SPI Relation object.
280
     *
281
     * @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation
282
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo
283
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo
284
     *
285
     * @return \eZ\Publish\API\Repository\Values\Content\Relation
286
     */
287
    public function buildRelationDomainObject(
288
        SPIRelation $spiRelation,
289
        ContentInfo $sourceContentInfo,
290
        ContentInfo $destinationContentInfo
291
    ) {
292
        $sourceFieldDefinitionIdentifier = null;
293
        if ($spiRelation->sourceFieldDefinitionId !== null) {
294
            $contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId);
295
            foreach ($contentType->fieldDefinitions as $fieldDefinition) {
296
                if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) {
297
                    continue;
298
                }
299
300
                $sourceFieldDefinitionIdentifier = $fieldDefinition->identifier;
301
                break;
302
            }
303
        }
304
305
        return new Relation(
306
            array(
307
                'id' => $spiRelation->id,
308
                'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier,
309
                'type' => $spiRelation->type,
310
                'sourceContentInfo' => $sourceContentInfo,
311
                'destinationContentInfo' => $destinationContentInfo,
312
            )
313
        );
314
    }
315
316
    /**
317
     * Builds domain location object from provided persistence location.
318
     *
319
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
320
     *
321
     * @return \eZ\Publish\API\Repository\Values\Content\Location
322
     */
323
    public function buildLocationDomainObject(SPILocation $spiLocation)
324
    {
325
        // TODO: this is hardcoded workaround for missing ContentInfo on root location
326
        if ($spiLocation->id == 1) {
327
            $legacyDateTime = $this->getDateTime(1030968000); //  first known commit of eZ Publish 3.x
328
            $contentInfo = new ContentInfo(
329
                array(
330
                    'id' => 0,
331
                    'name' => 'Top Level Nodes',
332
                    'sectionId' => 1,
333
                    'mainLocationId' => 1,
334
                    'contentTypeId' => 1,
335
                    'currentVersionNo' => 1,
336
                    'published' => 1,
337
                    'ownerId' => 14, // admin user
338
                    'modificationDate' => $legacyDateTime,
339
                    'publishedDate' => $legacyDateTime,
340
                    'alwaysAvailable' => 1,
341
                    'remoteId' => null,
342
                    'mainLanguageCode' => 'eng-GB',
343
                )
344
            );
345
        } else {
346
            $contentInfo = $this->buildContentInfoDomainObject(
347
                $this->contentHandler->loadContentInfo($spiLocation->contentId)
348
            );
349
        }
350
351
        return new Location(
352
            array(
353
                'contentInfo' => $contentInfo,
354
                'id' => $spiLocation->id,
355
                'priority' => $spiLocation->priority,
356
                'hidden' => $spiLocation->hidden,
357
                'invisible' => $spiLocation->invisible,
358
                'remoteId' => $spiLocation->remoteId,
359
                'parentLocationId' => $spiLocation->parentId,
360
                'pathString' => $spiLocation->pathString,
361
                'depth' => $spiLocation->depth,
362
                'sortField' => $spiLocation->sortField,
363
                'sortOrder' => $spiLocation->sortOrder,
364
            )
365
        );
366
    }
367
368
    /**
369
     * Creates an array of SPI location create structs from given array of API location create structs.
370
     *
371
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
372
     *
373
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
374
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
375
     * @param mixed $mainLocation
376
     * @param mixed $contentId
377
     * @param mixed $contentVersionNo
378
     *
379
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
380
     */
381
    public function buildSPILocationCreateStruct(
382
        $locationCreateStruct,
383
        APILocation $parentLocation,
384
        $mainLocation,
385
        $contentId,
386
        $contentVersionNo
387
    ) {
388
        if ($locationCreateStruct->priority !== null && !is_int($locationCreateStruct->priority)) {
389
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
390
        }
391
392
        if (!is_bool($locationCreateStruct->hidden)) {
393
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
394
        }
395
396
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
397
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
398
        }
399
400
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
401
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
402
        }
403
404
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
405
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
406
        }
407
408
        $remoteId = $locationCreateStruct->remoteId;
409
        if (null === $remoteId) {
410
            $remoteId = $this->getUniqueHash($locationCreateStruct);
411
        } else {
412
            try {
413
                $this->locationHandler->loadByRemoteId($remoteId);
414
                throw new InvalidArgumentException(
415
                    '$locationCreateStructs',
416
                    "Another Location with remoteId '{$remoteId}' exists"
417
                );
418
            } catch (NotFoundException $e) {
419
                // Do nothing
420
            }
421
        }
422
423
        return new SPILocationCreateStruct(
424
            array(
425
                'priority' => $locationCreateStruct->priority,
426
                'hidden' => $locationCreateStruct->hidden,
427
                // If we declare the new Location as hidden, it is automatically invisible
428
                // Otherwise it picks up visibility from parent Location
429
                // Note: There is no need to check for hidden status of parent, as hidden Location
430
                // is always invisible as well
431
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
432
                'remoteId' => $remoteId,
433
                'contentId' => $contentId,
434
                'contentVersion' => $contentVersionNo,
435
                // pathIdentificationString will be set in storage
436
                'pathIdentificationString' => null,
437
                'mainLocationId' => $mainLocation,
438
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
439
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
440
                'parentId' => $locationCreateStruct->parentLocationId,
441
            )
442
        );
443
    }
444
445
    /**
446
     * Checks if given $sortField value is one of the defined sort field constants.
447
     *
448
     * @param mixed $sortField
449
     *
450
     * @return bool
451
     */
452
    public function isValidLocationSortField($sortField)
453
    {
454
        switch ($sortField) {
455
            case APILocation::SORT_FIELD_PATH:
456
            case APILocation::SORT_FIELD_PUBLISHED:
457
            case APILocation::SORT_FIELD_MODIFIED:
458
            case APILocation::SORT_FIELD_SECTION:
459
            case APILocation::SORT_FIELD_DEPTH:
460
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
461
            case APILocation::SORT_FIELD_CLASS_NAME:
462
            case APILocation::SORT_FIELD_PRIORITY:
463
            case APILocation::SORT_FIELD_NAME:
464
            case APILocation::SORT_FIELD_MODIFIED_SUBNODE:
0 ignored issues
show
Deprecated Code introduced by
The constant eZ\Publish\API\Repositor..._FIELD_MODIFIED_SUBNODE has been deprecated.

This class constant has been deprecated.

Loading history...
465
            case APILocation::SORT_FIELD_NODE_ID:
466
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
467
                return true;
468
        }
469
470
        return false;
471
    }
472
473
    /**
474
     * Checks if given $sortOrder value is one of the defined sort order constants.
475
     *
476
     * @param mixed $sortOrder
477
     *
478
     * @return bool
479
     */
480
    public function isValidLocationSortOrder($sortOrder)
481
    {
482
        switch ($sortOrder) {
483
            case APILocation::SORT_ORDER_DESC:
484
            case APILocation::SORT_ORDER_ASC:
485
                return true;
486
        }
487
488
        return false;
489
    }
490
491
    /**
492
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
493
     *
494
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
495
     *
496
     * @param mixed $list
497
     * @param string $argumentName
498
     */
499
    public function validateTranslatedList($list, $argumentName)
500
    {
501
        if (!is_array($list)) {
502
            throw new InvalidArgumentType($argumentName, 'array', $list);
503
        }
504
505
        foreach ($list as $languageCode => $translation) {
506
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
507
508
            if (!is_string($translation)) {
509
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
510
            }
511
        }
512
    }
513
514
    /**
515
     * Returns \DateTime object from given $timestamp in environment timezone.
516
     *
517
     * This method is needed because constructing \DateTime with $timestamp will
518
     * return the object in UTC timezone.
519
     *
520
     * @param int $timestamp
521
     *
522
     * @return \DateTime
523
     */
524
    public function getDateTime($timestamp)
525
    {
526
        $dateTime = new DateTime();
527
        $dateTime->setTimestamp($timestamp);
528
529
        return $dateTime;
530
    }
531
532
    /**
533
     * Creates unique hash string for given $object.
534
     *
535
     * Used for remoteId.
536
     *
537
     * @param object $object
538
     *
539
     * @return string
540
     */
541
    public function getUniqueHash($object)
542
    {
543
        return md5(uniqid(get_class($object), true));
544
    }
545
}
546