Completed
Push — EZP-30969-fetch-reverse-relati... ( f9e12d...5e1246 )
by
unknown
18:37
created

ContentService::loadContentInfoList()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
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\Core\Repository\Values\Content\Location;
20
use eZ\Publish\API\Repository\Values\Content\Language;
21
use eZ\Publish\SPI\Persistence\Handler;
22
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct as APIContentUpdateStruct;
23
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
24
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
25
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
26
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
27
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
28
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
29
use eZ\Publish\API\Repository\Values\User\User;
30
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
31
use eZ\Publish\API\Repository\Values\Content\Field;
32
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
33
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
34
use eZ\Publish\Core\Base\Exceptions\BadStateException;
35
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
36
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
37
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
38
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
39
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
40
use eZ\Publish\Core\FieldType\ValidationError;
41
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
42
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
43
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
44
use eZ\Publish\SPI\Limitation\Target;
45
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
46
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
47
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
48
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
49
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
50
use Exception;
51
52
/**
53
 * This class provides service methods for managing content.
54
 *
55
 * @example Examples/content.php
56
 */
57
class ContentService implements ContentServiceInterface
58
{
59
    /** @var \eZ\Publish\Core\Repository\Repository */
60
    protected $repository;
61
62
    /** @var \eZ\Publish\SPI\Persistence\Handler */
63
    protected $persistenceHandler;
64
65
    /** @var array */
66
    protected $settings;
67
68
    /** @var \eZ\Publish\Core\Repository\Helper\DomainMapper */
69
    protected $domainMapper;
70
71
    /** @var \eZ\Publish\Core\Repository\Helper\RelationProcessor */
72
    protected $relationProcessor;
73
74
    /** @var \eZ\Publish\Core\Repository\Helper\NameSchemaService */
75
    protected $nameSchemaService;
76
77
    /** @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry */
78
    protected $fieldTypeRegistry;
79
80
    /**
81
     * Setups service with reference to repository object that created it & corresponding handler.
82
     *
83
     * @param \eZ\Publish\API\Repository\Repository $repository
84
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
85
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
86
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
87
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
88
     * @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...
89
     * @param array $settings
90
     */
91
    public function __construct(
92
        RepositoryInterface $repository,
93
        Handler $handler,
94
        Helper\DomainMapper $domainMapper,
95
        Helper\RelationProcessor $relationProcessor,
96
        Helper\NameSchemaService $nameSchemaService,
97
        Helper\FieldTypeRegistry $fieldTypeRegistry,
98
        array $settings = []
99
    ) {
100
        $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...
101
        $this->persistenceHandler = $handler;
102
        $this->domainMapper = $domainMapper;
103
        $this->relationProcessor = $relationProcessor;
104
        $this->nameSchemaService = $nameSchemaService;
105
        $this->fieldTypeRegistry = $fieldTypeRegistry;
106
        // Union makes sure default settings are ignored if provided in argument
107
        $this->settings = $settings + [
108
            // Version archive limit (0-50), only enforced on publish, not on un-publish.
109
            'default_version_archive_limit' => 5,
110
        ];
111
    }
112
113
    /**
114
     * Loads a content info object.
115
     *
116
     * To load fields use loadContent
117
     *
118
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
119
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
120
     *
121
     * @param int $contentId
122
     *
123
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
124
     */
125
    public function loadContentInfo($contentId)
126
    {
127
        $contentInfo = $this->internalLoadContentInfo($contentId);
128
        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...
129
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
130
        }
131
132
        return $contentInfo;
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function loadContentInfoList(array $contentIds): iterable
139
    {
140
        $contentInfoList = [];
141
        $spiInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList($contentIds);
142
        foreach ($spiInfoList as $id => $spiInfo) {
143
            $contentInfo = $this->domainMapper->buildContentInfoDomainObject($spiInfo);
144
            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...
145
                $contentInfoList[$id] = $contentInfo;
146
            }
147
        }
148
149
        return $contentInfoList;
150
    }
151
152
    /**
153
     * Loads a content info object.
154
     *
155
     * To load fields use loadContent
156
     *
157
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
158
     *
159
     * @param mixed $id
160
     * @param bool $isRemoteId
161
     *
162
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
163
     */
164
    public function internalLoadContentInfo($id, $isRemoteId = false)
165
    {
166
        try {
167
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
168
169
            return $this->domainMapper->buildContentInfoDomainObject(
170
                $this->persistenceHandler->contentHandler()->$method($id)
171
            );
172
        } catch (APINotFoundException $e) {
173
            throw new NotFoundException(
174
                'Content',
175
                $id,
176
                $e
177
            );
178
        }
179
    }
180
181
    /**
182
     * Loads a content info object for the given remoteId.
183
     *
184
     * To load fields use loadContent
185
     *
186
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
187
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
188
     *
189
     * @param string $remoteId
190
     *
191
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
192
     */
193
    public function loadContentInfoByRemoteId($remoteId)
194
    {
195
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
196
197
        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...
198
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
199
        }
200
201
        return $contentInfo;
202
    }
203
204
    /**
205
     * Loads a version info of the given content object.
206
     *
207
     * If no version number is given, the method returns the current version
208
     *
209
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
210
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
211
     *
212
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
213
     * @param int $versionNo the version number. If not given the current version is returned.
214
     *
215
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
216
     */
217
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
218
    {
219
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
220
    }
221
222
    /**
223
     * Loads a version info of the given content object id.
224
     *
225
     * If no version number is given, the method returns the current version
226
     *
227
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
228
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
229
     *
230
     * @param mixed $contentId
231
     * @param int $versionNo the version number. If not given the current version is returned.
232
     *
233
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
234
     */
235
    public function loadVersionInfoById($contentId, $versionNo = null)
236
    {
237
        try {
238
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
239
                $contentId,
240
                $versionNo
241
            );
242
        } catch (APINotFoundException $e) {
243
            throw new NotFoundException(
244
                'VersionInfo',
245
                [
246
                    'contentId' => $contentId,
247
                    'versionNo' => $versionNo,
248
                ],
249
                $e
250
            );
251
        }
252
253
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
254
255
        if ($versionInfo->isPublished()) {
256
            $function = 'read';
257
        } else {
258
            $function = 'versionread';
259
        }
260
261
        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...
262
            throw new UnauthorizedException('content', $function, ['contentId' => $contentId]);
263
        }
264
265
        return $versionInfo;
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
272
    {
273
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
274
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
275
            $useAlwaysAvailable = false;
276
        }
277
278
        return $this->loadContent(
279
            $contentInfo->id,
280
            $languages,
281
            $versionNo,// On purpose pass as-is and not use $contentInfo, to make sure to return actual current version on null
282
            $useAlwaysAvailable
283
        );
284
    }
285
286
    /**
287
     * {@inheritdoc}
288
     */
289
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
290
    {
291
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
292
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
293
            $useAlwaysAvailable = false;
294
        }
295
296
        return $this->loadContent(
297
            $versionInfo->getContentInfo()->id,
298
            $languages,
299
            $versionInfo->versionNo,
300
            $useAlwaysAvailable
301
        );
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
308
    {
309
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
310
311
        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...
312
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
313
        }
314
        if (
315
            !$content->getVersionInfo()->isPublished()
316
            && !$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...
317
        ) {
318
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId, 'versionNo' => $versionNo]);
319
        }
320
321
        return $content;
322
    }
323
324
    /**
325
     * Loads content in a version of the given content object.
326
     *
327
     * If no version number is given, the method returns the current version
328
     *
329
     * @internal
330
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
331
     *
332
     * @param mixed $id
333
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
334
     *                         returned value object. If not given all languages are returned.
335
     * @param int|null $versionNo the version number. If not given the current version is returned
336
     * @param bool $isRemoteId
337
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
338
     *
339
     * @return \eZ\Publish\API\Repository\Values\Content\Content
340
     */
