Completed
Push — 7.4 ( 084064...ff0312 )
by
unknown
39:02 queued 19:42
created

DomainMapper::isRootLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
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\API\Repository\Values\Content\Search\SearchResult;
12
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
13
use eZ\Publish\SPI\Persistence\Content\Handler as ContentHandler;
14
use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandler;
15
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
16
use eZ\Publish\SPI\Persistence\Content\Type\Handler as TypeHandler;
17
use eZ\Publish\Core\Repository\Values\Content\Content;
18
use eZ\Publish\Core\Repository\Values\Content\ContentProxy;
19
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
20
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
21
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
22
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
23
use eZ\Publish\API\Repository\Values\Content\Field;
24
use eZ\Publish\Core\Repository\Values\Content\Relation;
25
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
26
use eZ\Publish\Core\Repository\Values\Content\Location;
27
use eZ\Publish\SPI\Persistence\Content as SPIContent;
28
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
29
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo;
30
use eZ\Publish\SPI\Persistence\Content\ContentInfo as SPIContentInfo;
31
use eZ\Publish\SPI\Persistence\Content\Relation as SPIRelation;
32
use eZ\Publish\SPI\Persistence\Content\Type as SPIType;
33
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct as SPILocationCreateStruct;
34
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
35
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
36
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
37
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
38
use DateTime;
39
40
/**
41
 * DomainMapper is an internal service.
42
 *
43
 * @internal Meant for internal use by Repository.
44
 */
