Completed
Push — 7.3 ( 6b087b...18a66f )
by Łukasz
21:56
created

DomainMapper::buildRootLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 26
rs 9.504
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\SPI\Persistence\Content\Language\Handler
67
     */
68
    protected $contentLanguageHandler;
69
70
    /**
71
     * @var FieldTypeRegistry
72
     */
73
    protected $fieldTypeRegistry;
74
75
    /**
76
     * Setups service with reference to repository.
77
     *
78
     * @param \eZ\Publish\SPI\Persistence\Content\Handler $contentHandler
79
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler
80
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
81
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $contentLanguageHandler
82
     * @param FieldTypeRegistry $fieldTypeRegistry
83
     */
84
    public function __construct(
85
        ContentHandler $contentHandler,
86
        LocationHandler $locationHandler,
87
        TypeHandler $contentTypeHandler,
88
        LanguageHandler $contentLanguageHandler,
89
        FieldTypeRegistry $fieldTypeRegistry
90
    ) {
91
        $this->contentHandler = $contentHandler;
92
        $this->locationHandler = $locationHandler;
93
        $this->contentTypeHandler = $contentTypeHandler;
94
        $this->contentLanguageHandler = $contentLanguageHandler;
95
        $this->fieldTypeRegistry = $fieldTypeRegistry;
96
    }
97
98
    /**
99
     * Builds a Content domain object from value object returned from persistence.
100
     *
101
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
102
     * @param ContentType|SPIType $contentType
103
     * @param array $prioritizedLanguages Prioritized language codes to filter fields on
104
     * @param string|null $fieldAlwaysAvailableLanguage Language code fallback if a given field is not found in $prioritizedLanguages
105
     *
106
     * @return \eZ\Publish\Core\Repository\Values\Content\Content
107
     */
108
    public function buildContentDomainObject(
109
        SPIContent $spiContent,
110
        $contentType = null,
111
        array $prioritizedLanguages = [],
112
        string $fieldAlwaysAvailableLanguage = null
113
    ) {
114
        if ($contentType === null) {
115
            $contentType = $this->contentTypeHandler->load(
116
                $spiContent->versionInfo->contentInfo->contentTypeId
117
            );
118
        }
119
120
        $prioritizedFieldLanguageCode = null;
121
        if (!empty($prioritizedLanguages)) {
122
            $availableFieldLanguageMap = array_fill_keys($spiContent->versionInfo->languageCodes, true);
123
            foreach ($prioritizedLanguages as $prioritizedLanguage) {
124
                if (isset($availableFieldLanguageMap[$prioritizedLanguage])) {
125
                    $prioritizedFieldLanguageCode = $prioritizedLanguage;
126
                    break;
127
                }
128
            }
129
        }
130
131
        return new Content(
132
            array(
133
                'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $prioritizedLanguages, $fieldAlwaysAvailableLanguage),
134
                'versionInfo' => $this->buildVersionInfoDomainObject($spiContent->versionInfo, $prioritizedLanguages),
135
                'prioritizedFieldLanguageCode' => $prioritizedFieldLanguageCode,
136
            )
137
        );
138
    }
139
140
    /**
141
     * Builds a Content proxy object (lazy loaded, loads as soon as used).
142
     */
143
    public function buildContentProxy(
144
        SPIContent\ContentInfo $info,
145
        array $prioritizedLanguages = [],
146
        bool $useAlwaysAvailable = true
147
    ): APIContent {
148
        $generator = $this->generatorForContentList([$info], $prioritizedLanguages, $useAlwaysAvailable);
149
150
        return new ContentProxy($generator, $info->id);
151
    }
152
153
    /**
154
     * Builds a list of Content proxy objects (lazy loaded, loads all as soon as one of them loads).
155
     *
156
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo[] $infoList
157
     * @param string[] $prioritizedLanguages
158
     * @param bool $useAlwaysAvailable
159
     *
160
     * @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...
161
     */
162
    public function buildContentProxyList(
163
        array $infoList,
164
        array $prioritizedLanguages = [],
165
        bool $useAlwaysAvailable = true
166
    ): array {
167
        $list = [];
168
        $generator = $this->generatorForContentList($infoList, $prioritizedLanguages, $useAlwaysAvailable);
169
        foreach ($infoList as $info) {
170
            $list[$info->id] = new ContentProxy($generator, $info->id);
171
        }
172
173
        return $list;
174
    }
175
176
    /**
177
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo[] $infoList
178
     * @param string[] $prioritizedLanguages
179
     * @param bool $useAlwaysAvailable
180
     *
181
     * @return \Generator
182
     */
