Completed
Push — 7.5 ( 17c267...9e0292 )
by Łukasz
47:52 queued 28:25
created

ContentService::loadReverseRelationList()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
c 0
b 0
f 0
cc 5
nc 3
nop 3
rs 9.0168
1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\ContentService 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;
10
11
use eZ\Publish\API\Repository\ContentService as ContentServiceInterface;
12
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
13
use eZ\Publish\API\Repository\Values\Content\ContentDraftList;
14
use eZ\Publish\API\Repository\Values\Content\DraftList\Item\ContentDraftListItem;
15
use eZ\Publish\API\Repository\Values\Content\DraftList\Item\UnauthorizedContentDraftListItem;
16
use eZ\Publish\API\Repository\Values\Content\RelationList;
17
use eZ\Publish\API\Repository\Values\Content\RelationList\Item\RelationListItem;
18
use eZ\Publish\API\Repository\Values\Content\RelationList\Item\UnauthorizedRelationListItem;
19
use eZ\Publish\API\Repository\Values\User\UserReference;
20
use eZ\Publish\Core\Repository\Values\Content\Location;
21
use eZ\Publish\API\Repository\Values\Content\Language;
22
use eZ\Publish\SPI\Persistence\Handler;
23
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct as APIContentUpdateStruct;
24
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
25
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
26
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
27
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
28
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
29
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
30
use eZ\Publish\API\Repository\Values\User\User;
31
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
32
use eZ\Publish\API\Repository\Values\Content\Field;
33
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
34
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
35
use eZ\Publish\Core\Base\Exceptions\BadStateException;
36
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
37
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
38
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
39
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
40
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
41
use eZ\Publish\Core\FieldType\ValidationError;
42
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
43
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
44
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
45
use eZ\Publish\SPI\Limitation\Target;
46
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
47
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
48
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
49
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
50
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
51
use Exception;
52
53
/**
54
 * This class provides service methods for managing content.
55
 *
56
 * @example Examples/content.php
57
 */
