Completed
Push — EZP-30969-fetch-reverse-relati... ( da6afe...4545bb )
by
unknown
17:04 queued 45s
created

ContentService::revealContent()   A

Complexity

Conditions 4
Paths 10

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return array();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
1379
                    $fieldDefinition->id
1380
                );
1381
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1382
1383
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1384
                    continue;
1385
                }
1386
1387
                $spiFields[] = new SPIField(
1388
                    [
1389
                        'id' => $isLanguageNew ?
1390
                            null :
1391
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1392
                        'fieldDefinitionId' => $fieldDefinition->id,
1393
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1394
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1395
                        'languageCode' => $languageCode,
1396
                        'versionNo' => $versionInfo->versionNo,
1397
                    ]
1398
                );
1399
            }
1400
        }
1401
1402
        if (!empty($allFieldErrors)) {
1403
            throw new ContentFieldValidationException($allFieldErrors);
1404
        }
1405
1406
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1407
            [
1408
                'name' => $this->nameSchemaService->resolveNameSchema(
1409
                    $content,
1410
                    $fieldValues,
1411
                    $allLanguageCodes,
1412
                    $contentType
1413
                ),
1414
                'creatorId' => $contentUpdateStruct->creatorId ?: $this->repository->getCurrentUserReference()->getUserId(),
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Reposito...tCurrentUserReference() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::getCurrentUserReference() instead. Get current user reference.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
1935
                continue;
1936
            }
1937
1938
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1939
                $spiRelation,
1940
                $contentInfo,
1941
                $destinationContentInfo
1942
            );
1943
        }
1944
1945
        return $relations;
1946
    }
1947
1948
    /**
1949
     * Loads all incoming relations for a content object.
1950
     *
1951
     * The relations come only from published versions of the source content objects
1952
     *
1953
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1954
     *
1955
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1956
     *
1957
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1958
     */
1959
    public function loadReverseRelations(ContentInfo $contentInfo)
1960
    {
1961
        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...
1962
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
1963
        }
1964
1965
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1966
            $contentInfo->id
1967
        );
1968
1969
        $returnArray = [];
1970
        foreach ($spiRelations as $spiRelation) {
1971
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1972
            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...
1973
                continue;
1974
            }
1975
1976
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1977
                $spiRelation,
1978
                $sourceContentInfo,
1979
                $contentInfo
1980
            );
1981
        }
1982
1983
        return $returnArray;
1984
    }
1985
1986
    /**
1987
     * {@inheritdoc}
1988
     */
1989
    public function loadReverseRelationList(ContentInfo $contentInfo, int $offset = 0, int $limit = -1): RelationList
1990
    {
1991
        $list = new RelationList();
1992
        if (!$this->repository->getPermissionResolver()->canUser('content', 'reverserelatedlist', $contentInfo)) {
1993
            return $list;
1994
        }
1995
1996
        $list->totalCount = $this->persistenceHandler->contentHandler()->countReverseRelations(
0 ignored issues
show
Bug introduced by
The method countReverseRelations() does not seem to exist on object<eZ\Publish\SPI\Pe...stence\Content\Handler>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1997
            $contentInfo->id
1998
        );
1999
        if ($list->totalCount > 0) {
2000
            $spiRelationList = $this->persistenceHandler->contentHandler()->loadReverseRelationList(
2001
                $contentInfo->id,
2002
                $offset,
2003
                $limit
2004
            );
2005
            foreach ($spiRelationList as $spiRelation) {
2006
                $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2007
                if ($this->repository->getPermissionResolver()->canUser('content', 'read', $sourceContentInfo)) {
2008
                    $relation = $this->domainMapper->buildRelationDomainObject(
2009
                        $spiRelation,
2010
                        $sourceContentInfo,
2011
                        $contentInfo
2012
                    );
2013
                    $list->items[] = new RelationListItem($relation);
2014
                } else {
2015
                    $list->items[] = new UnauthorizedRelationListItem(
2016
                        'content',
2017
                        'read',
2018
                        ['contentId' => $sourceContentInfo->id]
2019
                    );
2020
                }
2021
            }
2022
        }
2023
2024
        return $list;
2025
    }
