Completed
Push — EZP-31681 ( e049ce...b3a798 )
by
unknown
18:44
created

ContentService::fieldValuesAreEqual()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
1418
                    $fieldDefinition->id
1419
                );
1420
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1421
1422
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1423
                    continue;
1424
                }
1425
1426
                $spiFields[] = new SPIField(
1427
                    [
1428
                        'id' => $isLanguageNew ?
1429
                            null :
1430
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1431
                        'fieldDefinitionId' => $fieldDefinition->id,
1432
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1433
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1434
                        'languageCode' => $languageCode,
1435
                        'versionNo' => $versionInfo->versionNo,
1436
                    ]
1437
                );
1438
            }
1439
        }
1440
1441
        if (!empty($allFieldErrors)) {
1442
            throw new ContentFieldValidationException($allFieldErrors);
1443
        }
1444
1445
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1446
            [
1447
                'name' => $this->nameSchemaService->resolveNameSchema(
1448
                    $content,
1449
                    $fieldValues,
1450
                    $allLanguageCodes,
1451
                    $contentType
1452
                ),
1453
                'creatorId' => $contentUpdateStruct->creatorId ?: $this->repository->getCurrentUserReference()->getUserId(),
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Reposito...tCurrentUserReference() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::getCurrentUserReference() instead. Get current user reference.

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

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

Loading history...
1454
                'fields' => $spiFields,
1455
                'modificationDate' => time(),
1456
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1457
                    $contentUpdateStruct->initialLanguageCode
1458
                )->id,
1459
            ]
1460
        );
1461
        $existingRelations = $this->internalLoadRelations($versionInfo);
1462
1463
        $this->repository->beginTransaction();
1464
        try {
1465
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1466
                $versionInfo->getContentInfo()->id,
1467
                $versionInfo->versionNo,
1468
                $spiContentUpdateStruct
1469
            );
1470
            $this->relationProcessor->processFieldRelations(
1471
                $inputRelations,
1472
                $spiContent->versionInfo->contentInfo->id,
1473
                $spiContent->versionInfo->versionNo,
1474
                $contentType,
1475
                $existingRelations
1476
            );
1477
            $this->repository->commit();
1478
        } catch (Exception $e) {
1479
            $this->repository->rollback();
1480
            throw $e;
1481
        }
1482
1483
        return $this->domainMapper->buildContentDomainObject(
1484
            $spiContent,
1485
            $contentType
1486
        );
1487
    }
1488
1489
    /**
1490
     * Returns only updated language codes.
1491
     *
1492
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1493
     *
1494
     * @return array
1495
     */
1496
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1497
    {
1498
        $languageCodes = [
1499
            $contentUpdateStruct->initialLanguageCode => true,
1500
        ];
1501
1502
        foreach ($contentUpdateStruct->fields as $field) {
1503
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1504
                continue;
1505
            }
1506
1507
            $languageCodes[$field->languageCode] = true;
1508
        }
1509
1510
        return array_keys($languageCodes);
1511
    }
1512
1513
    /**
1514
     * Returns all language codes used in given $fields.
1515
     *
1516
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1517
     *
1518
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1519
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1520
     *
1521
     * @return array
1522
     */
1523
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1524
    {
1525
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1526
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1527
1528
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1529
        foreach ($updatedLanguageCodes as $languageCode) {
1530
            $languageCodes[$languageCode] = true;
1531
        }
1532
1533
        return array_keys($languageCodes);
1534
    }
1535
1536
    /**
1537
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1538
     *
1539
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1540
     *                                                                          or value is set for non-translatable field in language
1541
     *                                                                          other than main
1542
     *
1543
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1544
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1545
     * @param string $mainLanguageCode
1546
     *
1547
     * @return array
1548
     */