45
class DomainMapper
46
{
47
    const MAX_LOCATION_PRIORITY = 2147483647;
48
    const MIN_LOCATION_PRIORITY = -2147483648;
49
50
    /**
51
     * @var \eZ\Publish\SPI\Persistence\Content\Handler
52
     */
53
    protected $contentHandler;
54
55
    /**
56
     * @var \eZ\Publish\SPI\Persistence\Content\Location\Handler
57
     */
58
    protected $locationHandler;
59
60
    /**
61
     * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
62
     */
63
    protected $contentTypeHandler;
64
65
    /**
66
     * @var \eZ\Publish\Core\Repository\Helper\ContentTypeDomainMapper
67
     */
68
    protected $contentTypeDomainMapper;
69
70
    /**
71
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
72
     */
73
    protected $contentLanguageHandler;
74
75
    /**
76
     * @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry
77
     */
78
    protected $fieldTypeRegistry;
79
80
    /**
81
     * Setups service with reference to repository.
82
     *
83
     * @param \eZ\Publish\SPI\Persistence\Content\Handler $contentHandler
84
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler
85
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
86
     * @param \eZ\Publish\Core\Repository\Helper\ContentTypeDomainMapper $contentTypeDomainMapper
87
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $contentLanguageHandler
88
     * @param \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry $fieldTypeRegistry
89
     */
90
    public function __construct(
91
        ContentHandler $contentHandler,
92
        LocationHandler $locationHandler,
93
        TypeHandler $contentTypeHandler,
94
        ContentTypeDomainMapper $contentTypeDomainMapper,
95
        LanguageHandler $contentLanguageHandler,
96
        FieldTypeRegistry $fieldTypeRegistry
97
    ) {
98
        $this->contentHandler = $contentHandler;
99
        $this->locationHandler = $locationHandler;
100
        $this->contentTypeHandler = $contentTypeHandler;
101
        $this->contentTypeDomainMapper = $contentTypeDomainMapper;
102
        $this->contentLanguageHandler = $contentLanguageHandler;
103
        $this->fieldTypeRegistry = $fieldTypeRegistry;
104
    }
105
106
    /**
107
     * Builds a Content domain object from value object returned from persistence.
108
     *
109
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
110
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
111
     * @param array $prioritizedLanguages Prioritized language codes to filter fields on
112
     * @param string|null $fieldAlwaysAvailableLanguage Language code fallback if a given field is not found in $prioritizedLanguages
113
     *
114
     * @return \eZ\Publish\Core\Repository\Values\Content\Content
115
     */
116
    public function buildContentDomainObject(
117
        SPIContent $spiContent,
118
        ContentType $contentType,
119
        array $prioritizedLanguages = [],
120
        string $fieldAlwaysAvailableLanguage = null
121
    ) {
122
        $prioritizedFieldLanguageCode = null;
123
        if (!empty($prioritizedLanguages)) {
124
            $availableFieldLanguageMap = array_fill_keys($spiContent->versionInfo->languageCodes, true);
125
            foreach ($prioritizedLanguages as $prioritizedLanguage) {
126
                if (isset($availableFieldLanguageMap[$prioritizedLanguage])) {
127
                    $prioritizedFieldLanguageCode = $prioritizedLanguage;
128
                    break;
129
                }
130
            }
131
        }
132
133
        return new Content(
134
            array(
135
                'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $prioritizedLanguages, $fieldAlwaysAvailableLanguage),
136
                'versionInfo' => $this->buildVersionInfoDomainObject($spiContent->versionInfo, $prioritizedLanguages),
137
                'contentType' => $contentType,
138
                'prioritizedFieldLanguageCode' => $prioritizedFieldLanguageCode,
139
            )
140
        );
141
    }
142
143
    /**
144
     * Builds a Content proxy object (lazy loaded, loads as soon as used).
145
     */
146
    public function buildContentProxy(
147
        SPIContent\ContentInfo $info,
148
        array $prioritizedLanguages = [],
149
        bool $useAlwaysAvailable = true
150
    ): APIContent {
151
        $generator = $this->generatorForContentList([$info], $prioritizedLanguages, $useAlwaysAvailable);
152
153
        return new ContentProxy($generator, $info->id);
154
    }
155
156
    /**
157
     * Builds a list of Content proxy objects (lazy loaded, loads all as soon as one of them loads).
158
     *
159
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo[] $infoList
160
     * @param string[] $prioritizedLanguages
161
     * @param bool $useAlwaysAvailable
162
     *
163
     * @return \eZ\Publish\API\Repository\Values\Content\Content[<int>]
0 ignored issues
show
Documentation introduced by
The doc-type \eZ\Publish\API\Reposito...\Content\Content[<int>] could not be parsed: Expected "]" at position 2, but found "<". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
164
     */
165
    public function buildContentProxyList(
166
        array $infoList,
167
        array $prioritizedLanguages = [],
168
        bool $useAlwaysAvailable = true
169
    ): array {
170
        $list = [];
171
        $generator = $this->generatorForContentList($infoList, $prioritizedLanguages, $useAlwaysAvailable);
172
        foreach ($infoList as $info) {
173
            $list[$info->id] = new ContentProxy($generator, $info->id);
174
        }
175
176
        return $list;
177
    }
178
179
    /**
180
     * @todo Maybe change signature to generatorForContentList($contentIds, $prioritizedLanguages, $translations)
181
     * @todo to avoid keeping referance to $infoList all the way until the generator is called.
182
     *
183
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo[] $infoList
184
     * @param string[] $prioritizedLanguages
185
     * @param bool $useAlwaysAvailable
186
     *
187
     * @return \Generator
188
     */
189
    private function generatorForContentList(
190
        array $infoList,
191
        array $prioritizedLanguages = [],
192
        bool $useAlwaysAvailable = true
193
    ): \Generator {
194
        $contentIds = [];
195
        $contentTypeIds = [];
196
        $translations = $prioritizedLanguages;
197
        foreach ($infoList as $info) {
198
            $contentIds[] = $info->id;
199
            $contentTypeIds[] = $info->contentTypeId;
200
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
201
            // Might in some case load more languages then intended, but prioritised handling will pick right one
202
            if (!empty($prioritizedLanguages) && $useAlwaysAvailable && $info->alwaysAvailable) {
203
                $translations[] = $info->mainLanguageCode;
204
            }
205
        }
206
207
        unset($infoList);
208
209
        $contentList = $this->contentHandler->loadContentList($contentIds, array_unique($translations));
210
        $contentTypeList = $this->contentTypeHandler->loadContentTypeList(array_unique($contentTypeIds));
211
        while (!empty($contentList)) {
212
            $id = yield;
213
            /** @var \eZ\Publish\SPI\Persistence\Content\ContentInfo $info */
214
            $info = $contentList[$id]->versionInfo->contentInfo;
215
            yield $this->buildContentDomainObject(
216
                $contentList[$id],
217
                $this->contentTypeDomainMapper->buildContentTypeDomainObject(
218
                    $contentTypeList[$info->contentTypeId],
219
                    $prioritizedLanguages
220
                ),
221
                $prioritizedLanguages,
222
                $info->alwaysAvailable ? $info->mainLanguageCode : null
223
            );
224
225
            unset($contentList[$id]);
226
        }
227
    }
228
229
    /**
230
     * Returns an array of domain fields created from given array of SPI fields.
231
     *
232
     * @throws InvalidArgumentType On invalid $contentType
233
     *
234
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
235
     * @param ContentType|SPIType $contentType
236
     * @param array $prioritizedLanguages A language priority, filters returned fields and is used as prioritized language code on
237
     *                         returned value object. If not given all languages are returned.
238
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $prioritizedLanguages
239
     *
240
     * @return array
241
     */
242
    public function buildDomainFields(
243
        array $spiFields,
244
        $contentType,
245
        array $prioritizedLanguages = [],
246
        string $alwaysAvailableLanguage = null
247
    ) {
248
        if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) {
249
            throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType');
250
        }
251
252
        $fieldDefinitionsMap = [];
253
        foreach ($contentType->fieldDefinitions as $fieldDefinition) {
254
            $fieldDefinitionsMap[$fieldDefinition->id] = $fieldDefinition;
255
        }
256
257
        $fieldInFilterLanguagesMap = array();
258
        if (!empty($prioritizedLanguages) && $alwaysAvailableLanguage !== null) {
259
            foreach ($spiFields as $spiField) {
260
                if (in_array($spiField->languageCode, $prioritizedLanguages)) {
261
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
262
                }
263
            }
264
        }
265
266
        $fields = array();
267
        foreach ($spiFields as $spiField) {
268
            // We ignore fields in content not part of the content type
269
            if (!isset($fieldDefinitionsMap[$spiField->fieldDefinitionId])) {
270
                continue;
271
            }
272
273
            $fieldDefinition = $fieldDefinitionsMap[$spiField->fieldDefinitionId];
274
275
            if (!empty($prioritizedLanguages) && !in_array($spiField->languageCode, $prioritizedLanguages)) {
276
                // If filtering is enabled we ignore fields in other languages then $prioritizedLanguages, if:
277
                if ($alwaysAvailableLanguage === null) {
278
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
279
                    continue;
280
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
281
                    // Ignore field if it exists in one of the filtered languages
282
                    continue;
283
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
284
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
285
                    continue;
286
                }
287
            }
288
289
            $fields[$fieldDefinition->position][] = new Field(
290
                array(
291
                    'id' => $spiField->id,
292
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
293
                        ->fromPersistenceValue($spiField->value),
294
                    'languageCode' => $spiField->languageCode,
295
                    'fieldDefIdentifier' => $fieldDefinition->identifier,
296
                    'fieldTypeIdentifier' => $spiField->type,
297
                )
298
            );
299
        }
300
301
        // Sort fields by content type field definition priority
302
        ksort($fields, SORT_NUMERIC);
303
304
        // Flatten array
305
        return array_merge(...$fields);
306
    }