58
class ContentService implements ContentServiceInterface
59
{
60
    /** @var \eZ\Publish\Core\Repository\Repository */
61
    protected $repository;
62
63
    /** @var \eZ\Publish\SPI\Persistence\Handler */
64
    protected $persistenceHandler;
65
66
    /** @var array */
67
    protected $settings;
68
69
    /** @var \eZ\Publish\Core\Repository\Helper\DomainMapper */
70
    protected $domainMapper;
71
72
    /** @var \eZ\Publish\Core\Repository\Helper\RelationProcessor */
73
    protected $relationProcessor;
74
75
    /** @var \eZ\Publish\Core\Repository\Helper\NameSchemaService */
76
    protected $nameSchemaService;
77
78
    /** @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry */
79
    protected $fieldTypeRegistry;
80
81
    /**
82
     * Setups service with reference to repository object that created it & corresponding handler.
83
     *
84
     * @param \eZ\Publish\API\Repository\Repository $repository
85
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
86
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
87
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
88
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
89
     * @param \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry $fieldTypeRegistry,
0 ignored issues
show
Documentation introduced by
There is no parameter named $fieldTypeRegistry,. Did you maybe mean $fieldTypeRegistry?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
90
     * @param array $settings
91
     */
92
    public function __construct(
93
        RepositoryInterface $repository,
94
        Handler $handler,
95
        Helper\DomainMapper $domainMapper,
96
        Helper\RelationProcessor $relationProcessor,
97
        Helper\NameSchemaService $nameSchemaService,
98
        Helper\FieldTypeRegistry $fieldTypeRegistry,
99
        array $settings = []
100
    ) {
101
        $this->repository = $repository;
0 ignored issues
show
Documentation Bug introduced by
$repository is of type object<eZ\Publish\API\Repository\Repository>, but the property $repository was declared to be of type object<eZ\Publish\Core\Repository\Repository>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
102
        $this->persistenceHandler = $handler;
103
        $this->domainMapper = $domainMapper;
104
        $this->relationProcessor = $relationProcessor;
105
        $this->nameSchemaService = $nameSchemaService;
106
        $this->fieldTypeRegistry = $fieldTypeRegistry;
107
        // Union makes sure default settings are ignored if provided in argument
108
        $this->settings = $settings + [
109
            // Version archive limit (0-50), only enforced on publish, not on un-publish.
110
            'default_version_archive_limit' => 5,
111
        ];
112
    }
113
114
    /**
115
     * Loads a content info object.
116
     *
117
     * To load fields use loadContent
118
     *
119
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
120
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
121
     *
122
     * @param int $contentId
123
     *
124
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
125
     */
126
    public function loadContentInfo($contentId)
127
    {
128
        $contentInfo = $this->internalLoadContentInfo($contentId);
129
        if (!$this->repository->canUser('content', 'read', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
130
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
131
        }
132
133
        return $contentInfo;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function loadContentInfoList(array $contentIds): iterable
140
    {
141
        $contentInfoList = [];
142
        $spiInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList($contentIds);
143
        foreach ($spiInfoList as $id => $spiInfo) {
144
            $contentInfo = $this->domainMapper->buildContentInfoDomainObject($spiInfo);
145
            if ($this->repository->canUser('content', 'read', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
146
                $contentInfoList[$id] = $contentInfo;
147
            }
148
        }
149
150
        return $contentInfoList;
151
    }
152
153
    /**
154
     * Loads a content info object.
155
     *
156
     * To load fields use loadContent
157
     *
158
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
159
     *
160
     * @param mixed $id
161
     * @param bool $isRemoteId
162
     *
163
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
164
     */
165
    public function internalLoadContentInfo($id, $isRemoteId = false)
166
    {
167
        try {
168
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
169
170
            return $this->domainMapper->buildContentInfoDomainObject(
171
                $this->persistenceHandler->contentHandler()->$method($id)
172
            );
173
        } catch (APINotFoundException $e) {
174
            throw new NotFoundException(
175
                'Content',
176
                $id,
177
                $e
178
            );
179
        }
180
    }
181
182
    /**
183
     * Loads a content info object for the given remoteId.
184
     *
185
     * To load fields use loadContent
186
     *
187
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
188
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
189
     *
190
     * @param string $remoteId
191
     *
192
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
193
     */
194
    public function loadContentInfoByRemoteId($remoteId)
195
    {
196
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
197
198
        if (!$this->repository->canUser('content', 'read', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
199
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
200
        }
201
202
        return $contentInfo;
203
    }
204
205
    /**
206
     * Loads a version info of the given content object.
207
     *
208
     * If no version number is given, the method returns the current version
209
     *
210
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
211
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
212
     *
213
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
214
     * @param int $versionNo the version number. If not given the current version is returned.
215
     *
216
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
217
     */
218
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
219
    {
220
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
221
    }
222
223
    /**
224
     * Loads a version info of the given content object id.
225
     *
226
     * If no version number is given, the method returns the current version
227
     *
228
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
229
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
230
     *
231
     * @param mixed $contentId
232
     * @param int $versionNo the version number. If not given the current version is returned.
233
     *
234
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
235
     */
236
    public function loadVersionInfoById($contentId, $versionNo = null)
237
    {
238
        try {
239
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
240
                $contentId,
241
                $versionNo
242
            );
243
        } catch (APINotFoundException $e) {
244
            throw new NotFoundException(
245
                'VersionInfo',
246
                [
247
                    'contentId' => $contentId,
248
                    'versionNo' => $versionNo,
249
                ],
250
                $e
251
            );
252
        }
253
254
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
255
256
        if ($versionInfo->isPublished()) {
257
            $function = 'read';
258
        } else {
259
            $function = 'versionread';
260
        }
261
262
        if (!$this->repository->canUser('content', $function, $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
263
            throw new UnauthorizedException('content', $function, ['contentId' => $contentId]);
264
        }
265
266
        return $versionInfo;
267
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
273
    {
274
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
275
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
276
            $useAlwaysAvailable = false;
277
        }
278
279
        return $this->loadContent(
280
            $contentInfo->id,
281
            $languages,
282
            $versionNo,// On purpose pass as-is and not use $contentInfo, to make sure to return actual current version on null
283
            $useAlwaysAvailable
284
        );
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
291
    {
292
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
293
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
294
            $useAlwaysAvailable = false;
295
        }
296
297
        return $this->loadContent(
298
            $versionInfo->getContentInfo()->id,
299
            $languages,
300
            $versionInfo->versionNo,
301
            $useAlwaysAvailable
302
        );
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
309
    {
310
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
311
312
        if (!$this->repository->canUser('content', 'read', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
313
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
314
        }
315
        if (
316
            !$content->getVersionInfo()->isPublished()
317
            && !$this->repository->canUser('content', 'versionread', $content)
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
318
        ) {
319
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId, 'versionNo' => $versionNo]);
320
        }
321
322
        return $content;
323
    }
324
325
    /**
326
     * Loads content in a version of the given content object.
327
     *
328
     * If no version number is given, the method returns the current version
329
     *
330
     * @internal
331
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
332
     *
333
     * @param mixed $id
334
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
335
     *                         returned value object. If not given all languages are returned.
336
     * @param int|null $versionNo the version number. If not given the current version is returned
337
     * @param bool $isRemoteId
338
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
339
     *
340
     * @return \eZ\Publish\API\Repository\Values\Content\Content
341
     */
342
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
343
    {
344
        try {
345
            // Get Content ID if lookup by remote ID
346
            if ($isRemoteId) {
347
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
348
                $id = $spiContentInfo->id;
349
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
350
                $isRemoteId = false;
351
            }
352
353
            $loadLanguages = $languages;
354
            $alwaysAvailableLanguageCode = null;
355
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
356
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
357
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
358
                if (!isset($spiContentInfo)) {
359
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
360
                }
361
362
                if ($spiContentInfo->alwaysAvailable) {
363
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
364
                    $loadLanguages = array_unique($loadLanguages);
365
                }
366
            }
367
368
            $spiContent = $this->persistenceHandler->contentHandler()->load(
369
                $id,
370
                $versionNo,
371
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 353 can also be of type array; however, eZ\Publish\SPI\Persistence\Content\Handler::load() does only seem to accept null|array<integer,string>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
372
            );
373
        } catch (APINotFoundException $e) {
374
            throw new NotFoundException(
375
                'Content',
376
                [
377
                    $isRemoteId ? 'remoteId' : 'id' => $id,
378
                    'languages' => $languages,
379
                    'versionNo' => $versionNo,
380
                ],
381
                $e
382
            );
383
        }
384
385
        return $this->domainMapper->buildContentDomainObject(
386
            $spiContent,
387
            $this->repository->getContentTypeService()->loadContentType(
388
                $spiContent->versionInfo->contentInfo->contentTypeId
389
            ),
390
            $languages ?? [],
391
            $alwaysAvailableLanguageCode
392
        );
393
    }
394
395
    /**
396
     * Loads content in a version for the content object reference by the given remote id.
397
     *
398
     * If no version is given, the method returns the current version
399
     *
400
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
401
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the user has no access to read content and in case of un-published content: read versions
402
     *
403
     * @param string $remoteId
404
     * @param array $languages A language filter for fields. If not given all languages are returned
405
     * @param int $versionNo the version number. If not given the current version is returned
406
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
407
     *
408
     * @return \eZ\Publish\API\Repository\Values\Content\Content
409
     */
410
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
411
    {
412
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
413
414
        if (!$this->repository->canUser('content', 'read', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
415
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
416
        }
417
418
        if (
419
            !$content->getVersionInfo()->isPublished()
420
            && !$this->repository->canUser('content', 'versionread', $content)
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
421
        ) {
422
            throw new UnauthorizedException('content', 'versionread', ['remoteId' => $remoteId, 'versionNo' => $versionNo]);
423
        }
424
425
        return $content;
426
    }
427
428
    /**
429
     * Bulk-load Content items by the list of ContentInfo Value Objects.
430
     *
431
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
432
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
433
     * allowed to access every Content on the list.
434
     *
435
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
436
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
437
     *                            returned value object. If not given all languages are returned.
438
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
439
     *                                 unless all languages have been asked for.
440
     *
441
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
442
     */
443
    public function loadContentListByContentInfo(
444
        array $contentInfoList,
445
        array $languages = [],
446
        $useAlwaysAvailable = true
447
    ) {
448
        $loadAllLanguages = $languages === Language::ALL;
449
        $contentIds = [];
450
        $contentTypeIds = [];
451
        $translations = $languages;
452
        foreach ($contentInfoList as $contentInfo) {
453
            $contentIds[] = $contentInfo->id;
454
            $contentTypeIds[] = $contentInfo->contentTypeId;
455
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
456
            // Might in some case load more languages then intended, but prioritised handling will pick right one
457
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
458
                $translations[] = $contentInfo->mainLanguageCode;
459
            }
460
        }
461
462
        $contentList = [];
463
        $translations = array_unique($translations);
464
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
465
            $contentIds,
466
            $translations
467
        );
468
        $contentTypeList = $this->repository->getContentTypeService()->loadContentTypeList(
469
            array_unique($contentTypeIds),
470
            $languages
471
        );
472
        foreach ($spiContentList as $contentId => $spiContent) {
473
            $contentInfo = $spiContent->versionInfo->contentInfo;
474
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
475
                $spiContent,
476
                $contentTypeList[$contentInfo->contentTypeId],
477
                $languages,
478
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
479
            );
480
        }
481
482
        return $contentList;
483
    }
484
485
    /**
486
     * Creates a new content draft assigned to the authenticated user.
487
     *
488
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
489
     * but this required special rights for the authenticated user
490
     * (this is useful for content staging where the transfer process does not
491
     * have to authenticate with the user which created the content object in the source server).
492
     * The user has to publish the draft if it should be visible.
493
     * In 4.x at least one location has to be provided in the location creation array.
494
     *
495
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
496
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
497
     *                                                                        struct are missing or invalid, or if multiple locations are under the
498
     *                                                                        same parent.
499
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
500
     *                                                                               or if a required field is missing / set to an empty value.
501
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
502
     *                                                                          or value is set for non-translatable field in language
503
     *                                                                          other than main.
504
     *
505
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
506
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
507
     *
508
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
509
     */
510
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = [])
511
    {
512
        if ($contentCreateStruct->mainLanguageCode === null) {
513
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
514
        }
515
516
        if ($contentCreateStruct->contentType === null) {
517
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
518
        }
519
520
        $contentCreateStruct = clone $contentCreateStruct;
521
522
        if ($contentCreateStruct->ownerId === null) {
523
            $contentCreateStruct->ownerId = $this->repository->getCurrentUserReference()->getUserId();
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Reposito...tCurrentUserReference() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::getCurrentUserReference() instead. Get current user reference.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
524
        }
525
526
        if ($contentCreateStruct->alwaysAvailable === null) {
527
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
528
        }
529
530
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
531
            $contentCreateStruct->contentType->id
532
        );
533
534
        if (empty($contentCreateStruct->sectionId)) {
535
            if (isset($locationCreateStructs[0])) {
536
                $location = $this->repository->getLocationService()->loadLocation(
537
                    $locationCreateStructs[0]->parentLocationId
538
                );
539
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
540
            } else {
541
                $contentCreateStruct->sectionId = 1;
542
            }
543
        }
544
545
        if (!$this->repository->canUser('content', 'create', $contentCreateStruct, $locationCreateStructs)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
546
            throw new UnauthorizedException(
547
                'content',
548
                'create',
549
                [
550
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
551
                            $locationCreateStructs[0]->parentLocationId :
552
                            null,
553
                    'sectionId' => $contentCreateStruct->sectionId,
554
                ]
555
            );
556
        }
557
558
        if (!empty($contentCreateStruct->remoteId)) {
559
            try {
560
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
561
562
                throw new InvalidArgumentException(
563
                    '$contentCreateStruct',
564
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
565
                );
566
            } catch (APINotFoundException $e) {
567
                // Do nothing
568
            }
569
        } else {
570
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
571
        }
572
573
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
574
575
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
576
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
577
578
        $fieldValues = [];
579
        $spiFields = [];
580
        $allFieldErrors = [];
581
        $inputRelations = [];
582
        $locationIdToContentIdMapping = [];
583
584
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
585
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
586
            $fieldType = $this->fieldTypeRegistry->getFieldType(
587
                $fieldDefinition->fieldTypeIdentifier
588
            );
589
590
            foreach ($languageCodes as $languageCode) {
591
                $isEmptyValue = false;
592
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
593
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
594
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
595
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
596
                } else {
597
                    $fieldValue = $fieldDefinition->defaultValue;
598
                }
599
600
                $fieldValue = $fieldType->acceptValue($fieldValue);
601
602
                if ($fieldType->isEmptyValue($fieldValue)) {
603
                    $isEmptyValue = true;
604
                    if ($fieldDefinition->isRequired) {
605
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
606
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
607
                            null,
608
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
609
                            'empty'
610
                        );
611
                    }
612
                } else {
613
                    $fieldErrors = $fieldType->validate(
614
                        $fieldDefinition,
615
                        $fieldValue
616
                    );
617
                    if (!empty($fieldErrors)) {
618
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
619
                    }
620
                }
621
622
                if (!empty($allFieldErrors)) {
623
                    continue;
624
                }