1549
    protected function mapFieldsForUpdate(
1550
        APIContentUpdateStruct $contentUpdateStruct,
1551
        ContentType $contentType,
1552
        $mainLanguageCode
1553
    ) {
1554
        $fields = [];
1555
1556
        foreach ($contentUpdateStruct->fields as $field) {
1557
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1558
1559
            if ($fieldDefinition === null) {
1560
                throw new ContentValidationException(
1561
                    "Field definition '%identifier%' does not exist in given ContentType",
1562
                    ['%identifier%' => $field->fieldDefIdentifier]
1563
                );
1564
            }
1565
1566
            if ($field->languageCode === null) {
1567
                if ($fieldDefinition->isTranslatable) {
1568
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1569
                } else {
1570
                    $languageCode = $mainLanguageCode;
1571
                }
1572
                $field = $this->cloneField($field, ['languageCode' => $languageCode]);
1573
            }
1574
1575
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $mainLanguageCode)) {
1576
                throw new ContentValidationException(
1577
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1578
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1579
                );
1580
            }
1581
1582
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1583
        }
1584
1585
        return $fields;
1586
    }
1587
1588
    /**
1589
     * Publishes a content version.
1590
     *
1591
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1592
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1593
     *
1594
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1595
     * @param string[] $translations
1596
     *
1597
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1598
     *
1599
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1600
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1601
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1602
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
1603
     */
1604
    public function publishVersion(APIVersionInfo $versionInfo, array $translations = Language::ALL)
1605
    {
1606
        $content = $this->internalLoadContent(
1607
            $versionInfo->contentInfo->id,
1608
            null,
1609
            $versionInfo->versionNo
1610
        );
1611
1612
        $targets = [];
1613
        if (!empty($translations)) {
1614
            $targets[] = (new Target\Builder\VersionBuilder())
1615
                ->publishTranslations($translations)
1616
                ->build();
1617
        }
1618
1619
        if (!$this->repository->getPermissionResolver()->canUser(
1620
            'content',
1621
            'publish',
1622
            $content,
1623
            $targets
1624
        )) {
1625
            throw new UnauthorizedException(
1626
                'content', 'publish', ['contentId' => $content->id]
1627
            );
1628
        }
1629
1630
        $this->repository->beginTransaction();
1631
        try {
1632
            $this->copyTranslationsFromPublishedVersion($content->versionInfo, $translations);
1633
            $content = $this->internalPublishVersion($content->getVersionInfo(), null);
1634
            $this->repository->commit();
1635
        } catch (Exception $e) {
1636
            $this->repository->rollback();
1637
            throw $e;
1638
        }
1639
1640
        return $content;
1641
    }
1642
1643
    /**
1644
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1645
     * @param array $translations
1646
     *
1647
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
1648
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
1649
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
1650
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1651
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
1652
     */
1653
    protected function copyTranslationsFromPublishedVersion(APIVersionInfo $versionInfo, array $translations = []): void
1654
    {
1655
        $contendId = $versionInfo->contentInfo->id;
1656
1657
        $currentContent = $this->internalLoadContent($contendId);
1658
        $currentVersionInfo = $currentContent->versionInfo;
1659
1660
        // Copying occurs only if:
1661
        // - There is published Version
1662
        // - Published version is older than the currently published one unless specific translations are provided.
1663
        if (!$currentVersionInfo->isPublished() ||
1664
            ($versionInfo->versionNo >= $currentVersionInfo->versionNo && empty($translations))) {
1665
            return;
1666
        }
1667
1668
        if (empty($translations)) {
1669
            $languagesToCopy = array_diff(
1670
                $currentVersionInfo->languageCodes,
1671
                $versionInfo->languageCodes
1672
            );
1673
        } else {
1674
            $languagesToCopy = array_diff(
1675
                $currentVersionInfo->languageCodes,
1676
                $translations
1677
            );
1678
        }
1679
1680
        if (empty($languagesToCopy)) {
1681
            return;
1682
        }
1683
1684
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1685
            $currentVersionInfo->contentInfo->contentTypeId
1686
        );
1687
1688
        // Find only translatable fields to update with selected languages
1689
        $updateStruct = $this->newContentUpdateStruct();
1690
        $updateStruct->initialLanguageCode = $versionInfo->initialLanguageCode;
1691
1692
        $contentToPublish = $this->internalLoadContent($contendId, null, $versionInfo->versionNo);
1693
        $fallbackUpdateStruct = $this->newContentUpdateStruct();
