Completed
Push — migrate-files-no-interaction ( 025687...608925 )
by
unknown
46:43 queued 18:48
created

DomainMapper::validateTranslatedList()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 2
dl 0
loc 14
rs 9.2
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
        $prioritizedFieldLanguageCode = null;
111
        $prioritizedLanguages = $fieldLanguages ?: [];
112
        if (!empty($prioritizedLanguages)) {
113
            $availableFieldLanguageMap = array_fill_keys($spiContent->versionInfo->languageCodes, true);
114
            foreach ($prioritizedLanguages as $prioritizedLanguage) {
115
                if (isset($availableFieldLanguageMap[$prioritizedLanguage])) {
116
                    $prioritizedFieldLanguageCode = $prioritizedLanguage;
117
                    break;
118
                }
119
            }
120
        }
121
122
        return new Content(
123
            array(
124
                'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $fieldLanguages, $fieldAlwaysAvailableLanguage),
125
                'versionInfo' => $this->buildVersionInfoDomainObject($spiContent->versionInfo, $prioritizedLanguages),
126
                'prioritizedFieldLanguageCode' => $prioritizedFieldLanguageCode,
127
            )
128
        );
129
    }
130
131
    /**
132
     * Returns an array of domain fields created from given array of SPI fields.
133
     *
134
     * @throws InvalidArgumentType On invalid $contentType
135
     *
136
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
137
     * @param ContentType|SPIType $contentType
138
     * @param array $languages A language priority, filters returned fields and is used as prioritized language code on
139
     *                         returned value object. If not given all languages are returned.
140
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $languages
141
     *
142
     * @return array
143
     */
144
    public function buildDomainFields(array $spiFields, $contentType, array $languages = null, $alwaysAvailableLanguage = null)
145
    {
146
        if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) {
147
            throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType');
148
        }
149
150
        $fieldIdentifierMap = array();
151
        foreach ($contentType->fieldDefinitions as $fieldDefinitions) {
152
            $fieldIdentifierMap[$fieldDefinitions->id] = $fieldDefinitions->identifier;
153
        }
154
155
        $fieldInFilterLanguagesMap = array();
156
        if ($languages !== null && $alwaysAvailableLanguage !== null) {
157
            foreach ($spiFields as $spiField) {
158
                if (in_array($spiField->languageCode, $languages)) {
159
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
160
                }
161
            }
162
        }
163
164
        $fields = array();
165
        foreach ($spiFields as $spiField) {
166
            // We ignore fields in content not part of the content type
167
            if (!isset($fieldIdentifierMap[$spiField->fieldDefinitionId])) {
168
                continue;
169
            }
170
171
            if ($languages !== null && !in_array($spiField->languageCode, $languages)) {
172
                // If filtering is enabled we ignore fields in other languages then $fieldLanguages, if:
173
                if ($alwaysAvailableLanguage === null) {
174
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
175
                    continue;
176
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
177
                    // Ignore field if it exists in one of the filtered languages
178
                    continue;
179
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
180
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
181
                    continue;
182
                }
183
            }
184
185
            $fields[] = new Field(
186
                array(
187
                    'id' => $spiField->id,
188
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
189
                        ->fromPersistenceValue($spiField->value),
190
                    'languageCode' => $spiField->languageCode,
191
                    'fieldDefIdentifier' => $fieldIdentifierMap[$spiField->fieldDefinitionId],
192
                    'fieldTypeIdentifier' => $spiField->type,
193
                )
194
            );
195
        }
196
197
        return $fields;
198
    }
199
200
    /**
201
     * Builds a VersionInfo domain object from value object returned from persistence.
202
     *
203
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo
204
     * @param array $prioritizedLanguages
205
     *
206
     * @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo
207
     */
208
    public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo, array $prioritizedLanguages = [])
209
    {
210
        // Map SPI statuses to API
211
        switch ($spiVersionInfo->status) {
212
            case SPIVersionInfo::STATUS_ARCHIVED:
213
                $status = APIVersionInfo::STATUS_ARCHIVED;
214
                break;
215
216
            case SPIVersionInfo::STATUS_PUBLISHED:
217
                $status = APIVersionInfo::STATUS_PUBLISHED;
218
                break;
219
220
            case SPIVersionInfo::STATUS_DRAFT:
221
            default:
222
                $status = APIVersionInfo::STATUS_DRAFT;
223
        }
224
225
        // Find prioritised language among names
226
        $prioritizedNameLanguageCode = null;
227
        foreach ($prioritizedLanguages as $prioritizedLanguage) {
228
            if (isset($spiVersionInfo->names[$prioritizedLanguage])) {
229
                $prioritizedNameLanguageCode = $prioritizedLanguage;
230
                break;
231
            }
232
        }
233
234
        return new VersionInfo(
235
            array(
236
                'id' => $spiVersionInfo->id,
237
                'versionNo' => $spiVersionInfo->versionNo,
238
                'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate),
239
                'creatorId' => $spiVersionInfo->creatorId,
240
                'creationDate' => $this->getDateTime($spiVersionInfo->creationDate),
241
                'status' => $status,
242
                'initialLanguageCode' => $spiVersionInfo->initialLanguageCode,
243
                'languageCodes' => $spiVersionInfo->languageCodes,
244
                'names' => $spiVersionInfo->names,
245
                'contentInfo' => $this->buildContentInfoDomainObject($spiVersionInfo->contentInfo),
246
                'prioritizedNameLanguageCode' => $prioritizedNameLanguageCode,
247
            )
248
        );
249
    }