341
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
342
    {
343
        try {
344
            // Get Content ID if lookup by remote ID
345
            if ($isRemoteId) {
346
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
347
                $id = $spiContentInfo->id;
348
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
349
                $isRemoteId = false;
350
            }
351
352
            $loadLanguages = $languages;
353
            $alwaysAvailableLanguageCode = null;
354
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
355
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
356
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
357
                if (!isset($spiContentInfo)) {
358
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
359
                }
360
361
                if ($spiContentInfo->alwaysAvailable) {
362
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
363
                    $loadLanguages = array_unique($loadLanguages);
364
                }
365
            }
366
367
            $spiContent = $this->persistenceHandler->contentHandler()->load(
368
                $id,
369
                $versionNo,
370
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 352 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...
371
            );
372
        } catch (APINotFoundException $e) {
373
            throw new NotFoundException(
374
                'Content',
375
                [
376
                    $isRemoteId ? 'remoteId' : 'id' => $id,
377
                    'languages' => $languages,
378
                    'versionNo' => $versionNo,
379
                ],
380
                $e
381
            );
382
        }
383
384
        return $this->domainMapper->buildContentDomainObject(
385
            $spiContent,
386
            $this->repository->getContentTypeService()->loadContentType(
387
                $spiContent->versionInfo->contentInfo->contentTypeId
388
            ),
389
            $languages ?? [],
390
            $alwaysAvailableLanguageCode
391
        );
392
    }
393
394
    /**
395
     * Loads content in a version for the content object reference by the given remote id.
396
     *
397
     * If no version is given, the method returns the current version
398
     *
399
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
400
     * @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
401
     *
402
     * @param string $remoteId
403
     * @param array $languages A language filter for fields. If not given all languages are returned
404
     * @param int $versionNo the version number. If not given the current version is returned
405
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
406
     *
407
     * @return \eZ\Publish\API\Repository\Values\Content\Content
408
     */
409
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
410
    {
411
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
412
413
        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...
414
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
415
        }
416
417
        if (
418
            !$content->getVersionInfo()->isPublished()
419
            && !$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...
420
        ) {
421
            throw new UnauthorizedException('content', 'versionread', ['remoteId' => $remoteId, 'versionNo' => $versionNo]);
422
        }
423
424
        return $content;
425
    }
426
427
    /**
428
     * Bulk-load Content items by the list of ContentInfo Value Objects.
429
     *
430
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
431
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
432
     * allowed to access every Content on the list.
433
     *
434
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
435
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
436
     *                            returned value object. If not given all languages are returned.
437
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
438
     *                                 unless all languages have been asked for.
439
     *
440
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
441
     */
442
    public function loadContentListByContentInfo(
443
        array $contentInfoList,
444
        array $languages = [],
445
        $useAlwaysAvailable = true
446
    ) {
447
        $loadAllLanguages = $languages === Language::ALL;
448
        $contentIds = [];
449
        $contentTypeIds = [];
450
        $translations = $languages;
451
        foreach ($contentInfoList as $contentInfo) {
452
            $contentIds[] = $contentInfo->id;
453
            $contentTypeIds[] = $contentInfo->contentTypeId;
454
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
455
            // Might in some case load more languages then intended, but prioritised handling will pick right one
456
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
457
                $translations[] = $contentInfo->mainLanguageCode;
458
            }
459
        }
460
461
        $contentList = [];
462
        $translations = array_unique($translations);
463
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
464
            $contentIds,
465
            $translations
466
        );
467
        $contentTypeList = $this->repository->getContentTypeService()->loadContentTypeList(
468
            array_unique($contentTypeIds),
469
            $languages
470
        );
471
        foreach ($spiContentList as $contentId => $spiContent) {
472
            $contentInfo = $spiContent->versionInfo->contentInfo;
473
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
474
                $spiContent,
475
                $contentTypeList[$contentInfo->contentTypeId],
476
                $languages,
477
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
478
            );
479
        }
480
481
        return $contentList;
482
    }
483
484
    /**
485
     * Creates a new content draft assigned to the authenticated user.
486
     *
487
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
488
     * but this required special rights for the authenticated user
489
     * (this is useful for content staging where the transfer process does not
490
     * have to authenticate with the user which created the content object in the source server).
491
     * The user has to publish the draft if it should be visible.
492
     * In 4.x at least one location has to be provided in the location creation array.
493
     *
494
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
495
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
496
     *                                                                        struct are missing or invalid, or if multiple locations are under the
497
     *                                                                        same parent.
498
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
499
     *                                                                               or if a required field is missing / set to an empty value.
500
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
501
     *                                                                          or value is set for non-translatable field in language
502
     *                                                                          other than main.
503
     *
504
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
505
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
506
     *
507
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
508
     */
509
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = [])
510
    {
511
        if ($contentCreateStruct->mainLanguageCode === null) {
512
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
513
        }
514
515
        if ($contentCreateStruct->contentType === null) {
516
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
517
        }
518
519
        $contentCreateStruct = clone $contentCreateStruct;
520
521
        if ($contentCreateStruct->ownerId === null) {
522
            $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...
523
        }
524
525
        if ($contentCreateStruct->alwaysAvailable === null) {
526
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
527
        }
528
529
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
530
            $contentCreateStruct->contentType->id
531
        );
532
533
        if (empty($contentCreateStruct->sectionId)) {
534
            if (isset($locationCreateStructs[0])) {
535
                $location = $this->repository->getLocationService()->loadLocation(
536
                    $locationCreateStructs[0]->parentLocationId
537
                );
538
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
539
            } else {
540
                $contentCreateStruct->sectionId = 1;
541
            }
542
        }
543
544
        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...
545
            throw new UnauthorizedException(
546
                'content',
547
                'create',
548
                [
549
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
550
                            $locationCreateStructs[0]->parentLocationId :
551
                            null,
552
                    'sectionId' => $contentCreateStruct->sectionId,
553
                ]
554
            );
555
        }
556
557
        if (!empty($contentCreateStruct->remoteId)) {
558
            try {
559
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
560
561
                throw new InvalidArgumentException(
562
                    '$contentCreateStruct',
563
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
564
                );
565
            } catch (APINotFoundException $e) {
566
                // Do nothing
567
            }
568
        } else {
569
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
570
        }
571
572
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
573
574
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
575
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
576
577
        $fieldValues = [];
578
        $spiFields = [];
579
        $allFieldErrors = [];
580
        $inputRelations = [];
581
        $locationIdToContentIdMapping = [];
582
583
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
584
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
585
            $fieldType = $this->fieldTypeRegistry->getFieldType(
586
                $fieldDefinition->fieldTypeIdentifier
587
            );
588
589
            foreach ($languageCodes as $languageCode) {
590
                $isEmptyValue = false;
591
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
592
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
593
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
594
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
595
                } else {
596
                    $fieldValue = $fieldDefinition->defaultValue;
597
                }
598
599
                $fieldValue = $fieldType->acceptValue($fieldValue);
600
601
                if ($fieldType->isEmptyValue($fieldValue)) {
602
                    $isEmptyValue = true;
603
                    if ($fieldDefinition->isRequired) {
604
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
605
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
606
                            null,
607
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
608
                            'empty'
609
                        );
610
                    }
611
                } else {
612
                    $fieldErrors = $fieldType->validate(
613
                        $fieldDefinition,
614
                        $fieldValue
615
                    );
616
                    if (!empty($fieldErrors)) {
617
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
618
                    }
619
                }
620
621
                if (!empty($allFieldErrors)) {
622
                    continue;
623
                }
624
625
                $this->relationProcessor->appendFieldRelations(
626
                    $inputRelations,
627
                    $locationIdToContentIdMapping,
628
                    $fieldType,
629
                    $fieldValue,
630
                    $fieldDefinition->id
631
                );
632
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
633
634
                // Only non-empty value for: translatable field or in main language
635
                if (
636
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
637
                    (!$isEmptyValue && $isLanguageMain)
638
                ) {
639
                    $spiFields[] = new SPIField(
640
                        [
641
                            'id' => null,
642
                            'fieldDefinitionId' => $fieldDefinition->id,
643
                            'type' => $fieldDefinition->fieldTypeIdentifier,
644
                            'value' => $fieldType->toPersistenceValue($fieldValue),
645
                            'languageCode' => $languageCode,
646
                            'versionNo' => null,
647
                        ]
648
                    );
649
                }