307
308
    /**
309
     * Builds a VersionInfo domain object from value object returned from persistence.
310
     *
311
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo
312
     * @param array $prioritizedLanguages
313
     *
314
     * @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo
315
     */
316
    public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo, array $prioritizedLanguages = [])
317
    {
318
        // Map SPI statuses to API
319
        switch ($spiVersionInfo->status) {
320
            case SPIVersionInfo::STATUS_ARCHIVED:
321
                $status = APIVersionInfo::STATUS_ARCHIVED;
322
                break;
323
324
            case SPIVersionInfo::STATUS_PUBLISHED:
325
                $status = APIVersionInfo::STATUS_PUBLISHED;
326
                break;
327
328
            case SPIVersionInfo::STATUS_DRAFT:
329
            default:
330
                $status = APIVersionInfo::STATUS_DRAFT;
331
        }
332
333
        // Find prioritised language among names
334
        $prioritizedNameLanguageCode = null;
335
        foreach ($prioritizedLanguages as $prioritizedLanguage) {
336
            if (isset($spiVersionInfo->names[$prioritizedLanguage])) {
337
                $prioritizedNameLanguageCode = $prioritizedLanguage;
338
                break;
339
            }
340
        }
341
342
        return new VersionInfo(
343
            array(
344
                'id' => $spiVersionInfo->id,
345
                'versionNo' => $spiVersionInfo->versionNo,
346
                'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate),
347
                'creatorId' => $spiVersionInfo->creatorId,
348
                'creationDate' => $this->getDateTime($spiVersionInfo->creationDate),
349
                'status' => $status,
350
                'initialLanguageCode' => $spiVersionInfo->initialLanguageCode,
351
                'languageCodes' => $spiVersionInfo->languageCodes,
352
                'names' => $spiVersionInfo->names,
353
                'contentInfo' => $this->buildContentInfoDomainObject($spiVersionInfo->contentInfo),
354
                'prioritizedNameLanguageCode' => $prioritizedNameLanguageCode,
355
            )
356
        );
357
    }