183
    private function generatorForContentList(
184
        array $infoList,
185
        array $prioritizedLanguages = [],
186
        bool $useAlwaysAvailable = true
187
    ): \Generator {
188
        $contentIds = [];
189
        $translations = $prioritizedLanguages;
190
        foreach ($infoList as $info) {
191
            $contentIds[] = $info->id;
192
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
193
            // Might in some case load more languages then intended, but prioritised handling will pick right one
194
            if (!empty($prioritizedLanguages) && $useAlwaysAvailable && $info->alwaysAvailable) {
195
                $translations[] = $info->mainLanguageCode;
196
            }
197
        }
198
199
        $list = $this->contentHandler->loadContentList($contentIds, array_unique($translations));
200
201
        while (!empty($list)) {
202
            $id = yield;
203
            $info = $list[$id]->versionInfo->contentInfo;
204
            yield $this->buildContentDomainObject(
205
                $list[$id],
206
                null,
207
                //@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases
208
                $prioritizedLanguages,
209
                $info->alwaysAvailable ? $info->mainLanguageCode : null
210
            );
211
212
            unset($list[$id]);
213
        }
214
    }
215
216
    /**
217
     * Returns an array of domain fields created from given array of SPI fields.
218
     *
219
     * @throws InvalidArgumentType On invalid $contentType
220
     *
221
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
222
     * @param ContentType|SPIType $contentType
223
     * @param array $prioritizedLanguages A language priority, filters returned fields and is used as prioritized language code on
224
     *                         returned value object. If not given all languages are returned.
225
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $prioritizedLanguages
226
     *
227
     * @return array
228
     */
229
    public function buildDomainFields(
230
        array $spiFields,
231
        $contentType,
232
        array $prioritizedLanguages = [],
233
        string $alwaysAvailableLanguage = null
234
    ) {
235
        if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) {
236
            throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType');
237
        }
238
239
        $fieldDefinitionsMap = [];
240
        foreach ($contentType->fieldDefinitions as $fieldDefinition) {
241
            $fieldDefinitionsMap[$fieldDefinition->id] = $fieldDefinition;
242
        }
243
244
        $fieldInFilterLanguagesMap = array();
245
        if (!empty($prioritizedLanguages) && $alwaysAvailableLanguage !== null) {
246
            foreach ($spiFields as $spiField) {
247
                if (in_array($spiField->languageCode, $prioritizedLanguages)) {
248
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
249
                }
250
            }
251
        }
252
253
        $fields = array();
254
        foreach ($spiFields as $spiField) {
255
            // We ignore fields in content not part of the content type
256
            if (!isset($fieldDefinitionsMap[$spiField->fieldDefinitionId])) {
257
                continue;
258
            }
259
260
            $fieldDefinition = $fieldDefinitionsMap[$spiField->fieldDefinitionId];
261
262
            if (!empty($prioritizedLanguages) && !in_array($spiField->languageCode, $prioritizedLanguages)) {
263
                // If filtering is enabled we ignore fields in other languages then $prioritizedLanguages, if:
264
                if ($alwaysAvailableLanguage === null) {
265
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
266
                    continue;
267
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
268
                    // Ignore field if it exists in one of the filtered languages
269
                    continue;
270
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
271
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
272
                    continue;
273
                }
274
            }
275
276
            $fields[$fieldDefinition->position][] = new Field(
277
                array(
278
                    'id' => $spiField->id,
279
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
280
                        ->fromPersistenceValue($spiField->value),
281
                    'languageCode' => $spiField->languageCode,
282
                    'fieldDefIdentifier' => $fieldDefinition->identifier,
283
                    'fieldTypeIdentifier' => $spiField->type,
284
                )
285
            );
286
        }
287
288
        // Sort fields by content type field definition priority
289
        ksort($fields, SORT_NUMERIC);
290
291
        // Flatten array
292
        return array_merge(...$fields);
293
    }
294
295
    /**
296
     * Builds a VersionInfo domain object from value object returned from persistence.
297
     *
298
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo
299
     * @param array $prioritizedLanguages
300
     *
301
     * @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo
302
     */
303
    public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo, array $prioritizedLanguages = [])