650
            }
651
        }
652
653
        if (!empty($allFieldErrors)) {
654
            throw new ContentFieldValidationException($allFieldErrors);
655
        }
656
657
        $spiContentCreateStruct = new SPIContentCreateStruct(
658
            [
659
                'name' => $this->nameSchemaService->resolve(
660
                    $contentCreateStruct->contentType->nameSchema,
661
                    $contentCreateStruct->contentType,
662
                    $fieldValues,
663
                    $languageCodes
664
                ),
665
                'typeId' => $contentCreateStruct->contentType->id,
666
                'sectionId' => $contentCreateStruct->sectionId,
667
                'ownerId' => $contentCreateStruct->ownerId,
668
                'locations' => $spiLocationCreateStructs,
669
                'fields' => $spiFields,
670
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
671
                'remoteId' => $contentCreateStruct->remoteId,
672
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
673
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
674
                    $contentCreateStruct->mainLanguageCode
675
                )->id,
676
            ]
677
        );
678
679
        $defaultObjectStates = $this->getDefaultObjectStates();
680
681
        $this->repository->beginTransaction();
682
        try {
683
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
684
            $this->relationProcessor->processFieldRelations(
685
                $inputRelations,
686
                $spiContent->versionInfo->contentInfo->id,
687
                $spiContent->versionInfo->versionNo,
688
                $contentCreateStruct->contentType
689
            );
690
691
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
692
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
693
                $objectStateHandler->setContentState(
694
                    $spiContent->versionInfo->contentInfo->id,
695
                    $objectStateGroupId,
696
                    $objectState->id
697
                );
698
            }
699
700
            $this->repository->commit();
701
        } catch (Exception $e) {
702
            $this->repository->rollback();
703
            throw $e;
704
        }
705
706
        return $this->domainMapper->buildContentDomainObject(
707
            $spiContent,
708
            $contentCreateStruct->contentType
709
        );
710
    }
711
712
    /**
713
     * Returns an array of default content states with content state group id as key.
714
     *
715
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
716
     */
717
    protected function getDefaultObjectStates()
718
    {
719
        $defaultObjectStatesMap = [];
720
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
721
722
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
723
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
724
                // Only register the first object state which is the default one.
725
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
726
                break;
727
            }
728
        }
729
730
        return $defaultObjectStatesMap;
731
    }
732
733
    /**
734
     * Returns all language codes used in given $fields.
735
     *
736
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
737
     *
738
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
739
     *
740
     * @return string[]
741
     */
742
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
743
    {
744
        $languageCodes = [];
745
746
        foreach ($contentCreateStruct->fields as $field) {
747
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
748
                continue;
749
            }
750
751
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
752
                $field->languageCode
753
            );
754
            $languageCodes[$field->languageCode] = true;
755
        }
756
757
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
758
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
759
                $contentCreateStruct->mainLanguageCode
760
            );
761
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
762
        }
763
764
        return array_keys($languageCodes);
765
    }
766
767
    /**
768
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
769
     *
770
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
771
     *                                                                          or value is set for non-translatable field in language
772
     *                                                                          other than main
773
     *
774
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
775
     *
776
     * @return array
777
     */
778
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
779
    {
780
        $fields = [];
781
782
        foreach ($contentCreateStruct->fields as $field) {
783
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
784
785
            if ($fieldDefinition === null) {
786
                throw new ContentValidationException(
787
                    "Field definition '%identifier%' does not exist in given ContentType",
788
                    ['%identifier%' => $field->fieldDefIdentifier]
789
                );
790
            }
791
792
            if ($field->languageCode === null) {
793
                $field = $this->cloneField(
794
                    $field,
795
                    ['languageCode' => $contentCreateStruct->mainLanguageCode]
796
                );
797
            }
798
799
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $contentCreateStruct->mainLanguageCode)) {
800
                throw new ContentValidationException(
801
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
802
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
803
                );
804
            }
805
806
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
807
        }
808
809
        return $fields;
810
    }
811
812
    /**
813
     * Clones $field with overriding specific properties from given $overrides array.
814
     *
815
     * @param Field $field
816
     * @param array $overrides
817
     *
818
     * @return Field
819
     */
820
    private function cloneField(Field $field, array $overrides = [])
821
    {
822
        $fieldData = array_merge(
823
            [
824
                'id' => $field->id,
825
                'value' => $field->value,
826
                'languageCode' => $field->languageCode,
827
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
828
                'fieldTypeIdentifier' => $field->fieldTypeIdentifier,
829
            ],
830
            $overrides
831
        );
832
833
        return new Field($fieldData);
834
    }
835
836
    /**
837
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
838
     *
839
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
840
     *
841
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
842
     */
843
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
844
    {
845
        $spiLocationCreateStructs = [];
846
        $parentLocationIdSet = [];
847
        $mainLocation = true;
848
849
        foreach ($locationCreateStructs as $locationCreateStruct) {
850
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
851
                throw new InvalidArgumentException(
852
                    '$locationCreateStructs',
853
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
854
                );
855
            }
856
857
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
858
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
859
            }
860
861
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
862
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
863
            }
864
865
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
866
            $parentLocation = $this->repository->getLocationService()->loadLocation(
867
                $locationCreateStruct->parentLocationId
868
            );
869
870
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
871
                $locationCreateStruct,
872
                $parentLocation,
873
                $mainLocation,
874
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
875
                null,
876
                null
877
            );
878
879
            // First Location in the list will be created as main Location
880
            $mainLocation = false;
881
        }
882
883
        return $spiLocationCreateStructs;
884
    }
885
886
    /**
887
     * Updates the metadata.
888
     *
889
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
890
     *
891
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
892
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
893
     *
894
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
895
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
896
     *
897
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
898
     */
899
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
900
    {
901
        $propertyCount = 0;
902
        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...
903
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
904
                $propertyCount += 1;
905
            }
906
        }
907
        if ($propertyCount === 0) {
908
            throw new InvalidArgumentException(
909
                '$contentMetadataUpdateStruct',
910
                'At least one property must be set'
911
            );
912
        }
913
914
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
915
916
        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...
917
            throw new UnauthorizedException('content', 'edit', ['contentId' => $loadedContentInfo->id]);
918
        }
919
920
        if (isset($contentMetadataUpdateStruct->remoteId)) {
921
            try {
922
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
923
924
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
925
                    throw new InvalidArgumentException(
926
                        '$contentMetadataUpdateStruct',
927
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
928
                    );
929
                }
930
            } catch (APINotFoundException $e) {
931
                // Do nothing
932
            }
933
        }
934
935
        $this->repository->beginTransaction();
936
        try {
937
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
938
                $this->persistenceHandler->contentHandler()->updateMetadata(
939
                    $loadedContentInfo->id,
940
                    new SPIMetadataUpdateStruct(
941
                        [
942
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
943
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
944
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
945
                                null,
946
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
947
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
948
                                null,
949
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
950
                                $this->repository->getContentLanguageService()->loadLanguage(
951
                                    $contentMetadataUpdateStruct->mainLanguageCode
952
                                )->id :
953
                                null,
954
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
955
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
956
                            'name' => $contentMetadataUpdateStruct->name,
957
                        ]
958
                    )
959
                );
960
            }
961
962
            // Change main location
963
            if (isset($contentMetadataUpdateStruct->mainLocationId)
964
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
965
                $this->persistenceHandler->locationHandler()->changeMainLocation(
966
                    $loadedContentInfo->id,
967
                    $contentMetadataUpdateStruct->mainLocationId
968
                );
969
            }
970
971
            // Republish URL aliases to update always-available flag
972
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
973
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
974
                $content = $this->loadContent($loadedContentInfo->id);
975
                $this->publishUrlAliasesForContent($content, false);
976
            }
977
978
            $this->repository->commit();
979
        } catch (Exception $e) {
980
            $this->repository->rollback();
981
            throw $e;
982
        }
983
984
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
985
    }
986
987
    /**
988
     * Publishes URL aliases for all locations of a given content.
989
     *
990
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
991
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
992
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
993
     */
