Completed
Push — EZP-31681 ( a8ed57...46e608 )
by
unknown
18:20
created

ContentService::isHashEqual()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return array();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
1041
            throw new UnauthorizedException('content', 'remove', ['contentId' => $contentInfo->id]);
1042
        }
1043
1044
        $affectedLocations = [];
1045
        $this->repository->beginTransaction();
1046
        try {
1047
            // Load Locations first as deleting Content also deletes belonging Locations
1048
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1049
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1050
            $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
1051
            foreach ($spiLocations as $spiLocation) {
1052
                $urlAliasHandler->locationDeleted($spiLocation->id);
1053
                $affectedLocations[] = $spiLocation->id;
1054
            }
1055
            $this->repository->commit();
1056
        } catch (Exception $e) {
1057
            $this->repository->rollback();
1058
            throw $e;
1059
        }
1060
1061
        return $affectedLocations;
1062
    }
1063
1064
    /**
1065
     * Creates a draft from a published or archived version.
1066
     *
1067
     * If no version is given, the current published version is used.
1068
     *
1069
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1070
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1071
     * @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
1072
     * @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.
1073
     *
1074
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1075
     *
1076
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
1077
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the current-user is not allowed to create the draft
1078
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1079
     */
1080
    public function createContentDraft(
1081
        ContentInfo $contentInfo,
1082
        APIVersionInfo $versionInfo = null,
1083
        User $creator = null,
1084
        ?Language $language = null
1085
    ) {
1086
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1087
1088
        if ($versionInfo !== null) {
1089
            // Check that given $contentInfo and $versionInfo belong to the same content
1090
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1091
                throw new InvalidArgumentException(
1092
                    '$versionInfo',
1093
                    'VersionInfo does not belong to the same content as given ContentInfo'
1094
                );
1095
            }
1096
1097
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1098
1099
            switch ($versionInfo->status) {
1100
                case VersionInfo::STATUS_PUBLISHED:
1101
                case VersionInfo::STATUS_ARCHIVED:
1102
                    break;
1103
1104
                default:
1105
                    // @todo: throw an exception here, to be defined
1106
                    throw new BadStateException(
1107
                        '$versionInfo',
1108
                        'Draft can not be created from a draft version'
1109
                    );
1110
            }
1111
1112
            $versionNo = $versionInfo->versionNo;
1113
        } elseif ($contentInfo->published) {
1114
            $versionNo = $contentInfo->currentVersionNo;
1115
        } else {
1116
            // @todo: throw an exception here, to be defined
1117
            throw new BadStateException(
1118
                '$contentInfo',
1119
                'Content is not published, draft can be created only from published or archived version'
1120
            );
1121
        }
1122
1123
        if ($creator === null) {
1124
            $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...
1125
        }
1126
1127
        $fallbackLanguageCode = $versionInfo->initialLanguageCode ?? $contentInfo->mainLanguageCode;
1128
        $languageCode = $language->languageCode ?? $fallbackLanguageCode;
1129
1130
        if (!$this->repository->getPermissionResolver()->canUser(
1131
            'content',
1132
            'edit',
1133
            $contentInfo,
1134
            [
1135
                (new Target\Builder\VersionBuilder())
1136
                    ->changeStatusTo(APIVersionInfo::STATUS_DRAFT)
1137
                    ->build(),
1138
            ]
1139
        )) {
1140
            throw new UnauthorizedException(
1141
                'content',
1142
                'edit',
1143
                ['contentId' => $contentInfo->id]
1144
            );
1145
        }
1146
1147
        $this->repository->beginTransaction();
1148
        try {
1149
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1150
                $contentInfo->id,
1151
                $versionNo,
1152
                $creator->getUserId(),
1153
                $languageCode
1154
            );
1155
            $this->repository->commit();
1156
        } catch (Exception $e) {
1157
            $this->repository->rollback();
1158
            throw $e;
1159
        }
1160
1161
        return $this->domainMapper->buildContentDomainObject(
1162
            $spiContent,
1163
            $this->repository->getContentTypeService()->loadContentType(
1164
                $spiContent->versionInfo->contentInfo->contentTypeId
1165
            )
1166
        );
1167
    }
1168
1169
    /**
1170
     * {@inheritdoc}
1171
     */
1172
    public function countContentDrafts(?User $user = null): int
1173
    {
1174
        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...
1175
            return 0;
1176
        }
1177
1178
        return $this->persistenceHandler->contentHandler()->countDraftsForUser(
1179
            $this->resolveUser($user)->getUserId()
1180
        );
1181
    }
1182
1183
    /**
1184
     * Loads drafts for a user.
1185
     *
1186
     * If no user is given the drafts for the authenticated user are returned
1187
     *
1188
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
1189
     *
1190
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Drafts owned by the given user
1191
     *
1192
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1193
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1194
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1195
     */
1196
    public function loadContentDrafts(User $user = null)
1197
    {
1198
        // throw early if user has absolutely no access to versionread
1199
        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...
1200
            throw new UnauthorizedException('content', 'versionread');
1201
        }
1202
1203
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser(
1204
            $this->resolveUser($user)->getUserId()
1205
        );
1206
        $versionInfoList = [];
1207
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1208
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1209
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1210
            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...
1211
                throw new UnauthorizedException('content', 'versionread', ['contentId' => $versionInfo->contentInfo->id]);
1212
            }
1213
1214
            $versionInfoList[] = $versionInfo;
1215
        }
1216
1217
        return $versionInfoList;