625
626
                $this->relationProcessor->appendFieldRelations(
627
                    $inputRelations,
628
                    $locationIdToContentIdMapping,
629
                    $fieldType,
630
                    $fieldValue,
631
                    $fieldDefinition->id
632
                );
633
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
634
635
                // Only non-empty value for: translatable field or in main language
636
                if (
637
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
638
                    (!$isEmptyValue && $isLanguageMain)
639
                ) {
640
                    $spiFields[] = new SPIField(
641
                        [
642
                            'id' => null,
643
                            'fieldDefinitionId' => $fieldDefinition->id,
644
                            'type' => $fieldDefinition->fieldTypeIdentifier,
645
                            'value' => $fieldType->toPersistenceValue($fieldValue),
646
                            'languageCode' => $languageCode,
647
                            'versionNo' => null,
648
                        ]
649
                    );
650
                }
651
            }
652
        }
653
654
        if (!empty($allFieldErrors)) {
655
            throw new ContentFieldValidationException($allFieldErrors);
656
        }
657
658
        $spiContentCreateStruct = new SPIContentCreateStruct(
659
            [
660
                'name' => $this->nameSchemaService->resolve(
661
                    $contentCreateStruct->contentType->nameSchema,
662
                    $contentCreateStruct->contentType,
663
                    $fieldValues,
664
                    $languageCodes
665
                ),
666
                'typeId' => $contentCreateStruct->contentType->id,
667
                'sectionId' => $contentCreateStruct->sectionId,
668
                'ownerId' => $contentCreateStruct->ownerId,
669
                'locations' => $spiLocationCreateStructs,
670
                'fields' => $spiFields,
671
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
672
                'remoteId' => $contentCreateStruct->remoteId,
673
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
674
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
675
                    $contentCreateStruct->mainLanguageCode
676
                )->id,
677
            ]
678
        );
679
680
        $defaultObjectStates = $this->getDefaultObjectStates();
681
682
        $this->repository->beginTransaction();
683
        try {
684
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
685
            $this->relationProcessor->processFieldRelations(
686
                $inputRelations,
687
                $spiContent->versionInfo->contentInfo->id,
688
                $spiContent->versionInfo->versionNo,
689
                $contentCreateStruct->contentType
690
            );
691
692
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
693
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
694
                $objectStateHandler->setContentState(
695
                    $spiContent->versionInfo->contentInfo->id,
696
                    $objectStateGroupId,
697
                    $objectState->id
698
                );
699
            }
700
701
            $this->repository->commit();
702
        } catch (Exception $e) {
703
            $this->repository->rollback();
704
            throw $e;
705
        }
706
707
        return $this->domainMapper->buildContentDomainObject(
708
            $spiContent,
709
            $contentCreateStruct->contentType
710
        );
711
    }
712
713
    /**
714
     * Returns an array of default content states with content state group id as key.
715
     *
716
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
717
     */
718
    protected function getDefaultObjectStates()
719
    {
720
        $defaultObjectStatesMap = [];
721
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
722
723
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
724
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
725
                // Only register the first object state which is the default one.
726
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
727
                break;
728
            }
729
        }
730
731
        return $defaultObjectStatesMap;
732
    }
733
734
    /**
735
     * Returns all language codes used in given $fields.
736
     *
737
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
738
     *
739
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
740
     *
741
     * @return string[]
742
     */
743
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
744
    {
745
        $languageCodes = [];
746
747
        foreach ($contentCreateStruct->fields as $field) {
748
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
749
                continue;
750
            }
751
752
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
753
                $field->languageCode
754
            );
755
            $languageCodes[$field->languageCode] = true;
756
        }
757
758
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
759
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
760
                $contentCreateStruct->mainLanguageCode
761
            );
762
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
763
        }
764
765
        return array_keys($languageCodes);
766
    }
767
768
    /**
769
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
770
     *
771
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
772
     *                                                                          or value is set for non-translatable field in language
773
     *                                                                          other than main
774
     *
775
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
776
     *
777
     * @return array
778
     */
779
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
780
    {
781
        $fields = [];
782
783
        foreach ($contentCreateStruct->fields as $field) {
784
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
785
786
            if ($fieldDefinition === null) {
787
                throw new ContentValidationException(
788
                    "Field definition '%identifier%' does not exist in given ContentType",
789
                    ['%identifier%' => $field->fieldDefIdentifier]
790
                );
791
            }
792
793
            if ($field->languageCode === null) {
794
                $field = $this->cloneField(
795
                    $field,
796
                    ['languageCode' => $contentCreateStruct->mainLanguageCode]
797
                );
798
            }
799
800
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $contentCreateStruct->mainLanguageCode)) {
801
                throw new ContentValidationException(
802
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
803
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
804
                );
805
            }
806
807
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
808
        }
809
810
        return $fields;
811
    }
812
813
    /**
814
     * Clones $field with overriding specific properties from given $overrides array.
815
     *
816
     * @param Field $field
817
     * @param array $overrides
818
     *
819
     * @return Field
820
     */
821
    private function cloneField(Field $field, array $overrides = [])
822
    {
823
        $fieldData = array_merge(
824
            [
825
                'id' => $field->id,
826
                'value' => $field->value,
827
                'languageCode' => $field->languageCode,
828
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
829
                'fieldTypeIdentifier' => $field->fieldTypeIdentifier,
830
            ],
831
            $overrides
832
        );
833
834
        return new Field($fieldData);
835
    }
836
837
    /**
838
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
839
     *
840
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
841
     *
842
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
843
     */
844
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
845
    {
846
        $spiLocationCreateStructs = [];
847
        $parentLocationIdSet = [];
848
        $mainLocation = true;
849
850
        foreach ($locationCreateStructs as $locationCreateStruct) {
851
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
852
                throw new InvalidArgumentException(
853
                    '$locationCreateStructs',
854
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
855
                );
856
            }
857
858
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
859
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
860
            }
861
862
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
863
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
864
            }
865
866
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
867
            $parentLocation = $this->repository->getLocationService()->loadLocation(
868
                $locationCreateStruct->parentLocationId
869
            );
870
871
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
872
                $locationCreateStruct,
873
                $parentLocation,
874
                $mainLocation,
875
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
876
                null,
877
                null
878
            );
879
880
            // First Location in the list will be created as main Location
881
            $mainLocation = false;
882
        }
883
884
        return $spiLocationCreateStructs;
885
    }
886
887
    /**
888
     * Updates the metadata.
889
     *
890
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
891
     *
892
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
893
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
894
     *
895
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
896
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
897
     *
898
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
899
     */
900
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
901
    {
902
        $propertyCount = 0;
903
        foreach ($contentMetadataUpdateStruct as $propertyName => $propertyValue) {
0 ignored issues
show
Bug introduced by
The expression $contentMetadataUpdateStruct of type object<eZ\Publish\API\Re...ntMetadataUpdateStruct> is not traversable.
Loading history...
904
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
905
                $propertyCount += 1;
906
            }
907
        }
908
        if ($propertyCount === 0) {
909
            throw new InvalidArgumentException(
910
                '$contentMetadataUpdateStruct',
911
                'At least one property must be set'
912
            );
913
        }
914
915
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
916
917
        if (!$this->repository->canUser('content', 'edit', $loadedContentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
918
            throw new UnauthorizedException('content', 'edit', ['contentId' => $loadedContentInfo->id]);
919
        }
920
921
        if (isset($contentMetadataUpdateStruct->remoteId)) {
922
            try {
923
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
924
925
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
926
                    throw new InvalidArgumentException(
927
                        '$contentMetadataUpdateStruct',
928
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
929
                    );
930
                }
931
            } catch (APINotFoundException $e) {
932
                // Do nothing
933
            }
934
        }
935
936
        $this->repository->beginTransaction();
937
        try {
938
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
939
                $this->persistenceHandler->contentHandler()->updateMetadata(
940
                    $loadedContentInfo->id,
941
                    new SPIMetadataUpdateStruct(
942
                        [
943
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
944
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
945
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
946
                                null,
947
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
948
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
949
                                null,
950
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
951
                                $this->repository->getContentLanguageService()->loadLanguage(
952
                                    $contentMetadataUpdateStruct->mainLanguageCode
953
                                )->id :
954
                                null,
955
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
956
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
957
                            'name' => $contentMetadataUpdateStruct->name,
958
                        ]
959
                    )
960
                );
961
            }
962
963
            // Change main location
964
            if (isset($contentMetadataUpdateStruct->mainLocationId)
965
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
966
                $this->persistenceHandler->locationHandler()->changeMainLocation(
967
                    $loadedContentInfo->id,
968
                    $contentMetadataUpdateStruct->mainLocationId
969
                );
970
            }
971
972
            // Republish URL aliases to update always-available flag