2026
2027
    /**
2028
     * Adds a relation of type common.
2029
     *
2030
     * The source of the relation is the content and version
2031
     * referenced by $versionInfo.
2032
     *
2033
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
2034
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2035
     *
2036
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2037
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
2038
     *
2039
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
2040
     */
2041
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2042
    {
2043
        $sourceVersion = $this->loadVersionInfoById(
2044
            $sourceVersion->contentInfo->id,
2045
            $sourceVersion->versionNo
2046
        );
2047
2048
        if (!$sourceVersion->isDraft()) {
2049
            throw new BadStateException(
2050
                '$sourceVersion',
2051
                'Relations of type common can only be added to versions of status draft'
2052
            );
2053
        }
2054
2055
        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...
2056
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2057
        }
2058
2059
        $sourceContentInfo = $sourceVersion->getContentInfo();
2060
2061
        $this->repository->beginTransaction();
2062
        try {
2063
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
2064
                new SPIRelationCreateStruct(
2065
                    [
2066
                        'sourceContentId' => $sourceContentInfo->id,
2067
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
2068
                        'sourceFieldDefinitionId' => null,
2069
                        'destinationContentId' => $destinationContent->id,
2070
                        'type' => APIRelation::COMMON,
2071
                    ]
2072
                )
2073
            );
2074
            $this->repository->commit();
2075
        } catch (Exception $e) {
2076
            $this->repository->rollback();
2077
            throw $e;
2078
        }
2079
2080
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
2081
    }
2082
2083
    /**
2084
     * Removes a relation of type COMMON from a draft.
2085
     *
2086
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
2087
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2088
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
2089
     *
2090
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2091
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
2092
     */
2093
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2094
    {
2095
        $sourceVersion = $this->loadVersionInfoById(
2096
            $sourceVersion->contentInfo->id,
2097
            $sourceVersion->versionNo
2098
        );
2099
2100
        if (!$sourceVersion->isDraft()) {
2101
            throw new BadStateException(
2102
                '$sourceVersion',
2103
                'Relations of type common can only be removed from versions of status draft'
2104
            );
2105
        }
2106
2107
        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...
2108
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2109
        }
2110
2111
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2112
            $sourceVersion->getContentInfo()->id,
2113
            $sourceVersion->versionNo,
2114
            APIRelation::COMMON
2115
        );
2116
2117
        if (empty($spiRelations)) {
2118
            throw new InvalidArgumentException(
2119
                '$sourceVersion',
2120
                'There are no relations of type COMMON for the given destination'
2121
            );
2122
        }
2123
2124
        // there should be only one relation of type COMMON for each destination,
2125
        // but in case there were ever more then one, we will remove them all
2126
        // @todo: alternatively, throw BadStateException?
2127
        $this->repository->beginTransaction();
2128
        try {
2129
            foreach ($spiRelations as $spiRelation) {
2130
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2131
                    $this->persistenceHandler->contentHandler()->removeRelation(
2132
                        $spiRelation->id,
2133
                        APIRelation::COMMON
2134
                    );
2135
                }
2136
            }
2137
            $this->repository->commit();
2138
        } catch (Exception $e) {
2139
            $this->repository->rollback();
2140
            throw $e;
2141
        }
2142
    }
2143
2144
    /**
2145
     * {@inheritdoc}
2146
     */
2147
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2148
    {
2149
        @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...
2150
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2151
            E_USER_DEPRECATED
2152
        );
2153
        $this->deleteTranslation($contentInfo, $languageCode);
2154
    }
2155
2156
    /**
2157
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2158
     *
2159
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2160
     *
2161
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2162
     *         is the Main Translation of a Content Item.
2163
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2164
     *         to delete the content (in one of the locations of the given Content Item).
2165
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2166
     *         is invalid for the given content.
2167
     *
2168
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2169
     * @param string $languageCode
2170
     *
2171
     * @since 6.13
2172
     */