994
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
995
    {
996
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
997
        $locations = $this->repository->getLocationService()->loadLocations(
998
            $content->getVersionInfo()->getContentInfo()
999
        );
1000
        $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
1001
        foreach ($locations as $location) {
1002
            foreach ($urlAliasNames as $languageCode => $name) {
1003
                $urlAliasHandler->publishUrlAliasForLocation(
1004
                    $location->id,
1005
                    $location->parentLocationId,
1006
                    $name,
1007
                    $languageCode,
1008
                    $content->contentInfo->alwaysAvailable,
1009
                    $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...
1010
                );
1011
            }
1012
            // archive URL aliases of Translations that got deleted
1013
            $urlAliasHandler->archiveUrlAliasesForDeletedTranslations(
1014
                $location->id,
1015
                $location->parentLocationId,
1016
                $content->versionInfo->languageCodes
1017
            );
1018
        }
1019
    }
1020
1021
    /**
1022
     * Deletes a content object including all its versions and locations including their subtrees.
1023
     *
1024
     * @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)
1025
     *
1026
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1027
     *
1028
     * @return mixed[] Affected Location Id's
1029
     */
1030
    public function deleteContent(ContentInfo $contentInfo)
1031
    {
1032
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1033
1034
        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...
1035
            throw new UnauthorizedException('content', 'remove', ['contentId' => $contentInfo->id]);
1036
        }
1037
1038
        $affectedLocations = [];
1039
        $this->repository->beginTransaction();
1040
        try {
1041
            // Load Locations first as deleting Content also deletes belonging Locations
1042
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1043
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1044
            $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
1045
            foreach ($spiLocations as $spiLocation) {
1046
                $urlAliasHandler->locationDeleted($spiLocation->id);
1047
                $affectedLocations[] = $spiLocation->id;
1048
            }
1049
            $this->repository->commit();
1050
        } catch (Exception $e) {
1051
            $this->repository->rollback();
1052
            throw $e;
1053
        }
1054
1055
        return $affectedLocations;
1056
    }
1057
1058
    /**
1059
     * Creates a draft from a published or archived version.
1060
     *
1061
     * If no version is given, the current published version is used.
1062
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1063
     * It can be changed on updating the version.
1064
     *
1065
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1066
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1067
     * @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
1068
     *
1069
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1070
     *
1071
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
1072
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the current-user is not allowed to create the draft
1073
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1074
     */
1075
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1076
    {
1077
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1078
1079
        if ($versionInfo !== null) {
1080
            // Check that given $contentInfo and $versionInfo belong to the same content
1081
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1082
                throw new InvalidArgumentException(
1083
                    '$versionInfo',
1084
                    'VersionInfo does not belong to the same content as given ContentInfo'
1085
                );
1086
            }
1087
1088
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1089
1090
            switch ($versionInfo->status) {
1091
                case VersionInfo::STATUS_PUBLISHED:
1092
                case VersionInfo::STATUS_ARCHIVED:
1093
                    break;
1094
1095
                default:
1096
                    // @todo: throw an exception here, to be defined
1097
                    throw new BadStateException(
1098
                        '$versionInfo',
1099
                        'Draft can not be created from a draft version'
1100
                    );
1101
            }
1102
1103
            $versionNo = $versionInfo->versionNo;
1104
        } elseif ($contentInfo->published) {
1105
            $versionNo = $contentInfo->currentVersionNo;
1106
        } else {
1107
            // @todo: throw an exception here, to be defined
1108
            throw new BadStateException(
1109
                '$contentInfo',
1110
                'Content is not published, draft can be created only from published or archived version'
1111
            );
1112
        }
1113
1114
        if ($creator === null) {
1115
            $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...
1116
        }
1117
1118
        if (!$this->repository->getPermissionResolver()->canUser(
1119
            'content',
1120
            'edit',
1121
            $contentInfo,
1122
            [
1123
                (new Target\Builder\VersionBuilder())
1124
                    ->changeStatusTo(APIVersionInfo::STATUS_DRAFT)
1125
                    ->build(),
1126
            ]
1127
        )) {
1128
            throw new UnauthorizedException(
1129
                'content',
1130
                'edit',
1131
                ['contentId' => $contentInfo->id]
1132
            );
1133
        }
1134
1135
        $this->repository->beginTransaction();
1136
        try {
1137
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1138
                $contentInfo->id,
1139
                $versionNo,
1140
                $creator->getUserId()
1141
            );
1142
            $this->repository->commit();
1143
        } catch (Exception $e) {
1144
            $this->repository->rollback();
1145
            throw $e;
1146
        }
1147
1148
        return $this->domainMapper->buildContentDomainObject(
1149
            $spiContent,
1150
            $this->repository->getContentTypeService()->loadContentType(
1151
                $spiContent->versionInfo->contentInfo->contentTypeId
1152
            )
1153
        );
1154
    }
1155
1156
    /**
1157
     * {@inheritdoc}
1158
     */
1159
    public function countContentDrafts(?User $user = null): int
1160
    {
1161
        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...
1162
            return 0;
1163
        }
1164
1165
        return $this->persistenceHandler->contentHandler()->countDraftsForUser(
1166
            $this->resolveUser($user)->getUserId()
1167
        );
1168
    }
1169
1170
    /**
1171
     * Loads drafts for a user.
1172
     *
1173
     * If no user is given the drafts for the authenticated user are returned
1174
     *
1175
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
1176
     *
1177
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Drafts owned by the given user
1178
     *
1179
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1180
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1181
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1182
     */
1183
    public function loadContentDrafts(User $user = null)
1184
    {
1185
        // throw early if user has absolutely no access to versionread
1186
        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...
1187
            throw new UnauthorizedException('content', 'versionread');
1188
        }
1189
1190
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser(
1191
            $this->resolveUser($user)->getUserId()
1192
        );
1193
        $versionInfoList = [];
1194
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1195
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1196
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1197
            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...
1198
                throw new UnauthorizedException('content', 'versionread', ['contentId' => $versionInfo->contentInfo->id]);
1199
            }
1200
1201
            $versionInfoList[] = $versionInfo;
1202
        }
1203
1204
        return $versionInfoList;
1205
    }
1206
1207
    /**
1208
     * {@inheritdoc}
1209
     */
1210
    public function loadContentDraftList(?User $user = null, int $offset = 0, int $limit = -1): ContentDraftList
1211
    {
1212
        $list = new ContentDraftList();
1213
        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...
1214
            return $list;
1215
        }
1216
1217
        $list->totalCount = $this->persistenceHandler->contentHandler()->countDraftsForUser(
1218
            $this->resolveUser($user)->getUserId()
1219
        );
1220
        if ($list->totalCount > 0) {
1221
            $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftListForUser(
1222
                $this->resolveUser($user)->getUserId(),
1223
                $offset,
1224
                $limit
1225
            );
1226
            foreach ($spiVersionInfoList as $spiVersionInfo) {
1227
                $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1228
                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...
1229
                    $list->items[] = new ContentDraftListItem($versionInfo);
1230
                } else {
1231
                    $list->items[] = new UnauthorizedContentDraftListItem(
1232
                        'content',
1233
                        'versionread',
1234
                        ['contentId' => $versionInfo->contentInfo->id]
1235
                    );
1236
                }
1237
            }
1238
        }
1239
1240
        return $list;
1241
    }
1242
1243
    /**
1244
     * Updates the fields of a draft.
1245
     *
1246
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1247
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1248
     *
1249
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1250
     *
1251
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1252
     *                                                                               or if a required field is missing / set to an empty value.
1253
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1254
     *                                                                          or value is set for non-translatable field in language
1255
     *                                                                          other than main.
1256
     *
1257
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1258
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1259
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1260
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1261
     */
