Completed
Push — publish_version_cleanup ( a9601f )
by
unknown
19:07
created

ContentService::publishVersion()   A

Complexity

Conditions 3
Paths 5

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return array();
}

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

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

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