Completed
Push — master ( cf2d59...36802d )
by André
16:28
created

DomainMapper::getDateTime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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