1694
1695
        foreach ($currentContent->getFields() as $field) {
1696
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1697
1698
            if (!$fieldDefinition->isTranslatable || !\in_array($field->languageCode, $languagesToCopy)) {
1699
                continue;
1700
            }
1701
1702
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1703
                $fieldDefinition->fieldTypeIdentifier
1704
            );
1705
1706
            $newValue = $contentToPublish->getFieldValue(
1707
                $fieldDefinition->identifier,
1708
                $field->languageCode
1709
            );
1710
1711
            $value = $field->value;
1712
            if ($fieldDefinition->isRequired && $fieldType->isEmptyValue($value)) {
1713
                if (!$fieldType->isEmptyValue($fieldDefinition->defaultValue)) {
1714
                    $value = $fieldDefinition->defaultValue;
1715
                } else {
1716
                    $value = $contentToPublish->getFieldValue($field->fieldDefIdentifier, $versionInfo->initialLanguageCode);
1717
                }
1718
                $fallbackUpdateStruct->setField(
1719
                    $field->fieldDefIdentifier,
1720
                    $value,
1721
                    $field->languageCode
1722
                );
1723
                continue;
1724
            }
1725
1726
            if ($newValue !== null
1727
                && $field->value !== null
1728
                && $this->fieldValuesAreEqual($fieldType, $newValue, $field->value)
1729
            ) {
1730
                continue;
1731
            }
1732
1733
            $updateStruct->setField($field->fieldDefIdentifier, $value, $field->languageCode);
1734
        }
1735
1736
        // Nothing to copy, skip update
1737
        if (empty($updateStruct->fields)) {
1738
            return;
1739
        }
1740
1741
        // Do fallback only if content needs to be updated
1742
        foreach ($fallbackUpdateStruct->fields as $fallbackField) {
1743
            $updateStruct->setField($fallbackField->fieldDefIdentifier, $fallbackField->value, $fallbackField->languageCode);
1744
        }
1745
1746
        $this->internalUpdateContent($versionInfo, $updateStruct);
1747
    }
1748
1749
    protected function fieldValuesAreEqual(FieldType $fieldType, Value $value1, Value $value2): bool
1750
    {
1751
        if ($fieldType instanceof Comparable) {
1752
            return $fieldType->valuesEqual($value1, $value2);
1753
        } else {
1754
            @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...
1755
                \sprintf(
1756
                    'In eZ Platform 2.5 and 3.x %s should implement %s. ' .
1757
                    'Since the 4.0 major release FieldType\Comparable contract will be a part of %s',
1758
                    get_class($fieldType),
1759
                    Comparable::class,
1760
                    FieldType::class
1761
                ),
1762
                E_USER_DEPRECATED
1763
            );
1764
1765
            return $fieldType->toHash($value1) === $fieldType->toHash($value2);
1766
        }
1767
    }
1768
1769
    /**
1770
     * Publishes a content version.
1771
     *
1772
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1773
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1774
     *
1775
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1776
     *
1777
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1778
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1779
     *
1780
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1781
     */
1782
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1783
    {
1784
        if (!$versionInfo->isDraft()) {
1785
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1786
        }
1787
1788
        $currentTime = $this->getUnixTimestamp();
1789
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1790
            $publicationDate = $currentTime;
1791
        }
1792
1793
        $contentInfo = $versionInfo->getContentInfo();
1794
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1795
        $metadataUpdateStruct->publicationDate = $publicationDate;
1796
        $metadataUpdateStruct->modificationDate = $currentTime;
1797
        $metadataUpdateStruct->isHidden = $contentInfo->isHidden;
1798
1799
        $contentId = $contentInfo->id;
1800
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1801
            $contentId,
1802
            $versionInfo->versionNo,
1803
            $metadataUpdateStruct
1804
        );
1805
1806
        $content = $this->domainMapper->buildContentDomainObject(
1807
            $spiContent,
1808
            $this->repository->getContentTypeService()->loadContentType(
1809
                $spiContent->versionInfo->contentInfo->contentTypeId
1810
            )
1811
        );
1812
1813
        $this->publishUrlAliasesForContent($content);
1814
1815
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1816
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1817
            $contentId,
1818
            APIVersionInfo::STATUS_ARCHIVED,
1819
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1820
        );
1821
1822
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1823
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1824
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1825
            $archiveVersion = array_shift($archiveList);