304
    {
305
        // Map SPI statuses to API
306
        switch ($spiVersionInfo->status) {
307
            case SPIVersionInfo::STATUS_ARCHIVED:
308
                $status = APIVersionInfo::STATUS_ARCHIVED;
309
                break;
310
311
            case SPIVersionInfo::STATUS_PUBLISHED:
312
                $status = APIVersionInfo::STATUS_PUBLISHED;
313
                break;
314
315
            case SPIVersionInfo::STATUS_DRAFT:
316
            default:
317
                $status = APIVersionInfo::STATUS_DRAFT;
318
        }
319
320
        // Find prioritised language among names
321
        $prioritizedNameLanguageCode = null;
322
        foreach ($prioritizedLanguages as $prioritizedLanguage) {
323
            if (isset($spiVersionInfo->names[$prioritizedLanguage])) {
324
                $prioritizedNameLanguageCode = $prioritizedLanguage;
325
                break;
326
            }
327
        }
328
329
        return new VersionInfo(
330
            array(
331
                'id' => $spiVersionInfo->id,
332
                'versionNo' => $spiVersionInfo->versionNo,
333
                'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate),
334
                'creatorId' => $spiVersionInfo->creatorId,
335
                'creationDate' => $this->getDateTime($spiVersionInfo->creationDate),
336
                'status' => $status,
337
                'initialLanguageCode' => $spiVersionInfo->initialLanguageCode,
338
                'languageCodes' => $spiVersionInfo->languageCodes,
339
                'names' => $spiVersionInfo->names,
340
                'contentInfo' => $this->buildContentInfoDomainObject($spiVersionInfo->contentInfo),
341
                'prioritizedNameLanguageCode' => $prioritizedNameLanguageCode,
342
            )
343
        );
344
    }
345
346
    /**
347
     * Builds a ContentInfo domain object from value object returned from persistence.
348
     *
349
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo
350
     *
351
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
352
     */
353
    public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo)
354
    {
355
        // Map SPI statuses to API
356
        switch ($spiContentInfo->status) {
357
            case SPIContentInfo::STATUS_TRASHED:
358
                $status = ContentInfo::STATUS_TRASHED;
359
                break;
360
361
            case SPIContentInfo::STATUS_PUBLISHED:
362
                $status = ContentInfo::STATUS_PUBLISHED;
363
                break;
364
365
            case SPIContentInfo::STATUS_DRAFT:
366
            default:
367
                $status = ContentInfo::STATUS_DRAFT;
368
        }
369
370
        return new ContentInfo(
371
            array(
372
                'id' => $spiContentInfo->id,
373
                'contentTypeId' => $spiContentInfo->contentTypeId,
374
                'name' => $spiContentInfo->name,
375
                'sectionId' => $spiContentInfo->sectionId,
376
                'currentVersionNo' => $spiContentInfo->currentVersionNo,
377
                'published' => $spiContentInfo->isPublished,
378
                'ownerId' => $spiContentInfo->ownerId,
379
                'modificationDate' => $spiContentInfo->modificationDate == 0 ?
380
                    null :
381
                    $this->getDateTime($spiContentInfo->modificationDate),
382
                'publishedDate' => $spiContentInfo->publicationDate == 0 ?
383
                    null :
384
                    $this->getDateTime($spiContentInfo->publicationDate),
385
                'alwaysAvailable' => $spiContentInfo->alwaysAvailable,
386
                'remoteId' => $spiContentInfo->remoteId,
387
                'mainLanguageCode' => $spiContentInfo->mainLanguageCode,
388
                'mainLocationId' => $spiContentInfo->mainLocationId,
389
                'status' => $status,
390
            )
391
        );
392
    }
393
394
    /**
395
     * Builds API Relation object from provided SPI Relation object.
396
     *
397
     * @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation
398
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo
399
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo
400
     *
401
     * @return \eZ\Publish\API\Repository\Values\Content\Relation
402
     */
403
    public function buildRelationDomainObject(
404
        SPIRelation $spiRelation,
405
        ContentInfo $sourceContentInfo,
406
        ContentInfo $destinationContentInfo
407
    ) {
408
        $sourceFieldDefinitionIdentifier = null;
409
        if ($spiRelation->sourceFieldDefinitionId !== null) {
410
            $contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId);
411
            foreach ($contentType->fieldDefinitions as $fieldDefinition) {
412
                if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) {
413
                    continue;
414
                }
415
416
                $sourceFieldDefinitionIdentifier = $fieldDefinition->identifier;
417
                break;
418
            }
419
        }
420
421
        return new Relation(
422
            array(
423
                'id' => $spiRelation->id,
424
                'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier,
425
                'type' => $spiRelation->type,
426
                'sourceContentInfo' => $sourceContentInfo,
427
                'destinationContentInfo' => $destinationContentInfo,
428
            )
429
        );