1218
    }
1219
1220
    /**
1221
     * {@inheritdoc}
1222
     */
1223
    public function loadContentDraftList(?User $user = null, int $offset = 0, int $limit = -1): ContentDraftList
1224
    {
1225
        $list = new ContentDraftList();
1226
        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...
1227
            return $list;
1228
        }
1229
1230
        $list->totalCount = $this->persistenceHandler->contentHandler()->countDraftsForUser(
1231
            $this->resolveUser($user)->getUserId()
1232
        );
1233
        if ($list->totalCount > 0) {
1234
            $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftListForUser(
1235
                $this->resolveUser($user)->getUserId(),
1236
                $offset,
1237
                $limit
1238
            );
1239
            foreach ($spiVersionInfoList as $spiVersionInfo) {
1240
                $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1241
                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...
1242
                    $list->items[] = new ContentDraftListItem($versionInfo);
1243
                } else {
1244
                    $list->items[] = new UnauthorizedContentDraftListItem(
1245
                        'content',
1246
                        'versionread',
1247
                        ['contentId' => $versionInfo->contentInfo->id]
1248
                    );
1249
                }
1250
            }
1251
        }
1252
1253
        return $list;
1254
    }
1255
1256
    /**
1257
     * Updates the fields of a draft.
1258
     *
1259
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1260
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1261
     *
1262
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1263
     *
1264
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1265
     *                                                                               or if a required field is missing / set to an empty value.
1266
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1267
     *                                                                          or value is set for non-translatable field in language
1268
     *                                                                          other than main.
1269
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1270
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1271
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1272
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1273
     */
1274
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1275
    {
1276
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1277
        $content = $this->loadContent(
1278
            $versionInfo->getContentInfo()->id,
1279
            null,
1280
            $versionInfo->versionNo
1281
        );
1282
1283
        if (!$this->repository->getPermissionResolver()->canUser(
1284
            'content',
1285
            'edit',
1286
            $content,
1287
            [
1288
                (new Target\Builder\VersionBuilder())
1289
                    ->updateFieldsTo(
1290
                        $contentUpdateStruct->initialLanguageCode,
1291
                        $contentUpdateStruct->fields
1292
                    )
1293
                    ->build(),
1294
            ]
1295
        )) {
1296
            throw new UnauthorizedException('content', 'edit', ['contentId' => $content->id]);
1297
        }
1298
1299
        return $this->internalUpdateContent($versionInfo, $contentUpdateStruct);
1300
    }
1301
1302
    /**
1303
     * Updates the fields of a draft without checking the permissions.
1304
     *
1305
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1306
     *                                                                               or if a required field is missing / set to an empty value.
1307
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1308
     *                                                                          or value is set for non-translatable field in language
1309
     *                                                                          other than main.
1310
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1311
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1312
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1313
     */
1314
    protected function internalUpdateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct): Content
1315
    {
1316
        $contentUpdateStruct = clone $contentUpdateStruct;
1317
1318
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1319
        $content = $this->internalLoadContent(
1320
            $versionInfo->getContentInfo()->id,
1321
            null,
1322
            $versionInfo->versionNo
1323
        );
1324
        if (!$content->versionInfo->isDraft()) {
1325
            throw new BadStateException(
1326
                '$versionInfo',
1327
                'Version is not a draft and can not be updated'
1328
            );
1329
        }
1330
1331
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1332
        if ($contentUpdateStruct->initialLanguageCode === null) {
1333
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1334
        }
1335
1336
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1337
        $contentLanguageHandler = $this->persistenceHandler->contentLanguageHandler();
1338
        foreach ($allLanguageCodes as $languageCode) {
1339
            $contentLanguageHandler->loadByLanguageCode($languageCode);
1340
        }
1341
1342
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1343
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1344
            $content->contentInfo->contentTypeId
1345
        );
1346
        $fields = $this->mapFieldsForUpdate(
1347
            $contentUpdateStruct,
1348
            $contentType,
1349
            $mainLanguageCode
1350
        );
1351
1352
        $fieldValues = [];
1353
        $spiFields = [];
1354
        $allFieldErrors = [];
1355
        $inputRelations = [];
1356
        $locationIdToContentIdMapping = [];
1357
1358
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1359
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1360
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1361
                $fieldDefinition->fieldTypeIdentifier
1362
            );
1363
1364
            foreach ($allLanguageCodes as $languageCode) {
1365
                $isCopied = $isEmpty = $isRetained = false;
1366
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1367
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1368
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1369
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1370
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1371
1372
                if (!$isFieldUpdated && !$isLanguageNew) {
1373
                    $isRetained = true;
1374
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1375
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1376
                    $isCopied = true;
1377
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1378
                } elseif ($isFieldUpdated) {
1379
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1380
                } else {
1381
                    $fieldValue = $fieldDefinition->defaultValue;
1382
                }
1383
1384
                $fieldValue = $fieldType->acceptValue($fieldValue);
1385
1386
                if ($fieldType->isEmptyValue($fieldValue)) {
1387
                    $isEmpty = true;
1388
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1389
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1390
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1391
                            null,
1392
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1393
                            'empty'
1394
                        );
1395
                    }
1396
                } elseif ($isLanguageUpdated) {
1397
                    $fieldErrors = $fieldType->validate(
1398
                        $fieldDefinition,
1399
                        $fieldValue
1400
                    );
1401
                    if (!empty($fieldErrors)) {
1402
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1403
                    }
1404
                }
