Completed
Push — master ( 65f512...fe0390 )
by Łukasz
26:26
created

buildContentDomainObjectsOnSearchResult()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 10
nop 2
dl 0
loc 44
rs 7.9715
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
        // Create list of LoadStruct, take into account main language as fallback language if alwaysAvailable
189
        // And skip setting versionNo to make sure we always get the current version when proxy is eventually loaded
190
        $loadStructList = [];
191
        foreach ($infoList as $info) {
192
            if ($useAlwaysAvailable && $info->alwaysAvailable) {
193
                $languages = $prioritizedLanguages;
194
                $languages[] = $info->mainLanguageCode;
195
                $loadStructList[] = new SPIContent\LoadStruct(['id' => $info->id, 'languages' => $languages]);
196
            } else {
197
                $loadStructList[] = new SPIContent\LoadStruct([
198
                    'id' => $info->id,
199
                    'languages' => $prioritizedLanguages,
200
                ]);
201
            }
202
        }
203
204
        $list = $this->contentHandler->loadContentList($loadStructList);
205
        unset($loadStructList);
206
207
        while (!empty($list)) {
208
            $id = yield;
209
            $info = $list[$id]->versionInfo->contentInfo;
210
            yield $this->buildContentDomainObject(
211
                $list[$id],
212
                null,
213
                //@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases
214
                $prioritizedLanguages,
215
                $info->alwaysAvailable ? $info->mainLanguageCode : null
216
            );
217
218
            unset($list[$id]);
219
        }
220
    }
221
222
    /**
223
     * Returns an array of domain fields created from given array of SPI fields.
224
     *
225
     * @throws InvalidArgumentType On invalid $contentType
226
     *
227
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
228
     * @param ContentType|SPIType $contentType
229
     * @param array $prioritizedLanguages A language priority, filters returned fields and is used as prioritized language code on
230
     *                         returned value object. If not given all languages are returned.
231
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $prioritizedLanguages
232
     *
233
     * @return array
234
     */
235
    public function buildDomainFields(
236
        array $spiFields,
237
        $contentType,
238
        array $prioritizedLanguages = [],
239
        string $alwaysAvailableLanguage = null
240
    ) {
241
        if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) {
242
            throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType');
243
        }
244
245
        $fieldIdentifierMap = array();
246
        foreach ($contentType->fieldDefinitions as $fieldDefinitions) {
247
            $fieldIdentifierMap[$fieldDefinitions->id] = $fieldDefinitions->identifier;
248
        }
249
250
        $fieldInFilterLanguagesMap = array();
251
        if (!empty($prioritizedLanguages) && $alwaysAvailableLanguage !== null) {
252
            foreach ($spiFields as $spiField) {
253
                if (in_array($spiField->languageCode, $prioritizedLanguages)) {
254
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
255
                }
256
            }
257
        }
258
259
        $fields = array();
260
        foreach ($spiFields as $spiField) {
261
            // We ignore fields in content not part of the content type
262
            if (!isset($fieldIdentifierMap[$spiField->fieldDefinitionId])) {
263
                continue;
264
            }
265
266
            if (!empty($prioritizedLanguages) && !in_array($spiField->languageCode, $prioritizedLanguages)) {
267
                // If filtering is enabled we ignore fields in other languages then $prioritizedLanguages, if:
268
                if ($alwaysAvailableLanguage === null) {
269
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
270
                    continue;
271
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
272
                    // Ignore field if it exists in one of the filtered languages
273
                    continue;
274
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
275
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
276
                    continue;
277
                }
278
            }
279
280
            $fields[] = new Field(
281
                array(
282
                    'id' => $spiField->id,
283
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
284
                        ->fromPersistenceValue($spiField->value),
285
                    'languageCode' => $spiField->languageCode,
286
                    'fieldDefIdentifier' => $fieldIdentifierMap[$spiField->fieldDefinitionId],
287
                    'fieldTypeIdentifier' => $spiField->type,
288
                )
289
            );
290
        }
