Completed
Push — apply-code-style ( 1a43bf...37cc85 )
by
unknown
45:48
created

ContentService::newContentCreateStruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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