Completed
Push — 7.5 ( 467086...6987e3 )
by Łukasz
19:03
created

ContentService   F

Complexity

Total Complexity 276

Size/Duplication

Total Lines 2377
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 60

Importance

Changes 0
Metric Value
dl 0
loc 2377
rs 0.8
c 0
b 0
f 0
wmc 276
lcom 1
cbo 60

51 Methods

Rating   Name   Duplication   Size   Complexity  
A loadContentByVersionInfo() 0 14 3
A publishUrlAliasesForContent() 0 26 4
A deleteContent() 0 27 4
A __construct() 0 21 1
A loadContentInfo() 0 9 2
A loadContentInfoList() 0 13 3
A internalLoadContentInfo() 0 16 3
A loadContentInfoByRemoteId() 0 10 2
A loadVersionInfo() 0 4 1
A loadVersionInfoById() 0 32 4
A loadContentByContentInfo() 0 14 3
A loadContent() 0 16 4
B internalLoadContent() 0 52 8
A loadContentByRemoteId() 0 17 4
B loadContentListByContentInfo() 0 41 7
F createContent() 0 202 28
A getDefaultObjectStates() 0 15 3
A getLanguageCodesForCreate() 0 24 5
B mapFieldsForCreate() 0 33 6
A cloneField() 0 15 1
B buildSPILocationCreateStructs() 0 42 5
F updateContentMetadata() 0 87 19
C createContentDraft() 0 80 9
A countContentDrafts() 0 10 2
A loadContentDrafts() 0 23 4
A loadContentDraftList() 0 32 5
F updateContent() 0 187 29
A getUpdatedLanguageCodes() 0 16 4
A getLanguageCodesForUpdate() 0 12 2
B mapFieldsForUpdate() 0 38 7
B publishVersion() 0 43 5
B copyTranslationsFromPublishedVersion() 0 49 9
B internalPublishVersion() 0 50 6
A getUnixTimestamp() 0 4 1
B deleteVersion() 0 42 6
B loadVersions() 0 29 6
B copyContent() 0 55 6
A loadRelations() 0 35 5
A countReverseRelations() 0 10 2
A loadReverseRelations() 0 26 4
A addRelation() 0 41 4
B deleteRelation() 0 50 7
A removeTranslation() 0 8 1
C deleteTranslation() 0 77 11
B deleteTranslationFromDraft() 0 66 8
A hideContent() 0 25 4
A revealContent() 0 25 4
A newContentCreateStruct() 0 10 1
A newContentMetadataUpdateStruct() 0 4 1
A newContentUpdateStruct() 0 4 1
A resolveUser() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like ContentService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ContentService, and based on these observations, apply Extract Interface, too.

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\User\UserReference;
17
use eZ\Publish\Core\Repository\Values\Content\Location;
18
use eZ\Publish\API\Repository\Values\Content\Language;
19
use eZ\Publish\SPI\Persistence\Handler;
20
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct as APIContentUpdateStruct;
21
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
22
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
23
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
24
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
25
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
26
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
27
use eZ\Publish\API\Repository\Values\User\User;
28
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
29
use eZ\Publish\API\Repository\Values\Content\Field;
30
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
31
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
32
use eZ\Publish\Core\Base\Exceptions\BadStateException;
33
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
34
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
35
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
36
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
37
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
38
use eZ\Publish\Core\FieldType\ValidationError;
39
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
40
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
41
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
42
use eZ\Publish\SPI\Limitation\Target;
43
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
44
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
45
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
46
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
47
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
48
use Exception;
49
50
/**
51
 * This class provides service methods for managing content.
52
 *
53
 * @example Examples/content.php
54
 */
55
class ContentService implements ContentServiceInterface
56
{
57
    /** @var \eZ\Publish\Core\Repository\Repository */
58
    protected $repository;
59
60
    /** @var \eZ\Publish\SPI\Persistence\Handler */
61
    protected $persistenceHandler;
62
63
    /** @var array */
64
    protected $settings;
65
66
    /** @var \eZ\Publish\Core\Repository\Helper\DomainMapper */
67
    protected $domainMapper;
68
69
    /** @var \eZ\Publish\Core\Repository\Helper\RelationProcessor */
70
    protected $relationProcessor;
71
72
    /** @var \eZ\Publish\Core\Repository\Helper\NameSchemaService */
73
    protected $nameSchemaService;
74
75
    /** @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry */
76
    protected $fieldTypeRegistry;
77
78
    /**
79
     * Setups service with reference to repository object that created it & corresponding handler.
80
     *
81
     * @param \eZ\Publish\API\Repository\Repository $repository
82
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
83
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
84
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
85
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
86
     * @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...
87
     * @param array $settings
88
     */
89
    public function __construct(
90
        RepositoryInterface $repository,
91
        Handler $handler,
92
        Helper\DomainMapper $domainMapper,
93
        Helper\RelationProcessor $relationProcessor,
94
        Helper\NameSchemaService $nameSchemaService,
95
        Helper\FieldTypeRegistry $fieldTypeRegistry,
96
        array $settings = []
97
    ) {
98
        $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...
99
        $this->persistenceHandler = $handler;
100
        $this->domainMapper = $domainMapper;
101
        $this->relationProcessor = $relationProcessor;
102
        $this->nameSchemaService = $nameSchemaService;
103
        $this->fieldTypeRegistry = $fieldTypeRegistry;
104
        // Union makes sure default settings are ignored if provided in argument
105
        $this->settings = $settings + [
106
            // Version archive limit (0-50), only enforced on publish, not on un-publish.
107
            'default_version_archive_limit' => 5,
108
        ];
109
    }
110
111
    /**
112
     * Loads a content info object.
113
     *
114
     * To load fields use loadContent
115
     *
116
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
117
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
118
     *
119
     * @param int $contentId
120
     *
121
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
122
     */
123
    public function loadContentInfo($contentId)
124
    {
125
        $contentInfo = $this->internalLoadContentInfo($contentId);
126
        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...
127
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
128
        }
129
130
        return $contentInfo;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function loadContentInfoList(array $contentIds): iterable
137
    {
138
        $contentInfoList = [];
139
        $spiInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList($contentIds);
140
        foreach ($spiInfoList as $id => $spiInfo) {
141
            $contentInfo = $this->domainMapper->buildContentInfoDomainObject($spiInfo);
142
            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...
143
                $contentInfoList[$id] = $contentInfo;
144
            }
145
        }
146
147
        return $contentInfoList;
148
    }
149
150
    /**
151
     * Loads a content info object.
152
     *
153
     * To load fields use loadContent
154
     *
155
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
156
     *
157
     * @param mixed $id
158
     * @param bool $isRemoteId
159
     *
160
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
161
     */
162
    public function internalLoadContentInfo($id, $isRemoteId = false)
163
    {
164
        try {
165
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
166
167
            return $this->domainMapper->buildContentInfoDomainObject(
168
                $this->persistenceHandler->contentHandler()->$method($id)
169
            );
170
        } catch (APINotFoundException $e) {
171
            throw new NotFoundException(
172
                'Content',
173
                $id,
174
                $e
175
            );
176
        }
177
    }
178
179
    /**
180
     * Loads a content info object for the given remoteId.
181
     *
182
     * To load fields use loadContent
183
     *
184
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
185
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
186
     *
187
     * @param string $remoteId
188
     *
189
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
190
     */
191
    public function loadContentInfoByRemoteId($remoteId)
192
    {
193
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
194
195
        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...
196
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
197
        }
198
199
        return $contentInfo;
200
    }
201
202
    /**
203
     * Loads a version info of the given content object.
204
     *
205
     * If no version number is given, the method returns the current version
206
     *
207
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
208
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
209
     *
210
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
211
     * @param int $versionNo the version number. If not given the current version is returned.
212
     *
213
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
214
     */
215
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
216
    {
217
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
218
    }
219
220
    /**
221
     * Loads a version info of the given content object id.
222
     *
223
     * If no version number is given, the method returns the current version
224
     *
225
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
226
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
227
     *
228
     * @param mixed $contentId
229
     * @param int $versionNo the version number. If not given the current version is returned.
230
     *
231
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
232
     */
233
    public function loadVersionInfoById($contentId, $versionNo = null)