291
292
        return $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,
0 ignored issues
show
Deprecated Code introduced by
The property eZ\Publish\SPI\Persisten...ntentInfo::$isPublished has been deprecated with message: Use SPI\ContentInfo::$status (with value ContentInfo::STATUS_PUBLISHED) Flag indicating if content is currently published.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
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 ($spiLocation->id == 1) {
456
            $legacyDateTime = $this->getDateTime(1030968000); //  first known commit of eZ Publish 3.x
457
            // NOTE: this is hardcoded workaround for missing ContentInfo on root location
458
            return $this->mapLocation(
459
                $spiLocation,
460
                new ContentInfo([
461
                    'id' => 0,
462
                    'name' => 'Top Level Nodes',
463
                    'sectionId' => 1,
464
                    'mainLocationId' => 1,
465
                    'contentTypeId' => 1,
466
                    'currentVersionNo' => 1,
467
                    'published' => 1,
468
                    'ownerId' => 14, // admin user
469
                    'modificationDate' => $legacyDateTime,
470
                    'publishedDate' => $legacyDateTime,
471
                    'alwaysAvailable' => 1,
472
                    'remoteId' => null,
473
                    'mainLanguageCode' => 'eng-GB',
474
                ]),
475
                new Content([])
476
            );
477
        }
478
479
        $spiContentInfo = $this->contentHandler->loadContentInfo($spiLocation->contentId);
480
481
        return $this->mapLocation(
482
            $spiLocation,
483
            $this->buildContentInfoDomainObject($spiContentInfo),
484
            $this->buildContentProxy($spiContentInfo, $prioritizedLanguages, $useAlwaysAvailable)
485
        );
486
    }
487
488
    public function buildLocationWithContent(
489
        SPILocation $spiLocation,
490
        APIContent $content,
491
        SPIContentInfo $spiContentInfo = null
492
    ): APILocation {
493
        if ($spiContentInfo !== null) {
494
            $contentInfo = $this->buildContentInfoDomainObject($spiContentInfo);
495
        } else {
496
            $contentInfo = $content->contentInfo;
497
        }
498
499
        return $this->mapLocation($spiLocation, $contentInfo, $content);
500
    }
501
502 View Code Duplication
    private function mapLocation(SPILocation $spiLocation, ContentInfo $contentInfo, APIContent $content): APILocation
503
    {
504
        return new Location(
505
            array(
506
                'content' => $content,
507
                'contentInfo' => $contentInfo,
508
                'id' => $spiLocation->id,
509
                'priority' => $spiLocation->priority,
510
                'hidden' => $spiLocation->hidden,
511
                'invisible' => $spiLocation->invisible,
512
                'remoteId' => $spiLocation->remoteId,
513
                'parentLocationId' => $spiLocation->parentId,
514
                'pathString' => $spiLocation->pathString,
515
                'depth' => $spiLocation->depth,
516
                'sortField' => $spiLocation->sortField,
517
                'sortOrder' => $spiLocation->sortOrder,
518
            )
519
        );
520
    }
521
522
    /**
523
     * Build API Content domain objects in bulk and apply to ContentSearchResult.
524
     *
525
     * Loading of Content objects are done in bulk.
526
     *
527
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI ContentInfo items as hits
528
     * @param array $languageFilter
529
     *
530
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo[] ContentInfo we did not find content for is returned.
531
     */
532
    public function buildContentDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