430
    }
431
432
    /**
433
     * @deprecated Since 7.2, use buildLocationWithContent(), buildLocation() or (private) mapLocation() instead.
434
     */
435
    public function buildLocationDomainObject(
436
        SPILocation $spiLocation,
437
        SPIContentInfo $contentInfo = null
438
    ) {
439
        if ($contentInfo === null) {
440
            return $this->buildLocation($spiLocation);
441
        }
442
443
        return $this->mapLocation(
444
            $spiLocation,
445
            $this->buildContentInfoDomainObject($contentInfo),
446
            $this->buildContentProxy($contentInfo)
447
        );
448
    }
449
450
    public function buildLocation(
451
        SPILocation $spiLocation,
452
        array $prioritizedLanguages = [],
453
        bool $useAlwaysAvailable = true
454
    ): APILocation {
455
        if ($this->isRootLocation($spiLocation)) {
456
            return $this->buildRootLocation($spiLocation);
457
        }
458
459
        $spiContentInfo = $this->contentHandler->loadContentInfo($spiLocation->contentId);
460
461
        return $this->mapLocation(
462
            $spiLocation,
463
            $this->buildContentInfoDomainObject($spiContentInfo),
464
            $this->buildContentProxy($spiContentInfo, $prioritizedLanguages, $useAlwaysAvailable)
465
        );
466
    }
467
468
    /**
469
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
470
     * @param \eZ\Publish\API\Repository\Values\Content\Content|null $content
471
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo|null $spiContentInfo
472
     *
473
     * @return \eZ\Publish\API\Repository\Values\Content\Location
474
     *
475
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
476
     */
477
    public function buildLocationWithContent(
478
        SPILocation $spiLocation,
479
        ?APIContent $content,
480
        ?SPIContentInfo $spiContentInfo = null
481
    ): APILocation {
482
        if ($this->isRootLocation($spiLocation)) {
483
            return $this->buildRootLocation($spiLocation);
484
        }
485
486
        if ($content === null) {
487
            throw new InvalidArgumentException('$content', "Location {$spiLocation->id} has missing Content");
488
        }
489
490
        if ($spiContentInfo !== null) {
491
            $contentInfo = $this->buildContentInfoDomainObject($spiContentInfo);
492
        } else {
493
            $contentInfo = $content->contentInfo;
494
        }
495
496
        return $this->mapLocation($spiLocation, $contentInfo, $content);
497
    }
498
499
    /**
500
     * Builds API Location object for tree root.
501
     *
502
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
503
     *
504
     * @return \eZ\Publish\API\Repository\Values\Content\Location
505
     */
506
    private function buildRootLocation(SPILocation $spiLocation): APILocation
507
    {
508
        //  first known commit of eZ Publish 3.x
509
        $legacyDateTime = $this->getDateTime(1030968000);
510
511
        // NOTE: this is hardcoded workaround for missing ContentInfo on root location
512
        return $this->mapLocation(
513
            $spiLocation,
514
            new ContentInfo([
515
                'id' => 0,
516
                'name' => 'Top Level Nodes',
517
                'sectionId' => 1,
518
                'mainLocationId' => 1,
519
                'contentTypeId' => 1,
520
                'currentVersionNo' => 1,
521
                'published' => 1,
522
                'ownerId' => 14, // admin user
523
                'modificationDate' => $legacyDateTime,
524
                'publishedDate' => $legacyDateTime,
525
                'alwaysAvailable' => 1,
526
                'remoteId' => null,
527
                'mainLanguageCode' => 'eng-GB',
528
            ]),
529
            new Content([])
530
        );
531
    }
532
533
    private function mapLocation(SPILocation $spiLocation, ContentInfo $contentInfo, APIContent $content): APILocation
534
    {
535
        return new Location(
536
            array(
537
                'content' => $content,
538
                'contentInfo' => $contentInfo,
539
                'id' => $spiLocation->id,
540
                'priority' => $spiLocation->priority,
541
                'hidden' => $spiLocation->hidden,
542
                'invisible' => $spiLocation->invisible,
543
                'remoteId' => $spiLocation->remoteId,
544
                'parentLocationId' => $spiLocation->parentId,
545
                'pathString' => $spiLocation->pathString,
546
                'depth' => $spiLocation->depth,
547
                'sortField' => $spiLocation->sortField,
548
                'sortOrder' => $spiLocation->sortOrder,
549
            )
550
        );
551
    }