1826
            $this->persistenceHandler->contentHandler()->deleteVersion(
1827
                $contentId,
1828
                $archiveVersion->versionNo
1829
            );
1830
        }
1831
1832
        return $content;
1833
    }
1834
1835
    /**
1836
     * @return int
1837
     */
1838
    protected function getUnixTimestamp()
1839
    {
1840
        return time();
1841
    }
1842
1843
    /**
1844
     * Removes the given version.
1845
     *
1846
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1847
     *         published state or is a last version of Content in non draft state
1848
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1849
     *
1850
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1851
     */
1852
    public function deleteVersion(APIVersionInfo $versionInfo)
1853
    {
1854
        if ($versionInfo->isPublished()) {
1855
            throw new BadStateException(
1856
                '$versionInfo',
1857
                'Version is published and can not be removed'
1858
            );
1859
        }
1860
1861
        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...
1862
            throw new UnauthorizedException(
1863
                'content',
1864
                'versionremove',
1865
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1866
            );
1867
        }
1868
1869
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1870
            $versionInfo->contentInfo->id,
1871
            null,
1872
            2
1873
        );
1874
1875
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1876
            throw new BadStateException(
1877
                '$versionInfo',
1878
                'Version is the last version of the Content and can not be removed'
1879
            );
1880
        }
1881
1882
        $this->repository->beginTransaction();
1883
        try {
1884
            $this->persistenceHandler->contentHandler()->deleteVersion(
1885
                $versionInfo->getContentInfo()->id,
1886
                $versionInfo->versionNo
1887
            );
1888
            $this->repository->commit();
1889
        } catch (Exception $e) {
1890
            $this->repository->rollback();
1891
            throw $e;
1892
        }
1893
    }
1894
1895
    /**
1896
     * Loads all versions for the given content.
1897
     *
1898
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1899
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the given status is invalid
1900
     *
1901
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1902
     * @param int|null $status
1903
     *
1904
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1905
     */
1906
    public function loadVersions(ContentInfo $contentInfo, ?int $status = null)
1907
    {
1908
        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...
1909
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1910
        }
1911
1912
        if ($status !== null && !in_array((int)$status, [VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED], true)) {
1913
            throw new InvalidArgumentException(
1914
                'status',
1915
                sprintf(
1916
                    'it can be one of %d (draft), %d (published), %d (archived), %d given',
1917
                    VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED, $status
1918
                ));
1919
        }
1920
1921
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id, $status);
1922
1923
        $versions = [];
1924
        foreach ($spiVersionInfoList as $spiVersionInfo) {
1925
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1926
            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...
1927
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1928
            }
1929
1930
            $versions[] = $versionInfo;
1931
        }
1932
1933
        return $versions;
1934
    }
1935
1936
    /**
1937
     * Copies the content to a new location. If no version is given,
1938
     * all versions are copied, otherwise only the given version.
1939
     *
1940
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1941
     *
1942
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1943
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1944
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1945
     *
1946
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1947
     */
1948
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1949
    {
1950
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1951
            $destinationLocationCreateStruct->parentLocationId
1952
        );
1953
        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...
1954
            throw new UnauthorizedException(
1955
                'content',
1956
                'create',
1957
                [
1958
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1959
                    'sectionId' => $contentInfo->sectionId,
1960
                ]
1961
            );
1962
        }
1963
        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...
1964
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
1965
        }
1966
1967
        $defaultObjectStates = $this->getDefaultObjectStates();
1968
1969
        $this->repository->beginTransaction();
1970
        try {
1971
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1972
                $contentInfo->id,
1973
                $versionInfo ? $versionInfo->versionNo : null,
1974
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1975
            );
1976
1977
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1978
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1979
                $objectStateHandler->setContentState(
1980
                    $spiContent->versionInfo->contentInfo->id,
1981
                    $objectStateGroupId,
1982
                    $objectState->id
1983
                );
1984
            }
1985
1986
            $content = $this->internalPublishVersion(
1987
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1988
                $spiContent->versionInfo->creationDate
1989
            );
1990
1991
            $this->repository->getLocationService()->createLocation(
1992
                $content->getVersionInfo()->getContentInfo(),
1993
                $destinationLocationCreateStruct
1994
            );