234
    {
235
        try {
236
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
237
                $contentId,
238
                $versionNo
239
            );
240
        } catch (APINotFoundException $e) {
241
            throw new NotFoundException(
242
                'VersionInfo',
243
                [
244
                    'contentId' => $contentId,
245
                    'versionNo' => $versionNo,
246
                ],
247
                $e
248
            );
249
        }
250
251
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
252
253
        if ($versionInfo->isPublished()) {
254
            $function = 'read';
255
        } else {
256
            $function = 'versionread';
257
        }
258
259
        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...
260
            throw new UnauthorizedException('content', $function, ['contentId' => $contentId]);
261
        }
262
263
        return $versionInfo;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
270
    {
271
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
272
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
273
            $useAlwaysAvailable = false;
274
        }
275
276
        return $this->loadContent(
277
            $contentInfo->id,
278
            $languages,
279
            $versionNo,// On purpose pass as-is and not use $contentInfo, to make sure to return actual current version on null
280
            $useAlwaysAvailable
281
        );
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
288
    {
289
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
290
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
291
            $useAlwaysAvailable = false;
292
        }
293
294
        return $this->loadContent(
295
            $versionInfo->getContentInfo()->id,
296
            $languages,
297
            $versionInfo->versionNo,
298
            $useAlwaysAvailable
299
        );
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
306
    {
307
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
308
309
        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...
310
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
311
        }
312
        if (
313
            !$content->getVersionInfo()->isPublished()
314
            && !$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...
315
        ) {
316
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId, 'versionNo' => $versionNo]);
317
        }
318
319
        return $content;
320
    }
321
322
    /**
323
     * Loads content in a version of the given content object.
324
     *
325
     * If no version number is given, the method returns the current version
326
     *
327
     * @internal
328
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
329
     *
330
     * @param mixed $id
331
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
332
     *                         returned value object. If not given all languages are returned.
333
     * @param int|null $versionNo the version number. If not given the current version is returned
334
     * @param bool $isRemoteId
335
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
336
     *
337
     * @return \eZ\Publish\API\Repository\Values\Content\Content
338
     */
339
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
340
    {
341
        try {
342
            // Get Content ID if lookup by remote ID
343
            if ($isRemoteId) {
344
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
345
                $id = $spiContentInfo->id;
346
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
347
                $isRemoteId = false;
348
            }
349
350
            $loadLanguages = $languages;
351
            $alwaysAvailableLanguageCode = null;
352
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
353
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
354
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
355
                if (!isset($spiContentInfo)) {
356
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
357
                }
358
359
                if ($spiContentInfo->alwaysAvailable) {
360
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
361
                    $loadLanguages = array_unique($loadLanguages);
362
                }
363
            }
364
365
            $spiContent = $this->persistenceHandler->contentHandler()->load(
366
                $id,
367
                $versionNo,
368
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 350 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...
369
            );
370
        } catch (APINotFoundException $e) {
371
            throw new NotFoundException(
372
                'Content',
373
                [
374
                    $isRemoteId ? 'remoteId' : 'id' => $id,
375
                    'languages' => $languages,
376
                    'versionNo' => $versionNo,
377
                ],
378
                $e
379
            );
380
        }
381
382
        return $this->domainMapper->buildContentDomainObject(
383
            $spiContent,
384
            $this->repository->getContentTypeService()->loadContentType(
385
                $spiContent->versionInfo->contentInfo->contentTypeId
386
            ),
387
            $languages ?? [],
388
            $alwaysAvailableLanguageCode
389
        );
390
    }
391
392
    /**
393
     * Loads content in a version for the content object reference by the given remote id.
394
     *
395
     * If no version is given, the method returns the current version
396
     *
397
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
398
     * @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
399
     *
400
     * @param string $remoteId
401
     * @param array $languages A language filter for fields. If not given all languages are returned
402
     * @param int $versionNo the version number. If not given the current version is returned
403
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
404
     *
405
     * @return \eZ\Publish\API\Repository\Values\Content\Content
406
     */
407
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
408
    {
409
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
410
411
        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...
412
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
413
        }
414
415
        if (
416
            !$content->getVersionInfo()->isPublished()
417
            && !$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...
418
        ) {
419
            throw new UnauthorizedException('content', 'versionread', ['remoteId' => $remoteId, 'versionNo' => $versionNo]);
420
        }
421
422
        return $content;
423
    }
424
425
    /**
426
     * Bulk-load Content items by the list of ContentInfo Value Objects.
427
     *
428
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
429
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
430
     * allowed to access every Content on the list.
431
     *
432
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
433
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
434
     *                            returned value object. If not given all languages are returned.
435
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
436
     *                                 unless all languages have been asked for.
437
     *
438
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
439
     */
440
    public function loadContentListByContentInfo(
441
        array $contentInfoList,
442
        array $languages = [],
443
        $useAlwaysAvailable = true
444
    ) {
445
        $loadAllLanguages = $languages === Language::ALL;
446
        $contentIds = [];
447
        $contentTypeIds = [];
448
        $translations = $languages;
449
        foreach ($contentInfoList as $contentInfo) {
450
            $contentIds[] = $contentInfo->id;
451
            $contentTypeIds[] = $contentInfo->contentTypeId;
452
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
453
            // Might in some case load more languages then intended, but prioritised handling will pick right one
454
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
455
                $translations[] = $contentInfo->mainLanguageCode;
456
            }
457
        }
458
459
        $contentList = [];
460
        $translations = array_unique($translations);
461
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
462
            $contentIds,
463
            $translations
464
        );
465
        $contentTypeList = $this->repository->getContentTypeService()->loadContentTypeList(
466
            array_unique($contentTypeIds),
467
            $languages
468
        );
469
        foreach ($spiContentList as $contentId => $spiContent) {
470
            $contentInfo = $spiContent->versionInfo->contentInfo;
471
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
472
                $spiContent,
473
                $contentTypeList[$contentInfo->contentTypeId],
474
                $languages,
475
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
476
            );
477
        }
478
479
        return $contentList;
480
    }
481
482
    /**
483
     * Creates a new content draft assigned to the authenticated user.
484
     *
485
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
486
     * but this required special rights for the authenticated user
487
     * (this is useful for content staging where the transfer process does not
488
     * have to authenticate with the user which created the content object in the source server).
489
     * The user has to publish the draft if it should be visible.
490
     * In 4.x at least one location has to be provided in the location creation array.
491
     *
492
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
493
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
494
     *                                                                        struct are missing or invalid, or if multiple locations are under the
495
     *                                                                        same parent.
496
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
497
     *                                                                               or if a required field is missing / set to an empty value.
498
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
499
     *                                                                          or value is set for non-translatable field in language
500
     *                                                                          other than main.
501
     *
502
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
503
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
504
     *
505
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
506
     */
507
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = [])
508
    {
509
        if ($contentCreateStruct->mainLanguageCode === null) {
510
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
511
        }
512
513
        if ($contentCreateStruct->contentType === null) {
514
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
515
        }
516
517
        $contentCreateStruct = clone $contentCreateStruct;
518
519
        if ($contentCreateStruct->ownerId === null) {
520
            $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...
521
        }
522
523
        if ($contentCreateStruct->alwaysAvailable === null) {
524
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
525
        }
526
527
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
528
            $contentCreateStruct->contentType->id
529
        );
530
531
        if (empty($contentCreateStruct->sectionId)) {
532
            if (isset($locationCreateStructs[0])) {
533
                $location = $this->repository->getLocationService()->loadLocation(
534
                    $locationCreateStructs[0]->parentLocationId
535
                );
536
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
537
            } else {
538
                $contentCreateStruct->sectionId = 1;
539
            }
540
        }
541
542
        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...
543
            throw new UnauthorizedException(
544
                'content',
545
                'create',
546
                [
547
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
548
                            $locationCreateStructs[0]->parentLocationId :
549
                            null,
550
                    'sectionId' => $contentCreateStruct->sectionId,
551
                ]
552
            );
553
        }
554
555
        if (!empty($contentCreateStruct->remoteId)) {
556
            try {
557
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
558
559
                throw new InvalidArgumentException(
560
                    '$contentCreateStruct',
561
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
562
                );
563
            } catch (APINotFoundException $e) {
564
                // Do nothing
565
            }
566
        } else {
567
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
568
        }
