Completed
Push — EZP-30997 ( af07b3...5db064 )
by
unknown
19:07
created

ContentService::loadContentInfoList()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
2162
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2163
        }
2164
2165
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2166
            $sourceVersion->getContentInfo()->id,
2167
            $sourceVersion->versionNo,
2168
            APIRelation::COMMON
2169
        );
2170
2171
        if (empty($spiRelations)) {
2172
            throw new InvalidArgumentException(
2173
                '$sourceVersion',
2174
                'There are no relations of type COMMON for the given destination'
2175
            );
2176
        }
2177
2178
        // there should be only one relation of type COMMON for each destination,
2179
        // but in case there were ever more then one, we will remove them all
2180
        // @todo: alternatively, throw BadStateException?
2181
        $this->repository->beginTransaction();
2182
        try {
2183
            foreach ($spiRelations as $spiRelation) {
2184
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2185
                    $this->persistenceHandler->contentHandler()->removeRelation(
2186
                        $spiRelation->id,
2187
                        APIRelation::COMMON
2188
                    );
2189
                }
2190
            }
2191
            $this->repository->commit();
2192
        } catch (Exception $e) {
2193
            $this->repository->rollback();
2194
            throw $e;
2195
        }
2196
    }
2197
2198
    /**
2199
     * {@inheritdoc}
2200
     */
2201
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2202
    {
2203
        @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...
2204
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2205
            E_USER_DEPRECATED
2206
        );
2207
        $this->deleteTranslation($contentInfo, $languageCode);
2208
    }
2209
2210
    /**
2211
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2212
     *
2213
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2214
     *
2215
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2216
     *         is the Main Translation of a Content Item.
2217
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2218
     *         to delete the content (in one of the locations of the given Content Item).
2219
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2220
     *         is invalid for the given content.
2221
     *
2222
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2223
     * @param string $languageCode
2224
     *
2225
     * @since 6.13
2226
     */
2227
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2228
    {
2229
        if ($contentInfo->mainLanguageCode === $languageCode) {
2230
            throw new BadStateException(
2231
                '$languageCode',
2232
                'Specified translation is the main translation of the Content Object'
2233
            );
2234
        }
2235
2236
        $translationWasFound = false;
2237
        $this->repository->beginTransaction();
2238
        try {
2239
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2240
                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...
2241
                    throw new UnauthorizedException(
2242
                        'content',
2243
                        'remove',
2244
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2245
                    );
2246
                }
2247
2248
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2249
                    continue;
2250
                }
2251
2252
                $translationWasFound = true;
2253
2254
                // If the translation is the version's only one, delete the version
2255
                if (count($versionInfo->languageCodes) < 2) {
2256
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2257
                        $versionInfo->getContentInfo()->id,
2258
                        $versionInfo->versionNo
2259
                    );
2260
                }
2261
            }
2262
2263
            if (!$translationWasFound) {
2264
                throw new InvalidArgumentException(
2265
                    '$languageCode',
2266
                    sprintf(
2267
                        '%s does not exist in the Content item(id=%d)',
2268
                        $languageCode,
2269
                        $contentInfo->id
2270
                    )
2271
                );
2272
            }
2273
2274
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2275
                $contentInfo->id,
2276
                $languageCode
2277
            );
2278
            $locationIds = array_map(
2279
                function (Location $location) {
2280
                    return $location->id;
2281
                },
2282
                $this->repository->getLocationService()->loadLocations($contentInfo)
2283
            );
2284
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2285
                $locationIds,
2286
                $languageCode
2287
            );
2288
            $this->repository->commit();
2289
        } catch (InvalidArgumentException $e) {
2290
            $this->repository->rollback();
2291
            throw $e;
2292
        } catch (BadStateException $e) {
2293
            $this->repository->rollback();
2294
            throw $e;
2295
        } catch (UnauthorizedException $e) {
2296
            $this->repository->rollback();
2297
            throw $e;
2298
        } catch (Exception $e) {
2299
            $this->repository->rollback();
2300
            // cover generic unexpected exception to fulfill API promise on @throws
2301
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2302
        }
2303
    }
2304
2305
    /**
2306
     * Delete specified Translation from a Content Draft.
2307
     *
2308
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2309
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2310
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2311
     *         to edit the Content (in one of the locations of the given Content Object).
2312
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2313
     *         is invalid for the given Draft.
2314
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2315
     *
2316
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2317
     * @param string $languageCode Language code of the Translation to be removed
2318
     *
2319
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2320
     *
2321
     * @since 6.12
2322
     */
2323
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2324
    {
2325
        if (!$versionInfo->isDraft()) {
2326
            throw new BadStateException(
2327
                '$versionInfo',
2328
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2329
            );
2330
        }
2331
2332
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2333
            throw new BadStateException(
2334
                '$languageCode',
2335
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2336
            );
2337
        }
2338
2339
        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...