2173
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2174
    {
2175
        if ($contentInfo->mainLanguageCode === $languageCode) {
2176
            throw new BadStateException(
2177
                '$languageCode',
2178
                'Specified translation is the main translation of the Content Object'
2179
            );
2180
        }
2181
2182
        $translationWasFound = false;
2183
        $this->repository->beginTransaction();
2184
        try {
2185
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2186
                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...
2187
                    throw new UnauthorizedException(
2188
                        'content',
2189
                        'remove',
2190
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2191
                    );
2192
                }
2193
2194
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2195
                    continue;
2196
                }
2197
2198
                $translationWasFound = true;
2199
2200
                // If the translation is the version's only one, delete the version
2201
                if (count($versionInfo->languageCodes) < 2) {
2202
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2203
                        $versionInfo->getContentInfo()->id,
2204
                        $versionInfo->versionNo
2205
                    );
2206
                }
2207
            }
2208
2209
            if (!$translationWasFound) {
2210
                throw new InvalidArgumentException(
2211
                    '$languageCode',
2212
                    sprintf(
2213
                        '%s does not exist in the Content item(id=%d)',
2214
                        $languageCode,
2215
                        $contentInfo->id
2216
                    )
2217
                );
2218
            }
2219
2220
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2221
                $contentInfo->id,
2222
                $languageCode
2223
            );
2224
            $locationIds = array_map(
2225
                function (Location $location) {
2226
                    return $location->id;
2227
                },
2228
                $this->repository->getLocationService()->loadLocations($contentInfo)
2229
            );
2230
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2231
                $locationIds,
2232
                $languageCode
2233
            );
2234
            $this->repository->commit();
2235
        } catch (InvalidArgumentException $e) {
2236
            $this->repository->rollback();
2237
            throw $e;
2238
        } catch (BadStateException $e) {
2239
            $this->repository->rollback();
2240
            throw $e;
2241
        } catch (UnauthorizedException $e) {
2242
            $this->repository->rollback();
2243
            throw $e;
2244
        } catch (Exception $e) {
2245
            $this->repository->rollback();
2246
            // cover generic unexpected exception to fulfill API promise on @throws
2247
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2248
        }
2249
    }
2250
2251
    /**
2252
     * Delete specified Translation from a Content Draft.
2253
     *
2254
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2255
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2256
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2257
     *         to edit the Content (in one of the locations of the given Content Object).
2258
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2259
     *         is invalid for the given Draft.
2260
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2261
     *
2262
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2263
     * @param string $languageCode Language code of the Translation to be removed
2264
     *
2265
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2266
     *
2267
     * @since 6.12
2268
     */
2269
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2270
    {
2271
        if (!$versionInfo->isDraft()) {
2272
            throw new BadStateException(
2273
                '$versionInfo',
2274
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2275
            );
2276
        }
2277
2278
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2279
            throw new BadStateException(
2280
                '$languageCode',
2281
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2282
            );
2283
        }
2284
2285
        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...
2286
            throw new UnauthorizedException(
2287
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2288
            );
2289
        }
2290
2291
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2292
            throw new InvalidArgumentException(
2293
                '$languageCode',
2294
                sprintf(
2295
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2296
                    $versionInfo->contentInfo->id,
2297
                    $versionInfo->versionNo,
2298
                    $languageCode
2299
                )
2300
            );
2301
        }
2302
2303
        if (count($versionInfo->languageCodes) === 1) {
2304
            throw new BadStateException(
2305
                '$languageCode',
2306
                'Specified Translation is the only one Content Object Version has'
2307
            );
2308
        }
2309
2310
        $this->repository->beginTransaction();
2311
        try {
2312
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2313
                $versionInfo->contentInfo->id,
2314
                $versionInfo->versionNo,
2315
                $languageCode
2316
            );
2317
            $this->repository->commit();