569
570
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
571
572
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
573
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
574
575
        $fieldValues = [];
576
        $spiFields = [];
577
        $allFieldErrors = [];
578
        $inputRelations = [];
579
        $locationIdToContentIdMapping = [];
580
581
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
582
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
583
            $fieldType = $this->fieldTypeRegistry->getFieldType(
584
                $fieldDefinition->fieldTypeIdentifier
585
            );
586
587
            foreach ($languageCodes as $languageCode) {
588
                $isEmptyValue = false;
589
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
590
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
591
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
592
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
593
                } else {
594
                    $fieldValue = $fieldDefinition->defaultValue;
595
                }
596
597
                $fieldValue = $fieldType->acceptValue($fieldValue);
598
599
                if ($fieldType->isEmptyValue($fieldValue)) {
600
                    $isEmptyValue = true;
601
                    if ($fieldDefinition->isRequired) {
602
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
603
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
604
                            null,
605
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
606
                            'empty'
607
                        );
608
                    }
609
                } else {
610
                    $fieldErrors = $fieldType->validate(
611
                        $fieldDefinition,
612
                        $fieldValue
613
                    );
614
                    if (!empty($fieldErrors)) {
615
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
616
                    }
617
                }
618
619
                if (!empty($allFieldErrors)) {
620
                    continue;
621
                }
622
623
                $this->relationProcessor->appendFieldRelations(
624
                    $inputRelations,
625
                    $locationIdToContentIdMapping,
626
                    $fieldType,
627
                    $fieldValue,
628
                    $fieldDefinition->id
629
                );
630
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
631
632
                // Only non-empty value for: translatable field or in main language
633
                if (
634
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
635
                    (!$isEmptyValue && $isLanguageMain)
636
                ) {
637
                    $spiFields[] = new SPIField(
638
                        [
639
                            'id' => null,
640
                            'fieldDefinitionId' => $fieldDefinition->id,
641
                            'type' => $fieldDefinition->fieldTypeIdentifier,
642
                            'value' => $fieldType->toPersistenceValue($fieldValue),
643
                            'languageCode' => $languageCode,
644
                            'versionNo' => null,
645
                        ]
646
                    );
647
                }
648
            }
649
        }
650
651
        if (!empty($allFieldErrors)) {
652
            throw new ContentFieldValidationException($allFieldErrors);
653
        }
654
655
        $spiContentCreateStruct = new SPIContentCreateStruct(
656
            [
657
                'name' => $this->nameSchemaService->resolve(
658
                    $contentCreateStruct->contentType->nameSchema,
659
                    $contentCreateStruct->contentType,
660
                    $fieldValues,
661
                    $languageCodes
662
                ),
663
                'typeId' => $contentCreateStruct->contentType->id,
664
                'sectionId' => $contentCreateStruct->sectionId,
665
                'ownerId' => $contentCreateStruct->ownerId,
666
                'locations' => $spiLocationCreateStructs,
667
                'fields' => $spiFields,
668
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
669
                'remoteId' => $contentCreateStruct->remoteId,
670
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
671
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
672
                    $contentCreateStruct->mainLanguageCode
673
                )->id,
674
            ]
675
        );
676
677
        $defaultObjectStates = $this->getDefaultObjectStates();
678
679
        $this->repository->beginTransaction();
680
        try {
681
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
682
            $this->relationProcessor->processFieldRelations(
683
                $inputRelations,
684
                $spiContent->versionInfo->contentInfo->id,
685
                $spiContent->versionInfo->versionNo,
686
                $contentCreateStruct->contentType
687
            );
688
689
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
690
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
691
                $objectStateHandler->setContentState(
692
                    $spiContent->versionInfo->contentInfo->id,
693
                    $objectStateGroupId,
694
                    $objectState->id
695
                );
696
            }
697
698
            $this->repository->commit();
699
        } catch (Exception $e) {
700
            $this->repository->rollback();
701
            throw $e;
702
        }
703
704
        return $this->domainMapper->buildContentDomainObject(
705
            $spiContent,
706
            $contentCreateStruct->contentType
707
        );
708
    }
709
710
    /**
711
     * Returns an array of default content states with content state group id as key.
712
     *
713
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
714
     */
715
    protected function getDefaultObjectStates()
716
    {
717
        $defaultObjectStatesMap = [];
718
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
719
720
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
721
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
722
                // Only register the first object state which is the default one.
723
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
724
                break;
725
            }
726
        }
727
728
        return $defaultObjectStatesMap;
729
    }
730
731
    /**
732
     * Returns all language codes used in given $fields.
733
     *
734
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
735
     *
736
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
737
     *
738
     * @return string[]
739
     */
740
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
741
    {
742
        $languageCodes = [];
743
744
        foreach ($contentCreateStruct->fields as $field) {
745
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
746
                continue;
747
            }
748
749
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
750
                $field->languageCode
751
            );
752
            $languageCodes[$field->languageCode] = true;
753
        }
754
755
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
756
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
757
                $contentCreateStruct->mainLanguageCode
758
            );
759
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
760
        }
761
762
        return array_keys($languageCodes);
763
    }
764
765
    /**
766
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
767
     *
768
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
769
     *                                                                          or value is set for non-translatable field in language
770
     *                                                                          other than main
771
     *
772
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
773
     *
774
     * @return array
775
     */
776
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
777
    {
778
        $fields = [];
779
780
        foreach ($contentCreateStruct->fields as $field) {
781
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
782
783
            if ($fieldDefinition === null) {
784
                throw new ContentValidationException(
785
                    "Field definition '%identifier%' does not exist in given ContentType",
786
                    ['%identifier%' => $field->fieldDefIdentifier]
787
                );
788
            }
789
790
            if ($field->languageCode === null) {
791
                $field = $this->cloneField(
792
                    $field,
793
                    ['languageCode' => $contentCreateStruct->mainLanguageCode]
794
                );
795
            }
796
797
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $contentCreateStruct->mainLanguageCode)) {
798
                throw new ContentValidationException(
799
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
800
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
801
                );
802
            }
803
804
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
805
        }
806
807
        return $fields;
808
    }
809
810
    /**
811
     * Clones $field with overriding specific properties from given $overrides array.
812
     *
813
     * @param Field $field
814
     * @param array $overrides
815
     *
816
     * @return Field
817
     */
818
    private function cloneField(Field $field, array $overrides = [])
819
    {
820
        $fieldData = array_merge(
821
            [
822
                'id' => $field->id,
823
                'value' => $field->value,
824
                'languageCode' => $field->languageCode,
825
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
826
                'fieldTypeIdentifier' => $field->fieldTypeIdentifier,
827
            ],
828
            $overrides
829
        );
830
831
        return new Field($fieldData);
832
    }
833
834
    /**
835
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
836
     *
837
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
838
     *
839
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
840
     */
841
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
842
    {
843
        $spiLocationCreateStructs = [];
844
        $parentLocationIdSet = [];
845
        $mainLocation = true;
846
847
        foreach ($locationCreateStructs as $locationCreateStruct) {
848
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
849
                throw new InvalidArgumentException(
850
                    '$locationCreateStructs',
851
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
852
                );
853
            }
854
855
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
856
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
857
            }
858
859
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
860
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
861
            }
862
863
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
864
            $parentLocation = $this->repository->getLocationService()->loadLocation(
865
                $locationCreateStruct->parentLocationId
866
            );
867
868
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
869
                $locationCreateStruct,
870
                $parentLocation,
871
                $mainLocation,
872
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
873
                null,
874
                null
875
            );
876
877
            // First Location in the list will be created as main Location
878
            $mainLocation = false;
879
        }
880
881
        return $spiLocationCreateStructs;
882
    }
883
884
    /**
885
     * Updates the metadata.
886
     *
887
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
888
     *
889
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
890
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
891
     *
892
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
893
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
894
     *
895
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
896
     */
897
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
898
    {
899
        $propertyCount = 0;
900
        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...
901
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
902
                $propertyCount += 1;
903
            }
904
        }
905
        if ($propertyCount === 0) {
906
            throw new InvalidArgumentException(
907
                '$contentMetadataUpdateStruct',
908
                'At least one property must be set'
909
            );
910
        }