1995
            $this->repository->commit();
1996
        } catch (Exception $e) {
1997
            $this->repository->rollback();
1998
            throw $e;
1999
        }
2000
2001
        return $this->internalLoadContent($content->id);
2002
    }
2003
2004
    /**
2005
     * Loads all outgoing relations for the given version.
2006
     *
2007
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
2008
     *
2009
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
2010
     *
2011
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
2012
     */
2013
    public function loadRelations(APIVersionInfo $versionInfo)
2014
    {
2015
        if ($versionInfo->isPublished()) {
2016
            $function = 'read';
2017
        } else {
2018
            $function = 'versionread';
2019
        }
2020
2021
        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...
2022
            throw new UnauthorizedException('content', $function);
2023
        }
2024
2025
        return $this->internalLoadRelations($versionInfo);
2026
    }
2027
2028
    /**
2029
     * Loads all outgoing relations for the given version without checking the permissions.
2030
     *
2031
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
2032
     *
2033
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
2034
     */
2035
    protected function internalLoadRelations(APIVersionInfo $versionInfo): array
2036
    {
2037
        $contentInfo = $versionInfo->getContentInfo();
2038
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2039
            $contentInfo->id,
2040
            $versionInfo->versionNo
2041
        );
2042
2043
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
2044
        $relations = [];
2045
        foreach ($spiRelations as $spiRelation) {
2046
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
2047
            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...
2048
                continue;
2049
            }
2050
2051
            $relations[] = $this->domainMapper->buildRelationDomainObject(
2052
                $spiRelation,
2053
                $contentInfo,
2054
                $destinationContentInfo
2055
            );
2056
        }
2057
2058
        return $relations;
2059
    }
2060
2061
    /**
2062
     * {@inheritdoc}
2063
     */
2064
    public function countReverseRelations(ContentInfo $contentInfo): int
2065
    {
2066
        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...
2067
            return 0;
2068
        }
2069
2070
        return $this->persistenceHandler->contentHandler()->countReverseRelations(
2071
            $contentInfo->id
2072
        );
2073
    }
2074
2075
    /**
2076
     * Loads all incoming relations for a content object.
2077
     *
2078
     * The relations come only from published versions of the source content objects
2079
     *
2080
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
2081
     *
2082
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2083
     *
2084
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
2085
     */
2086
    public function loadReverseRelations(ContentInfo $contentInfo)
2087
    {
2088
        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...
2089
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
2090
        }
2091
2092
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
2093
            $contentInfo->id
2094
        );
2095
2096
        $returnArray = [];
2097
        foreach ($spiRelations as $spiRelation) {
2098
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2099
            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...
2100
                continue;
2101
            }
2102
2103
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
2104
                $spiRelation,
2105
                $sourceContentInfo,
2106
                $contentInfo
2107
            );
2108
        }
2109
2110
        return $returnArray;
2111
    }
2112
2113
    /**
2114
     * {@inheritdoc}
2115
     */
2116
    public function loadReverseRelationList(ContentInfo $contentInfo, int $offset = 0, int $limit = -1): RelationList
2117
    {
2118
        $list = new RelationList();
2119
        if (!$this->repository->getPermissionResolver()->canUser('content', 'reverserelatedlist', $contentInfo)) {
2120
            return $list;
2121
        }
2122
2123
        $list->totalCount = $this->persistenceHandler->contentHandler()->countReverseRelations(
2124
            $contentInfo->id
2125
        );
2126
        if ($list->totalCount > 0) {
2127
            $spiRelationList = $this->persistenceHandler->contentHandler()->loadReverseRelationList(
2128
                $contentInfo->id,
2129
                $offset,
2130
                $limit
2131
            );
2132
            foreach ($spiRelationList as $spiRelation) {
2133
                $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
2134
                if ($this->repository->getPermissionResolver()->canUser('content', 'read', $sourceContentInfo)) {
2135
                    $relation = $this->domainMapper->buildRelationDomainObject(
2136
                        $spiRelation,
2137
                        $sourceContentInfo,
2138
                        $contentInfo
2139
                    );
2140
                    $list->items[] = new RelationListItem($relation);
2141
                } else {
2142
                    $list->items[] = new UnauthorizedRelationListItem(
2143
                        'content',
2144
                        'read',
2145
                        ['contentId' => $sourceContentInfo->id]
2146
                    );
2147
                }
2148
            }
2149
        }
2150
2151
        return $list;
2152
    }