358
359
    /**
360
     * Builds a ContentInfo domain object from value object returned from persistence.
361
     *
362
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo
363
     *
364
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
365
     */
366
    public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo)
367
    {
368
        // Map SPI statuses to API
369
        switch ($spiContentInfo->status) {
370
            case SPIContentInfo::STATUS_TRASHED:
371
                $status = ContentInfo::STATUS_TRASHED;
372
                break;
373
374
            case SPIContentInfo::STATUS_PUBLISHED:
375
                $status = ContentInfo::STATUS_PUBLISHED;
376
                break;
377
378
            case SPIContentInfo::STATUS_DRAFT:
379
            default:
380
                $status = ContentInfo::STATUS_DRAFT;
381
        }
382
383
        return new ContentInfo(
384
            array(
385
                'id' => $spiContentInfo->id,
386
                'contentTypeId' => $spiContentInfo->contentTypeId,
387
                'name' => $spiContentInfo->name,
388
                'sectionId' => $spiContentInfo->sectionId,
389
                'currentVersionNo' => $spiContentInfo->currentVersionNo,
390
                'published' => $spiContentInfo->isPublished,
391
                'ownerId' => $spiContentInfo->ownerId,
392
                'modificationDate' => $spiContentInfo->modificationDate == 0 ?
393
                    null :
394
                    $this->getDateTime($spiContentInfo->modificationDate),
395
                'publishedDate' => $spiContentInfo->publicationDate == 0 ?
396
                    null :
397
                    $this->getDateTime($spiContentInfo->publicationDate),
398
                'alwaysAvailable' => $spiContentInfo->alwaysAvailable,
399
                'remoteId' => $spiContentInfo->remoteId,
400
                'mainLanguageCode' => $spiContentInfo->mainLanguageCode,
401
                'mainLocationId' => $spiContentInfo->mainLocationId,
402
                'status' => $status,
403
            )
404
        );
405
    }
406
407
    /**
408
     * Builds API Relation object from provided SPI Relation object.
409
     *
410
     * @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation
411
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo
412
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo
413
     *
414
     * @return \eZ\Publish\API\Repository\Values\Content\Relation
415
     */