533
    {
534
        if (empty($result->searchHits)) {
535
            return [];
536
        }
537
538
        $loadStructList = [];
539
        $prioritizedLanguages = $languageFilter['languages'] ?? [];
540
        $useAlwaysAvailable = $languageFilter['useAlwaysAvailable'] ?? true;
541
        foreach ($result->searchHits as $hit) {
542
            if ($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...
543
                $languages = $prioritizedLanguages;
544
                $languages[] = $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...
545
                $loadStructList[] = new SPIContent\LoadStruct([
546
                    'id' => $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...
547
                    'languages' => $languages,
548
                ]);
549
            } else {
550
                $loadStructList[] = new SPIContent\LoadStruct([
551
                    'id' => $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...
552
                    'languages' => $prioritizedLanguages,
553
                ]);
554
            }
555
        }
556
557
        $missingContentList = [];
558
        $contentList = $this->contentHandler->loadContentList($loadStructList);
559
        foreach ($result->searchHits as $key => $hit) {
560
            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...
561
                $hit->valueObject = $this->buildContentDomainObject(
562
                    $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...
563
                    null,//@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases
564
                    $languageFilter['languages'] ?? [],
565
                    $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...
566
                );
567
            } else {
568
                $missingContentList[] = $hit->valueObject;
569
                unset($result->searchHits[$key]);
570
                --$result->totalCount;
571
            }
572
        }
573
574
        return $missingContentList;
575
    }
576
577
    /**
578
     * Build API Location and corresponding ContentInfo domain objects and apply to LocationSearchResult.
579
     *
580
     * This is done in order to be able to:
581
     * Load ContentInfo objects in bulk, generate proxy objects for Content that will loaded in bulk on-demand (on use).
582
     *
583
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI Location items as hits
584
     * @param array $languageFilter
585
     *
586
     * @return \eZ\Publish\SPI\Persistence\Content\Location[] Locations we did not find content info for is returned.
587
     */
588
    public function buildLocationDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
589
    {
590
        if (empty($result->searchHits)) {
591
            return [];
592
        }
593
594
        $contentIds = [];
595
        foreach ($result->searchHits as $hit) {
596
            $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...
597
        }
598
599
        $missingLocations = [];
600
        $contentInfoList = $this->contentHandler->loadContentInfoList($contentIds);
601
        $contentList = $this->buildContentProxyList(
602
            $contentInfoList,
603
            !empty($languageFilter['languages']) ? $languageFilter['languages'] : []
604
        );
605
        foreach ($result->searchHits as $key => $hit) {
606
            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...
607
                $hit->valueObject = $this->buildLocationWithContent(
608
                    $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...
609
                    $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...
610
                    $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...
611
                );
612
            } else {
613
                $missingLocations[] = $hit->valueObject;
614
                unset($result->searchHits[$key]);
615
                --$result->totalCount;
616
            }
617
        }
618
619
        return $missingLocations;
620
    }
621
622
    /**
623
     * Creates an array of SPI location create structs from given array of API location create structs.
624
     *
625
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
626
     *
627
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
628
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
629
     * @param mixed $mainLocation
630
     * @param mixed $contentId
631
     * @param mixed $contentVersionNo
632
     *
633
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
634
     */
635
    public function buildSPILocationCreateStruct(
636
        $locationCreateStruct,
637
        APILocation $parentLocation,
638
        $mainLocation,
639
        $contentId,
640
        $contentVersionNo
641
    ) {
642
        if (!$this->isValidLocationPriority($locationCreateStruct->priority)) {
643
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
644
        }
645
646
        if (!is_bool($locationCreateStruct->hidden)) {
647
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
648
        }
649
650
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
651
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
652
        }
653
654
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
655
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
656
        }
657
658
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
659
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
660
        }
661
662
        $remoteId = $locationCreateStruct->remoteId;
663
        if (null === $remoteId) {
664
            $remoteId = $this->getUniqueHash($locationCreateStruct);
665
        } else {
666
            try {
667
                $this->locationHandler->loadByRemoteId($remoteId);
668
                throw new InvalidArgumentException(
669
                    '$locationCreateStructs',
670
                    "Another Location with remoteId '{$remoteId}' exists"
671
                );
672
            } catch (NotFoundException $e) {
673
                // Do nothing
674
            }
675
        }