911
912
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
913
914
        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...
915
            throw new UnauthorizedException('content', 'edit', ['contentId' => $loadedContentInfo->id]);
916
        }
917
918
        if (isset($contentMetadataUpdateStruct->remoteId)) {
919
            try {
920
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
921
922
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
923
                    throw new InvalidArgumentException(
924
                        '$contentMetadataUpdateStruct',
925
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
926
                    );
927
                }
928
            } catch (APINotFoundException $e) {
929
                // Do nothing
930
            }
931
        }
932
933
        $this->repository->beginTransaction();
934
        try {
935
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
936
                $this->persistenceHandler->contentHandler()->updateMetadata(
937
                    $loadedContentInfo->id,
938
                    new SPIMetadataUpdateStruct(
939
                        [
940
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
941
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
942
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
943
                                null,
944
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
945
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
946
                                null,
947
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
948
                                $this->repository->getContentLanguageService()->loadLanguage(
949
                                    $contentMetadataUpdateStruct->mainLanguageCode
950
                                )->id :
951
                                null,
952
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
953
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
954
                            'name' => $contentMetadataUpdateStruct->name,
955
                        ]
956
                    )
957
                );
958
            }
959
960
            // Change main location
961
            if (isset($contentMetadataUpdateStruct->mainLocationId)
962
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
963
                $this->persistenceHandler->locationHandler()->changeMainLocation(
964
                    $loadedContentInfo->id,
965
                    $contentMetadataUpdateStruct->mainLocationId
966
                );
967
            }
968
969
            // Republish URL aliases to update always-available flag
970
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
971
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
972
                $content = $this->loadContent($loadedContentInfo->id);
973
                $this->publishUrlAliasesForContent($content, false);
974
            }
975
976
            $this->repository->commit();
977
        } catch (Exception $e) {
978
            $this->repository->rollback();
979
            throw $e;
980
        }
981
982
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
983
    }
984
985
    /**
986
     * Publishes URL aliases for all locations of a given content.
987
     *
988
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
989
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
990
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
991
     */
992
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
993
    {
994
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
995
        $locations = $this->repository->getLocationService()->loadLocations(
996
            $content->getVersionInfo()->getContentInfo()
997
        );
998
        $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
999
        foreach ($locations as $location) {
1000
            foreach ($urlAliasNames as $languageCode => $name) {
1001
                $urlAliasHandler->publishUrlAliasForLocation(
1002
                    $location->id,
1003
                    $location->parentLocationId,
1004
                    $name,
1005
                    $languageCode,
1006
                    $content->contentInfo->alwaysAvailable,
1007
                    $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...
1008
                );
1009
            }
1010
            // archive URL aliases of Translations that got deleted
1011
            $urlAliasHandler->archiveUrlAliasesForDeletedTranslations(
1012
                $location->id,
1013
                $location->parentLocationId,
1014
                $content->versionInfo->languageCodes
1015
            );
1016
        }
1017
    }
1018
1019
    /**
1020
     * Deletes a content object including all its versions and locations including their subtrees.
1021
     *
1022
     * @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)
1023
     *
1024
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1025
     *
1026
     * @return mixed[] Affected Location Id's
1027
     */
1028
    public function deleteContent(ContentInfo $contentInfo)
1029
    {
1030
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1031
1032
        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...
1033
            throw new UnauthorizedException('content', 'remove', ['contentId' => $contentInfo->id]);
1034
        }
1035
1036
        $affectedLocations = [];
1037
        $this->repository->beginTransaction();
1038
        try {
1039
            // Load Locations first as deleting Content also deletes belonging Locations
1040
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1041
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1042
            $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
1043
            foreach ($spiLocations as $spiLocation) {
1044
                $urlAliasHandler->locationDeleted($spiLocation->id);
1045
                $affectedLocations[] = $spiLocation->id;
1046
            }
1047
            $this->repository->commit();
1048
        } catch (Exception $e) {
1049
            $this->repository->rollback();
1050
            throw $e;
1051
        }
1052
1053
        return $affectedLocations;
1054
    }
1055
1056
    /**
1057
     * Creates a draft from a published or archived version.
1058
     *
1059
     * If no version is given, the current published version is used.
1060
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1061
     * It can be changed on updating the version.
1062
     *
1063
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1064
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1065
     * @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
1066
     *
1067
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1068
     *
1069
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
1070
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the current-user is not allowed to create the draft
1071
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1072
     */
1073
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1074
    {
1075
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1076
1077
        if ($versionInfo !== null) {
1078
            // Check that given $contentInfo and $versionInfo belong to the same content
1079
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1080
                throw new InvalidArgumentException(
1081
                    '$versionInfo',
1082
                    'VersionInfo does not belong to the same content as given ContentInfo'
1083
                );
1084
            }
1085
1086
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1087
1088
            switch ($versionInfo->status) {
1089
                case VersionInfo::STATUS_PUBLISHED:
1090
                case VersionInfo::STATUS_ARCHIVED:
1091
                    break;
1092
1093
                default:
1094
                    // @todo: throw an exception here, to be defined
1095
                    throw new BadStateException(
1096
                        '$versionInfo',
1097
                        'Draft can not be created from a draft version'
1098
                    );
1099
            }
1100
1101
            $versionNo = $versionInfo->versionNo;
1102
        } elseif ($contentInfo->published) {
1103
            $versionNo = $contentInfo->currentVersionNo;
1104
        } else {
1105
            // @todo: throw an exception here, to be defined
1106
            throw new BadStateException(
1107
                '$contentInfo',
1108
                'Content is not published, draft can be created only from published or archived version'
1109
            );
1110
        }
1111
1112
        if ($creator === null) {
1113
            $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...
1114
        }
1115
1116
        if (!$this->repository->getPermissionResolver()->canUser(
1117
            'content',
1118
            'edit',
1119
            $contentInfo,
1120
            [
1121
                (new Target\Builder\VersionBuilder())
1122
                    ->changeStatusTo(APIVersionInfo::STATUS_DRAFT)
1123
                    ->build(),
1124
            ]
1125
        )) {
1126
            throw new UnauthorizedException(
1127
                'content',
1128
                'edit',
1129
                ['contentId' => $contentInfo->id]
1130
            );
1131
        }
1132
1133
        $this->repository->beginTransaction();
1134
        try {
1135
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1136
                $contentInfo->id,
1137
                $versionNo,
1138
                $creator->getUserId()
1139
            );
1140
            $this->repository->commit();
1141
        } catch (Exception $e) {
1142
            $this->repository->rollback();
1143
            throw $e;
1144
        }
1145
1146
        return $this->domainMapper->buildContentDomainObject(
1147
            $spiContent,
1148
            $this->repository->getContentTypeService()->loadContentType(
1149
                $spiContent->versionInfo->contentInfo->contentTypeId
1150
            )
1151
        );
1152
    }
1153
1154
    /**
1155
     * {@inheritdoc}
1156
     */
1157
    public function countContentDrafts(?User $user = null): int
1158
    {
1159
        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...
1160
            return 0;
1161
        }
1162
1163
        return $this->persistenceHandler->contentHandler()->countDraftsForUser(
1164
            $this->resolveUser($user)->getUserId()
1165
        );
1166
    }
1167
1168
    /**
1169
     * Loads drafts for a user.
1170
     *
1171
     * If no user is given the drafts for the authenticated user are returned
1172
     *
1173
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
1174
     *
1175
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Drafts owned by the given user
1176
     *
1177
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1178
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1179
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1180
     */
1181
    public function loadContentDrafts(User $user = null)
1182
    {
1183
        // throw early if user has absolutely no access to versionread
1184
        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...
1185
            throw new UnauthorizedException('content', 'versionread');
1186
        }
1187
1188
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser(
1189
            $this->resolveUser($user)->getUserId()
1190
        );
1191
        $versionInfoList = [];
1192
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1193
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1194
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1195
            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...
1196
                throw new UnauthorizedException('content', 'versionread', ['contentId' => $versionInfo->contentInfo->id]);
1197
            }
1198
1199
            $versionInfoList[] = $versionInfo;
1200
        }
1201
1202
        return $versionInfoList;
1203
    }
1204
1205
    /**
1206
     * {@inheritdoc}
1207
     */