2153
2154
    /**
2155
     * Adds a relation of type common.
2156
     *
2157
     * The source of the relation is the content and version
2158
     * referenced by $versionInfo.
2159
     *
2160
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
2161
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2162
     *
2163
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2164
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
2165
     *
2166
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
2167
     */
2168
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2169
    {
2170
        $sourceVersion = $this->loadVersionInfoById(
2171
            $sourceVersion->contentInfo->id,
2172
            $sourceVersion->versionNo
2173
        );
2174
2175
        if (!$sourceVersion->isDraft()) {
2176
            throw new BadStateException(
2177
                '$sourceVersion',
2178
                'Relations of type common can only be added to versions of status draft'
2179
            );
2180
        }
2181
2182
        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...
2183
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2184
        }
2185
2186
        $sourceContentInfo = $sourceVersion->getContentInfo();
2187
2188
        $this->repository->beginTransaction();
2189
        try {
2190
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
2191
                new SPIRelationCreateStruct(
2192
                    [
2193
                        'sourceContentId' => $sourceContentInfo->id,
2194
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
2195
                        'sourceFieldDefinitionId' => null,
2196
                        'destinationContentId' => $destinationContent->id,
2197
                        'type' => APIRelation::COMMON,
2198
                    ]
2199
                )
2200
            );
2201
            $this->repository->commit();
2202
        } catch (Exception $e) {
2203
            $this->repository->rollback();
2204
            throw $e;
2205
        }
2206
2207
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
2208
    }
2209
2210
    /**
2211
     * Removes a relation of type COMMON from a draft.
2212
     *
2213
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
2214
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
2215
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
2216
     *
2217
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
2218
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
2219
     */
2220
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
2221
    {
2222
        $sourceVersion = $this->loadVersionInfoById(
2223
            $sourceVersion->contentInfo->id,
2224
            $sourceVersion->versionNo
2225
        );
2226
2227
        if (!$sourceVersion->isDraft()) {
2228
            throw new BadStateException(
2229
                '$sourceVersion',
2230
                'Relations of type common can only be removed from versions of status draft'
2231
            );
2232
        }
2233
2234
        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...
2235
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
2236
        }
2237
2238
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
2239
            $sourceVersion->getContentInfo()->id,
2240
            $sourceVersion->versionNo,
2241
            APIRelation::COMMON
2242
        );
2243
2244
        if (empty($spiRelations)) {
2245
            throw new InvalidArgumentException(
2246
                '$sourceVersion',
2247
                'There are no relations of type COMMON for the given destination'
2248
            );
2249
        }
2250
2251
        // there should be only one relation of type COMMON for each destination,
2252
        // but in case there were ever more then one, we will remove them all
2253
        // @todo: alternatively, throw BadStateException?
2254
        $this->repository->beginTransaction();
2255
        try {
2256
            foreach ($spiRelations as $spiRelation) {
2257
                if ($spiRelation->destinationContentId == $destinationContent->id) {
2258
                    $this->persistenceHandler->contentHandler()->removeRelation(
2259
                        $spiRelation->id,
2260
                        APIRelation::COMMON
2261
                    );
2262
                }
2263
            }
2264
            $this->repository->commit();
2265
        } catch (Exception $e) {
2266
            $this->repository->rollback();
2267
            throw $e;
2268
        }
2269
    }
2270
2271
    /**
2272
     * {@inheritdoc}
2273
     */
2274
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
2275
    {
2276
        @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...
2277
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
2278
            E_USER_DEPRECATED
2279
        );
2280
        $this->deleteTranslation($contentInfo, $languageCode);
2281
    }
2282
2283
    /**
2284
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2285
     *
2286
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2287
     *
2288
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2289
     *         is the Main Translation of a Content Item.
2290
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2291
     *         to delete the content (in one of the locations of the given Content Item).
2292
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2293
     *         is invalid for the given content.
2294
     *
2295
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2296
     * @param string $languageCode
2297
     *
2298
     * @since 6.13
2299
     */