416
    public function buildRelationDomainObject(
417
        SPIRelation $spiRelation,
418
        ContentInfo $sourceContentInfo,
419
        ContentInfo $destinationContentInfo
420
    ) {
421
        $sourceFieldDefinitionIdentifier = null;
422
        if ($spiRelation->sourceFieldDefinitionId !== null) {
423
            $contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId);
424
            foreach ($contentType->fieldDefinitions as $fieldDefinition) {
425
                if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) {
426
                    continue;
427
                }
428
429
                $sourceFieldDefinitionIdentifier = $fieldDefinition->identifier;
430
                break;
431
            }
432
        }
433
434
        return new Relation(
435
            array(
436
                'id' => $spiRelation->id,
437
                'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier,
438
                'type' => $spiRelation->type,
439
                'sourceContentInfo' => $sourceContentInfo,
440
                'destinationContentInfo' => $destinationContentInfo,
441
            )
442
        );
443
    }
444
445
    /**
446
     * @deprecated Since 7.2, use buildLocationWithContent(), buildLocation() or (private) mapLocation() instead.
447
     */
448
    public function buildLocationDomainObject(
449
        SPILocation $spiLocation,
450
        SPIContentInfo $contentInfo = null
451
    ) {
452
        if ($contentInfo === null) {
453
            return $this->buildLocation($spiLocation);
454
        }
455
456
        return $this->mapLocation(
457
            $spiLocation,
458
            $this->buildContentInfoDomainObject($contentInfo),
459
            $this->buildContentProxy($contentInfo)
460
        );
461
    }
462
463
    public function buildLocation(
464
        SPILocation $spiLocation,
465
        array $prioritizedLanguages = [],
466
        bool $useAlwaysAvailable = true
467
    ): APILocation {
468
        if ($this->isRootLocation($spiLocation)) {
469
            return $this->buildRootLocation($spiLocation);
470
        }
471
472
        $spiContentInfo = $this->contentHandler->loadContentInfo($spiLocation->contentId);
473
474
        return $this->mapLocation(
475
            $spiLocation,
476
            $this->buildContentInfoDomainObject($spiContentInfo),
477
            $this->buildContentProxy($spiContentInfo, $prioritizedLanguages, $useAlwaysAvailable)
478
        );
479
    }
480
481
    /**
482
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
483
     * @param \eZ\Publish\API\Repository\Values\Content\Content|null $content
484
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo|null $spiContentInfo
485
     *
486
     * @return \eZ\Publish\API\Repository\Values\Content\Location
487
     *
488
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
489
     */
490
    public function buildLocationWithContent(
491
        SPILocation $spiLocation,
492
        ?APIContent $content,
493
        ?SPIContentInfo $spiContentInfo = null
494
    ): APILocation {
495
        if ($this->isRootLocation($spiLocation)) {
496
            return $this->buildRootLocation($spiLocation);
497
        }
498
499
        if ($content === null) {
500
            throw new InvalidArgumentException('$content', "Location {$spiLocation->id} has missing Content");
501
        }
502
503
        if ($spiContentInfo !== null) {
504
            $contentInfo = $this->buildContentInfoDomainObject($spiContentInfo);
505
        } else {
506
            $contentInfo = $content->contentInfo;
507
        }
508
509
        return $this->mapLocation($spiLocation, $contentInfo, $content);
510
    }
511
512
    /**
513
     * Builds API Location object for tree root.
514
     *
515
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
516
     *
517
     * @return \eZ\Publish\API\Repository\Values\Content\Location
518
     */
519
    private function buildRootLocation(SPILocation $spiLocation): APILocation