973
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
974
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
975
                $content = $this->loadContent($loadedContentInfo->id);
976
                $this->publishUrlAliasesForContent($content, false);
977
            }
978
979
            $this->repository->commit();
980
        } catch (Exception $e) {
981
            $this->repository->rollback();
982
            throw $e;
983
        }
984
985
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
986
    }
987
988
    /**
989
     * Publishes URL aliases for all locations of a given content.
990
     *
991
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
992
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
993
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
994
     */
995
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
996
    {
997
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
998
        $locations = $this->repository->getLocationService()->loadLocations(
999
            $content->getVersionInfo()->getContentInfo()
1000
        );
1001
        $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
1002
        foreach ($locations as $location) {
1003
            foreach ($urlAliasNames as $languageCode => $name) {
1004
                $urlAliasHandler->publishUrlAliasForLocation(
1005
                    $location->id,
1006
                    $location->parentLocationId,
1007
                    $name,
1008
                    $languageCode,
1009
                    $content->contentInfo->alwaysAvailable,
1010
                    $updatePathIdentificationString ? $languageCode === $content->contentInfo->mainLanguageCode : false
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $languageCode (integer) and $content->contentInfo->mainLanguageCode (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1011
                );
1012
            }
1013
            // archive URL aliases of Translations that got deleted
1014
            $urlAliasHandler->archiveUrlAliasesForDeletedTranslations(
1015
                $location->id,
1016
                $location->parentLocationId,
1017
                $content->versionInfo->languageCodes
1018
            );
1019
        }
1020
    }
1021
1022
    /**
1023
     * Deletes a content object including all its versions and locations including their subtrees.
1024
     *
1025
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete the content (in one of the locations of the given content object)
1026
     *
1027
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1028
     *
1029
     * @return mixed[] Affected Location Id's
1030
     */
1031
    public function deleteContent(ContentInfo $contentInfo)
1032
    {
1033
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1034
1035
        if (!$this->repository->canUser('content', 'remove', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1036
            throw new UnauthorizedException('content', 'remove', ['contentId' => $contentInfo->id]);
1037
        }
1038
1039
        $affectedLocations = [];
1040
        $this->repository->beginTransaction();
1041
        try {
1042
            // Load Locations first as deleting Content also deletes belonging Locations
1043
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1044
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1045
            $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
1046
            foreach ($spiLocations as $spiLocation) {
1047
                $urlAliasHandler->locationDeleted($spiLocation->id);
1048
                $affectedLocations[] = $spiLocation->id;
1049
            }
1050
            $this->repository->commit();
1051
        } catch (Exception $e) {
1052
            $this->repository->rollback();
1053
            throw $e;
1054
        }
1055
1056
        return $affectedLocations;
1057
    }
1058
1059
    /**
1060
     * Creates a draft from a published or archived version.
1061
     *
1062
     * If no version is given, the current published version is used.
1063
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1064
     * It can be changed on updating the version.
1065
     *
1066
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1067
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1068
     * @param \eZ\Publish\API\Repository\Values\User\User $creator if set given user is used to create the draft - otherwise the current-user is used
1069
     *
1070
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1071
     *
1072
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
1073
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the current-user is not allowed to create the draft
1074
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1075
     */
1076
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1077
    {
1078
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1079
1080
        if ($versionInfo !== null) {
1081
            // Check that given $contentInfo and $versionInfo belong to the same content
1082
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1083
                throw new InvalidArgumentException(
1084
                    '$versionInfo',
1085
                    'VersionInfo does not belong to the same content as given ContentInfo'
1086
                );
1087
            }
1088
1089
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1090
1091
            switch ($versionInfo->status) {
1092
                case VersionInfo::STATUS_PUBLISHED:
1093
                case VersionInfo::STATUS_ARCHIVED:
1094
                    break;
1095
1096
                default:
1097
                    // @todo: throw an exception here, to be defined
1098
                    throw new BadStateException(
1099
                        '$versionInfo',
1100
                        'Draft can not be created from a draft version'
1101
                    );
1102
            }
1103
1104
            $versionNo = $versionInfo->versionNo;
1105
        } elseif ($contentInfo->published) {
1106
            $versionNo = $contentInfo->currentVersionNo;
1107
        } else {
1108
            // @todo: throw an exception here, to be defined
1109
            throw new BadStateException(
1110
                '$contentInfo',
1111
                'Content is not published, draft can be created only from published or archived version'
1112
            );
1113
        }
1114
1115
        if ($creator === null) {
1116
            $creator = $this->repository->getCurrentUserReference();
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Reposito...tCurrentUserReference() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::getCurrentUserReference() instead. Get current user reference.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1117
        }
1118
1119
        if (!$this->repository->getPermissionResolver()->canUser(
1120
            'content',
1121
            'edit',
1122
            $contentInfo,
1123
            [
1124
                (new Target\Builder\VersionBuilder())
1125
                    ->changeStatusTo(APIVersionInfo::STATUS_DRAFT)
1126
                    ->build(),
1127
            ]
1128
        )) {
1129
            throw new UnauthorizedException(
1130
                'content',
1131
                'edit',
1132
                ['contentId' => $contentInfo->id]
1133
            );
1134
        }
1135
1136
        $this->repository->beginTransaction();
1137
        try {
1138
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1139
                $contentInfo->id,
1140
                $versionNo,
1141
                $creator->getUserId()
1142
            );
1143
            $this->repository->commit();
1144
        } catch (Exception $e) {
1145
            $this->repository->rollback();
1146
            throw $e;
1147
        }
1148
1149
        return $this->domainMapper->buildContentDomainObject(
1150
            $spiContent,
1151
            $this->repository->getContentTypeService()->loadContentType(
1152
                $spiContent->versionInfo->contentInfo->contentTypeId
1153
            )
1154
        );
1155
    }
1156
1157
    /**
1158
     * {@inheritdoc}
1159
     */
1160
    public function countContentDrafts(?User $user = null): int
1161
    {
1162
        if ($this->repository->hasAccess('content', 'versionread') === false) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::hasAccess() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::hasAccess() instead. Check if user has access to a given module / function. Low level function, use canUser instead if you have objects to check against.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1163
            return 0;
1164
        }
1165
1166
        return $this->persistenceHandler->contentHandler()->countDraftsForUser(
1167
            $this->resolveUser($user)->getUserId()
1168
        );
1169
    }
1170
1171
    /**
1172
     * Loads drafts for a user.
1173
     *
1174
     * If no user is given the drafts for the authenticated user are returned
1175
     *
1176
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
1177
     *
1178
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Drafts owned by the given user
1179
     *
1180
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1181
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1182
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1183
     */
1184
    public function loadContentDrafts(User $user = null)
1185
    {
1186
        // throw early if user has absolutely no access to versionread
1187
        if ($this->repository->hasAccess('content', 'versionread') === false) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::hasAccess() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::hasAccess() instead. Check if user has access to a given module / function. Low level function, use canUser instead if you have objects to check against.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1188
            throw new UnauthorizedException('content', 'versionread');
1189
        }
1190
1191
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser(
1192
            $this->resolveUser($user)->getUserId()
1193
        );
1194
        $versionInfoList = [];
1195
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1196
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1197
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1198
            if (!$this->repository->canUser('content', 'versionread', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1199
                throw new UnauthorizedException('content', 'versionread', ['contentId' => $versionInfo->contentInfo->id]);
1200
            }
1201
1202
            $versionInfoList[] = $versionInfo;
1203
        }
1204
1205
        return $versionInfoList;
1206
    }
1207
1208
    /**
1209
     * {@inheritdoc}
1210
     */
1211
    public function loadContentDraftList(?User $user = null, int $offset = 0, int $limit = -1): ContentDraftList
1212
    {
1213
        $list = new ContentDraftList();
1214
        if ($this->repository->hasAccess('content', 'versionread') === false) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::hasAccess() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::hasAccess() instead. Check if user has access to a given module / function. Low level function, use canUser instead if you have objects to check against.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1215
            return $list;
1216
        }
1217
1218
        $list->totalCount = $this->persistenceHandler->contentHandler()->countDraftsForUser(
1219
            $this->resolveUser($user)->getUserId()
1220
        );
1221
        if ($list->totalCount > 0) {
1222
            $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftListForUser(
1223
                $this->resolveUser($user)->getUserId(),
1224
                $offset,
1225
                $limit
1226
            );
1227
            foreach ($spiVersionInfoList as $spiVersionInfo) {
1228
                $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1229
                if ($this->repository->canUser('content', 'versionread', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1230
                    $list->items[] = new ContentDraftListItem($versionInfo);
1231
                } else {
1232
                    $list->items[] = new UnauthorizedContentDraftListItem(
1233
                        'content',
1234
                        'versionread',
1235
                        ['contentId' => $versionInfo->contentInfo->id]
1236
                    );
1237
                }
1238
            }
1239
        }
1240
1241
        return $list;
1242
    }