250
251
    /**
252
     * Builds a ContentInfo domain object from value object returned from persistence.
253
     *
254
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo
255
     *
256
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
257
     */
258
    public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo)
259
    {
260
        return new ContentInfo(
261
            array(
262
                'id' => $spiContentInfo->id,
263
                'contentTypeId' => $spiContentInfo->contentTypeId,
264
                'name' => $spiContentInfo->name,
265
                'sectionId' => $spiContentInfo->sectionId,
266
                'currentVersionNo' => $spiContentInfo->currentVersionNo,
267
                'published' => $spiContentInfo->isPublished,
268
                'ownerId' => $spiContentInfo->ownerId,
269
                'modificationDate' => $spiContentInfo->modificationDate == 0 ?
270
                    null :
271
                    $this->getDateTime($spiContentInfo->modificationDate),
272
                'publishedDate' => $spiContentInfo->publicationDate == 0 ?
273
                    null :
274
                    $this->getDateTime($spiContentInfo->publicationDate),
275
                'alwaysAvailable' => $spiContentInfo->alwaysAvailable,
276
                'remoteId' => $spiContentInfo->remoteId,
277
                'mainLanguageCode' => $spiContentInfo->mainLanguageCode,
278
                'mainLocationId' => $spiContentInfo->mainLocationId,
279
            )
280
        );
281
    }
282
283
    /**
284
     * Builds API Relation object from provided SPI Relation object.
285
     *
286
     * @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation
287
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo
288
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo
289
     *
290
     * @return \eZ\Publish\API\Repository\Values\Content\Relation
291
     */
292
    public function buildRelationDomainObject(
293
        SPIRelation $spiRelation,
294
        ContentInfo $sourceContentInfo,
295
        ContentInfo $destinationContentInfo
296
    ) {
297
        $sourceFieldDefinitionIdentifier = null;
298
        if ($spiRelation->sourceFieldDefinitionId !== null) {
299
            $contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId);
300
            foreach ($contentType->fieldDefinitions as $fieldDefinition) {
301
                if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) {
302
                    continue;
303
                }
304
305
                $sourceFieldDefinitionIdentifier = $fieldDefinition->identifier;
306
                break;
307
            }
308
        }
309
310
        return new Relation(
311
            array(
312
                'id' => $spiRelation->id,
313
                'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier,
314
                'type' => $spiRelation->type,
315
                'sourceContentInfo' => $sourceContentInfo,
316
                'destinationContentInfo' => $destinationContentInfo,
317
            )
318
        );
319
    }
320
321
    /**
322
     * Builds domain location object from provided persistence location.
323
     *
324
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
325
     *
326
     * @return \eZ\Publish\API\Repository\Values\Content\Location
327
     */
328
    public function buildLocationDomainObject(SPILocation $spiLocation)
329
    {
330
        // TODO: this is hardcoded workaround for missing ContentInfo on root location
331
        if ($spiLocation->id == 1) {
332
            $legacyDateTime = $this->getDateTime(1030968000); //  first known commit of eZ Publish 3.x
333
            $contentInfo = new ContentInfo(
334
                array(
335
                    'id' => 0,
336
                    'name' => 'Top Level Nodes',
337
                    'sectionId' => 1,
338
                    'mainLocationId' => 1,
339
                    'contentTypeId' => 1,
340
                    'currentVersionNo' => 1,
341
                    'published' => 1,
342
                    'ownerId' => 14, // admin user
343
                    'modificationDate' => $legacyDateTime,
344
                    'publishedDate' => $legacyDateTime,
345
                    'alwaysAvailable' => 1,
346
                    'remoteId' => null,
347
                    'mainLanguageCode' => 'eng-GB',
348
                )
349
            );
350
        } else {
351
            $contentInfo = $this->buildContentInfoDomainObject(
352
                $this->contentHandler->loadContentInfo($spiLocation->contentId)
353
            );
354
        }
355
356
        return new Location(
357
            array(
358
                'contentInfo' => $contentInfo,
359
                'id' => $spiLocation->id,
360
                'priority' => $spiLocation->priority,
361
                'hidden' => $spiLocation->hidden,
362
                'invisible' => $spiLocation->invisible,
363
                'remoteId' => $spiLocation->remoteId,
364
                'parentLocationId' => $spiLocation->parentId,
365
                'pathString' => $spiLocation->pathString,
366
                'depth' => $spiLocation->depth,
367
                'sortField' => $spiLocation->sortField,
368
                'sortOrder' => $spiLocation->sortOrder,
369
            )
370
        );
371
    }