520
    {
521
        //  first known commit of eZ Publish 3.x
522
        $legacyDateTime = $this->getDateTime(1030968000);
523
524
        // NOTE: this is hardcoded workaround for missing ContentInfo on root location
525
        return $this->mapLocation(
526
            $spiLocation,
527
            new ContentInfo([
528
                'id' => 0,
529
                'name' => 'Top Level Nodes',
530
                'sectionId' => 1,
531
                'mainLocationId' => 1,
532
                'contentTypeId' => 1,
533
                'currentVersionNo' => 1,
534
                'published' => 1,
535
                'ownerId' => 14, // admin user
536
                'modificationDate' => $legacyDateTime,
537
                'publishedDate' => $legacyDateTime,
538
                'alwaysAvailable' => 1,
539
                'remoteId' => null,
540
                'mainLanguageCode' => 'eng-GB',
541
            ]),
542
            new Content([])
543
        );
544
    }
545
546
    private function mapLocation(SPILocation $spiLocation, ContentInfo $contentInfo, APIContent $content): APILocation
547
    {
548
        return new Location(
549
            array(
550
                'content' => $content,
551
                'contentInfo' => $contentInfo,
552
                'id' => $spiLocation->id,
553
                'priority' => $spiLocation->priority,
554
                'hidden' => $spiLocation->hidden,
555
                'invisible' => $spiLocation->invisible,
556
                'remoteId' => $spiLocation->remoteId,
557
                'parentLocationId' => $spiLocation->parentId,
558
                'pathString' => $spiLocation->pathString,
559
                'depth' => $spiLocation->depth,
560
                'sortField' => $spiLocation->sortField,
561
                'sortOrder' => $spiLocation->sortOrder,
562
            )
563
        );
564
    }
565
566
    /**
567
     * Build API Content domain objects in bulk and apply to ContentSearchResult.
568
     *
569
     * Loading of Content objects are done in bulk.
570
     *
571
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI ContentInfo items as hits
572
     * @param array $languageFilter
573
     *
574
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo[] ContentInfo we did not find content for is returned.
575
     */
576
    public function buildContentDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
577
    {
578
        if (empty($result->searchHits)) {
579
            return [];
580
        }
581
582
        $contentIds = [];
583
        $contentTypeIds = [];
584
        $translations = $languageFilter['languages'] ?? [];
585
        $useAlwaysAvailable = $languageFilter['useAlwaysAvailable'] ?? true;
586
        foreach ($result->searchHits as $hit) {
587
            /** @var \eZ\Publish\SPI\Persistence\Content\ContentInfo $info */
588
            $info = $hit->valueObject;
589
            $contentIds[] = $info->id;
590
            $contentTypeIds[] = $info->contentTypeId;
591
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
592
            // Might in some case load more languages then intended, but prioritised handling will pick right one
593
            if (!empty($languageFilter['languages']) && $useAlwaysAvailable && $info->alwaysAvailable) {
594
                $translations[] = $info->mainLanguageCode;
595
            }
596
        }
597
598
        $missingContentList = [];
599
        $contentList = $this->contentHandler->loadContentList($contentIds, array_unique($translations));
600
        $contentTypeList = $this->contentTypeHandler->loadContentTypeList(array_unique($contentTypeIds));
601
        foreach ($result->searchHits as $key => $hit) {
602
            if (isset($contentList[$hit->valueObject->id])) {
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
603
                $hit->valueObject = $this->buildContentDomainObject(
604
                    $contentList[$hit->valueObject->id],
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
605
                    $this->contentTypeDomainMapper->buildContentTypeDomainObject(
606
                        $contentTypeList[$hit->valueObject->contentTypeId],
0 ignored issues
show
Documentation introduced by
The property contentTypeId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
607
                        $languageFilter['languages'] ?? []
608
                    ),
609
                    $languageFilter['languages'] ?? [],
610
                    $useAlwaysAvailable ? $hit->valueObject->mainLanguageCode : null
0 ignored issues
show
Documentation introduced by
The property mainLanguageCode does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
611
                );
612
            } else {
613
                $missingContentList[] = $hit->valueObject;
614
                unset($result->searchHits[$key]);
615
                --$result->totalCount;
616
            }
617
        }
618
619
        return $missingContentList;
620
    }