1243
1244
    /**
1245
     * Updates the fields of a draft.
1246
     *
1247
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1248
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1249
     *
1250
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1251
     *
1252
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1253
     *                                                                               or if a required field is missing / set to an empty value.
1254
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1255
     *                                                                          or value is set for non-translatable field in language
1256
     *                                                                          other than main.
1257
     *
1258
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1259
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1260
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1261
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1262
     */
1263
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1264
    {
1265
        $contentUpdateStruct = clone $contentUpdateStruct;
1266
1267
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1268
        $content = $this->loadContent(
1269
            $versionInfo->getContentInfo()->id,
1270
            null,
1271
            $versionInfo->versionNo
1272
        );
1273
        if (!$content->versionInfo->isDraft()) {
1274
            throw new BadStateException(
1275
                '$versionInfo',
1276
                'Version is not a draft and can not be updated'
1277
            );
1278
        }
1279
1280
        if (!$this->repository->getPermissionResolver()->canUser(
1281
            'content',
1282
            'edit',
1283
            $content,
1284
            [
1285
                (new Target\Builder\VersionBuilder())
1286
                    ->updateFieldsTo(
1287
                        $contentUpdateStruct->initialLanguageCode,
1288
                        $contentUpdateStruct->fields
1289
                    )
1290
                    ->build(),
1291
            ]
1292
        )) {
1293
            throw new UnauthorizedException('content', 'edit', ['contentId' => $content->id]);
1294
        }
1295
1296
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1297
        if ($contentUpdateStruct->initialLanguageCode === null) {
1298
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1299
        }
1300
1301
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1302
        $contentLanguageHandler = $this->persistenceHandler->contentLanguageHandler();
1303
        foreach ($allLanguageCodes as $languageCode) {
1304
            $contentLanguageHandler->loadByLanguageCode($languageCode);
1305
        }
1306
1307
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1308
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1309
            $content->contentInfo->contentTypeId
1310
        );
1311
        $fields = $this->mapFieldsForUpdate(
1312
            $contentUpdateStruct,
1313
            $contentType,
1314
            $mainLanguageCode
1315
        );
1316
1317
        $fieldValues = [];
1318
        $spiFields = [];
1319
        $allFieldErrors = [];
1320
        $inputRelations = [];
1321
        $locationIdToContentIdMapping = [];
1322
1323
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1324
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1325
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1326
                $fieldDefinition->fieldTypeIdentifier
1327
            );
1328
1329
            foreach ($allLanguageCodes as $languageCode) {
1330
                $isCopied = $isEmpty = $isRetained = false;
1331
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1332
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1333
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1334
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1335
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1336
1337
                if (!$isFieldUpdated && !$isLanguageNew) {
1338
                    $isRetained = true;
1339
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1340
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1341
                    $isCopied = true;
1342
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1343
                } elseif ($isFieldUpdated) {
1344
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1345
                } else {
1346
                    $fieldValue = $fieldDefinition->defaultValue;
1347
                }
1348
1349
                $fieldValue = $fieldType->acceptValue($fieldValue);
1350
1351
                if ($fieldType->isEmptyValue($fieldValue)) {
1352
                    $isEmpty = true;
1353
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1354
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1355
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1356
                            null,
1357
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1358
                            'empty'
1359
                        );
1360
                    }
1361
                } elseif ($isLanguageUpdated) {
1362
                    $fieldErrors = $fieldType->validate(
1363
                        $fieldDefinition,
1364
                        $fieldValue
1365
                    );
1366
                    if (!empty($fieldErrors)) {
1367
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1368
                    }
1369
                }
1370
1371
                if (!empty($allFieldErrors)) {
1372
                    continue;
1373
                }
1374
1375
                $this->relationProcessor->appendFieldRelations(
1376
                    $inputRelations,
1377
                    $locationIdToContentIdMapping,
1378
                    $fieldType,
1379
                    $fieldValue,
0 ignored issues
show
Compatibility introduced by
$fieldValue of type object<eZ\Publish\SPI\FieldType\Value> is not a sub-type of object<eZ\Publish\Core\FieldType\Value>. It seems like you assume a concrete implementation of the interface eZ\Publish\SPI\FieldType\Value 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...
1380
                    $fieldDefinition->id
1381
                );
1382
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1383
1384
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1385
                    continue;
1386
                }
1387
1388
                $spiFields[] = new SPIField(
1389
                    [
1390
                        'id' => $isLanguageNew ?
1391
                            null :
1392
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1393
                        'fieldDefinitionId' => $fieldDefinition->id,
1394
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1395
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1396
                        'languageCode' => $languageCode,
1397
                        'versionNo' => $versionInfo->versionNo,
1398
                    ]
1399
                );
1400
            }
1401
        }
1402
1403
        if (!empty($allFieldErrors)) {
1404
            throw new ContentFieldValidationException($allFieldErrors);
1405
        }
1406
1407
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1408
            [
1409
                'name' => $this->nameSchemaService->resolveNameSchema(
1410
                    $content,
1411
                    $fieldValues,
1412
                    $allLanguageCodes,
1413
                    $contentType
1414
                ),
1415
                'creatorId' => $contentUpdateStruct->creatorId ?: $this->repository->getCurrentUserReference()->getUserId(),
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Reposito...tCurrentUserReference() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::getCurrentUserReference() instead. Get current user reference.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1416
                'fields' => $spiFields,
1417
                'modificationDate' => time(),
1418
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1419
                    $contentUpdateStruct->initialLanguageCode
1420
                )->id,
1421
            ]
1422
        );
1423
        $existingRelations = $this->loadRelations($versionInfo);
1424
1425
        $this->repository->beginTransaction();
1426
        try {
1427
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1428
                $versionInfo->getContentInfo()->id,
1429
                $versionInfo->versionNo,
1430
                $spiContentUpdateStruct
1431
            );
1432
            $this->relationProcessor->processFieldRelations(
1433
                $inputRelations,
1434
                $spiContent->versionInfo->contentInfo->id,
1435
                $spiContent->versionInfo->versionNo,
1436
                $contentType,
1437
                $existingRelations
1438
            );
1439
            $this->repository->commit();
1440
        } catch (Exception $e) {
1441
            $this->repository->rollback();
1442
            throw $e;
1443
        }
1444
1445
        return $this->domainMapper->buildContentDomainObject(
1446
            $spiContent,
1447
            $contentType
1448
        );
1449
    }
1450
1451
    /**
1452
     * Returns only updated language codes.
1453
     *
1454
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1455
     *
1456
     * @return array
1457
     */
1458
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1459
    {
1460
        $languageCodes = [
1461
            $contentUpdateStruct->initialLanguageCode => true,
1462
        ];
1463
1464
        foreach ($contentUpdateStruct->fields as $field) {
1465
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1466
                continue;
1467
            }
1468
1469
            $languageCodes[$field->languageCode] = true;
1470
        }
1471
1472
        return array_keys($languageCodes);
1473
    }
1474
1475
    /**
1476
     * Returns all language codes used in given $fields.
1477
     *
1478
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1479
     *
1480
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1481
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1482
     *
1483
     * @return array
1484
     */
1485
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1486
    {
1487
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1488
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1489
1490
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1491
        foreach ($updatedLanguageCodes as $languageCode) {
1492
            $languageCodes[$languageCode] = true;
1493
        }
1494
1495
        return array_keys($languageCodes);
1496
    }
1497
1498
    /**
1499
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1500
     *
1501
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1502
     *                                                                          or value is set for non-translatable field in language
1503
     *                                                                          other than main
1504
     *
1505
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1506
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1507
     * @param string $mainLanguageCode
1508
     *
1509
     * @return array
1510
     */
1511
    protected function mapFieldsForUpdate(
1512
        APIContentUpdateStruct $contentUpdateStruct,
1513
        ContentType $contentType,
1514
        $mainLanguageCode
1515
    ) {
1516
        $fields = [];
1517
1518
        foreach ($contentUpdateStruct->fields as $field) {
1519
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1520
1521
            if ($fieldDefinition === null) {
1522
                throw new ContentValidationException(
1523
                    "Field definition '%identifier%' does not exist in given ContentType",
1524
                    ['%identifier%' => $field->fieldDefIdentifier]
1525
                );
1526
            }
1527
1528
            if ($field->languageCode === null) {
1529
                if ($fieldDefinition->isTranslatable) {
1530
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1531
                } else {
1532
                    $languageCode = $mainLanguageCode;
1533
                }
1534
                $field = $this->cloneField($field, ['languageCode' => $languageCode]);
1535
            }
1536
1537
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $mainLanguageCode)) {
1538
                throw new ContentValidationException(
1539
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1540
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1541
                );
1542
            }