1405
1406
                if (!empty($allFieldErrors)) {
1407
                    continue;
1408
                }
1409
1410
                $this->relationProcessor->appendFieldRelations(
1411
                    $inputRelations,
1412
                    $locationIdToContentIdMapping,
1413
                    $fieldType,
1414
                    $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...
1415
                    $fieldDefinition->id
1416
                );
1417
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1418
1419
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1420
                    continue;
1421
                }
1422
1423
                $spiFields[] = new SPIField(
1424
                    [
1425
                        'id' => $isLanguageNew ?
1426
                            null :
1427
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1428
                        'fieldDefinitionId' => $fieldDefinition->id,
1429
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1430
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1431
                        'languageCode' => $languageCode,
1432
                        'versionNo' => $versionInfo->versionNo,
1433
                    ]
1434
                );
1435
            }
1436
        }
1437
1438
        if (!empty($allFieldErrors)) {
1439
            throw new ContentFieldValidationException($allFieldErrors);
1440
        }
1441
1442
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1443
            [
1444
                'name' => $this->nameSchemaService->resolveNameSchema(
1445
                    $content,
1446
                    $fieldValues,
1447
                    $allLanguageCodes,
1448
                    $contentType
1449
                ),
1450
                '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...
1451
                'fields' => $spiFields,
1452
                'modificationDate' => time(),
1453
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1454
                    $contentUpdateStruct->initialLanguageCode
1455
                )->id,
1456
            ]
1457
        );
1458
        $existingRelations = $this->internalLoadRelations($versionInfo);
1459
1460
        $this->repository->beginTransaction();
1461
        try {
1462
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1463
                $versionInfo->getContentInfo()->id,
1464
                $versionInfo->versionNo,
1465
                $spiContentUpdateStruct
1466
            );
1467
            $this->relationProcessor->processFieldRelations(
1468
                $inputRelations,
1469
                $spiContent->versionInfo->contentInfo->id,
1470
                $spiContent->versionInfo->versionNo,
1471
                $contentType,
1472
                $existingRelations
1473
            );
1474
            $this->repository->commit();
1475
        } catch (Exception $e) {
1476
            $this->repository->rollback();
1477
            throw $e;
1478
        }
1479
1480
        return $this->domainMapper->buildContentDomainObject(
1481
            $spiContent,
1482
            $contentType
1483
        );
1484
    }
1485
1486
    /**
1487
     * Returns only updated language codes.
1488
     *
1489
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1490
     *
1491
     * @return array
1492
     */
1493
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1494
    {
1495
        $languageCodes = [
1496
            $contentUpdateStruct->initialLanguageCode => true,
1497
        ];
1498
1499
        foreach ($contentUpdateStruct->fields as $field) {
1500
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1501
                continue;
1502
            }
1503
1504
            $languageCodes[$field->languageCode] = true;
1505
        }
1506
1507
        return array_keys($languageCodes);
1508
    }
1509
1510
    /**
1511
     * Returns all language codes used in given $fields.
1512
     *
1513
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1514
     *
1515
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1516
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1517
     *
1518
     * @return array
1519
     */
1520
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1521
    {
1522
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1523
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1524
1525
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1526
        foreach ($updatedLanguageCodes as $languageCode) {
1527
            $languageCodes[$languageCode] = true;
1528
        }
1529
1530
        return array_keys($languageCodes);
1531
    }
1532
1533
    /**
1534
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1535
     *
1536
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1537
     *                                                                          or value is set for non-translatable field in language
1538
     *                                                                          other than main
1539
     *
1540
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1541
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1542
     * @param string $mainLanguageCode
1543
     *
1544
     * @return array
1545
     */
1546
    protected function mapFieldsForUpdate(
1547
        APIContentUpdateStruct $contentUpdateStruct,
1548
        ContentType $contentType,
1549
        $mainLanguageCode
1550
    ) {
1551
        $fields = [];
1552
1553
        foreach ($contentUpdateStruct->fields as $field) {
1554
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1555
1556
            if ($fieldDefinition === null) {
1557
                throw new ContentValidationException(
1558
                    "Field definition '%identifier%' does not exist in given ContentType",
1559
                    ['%identifier%' => $field->fieldDefIdentifier]
1560
                );
1561
            }
1562
1563
            if ($field->languageCode === null) {
1564
                if ($fieldDefinition->isTranslatable) {
1565
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1566
                } else {
1567
                    $languageCode = $mainLanguageCode;
1568
                }
1569
                $field = $this->cloneField($field, ['languageCode' => $languageCode]);
1570
            }
1571
1572
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $mainLanguageCode)) {
1573
                throw new ContentValidationException(
1574
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1575
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1576
                );
1577
            }
1578
1579
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1580
        }
1581
1582
        return $fields;
1583
    }
1584
1585
    /**
1586
     * Publishes a content version.
1587
     *
1588
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1589
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1590
     *
1591
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1592
     * @param string[] $translations
1593
     *
1594
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1595
     *
1596
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1597
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1598
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1599
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1600
     */
1601
    public function publishVersion(APIVersionInfo $versionInfo, array $translations = Language::ALL)