2318
2319
            return $this->domainMapper->buildContentDomainObject(
2320
                $spiContent,
2321
                $this->repository->getContentTypeService()->loadContentType(
2322
                    $spiContent->versionInfo->contentInfo->contentTypeId
2323
                )
2324
            );
2325
        } catch (APINotFoundException $e) {
2326
            // avoid wrapping expected NotFoundException in BadStateException handled below
2327
            $this->repository->rollback();
2328
            throw $e;
2329
        } catch (Exception $e) {
2330
            $this->repository->rollback();
2331
            // cover generic unexpected exception to fulfill API promise on @throws
2332
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2333
        }
2334
    }
2335
2336
    /**
2337
     * Hides Content by making all the Locations appear hidden.
2338
     * It does not persist hidden state on Location object itself.
2339
     *
2340
     * Content hidden by this API can be revealed by revealContent API.
2341
     *
2342
     * @see revealContent
2343
     *
2344
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2345
     */
2346
    public function hideContent(ContentInfo $contentInfo): void
2347
    {
2348
        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...
2349
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2350
        }
2351
2352
        $this->repository->beginTransaction();
2353
        try {
2354
            $this->persistenceHandler->contentHandler()->updateMetadata(
2355
                $contentInfo->id,
2356
                new SPIMetadataUpdateStruct([
2357
                    'isHidden' => true,
2358
                ])
2359
            );
2360
            $locationHandler = $this->persistenceHandler->locationHandler();
2361
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2362
            foreach ($childLocations as $childLocation) {
2363
                $locationHandler->setInvisible($childLocation->id);
2364
            }
2365
            $this->repository->commit();
2366
        } catch (Exception $e) {
2367
            $this->repository->rollback();
2368
            throw $e;
2369
        }
2370
    }
2371
2372
    /**
2373
     * Reveals Content hidden by hideContent API.
2374
     * Locations which were hidden before hiding Content will remain hidden.
2375
     *
2376
     * @see hideContent
2377
     *
2378
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2379
     */
2380
    public function revealContent(ContentInfo $contentInfo): void
2381
    {
2382
        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...
2383
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2384
        }
2385
2386
        $this->repository->beginTransaction();
2387
        try {
2388
            $this->persistenceHandler->contentHandler()->updateMetadata(
2389
                $contentInfo->id,
2390
                new SPIMetadataUpdateStruct([
2391
                    'isHidden' => false,
2392
                ])
2393
            );
2394
            $locationHandler = $this->persistenceHandler->locationHandler();
2395
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2396
            foreach ($childLocations as $childLocation) {
2397
                $locationHandler->setVisible($childLocation->id);
2398
            }
2399
            $this->repository->commit();
2400
        } catch (Exception $e) {
2401
            $this->repository->rollback();
2402
            throw $e;
2403
        }
2404
    }
2405
2406
    /**
2407
     * Instantiates a new content create struct object.
2408
     *
2409
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2410
     *
2411
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2412
     * @param string $mainLanguageCode
2413
     *
2414
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2415
     */
2416
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2417
    {
2418
        return new ContentCreateStruct(
2419
            [
2420
                'contentType' => $contentType,
2421
                'mainLanguageCode' => $mainLanguageCode,
2422
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2423
            ]
2424
        );
2425
    }
2426
2427
    /**
2428
     * Instantiates a new content meta data update struct.
2429
     *
2430
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2431
     */
2432
    public function newContentMetadataUpdateStruct()
2433
    {
2434
        return new ContentMetadataUpdateStruct();
2435
    }
2436
2437
    /**
2438
     * Instantiates a new content update struct.
2439
     *
2440
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2441
     */
2442
    public function newContentUpdateStruct()
2443
    {
2444
        return new ContentUpdateStruct();
2445
    }
2446
2447
    /**
2448
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2449
     *
2450
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2451
     */
2452
    private function resolveUser(?User $user): UserReference
2453
    {
2454
        if ($user === null) {
2455
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2456
        }
2457
2458
        return $user;
2459
    }
2460
}
2461