Completed
Push — 7.5 ( 302b83...d9c892 )
by
unknown
44:21 queued 26:35
created

ContentService::publishVersion()   A

Complexity

Conditions 4
Paths 10

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

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