552
553
    /**
554
     * Build API Content domain objects in bulk and apply to ContentSearchResult.
555
     *
556
     * Loading of Content objects are done in bulk.
557
     *
558
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI ContentInfo items as hits
559
     * @param array $languageFilter
560
     *
561
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo[] ContentInfo we did not find content for is returned.
562
     */
563
    public function buildContentDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
564
    {
565
        if (empty($result->searchHits)) {
566
            return [];
567
        }
568
569
        $contentIds = [];
570
        $translations = $languageFilter['languages'] ?? [];
571
        $useAlwaysAvailable = $languageFilter['useAlwaysAvailable'] ?? true;
572
        foreach ($result->searchHits as $hit) {
573
            $contentIds[] = $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 __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...
574
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
575
            // Might in some case load more languages then intended, but prioritised handling will pick right one
576
            if (!empty($languageFilter['languages']) && $useAlwaysAvailable && $hit->valueObject->alwaysAvailable) {
0 ignored issues
show
Documentation introduced by
The property alwaysAvailable 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...
577
                $translations[] = $hit->valueObject->mainLanguageCode;
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 __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...
578
            }
579
        }
580
581
        $missingContentList = [];
582
        $contentList = $this->contentHandler->loadContentList($contentIds, array_unique($translations));
583
        foreach ($result->searchHits as $key => $hit) {
584
            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...
585
                $hit->valueObject = $this->buildContentDomainObject(
586
                    $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...
587
                    null,//@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases
588
                    $languageFilter['languages'] ?? [],
589
                    $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...
590
                );
591
            } else {
592
                $missingContentList[] = $hit->valueObject;
593
                unset($result->searchHits[$key]);
594
                --$result->totalCount;
595
            }
596
        }
597
598
        return $missingContentList;
599
    }
600
601
    /**
602
     * Build API Location and corresponding ContentInfo domain objects and apply to LocationSearchResult.
603
     *
604
     * This is done in order to be able to:
605
     * Load ContentInfo objects in bulk, generate proxy objects for Content that will loaded in bulk on-demand (on use).
606
     *
607
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI Location items as hits
608
     * @param array $languageFilter
609
     *
610
     * @return \eZ\Publish\SPI\Persistence\Content\Location[] Locations we did not find content info for is returned.
611
     */
612
    public function buildLocationDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
613
    {
614
        if (empty($result->searchHits)) {
615
            return [];
616
        }
617
618
        $contentIds = [];
619
        foreach ($result->searchHits as $hit) {
620
            $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...
621
        }
622
623
        $missingLocations = [];
624
        $contentInfoList = $this->contentHandler->loadContentInfoList($contentIds);
625
        $contentList = $this->buildContentProxyList(
626
            $contentInfoList,
627
            !empty($languageFilter['languages']) ? $languageFilter['languages'] : []
628
        );
629
        foreach ($result->searchHits as $key => $hit) {
630
            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...
631
                $hit->valueObject = $this->buildLocationWithContent(
632
                    $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...
633
                    $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...
634
                    $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...
635
                );
636
            } else {
637
                $missingLocations[] = $hit->valueObject;
638
                unset($result->searchHits[$key]);
639
                --$result->totalCount;
640
            }
641
        }
642
643
        return $missingLocations;
644
    }
645
646
    /**
647
     * Creates an array of SPI location create structs from given array of API location create structs.
648
     *
649
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
650
     *
651
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
652
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
653
     * @param mixed $mainLocation
654
     * @param mixed $contentId
655
     * @param mixed $contentVersionNo
656
     *
657
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
658
     */
659
    public function buildSPILocationCreateStruct(
660
        $locationCreateStruct,
661
        APILocation $parentLocation,
662
        $mainLocation,
663
        $contentId,
664
        $contentVersionNo
665
    ) {
666
        if (!$this->isValidLocationPriority($locationCreateStruct->priority)) {
667
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
668
        }
669
670
        if (!is_bool($locationCreateStruct->hidden)) {
671
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
672
        }
673
674
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
675
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
676
        }
677
678
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
679
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
680
        }
681
682
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
683
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
684
        }
685
686
        $remoteId = $locationCreateStruct->remoteId;
687
        if (null === $remoteId) {
688
            $remoteId = $this->getUniqueHash($locationCreateStruct);
689
        } else {
690
            try {
691
                $this->locationHandler->loadByRemoteId($remoteId);
692
                throw new InvalidArgumentException(
693
                    '$locationCreateStructs',
694
                    "Another Location with remoteId '{$remoteId}' exists"
695
                );
696
            } catch (NotFoundException $e) {
697
                // Do nothing
698
            }
699
        }
