Completed
Push — EZP-30997 ( 014d9d...2cfc5a )
by
unknown
16:02
created

ContentService::updateContent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return array();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
1606
        if ($content->contentInfo->currentVersionNo !== $versionInfo->versionNo) {
1607
            $fromContent = $this->internalLoadContent(
1608
                $content->contentInfo->id,
1609
                null,
1610
                $content->contentInfo->currentVersionNo
1611
            );
1612
            // should not occur now, might occur in case of un-publish
1613
            if (!$fromContent->contentInfo->isPublished()) {
1614
                $fromContent = null;
0 ignored issues
show
Unused Code introduced by
$fromContent is not used, you could remove the assignment.

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

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

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

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

Loading history...
1615
            }
1616
        }
1617
1618
        if (!$this->repository->getPermissionResolver()->canUser(
1619
            'content',
1620
            'publish',
1621
            $content
1622
        )) {
1623
            throw new UnauthorizedException(
1624
                'content', 'publish', ['contentId' => $content->id]
1625
            );
1626
        }
1627
1628
        $this->repository->beginTransaction();
1629
        try {
1630
            $this->copyTranslationsFromPublishedVersion($content->versionInfo, $translations);
1631
            $content = $this->internalPublishVersion($content->getVersionInfo(), null);
1632
            $this->repository->commit();
1633
        } catch (Exception $e) {
1634
            $this->repository->rollback();
1635
            throw $e;
1636
        }
1637
1638
        return $content;
1639
    }
1640
1641
    /**
1642
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1643
     * @param array $translations
1644
     *
1645
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1646
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
1647
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
1648
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1649
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1650
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1651
     */
1652
    protected function copyTranslationsFromPublishedVersion(APIVersionInfo $versionInfo, array $translations = []): void
1653
    {
1654
        $contendId = $versionInfo->contentInfo->id;
1655
1656
        $currentContent = $this->internalLoadContent($contendId);
1657
        $currentVersionInfo = $currentContent->versionInfo;
1658
1659
        // Copying occurs only if:
1660
        // - There is published Version
1661
        // - Published version is older than the currently published one unless specific translations are provided.
1662
        if (!$currentVersionInfo->isPublished() ||
1663
            ($versionInfo->versionNo >= $currentVersionInfo->versionNo && empty($translations))) {
1664
            return;
1665
        }
1666
1667
        if (empty($translations)) {
1668
            $languagesToCopy = array_diff(
1669
                $currentVersionInfo->languageCodes,
1670
                $versionInfo->languageCodes
1671
            );
1672
        } else {
1673
            $languagesToCopy = array_diff(
1674
                $currentVersionInfo->languageCodes,
1675
                $translations
1676
            );
1677
        }
1678
1679
        if (empty($languagesToCopy)) {
1680
            return;
1681
        }
1682
1683
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1684
            $currentVersionInfo->contentInfo->contentTypeId
1685
        );
1686
1687
        // Find only translatable fields to update with selected languages
1688
        $updateStruct = $this->newContentUpdateStruct();
1689
        $updateStruct->initialLanguageCode = $versionInfo->initialLanguageCode;
1690
1691
        foreach ($currentContent->getFields() as $field) {
1692
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1693
1694
            if ($fieldDefinition->isTranslatable && in_array($field->languageCode, $languagesToCopy)) {
1695
                $updateStruct->setField($field->fieldDefIdentifier, $field->value, $field->languageCode);
1696
            }
1697
        }
1698
1699
        $this->internalUpdateContent($versionInfo, $updateStruct);
1700
    }
1701
1702
    /**
1703
     * Publishes a content version.
1704
     *
1705
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1706
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1707
     *
1708
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1709
     *
1710
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1711
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1712
     *
1713
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1714
     */
1715
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1716
    {
1717
        if (!$versionInfo->isDraft()) {
1718
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1719
        }
1720
1721
        $currentTime = $this->getUnixTimestamp();
1722
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1723
            $publicationDate = $currentTime;
1724
        }
1725
1726
        $contentInfo = $versionInfo->getContentInfo();
1727
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1728
        $metadataUpdateStruct->publicationDate = $publicationDate;
1729
        $metadataUpdateStruct->modificationDate = $currentTime;