1262
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1263
    {
1264
        $contentUpdateStruct = clone $contentUpdateStruct;
1265
1266
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1267
        $content = $this->loadContent(
1268
            $versionInfo->getContentInfo()->id,
1269
            null,
1270
            $versionInfo->versionNo
1271
        );
1272
        if (!$content->versionInfo->isDraft()) {
1273
            throw new BadStateException(
1274
                '$versionInfo',
1275
                'Version is not a draft and can not be updated'
1276
            );
1277
        }
1278
1279
        if (!$this->repository->getPermissionResolver()->canUser(
1280
            'content',
1281
            'edit',
1282
            $content,
1283
            [
1284
                (new Target\Builder\VersionBuilder())
1285
                    ->updateFieldsTo(
1286
                        $contentUpdateStruct->initialLanguageCode,
1287
                        $contentUpdateStruct->fields
1288
                    )
1289
                    ->build(),
1290
            ]
1291
        )) {
1292
            throw new UnauthorizedException('content', 'edit', ['contentId' => $content->id]);
1293
        }
1294
1295
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1296
        if ($contentUpdateStruct->initialLanguageCode === null) {
1297
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1298
        }
1299
1300
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1301
        $contentLanguageHandler = $this->persistenceHandler->contentLanguageHandler();
1302
        foreach ($allLanguageCodes as $languageCode) {
1303
            $contentLanguageHandler->loadByLanguageCode($languageCode);
1304
        }
1305
1306
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1307
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1308
            $content->contentInfo->contentTypeId
1309
        );
1310
        $fields = $this->mapFieldsForUpdate(
1311
            $contentUpdateStruct,
1312
            $contentType,
1313
            $mainLanguageCode
1314
        );
1315
1316
        $fieldValues = [];
1317
        $spiFields = [];
1318
        $allFieldErrors = [];
1319
        $inputRelations = [];
1320
        $locationIdToContentIdMapping = [];
1321
1322
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1323
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1324
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1325
                $fieldDefinition->fieldTypeIdentifier
1326
            );
1327
1328
            foreach ($allLanguageCodes as $languageCode) {
1329
                $isCopied = $isEmpty = $isRetained = false;
1330
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1331
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1332
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1333
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1334
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1335
1336
                if (!$isFieldUpdated && !$isLanguageNew) {
1337
                    $isRetained = true;
1338
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1339
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1340
                    $isCopied = true;
1341
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1342
                } elseif ($isFieldUpdated) {
1343
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1344
                } else {
1345
                    $fieldValue = $fieldDefinition->defaultValue;
1346
                }
1347
1348
                $fieldValue = $fieldType->acceptValue($fieldValue);
1349
1350
                if ($fieldType->isEmptyValue($fieldValue)) {
1351
                    $isEmpty = true;
1352
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1353
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1354
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1355
                            null,
1356
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1357
                            'empty'
1358
                        );
1359
                    }
1360
                } elseif ($isLanguageUpdated) {
1361
                    $fieldErrors = $fieldType->validate(
1362
                        $fieldDefinition,
1363
                        $fieldValue
1364
                    );
1365
                    if (!empty($fieldErrors)) {
1366
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1367
                    }
1368
                }
1369
1370
                if (!empty($allFieldErrors)) {
1371
                    continue;
1372
                }
1373
1374
                $this->relationProcessor->appendFieldRelations(
1375
                    $inputRelations,
1376
                    $locationIdToContentIdMapping,
1377
                    $fieldType,
1378
                    $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...
1379
                    $fieldDefinition->id
1380
                );
1381
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1382
1383
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1384
                    continue;
1385
                }
1386
1387
                $spiFields[] = new SPIField(
1388
                    [
1389
                        'id' => $isLanguageNew ?
1390
                            null :
1391
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1392
                        'fieldDefinitionId' => $fieldDefinition->id,
1393
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1394
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1395
                        'languageCode' => $languageCode,
1396
                        'versionNo' => $versionInfo->versionNo,
1397
                    ]
1398
                );
1399
            }
1400
        }
1401
1402
        if (!empty($allFieldErrors)) {
1403
            throw new ContentFieldValidationException($allFieldErrors);
1404
        }
1405
1406
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1407
            [
1408
                'name' => $this->nameSchemaService->resolveNameSchema(
1409
                    $content,
1410
                    $fieldValues,
1411
                    $allLanguageCodes,
1412
                    $contentType
1413
                ),
1414
                '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...
1415
                'fields' => $spiFields,
1416
                'modificationDate' => time(),
1417
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1418
                    $contentUpdateStruct->initialLanguageCode
1419
                )->id,
1420
            ]
1421
        );
1422
        $existingRelations = $this->loadRelations($versionInfo);
1423
1424
        $this->repository->beginTransaction();
1425
        try {
1426
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1427
                $versionInfo->getContentInfo()->id,
1428
                $versionInfo->versionNo,
1429
                $spiContentUpdateStruct
1430
            );
1431
            $this->relationProcessor->processFieldRelations(
1432
                $inputRelations,
1433
                $spiContent->versionInfo->contentInfo->id,
1434
                $spiContent->versionInfo->versionNo,
1435
                $contentType,
1436
                $existingRelations
1437
            );
1438
            $this->repository->commit();
1439
        } catch (Exception $e) {
1440
            $this->repository->rollback();
1441
            throw $e;
1442
        }
1443
1444
        return $this->domainMapper->buildContentDomainObject(
1445
            $spiContent,
1446
            $contentType
1447
        );
1448
    }
1449
1450
    /**
1451
     * Returns only updated language codes.
1452
     *
1453
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1454
     *
1455
     * @return array
1456
     */
1457
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1458
    {
1459
        $languageCodes = [
1460
            $contentUpdateStruct->initialLanguageCode => true,
1461
        ];
1462
1463
        foreach ($contentUpdateStruct->fields as $field) {
1464
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1465
                continue;
1466
            }
1467
1468
            $languageCodes[$field->languageCode] = true;
1469
        }
1470
1471
        return array_keys($languageCodes);
1472
    }
1473
1474
    /**
1475
     * Returns all language codes used in given $fields.
1476
     *
1477
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1478
     *
1479
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1480
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1481
     *
1482
     * @return array
1483
     */
1484
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1485
    {
1486
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1487
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1488
1489
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1490
        foreach ($updatedLanguageCodes as $languageCode) {
1491
            $languageCodes[$languageCode] = true;
1492
        }
1493
1494
        return array_keys($languageCodes);
1495
    }
1496
1497
    /**
1498
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1499
     *
1500
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1501
     *                                                                          or value is set for non-translatable field in language
1502
     *                                                                          other than main
1503
     *
1504
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1505
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1506
     * @param string $mainLanguageCode
1507
     *
1508
     * @return array
1509
     */
1510
    protected function mapFieldsForUpdate(
1511
        APIContentUpdateStruct $contentUpdateStruct,
1512
        ContentType $contentType,
1513
        $mainLanguageCode
1514
    ) {
1515
        $fields = [];
1516
1517
        foreach ($contentUpdateStruct->fields as $field) {
1518
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1519
1520
            if ($fieldDefinition === null) {
1521
                throw new ContentValidationException(
1522
                    "Field definition '%identifier%' does not exist in given ContentType",
1523
                    ['%identifier%' => $field->fieldDefIdentifier]
1524
                );
1525
            }
1526
1527
            if ($field->languageCode === null) {
1528
                if ($fieldDefinition->isTranslatable) {
1529
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1530
                } else {
1531
                    $languageCode = $mainLanguageCode;
1532
                }
1533
                $field = $this->cloneField($field, ['languageCode' => $languageCode]);
1534
            }
1535
1536
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $mainLanguageCode)) {
1537
                throw new ContentValidationException(
1538
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1539
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1540
                );
1541
            }
1542
1543
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1544
        }
1545
1546
        return $fields;
1547
    }
1548
1549
    /**
1550
     * Publishes a content version.
1551
     *
1552
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1553
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1554
     *
1555
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1556
     * @param string[] $translations
1557
     *
1558
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1559
     *
1560
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1561
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1562
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1563
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1564
     */
1565
    public function publishVersion(APIVersionInfo $versionInfo, array $translations = Language::ALL)