1602
    {
1603
        $content = $this->internalLoadContent(
1604
            $versionInfo->contentInfo->id,
1605
            null,
1606
            $versionInfo->versionNo
1607
        );
1608
1609
        $targets = [];
1610
        if (!empty($translations)) {
1611
            $targets[] = (new Target\Builder\VersionBuilder())
1612
                ->publishTranslations($translations)
1613
                ->build();
1614
        }
1615
1616
        if (!$this->repository->getPermissionResolver()->canUser(
1617
            'content',
1618
            'publish',
1619
            $content,
1620
            $targets
1621
        )) {
1622
            throw new UnauthorizedException(
1623
                'content', 'publish', ['contentId' => $content->id]
1624
            );
1625
        }
1626
1627
        $this->repository->beginTransaction();
1628
        try {
1629
            $this->copyTranslationsFromPublishedVersion($content->versionInfo, $translations);
1630
            $content = $this->internalPublishVersion($content->getVersionInfo(), null);
1631
            $this->repository->commit();
1632
        } catch (Exception $e) {
1633
            $this->repository->rollback();
1634
            throw $e;
1635
        }
1636
1637
        return $content;
1638
    }
1639
1640
    /**
1641
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1642
     * @param array $translations
1643
     *
1644
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1645
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
1646
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
1647
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1648
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1649
     */
1650
    protected function copyTranslationsFromPublishedVersion(APIVersionInfo $versionInfo, array $translations = []): void
1651
    {
1652
        $contendId = $versionInfo->contentInfo->id;
1653
1654
        $currentContent = $this->internalLoadContent($contendId);
1655
        $currentVersionInfo = $currentContent->versionInfo;
1656
1657
        // Copying occurs only if:
1658
        // - There is published Version
1659
        // - Published version is older than the currently published one unless specific translations are provided.
1660
        if (!$currentVersionInfo->isPublished() ||
1661
            ($versionInfo->versionNo >= $currentVersionInfo->versionNo && empty($translations))) {
1662
            return;
1663
        }
1664
1665
        if (empty($translations)) {
1666
            $languagesToCopy = array_diff(
1667
                $currentVersionInfo->languageCodes,
1668
                $versionInfo->languageCodes
1669
            );
1670
        } else {
1671
            $languagesToCopy = array_diff(
1672
                $currentVersionInfo->languageCodes,
1673
                $translations
1674
            );
1675
        }
1676
1677
        if (empty($languagesToCopy)) {
1678
            return;
1679
        }
1680
1681
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1682
            $currentVersionInfo->contentInfo->contentTypeId
1683
        );
1684
1685
        // Find only translatable fields to update with selected languages
1686
        $updateStruct = $this->newContentUpdateStruct();
1687
        $updateStruct->initialLanguageCode = $versionInfo->initialLanguageCode;
1688
1689
        $contentToPublish = $this->internalLoadContent($contendId, null, $versionInfo->versionNo);
1690
        $fallbackUpdateStruct = $this->newContentUpdateStruct();
1691
1692
        foreach ($currentContent->getFields() as $field) {
1693
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1694
1695
            if (!$fieldDefinition->isTranslatable || !\in_array($field->languageCode, $languagesToCopy)) {
1696
                continue;
1697
            }
1698
1699
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1700
                $fieldDefinition->fieldTypeIdentifier
1701
            );
1702
1703
            $newValue = $contentToPublish->getFieldValue(
1704
                $fieldDefinition->identifier,
1705
                $field->languageCode
1706
            );
1707
1708
            $value = $field->value;
1709
            if ($fieldDefinition->isRequired && $fieldType->isEmptyValue($value)) {
1710
                if (!$fieldType->isEmptyValue($fieldDefinition->defaultValue)) {
1711
                    $value = $fieldDefinition->defaultValue;
1712
                } else {
1713
                    $value = $contentToPublish->getFieldValue($field->fieldDefIdentifier, $versionInfo->initialLanguageCode);
1714
                }
1715
                $fallbackUpdateStruct->setField(
1716
                    $field->fieldDefIdentifier,
1717
                    $value,
1718
                    $field->languageCode
1719
                );
1720
                continue;
1721
            }
1722
1723
            if ($newValue !== null
1724
                && $field->value !== null
1725
                && $this->equalsHash($fieldType, $newValue, $field->value)) {
0 ignored issues
show
Bug introduced by
The method equalsHash() does not seem to exist on object<eZ\Publish\Core\Repository\ContentService>.

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

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

Loading history...
1726
                continue;
1727
            }
1728
1729
            $updateStruct->setField($field->fieldDefIdentifier, $value, $field->languageCode);
1730
        }
1731
1732
        // Nothing to copy, skip update
1733
        if (empty($updateStruct->fields)) {
1734
            return;
1735
        }
1736
1737
        // Do fallback only if content needs to be updated
1738
        foreach ($fallbackUpdateStruct->fields as $fallbackField) {
1739
            $updateStruct->setField($fallbackField->fieldDefIdentifier, $fallbackField->value, $fallbackField->languageCode);
1740
        }
1741
1742
        $this->internalUpdateContent($versionInfo, $updateStruct);
1743
    }
1744
1745
    protected function isHashEqual($fieldType, $newValue, $fieldValue): bool
1746
    {
1747
        $newHash = $fieldType->toHash($newValue);
1748
        $currentHash = $fieldType->toHash($fieldValue);
1749
        if ($newHash === $currentHash) {
1750
            return true;
1751
        } elseif ($fieldType instanceof \eZ\Publish\Core\FieldType\Image\Type) {
1752
            $imageHashDiff = array_diff($newHash, $currentHash);
1753
1754
            return count($imageHashDiff) === 1 && !empty($imageHashDiff['imageId']);
1755
        }
1756
1757
        return false;
1758
    }