1730
        $metadataUpdateStruct->isHidden = $contentInfo->isHidden;
1731
1732
        $contentId = $contentInfo->id;
1733
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1734
            $contentId,
1735
            $versionInfo->versionNo,
1736
            $metadataUpdateStruct
1737
        );
1738
1739
        $content = $this->domainMapper->buildContentDomainObject(
1740
            $spiContent,
1741
            $this->repository->getContentTypeService()->loadContentType(
1742
                $spiContent->versionInfo->contentInfo->contentTypeId
1743
            )
1744
        );
1745
1746
        $this->publishUrlAliasesForContent($content);
1747
1748
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1749
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1750
            $contentId,
1751
            APIVersionInfo::STATUS_ARCHIVED,
1752
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1753
        );
1754
1755
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1756
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1757
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1758
            $archiveVersion = array_shift($archiveList);
1759
            $this->persistenceHandler->contentHandler()->deleteVersion(
1760
                $contentId,
1761
                $archiveVersion->versionNo
1762
            );
1763
        }
1764
1765
        return $content;
1766
    }
1767
1768
    /**
1769
     * @return int
1770
     */
1771
    protected function getUnixTimestamp()
1772
    {
1773
        return time();
1774
    }
1775
1776
    /**
1777
     * Removes the given version.
1778
     *
1779
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1780
     *         published state or is a last version of Content in non draft state
1781
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1782
     *
1783
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1784
     */
1785
    public function deleteVersion(APIVersionInfo $versionInfo)
1786
    {
1787
        if ($versionInfo->isPublished()) {
1788
            throw new BadStateException(
1789
                '$versionInfo',
1790
                'Version is published and can not be removed'
1791
            );
1792
        }
1793
1794
        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...
1795
            throw new UnauthorizedException(
1796
                'content',
1797
                'versionremove',
1798
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1799
            );
1800
        }
1801
1802
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1803
            $versionInfo->contentInfo->id,
1804
            null,
1805
            2
1806
        );
1807
1808
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1809
            throw new BadStateException(
1810
                '$versionInfo',
1811
                'Version is the last version of the Content and can not be removed'
1812
            );
1813
        }
1814
1815
        $this->repository->beginTransaction();
1816
        try {
1817
            $this->persistenceHandler->contentHandler()->deleteVersion(
1818
                $versionInfo->getContentInfo()->id,
1819
                $versionInfo->versionNo
1820
            );
1821
            $this->repository->commit();
1822
        } catch (Exception $e) {
1823
            $this->repository->rollback();
1824
            throw $e;
1825
        }
1826
    }
1827
1828
    /**
1829
     * Loads all versions for the given content.
1830
     *
1831
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1832
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the given status is invalid
1833
     *
1834
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1835
     * @param int|null $status
1836
     *
1837
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1838
     */
1839
    public function loadVersions(ContentInfo $contentInfo, ?int $status = null)
1840
    {
1841
        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...
1842
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1843
        }
1844
1845
        if ($status !== null && !in_array((int)$status, [VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED], true)) {
1846
            throw new InvalidArgumentException(
1847
                'status',
1848
                sprintf(
1849
                    'it can be one of %d (draft), %d (published), %d (archived), %d given',
1850
                    VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED, $status
1851
                ));
1852
        }
1853
1854
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id, $status);
1855
1856
        $versions = [];
1857
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1858
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1859
            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...
1860
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1861
            }
1862
1863
            $versions[] = $versionInfo;
1864
        }
1865
1866
        return $versions;
1867
    }
1868
1869
    /**
1870
     * Copies the content to a new location. If no version is given,
1871
     * all versions are copied, otherwise only the given version.
1872
     *
1873
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1874
     *
1875
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1876
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1877
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1878
     *
1879
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1880
     */
1881
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1882
    {
1883
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1884
            $destinationLocationCreateStruct->parentLocationId
1885
        );
1886
        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...
1887
            throw new UnauthorizedException(
1888
                'content',
1889
                'create',
1890
                [
1891
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1892
                    'sectionId' => $contentInfo->sectionId,
1893
                ]
1894
            );
1895
        }
1896
        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...
1897
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
1898
        }
1899
1900
        $defaultObjectStates = $this->getDefaultObjectStates();