2300
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2301
    {
2302
        if ($contentInfo->mainLanguageCode === $languageCode) {
2303
            throw new BadStateException(
2304
                '$languageCode',
2305
                'Specified translation is the main translation of the Content Object'
2306
            );
2307
        }
2308
2309
        $translationWasFound = false;
2310
        $this->repository->beginTransaction();
2311
        try {
2312
            $target = (new Target\Builder\VersionBuilder())->translateToAnyLanguageOf([$languageCode])->build();
2313
2314
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2315
                if (!$this->repository->canUser('content', 'remove', $versionInfo, [$target])) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
2316
                    throw new UnauthorizedException(
2317
                        'content',
2318
                        'remove',
2319
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo, 'languageCode' => $languageCode]
2320
                    );
2321
                }
2322
2323
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2324
                    continue;
2325
                }
2326
2327
                $translationWasFound = true;
2328
2329
                // If the translation is the version's only one, delete the version
2330
                if (count($versionInfo->languageCodes) < 2) {
2331
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2332
                        $versionInfo->getContentInfo()->id,
2333
                        $versionInfo->versionNo
2334
                    );
2335
                }
2336
            }
2337
2338
            if (!$translationWasFound) {
2339
                throw new InvalidArgumentException(
2340
                    '$languageCode',
2341
                    sprintf(
2342
                        '%s does not exist in the Content item(id=%d)',
2343
                        $languageCode,
2344
                        $contentInfo->id
2345
                    )
2346
                );
2347
            }
2348
2349
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2350
                $contentInfo->id,
2351
                $languageCode
2352
            );
2353
            $locationIds = array_map(
2354
                function (Location $location) {
2355
                    return $location->id;
2356
                },
2357
                $this->repository->getLocationService()->loadLocations($contentInfo)
2358
            );
2359
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2360
                $locationIds,
2361
                $languageCode
2362
            );
2363
            $this->repository->commit();
2364
        } catch (InvalidArgumentException $e) {
2365
            $this->repository->rollback();
2366
            throw $e;
2367
        } catch (BadStateException $e) {
2368
            $this->repository->rollback();
2369
            throw $e;
2370
        } catch (UnauthorizedException $e) {
2371
            $this->repository->rollback();
2372
            throw $e;
2373
        } catch (Exception $e) {
2374
            $this->repository->rollback();
2375
            // cover generic unexpected exception to fulfill API promise on @throws
2376
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2377
        }
2378
    }
2379
2380
    /**
2381
     * Delete specified Translation from a Content Draft.
2382
     *
2383
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2384
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2385
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2386
     *         to edit the Content (in one of the locations of the given Content Object).
2387
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2388
     *         is invalid for the given Draft.
2389
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2390
     *
2391
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2392
     * @param string $languageCode Language code of the Translation to be removed
2393
     *
2394
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2395
     *
2396
     * @since 6.12
2397
     */
2398
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2399
    {
2400
        if (!$versionInfo->isDraft()) {
2401
            throw new BadStateException(
2402
                '$versionInfo',
2403
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2404
            );
2405
        }
2406
2407
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2408
            throw new BadStateException(
2409
                '$languageCode',
2410
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2411
            );
2412
        }
2413
2414
        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...
2415
            throw new UnauthorizedException(
2416
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2417
            );
2418
        }
2419
2420
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2421
            throw new InvalidArgumentException(
2422
                '$languageCode',
2423
                sprintf(
2424
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2425
                    $versionInfo->contentInfo->id,
2426
                    $versionInfo->versionNo,
2427
                    $languageCode
2428
                )
2429
            );
2430
        }
2431
2432
        if (count($versionInfo->languageCodes) === 1) {
2433
            throw new BadStateException(
2434
                '$languageCode',
2435
                'Specified Translation is the only one Content Object Version has'
2436
            );
2437
        }
2438
2439
        $this->repository->beginTransaction();
2440
        try {
2441
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2442
                $versionInfo->contentInfo->id,
2443
                $versionInfo->versionNo,
2444
                $languageCode
2445
            );
2446
            $this->repository->commit();