1543
1544
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1545
        }
1546
1547
        return $fields;
1548
    }
1549
1550
    /**
1551
     * Publishes a content version.
1552
     *
1553
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1554
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1555
     *
1556
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1557
     * @param string[] $translations
1558
     *
1559
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1560
     *
1561
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1562
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1563
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1564
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1565
     */
1566
    public function publishVersion(APIVersionInfo $versionInfo, array $translations = Language::ALL)
1567
    {
1568
        $content = $this->internalLoadContent(
1569
            $versionInfo->contentInfo->id,
1570
            null,
1571
            $versionInfo->versionNo
1572
        );
1573
1574
        $fromContent = null;
0 ignored issues
show
Unused Code introduced by
$fromContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1575
        if ($content->contentInfo->currentVersionNo !== $versionInfo->versionNo) {
1576
            $fromContent = $this->internalLoadContent(
1577
                $content->contentInfo->id,
1578
                null,
1579
                $content->contentInfo->currentVersionNo
1580
            );
1581
            // should not occur now, might occur in case of un-publish
1582
            if (!$fromContent->contentInfo->isPublished()) {
1583
                $fromContent = null;
0 ignored issues
show
Unused Code introduced by
$fromContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1584
            }
1585
        }
1586
1587
        if (!$this->repository->getPermissionResolver()->canUser(
1588
            'content',
1589
            'publish',
1590
            $content
1591
        )) {
1592
            throw new UnauthorizedException(
1593
                'content', 'publish', ['contentId' => $content->id]
1594
            );
1595
        }
1596
1597
        $this->repository->beginTransaction();
1598
        try {
1599
            $this->copyTranslationsFromPublishedVersion($content->versionInfo, $translations);
1600
            $content = $this->internalPublishVersion($content->getVersionInfo(), null);
1601
            $this->repository->commit();
1602
        } catch (Exception $e) {
1603
            $this->repository->rollback();
1604
            throw $e;
1605
        }
1606
1607
        return $content;
1608
    }
1609
1610
    /**
1611
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1612
     * @param array $translations
1613
     *
1614
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1615
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
1616
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
1617
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1618
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1619
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1620
     */
1621
    protected function copyTranslationsFromPublishedVersion(APIVersionInfo $versionInfo, array $translations = []): void
1622
    {
1623
        $contendId = $versionInfo->contentInfo->id;
1624
1625
        $currentContent = $this->internalLoadContent($contendId);
1626
        $currentVersionInfo = $currentContent->versionInfo;
1627
1628
        // Copying occurs only if:
1629
        // - There is published Version
1630
        // - Published version is older than the currently published one unless specific translations are provided.
1631
        if (!$currentVersionInfo->isPublished() ||
1632
            ($versionInfo->versionNo >= $currentVersionInfo->versionNo && empty($translations))) {
1633
            return;
1634
        }
1635
1636
        if (empty($translations)) {
1637
            $languagesToCopy = array_diff(
1638
                $currentVersionInfo->languageCodes,
1639
                $versionInfo->languageCodes
1640
            );
1641
        } else {
1642
            $languagesToCopy = array_diff(
1643
                $currentVersionInfo->languageCodes,
1644
                $translations
1645
            );
1646
        }
1647
1648
        if (empty($languagesToCopy)) {
1649
            return;
1650
        }
1651
1652
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1653
            $currentVersionInfo->contentInfo->contentTypeId
1654
        );
1655
1656
        // Find only translatable fields to update with selected languages
1657
        $updateStruct = $this->newContentUpdateStruct();
1658
        $updateStruct->initialLanguageCode = $versionInfo->initialLanguageCode;
1659
1660
        foreach ($currentContent->getFields() as $field) {
1661
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1662
1663
            if ($fieldDefinition->isTranslatable && in_array($field->languageCode, $languagesToCopy)) {
1664
                $updateStruct->setField($field->fieldDefIdentifier, $field->value, $field->languageCode);
1665
            }
1666
        }
1667
1668
        $this->updateContent($versionInfo, $updateStruct);
1669
    }
1670
1671
    /**
1672
     * Publishes a content version.
1673
     *
1674
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1675
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1676
     *
1677
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1678
     *
1679
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1680
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1681
     *
1682
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1683
     */
1684
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1685
    {
1686
        if (!$versionInfo->isDraft()) {
1687
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1688
        }
1689
1690
        $currentTime = $this->getUnixTimestamp();
1691
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1692
            $publicationDate = $currentTime;
1693
        }
1694
1695
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1696
        $metadataUpdateStruct->publicationDate = $publicationDate;
1697
        $metadataUpdateStruct->modificationDate = $currentTime;
1698
1699
        $contentId = $versionInfo->getContentInfo()->id;
1700
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1701
            $contentId,
1702
            $versionInfo->versionNo,
1703
            $metadataUpdateStruct
1704
        );
1705
1706
        $content = $this->domainMapper->buildContentDomainObject(
1707
            $spiContent,
1708
            $this->repository->getContentTypeService()->loadContentType(
1709
                $spiContent->versionInfo->contentInfo->contentTypeId
1710
            )
1711
        );
1712
1713
        $this->publishUrlAliasesForContent($content);
1714
1715
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1716
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1717
            $contentId,
1718
            APIVersionInfo::STATUS_ARCHIVED,
1719
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1720
        );
1721
1722
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1723
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1724
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1725
            $archiveVersion = array_shift($archiveList);
1726
            $this->persistenceHandler->contentHandler()->deleteVersion(
1727
                $contentId,
1728
                $archiveVersion->versionNo
1729
            );
1730
        }
1731
1732
        return $content;
1733
    }
1734
1735
    /**
1736
     * @return int
1737
     */
1738
    protected function getUnixTimestamp()
1739
    {
1740
        return time();
1741
    }
1742
1743
    /**
1744
     * Removes the given version.
1745
     *
1746
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1747
     *         published state or is a last version of Content in non draft state
1748
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1749
     *
1750
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1751
     */
1752
    public function deleteVersion(APIVersionInfo $versionInfo)
1753
    {
1754
        if ($versionInfo->isPublished()) {
1755
            throw new BadStateException(
1756
                '$versionInfo',
1757
                'Version is published and can not be removed'
1758
            );
1759
        }
1760
1761
        if (!$this->repository->canUser('content', 'versionremove', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1762
            throw new UnauthorizedException(
1763
                'content',
1764
                'versionremove',
1765
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1766
            );
1767
        }
1768
1769
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1770
            $versionInfo->contentInfo->id,
1771
            null,
1772
            2
1773
        );
1774
1775
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1776
            throw new BadStateException(
1777
                '$versionInfo',
1778
                'Version is the last version of the Content and can not be removed'
1779
            );
1780
        }
1781
1782
        $this->repository->beginTransaction();
1783
        try {
1784
            $this->persistenceHandler->contentHandler()->deleteVersion(
1785
                $versionInfo->getContentInfo()->id,
1786
                $versionInfo->versionNo
1787
            );
1788
            $this->repository->commit();
1789
        } catch (Exception $e) {
1790
            $this->repository->rollback();
1791
            throw $e;
1792
        }
1793
    }
1794
1795
    /**
1796
     * Loads all versions for the given content.
1797
     *
1798
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1799
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the given status is invalid
1800
     *
1801
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1802
     * @param int|null $status
1803
     *
1804
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1805
     */
1806
    public function loadVersions(ContentInfo $contentInfo, ?int $status = null)
1807
    {
1808
        if (!$this->repository->canUser('content', 'versionread', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1809
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1810
        }
1811
1812
        if ($status !== null && !in_array((int)$status, [VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED], true)) {
1813
            throw new InvalidArgumentException(
1814
                'status',
1815
                sprintf(
1816
                    'it can be one of %d (draft), %d (published), %d (archived), %d given',
1817
                    VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED, $status
1818
                ));
1819
        }
1820
1821
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id, $status);
1822
1823
        $versions = [];
1824
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1825
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1826
            if (!$this->repository->canUser('content', 'versionread', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1827
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1828
            }
1829
1830
            $versions[] = $versionInfo;
1831
        }
1832
1833
        return $versions;
1834
    }
1835
1836
    /**
1837
     * Copies the content to a new location. If no version is given,
1838
     * all versions are copied, otherwise only the given version.
1839
     *
1840
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1841
     *
1842
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1843
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1844
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1845
     *
1846
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1847
     */
1848
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1849
    {
1850
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1851
            $destinationLocationCreateStruct->parentLocationId
1852
        );