372
373
    /**
374
     * Creates an array of SPI location create structs from given array of API location create structs.
375
     *
376
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
377
     *
378
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
379
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
380
     * @param mixed $mainLocation
381
     * @param mixed $contentId
382
     * @param mixed $contentVersionNo
383
     *
384
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
385
     */
386
    public function buildSPILocationCreateStruct(
387
        $locationCreateStruct,
388
        APILocation $parentLocation,
389
        $mainLocation,
390
        $contentId,
391
        $contentVersionNo
392
    ) {
393
        if ($locationCreateStruct->priority !== null && !is_int($locationCreateStruct->priority)) {
394
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
395
        }
396
397
        if (!is_bool($locationCreateStruct->hidden)) {
398
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
399
        }
400
401
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
402
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
403
        }
404
405
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
406
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
407
        }
408
409
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
410
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
411
        }
412
413
        $remoteId = $locationCreateStruct->remoteId;
414
        if (null === $remoteId) {
415
            $remoteId = $this->getUniqueHash($locationCreateStruct);
416
        } else {
417
            try {
418
                $this->locationHandler->loadByRemoteId($remoteId);
419
                throw new InvalidArgumentException(
420
                    '$locationCreateStructs',
421
                    "Another Location with remoteId '{$remoteId}' exists"
422
                );
423
            } catch (NotFoundException $e) {
424
                // Do nothing
425
            }
426
        }
427
428
        return new SPILocationCreateStruct(
429
            array(
430
                'priority' => $locationCreateStruct->priority,
431
                'hidden' => $locationCreateStruct->hidden,
432
                // If we declare the new Location as hidden, it is automatically invisible
433
                // Otherwise it picks up visibility from parent Location
434
                // Note: There is no need to check for hidden status of parent, as hidden Location
435
                // is always invisible as well
436
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
437
                'remoteId' => $remoteId,
438
                'contentId' => $contentId,
439
                'contentVersion' => $contentVersionNo,
440
                // pathIdentificationString will be set in storage
441
                'pathIdentificationString' => null,
442
                'mainLocationId' => $mainLocation,
443
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
444
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
445
                'parentId' => $locationCreateStruct->parentLocationId,
446
            )
447
        );
448
    }
449
450
    /**
451
     * Checks if given $sortField value is one of the defined sort field constants.
452
     *
453
     * @param mixed $sortField
454
     *
455
     * @return bool
456
     */
457
    public function isValidLocationSortField($sortField)
458
    {
459
        switch ($sortField) {
460
            case APILocation::SORT_FIELD_PATH:
461
            case APILocation::SORT_FIELD_PUBLISHED:
462
            case APILocation::SORT_FIELD_MODIFIED:
463
            case APILocation::SORT_FIELD_SECTION:
464
            case APILocation::SORT_FIELD_DEPTH:
465
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
466
            case APILocation::SORT_FIELD_CLASS_NAME:
467
            case APILocation::SORT_FIELD_PRIORITY:
468
            case APILocation::SORT_FIELD_NAME:
469
            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...
470
            case APILocation::SORT_FIELD_NODE_ID:
471
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
472
                return true;
473
        }
474
475
        return false;
476
    }
477
478
    /**
479
     * Checks if given $sortOrder value is one of the defined sort order constants.
480
     *
481
     * @param mixed $sortOrder
482
     *
483
     * @return bool
484
     */
485
    public function isValidLocationSortOrder($sortOrder)
486
    {
487
        switch ($sortOrder) {
488
            case APILocation::SORT_ORDER_DESC:
489
            case APILocation::SORT_ORDER_ASC:
490
                return true;
491
        }
492
493
        return false;
494
    }
495
496
    /**
497
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
498
     *
499
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
500
     *
501
     * @param mixed $list
502
     * @param string $argumentName
503
     */
504
    public function validateTranslatedList($list, $argumentName)
505
    {
506
        if (!is_array($list)) {
507
            throw new InvalidArgumentType($argumentName, 'array', $list);
508
        }
509
510
        foreach ($list as $languageCode => $translation) {
511
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
512
513
            if (!is_string($translation)) {
514
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
515
            }
516
        }
517
    }
518
519
    /**
520
     * Returns \DateTime object from given $timestamp in environment timezone.
521
     *
522
     * This method is needed because constructing \DateTime with $timestamp will
523
     * return the object in UTC timezone.
524
     *
525
     * @param int $timestamp
526
     *
527
     * @return \DateTime
528
     */
529
    public function getDateTime($timestamp)
530
    {
531
        $dateTime = new DateTime();
532
        $dateTime->setTimestamp($timestamp);
533
534
        return $dateTime;
535
    }
536
537
    /**
538
     * Creates unique hash string for given $object.
539
     *
540
     * Used for remoteId.
541
     *
542
     * @param object $object
543
     *
544
     * @return string
545
     */
546
    public function getUniqueHash($object)
547
    {
548
        return md5(uniqid(get_class($object), true));
549
    }
550
}
551