1759
1760
    /**
1761
     * Publishes a content version.
1762
     *
1763
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1764
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1765
     *
1766
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1767
     *
1768
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1769
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1770
     *
1771
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1772
     */
1773
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1774
    {
1775
        if (!$versionInfo->isDraft()) {
1776
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1777
        }
1778
1779
        $currentTime = $this->getUnixTimestamp();
1780
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1781
            $publicationDate = $currentTime;
1782
        }
1783
1784
        $contentInfo = $versionInfo->getContentInfo();
1785
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1786
        $metadataUpdateStruct->publicationDate = $publicationDate;
1787
        $metadataUpdateStruct->modificationDate = $currentTime;
1788
        $metadataUpdateStruct->isHidden = $contentInfo->isHidden;
1789
1790
        $contentId = $contentInfo->id;
1791
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1792
            $contentId,
1793
            $versionInfo->versionNo,
1794
            $metadataUpdateStruct
1795
        );
1796
1797
        $content = $this->domainMapper->buildContentDomainObject(
1798
            $spiContent,
1799
            $this->repository->getContentTypeService()->loadContentType(
1800
                $spiContent->versionInfo->contentInfo->contentTypeId
1801
            )
1802
        );
1803
1804
        $this->publishUrlAliasesForContent($content);
1805
1806
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1807
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1808
            $contentId,
1809
            APIVersionInfo::STATUS_ARCHIVED,
1810
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1811
        );
1812
1813
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1814
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1815
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1816
            $archiveVersion = array_shift($archiveList);
1817
            $this->persistenceHandler->contentHandler()->deleteVersion(
1818
                $contentId,
1819
                $archiveVersion->versionNo
1820
            );
1821
        }
1822
1823
        return $content;
1824
    }
1825
1826
    /**
1827
     * @return int
1828
     */
1829
    protected function getUnixTimestamp()
1830
    {
1831
        return time();
1832
    }
1833
1834
    /**
1835
     * Removes the given version.
1836
     *
1837
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1838
     *         published state or is a last version of Content in non draft state
1839
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1840
     *
1841
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1842
     */
1843
    public function deleteVersion(APIVersionInfo $versionInfo)
1844
    {
1845
        if ($versionInfo->isPublished()) {
1846
            throw new BadStateException(
1847
                '$versionInfo',
1848
                'Version is published and can not be removed'
1849
            );
1850
        }
1851
1852
        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...
1853
            throw new UnauthorizedException(
1854
                'content',
1855
                'versionremove',
1856
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1857
            );
1858
        }
1859
1860
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1861
            $versionInfo->contentInfo->id,
1862
            null,
1863
            2
1864
        );
1865
1866
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1867
            throw new BadStateException(
1868
                '$versionInfo',
1869
                'Version is the last version of the Content and can not be removed'
1870
            );
1871
        }
1872
1873
        $this->repository->beginTransaction();
1874
        try {
1875
            $this->persistenceHandler->contentHandler()->deleteVersion(
1876
                $versionInfo->getContentInfo()->id,
1877
                $versionInfo->versionNo
1878
            );
1879
            $this->repository->commit();
1880
        } catch (Exception $e) {
1881
            $this->repository->rollback();
1882
            throw $e;
1883
        }
1884
    }
1885
1886
    /**
1887
     * Loads all versions for the given content.
1888
     *
1889
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1890
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the given status is invalid
1891
     *
1892
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1893
     * @param int|null $status
1894
     *
1895
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1896
     */
1897
    public function loadVersions(ContentInfo $contentInfo, ?int $status = null)
1898
    {
1899
        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...
1900
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1901
        }
1902
1903
        if ($status !== null && !in_array((int)$status, [VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED], true)) {
1904
            throw new InvalidArgumentException(
1905
                'status',
1906
                sprintf(
1907
                    'it can be one of %d (draft), %d (published), %d (archived), %d given',
1908
                    VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED, $status
1909
                ));
1910
        }
1911
1912
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id, $status);
1913
1914
        $versions = [];
1915
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1916
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1917
            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...
1918
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1919
            }
1920
1921
            $versions[] = $versionInfo;
1922
        }
1923
1924
        return $versions;
1925
    }
1926
1927
    /**
1928
     * Copies the content to a new location. If no version is given,
1929
     * all versions are copied, otherwise only the given version.
1930
     *
1931
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1932
     *
1933
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1934
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1935
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1936
     *
1937
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1938
     */
1939
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1940
    {
1941
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1942
            $destinationLocationCreateStruct->parentLocationId
1943
        );
1944
        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...
1945
            throw new UnauthorizedException(
1946
                'content',
1947
                'create',
1948
                [
1949
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1950
                    'sectionId' => $contentInfo->sectionId,
1951
                ]
1952
            );
1953
        }
1954
        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...
1955
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
1956
        }
1957
1958
        $defaultObjectStates = $this->getDefaultObjectStates();
1959
1960
        $this->repository->beginTransaction();
1961
        try {
1962
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1963
                $contentInfo->id,
1964
                $versionInfo ? $versionInfo->versionNo : null,
1965
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1966
            );
1967
1968
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1969
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1970
                $objectStateHandler->setContentState(
1971
                    $spiContent->versionInfo->contentInfo->id,
1972
                    $objectStateGroupId,
1973
                    $objectState->id
1974
                );
1975
            }
1976
1977
            $content = $this->internalPublishVersion(
1978
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1979
                $spiContent->versionInfo->creationDate
1980
            );
1981
1982
            $this->repository->getLocationService()->createLocation(
1983
                $content->getVersionInfo()->getContentInfo(),
1984
                $destinationLocationCreateStruct
1985
            );