1901
1902
        $this->repository->beginTransaction();
1903
        try {
1904
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1905
                $contentInfo->id,
1906
                $versionInfo ? $versionInfo->versionNo : null,
1907
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1908
            );
1909
1910
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1911
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1912
                $objectStateHandler->setContentState(
1913
                    $spiContent->versionInfo->contentInfo->id,
1914
                    $objectStateGroupId,
1915
                    $objectState->id
1916
                );
1917
            }
1918
1919
            $content = $this->internalPublishVersion(
1920
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1921
                $spiContent->versionInfo->creationDate
1922
            );
1923
1924
            $this->repository->getLocationService()->createLocation(
1925
                $content->getVersionInfo()->getContentInfo(),
1926
                $destinationLocationCreateStruct
1927
            );
1928
            $this->repository->commit();
1929
        } catch (Exception $e) {
1930
            $this->repository->rollback();
1931
            throw $e;
1932
        }
1933
1934
        return $this->internalLoadContent($content->id);
1935
    }
1936
1937
    /**
1938
     * Loads all outgoing relations for the given version.
1939
     *
1940
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1941
     *
1942
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1943
     *
1944
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1945
     */
1946
    public function loadRelations(APIVersionInfo $versionInfo)
1947
    {
1948
        if ($versionInfo->isPublished()) {
1949
            $function = 'read';
1950
        } else {
1951
            $function = 'versionread';
1952
        }
1953
1954
        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...
1955
            throw new UnauthorizedException('content', $function);
1956
        }
1957
1958
        return $this->internalLoadRelations($versionInfo);
1959
    }
1960
1961
    /**
1962
     * Loads all outgoing relations for the given version without checking the permissions.
1963
     *
1964
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1965
     *
1966
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1967
     */
1968
    protected function internalLoadRelations(APIVersionInfo $versionInfo): array
1969
    {
1970
        $contentInfo = $versionInfo->getContentInfo();
1971
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1972
            $contentInfo->id,
1973
            $versionInfo->versionNo
1974
        );
1975
1976
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1977
        $relations = [];
1978
        foreach ($spiRelations as $spiRelation) {
1979
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1980
            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...
1981
                continue;
1982
            }
1983
1984
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1985
                $spiRelation,
1986
                $contentInfo,
1987
                $destinationContentInfo
1988
            );
1989
        }
1990
1991
        return $relations;
1992
    }
1993
1994
    /**
1995
     * {@inheritdoc}
1996
     */
1997
    public function countReverseRelations(ContentInfo $contentInfo): int
1998
    {
1999
        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...
2000
            return 0;
2001
        }
2002
2003
        return $this->persistenceHandler->contentHandler()->countReverseRelations(
2004
            $contentInfo->id
2005
        );
2006
    }
2007
2008
    /**
2009
     * Loads all incoming relations for a content object.
2010
     *
2011
     * The relations come only from published versions of the source content objects
2012
     *
2013
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
2014
     *
2015
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2016
     *
2017
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
2018
     */
2019
    public function loadReverseRelations(ContentInfo $contentInfo)
2020
    {
2021
        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...
2022
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
2023
        }
2024
2025
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
2026
            $contentInfo->id
2027
        );
2028
2029
        $returnArray = [];
2030
        foreach ($spiRelations as $spiRelation) {
2031
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2032
            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...
2033
                continue;
2034
            }
2035
2036
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
2037
                $spiRelation,
2038
                $sourceContentInfo,
2039
                $contentInfo
2040
            );
2041
        }
2042
2043
        return $returnArray;
2044
    }
2045
2046
    /**
2047
     * {@inheritdoc}
2048
     */
2049
    public function loadReverseRelationList(ContentInfo $contentInfo, int $offset = 0, int $limit = -1): RelationList