1853
        if (!$this->repository->canUser('content', 'create', $contentInfo, [$destinationLocation])) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1854
            throw new UnauthorizedException(
1855
                'content',
1856
                'create',
1857
                [
1858
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1859
                    'sectionId' => $contentInfo->sectionId,
1860
                ]
1861
            );
1862
        }
1863
        if (!$this->repository->canUser('content', 'manage_locations', $contentInfo, [$destinationLocation])) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1864
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
1865
        }
1866
1867
        $defaultObjectStates = $this->getDefaultObjectStates();
1868
1869
        $this->repository->beginTransaction();
1870
        try {
1871
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1872
                $contentInfo->id,
1873
                $versionInfo ? $versionInfo->versionNo : null,
1874
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1875
            );
1876
1877
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1878
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1879
                $objectStateHandler->setContentState(
1880
                    $spiContent->versionInfo->contentInfo->id,
1881
                    $objectStateGroupId,
1882
                    $objectState->id
1883
                );
1884
            }
1885
1886
            $content = $this->internalPublishVersion(
1887
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1888
                $spiContent->versionInfo->creationDate
1889
            );
1890
1891
            $this->repository->getLocationService()->createLocation(
1892
                $content->getVersionInfo()->getContentInfo(),
1893
                $destinationLocationCreateStruct
1894
            );
1895
            $this->repository->commit();
1896
        } catch (Exception $e) {
1897
            $this->repository->rollback();
1898
            throw $e;
1899
        }
1900
1901
        return $this->internalLoadContent($content->id);
1902
    }
1903
1904
    /**
1905
     * Loads all outgoing relations for the given version.
1906
     *
1907
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1908
     *
1909
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1910
     *
1911
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1912
     */
1913
    public function loadRelations(APIVersionInfo $versionInfo)
1914
    {
1915
        if ($versionInfo->isPublished()) {
1916
            $function = 'read';
1917
        } else {
1918
            $function = 'versionread';
1919
        }
1920
1921
        if (!$this->repository->canUser('content', $function, $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1922
            throw new UnauthorizedException('content', $function);
1923
        }
1924
1925
        $contentInfo = $versionInfo->getContentInfo();
1926
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1927
            $contentInfo->id,
1928
            $versionInfo->versionNo
1929
        );
1930
1931
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1932
        $relations = [];
1933
        foreach ($spiRelations as $spiRelation) {
1934
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1935
            if (!$this->repository->canUser('content', 'read', $destinationContentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1936
                continue;
1937
            }
1938
1939
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1940
                $spiRelation,
1941
                $contentInfo,
1942
                $destinationContentInfo
1943
            );
1944
        }
1945
1946
        return $relations;
1947
    }
1948
1949
    /**
1950
     * {@inheritdoc}
1951
     */
1952
    public function countReverseRelations(ContentInfo $contentInfo): int
1953
    {
1954
        if (!$this->repository->canUser('content', 'reverserelatedlist', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1955
            return 0;
1956
        }
1957
1958
        return $this->persistenceHandler->contentHandler()->countReverseRelations(
1959
            $contentInfo->id
1960
        );
1961
    }
1962
1963
    /**
1964
     * Loads all incoming relations for a content object.
1965
     *
1966
     * The relations come only from published versions of the source content objects
1967
     *
1968
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1969
     *
1970
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1971
     *
1972
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1973
     */
1974
    public function loadReverseRelations(ContentInfo $contentInfo)
1975
    {
1976
        if (!$this->repository->canUser('content', 'reverserelatedlist', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1977
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
1978
        }
1979
1980
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1981
            $contentInfo->id
1982
        );
1983
1984
        $returnArray = [];
1985
        foreach ($spiRelations as $spiRelation) {
1986
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1987
            if (!$this->repository->canUser('content', 'read', $sourceContentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
1988
                continue;
1989
            }
1990
1991
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1992
                $spiRelation,
1993
                $sourceContentInfo,
1994
                $contentInfo
1995
            );
1996
        }
1997
1998
        return $returnArray;
1999
    }
2000
2001
    /**
2002
     * {@inheritdoc}
2003
     */
2004
    public function loadReverseRelationList(ContentInfo $contentInfo, int $offset = 0, int $limit = -1): RelationList
2005
    {
2006
        $list = new RelationList();
2007
        if (!$this->repository->getPermissionResolver()->canUser('content', 'reverserelatedlist', $contentInfo)) {
2008
            return $list;
2009
        }
2010
2011
        $list->totalCount = $this->persistenceHandler->contentHandler()->countReverseRelations(
2012
            $contentInfo->id
2013
        );
2014
        if ($list->totalCount > 0) {
2015
            $spiRelationList = $this->persistenceHandler->contentHandler()->loadReverseRelationList(
2016
                $contentInfo->id,
2017
                $offset,
2018
                $limit
2019
            );
2020
            foreach ($spiRelationList as $spiRelation) {
2021
                $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2022
                if ($this->repository->getPermissionResolver()->canUser('content', 'read', $sourceContentInfo)) {
2023
                    $relation = $this->domainMapper->buildRelationDomainObject(
2024
                        $spiRelation,
2025
                        $sourceContentInfo,
2026
                        $contentInfo
2027
                    );
2028
                    $list->items[] = new RelationListItem($relation);
2029
                } else {
2030
                    $list->items[] = new UnauthorizedRelationListItem(
2031
                        'content',
2032
                        'read',
2033
                        ['contentId' => $sourceContentInfo->id]
2034
                    );
2035
                }
2036
            }
2037
        }
2038
2039
        return $list;
2040
    }
2041
2042
    /**
2043
     * Adds a relation of type common.
2044
     *
2045
     * The source of the relation is the content and version
2046
     * referenced by $versionInfo.
2047
     *
2048
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
2049
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2050
     *
2051
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2052
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
2053
     *
2054
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
2055
     */
2056
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2057
    {
2058
        $sourceVersion = $this->loadVersionInfoById(
2059
            $sourceVersion->contentInfo->id,
2060
            $sourceVersion->versionNo
2061
        );
2062
2063
        if (!$sourceVersion->isDraft()) {
2064
            throw new BadStateException(
2065
                '$sourceVersion',
2066
                'Relations of type common can only be added to versions of status draft'
2067
            );
2068
        }
2069
2070
        if (!$this->repository->canUser('content', 'edit', $sourceVersion)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
2071
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2072
        }
2073
2074
        $sourceContentInfo = $sourceVersion->getContentInfo();
2075
2076
        $this->repository->beginTransaction();
2077
        try {
2078
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
2079
                new SPIRelationCreateStruct(
2080
                    [
2081
                        'sourceContentId' => $sourceContentInfo->id,
2082
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
2083
                        'sourceFieldDefinitionId' => null,
2084
                        'destinationContentId' => $destinationContent->id,
2085
                        'type' => APIRelation::COMMON,
2086
                    ]
2087
                )
2088
            );
2089
            $this->repository->commit();
2090
        } catch (Exception $e) {
2091
            $this->repository->rollback();
2092
            throw $e;
2093
        }
2094
2095
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
2096
    }
2097
2098
    /**
2099
     * Removes a relation of type COMMON from a draft.
2100
     *
2101
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
2102
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2103
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
2104
     *
2105
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2106
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
2107
     */
2108
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2109
    {
2110
        $sourceVersion = $this->loadVersionInfoById(
2111
            $sourceVersion->contentInfo->id,
2112
            $sourceVersion->versionNo
2113
        );
2114
2115
        if (!$sourceVersion->isDraft()) {
2116
            throw new BadStateException(
2117
                '$sourceVersion',
2118
                'Relations of type common can only be removed from versions of status draft'
2119
            );
2120
        }
2121
2122
        if (!$this->repository->canUser('content', 'edit', $sourceVersion)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
2123
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2124
        }
2125
2126
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2127
            $sourceVersion->getContentInfo()->id,
2128
            $sourceVersion->versionNo,
2129
            APIRelation::COMMON
2130
        );
2131
2132
        if (empty($spiRelations)) {
2133
            throw new InvalidArgumentException(
2134
                '$sourceVersion',
2135
                'There are no relations of type COMMON for the given destination'
2136
            );
2137
        }
2138
2139
        // there should be only one relation of type COMMON for each destination,
2140
        // but in case there were ever more then one, we will remove them all
2141
        // @todo: alternatively, throw BadStateException?
2142
        $this->repository->beginTransaction();
2143
        try {
2144
            foreach ($spiRelations as $spiRelation) {
2145
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2146
                    $this->persistenceHandler->contentHandler()->removeRelation(
2147
                        $spiRelation->id,
2148
                        APIRelation::COMMON
2149
                    );
2150
                }
2151
            }
2152
            $this->repository->commit();