1986
            $this->repository->commit();
1987
        } catch (Exception $e) {
1988
            $this->repository->rollback();
1989
            throw $e;
1990
        }
1991
1992
        return $this->internalLoadContent($content->id);
1993
    }
1994
1995
    /**
1996
     * Loads all outgoing relations for the given version.
1997
     *
1998
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1999
     *
2000
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
2001
     *
2002
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
2003
     */
2004
    public function loadRelations(APIVersionInfo $versionInfo)
2005
    {
2006
        if ($versionInfo->isPublished()) {
2007
            $function = 'read';
2008
        } else {
2009
            $function = 'versionread';
2010
        }
2011
2012
        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...
2013
            throw new UnauthorizedException('content', $function);
2014
        }
2015
2016
        return $this->internalLoadRelations($versionInfo);
2017
    }
2018
2019
    /**
2020
     * Loads all outgoing relations for the given version without checking the permissions.
2021
     *
2022
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
2023
     *
2024
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
2025
     */
2026
    protected function internalLoadRelations(APIVersionInfo $versionInfo): array
2027
    {
2028
        $contentInfo = $versionInfo->getContentInfo();
2029
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2030
            $contentInfo->id,
2031
            $versionInfo->versionNo
2032
        );
2033
2034
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
2035
        $relations = [];
2036
        foreach ($spiRelations as $spiRelation) {
2037
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
2038
            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...
2039
                continue;
2040
            }
2041
2042
            $relations[] = $this->domainMapper->buildRelationDomainObject(
2043
                $spiRelation,
2044
                $contentInfo,
2045
                $destinationContentInfo
2046
            );
2047
        }
2048
2049
        return $relations;
2050
    }
2051
2052
    /**
2053
     * {@inheritdoc}
2054
     */
2055
    public function countReverseRelations(ContentInfo $contentInfo): int
2056
    {
2057
        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...
2058
            return 0;
2059
        }
2060
2061
        return $this->persistenceHandler->contentHandler()->countReverseRelations(
2062
            $contentInfo->id
2063
        );
2064
    }
2065
2066
    /**
2067
     * Loads all incoming relations for a content object.
2068
     *
2069
     * The relations come only from published versions of the source content objects
2070
     *
2071
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
2072
     *
2073
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2074
     *
2075
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
2076
     */
2077
    public function loadReverseRelations(ContentInfo $contentInfo)
2078
    {
2079
        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...
2080
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
2081
        }
2082
2083
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
2084
            $contentInfo->id
2085
        );
2086
2087
        $returnArray = [];
2088
        foreach ($spiRelations as $spiRelation) {
2089
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2090
            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...
2091
                continue;
2092
            }
2093
2094
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
2095
                $spiRelation,
2096
                $sourceContentInfo,
2097
                $contentInfo
2098
            );
2099
        }
2100
2101
        return $returnArray;
2102
    }
2103
2104
    /**
2105
     * {@inheritdoc}
2106
     */
2107
    public function loadReverseRelationList(ContentInfo $contentInfo, int $offset = 0, int $limit = -1): RelationList
2108
    {
2109
        $list = new RelationList();
2110
        if (!$this->repository->getPermissionResolver()->canUser('content', 'reverserelatedlist', $contentInfo)) {
2111
            return $list;
2112
        }
2113
2114
        $list->totalCount = $this->persistenceHandler->contentHandler()->countReverseRelations(
2115
            $contentInfo->id
2116
        );
2117
        if ($list->totalCount > 0) {
2118
            $spiRelationList = $this->persistenceHandler->contentHandler()->loadReverseRelationList(
2119
                $contentInfo->id,
2120
                $offset,
2121
                $limit
2122
            );
2123
            foreach ($spiRelationList as $spiRelation) {
2124
                $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2125
                if ($this->repository->getPermissionResolver()->canUser('content', 'read', $sourceContentInfo)) {
2126
                    $relation = $this->domainMapper->buildRelationDomainObject(
2127
                        $spiRelation,
2128
                        $sourceContentInfo,
2129
                        $contentInfo
2130
                    );
2131
                    $list->items[] = new RelationListItem($relation);
2132
                } else {
2133
                    $list->items[] = new UnauthorizedRelationListItem(
2134
                        'content',
2135
                        'read',
2136
                        ['contentId' => $sourceContentInfo->id]
2137
                    );
2138
                }
2139
            }
2140
        }
2141
2142
        return $list;
2143
    }
2144
2145
    /**
2146
     * Adds a relation of type common.
2147
     *
2148
     * The source of the relation is the content and version
2149
     * referenced by $versionInfo.
2150
     *
2151
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
2152
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2153
     *
2154
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2155
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
2156
     *
2157
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
2158
     */
2159
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2160
    {
2161
        $sourceVersion = $this->loadVersionInfoById(
2162
            $sourceVersion->contentInfo->id,
2163
            $sourceVersion->versionNo
2164
        );
2165
2166
        if (!$sourceVersion->isDraft()) {
2167
            throw new BadStateException(
2168
                '$sourceVersion',
2169
                'Relations of type common can only be added to versions of status draft'
2170
            );
2171
        }
2172
2173
        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...
2174
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2175
        }
2176
2177
        $sourceContentInfo = $sourceVersion->getContentInfo();
2178
2179
        $this->repository->beginTransaction();