2050
    {
2051
        $list = new RelationList();
2052
        if (!$this->repository->getPermissionResolver()->canUser('content', 'reverserelatedlist', $contentInfo)) {
2053
            return $list;
2054
        }
2055
2056
        $list->totalCount = $this->persistenceHandler->contentHandler()->countReverseRelations(
2057
            $contentInfo->id
2058
        );
2059
        if ($list->totalCount > 0) {
2060
            $spiRelationList = $this->persistenceHandler->contentHandler()->loadReverseRelationList(
2061
                $contentInfo->id,
2062
                $offset,
2063
                $limit
2064
            );
2065
            foreach ($spiRelationList as $spiRelation) {
2066
                $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2067
                if ($this->repository->getPermissionResolver()->canUser('content', 'read', $sourceContentInfo)) {
2068
                    $relation = $this->domainMapper->buildRelationDomainObject(
2069
                        $spiRelation,
2070
                        $sourceContentInfo,
2071
                        $contentInfo
2072
                    );
2073
                    $list->items[] = new RelationListItem($relation);
2074
                } else {
2075
                    $list->items[] = new UnauthorizedRelationListItem(
2076
                        'content',
2077
                        'read',
2078
                        ['contentId' => $sourceContentInfo->id]
2079
                    );
2080
                }
2081
            }
2082
        }
2083
2084
        return $list;
2085
    }
2086
2087
    /**
2088
     * Adds a relation of type common.
2089
     *
2090
     * The source of the relation is the content and version
2091
     * referenced by $versionInfo.
2092
     *
2093
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
2094
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2095
     *
2096
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2097
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
2098
     *
2099
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
2100
     */
2101
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2102
    {
2103
        $sourceVersion = $this->loadVersionInfoById(
2104
            $sourceVersion->contentInfo->id,
2105
            $sourceVersion->versionNo
2106
        );
2107
2108
        if (!$sourceVersion->isDraft()) {
2109
            throw new BadStateException(
2110
                '$sourceVersion',
2111
                'Relations of type common can only be added to versions of status draft'
2112
            );
2113
        }
2114
2115
        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...
2116
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2117
        }
2118
2119
        $sourceContentInfo = $sourceVersion->getContentInfo();
2120
2121
        $this->repository->beginTransaction();
2122
        try {
2123
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
2124
                new SPIRelationCreateStruct(
2125
                    [
2126
                        'sourceContentId' => $sourceContentInfo->id,
2127
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
2128
                        'sourceFieldDefinitionId' => null,
2129
                        'destinationContentId' => $destinationContent->id,
2130
                        'type' => APIRelation::COMMON,
2131
                    ]
2132
                )
2133
            );
2134
            $this->repository->commit();
2135
        } catch (Exception $e) {
2136
            $this->repository->rollback();
2137
            throw $e;
2138
        }
2139
2140
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
2141
    }
2142
2143
    /**
2144
     * Removes a relation of type COMMON from a draft.
2145
     *
2146
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
2147
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2148
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
2149
     *
2150
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2151
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
2152
     */
2153
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2154
    {
2155
        $sourceVersion = $this->loadVersionInfoById(
2156
            $sourceVersion->contentInfo->id,
2157
            $sourceVersion->versionNo
2158
        );
2159
2160
        if (!$sourceVersion->isDraft()) {
2161
            throw new BadStateException(
2162
                '$sourceVersion',
2163
                'Relations of type common can only be removed from versions of status draft'
2164
            );
2165
        }
2166
2167
        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...
2168
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2169
        }
2170
2171
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2172
            $sourceVersion->getContentInfo()->id,
2173
            $sourceVersion->versionNo,
2174
            APIRelation::COMMON
2175
        );
2176
2177
        if (empty($spiRelations)) {
2178
            throw new InvalidArgumentException(
2179
                '$sourceVersion',
2180
                'There are no relations of type COMMON for the given destination'
2181
            );
2182
        }
2183
2184
        // there should be only one relation of type COMMON for each destination,
2185
        // but in case there were ever more then one, we will remove them all
2186
        // @todo: alternatively, throw BadStateException?
2187
        $this->repository->beginTransaction();
2188
        try {
2189
            foreach ($spiRelations as $spiRelation) {
2190
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2191
                    $this->persistenceHandler->contentHandler()->removeRelation(
2192
                        $spiRelation->id,
2193
                        APIRelation::COMMON
2194
                    );
2195
                }
2196
            }
2197
            $this->repository->commit();
2198
        } catch (Exception $e) {
2199
            $this->repository->rollback();
2200
            throw $e;
2201
        }
2202
    }
2203
2204
    /**
2205
     * {@inheritdoc}
2206
     */