1208
    public function loadContentDraftList(?User $user = null, int $offset = 0, int $limit = -1): ContentDraftList
1209
    {
1210
        $list = new ContentDraftList();
1211
        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...
1212
            return $list;
1213
        }
1214
1215
        $list->totalCount = $this->persistenceHandler->contentHandler()->countDraftsForUser(
1216
            $this->resolveUser($user)->getUserId()
1217
        );
1218
        if ($list->totalCount > 0) {
1219
            $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftListForUser(
1220
                $this->resolveUser($user)->getUserId(),
1221
                $offset,
1222
                $limit
1223
            );
1224
            foreach ($spiVersionInfoList as $spiVersionInfo) {
1225
                $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1226
                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...
1227
                    $list->items[] = new ContentDraftListItem($versionInfo);
1228
                } else {
1229
                    $list->items[] = new UnauthorizedContentDraftListItem(
1230
                        'content',
1231
                        'versionread',
1232
                        ['contentId' => $versionInfo->contentInfo->id]
1233
                    );
1234
                }
1235
            }
1236
        }
1237
1238
        return $list;
1239
    }
1240
1241
    /**
1242
     * Updates the fields of a draft.
1243
     *
1244
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1245
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1246
     *
1247
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1248
     *
1249
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1250
     *                                                                               or if a required field is missing / set to an empty value.
1251
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1252
     *                                                                          or value is set for non-translatable field in language
1253
     *                                                                          other than main.
1254
     *
1255
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1256
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1257
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1258
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1259
     */
1260
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1261
    {
1262
        $contentUpdateStruct = clone $contentUpdateStruct;
1263
1264
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1265
        $content = $this->loadContent(
1266
            $versionInfo->getContentInfo()->id,
1267
            null,
1268
            $versionInfo->versionNo
1269
        );
1270
        if (!$content->versionInfo->isDraft()) {
1271
            throw new BadStateException(
1272
                '$versionInfo',
1273
                'Version is not a draft and can not be updated'
1274
            );
1275
        }
1276
1277
        if (!$this->repository->getPermissionResolver()->canUser(
1278
            'content',
1279
            'edit',
1280
            $content,
1281
            [
1282
                (new Target\Builder\VersionBuilder())
1283
                    ->updateFieldsTo(
1284
                        $contentUpdateStruct->initialLanguageCode,
1285
                        $contentUpdateStruct->fields
1286
                    )
1287
                    ->build(),
1288
            ]
1289
        )) {
1290
            throw new UnauthorizedException('content', 'edit', ['contentId' => $content->id]);
1291
        }
1292
1293
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1294
        if ($contentUpdateStruct->initialLanguageCode === null) {
1295
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1296
        }
1297
1298
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1299
        $contentLanguageHandler = $this->persistenceHandler->contentLanguageHandler();
1300
        foreach ($allLanguageCodes as $languageCode) {
1301
            $contentLanguageHandler->loadByLanguageCode($languageCode);
1302
        }
1303
1304
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1305
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1306
            $content->contentInfo->contentTypeId
1307
        );
1308
        $fields = $this->mapFieldsForUpdate(
1309
            $contentUpdateStruct,
1310
            $contentType,
1311
            $mainLanguageCode
1312
        );
1313
1314
        $fieldValues = [];
1315
        $spiFields = [];
1316
        $allFieldErrors = [];
1317
        $inputRelations = [];
1318
        $locationIdToContentIdMapping = [];
1319
1320
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1321
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1322
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1323
                $fieldDefinition->fieldTypeIdentifier
1324
            );
1325
1326
            foreach ($allLanguageCodes as $languageCode) {
1327
                $isCopied = $isEmpty = $isRetained = false;
1328
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1329
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1330
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1331
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1332
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1333
1334
                if (!$isFieldUpdated && !$isLanguageNew) {
1335
                    $isRetained = true;
1336
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1337
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1338
                    $isCopied = true;
1339
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1340
                } elseif ($isFieldUpdated) {
1341
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1342
                } else {
1343
                    $fieldValue = $fieldDefinition->defaultValue;
1344
                }
1345
1346
                $fieldValue = $fieldType->acceptValue($fieldValue);
1347
1348
                if ($fieldType->isEmptyValue($fieldValue)) {
1349
                    $isEmpty = true;
1350
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1351
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1352
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1353
                            null,
1354
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1355
                            'empty'
1356
                        );
1357
                    }
1358
                } elseif ($isLanguageUpdated) {
1359
                    $fieldErrors = $fieldType->validate(
1360
                        $fieldDefinition,
1361
                        $fieldValue
1362
                    );
1363
                    if (!empty($fieldErrors)) {
1364
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1365
                    }
1366
                }
1367
1368
                if (!empty($allFieldErrors)) {
1369
                    continue;
1370
                }
1371
1372
                $this->relationProcessor->appendFieldRelations(
1373
                    $inputRelations,
1374
                    $locationIdToContentIdMapping,
1375
                    $fieldType,
1376
                    $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...
1377
                    $fieldDefinition->id
1378
                );
1379
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1380
1381
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1382
                    continue;
1383
                }
1384
1385
                $spiFields[] = new SPIField(
1386
                    [
1387
                        'id' => $isLanguageNew ?
1388
                            null :
1389
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1390
                        'fieldDefinitionId' => $fieldDefinition->id,
1391
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1392
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1393
                        'languageCode' => $languageCode,
1394
                        'versionNo' => $versionInfo->versionNo,
1395
                    ]
1396
                );
1397
            }
1398
        }
1399
1400
        if (!empty($allFieldErrors)) {
1401
            throw new ContentFieldValidationException($allFieldErrors);
1402
        }
1403
1404
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1405
            [
1406
                'name' => $this->nameSchemaService->resolveNameSchema(
1407
                    $content,
1408
                    $fieldValues,
1409
                    $allLanguageCodes,
1410
                    $contentType
1411
                ),
1412
                '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...
1413
                'fields' => $spiFields,
1414
                'modificationDate' => time(),
1415
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1416
                    $contentUpdateStruct->initialLanguageCode
1417
                )->id,
1418
            ]
1419
        );
1420
        $existingRelations = $this->loadRelations($versionInfo);
1421
1422
        $this->repository->beginTransaction();
1423
        try {
1424
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1425
                $versionInfo->getContentInfo()->id,
1426
                $versionInfo->versionNo,
1427
                $spiContentUpdateStruct
1428
            );
1429
            $this->relationProcessor->processFieldRelations(
1430
                $inputRelations,
1431
                $spiContent->versionInfo->contentInfo->id,
1432
                $spiContent->versionInfo->versionNo,
1433
                $contentType,
1434
                $existingRelations
1435
            );
1436
            $this->repository->commit();
1437
        } catch (Exception $e) {
1438
            $this->repository->rollback();
1439
            throw $e;
1440
        }
1441
1442
        return $this->domainMapper->buildContentDomainObject(
1443
            $spiContent,
1444
            $contentType
1445
        );
1446
    }
1447
1448
    /**
1449
     * Returns only updated language codes.
1450
     *
1451
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1452
     *
1453
     * @return array
1454
     */
1455
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1456
    {
1457
        $languageCodes = [
1458
            $contentUpdateStruct->initialLanguageCode => true,
1459
        ];
1460
1461
        foreach ($contentUpdateStruct->fields as $field) {
1462
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1463
                continue;
1464
            }
1465
1466
            $languageCodes[$field->languageCode] = true;
1467
        }
1468
1469
        return array_keys($languageCodes);
1470
    }
1471
1472
    /**
1473
     * Returns all language codes used in given $fields.
1474
     *
1475
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1476
     *
1477
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1478
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1479
     *
1480
     * @return array
1481
     */
1482
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1483
    {
1484
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1485
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1486
1487
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1488
        foreach ($updatedLanguageCodes as $languageCode) {
1489
            $languageCodes[$languageCode] = true;
1490
        }
1491
1492
        return array_keys($languageCodes);
1493
    }
1494
1495
    /**
1496
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1497
     *
1498
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1499
     *                                                                          or value is set for non-translatable field in language
1500
     *                                                                          other than main
1501
     *
1502
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1503
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1504
     * @param string $mainLanguageCode
1505
     *
1506
     * @return array
1507
     */
