Completed
Push — sf_cache ( f6a6ab...bf1241 )
by André
35:58 queued 24:16
created

buildLocationDomainObjectsOnSearchResult()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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