2207
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2208
    {
2209
        @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...
2210
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2211
            E_USER_DEPRECATED
2212
        );
2213
        $this->deleteTranslation($contentInfo, $languageCode);
2214
    }
2215
2216
    /**
2217
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2218
     *
2219
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2220
     *
2221
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2222
     *         is the Main Translation of a Content Item.
2223
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2224
     *         to delete the content (in one of the locations of the given Content Item).
2225
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2226
     *         is invalid for the given content.
2227
     *
2228
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2229
     * @param string $languageCode
2230
     *
2231
     * @since 6.13
2232
     */
2233
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2234
    {
2235
        if ($contentInfo->mainLanguageCode === $languageCode) {
2236
            throw new BadStateException(
2237
                '$languageCode',
2238
                'Specified translation is the main translation of the Content Object'
2239
            );
2240
        }
2241
2242
        $translationWasFound = false;
2243
        $this->repository->beginTransaction();
2244
        try {
2245
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2246
                if (!$this->repository->canUser('content', 'remove', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
2247
                    throw new UnauthorizedException(
2248
                        'content',
2249
                        'remove',
2250
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2251
                    );
2252
                }
2253
2254
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2255
                    continue;
2256
                }
2257
2258
                $translationWasFound = true;
2259
2260
                // If the translation is the version's only one, delete the version
2261
                if (count($versionInfo->languageCodes) < 2) {
2262
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2263
                        $versionInfo->getContentInfo()->id,
2264
                        $versionInfo->versionNo
2265
                    );
2266
                }
2267
            }
2268
2269
            if (!$translationWasFound) {
2270
                throw new InvalidArgumentException(
2271
                    '$languageCode',
2272
                    sprintf(
2273
                        '%s does not exist in the Content item(id=%d)',
2274
                        $languageCode,
2275
                        $contentInfo->id
2276
                    )
2277
                );
2278
            }
2279
2280
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2281
                $contentInfo->id,
2282
                $languageCode
2283
            );
2284
            $locationIds = array_map(
2285
                function (Location $location) {
2286
                    return $location->id;
2287
                },
2288
                $this->repository->getLocationService()->loadLocations($contentInfo)
2289
            );
2290
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2291
                $locationIds,
2292
                $languageCode
2293
            );
2294
            $this->repository->commit();
2295
        } catch (InvalidArgumentException $e) {
2296
            $this->repository->rollback();
2297
            throw $e;
2298
        } catch (BadStateException $e) {
2299
            $this->repository->rollback();
2300
            throw $e;
2301
        } catch (UnauthorizedException $e) {
2302
            $this->repository->rollback();
2303
            throw $e;
2304
        } catch (Exception $e) {
2305
            $this->repository->rollback();
2306
            // cover generic unexpected exception to fulfill API promise on @throws
2307
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2308
        }
2309
    }
2310
2311
    /**
2312
     * Delete specified Translation from a Content Draft.
2313
     *
2314
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2315
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2316
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2317
     *         to edit the Content (in one of the locations of the given Content Object).
2318
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2319
     *         is invalid for the given Draft.
2320
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2321
     *
2322
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2323
     * @param string $languageCode Language code of the Translation to be removed
2324
     *
2325
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2326
     *
2327
     * @since 6.12
2328
     */
2329
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2330
    {
2331
        if (!$versionInfo->isDraft()) {
2332
            throw new BadStateException(
2333
                '$versionInfo',
2334
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2335
            );
2336
        }
2337
2338
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2339
            throw new BadStateException(
2340
                '$languageCode',
2341
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2342
            );
2343
        }
2344
2345
        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...
2346
            throw new UnauthorizedException(
2347
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2348
            );
2349
        }
2350
2351
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2352
            throw new InvalidArgumentException(
2353
                '$languageCode',
2354
                sprintf(
2355
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2356
                    $versionInfo->contentInfo->id,
2357
                    $versionInfo->versionNo,
2358
                    $languageCode
2359
                )
2360
            );
2361
        }
2362
2363
        if (count($versionInfo->languageCodes) === 1) {
2364
            throw new BadStateException(
2365
                '$languageCode',
2366
                'Specified Translation is the only one Content Object Version has'
2367
            );
2368
        }
2369
2370
        $this->repository->beginTransaction();