676
677
        return new SPILocationCreateStruct(
678
            array(
679
                'priority' => $locationCreateStruct->priority,
680
                'hidden' => $locationCreateStruct->hidden,
681
                // If we declare the new Location as hidden, it is automatically invisible
682
                // Otherwise it picks up visibility from parent Location
683
                // Note: There is no need to check for hidden status of parent, as hidden Location
684
                // is always invisible as well
685
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
686
                'remoteId' => $remoteId,
687
                'contentId' => $contentId,
688
                'contentVersion' => $contentVersionNo,
689
                // pathIdentificationString will be set in storage
690
                'pathIdentificationString' => null,
691
                'mainLocationId' => $mainLocation,
692
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
693
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
694
                'parentId' => $locationCreateStruct->parentLocationId,
695
            )
696
        );
697
    }
698
699
    /**
700
     * Checks if given $sortField value is one of the defined sort field constants.
701
     *
702
     * @param mixed $sortField
703
     *
704
     * @return bool
705
     */
706
    public function isValidLocationSortField($sortField)
707
    {
708
        switch ($sortField) {
709
            case APILocation::SORT_FIELD_PATH:
710
            case APILocation::SORT_FIELD_PUBLISHED:
711
            case APILocation::SORT_FIELD_MODIFIED:
712
            case APILocation::SORT_FIELD_SECTION:
713
            case APILocation::SORT_FIELD_DEPTH:
714
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
715
            case APILocation::SORT_FIELD_CLASS_NAME:
716
            case APILocation::SORT_FIELD_PRIORITY:
717
            case APILocation::SORT_FIELD_NAME:
718
            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...
719
            case APILocation::SORT_FIELD_NODE_ID:
720
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
721
                return true;
722
        }
723
724
        return false;
725
    }
726
727
    /**
728
     * Checks if given $sortOrder value is one of the defined sort order constants.
729
     *
730
     * @param mixed $sortOrder
731
     *
732
     * @return bool
733
     */
734
    public function isValidLocationSortOrder($sortOrder)
735
    {
736
        switch ($sortOrder) {
737
            case APILocation::SORT_ORDER_DESC:
738
            case APILocation::SORT_ORDER_ASC:
739
                return true;
740
        }
741
742
        return false;
743
    }
744
745
    /**
746
     * Checks if given $priority is valid.
747
     *
748
     * @param int $priority
749
     *
750
     * @return bool
751
     */
752
    public function isValidLocationPriority($priority)
753
    {
754
        if ($priority === null) {
755
            return true;
756
        }
757
758
        return is_int($priority) && $priority >= self::MIN_LOCATION_PRIORITY && $priority <= self::MAX_LOCATION_PRIORITY;
759
    }
760
761
    /**
762
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
763
     *
764
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
765
     *
766
     * @param mixed $list
767
     * @param string $argumentName
768
     */
769
    public function validateTranslatedList($list, $argumentName)
770
    {
771
        if (!is_array($list)) {
772
            throw new InvalidArgumentType($argumentName, 'array', $list);
773
        }
774
775
        foreach ($list as $languageCode => $translation) {
776
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
777
778
            if (!is_string($translation)) {
779
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
780
            }
781
        }
782
    }
783
784
    /**
785
     * Returns \DateTime object from given $timestamp in environment timezone.
786
     *
787
     * This method is needed because constructing \DateTime with $timestamp will
788
     * return the object in UTC timezone.
789
     *
790
     * @param int $timestamp
791
     *
792
     * @return \DateTime
793
     */
794
    public function getDateTime($timestamp)
795
    {
796
        $dateTime = new DateTime();
797
        $dateTime->setTimestamp($timestamp);
798
799
        return $dateTime;
800
    }
801
802
    /**
803
     * Creates unique hash string for given $object.
804
     *
805
     * Used for remoteId.
806
     *
807
     * @param object $object
808
     *
809
     * @return string
810
     */
811
    public function getUniqueHash($object)
812
    {
813
        return md5(uniqid(get_class($object), true));
814
    }
815
}
816