2180
        try {
2181
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
2182
                new SPIRelationCreateStruct(
2183
                    [
2184
                        'sourceContentId' => $sourceContentInfo->id,
2185
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
2186
                        'sourceFieldDefinitionId' => null,
2187
                        'destinationContentId' => $destinationContent->id,
2188
                        'type' => APIRelation::COMMON,
2189
                    ]
2190
                )
2191
            );
2192
            $this->repository->commit();
2193
        } catch (Exception $e) {
2194
            $this->repository->rollback();
2195
            throw $e;
2196
        }
2197
2198
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
2199
    }
2200
2201
    /**
2202
     * Removes a relation of type COMMON from a draft.
2203
     *
2204
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
2205
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2206
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
2207
     *
2208
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2209
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
2210
     */
2211
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2212
    {
2213
        $sourceVersion = $this->loadVersionInfoById(
2214
            $sourceVersion->contentInfo->id,
2215
            $sourceVersion->versionNo
2216
        );
2217
2218
        if (!$sourceVersion->isDraft()) {
2219
            throw new BadStateException(
2220
                '$sourceVersion',
2221
                'Relations of type common can only be removed from versions of status draft'
2222
            );
2223
        }
2224
2225
        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...
2226
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2227
        }
2228
2229
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2230
            $sourceVersion->getContentInfo()->id,
2231
            $sourceVersion->versionNo,
2232
            APIRelation::COMMON
2233
        );
2234
2235
        if (empty($spiRelations)) {
2236
            throw new InvalidArgumentException(
2237
                '$sourceVersion',
2238
                'There are no relations of type COMMON for the given destination'
2239
            );
2240
        }
2241
2242
        // there should be only one relation of type COMMON for each destination,
2243
        // but in case there were ever more then one, we will remove them all
2244
        // @todo: alternatively, throw BadStateException?
2245
        $this->repository->beginTransaction();
2246
        try {
2247
            foreach ($spiRelations as $spiRelation) {
2248
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2249
                    $this->persistenceHandler->contentHandler()->removeRelation(
2250
                        $spiRelation->id,
2251
                        APIRelation::COMMON
2252
                    );
2253
                }
2254
            }
2255
            $this->repository->commit();
2256
        } catch (Exception $e) {
2257
            $this->repository->rollback();
2258
            throw $e;
2259
        }
2260
    }
2261
2262
    /**
2263
     * {@inheritdoc}
2264
     */
2265
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2266
    {
2267
        @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...
2268
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2269
            E_USER_DEPRECATED
2270
        );
2271
        $this->deleteTranslation($contentInfo, $languageCode);
2272
    }
2273
2274
    /**
2275
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2276
     *
2277
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2278
     *
2279
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2280
     *         is the Main Translation of a Content Item.
2281
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2282
     *         to delete the content (in one of the locations of the given Content Item).
2283
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2284
     *         is invalid for the given content.
2285
     *
2286
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2287
     * @param string $languageCode
2288
     *
2289
     * @since 6.13
2290
     */
2291
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2292
    {
2293
        if ($contentInfo->mainLanguageCode === $languageCode) {
2294
            throw new BadStateException(
2295
                '$languageCode',
2296
                'Specified translation is the main translation of the Content Object'
2297
            );
2298
        }
2299
2300
        $translationWasFound = false;
2301
        $this->repository->beginTransaction();
2302
        try {
2303
            $target = (new Target\Builder\VersionBuilder())->translateToAnyLanguageOf([$languageCode])->build();
2304
2305
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2306
                if (!$this->repository->canUser('content', 'remove', $versionInfo, [$target])) {
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...
2307
                    throw new UnauthorizedException(
2308
                        'content',
2309
                        'remove',
2310
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo, 'languageCode' => $languageCode]
2311
                    );
2312
                }
2313
2314
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2315
                    continue;
2316
                }
2317
2318
                $translationWasFound = true;
2319
2320
                // If the translation is the version's only one, delete the version
2321
                if (count($versionInfo->languageCodes) < 2) {
2322
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2323
                        $versionInfo->getContentInfo()->id,
2324
                        $versionInfo->versionNo
2325
                    );
2326
                }
2327
            }
2328
2329
            if (!$translationWasFound) {
2330
                throw new InvalidArgumentException(
2331
                    '$languageCode',
2332
                    sprintf(
2333
                        '%s does not exist in the Content item(id=%d)',
2334
                        $languageCode,
2335
                        $contentInfo->id
2336
                    )
2337
                );
2338
            }
2339
2340
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2341
                $contentInfo->id,
2342
                $languageCode
2343
            );
2344
            $locationIds = array_map(
2345
                function (Location $location) {
2346
                    return $location->id;
2347
                },
2348
                $this->repository->getLocationService()->loadLocations($contentInfo)
2349
            );
2350
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2351
                $locationIds,
2352
                $languageCode
2353
            );
2354
            $this->repository->commit();
2355
        } catch (InvalidArgumentException $e) {
2356
            $this->repository->rollback();
2357
            throw $e;
2358
        } catch (BadStateException $e) {
2359
            $this->repository->rollback();
2360
            throw $e;
2361
        } catch (UnauthorizedException $e) {
2362
            $this->repository->rollback();
2363
            throw $e;
2364
        } catch (Exception $e) {
2365
            $this->repository->rollback();
2366
            // cover generic unexpected exception to fulfill API promise on @throws
2367
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2368
        }
2369
    }