700
701
        return new SPILocationCreateStruct(
702
            array(
703
                'priority' => $locationCreateStruct->priority,
704
                'hidden' => $locationCreateStruct->hidden,
705
                // If we declare the new Location as hidden, it is automatically invisible
706
                // Otherwise it picks up visibility from parent Location
707
                // Note: There is no need to check for hidden status of parent, as hidden Location
708
                // is always invisible as well
709
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
710
                'remoteId' => $remoteId,
711
                'contentId' => $contentId,
712
                'contentVersion' => $contentVersionNo,
713
                // pathIdentificationString will be set in storage
714
                'pathIdentificationString' => null,
715
                'mainLocationId' => $mainLocation,
716
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
717
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
718
                'parentId' => $locationCreateStruct->parentLocationId,
719
            )
720
        );
721
    }
722
723
    /**
724
     * Checks if given $sortField value is one of the defined sort field constants.
725
     *
726
     * @param mixed $sortField
727
     *
728
     * @return bool
729
     */
730
    public function isValidLocationSortField($sortField)
731
    {
732
        switch ($sortField) {
733
            case APILocation::SORT_FIELD_PATH:
734
            case APILocation::SORT_FIELD_PUBLISHED:
735
            case APILocation::SORT_FIELD_MODIFIED:
736
            case APILocation::SORT_FIELD_SECTION:
737
            case APILocation::SORT_FIELD_DEPTH:
738
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
739
            case APILocation::SORT_FIELD_CLASS_NAME:
740
            case APILocation::SORT_FIELD_PRIORITY:
741
            case APILocation::SORT_FIELD_NAME:
742
            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...
743
            case APILocation::SORT_FIELD_NODE_ID:
744
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
745
                return true;
746
        }
747
748
        return false;
749
    }
750
751
    /**
752
     * Checks if given $sortOrder value is one of the defined sort order constants.
753
     *
754
     * @param mixed $sortOrder
755
     *
756
     * @return bool
757
     */
758
    public function isValidLocationSortOrder($sortOrder)
759
    {
760
        switch ($sortOrder) {
761
            case APILocation::SORT_ORDER_DESC:
762
            case APILocation::SORT_ORDER_ASC:
763
                return true;
764
        }
765
766
        return false;
767
    }
768
769
    /**
770
     * Checks if given $priority is valid.
771
     *
772
     * @param int $priority
773
     *
774
     * @return bool
775
     */
776
    public function isValidLocationPriority($priority)
777
    {
778
        if ($priority === null) {
779
            return true;
780
        }
781
782
        return is_int($priority) && $priority >= self::MIN_LOCATION_PRIORITY && $priority <= self::MAX_LOCATION_PRIORITY;
783
    }
784
785
    /**
786
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
787
     *
788
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
789
     *
790
     * @param mixed $list
791
     * @param string $argumentName
792
     */
793
    public function validateTranslatedList($list, $argumentName)
794
    {
795
        if (!is_array($list)) {
796
            throw new InvalidArgumentType($argumentName, 'array', $list);
797
        }
798
799
        foreach ($list as $languageCode => $translation) {
800
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
801
802
            if (!is_string($translation)) {
803
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
804
            }
805
        }
806
    }
807
808
    /**
809
     * Returns \DateTime object from given $timestamp in environment timezone.
810
     *
811
     * This method is needed because constructing \DateTime with $timestamp will
812
     * return the object in UTC timezone.
813
     *
814
     * @param int $timestamp
815
     *
816
     * @return \DateTime
817
     */
818
    public function getDateTime($timestamp)
819
    {
820
        $dateTime = new DateTime();
821
        $dateTime->setTimestamp($timestamp);
822
823
        return $dateTime;
824
    }
825
826
    /**
827
     * Creates unique hash string for given $object.
828
     *
829
     * Used for remoteId.
830
     *
831
     * @param object $object
832
     *
833
     * @return string
834
     */
835
    public function getUniqueHash($object)
836
    {
837
        return md5(uniqid(get_class($object), true));
838
    }
839
840
    /**
841
     * Returns true if given location is a tree root.
842
     *
843
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
844
     *
845
     * @return bool
846
     */
847
    private function isRootLocation(SPILocation $spiLocation): bool
848
    {
849
        return $spiLocation->id === $spiLocation->parentId;
850
    }
851
}
852