621
622
    /**
623
     * Build API Location and corresponding ContentInfo domain objects and apply to LocationSearchResult.
624
     *
625
     * This is done in order to be able to:
626
     * Load ContentInfo objects in bulk, generate proxy objects for Content that will loaded in bulk on-demand (on use).
627
     *
628
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI Location items as hits
629
     * @param array $languageFilter
630
     *
631
     * @return \eZ\Publish\SPI\Persistence\Content\Location[] Locations we did not find content info for is returned.
632
     */
633
    public function buildLocationDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
634
    {
635
        if (empty($result->searchHits)) {
636
            return [];
637
        }
638
639
        $contentIds = [];
640
        foreach ($result->searchHits as $hit) {
641
            $contentIds[] = $hit->valueObject->contentId;
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
642
        }
643
644
        $missingLocations = [];
645
        $contentInfoList = $this->contentHandler->loadContentInfoList($contentIds);
646
        $contentList = $this->buildContentProxyList(
647
            $contentInfoList,
648
            !empty($languageFilter['languages']) ? $languageFilter['languages'] : []
649
        );
650
        foreach ($result->searchHits as $key => $hit) {
651
            if (isset($contentInfoList[$hit->valueObject->contentId])) {
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
652
                $hit->valueObject = $this->buildLocationWithContent(
653
                    $hit->valueObject,
0 ignored issues
show
Compatibility introduced by
$hit->valueObject of type object<eZ\Publish\API\Re...ory\Values\ValueObject> is not a sub-type of object<eZ\Publish\SPI\Pe...tence\Content\Location>. It seems like you assume a child class of the class eZ\Publish\API\Repository\Values\ValueObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
654
                    $contentList[$hit->valueObject->contentId],
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
655
                    $contentInfoList[$hit->valueObject->contentId]
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
656
                );
657
            } else {
658
                $missingLocations[] = $hit->valueObject;
659
                unset($result->searchHits[$key]);
660
                --$result->totalCount;
661
            }
662
        }
663
664
        return $missingLocations;
665
    }
666
667
    /**
668
     * Creates an array of SPI location create structs from given array of API location create structs.
669
     *
670
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
671
     *
672
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
673
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
674
     * @param mixed $mainLocation
675
     * @param mixed $contentId
676
     * @param mixed $contentVersionNo
677
     *
678
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
679
     */
680
    public function buildSPILocationCreateStruct(
681
        $locationCreateStruct,
682
        APILocation $parentLocation,
683
        $mainLocation,
684
        $contentId,
685
        $contentVersionNo
686
    ) {
687
        if (!$this->isValidLocationPriority($locationCreateStruct->priority)) {
688
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
689
        }
690
691
        if (!is_bool($locationCreateStruct->hidden)) {
692
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
693
        }
694
695
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
696
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
697
        }
698
699
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
700
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
701
        }
702
703
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
704
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
705
        }
706
707
        $remoteId = $locationCreateStruct->remoteId;
708
        if (null === $remoteId) {
709
            $remoteId = $this->getUniqueHash($locationCreateStruct);
710
        } else {
711
            try {
712
                $this->locationHandler->loadByRemoteId($remoteId);
713
                throw new InvalidArgumentException(
714
                    '$locationCreateStructs',
715
                    "Another Location with remoteId '{$remoteId}' exists"
716
                );
717
            } catch (NotFoundException $e) {
718
                // Do nothing
719
            }
720
        }
721
722
        return new SPILocationCreateStruct(
723
            array(
724
                'priority' => $locationCreateStruct->priority,
725
                'hidden' => $locationCreateStruct->hidden,
726
                // If we declare the new Location as hidden, it is automatically invisible
727
                // Otherwise it picks up visibility from parent Location
728
                // Note: There is no need to check for hidden status of parent, as hidden Location
729
                // is always invisible as well
730
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
731
                'remoteId' => $remoteId,
732
                'contentId' => $contentId,
733
                'contentVersion' => $contentVersionNo,
734
                // pathIdentificationString will be set in storage
735
                'pathIdentificationString' => null,
736
                'mainLocationId' => $mainLocation,
737
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
738
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
739
                'parentId' => $locationCreateStruct->parentLocationId,
740
            )
741
        );