2370
2371
    /**
2372
     * Delete specified Translation from a Content Draft.
2373
     *
2374
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2375
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2376
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2377
     *         to edit the Content (in one of the locations of the given Content Object).
2378
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2379
     *         is invalid for the given Draft.
2380
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2381
     *
2382
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2383
     * @param string $languageCode Language code of the Translation to be removed
2384
     *
2385
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2386
     *
2387
     * @since 6.12
2388
     */
2389
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2390
    {
2391
        if (!$versionInfo->isDraft()) {
2392
            throw new BadStateException(
2393
                '$versionInfo',
2394
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2395
            );
2396
        }
2397
2398
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2399
            throw new BadStateException(
2400
                '$languageCode',
2401
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2402
            );
2403
        }
2404
2405
        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...
2406
            throw new UnauthorizedException(
2407
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2408
            );
2409
        }
2410
2411
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2412
            throw new InvalidArgumentException(
2413
                '$languageCode',
2414
                sprintf(
2415
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2416
                    $versionInfo->contentInfo->id,
2417
                    $versionInfo->versionNo,
2418
                    $languageCode
2419
                )
2420
            );
2421
        }
2422
2423
        if (count($versionInfo->languageCodes) === 1) {
2424
            throw new BadStateException(
2425
                '$languageCode',
2426
                'Specified Translation is the only one Content Object Version has'
2427
            );
2428
        }
2429
2430
        $this->repository->beginTransaction();
2431
        try {
2432
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2433
                $versionInfo->contentInfo->id,
2434
                $versionInfo->versionNo,
2435
                $languageCode
2436
            );
2437
            $this->repository->commit();
2438
2439
            return $this->domainMapper->buildContentDomainObject(
2440
                $spiContent,
2441
                $this->repository->getContentTypeService()->loadContentType(
2442
                    $spiContent->versionInfo->contentInfo->contentTypeId
2443
                )
2444
            );
2445
        } catch (APINotFoundException $e) {
2446
            // avoid wrapping expected NotFoundException in BadStateException handled below
2447
            $this->repository->rollback();
2448
            throw $e;
2449
        } catch (Exception $e) {
2450
            $this->repository->rollback();
2451
            // cover generic unexpected exception to fulfill API promise on @throws
2452
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2453
        }
2454
    }
2455
2456
    /**
2457
     * Hides Content by making all the Locations appear hidden.
2458
     * It does not persist hidden state on Location object itself.
2459
     *
2460
     * Content hidden by this API can be revealed by revealContent API.
2461
     *
2462
     * @see revealContent
2463
     *
2464
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2465
     */
2466
    public function hideContent(ContentInfo $contentInfo): void
2467
    {
2468
        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...
2469
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2470
        }
2471
2472
        $this->repository->beginTransaction();
2473
        try {
2474
            $this->persistenceHandler->contentHandler()->updateMetadata(
2475
                $contentInfo->id,
2476
                new SPIMetadataUpdateStruct([
2477
                    'isHidden' => true,
2478
                ])
2479
            );
2480
            $locationHandler = $this->persistenceHandler->locationHandler();
2481
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2482
            foreach ($childLocations as $childLocation) {
2483
                $locationHandler->setInvisible($childLocation->id);
2484
            }
2485
            $this->repository->commit();
2486
        } catch (Exception $e) {
2487
            $this->repository->rollback();
2488
            throw $e;
2489
        }
2490
    }
2491
2492
    /**
2493
     * Reveals Content hidden by hideContent API.
2494
     * Locations which were hidden before hiding Content will remain hidden.
2495
     *
2496
     * @see hideContent
2497
     *
2498
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2499
     */
2500
    public function revealContent(ContentInfo $contentInfo): void
2501
    {
2502
        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...
2503
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2504
        }
2505
2506
        $this->repository->beginTransaction();
2507
        try {
2508
            $this->persistenceHandler->contentHandler()->updateMetadata(
2509
                $contentInfo->id,
2510
                new SPIMetadataUpdateStruct([
2511
                    'isHidden' => false,
2512
                ])
2513
            );
2514
            $locationHandler = $this->persistenceHandler->locationHandler();
2515
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2516
            foreach ($childLocations as $childLocation) {
2517
                $locationHandler->setVisible($childLocation->id);
2518
            }
2519
            $this->repository->commit();
2520
        } catch (Exception $e) {
2521
            $this->repository->rollback();
2522
            throw $e;
2523
        }
2524
    }
2525
2526
    /**
2527
     * Instantiates a new content create struct object.
2528
     *
2529
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2530
     *
2531
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2532
     * @param string $mainLanguageCode
2533
     *
2534
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2535
     */
2536
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2537
    {
2538
        return new ContentCreateStruct(
2539
            [
2540
                'contentType' => $contentType,
2541
                'mainLanguageCode' => $mainLanguageCode,
2542
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2543
            ]
2544
        );
2545
    }
2546
2547
    /**
2548
     * Instantiates a new content meta data update struct.
2549
     *
2550
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2551
     */
2552
    public function newContentMetadataUpdateStruct()
2553
    {
2554
        return new ContentMetadataUpdateStruct();
2555
    }
2556
2557
    /**
2558
     * Instantiates a new content update struct.
2559
     *
2560
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2561
     */
2562
    public function newContentUpdateStruct()
2563
    {
2564
        return new ContentUpdateStruct();
2565
    }
2566
2567
    /**
2568
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2569
     *
2570
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2571
     */
2572
    private function resolveUser(?User $user): UserReference
2573
    {
2574
        if ($user === null) {
2575
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2576
        }
2577
2578
        return $user;
2579
    }
2580
}
2581