1566
    {
1567
        $content = $this->internalLoadContent(
1568
            $versionInfo->contentInfo->id,
1569
            null,
1570
            $versionInfo->versionNo
1571
        );
1572
1573
        $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...
1574
        if ($content->contentInfo->currentVersionNo !== $versionInfo->versionNo) {
1575
            $fromContent = $this->internalLoadContent(
1576
                $content->contentInfo->id,
1577
                null,
1578
                $content->contentInfo->currentVersionNo
1579
            );
1580
            // should not occur now, might occur in case of un-publish
1581
            if (!$fromContent->contentInfo->isPublished()) {
1582
                $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...
1583
            }
1584
        }
1585
1586
        if (!$this->repository->getPermissionResolver()->canUser(
1587
            'content',
1588
            'publish',
1589
            $content
1590
        )) {
1591
            throw new UnauthorizedException(
1592
                'content', 'publish', ['contentId' => $content->id]
1593
            );
1594
        }
1595
1596
        $this->repository->beginTransaction();
1597
        try {
1598
            $this->copyTranslationsFromPublishedVersion($content->versionInfo, $translations);
1599
            $content = $this->internalPublishVersion($content->getVersionInfo(), null);
1600
            $this->repository->commit();
1601
        } catch (Exception $e) {
1602
            $this->repository->rollback();
1603
            throw $e;
1604
        }
1605
1606
        return $content;
1607
    }
1608
1609
    /**
1610
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1611
     * @param array $translations
1612
     *
1613
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1614
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
1615
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
1616
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1617
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1618
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1619
     */
1620
    protected function copyTranslationsFromPublishedVersion(APIVersionInfo $versionInfo, array $translations = []): void
1621
    {
1622
        $contendId = $versionInfo->contentInfo->id;
1623
1624
        $currentContent = $this->internalLoadContent($contendId);
1625
        $currentVersionInfo = $currentContent->versionInfo;
1626
1627
        // Copying occurs only if:
1628
        // - There is published Version
1629
        // - Published version is older than the currently published one unless specific translations are provided.
1630
        if (!$currentVersionInfo->isPublished() ||
1631
            ($versionInfo->versionNo >= $currentVersionInfo->versionNo && empty($translations))) {
1632
            return;
1633
        }
1634
1635
        if (empty($translations)) {
1636
            $languagesToCopy = array_diff(
1637
                $currentVersionInfo->languageCodes,
1638
                $versionInfo->languageCodes
1639
            );
1640
        } else {
1641
            $languagesToCopy = array_diff(
1642
                $currentVersionInfo->languageCodes,
1643
                $translations
1644
            );
1645
        }
1646
1647
        if (empty($languagesToCopy)) {
1648
            return;
1649
        }
1650
1651
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1652
            $currentVersionInfo->contentInfo->contentTypeId
1653
        );
1654
1655
        // Find only translatable fields to update with selected languages
1656
        $updateStruct = $this->newContentUpdateStruct();
1657
        $updateStruct->initialLanguageCode = $versionInfo->initialLanguageCode;
1658
1659
        foreach ($currentContent->getFields() as $field) {
1660
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1661
1662
            if ($fieldDefinition->isTranslatable && in_array($field->languageCode, $languagesToCopy)) {
1663
                $updateStruct->setField($field->fieldDefIdentifier, $field->value, $field->languageCode);
1664
            }
1665
        }
1666
1667
        $this->updateContent($versionInfo, $updateStruct);
1668
    }
1669
1670
    /**
1671
     * Publishes a content version.
1672
     *
1673
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1674
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1675
     *
1676
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1677
     *
1678
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1679
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1680
     *
1681
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1682
     */
1683
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1684
    {
1685
        if (!$versionInfo->isDraft()) {
1686
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1687
        }
1688
1689
        $currentTime = $this->getUnixTimestamp();
1690
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1691
            $publicationDate = $currentTime;
1692
        }
1693
1694
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1695
        $metadataUpdateStruct->publicationDate = $publicationDate;
1696
        $metadataUpdateStruct->modificationDate = $currentTime;
1697
1698
        $contentId = $versionInfo->getContentInfo()->id;
1699
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1700
            $contentId,
1701
            $versionInfo->versionNo,
1702
            $metadataUpdateStruct
1703
        );
1704
1705
        $content = $this->domainMapper->buildContentDomainObject(
1706
            $spiContent,
1707
            $this->repository->getContentTypeService()->loadContentType(
1708
                $spiContent->versionInfo->contentInfo->contentTypeId
1709
            )
1710
        );
1711
1712
        $this->publishUrlAliasesForContent($content);
1713
1714
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1715
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1716
            $contentId,
1717
            APIVersionInfo::STATUS_ARCHIVED,
1718
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1719
        );
1720
1721
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1722
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1723
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1724
            $archiveVersion = array_shift($archiveList);
1725
            $this->persistenceHandler->contentHandler()->deleteVersion(
1726
                $contentId,
1727
                $archiveVersion->versionNo
1728
            );
1729
        }
1730
1731
        return $content;
1732
    }
1733
1734
    /**
1735
     * @return int
1736
     */
1737
    protected function getUnixTimestamp()
1738
    {
1739
        return time();
1740
    }
1741
1742
    /**
1743
     * Removes the given version.
1744
     *
1745
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1746
     *         published state or is a last version of Content in non draft state
1747
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1748
     *
1749
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1750
     */
1751
    public function deleteVersion(APIVersionInfo $versionInfo)
1752
    {
1753
        if ($versionInfo->isPublished()) {
1754
            throw new BadStateException(
1755
                '$versionInfo',
1756
                'Version is published and can not be removed'
1757
            );
1758
        }
1759
1760
        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...
1761
            throw new UnauthorizedException(
1762
                'content',
1763
                'versionremove',
1764
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1765
            );
1766
        }
1767
1768
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1769
            $versionInfo->contentInfo->id,
1770
            null,
1771
            2
1772
        );
1773
1774
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1775
            throw new BadStateException(
1776
                '$versionInfo',
1777
                'Version is the last version of the Content and can not be removed'
1778
            );
1779
        }
1780
1781
        $this->repository->beginTransaction();
1782
        try {
1783
            $this->persistenceHandler->contentHandler()->deleteVersion(
1784
                $versionInfo->getContentInfo()->id,
1785
                $versionInfo->versionNo
1786
            );
1787
            $this->repository->commit();
1788
        } catch (Exception $e) {
1789
            $this->repository->rollback();
1790
            throw $e;
1791
        }
1792
    }
1793
1794
    /**
1795
     * Loads all versions for the given content.
1796
     *
1797
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1798
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the given status is invalid
1799
     *
1800
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1801
     * @param int|null $status
1802
     *
1803
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1804
     */
1805
    public function loadVersions(ContentInfo $contentInfo, ?int $status = null)
1806
    {
1807
        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...
1808
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1809
        }
1810
1811
        if ($status !== null && !in_array((int)$status, [VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED], true)) {
1812
            throw new InvalidArgumentException(
1813
                'status',
1814
                sprintf(
1815
                    'it can be one of %d (draft), %d (published), %d (archived), %d given',
1816
                    VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED, $status
1817
                ));
1818
        }
1819
1820
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id, $status);
1821
1822
        $versions = [];
1823
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1824
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1825
            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...
1826
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1827
            }
1828
1829
            $versions[] = $versionInfo;
1830
        }
1831
1832
        return $versions;
1833
    }
1834
1835
    /**
1836
     * Copies the content to a new location. If no version is given,
1837
     * all versions are copied, otherwise only the given version.
1838
     *
1839
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1840
     *
1841
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1842
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1843
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1844
     *
1845
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1846
     */
1847
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1848
    {
1849
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1850
            $destinationLocationCreateStruct->parentLocationId
1851
        );
1852
        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...
1853
            throw new UnauthorizedException(
1854
                'content',
1855
                'create',
1856
                [
1857
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1858
                    'sectionId' => $contentInfo->sectionId,
1859
                ]
1860
            );
1861
        }
1862
        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...
1863
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
1864
        }
1865
1866
        $defaultObjectStates = $this->getDefaultObjectStates();
1867
1868
        $this->repository->beginTransaction();
1869
        try {
1870
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1871
                $contentInfo->id,
1872
                $versionInfo ? $versionInfo->versionNo : null,
1873
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1874
            );