2447
2448
            return $this->domainMapper->buildContentDomainObject(
2449
                $spiContent,
2450
                $this->repository->getContentTypeService()->loadContentType(
2451
                    $spiContent->versionInfo->contentInfo->contentTypeId
2452
                )
2453
            );
2454
        } catch (APINotFoundException $e) {
2455
            // avoid wrapping expected NotFoundException in BadStateException handled below
2456
            $this->repository->rollback();
2457
            throw $e;
2458
        } catch (Exception $e) {
2459
            $this->repository->rollback();
2460
            // cover generic unexpected exception to fulfill API promise on @throws
2461
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2462
        }
2463
    }
2464
2465
    /**
2466
     * Hides Content by making all the Locations appear hidden.
2467
     * It does not persist hidden state on Location object itself.
2468
     *
2469
     * Content hidden by this API can be revealed by revealContent API.
2470
     *
2471
     * @see revealContent
2472
     *
2473
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2474
     */
2475
    public function hideContent(ContentInfo $contentInfo): void
2476
    {
2477
        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...
2478
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2479
        }
2480
2481
        $this->repository->beginTransaction();
2482
        try {
2483
            $this->persistenceHandler->contentHandler()->updateMetadata(
2484
                $contentInfo->id,
2485
                new SPIMetadataUpdateStruct([
2486
                    'isHidden' => true,
2487
                ])
2488
            );
2489
            $locationHandler = $this->persistenceHandler->locationHandler();
2490
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2491
            foreach ($childLocations as $childLocation) {
2492
                $locationHandler->setInvisible($childLocation->id);
2493
            }
2494
            $this->repository->commit();
2495
        } catch (Exception $e) {
2496
            $this->repository->rollback();
2497
            throw $e;
2498
        }
2499
    }
2500
2501
    /**
2502
     * Reveals Content hidden by hideContent API.
2503
     * Locations which were hidden before hiding Content will remain hidden.
2504
     *
2505
     * @see hideContent
2506
     *
2507
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2508
     */
2509
    public function revealContent(ContentInfo $contentInfo): void
2510
    {
2511
        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...
2512
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2513
        }
2514
2515
        $this->repository->beginTransaction();
2516
        try {
2517
            $this->persistenceHandler->contentHandler()->updateMetadata(
2518
                $contentInfo->id,
2519
                new SPIMetadataUpdateStruct([
2520
                    'isHidden' => false,
2521
                ])
2522
            );
2523
            $locationHandler = $this->persistenceHandler->locationHandler();
2524
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2525
            foreach ($childLocations as $childLocation) {
2526
                $locationHandler->setVisible($childLocation->id);
2527
            }
2528
            $this->repository->commit();
2529
        } catch (Exception $e) {
2530
            $this->repository->rollback();
2531
            throw $e;
2532
        }
2533
    }
2534
2535
    /**
2536
     * Instantiates a new content create struct object.
2537
     *
2538
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2539
     *
2540
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2541
     * @param string $mainLanguageCode
2542
     *
2543
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2544
     */
2545
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2546
    {
2547
        return new ContentCreateStruct(
2548
            [
2549
                'contentType' => $contentType,
2550
                'mainLanguageCode' => $mainLanguageCode,
2551
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2552
            ]
2553
        );
2554
    }
2555
2556
    /**
2557
     * Instantiates a new content meta data update struct.
2558
     *
2559
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2560
     */
2561
    public function newContentMetadataUpdateStruct()
2562
    {
2563
        return new ContentMetadataUpdateStruct();
2564
    }
2565
2566
    /**
2567
     * Instantiates a new content update struct.
2568
     *
2569
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2570
     */
2571
    public function newContentUpdateStruct()
2572
    {
2573
        return new ContentUpdateStruct();
2574
    }
2575
2576
    /**
2577
     * @param \eZ\Publish\API\Repository\Values\User\User|null $user
2578
     *
2579
     * @return \eZ\Publish\API\Repository\Values\User\UserReference
2580
     */
2581
    private function resolveUser(?User $user): UserReference
2582
    {
2583
        if ($user === null) {
2584
            $user = $this->repository->getPermissionResolver()->getCurrentUserReference();
2585
        }
2586
2587
        return $user;
2588
    }
2589
}
2590