2153
        } catch (Exception $e) {
2154
            $this->repository->rollback();
2155
            throw $e;
2156
        }
2157
    }
2158
2159
    /**
2160
     * {@inheritdoc}
2161
     */
2162
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2163
    {
2164
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2165
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2166
            E_USER_DEPRECATED
2167
        );
2168
        $this->deleteTranslation($contentInfo, $languageCode);
2169
    }
2170
2171
    /**
2172
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2173
     *
2174
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2175
     *
2176
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2177
     *         is the Main Translation of a Content Item.
2178
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2179
     *         to delete the content (in one of the locations of the given Content Item).
2180
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2181
     *         is invalid for the given content.
2182
     *
2183
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2184
     * @param string $languageCode
2185
     *
2186
     * @since 6.13
2187
     */
2188
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2189
    {
2190
        if ($contentInfo->mainLanguageCode === $languageCode) {
2191
            throw new BadStateException(
2192
                '$languageCode',
2193
                'Specified translation is the main translation of the Content Object'
2194
            );
2195
        }
2196
2197
        $translationWasFound = false;
2198
        $this->repository->beginTransaction();
2199
        try {
2200
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2201
                if (!$this->repository->canUser('content', 'remove', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
2202
                    throw new UnauthorizedException(
2203
                        'content',
2204
                        'remove',
2205
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2206
                    );
2207
                }
2208
2209
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2210
                    continue;
2211
                }
2212
2213
                $translationWasFound = true;
2214
2215
                // If the translation is the version's only one, delete the version
2216
                if (count($versionInfo->languageCodes) < 2) {
2217
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2218
                        $versionInfo->getContentInfo()->id,
2219
                        $versionInfo->versionNo
2220
                    );
2221
                }
2222
            }
2223
2224
            if (!$translationWasFound) {
2225
                throw new InvalidArgumentException(
2226
                    '$languageCode',
2227
                    sprintf(
2228
                        '%s does not exist in the Content item(id=%d)',
2229
                        $languageCode,
2230
                        $contentInfo->id
2231
                    )
2232
                );
2233
            }
2234
2235
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2236
                $contentInfo->id,
2237
                $languageCode
2238
            );
2239
            $locationIds = array_map(
2240
                function (Location $location) {
2241
                    return $location->id;
2242
                },
2243
                $this->repository->getLocationService()->loadLocations($contentInfo)
2244
            );
2245
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2246
                $locationIds,
2247
                $languageCode
2248
            );
2249
            $this->repository->commit();
2250
        } catch (InvalidArgumentException $e) {
2251
            $this->repository->rollback();
2252
            throw $e;
2253
        } catch (BadStateException $e) {
2254
            $this->repository->rollback();
2255
            throw $e;
2256
        } catch (UnauthorizedException $e) {
2257
            $this->repository->rollback();
2258
            throw $e;
2259
        } catch (Exception $e) {
2260
            $this->repository->rollback();
2261
            // cover generic unexpected exception to fulfill API promise on @throws
2262
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2263
        }
2264
    }
2265
2266
    /**
2267
     * Delete specified Translation from a Content Draft.
2268
     *
2269
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2270
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2271
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2272
     *         to edit the Content (in one of the locations of the given Content Object).
2273
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2274
     *         is invalid for the given Draft.
2275
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2276
     *
2277
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2278
     * @param string $languageCode Language code of the Translation to be removed
2279
     *
2280
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2281
     *
2282
     * @since 6.12
2283
     */
2284
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2285
    {
2286
        if (!$versionInfo->isDraft()) {
2287
            throw new BadStateException(
2288
                '$versionInfo',
2289
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2290
            );
2291
        }
2292
2293
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2294
            throw new BadStateException(
2295
                '$languageCode',
2296
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2297
            );
2298
        }
2299
2300
        if (!$this->repository->canUser('content', 'edit', $versionInfo->contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
2301
            throw new UnauthorizedException(
2302
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2303
            );
2304
        }
2305
2306
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2307
            throw new InvalidArgumentException(
2308
                '$languageCode',
2309
                sprintf(
2310
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2311
                    $versionInfo->contentInfo->id,
2312
                    $versionInfo->versionNo,
2313
                    $languageCode
2314
                )
2315
            );
2316
        }
2317
2318
        if (count($versionInfo->languageCodes) === 1) {
2319
            throw new BadStateException(
2320
                '$languageCode',
2321
                'Specified Translation is the only one Content Object Version has'
2322
            );
2323
        }
2324
2325
        $this->repository->beginTransaction();
2326
        try {
2327
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2328
                $versionInfo->contentInfo->id,
2329
                $versionInfo->versionNo,
2330
                $languageCode
2331
            );
2332
            $this->repository->commit();
2333
2334
            return $this->domainMapper->buildContentDomainObject(
2335
                $spiContent,
2336
                $this->repository->getContentTypeService()->loadContentType(
2337
                    $spiContent->versionInfo->contentInfo->contentTypeId
2338
                )
2339
            );
2340
        } catch (APINotFoundException $e) {
2341
            // avoid wrapping expected NotFoundException in BadStateException handled below
2342
            $this->repository->rollback();
2343
            throw $e;
2344
        } catch (Exception $e) {
2345
            $this->repository->rollback();
2346
            // cover generic unexpected exception to fulfill API promise on @throws
2347
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2348
        }
2349
    }
2350
2351
    /**
2352
     * Hides Content by making all the Locations appear hidden.
2353
     * It does not persist hidden state on Location object itself.
2354
     *
2355
     * Content hidden by this API can be revealed by revealContent API.
2356
     *
2357
     * @see revealContent
2358
     *
2359
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2360
     */
2361
    public function hideContent(ContentInfo $contentInfo): void
2362
    {
2363
        if (!$this->repository->canUser('content', 'hide', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
2364
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2365
        }
2366
2367
        $this->repository->beginTransaction();
2368
        try {
2369
            $this->persistenceHandler->contentHandler()->updateMetadata(
2370
                $contentInfo->id,
2371
                new SPIMetadataUpdateStruct([
2372
                    'isHidden' => true,
2373
                ])
2374
            );
2375
            $locationHandler = $this->persistenceHandler->locationHandler();
2376
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2377
            foreach ($childLocations as $childLocation) {
2378
                $locationHandler->setInvisible($childLocation->id);
2379
            }
2380
            $this->repository->commit();
2381
        } catch (Exception $e) {
2382
            $this->repository->rollback();
2383
            throw $e;
2384
        }
2385
    }
2386
2387
    /**
2388
     * Reveals Content hidden by hideContent API.
2389
     * Locations which were hidden before hiding Content will remain hidden.
2390
     *
2391
     * @see hideContent
2392
     *
2393
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2394
     */
2395
    public function revealContent(ContentInfo $contentInfo): void
2396
    {
2397
        if (!$this->repository->canUser('content', 'hide', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
2398
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2399
        }
2400
2401
        $this->repository->beginTransaction();
2402
        try {
2403
            $this->persistenceHandler->contentHandler()->updateMetadata(
2404
                $contentInfo->id,
2405
                new SPIMetadataUpdateStruct([
2406
                    'isHidden' => false,
2407
                ])
2408
            );
2409
            $locationHandler = $this->persistenceHandler->locationHandler();
2410
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2411
            foreach ($childLocations as $childLocation) {
2412
                $locationHandler->setVisible($childLocation->id);
2413
            }
2414
            $this->repository->commit();
2415
        } catch (Exception $e) {
2416
            $this->repository->rollback();
2417
            throw $e;
2418
        }
2419
    }
2420
2421
    /**
2422
     * Instantiates a new content create struct object.
2423
     *
2424
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2425
     *
2426
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2427
     * @param string $mainLanguageCode
2428
     *
2429
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2430
     */
2431
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2432
    {
2433
        return new ContentCreateStruct(
2434
            [
2435
                'contentType' => $contentType,
2436
                'mainLanguageCode' => $mainLanguageCode,
2437
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2438
            ]
2439
        );
2440
    }
2441
2442
    /**
2443
     * Instantiates a new content meta data update struct.
2444
     *
2445
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2446
     */
2447
    public function newContentMetadataUpdateStruct()
2448
    {
2449
        return new ContentMetadataUpdateStruct();
2450
    }
2451
2452
    /**
2453
     * Instantiates a new content update struct.
2454
     *
2455
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2456
     */
2457
    public function newContentUpdateStruct()
2458
    {
2459
        return new ContentUpdateStruct();
2460
    }
2461
2462
    /**
2463
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2464
     *
2465
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2466
     */
2467
    private function resolveUser(?User $user): UserReference
2468
    {
2469
        if ($user === null) {
2470
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2471
        }
2472
2473
        return $user;
2474
    }
2475
}
2476