1875
1876
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1877
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1878
                $objectStateHandler->setContentState(
1879
                    $spiContent->versionInfo->contentInfo->id,
1880
                    $objectStateGroupId,
1881
                    $objectState->id
1882
                );
1883
            }
1884
1885
            $content = $this->internalPublishVersion(
1886
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1887
                $spiContent->versionInfo->creationDate
1888
            );
1889
1890
            $this->repository->getLocationService()->createLocation(
1891
                $content->getVersionInfo()->getContentInfo(),
1892
                $destinationLocationCreateStruct
1893
            );
1894
            $this->repository->commit();
1895
        } catch (Exception $e) {
1896
            $this->repository->rollback();
1897
            throw $e;
1898
        }
1899
1900
        return $this->internalLoadContent($content->id);
1901
    }
1902
1903
    /**
1904
     * Loads all outgoing relations for the given version.
1905
     *
1906
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1907
     *
1908
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1909
     *
1910
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1911
     */
1912
    public function loadRelations(APIVersionInfo $versionInfo)
1913
    {
1914
        if ($versionInfo->isPublished()) {
1915
            $function = 'read';
1916
        } else {
1917
            $function = 'versionread';
1918
        }
1919
1920
        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...
1921
            throw new UnauthorizedException('content', $function);
1922
        }
1923
1924
        $contentInfo = $versionInfo->getContentInfo();
1925
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1926
            $contentInfo->id,
1927
            $versionInfo->versionNo
1928
        );
1929
1930
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1931
        $relations = [];
1932
        foreach ($spiRelations as $spiRelation) {
1933
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1934
            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...
1935
                continue;
1936
            }
1937
1938
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1939
                $spiRelation,
1940
                $contentInfo,
1941
                $destinationContentInfo
1942
            );
1943
        }
1944
1945
        return $relations;
1946
    }
1947
1948
    /**
1949
     * {@inheritdoc}
1950
     */
1951
    public function countReverseRelations(ContentInfo $contentInfo): int
1952
    {
1953
        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...
1954
            return 0;
1955
        }
1956
1957
        return $this->persistenceHandler->contentHandler()->countReverseRelations(
1958
            $contentInfo->id
1959
        );
1960
    }
1961
1962
    /**
1963
     * Loads all incoming relations for a content object.
1964
     *
1965
     * The relations come only from published versions of the source content objects
1966
     *
1967
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1968
     *
1969
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1970
     *
1971
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1972
     */
1973
    public function loadReverseRelations(ContentInfo $contentInfo)
1974
    {
1975
        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...
1976
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
1977
        }
1978
1979
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1980
            $contentInfo->id
1981
        );
1982
1983
        $returnArray = [];
1984
        foreach ($spiRelations as $spiRelation) {
1985
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1986
            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...
1987
                continue;
1988
            }
1989
1990
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1991
                $spiRelation,
1992
                $sourceContentInfo,
1993
                $contentInfo
1994
            );
1995
        }
1996
1997
        return $returnArray;
1998
    }
1999
2000
    /**
2001
     * {@inheritdoc}
2002
     */
2003
    public function loadReverseRelationList(ContentInfo $contentInfo, int $offset = 0, int $limit = -1): RelationList
2004
    {
2005
        $list = new RelationList();
2006
        if (!$this->repository->getPermissionResolver()->canUser('content', 'reverserelatedlist', $contentInfo)) {
2007
            return $list;
2008
        }
2009
2010
        $list->totalCount = $this->persistenceHandler->contentHandler()->countReverseRelations(
2011
            $contentInfo->id
2012
        );
2013
        if ($list->totalCount > 0) {
2014
            $spiRelationList = $this->persistenceHandler->contentHandler()->loadReverseRelationList(
2015
                $contentInfo->id,
2016
                $offset,
2017
                $limit
2018
            );
2019
            foreach ($spiRelationList as $spiRelation) {
2020
                $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2021
                if ($this->repository->getPermissionResolver()->canUser('content', 'read', $sourceContentInfo)) {
2022
                    $relation = $this->domainMapper->buildRelationDomainObject(
2023
                        $spiRelation,
2024
                        $sourceContentInfo,
2025
                        $contentInfo
2026
                    );
2027
                    $list->items[] = new RelationListItem($relation);
2028
                } else {
2029
                    $list->items[] = new UnauthorizedRelationListItem(
2030
                        'content',
2031
                        'read',
2032
                        ['contentId' => $sourceContentInfo->id]
2033
                    );
2034
                }
2035
            }
2036
        }
2037
2038
        return $list;
2039
    }
2040
2041
    /**
2042
     * Adds a relation of type common.
2043
     *
2044
     * The source of the relation is the content and version
2045
     * referenced by $versionInfo.
2046
     *
2047
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
2048
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2049
     *
2050
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2051
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
2052
     *
2053
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
2054
     */
2055
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2056
    {
2057
        $sourceVersion = $this->loadVersionInfoById(
2058
            $sourceVersion->contentInfo->id,
2059
            $sourceVersion->versionNo
2060
        );
2061
2062
        if (!$sourceVersion->isDraft()) {
2063
            throw new BadStateException(
2064
                '$sourceVersion',
2065
                'Relations of type common can only be added to versions of status draft'
2066
            );
2067
        }
2068
2069
        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...
2070
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2071
        }
2072
2073
        $sourceContentInfo = $sourceVersion->getContentInfo();
2074
2075
        $this->repository->beginTransaction();
2076
        try {
2077
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
2078
                new SPIRelationCreateStruct(
2079
                    [
2080
                        'sourceContentId' => $sourceContentInfo->id,
2081
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
2082
                        'sourceFieldDefinitionId' => null,
2083
                        'destinationContentId' => $destinationContent->id,
2084
                        'type' => APIRelation::COMMON,
2085
                    ]
2086
                )
2087
            );
2088
            $this->repository->commit();
2089
        } catch (Exception $e) {
2090
            $this->repository->rollback();
2091
            throw $e;
2092
        }
2093
2094
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
2095
    }
2096
2097
    /**
2098
     * Removes a relation of type COMMON from a draft.
2099
     *
2100
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
2101
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2102
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
2103
     *
2104
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2105
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
2106
     */
2107
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2108
    {
2109
        $sourceVersion = $this->loadVersionInfoById(
2110
            $sourceVersion->contentInfo->id,
2111
            $sourceVersion->versionNo
2112
        );
2113
2114
        if (!$sourceVersion->isDraft()) {
2115
            throw new BadStateException(
2116
                '$sourceVersion',
2117
                'Relations of type common can only be removed from versions of status draft'
2118
            );
2119
        }
2120
2121
        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...
2122
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2123
        }
2124
2125
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2126
            $sourceVersion->getContentInfo()->id,
2127
            $sourceVersion->versionNo,
2128
            APIRelation::COMMON
2129
        );
2130
2131
        if (empty($spiRelations)) {
2132
            throw new InvalidArgumentException(
2133
                '$sourceVersion',
2134
                'There are no relations of type COMMON for the given destination'
2135
            );
2136
        }
2137
2138
        // there should be only one relation of type COMMON for each destination,
2139
        // but in case there were ever more then one, we will remove them all
2140
        // @todo: alternatively, throw BadStateException?
2141
        $this->repository->beginTransaction();
2142
        try {
2143
            foreach ($spiRelations as $spiRelation) {
2144
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2145
                    $this->persistenceHandler->contentHandler()->removeRelation(
2146
                        $spiRelation->id,
2147
                        APIRelation::COMMON
2148
                    );
2149
                }
2150
            }
2151
            $this->repository->commit();
2152
        } catch (Exception $e) {
2153
            $this->repository->rollback();
2154
            throw $e;
2155
        }
2156
    }
2157
2158
    /**
2159
     * {@inheritdoc}
2160
     */
2161
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2162
    {
2163
        @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...
2164
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2165
            E_USER_DEPRECATED
2166
        );
2167
        $this->deleteTranslation($contentInfo, $languageCode);
2168
    }