1508
    protected function mapFieldsForUpdate(
1509
        APIContentUpdateStruct $contentUpdateStruct,
1510
        ContentType $contentType,
1511
        $mainLanguageCode
1512
    ) {
1513
        $fields = [];
1514
1515
        foreach ($contentUpdateStruct->fields as $field) {
1516
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1517
1518
            if ($fieldDefinition === null) {
1519
                throw new ContentValidationException(
1520
                    "Field definition '%identifier%' does not exist in given ContentType",
1521
                    ['%identifier%' => $field->fieldDefIdentifier]
1522
                );
1523
            }
1524
1525
            if ($field->languageCode === null) {
1526
                if ($fieldDefinition->isTranslatable) {
1527
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1528
                } else {
1529
                    $languageCode = $mainLanguageCode;
1530
                }
1531
                $field = $this->cloneField($field, ['languageCode' => $languageCode]);
1532
            }
1533
1534
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $mainLanguageCode)) {
1535
                throw new ContentValidationException(
1536
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1537
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1538
                );
1539
            }
1540
1541
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1542
        }
1543
1544
        return $fields;
1545
    }
1546
1547
    /**
1548
     * Publishes a content version.
1549
     *
1550
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1551
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1552
     *
1553
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1554
     * @param string[] $translations
1555
     *
1556
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1557
     *
1558
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1559
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1560
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1561
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1562
     */
1563
    public function publishVersion(APIVersionInfo $versionInfo, array $translations = Language::ALL)
1564
    {
1565
        $content = $this->internalLoadContent(
1566
            $versionInfo->contentInfo->id,
1567
            null,
1568
            $versionInfo->versionNo
1569
        );
1570
1571
        $fromContent = null;
0 ignored issues
show
Unused Code introduced by
$fromContent is not used, you could remove the assignment.

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

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

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

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

Loading history...
1572
        if ($content->contentInfo->currentVersionNo !== $versionInfo->versionNo) {
1573
            $fromContent = $this->internalLoadContent(
1574
                $content->contentInfo->id,
1575
                null,
1576
                $content->contentInfo->currentVersionNo
1577
            );
1578
            // should not occur now, might occur in case of un-publish
1579
            if (!$fromContent->contentInfo->isPublished()) {
1580
                $fromContent = null;
0 ignored issues
show
Unused Code introduced by
$fromContent is not used, you could remove the assignment.

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

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

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

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

Loading history...
1581
            }
1582
        }
1583
1584
        if (!$this->repository->getPermissionResolver()->canUser(
1585
            'content',
1586
            'publish',
1587
            $content
1588
        )) {
1589
            throw new UnauthorizedException(
1590
                'content', 'publish', ['contentId' => $content->id]
1591
            );
1592
        }
1593
1594
        $this->repository->beginTransaction();
1595
        try {
1596
            $this->copyTranslationsFromPublishedVersion($content->versionInfo, $translations);
1597
            $content = $this->internalPublishVersion($content->getVersionInfo(), null);
1598
            $this->repository->commit();
1599
        } catch (Exception $e) {
1600
            $this->repository->rollback();
1601
            throw $e;
1602
        }
1603
1604
        return $content;
1605
    }
1606
1607
    /**
1608
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1609
     * @param array $translations
1610
     *
1611
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1612
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
1613
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
1614
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1615
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1616
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1617
     */
1618
    protected function copyTranslationsFromPublishedVersion(APIVersionInfo $versionInfo, array $translations = []): void
1619
    {
1620
        $contendId = $versionInfo->contentInfo->id;
1621
1622
        $currentContent = $this->internalLoadContent($contendId);
1623
        $currentVersionInfo = $currentContent->versionInfo;
1624
1625
        // Copying occurs only if:
1626
        // - There is published Version
1627
        // - Published version is older than the currently published one unless specific translations are provided.
1628
        if (!$currentVersionInfo->isPublished() ||
1629
            ($versionInfo->versionNo >= $currentVersionInfo->versionNo && empty($translations))) {
1630
            return;
1631
        }
1632
1633
        if (empty($translations)) {
1634
            $languagesToCopy = array_diff(
1635
                $currentVersionInfo->languageCodes,
1636
                $versionInfo->languageCodes
1637
            );
1638
        } else {
1639
            $languagesToCopy = array_diff(
1640
                $currentVersionInfo->languageCodes,
1641
                $translations
1642
            );
1643
        }
1644
1645
        if (empty($languagesToCopy)) {
1646
            return;
1647
        }
1648
1649
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1650
            $currentVersionInfo->contentInfo->contentTypeId
1651
        );
1652
1653
        // Find only translatable fields to update with selected languages
1654
        $updateStruct = $this->newContentUpdateStruct();
1655
        $updateStruct->initialLanguageCode = $versionInfo->initialLanguageCode;
1656
1657
        foreach ($currentContent->getFields() as $field) {
1658
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1659
1660
            if ($fieldDefinition->isTranslatable && in_array($field->languageCode, $languagesToCopy)) {
1661
                $updateStruct->setField($field->fieldDefIdentifier, $field->value, $field->languageCode);
1662
            }
1663
        }
1664
1665
        $this->updateContent($versionInfo, $updateStruct);
1666
    }
1667
1668
    /**
1669
     * Publishes a content version.
1670
     *
1671
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1672
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1673
     *
1674
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1675
     *
1676
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1677
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1678
     *
1679
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1680
     */
1681
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1682
    {
1683
        if (!$versionInfo->isDraft()) {
1684
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1685
        }
1686
1687
        $currentTime = $this->getUnixTimestamp();
1688
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1689
            $publicationDate = $currentTime;
1690
        }
1691
1692
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1693
        $metadataUpdateStruct->publicationDate = $publicationDate;
1694
        $metadataUpdateStruct->modificationDate = $currentTime;
1695
1696
        $contentId = $versionInfo->getContentInfo()->id;
1697
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1698
            $contentId,
1699
            $versionInfo->versionNo,
1700
            $metadataUpdateStruct
1701
        );
1702
1703
        $content = $this->domainMapper->buildContentDomainObject(
1704
            $spiContent,
1705
            $this->repository->getContentTypeService()->loadContentType(
1706
                $spiContent->versionInfo->contentInfo->contentTypeId
1707
            )
1708
        );
1709
1710
        $this->publishUrlAliasesForContent($content);
1711
1712
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1713
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1714
            $contentId,
1715
            APIVersionInfo::STATUS_ARCHIVED,
1716
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1717
        );
1718
1719
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1720
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1721
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1722
            $archiveVersion = array_shift($archiveList);
1723
            $this->persistenceHandler->contentHandler()->deleteVersion(
1724
                $contentId,
1725
                $archiveVersion->versionNo
1726
            );
1727
        }
1728
1729
        return $content;
1730
    }
1731
1732
    /**
1733
     * @return int
1734
     */
1735
    protected function getUnixTimestamp()
1736
    {
1737
        return time();
1738
    }
1739
1740
    /**
1741
     * Removes the given version.
1742
     *
1743
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1744
     *         published state or is a last version of Content in non draft state
1745
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1746
     *
1747
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1748
     */
1749
    public function deleteVersion(APIVersionInfo $versionInfo)
1750
    {
1751
        if ($versionInfo->isPublished()) {
1752
            throw new BadStateException(
1753
                '$versionInfo',
1754
                'Version is published and can not be removed'
1755
            );
1756
        }
1757
1758
        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...
1759
            throw new UnauthorizedException(
1760
                'content',
1761
                'versionremove',
1762
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1763
            );
1764
        }
1765
1766
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1767
            $versionInfo->contentInfo->id,
1768
            null,
1769
            2
1770
        );
1771
1772
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1773
            throw new BadStateException(
1774
                '$versionInfo',
1775
                'Version is the last version of the Content and can not be removed'
1776
            );
1777
        }
1778
1779
        $this->repository->beginTransaction();
1780
        try {
1781
            $this->persistenceHandler->contentHandler()->deleteVersion(
1782
                $versionInfo->getContentInfo()->id,
1783
                $versionInfo->versionNo
1784
            );
1785
            $this->repository->commit();
1786
        } catch (Exception $e) {
1787
            $this->repository->rollback();
1788
            throw $e;
1789
        }