2371
        try {
2372
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2373
                $versionInfo->contentInfo->id,
2374
                $versionInfo->versionNo,
2375
                $languageCode
2376
            );
2377
            $this->repository->commit();
2378
2379
            return $this->domainMapper->buildContentDomainObject(
2380
                $spiContent,
2381
                $this->repository->getContentTypeService()->loadContentType(
2382
                    $spiContent->versionInfo->contentInfo->contentTypeId
2383
                )
2384
            );
2385
        } catch (APINotFoundException $e) {
2386
            // avoid wrapping expected NotFoundException in BadStateException handled below
2387
            $this->repository->rollback();
2388
            throw $e;
2389
        } catch (Exception $e) {
2390
            $this->repository->rollback();
2391
            // cover generic unexpected exception to fulfill API promise on @throws
2392
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2393
        }
2394
    }
2395
2396
    /**
2397
     * Hides Content by making all the Locations appear hidden.
2398
     * It does not persist hidden state on Location object itself.
2399
     *
2400
     * Content hidden by this API can be revealed by revealContent API.
2401
     *
2402
     * @see revealContent
2403
     *
2404
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2405
     */
2406
    public function hideContent(ContentInfo $contentInfo): void
2407
    {
2408
        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...
2409
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2410
        }
2411
2412
        $this->repository->beginTransaction();
2413
        try {
2414
            $this->persistenceHandler->contentHandler()->updateMetadata(
2415
                $contentInfo->id,
2416
                new SPIMetadataUpdateStruct([
2417
                    'isHidden' => true,
2418
                ])
2419
            );
2420
            $locationHandler = $this->persistenceHandler->locationHandler();
2421
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2422
            foreach ($childLocations as $childLocation) {
2423
                $locationHandler->setInvisible($childLocation->id);
2424
            }
2425
            $this->repository->commit();
2426
        } catch (Exception $e) {
2427
            $this->repository->rollback();
2428
            throw $e;
2429
        }
2430
    }
2431
2432
    /**
2433
     * Reveals Content hidden by hideContent API.
2434
     * Locations which were hidden before hiding Content will remain hidden.
2435
     *
2436
     * @see hideContent
2437
     *
2438
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2439
     */
2440
    public function revealContent(ContentInfo $contentInfo): void
2441
    {
2442
        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...
2443
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2444
        }
2445
2446
        $this->repository->beginTransaction();
2447
        try {
2448
            $this->persistenceHandler->contentHandler()->updateMetadata(
2449
                $contentInfo->id,
2450
                new SPIMetadataUpdateStruct([
2451
                    'isHidden' => false,
2452
                ])
2453
            );
2454
            $locationHandler = $this->persistenceHandler->locationHandler();
2455
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2456
            foreach ($childLocations as $childLocation) {
2457
                $locationHandler->setVisible($childLocation->id);
2458
            }
2459
            $this->repository->commit();
2460
        } catch (Exception $e) {
2461
            $this->repository->rollback();
2462
            throw $e;
2463
        }
2464
    }
2465
2466
    /**
2467
     * Instantiates a new content create struct object.
2468
     *
2469
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2470
     *
2471
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2472
     * @param string $mainLanguageCode
2473
     *
2474
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2475
     */
2476
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2477
    {
2478
        return new ContentCreateStruct(
2479
            [
2480
                'contentType' => $contentType,
2481
                'mainLanguageCode' => $mainLanguageCode,
2482
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2483
            ]
2484
        );
2485
    }
2486
2487
    /**
2488
     * Instantiates a new content meta data update struct.
2489
     *
2490
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2491
     */
2492
    public function newContentMetadataUpdateStruct()
2493
    {
2494
        return new ContentMetadataUpdateStruct();
2495
    }
2496
2497
    /**
2498
     * Instantiates a new content update struct.
2499
     *
2500
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2501
     */
2502
    public function newContentUpdateStruct()
2503
    {
2504
        return new ContentUpdateStruct();
2505
    }
2506
2507
    /**
2508
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2509
     *
2510
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2511
     */
2512
    private function resolveUser(?User $user): UserReference
2513
    {
2514
        if ($user === null) {
2515
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2516
        }
2517
2518
        return $user;
2519
    }
2520
}
2521