2169
2170
    /**
2171
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2172
     *
2173
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2174
     *
2175
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2176
     *         is the Main Translation of a Content Item.
2177
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2178
     *         to delete the content (in one of the locations of the given Content Item).
2179
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2180
     *         is invalid for the given content.
2181
     *
2182
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2183
     * @param string $languageCode
2184
     *
2185
     * @since 6.13
2186
     */
2187
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2188
    {
2189
        if ($contentInfo->mainLanguageCode === $languageCode) {
2190
            throw new BadStateException(
2191
                '$languageCode',
2192
                'Specified translation is the main translation of the Content Object'
2193
            );
2194
        }
2195
2196
        $translationWasFound = false;
2197
        $this->repository->beginTransaction();
2198
        try {
2199
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2200
                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...
2201
                    throw new UnauthorizedException(
2202
                        'content',
2203
                        'remove',
2204
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2205
                    );
2206
                }
2207
2208
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2209
                    continue;
2210
                }
2211
2212
                $translationWasFound = true;
2213
2214
                // If the translation is the version's only one, delete the version
2215
                if (count($versionInfo->languageCodes) < 2) {
2216
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2217
                        $versionInfo->getContentInfo()->id,
2218
                        $versionInfo->versionNo
2219
                    );
2220
                }
2221
            }
2222
2223
            if (!$translationWasFound) {
2224
                throw new InvalidArgumentException(
2225
                    '$languageCode',
2226
                    sprintf(
2227
                        '%s does not exist in the Content item(id=%d)',
2228
                        $languageCode,
2229
                        $contentInfo->id
2230
                    )
2231
                );
2232
            }
2233
2234
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2235
                $contentInfo->id,
2236
                $languageCode
2237
            );
2238
            $locationIds = array_map(
2239
                function (Location $location) {
2240
                    return $location->id;
2241
                },
2242
                $this->repository->getLocationService()->loadLocations($contentInfo)
2243
            );
2244
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2245
                $locationIds,
2246
                $languageCode
2247
            );
2248
            $this->repository->commit();
2249
        } catch (InvalidArgumentException $e) {
2250
            $this->repository->rollback();
2251
            throw $e;
2252
        } catch (BadStateException $e) {
2253
            $this->repository->rollback();
2254
            throw $e;
2255
        } catch (UnauthorizedException $e) {
2256
            $this->repository->rollback();
2257
            throw $e;
2258
        } catch (Exception $e) {
2259
            $this->repository->rollback();
2260
            // cover generic unexpected exception to fulfill API promise on @throws
2261
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2262
        }
2263
    }
2264
2265
    /**
2266
     * Delete specified Translation from a Content Draft.
2267
     *
2268
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2269
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2270
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2271
     *         to edit the Content (in one of the locations of the given Content Object).
2272
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2273
     *         is invalid for the given Draft.
2274
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2275
     *
2276
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2277
     * @param string $languageCode Language code of the Translation to be removed
2278
     *
2279
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2280
     *
2281
     * @since 6.12
2282
     */
2283
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2284
    {
2285
        if (!$versionInfo->isDraft()) {
2286
            throw new BadStateException(
2287
                '$versionInfo',
2288
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2289
            );
2290
        }
2291
2292
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2293
            throw new BadStateException(
2294
                '$languageCode',
2295
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2296
            );
2297
        }
2298
2299
        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...
2300
            throw new UnauthorizedException(
2301
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2302
            );
2303
        }
2304
2305
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2306
            throw new InvalidArgumentException(
2307
                '$languageCode',
2308
                sprintf(
2309
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2310
                    $versionInfo->contentInfo->id,
2311
                    $versionInfo->versionNo,
2312
                    $languageCode
2313
                )
2314
            );
2315
        }
2316
2317
        if (count($versionInfo->languageCodes) === 1) {
2318
            throw new BadStateException(
2319
                '$languageCode',
2320
                'Specified Translation is the only one Content Object Version has'
2321
            );
2322
        }
2323
2324
        $this->repository->beginTransaction();
2325
        try {
2326
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2327
                $versionInfo->contentInfo->id,
2328
                $versionInfo->versionNo,
2329
                $languageCode
2330
            );
2331
            $this->repository->commit();
2332
2333
            return $this->domainMapper->buildContentDomainObject(
2334
                $spiContent,
2335
                $this->repository->getContentTypeService()->loadContentType(
2336
                    $spiContent->versionInfo->contentInfo->contentTypeId
2337
                )
2338
            );
2339
        } catch (APINotFoundException $e) {
2340
            // avoid wrapping expected NotFoundException in BadStateException handled below
2341
            $this->repository->rollback();
2342
            throw $e;
2343
        } catch (Exception $e) {
2344
            $this->repository->rollback();
2345
            // cover generic unexpected exception to fulfill API promise on @throws
2346
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2347
        }
2348
    }
2349
2350
    /**
2351
     * Hides Content by making all the Locations appear hidden.
2352
     * It does not persist hidden state on Location object itself.
2353
     *
2354
     * Content hidden by this API can be revealed by revealContent API.
2355
     *
2356
     * @see revealContent
2357
     *
2358
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2359
     */
2360
    public function hideContent(ContentInfo $contentInfo): void
2361
    {
2362
        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...
2363
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2364
        }
2365
2366
        $this->repository->beginTransaction();
2367
        try {
2368
            $this->persistenceHandler->contentHandler()->updateMetadata(
2369
                $contentInfo->id,
2370
                new SPIMetadataUpdateStruct([
2371
                    'isHidden' => true,
2372
                ])
2373
            );
2374
            $locationHandler = $this->persistenceHandler->locationHandler();
2375
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2376
            foreach ($childLocations as $childLocation) {
2377
                $locationHandler->setInvisible($childLocation->id);
2378
            }
2379
            $this->repository->commit();
2380
        } catch (Exception $e) {
2381
            $this->repository->rollback();
2382
            throw $e;
2383
        }
2384
    }
2385
2386
    /**
2387
     * Reveals Content hidden by hideContent API.
2388
     * Locations which were hidden before hiding Content will remain hidden.
2389
     *
2390
     * @see hideContent
2391
     *
2392
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2393
     */
2394
    public function revealContent(ContentInfo $contentInfo): void
2395
    {
2396
        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...
2397
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2398
        }
2399
2400
        $this->repository->beginTransaction();
2401
        try {
2402
            $this->persistenceHandler->contentHandler()->updateMetadata(
2403
                $contentInfo->id,
2404
                new SPIMetadataUpdateStruct([
2405
                    'isHidden' => false,
2406
                ])
2407
            );
2408
            $locationHandler = $this->persistenceHandler->locationHandler();
2409
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2410
            foreach ($childLocations as $childLocation) {
2411
                $locationHandler->setVisible($childLocation->id);
2412
            }
2413
            $this->repository->commit();
2414
        } catch (Exception $e) {
2415
            $this->repository->rollback();
2416
            throw $e;
2417
        }
2418
    }
2419
2420
    /**
2421
     * Instantiates a new content create struct object.
2422
     *
2423
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2424
     *
2425
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2426
     * @param string $mainLanguageCode
2427
     *
2428
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2429
     */
2430
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2431
    {
2432
        return new ContentCreateStruct(
2433
            [
2434
                'contentType' => $contentType,
2435
                'mainLanguageCode' => $mainLanguageCode,
2436
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2437
            ]
2438
        );
2439
    }
2440
2441
    /**
2442
     * Instantiates a new content meta data update struct.
2443
     *
2444
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2445
     */
2446
    public function newContentMetadataUpdateStruct()
2447
    {
2448
        return new ContentMetadataUpdateStruct();
2449
    }
2450
2451
    /**
2452
     * Instantiates a new content update struct.
2453
     *
2454
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2455
     */
2456
    public function newContentUpdateStruct()
2457
    {
2458
        return new ContentUpdateStruct();
2459
    }
2460
2461
    /**
2462
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2463
     *
2464
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2465
     */
2466
    private function resolveUser(?User $user): UserReference
2467
    {
2468
        if ($user === null) {
2469
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2470
        }
2471
2472
        return $user;
2473
    }
2474
}
2475