1790
    }
1791
1792
    /**
1793
     * Loads all versions for the given content.
1794
     *
1795
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1796
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the given status is invalid
1797
     *
1798
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1799
     * @param int|null $status
1800
     *
1801
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1802
     */
1803
    public function loadVersions(ContentInfo $contentInfo, ?int $status = null)
1804
    {
1805
        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...
1806
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1807
        }
1808
1809
        if ($status !== null && !in_array((int)$status, [VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED], true)) {
1810
            throw new InvalidArgumentException(
1811
                'status',
1812
                sprintf(
1813
                    'it can be one of %d (draft), %d (published), %d (archived), %d given',
1814
                    VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED, $status
1815
                ));
1816
        }
1817
1818
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id, $status);
1819
1820
        $versions = [];
1821
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1822
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1823
            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...
1824
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1825
            }
1826
1827
            $versions[] = $versionInfo;
1828
        }
1829
1830
        return $versions;
1831
    }
1832
1833
    /**
1834
     * Copies the content to a new location. If no version is given,
1835
     * all versions are copied, otherwise only the given version.
1836
     *
1837
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1838
     *
1839
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1840
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1841
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1842
     *
1843
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1844
     */
1845
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1846
    {
1847
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1848
            $destinationLocationCreateStruct->parentLocationId
1849
        );
1850
        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...
1851
            throw new UnauthorizedException(
1852
                'content',
1853
                'create',
1854
                [
1855
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1856
                    'sectionId' => $contentInfo->sectionId,
1857
                ]
1858
            );
1859
        }
1860
        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...
1861
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
1862
        }
1863
1864
        $defaultObjectStates = $this->getDefaultObjectStates();
1865
1866
        $this->repository->beginTransaction();
1867
        try {
1868
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1869
                $contentInfo->id,
1870
                $versionInfo ? $versionInfo->versionNo : null,
1871
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1872
            );
1873
1874
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1875
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1876
                $objectStateHandler->setContentState(
1877
                    $spiContent->versionInfo->contentInfo->id,
1878
                    $objectStateGroupId,
1879
                    $objectState->id
1880
                );
1881
            }
1882
1883
            $content = $this->internalPublishVersion(
1884
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1885
                $spiContent->versionInfo->creationDate
1886
            );
1887
1888
            $this->repository->getLocationService()->createLocation(
1889
                $content->getVersionInfo()->getContentInfo(),
1890
                $destinationLocationCreateStruct
1891
            );
1892
            $this->repository->commit();
1893
        } catch (Exception $e) {
1894
            $this->repository->rollback();
1895
            throw $e;
1896
        }
1897
1898
        return $this->internalLoadContent($content->id);
1899
    }
1900
1901
    /**
1902
     * Loads all outgoing relations for the given version.
1903
     *
1904
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1905
     *
1906
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1907
     *
1908
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1909
     */
1910
    public function loadRelations(APIVersionInfo $versionInfo)
1911
    {
1912
        if ($versionInfo->isPublished()) {
1913
            $function = 'read';
1914
        } else {
1915
            $function = 'versionread';
1916
        }
1917
1918
        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...
1919
            throw new UnauthorizedException('content', $function);
1920
        }
1921
1922
        $contentInfo = $versionInfo->getContentInfo();
1923
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1924
            $contentInfo->id,
1925
            $versionInfo->versionNo
1926
        );
1927
1928
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1929
        $relations = [];
1930
        foreach ($spiRelations as $spiRelation) {
1931
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1932
            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...
1933
                continue;
1934
            }
1935
1936
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1937
                $spiRelation,
1938
                $contentInfo,
1939
                $destinationContentInfo
1940
            );
1941
        }
1942
1943
        return $relations;
1944
    }
1945
1946
    /**
1947
     * {@inheritdoc}
1948
     */
1949
    public function countReverseRelations(ContentInfo $contentInfo): int
1950
    {
1951
        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...
1952
            return 0;
1953
        }
1954
1955
        return $this->persistenceHandler->contentHandler()->countReverseRelations(
1956
            $contentInfo->id
1957
        );
1958
    }
1959
1960
    /**
1961
     * Loads all incoming relations for a content object.
1962
     *
1963
     * The relations come only from published versions of the source content objects
1964
     *
1965
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1966
     *
1967
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1968
     *
1969
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1970
     */
1971
    public function loadReverseRelations(ContentInfo $contentInfo)
1972
    {
1973
        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...
1974
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
1975
        }
1976
1977
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1978
            $contentInfo->id
1979
        );
1980
1981
        $returnArray = [];
1982
        foreach ($spiRelations as $spiRelation) {
1983
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1984
            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...
1985
                continue;
1986
            }
1987
1988
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1989
                $spiRelation,
1990
                $sourceContentInfo,
1991
                $contentInfo
1992
            );
1993
        }
1994
1995
        return $returnArray;
1996
    }
1997
1998
    /**
1999
     * Adds a relation of type common.
2000
     *
2001
     * The source of the relation is the content and version
2002
     * referenced by $versionInfo.
2003
     *
2004
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
2005
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2006
     *
2007
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2008
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
2009
     *
2010
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
2011
     */
2012
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2013
    {
2014
        $sourceVersion = $this->loadVersionInfoById(
2015
            $sourceVersion->contentInfo->id,
2016
            $sourceVersion->versionNo
2017
        );
2018
2019
        if (!$sourceVersion->isDraft()) {
2020
            throw new BadStateException(
2021
                '$sourceVersion',
2022
                'Relations of type common can only be added to versions of status draft'
2023
            );
2024
        }
2025
2026
        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...
2027
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2028
        }
2029
2030
        $sourceContentInfo = $sourceVersion->getContentInfo();
2031
2032
        $this->repository->beginTransaction();
2033
        try {
2034
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
2035
                new SPIRelationCreateStruct(
2036
                    [
2037
                        'sourceContentId' => $sourceContentInfo->id,
2038
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
2039
                        'sourceFieldDefinitionId' => null,
2040
                        'destinationContentId' => $destinationContent->id,
2041
                        'type' => APIRelation::COMMON,
2042
                    ]
2043
                )
2044
            );
2045
            $this->repository->commit();
2046
        } catch (Exception $e) {
2047
            $this->repository->rollback();
2048
            throw $e;
2049
        }
2050
2051
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
2052
    }
2053
2054
    /**
2055
     * Removes a relation of type COMMON from a draft.
2056
     *
2057
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
2058
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2059
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
2060
     *
2061
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2062
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
2063
     */
2064
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2065
    {
2066
        $sourceVersion = $this->loadVersionInfoById(
2067
            $sourceVersion->contentInfo->id,
2068
            $sourceVersion->versionNo
2069
        );
2070
2071
        if (!$sourceVersion->isDraft()) {
2072
            throw new BadStateException(
2073
                '$sourceVersion',
2074
                'Relations of type common can only be removed from versions of status draft'
2075
            );
2076
        }
2077
2078
        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...
2079
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2080
        }
2081
2082
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2083
            $sourceVersion->getContentInfo()->id,
2084
            $sourceVersion->versionNo,
2085
            APIRelation::COMMON
2086
        );
2087
2088
        if (empty($spiRelations)) {
2089
            throw new InvalidArgumentException(
2090
                '$sourceVersion',
2091
                'There are no relations of type COMMON for the given destination'
2092
            );
2093
        }
2094
2095
        // there should be only one relation of type COMMON for each destination,
2096
        // but in case there were ever more then one, we will remove them all
2097
        // @todo: alternatively, throw BadStateException?
2098
        $this->repository->beginTransaction();
2099
        try {
2100
            foreach ($spiRelations as $spiRelation) {
2101
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2102
                    $this->persistenceHandler->contentHandler()->removeRelation(
2103
                        $spiRelation->id,
2104
                        APIRelation::COMMON
2105
                    );
2106
                }
2107
            }
2108
            $this->repository->commit();
2109
        } catch (Exception $e) {
2110
            $this->repository->rollback();
2111
            throw $e;
2112
        }
2113
    }
2114
2115
    /**
2116
     * {@inheritdoc}
2117
     */
2118
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2119
    {
2120
        @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...
2121
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2122
            E_USER_DEPRECATED
2123
        );