2340
            throw new UnauthorizedException(
2341
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2342
            );
2343
        }
2344
2345
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2346
            throw new InvalidArgumentException(
2347
                '$languageCode',
2348
                sprintf(
2349
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2350
                    $versionInfo->contentInfo->id,
2351
                    $versionInfo->versionNo,
2352
                    $languageCode
2353
                )
2354
            );
2355
        }
2356
2357
        if (count($versionInfo->languageCodes) === 1) {
2358
            throw new BadStateException(
2359
                '$languageCode',
2360
                'Specified Translation is the only one Content Object Version has'
2361
            );
2362
        }
2363
2364
        $this->repository->beginTransaction();
2365
        try {
2366
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2367
                $versionInfo->contentInfo->id,
2368
                $versionInfo->versionNo,
2369
                $languageCode
2370
            );
2371
            $this->repository->commit();
2372
2373
            return $this->domainMapper->buildContentDomainObject(
2374
                $spiContent,
2375
                $this->repository->getContentTypeService()->loadContentType(
2376
                    $spiContent->versionInfo->contentInfo->contentTypeId
2377
                )
2378
            );
2379
        } catch (APINotFoundException $e) {
2380
            // avoid wrapping expected NotFoundException in BadStateException handled below
2381
            $this->repository->rollback();
2382
            throw $e;
2383
        } catch (Exception $e) {
2384
            $this->repository->rollback();
2385
            // cover generic unexpected exception to fulfill API promise on @throws
2386
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2387
        }
2388
    }
2389
2390
    /**
2391
     * Hides Content by making all the Locations appear hidden.
2392
     * It does not persist hidden state on Location object itself.
2393
     *
2394
     * Content hidden by this API can be revealed by revealContent API.
2395
     *
2396
     * @see revealContent
2397
     *
2398
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2399
     */
2400
    public function hideContent(ContentInfo $contentInfo): void
2401
    {
2402
        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...
2403
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2404
        }
2405
2406
        $this->repository->beginTransaction();
2407
        try {
2408
            $this->persistenceHandler->contentHandler()->updateMetadata(
2409
                $contentInfo->id,
2410
                new SPIMetadataUpdateStruct([
2411
                    'isHidden' => true,
2412
                ])
2413
            );
2414
            $locationHandler = $this->persistenceHandler->locationHandler();
2415
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2416
            foreach ($childLocations as $childLocation) {
2417
                $locationHandler->setInvisible($childLocation->id);
2418
            }
2419
            $this->repository->commit();
2420
        } catch (Exception $e) {
2421
            $this->repository->rollback();
2422
            throw $e;
2423
        }
2424
    }
2425
2426
    /**
2427
     * Reveals Content hidden by hideContent API.
2428
     * Locations which were hidden before hiding Content will remain hidden.
2429
     *
2430
     * @see hideContent
2431
     *
2432
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2433
     */
2434
    public function revealContent(ContentInfo $contentInfo): void
2435
    {
2436
        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...
2437
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2438
        }
2439
2440
        $this->repository->beginTransaction();
2441
        try {
2442
            $this->persistenceHandler->contentHandler()->updateMetadata(
2443
                $contentInfo->id,
2444
                new SPIMetadataUpdateStruct([
2445
                    'isHidden' => false,
2446
                ])
2447
            );
2448
            $locationHandler = $this->persistenceHandler->locationHandler();
2449
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2450
            foreach ($childLocations as $childLocation) {
2451
                $locationHandler->setVisible($childLocation->id);
2452
            }
2453
            $this->repository->commit();
2454
        } catch (Exception $e) {
2455
            $this->repository->rollback();
2456
            throw $e;
2457
        }
2458
    }
2459
2460
    /**
2461
     * Instantiates a new content create struct object.
2462
     *
2463
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2464
     *
2465
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2466
     * @param string $mainLanguageCode
2467
     *
2468
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2469
     */
2470
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2471
    {
2472
        return new ContentCreateStruct(
2473
            [
2474
                'contentType' => $contentType,
2475
                'mainLanguageCode' => $mainLanguageCode,
2476
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2477
            ]
2478
        );
2479
    }
2480
2481
    /**
2482
     * Instantiates a new content meta data update struct.
2483
     *
2484
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2485
     */
2486
    public function newContentMetadataUpdateStruct()
2487
    {
2488
        return new ContentMetadataUpdateStruct();
2489
    }
2490
2491
    /**
2492
     * Instantiates a new content update struct.
2493
     *
2494
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2495
     */
2496
    public function newContentUpdateStruct()
2497
    {
2498
        return new ContentUpdateStruct();
2499
    }
2500
2501
    /**
2502
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2503
     *
2504
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2505
     */
2506
    private function resolveUser(?User $user): UserReference
2507
    {
2508
        if ($user === null) {
2509
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2510
        }
2511
2512
        return $user;
2513
    }
2514
}
2515