742
    }
743
744
    /**
745
     * Checks if given $sortField value is one of the defined sort field constants.
746
     *
747
     * @param mixed $sortField
748
     *
749
     * @return bool
750
     */
751
    public function isValidLocationSortField($sortField)
752
    {
753
        switch ($sortField) {
754
            case APILocation::SORT_FIELD_PATH:
755
            case APILocation::SORT_FIELD_PUBLISHED:
756
            case APILocation::SORT_FIELD_MODIFIED:
757
            case APILocation::SORT_FIELD_SECTION:
758
            case APILocation::SORT_FIELD_DEPTH:
759
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
760
            case APILocation::SORT_FIELD_CLASS_NAME:
761
            case APILocation::SORT_FIELD_PRIORITY:
762
            case APILocation::SORT_FIELD_NAME:
763
            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...
764
            case APILocation::SORT_FIELD_NODE_ID:
765
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
766
                return true;
767
        }
768
769
        return false;
770
    }
771
772
    /**
773
     * Checks if given $sortOrder value is one of the defined sort order constants.
774
     *
775
     * @param mixed $sortOrder
776
     *
777
     * @return bool
778
     */
779
    public function isValidLocationSortOrder($sortOrder)
780
    {
781
        switch ($sortOrder) {
782
            case APILocation::SORT_ORDER_DESC:
783
            case APILocation::SORT_ORDER_ASC:
784
                return true;
785
        }
786
787
        return false;
788
    }
789
790
    /**
791
     * Checks if given $priority is valid.
792
     *
793
     * @param int $priority
794
     *
795
     * @return bool
796
     */
797
    public function isValidLocationPriority($priority)
798
    {
799
        if ($priority === null) {
800
            return true;
801
        }
802
803
        return is_int($priority) && $priority >= self::MIN_LOCATION_PRIORITY && $priority <= self::MAX_LOCATION_PRIORITY;
804
    }
805
806
    /**
807
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
808
     *
809
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
810
     *
811
     * @param mixed $list
812
     * @param string $argumentName
813
     */
814
    public function validateTranslatedList($list, $argumentName)
815
    {
816
        if (!is_array($list)) {
817
            throw new InvalidArgumentType($argumentName, 'array', $list);
818
        }
819
820
        foreach ($list as $languageCode => $translation) {
821
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
822
823
            if (!is_string($translation)) {
824
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
825
            }
826
        }
827
    }
828
829
    /**
830
     * Returns \DateTime object from given $timestamp in environment timezone.
831
     *
832
     * This method is needed because constructing \DateTime with $timestamp will
833
     * return the object in UTC timezone.
834
     *
835
     * @param int $timestamp
836
     *
837
     * @return \DateTime
838
     */
839
    public function getDateTime($timestamp)
840
    {
841
        $dateTime = new DateTime();
842
        $dateTime->setTimestamp($timestamp);
843
844
        return $dateTime;
845
    }
846
847
    /**
848
     * Creates unique hash string for given $object.
849
     *
850
     * Used for remoteId.
851
     *
852
     * @param object $object
853
     *
854
     * @return string
855
     */
856
    public function getUniqueHash($object)
857
    {
858
        return md5(uniqid(get_class($object), true));
859
    }
860
861
    /**
862
     * Returns true if given location is a tree root.
863
     *
864
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
865
     *
866
     * @return bool
867
     */
868
    private function isRootLocation(SPILocation $spiLocation): bool
869
    {
870
        return $spiLocation->id === $spiLocation->parentId;
871
    }
872
}
873