2124
        $this->deleteTranslation($contentInfo, $languageCode);
2125
    }
2126
2127
    /**
2128
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2129
     *
2130
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2131
     *
2132
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2133
     *         is the Main Translation of a Content Item.
2134
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2135
     *         to delete the content (in one of the locations of the given Content Item).
2136
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2137
     *         is invalid for the given content.
2138
     *
2139
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2140
     * @param string $languageCode
2141
     *
2142
     * @since 6.13
2143
     */
2144
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2145
    {
2146
        if ($contentInfo->mainLanguageCode === $languageCode) {
2147
            throw new BadStateException(
2148
                '$languageCode',
2149
                'Specified translation is the main translation of the Content Object'
2150
            );
2151
        }
2152
2153
        $translationWasFound = false;
2154
        $this->repository->beginTransaction();
2155
        try {
2156
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2157
                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...
2158
                    throw new UnauthorizedException(
2159
                        'content',
2160
                        'remove',
2161
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2162
                    );
2163
                }
2164
2165
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2166
                    continue;
2167
                }
2168
2169
                $translationWasFound = true;
2170
2171
                // If the translation is the version's only one, delete the version
2172
                if (count($versionInfo->languageCodes) < 2) {
2173
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2174
                        $versionInfo->getContentInfo()->id,
2175
                        $versionInfo->versionNo
2176
                    );
2177
                }
2178
            }
2179
2180
            if (!$translationWasFound) {
2181
                throw new InvalidArgumentException(
2182
                    '$languageCode',
2183
                    sprintf(
2184
                        '%s does not exist in the Content item(id=%d)',
2185
                        $languageCode,
2186
                        $contentInfo->id
2187
                    )
2188
                );
2189
            }
2190
2191
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2192
                $contentInfo->id,
2193
                $languageCode
2194
            );
2195
            $locationIds = array_map(
2196
                function (Location $location) {
2197
                    return $location->id;
2198
                },
2199
                $this->repository->getLocationService()->loadLocations($contentInfo)
2200
            );
2201
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2202
                $locationIds,
2203
                $languageCode
2204
            );
2205
            $this->repository->commit();
2206
        } catch (InvalidArgumentException $e) {
2207
            $this->repository->rollback();
2208
            throw $e;
2209
        } catch (BadStateException $e) {
2210
            $this->repository->rollback();
2211
            throw $e;
2212
        } catch (UnauthorizedException $e) {
2213
            $this->repository->rollback();
2214
            throw $e;
2215
        } catch (Exception $e) {
2216
            $this->repository->rollback();
2217
            // cover generic unexpected exception to fulfill API promise on @throws
2218
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2219
        }
2220
    }
2221
2222
    /**
2223
     * Delete specified Translation from a Content Draft.
2224
     *
2225
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2226
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2227
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2228
     *         to edit the Content (in one of the locations of the given Content Object).
2229
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2230
     *         is invalid for the given Draft.
2231
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2232
     *
2233
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2234
     * @param string $languageCode Language code of the Translation to be removed
2235
     *
2236
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2237
     *
2238
     * @since 6.12
2239
     */
2240
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2241
    {
2242
        if (!$versionInfo->isDraft()) {
2243
            throw new BadStateException(
2244
                '$versionInfo',
2245
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2246
            );
2247
        }
2248
2249
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2250
            throw new BadStateException(
2251
                '$languageCode',
2252
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2253
            );
2254
        }
2255
2256
        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...
2257
            throw new UnauthorizedException(
2258
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2259
            );
2260
        }
2261
2262
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2263
            throw new InvalidArgumentException(
2264
                '$languageCode',
2265
                sprintf(
2266
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2267
                    $versionInfo->contentInfo->id,
2268
                    $versionInfo->versionNo,
2269
                    $languageCode
2270
                )
2271
            );
2272
        }
2273
2274
        if (count($versionInfo->languageCodes) === 1) {
2275
            throw new BadStateException(
2276
                '$languageCode',
2277
                'Specified Translation is the only one Content Object Version has'
2278
            );
2279
        }
2280
2281
        $this->repository->beginTransaction();
2282
        try {
2283
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2284
                $versionInfo->contentInfo->id,
2285
                $versionInfo->versionNo,
2286
                $languageCode
2287
            );
2288
            $this->repository->commit();
2289
2290
            return $this->domainMapper->buildContentDomainObject(
2291
                $spiContent,
2292
                $this->repository->getContentTypeService()->loadContentType(
2293
                    $spiContent->versionInfo->contentInfo->contentTypeId
2294
                )
2295
            );
2296
        } catch (APINotFoundException $e) {
2297
            // avoid wrapping expected NotFoundException in BadStateException handled below
2298
            $this->repository->rollback();
2299
            throw $e;
2300
        } catch (Exception $e) {
2301
            $this->repository->rollback();
2302
            // cover generic unexpected exception to fulfill API promise on @throws
2303
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2304
        }
2305
    }
2306
2307
    /**
2308
     * Hides Content by making all the Locations appear hidden.
2309
     * It does not persist hidden state on Location object itself.
2310
     *
2311
     * Content hidden by this API can be revealed by revealContent API.
2312
     *
2313
     * @see revealContent
2314
     *
2315
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2316
     */
2317
    public function hideContent(ContentInfo $contentInfo): void
2318
    {
2319
        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...
2320
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2321
        }
2322
2323
        $this->repository->beginTransaction();
2324
        try {
2325
            $this->persistenceHandler->contentHandler()->updateMetadata(
2326
                $contentInfo->id,
2327
                new SPIMetadataUpdateStruct([
2328
                    'isHidden' => true,
2329
                ])
2330
            );
2331
            $locationHandler = $this->persistenceHandler->locationHandler();
2332
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2333
            foreach ($childLocations as $childLocation) {
2334
                $locationHandler->setInvisible($childLocation->id);
2335
            }
2336
            $this->repository->commit();
2337
        } catch (Exception $e) {
2338
            $this->repository->rollback();
2339
            throw $e;
2340
        }
2341
    }
2342
2343
    /**
2344
     * Reveals Content hidden by hideContent API.
2345
     * Locations which were hidden before hiding Content will remain hidden.
2346
     *
2347
     * @see hideContent
2348
     *
2349
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2350
     */
2351
    public function revealContent(ContentInfo $contentInfo): void
2352
    {
2353
        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...
2354
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2355
        }
2356
2357
        $this->repository->beginTransaction();
2358
        try {
2359
            $this->persistenceHandler->contentHandler()->updateMetadata(
2360
                $contentInfo->id,
2361
                new SPIMetadataUpdateStruct([
2362
                    'isHidden' => false,
2363
                ])
2364
            );
2365
            $locationHandler = $this->persistenceHandler->locationHandler();
2366
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2367
            foreach ($childLocations as $childLocation) {
2368
                $locationHandler->setVisible($childLocation->id);
2369
            }
2370
            $this->repository->commit();
2371
        } catch (Exception $e) {
2372
            $this->repository->rollback();
2373
            throw $e;
2374
        }
2375
    }
2376
2377
    /**
2378
     * Instantiates a new content create struct object.
2379
     *
2380
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2381
     *
2382
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2383
     * @param string $mainLanguageCode
2384
     *
2385
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2386
     */
2387
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2388
    {
2389
        return new ContentCreateStruct(
2390
            [
2391
                'contentType' => $contentType,
2392
                'mainLanguageCode' => $mainLanguageCode,
2393
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2394
            ]
2395
        );
2396
    }
2397
2398
    /**
2399
     * Instantiates a new content meta data update struct.
2400
     *
2401
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2402
     */
2403
    public function newContentMetadataUpdateStruct()
2404
    {
2405
        return new ContentMetadataUpdateStruct();
2406
    }
2407
2408
    /**
2409
     * Instantiates a new content update struct.
2410
     *
2411
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2412
     */
2413
    public function newContentUpdateStruct()
2414
    {
2415
        return new ContentUpdateStruct();
2416
    }
2417
2418
    /**
2419
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2420
     *
2421
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2422
     */
2423
    private function resolveUser(?User $user): UserReference
2424
    {
2425
        if ($user === null) {
2426
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2427
        }
2428
2429
        return $user;
2430
    }
2431
}
2432