Completed
Push — removed-unused-parameters-in-d... ( 2f71fe...26956e )
by
unknown
18:13
created

ContentService::deleteTranslation()   C

Complexity

Conditions 11
Paths 96

Size

Total Lines 77

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 96
nop 2
dl 0
loc 77
rs 6.3551
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\ContentService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\ContentService as ContentServiceInterface;
12
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
13
use eZ\Publish\API\Repository\Values\Content\Language;
14
use eZ\Publish\Core\Repository\Values\Content\Location;
15
use eZ\Publish\SPI\Persistence\Handler;
16
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct as APIContentUpdateStruct;
17
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
18
use eZ\Publish\API\Repository\Values\Content\TranslationInfo;
19
use eZ\Publish\API\Repository\Values\Content\TranslationValues as APITranslationValues;
20
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
21
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
22
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
23
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
24
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
25
use eZ\Publish\API\Repository\Values\User\User;
26
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
27
use eZ\Publish\API\Repository\Values\Content\Field;
28
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
29
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
30
use eZ\Publish\Core\Base\Exceptions\BadStateException;
31
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
32
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
33
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
34
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
35
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
36
use eZ\Publish\Core\FieldType\ValidationError;
37
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
38
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
39
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
40
use eZ\Publish\Core\Repository\Values\Content\TranslationValues;
41
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
42
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
43
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
44
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
45
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
46
use Exception;
47
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
48
49
/**
50
 * This class provides service methods for managing content.
51
 *
52
 * @example Examples/content.php
53
 */
54
class ContentService implements ContentServiceInterface
55
{
56
    /**
57
     * @var \eZ\Publish\Core\Repository\Repository
58
     */
59
    protected $repository;
60
61
    /**
62
     * @var \eZ\Publish\SPI\Persistence\Handler
63
     */
64
    protected $persistenceHandler;
65
66
    /**
67
     * @var array
68
     */
69
    protected $settings;
70
71
    /**
72
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
73
     */
74
    protected $domainMapper;
75
76
    /**
77
     * @var \eZ\Publish\Core\Repository\Helper\RelationProcessor
78
     */
79
    protected $relationProcessor;
80
81
    /**
82
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
83
     */
84
    protected $nameSchemaService;
85
86
    /**
87
     * @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry
88
     */
89
    protected $fieldTypeRegistry;
90
91
    /**
92
     * Setups service with reference to repository object that created it & corresponding handler.
93
     *
94
     * @param \eZ\Publish\API\Repository\Repository $repository
95
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
96
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
97
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
98
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
99
     * @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...
100
     * @param array $settings
101
     */
102
    public function __construct(
103
        RepositoryInterface $repository,
104
        Handler $handler,
105
        Helper\DomainMapper $domainMapper,
106
        Helper\RelationProcessor $relationProcessor,
107
        Helper\NameSchemaService $nameSchemaService,
108
        Helper\FieldTypeRegistry $fieldTypeRegistry,
109
        array $settings = []
110
    ) {
111
        $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...
112
        $this->persistenceHandler = $handler;
113
        $this->domainMapper = $domainMapper;
114
        $this->relationProcessor = $relationProcessor;
115
        $this->nameSchemaService = $nameSchemaService;
116
        $this->fieldTypeRegistry = $fieldTypeRegistry;
117
        // Union makes sure default settings are ignored if provided in argument
118
        $this->settings = $settings + [
119
            // Version archive limit (0-50), only enforced on publish, not on un-publish.
120
            'default_version_archive_limit' => 5,
121
        ];
122
    }
123
124
    /**
125
     * Loads a content info object.
126
     *
127
     * To load fields use loadContent
128
     *
129
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
130
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
131
     *
132
     * @param int $contentId
133
     *
134
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
135
     */
136 View Code Duplication
    public function loadContentInfo($contentId)
137
    {
138
        $contentInfo = $this->internalLoadContentInfo($contentId);
139
        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...
140
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
141
        }
142
143
        return $contentInfo;
144
    }
145
146
    /**
147
     * Loads a content info object.
148
     *
149
     * To load fields use loadContent
150
     *
151
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
152
     *
153
     * @param mixed $id
154
     * @param bool $isRemoteId
155
     *
156
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
157
     */
158
    public function internalLoadContentInfo($id, $isRemoteId = false)
159
    {
160
        try {
161
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
162
163
            return $this->domainMapper->buildContentInfoDomainObject(
164
                $this->persistenceHandler->contentHandler()->$method($id)
165
            );
166
        } catch (APINotFoundException $e) {
167
            throw new NotFoundException(
168
                'Content',
169
                $id,
170
                $e
171
            );
172
        }
173
    }
174
175
    /**
176
     * Loads a content info object for the given remoteId.
177
     *
178
     * To load fields use loadContent
179
     *
180
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
181
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
182
     *
183
     * @param string $remoteId
184
     *
185
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
186
     */
187 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
188
    {
189
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
190
191
        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...
192
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
193
        }
194
195
        return $contentInfo;
196
    }
197
198
    /**
199
     * Loads a version info of the given content object.
200
     *
201
     * If no version number is given, the method returns the current version
202
     *
203
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
204
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
205
     *
206
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
207
     * @param int $versionNo the version number. If not given the current version is returned.
208
     *
209
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
210
     */
211
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
212
    {
213
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
214
    }
215
216
    /**
217
     * Loads a version info of the given content object id.
218
     *
219
     * If no version number is given, the method returns the current version
220
     *
221
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
222
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
223
     *
224
     * @param mixed $contentId
225
     * @param int $versionNo the version number. If not given the current version is returned.
226
     *
227
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
228
     */
229
    public function loadVersionInfoById($contentId, $versionNo = null)
230
    {
231
        try {
232
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
233
                $contentId,
234
                $versionNo
235
            );
236
        } catch (APINotFoundException $e) {
237
            throw new NotFoundException(
238
                'VersionInfo',
239
                [
240
                    'contentId' => $contentId,
241
                    'versionNo' => $versionNo,
242
                ],
243
                $e
244
            );
245
        }
246
247
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
248
249
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
250
            $function = 'read';
251
        } else {
252
            $function = 'versionread';
253
        }
254
255
        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...
256
            throw new UnauthorizedException('content', $function, ['contentId' => $contentId]);
257
        }
258
259
        return $versionInfo;
260
    }
261
262
    /**
263
     * Loads content in a version for the given content info object.
264
     *
265
     * If no version number is given, the method returns the current version
266
     *
267
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if version with the given number does not exist
268
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
269
     *
270
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
271
     * @param array $languages A language filter for fields. If not given all languages are returned
272
     * @param int $versionNo the version number. If not given the current version is returned
273
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
274
     *
275
     * @return \eZ\Publish\API\Repository\Values\Content\Content
276
     */
277
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
278
    {
279
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
280
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
281
            $useAlwaysAvailable = false;
282
        }
283
284
        return $this->loadContent(
285
            $contentInfo->id,
286
            $languages,
287
            $versionNo,
288
            $useAlwaysAvailable
289
        );
290
    }
291
292
    /**
293
     * Loads content in the version given by version info.
294
     *
295
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
296
     *
297
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
298
     * @param array $languages A language filter for fields. If not given all languages are returned
299
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
300
     *
301
     * @return \eZ\Publish\API\Repository\Values\Content\Content
302
     */
303
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
304
    {
305
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
306
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
307
            $useAlwaysAvailable = false;
308
        }
309
310
        return $this->loadContent(
311
            $versionInfo->getContentInfo()->id,
312
            $languages,
313
            $versionInfo->versionNo,
314
            $useAlwaysAvailable
315
        );
316
    }
317
318
    /**
319
     * Loads content in a version of the given content object.
320
     *
321
     * If no version number is given, the method returns the current version
322
     *
323
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
324
     * @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
325
     *
326
     * @param int $contentId
327
     * @param array|null $languages A language filter for fields. If not given all languages are returned
328
     * @param int|null $versionNo the version number. If not given the current version is returned
329
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
330
     *
331
     * @return \eZ\Publish\API\Repository\Values\Content\Content
332
     */
333 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
334
    {
335
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
336
337
        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...
338
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
339
        }
340
341
        if (
342
            $content->getVersionInfo()->status !== APIVersionInfo::STATUS_PUBLISHED
343
            && !$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...
344
        ) {
345
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId, 'versionNo' => $versionNo]);
346
        }
347
348
        return $content;
349
    }
350
351
    /**
352
     * Loads content in a version of the given content object.
353
     *
354
     * If no version number is given, the method returns the current version
355
     *
356
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
357
     *
358
     * @param mixed $id
359
     * @param array|null $languages A language filter for fields. If not given all languages are returned
360
     * @param int|null $versionNo the version number. If not given the current version is returned
361
     * @param bool $isRemoteId
362
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
363
     *
364
     * @return \eZ\Publish\API\Repository\Values\Content\Content
365
     */
366
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
367
    {
368
        try {
369
            // Get Content ID if lookup by remote ID
370
            if ($isRemoteId) {
371
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
372
                $id = $spiContentInfo->id;
373
            }
374
375
            $loadLanguages = $languages;
376
            $alwaysAvailableLanguageCode = null;
377
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
378
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
379
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
380
                if (!isset($spiContentInfo)) {
381
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
382
                }
383
384
                if ($spiContentInfo->alwaysAvailable) {
385
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
386
                    $loadLanguages = array_unique($loadLanguages);
387
                }
388
            }
389
390
            $spiContent = $this->persistenceHandler->contentHandler()->load(
391
                $id,
392
                $versionNo,
393
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 375 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...
394
            );
395
        } catch (APINotFoundException $e) {
396
            throw new NotFoundException(
397
                'Content',
398
                [
399
                    $isRemoteId ? 'remoteId' : 'id' => $id,
400
                    'languages' => $languages,
401
                    'versionNo' => $versionNo,
402
                ],
403
                $e
404
            );
405
        }
406
407
        return $this->domainMapper->buildContentDomainObject(
408
            $spiContent,
409
            null,
410
            empty($languages) ? null : $languages,
411
            $alwaysAvailableLanguageCode
412
        );
413
    }
414
415
    /**
416
     * Loads content in a version for the content object reference by the given remote id.
417
     *
418
     * If no version is given, the method returns the current version
419
     *
420
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
421
     * @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
422
     *
423
     * @param string $remoteId
424
     * @param array $languages A language filter for fields. If not given all languages are returned
425
     * @param int $versionNo the version number. If not given the current version is returned
426
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
427
     *
428
     * @return \eZ\Publish\API\Repository\Values\Content\Content
429
     */
430 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
431
    {
432
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
433
434
        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...
435
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
436
        }
437
438
        if (
439
            $content->getVersionInfo()->status !== APIVersionInfo::STATUS_PUBLISHED
440
            && !$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...
441
        ) {
442
            throw new UnauthorizedException('content', 'versionread', ['remoteId' => $remoteId, 'versionNo' => $versionNo]);
443
        }
444
445
        return $content;
446
    }
447
448
    /**
449
     * Bulk-load Content items by the list of ContentInfo Value Objects.
450
     *
451
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
452
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
453
     * allowed to access every Content on the list.
454
     *
455
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
456
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
457
     *                            returned value object. If not given all languages are returned.
458
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
459
     *                                 unless all languages have been asked for.
460
     *
461
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
462
     */
463
    public function loadContentListByContentInfo(
464
        array $contentInfoList,
465
        array $languages = [],
466
        $useAlwaysAvailable = true
467
    ) {
468
        $loadAllLanguages = $languages === Language::ALL;
469
        $contentIds = [];
470
        $translations = $languages;
471
        foreach ($contentInfoList as $contentInfo) {
472
            $contentIds[] = $contentInfo->id;
473
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
474
            // Might in some case load more languages then intended, but prioritised handling will pick right one
475
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
476
                $translations[] = $contentInfo->mainLanguageCode;
477
            }
478
        }
479
        $translations = array_unique($translations);
480
481
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
482
            $contentIds,
483
            $translations
484
        );
485
        $contentList = [];
486
        foreach ($spiContentList as $contentId => $spiContent) {
487
            $contentInfo = $spiContent->versionInfo->contentInfo;
488
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
489
                $spiContent,
490
                null,
491
                $languages,
492
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
493
            );
494
        }
495
496
        return $contentList;
497
    }
498
499
    /**
500
     * Creates a new content draft assigned to the authenticated user.
501
     *
502
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
503
     * but this required special rights for the authenticated user
504
     * (this is useful for content staging where the transfer process does not
505
     * have to authenticate with the user which created the content object in the source server).
506
     * The user has to publish the draft if it should be visible.
507
     * In 4.x at least one location has to be provided in the location creation array.
508
     *
509
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
510
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
511
     *                                                                        struct are missing or invalid, or if multiple locations are under the
512
     *                                                                        same parent.
513
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
514
     *                                                                               or if a required field is missing / set to an empty value.
515
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
516
     *                                                                          or value is set for non-translatable field in language
517
     *                                                                          other than main.
518
     *
519
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
520
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
521
     *
522
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
523
     */
524
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = [])
525
    {
526
        if ($contentCreateStruct->mainLanguageCode === null) {
527
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
528
        }
529
530
        if ($contentCreateStruct->contentType === null) {
531
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
532
        }
533
534
        $contentCreateStruct = clone $contentCreateStruct;
535
536
        if ($contentCreateStruct->ownerId === null) {
537
            $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...
538
        }
539
540
        if ($contentCreateStruct->alwaysAvailable === null) {
541
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
542
        }
543
544
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
545
            $contentCreateStruct->contentType->id
546
        );
547
548
        if (empty($contentCreateStruct->sectionId)) {
549
            if (isset($locationCreateStructs[0])) {
550
                $location = $this->repository->getLocationService()->loadLocation(
551
                    $locationCreateStructs[0]->parentLocationId
552
                );
553
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
554
            } else {
555
                $contentCreateStruct->sectionId = 1;
556
            }
557
        }
558
559
        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...
560
            throw new UnauthorizedException(
561
                'content',
562
                'create',
563
                [
564
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
565
                            $locationCreateStructs[0]->parentLocationId :
566
                            null,
567
                    'sectionId' => $contentCreateStruct->sectionId,
568
                ]
569
            );
570
        }
571
572
        if (!empty($contentCreateStruct->remoteId)) {
573
            try {
574
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
575
576
                throw new InvalidArgumentException(
577
                    '$contentCreateStruct',
578
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
579
                );
580
            } catch (APINotFoundException $e) {
581
                // Do nothing
582
            }
583
        } else {
584
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
585
        }
586
587
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
588
589
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
590
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
591
592
        $fieldValues = [];
593
        $spiFields = [];
594
        $allFieldErrors = [];
595
        $inputRelations = [];
596
        $locationIdToContentIdMapping = [];
597
598
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
599
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
600
            $fieldType = $this->fieldTypeRegistry->getFieldType(
601
                $fieldDefinition->fieldTypeIdentifier
602
            );
603
604
            foreach ($languageCodes as $languageCode) {
605
                $isEmptyValue = false;
606
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
607
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
608
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
609
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
610
                } else {
611
                    $fieldValue = $fieldDefinition->defaultValue;
612
                }
613
614
                $fieldValue = $fieldType->acceptValue($fieldValue);
615
616 View Code Duplication
                if ($fieldType->isEmptyValue($fieldValue)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
617
                    $isEmptyValue = true;
618
                    if ($fieldDefinition->isRequired) {
619
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
620
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
621
                            null,
622
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
623
                            'empty'
624
                        );
625
                    }
626
                } else {
627
                    $fieldErrors = $fieldType->validate(
628
                        $fieldDefinition,
629
                        $fieldValue
630
                    );
631
                    if (!empty($fieldErrors)) {
632
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
633
                    }
634
                }
635
636
                if (!empty($allFieldErrors)) {
637
                    continue;
638
                }
639
640
                $this->relationProcessor->appendFieldRelations(
641
                    $inputRelations,
642
                    $locationIdToContentIdMapping,
643
                    $fieldType,
644
                    $fieldValue,
645
                    $fieldDefinition->id
646
                );
647
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
648
649
                // Only non-empty value for: translatable field or in main language
650
                if (
651
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
652
                    (!$isEmptyValue && $isLanguageMain)
653
                ) {
654
                    $spiFields[] = new SPIField(
655
                        [
656
                            'id' => null,
657
                            'fieldDefinitionId' => $fieldDefinition->id,
658
                            'type' => $fieldDefinition->fieldTypeIdentifier,
659
                            'value' => $fieldType->toPersistenceValue($fieldValue),
660
                            'languageCode' => $languageCode,
661
                            'versionNo' => null,
662
                        ]
663
                    );
664
                }
665
            }
666
        }
667
668
        if (!empty($allFieldErrors)) {
669
            throw new ContentFieldValidationException($allFieldErrors);
670
        }
671
672
        $spiContentCreateStruct = new SPIContentCreateStruct(
673
            [
674
                'name' => $this->nameSchemaService->resolve(
675
                    $contentCreateStruct->contentType->nameSchema,
676
                    $contentCreateStruct->contentType,
677
                    $fieldValues,
678
                    $languageCodes
679
                ),
680
                'typeId' => $contentCreateStruct->contentType->id,
681
                'sectionId' => $contentCreateStruct->sectionId,
682
                'ownerId' => $contentCreateStruct->ownerId,
683
                'locations' => $spiLocationCreateStructs,
684
                'fields' => $spiFields,
685
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
686
                'remoteId' => $contentCreateStruct->remoteId,
687
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
688
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
689
                    $contentCreateStruct->mainLanguageCode
690
                )->id,
691
            ]
692
        );
693
694
        $defaultObjectStates = $this->getDefaultObjectStates();
695
696
        $this->repository->beginTransaction();
697
        try {
698
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
699
            $this->relationProcessor->processFieldRelations(
700
                $inputRelations,
701
                $spiContent->versionInfo->contentInfo->id,
702
                $spiContent->versionInfo->versionNo,
703
                $contentCreateStruct->contentType
704
            );
705
706
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
707
                $this->persistenceHandler->objectStateHandler()->setContentState(
708
                    $spiContent->versionInfo->contentInfo->id,
709
                    $objectStateGroupId,
710
                    $objectState->id
711
                );
712
            }
713
714
            $this->repository->commit();
715
        } catch (Exception $e) {
716
            $this->repository->rollback();
717
            throw $e;
718
        }
719
720
        return $this->domainMapper->buildContentDomainObject($spiContent);
721
    }
722
723
    /**
724
     * Returns an array of default content states with content state group id as key.
725
     *
726
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
727
     */
728
    protected function getDefaultObjectStates()
729
    {
730
        $defaultObjectStatesMap = [];
731
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
732
733
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
734
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
735
                // Only register the first object state which is the default one.
736
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
737
                break;
738
            }
739
        }
740
741
        return $defaultObjectStatesMap;
742
    }
743
744
    /**
745
     * Returns all language codes used in given $fields.
746
     *
747
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
748
     *
749
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
750
     *
751
     * @return string[]
752
     */
753
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
754
    {
755
        $languageCodes = [];
756
757
        foreach ($contentCreateStruct->fields as $field) {
758
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
759
                continue;
760
            }
761
762
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
763
                $field->languageCode
764
            );
765
            $languageCodes[$field->languageCode] = true;
766
        }
767
768
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
769
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
770
                $contentCreateStruct->mainLanguageCode
771
            );
772
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
773
        }
774
775
        return array_keys($languageCodes);
776
    }
777
778
    /**
779
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
780
     *
781
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
782
     *                                                                          or value is set for non-translatable field in language
783
     *                                                                          other than main
784
     *
785
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
786
     *
787
     * @return array
788
     */
789
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
790
    {
791
        $fields = [];
792
793
        foreach ($contentCreateStruct->fields as $field) {
794
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
795
796
            if ($fieldDefinition === null) {
797
                throw new ContentValidationException(
798
                    "Field definition '%identifier%' does not exist in given ContentType",
799
                    ['%identifier%' => $field->fieldDefIdentifier]
800
                );
801
            }
802
803
            if ($field->languageCode === null) {
804
                $field = $this->cloneField(
805
                    $field,
806
                    ['languageCode' => $contentCreateStruct->mainLanguageCode]
807
                );
808
            }
809
810 View Code Duplication
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $contentCreateStruct->mainLanguageCode)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
811
                throw new ContentValidationException(
812
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
813
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
814
                );
815
            }
816
817
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
818
        }
819
820
        return $fields;
821
    }
822
823
    /**
824
     * Clones $field with overriding specific properties from given $overrides array.
825
     *
826
     * @param Field $field
827
     * @param array $overrides
828
     *
829
     * @return Field
830
     */
831
    private function cloneField(Field $field, array $overrides = [])
832
    {
833
        $fieldData = array_merge(
834
            [
835
                'id' => $field->id,
836
                'value' => $field->value,
837
                'languageCode' => $field->languageCode,
838
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
839
            ],
840
            $overrides
841
        );
842
843
        return new Field($fieldData);
844
    }
845
846
    /**
847
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
848
     *
849
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
850
     *
851
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
852
     */
853
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
854
    {
855
        $spiLocationCreateStructs = [];
856
        $parentLocationIdSet = [];
857
        $mainLocation = true;
858
859
        foreach ($locationCreateStructs as $locationCreateStruct) {
860
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
861
                throw new InvalidArgumentException(
862
                    '$locationCreateStructs',
863
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
864
                );
865
            }
866
867
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
868
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
869
            }
870
871
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
872
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
873
            }
874
875
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
876
            $parentLocation = $this->repository->getLocationService()->loadLocation(
877
                $locationCreateStruct->parentLocationId
878
            );
879
880
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
881
                $locationCreateStruct,
882
                $parentLocation,
883
                $mainLocation,
884
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
885
                null,
886
                null
887
            );
888
889
            // First Location in the list will be created as main Location
890
            $mainLocation = false;
891
        }
892
893
        return $spiLocationCreateStructs;
894
    }
895
896
    /**
897
     * Updates the metadata.
898
     *
899
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
900
     *
901
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
902
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
903
     *
904
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
905
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
906
     *
907
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
908
     */
909
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
910
    {
911
        $propertyCount = 0;
912
        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...
913
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
914
                $propertyCount += 1;
915
            }
916
        }
917
        if ($propertyCount === 0) {
918
            throw new InvalidArgumentException(
919
                '$contentMetadataUpdateStruct',
920
                'At least one property must be set'
921
            );
922
        }
923
924
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
925
926
        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...
927
            throw new UnauthorizedException('content', 'edit', ['contentId' => $loadedContentInfo->id]);
928
        }
929
930
        if (isset($contentMetadataUpdateStruct->remoteId)) {
931
            try {
932
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
933
934
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
935
                    throw new InvalidArgumentException(
936
                        '$contentMetadataUpdateStruct',
937
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
938
                    );
939
                }
940
            } catch (APINotFoundException $e) {
941
                // Do nothing
942
            }
943
        }
944
945
        $this->repository->beginTransaction();
946
        try {
947
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
948
                $this->persistenceHandler->contentHandler()->updateMetadata(
949
                    $loadedContentInfo->id,
950
                    new SPIMetadataUpdateStruct(
951
                        [
952
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
953
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
954
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
955
                                null,
956
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
957
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
958
                                null,
959
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
960
                                $this->repository->getContentLanguageService()->loadLanguage(
961
                                    $contentMetadataUpdateStruct->mainLanguageCode
962
                                )->id :
963
                                null,
964
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
965
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
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 View Code Duplication
        foreach ($locations as $location) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1010
            foreach ($urlAliasNames as $languageCode => $name) {
1011
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
1012
                    $location->id,
1013
                    $location->parentLocationId,
1014
                    $name,
1015
                    $languageCode,
1016
                    $content->contentInfo->alwaysAvailable,
1017
                    $updatePathIdentificationString ? $languageCode === $content->contentInfo->mainLanguageCode : false
1018
                );
1019
            }
1020
        }
1021
    }
1022
1023
    /**
1024
     * Deletes a content object including all its versions and locations including their subtrees.
1025
     *
1026
     * @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)
1027
     *
1028
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1029
     *
1030
     * @return mixed[] Affected Location Id's
1031
     */
1032
    public function deleteContent(ContentInfo $contentInfo)
1033
    {
1034
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1035
1036
        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...
1037
            throw new UnauthorizedException('content', 'remove', ['contentId' => $contentInfo->id]);
1038
        }
1039
1040
        $affectedLocations = [];
1041
        $this->repository->beginTransaction();
1042
        try {
1043
            // Load Locations first as deleting Content also deletes belonging Locations
1044
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1045
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1046
            foreach ($spiLocations as $spiLocation) {
1047
                $this->persistenceHandler->urlAliasHandler()->locationDeleted($spiLocation->id);
1048
                $affectedLocations[] = $spiLocation->id;
1049
            }
1050
            $this->repository->commit();
1051
        } catch (Exception $e) {
1052
            $this->repository->rollback();
1053
            throw $e;
1054
        }
1055
1056
        return $affectedLocations;
1057
    }
1058
1059
    /**
1060
     * Creates a draft from a published or archived version.
1061
     *
1062
     * If no version is given, the current published version is used.
1063
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1064
     * It can be changed on updating the version.
1065
     *
1066
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1067
     *
1068
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1069
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1070
     * @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
1071
     *
1072
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1073
     */
1074
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1075
    {
1076
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1077
1078
        if ($versionInfo !== null) {
1079
            // Check that given $contentInfo and $versionInfo belong to the same content
1080
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1081
                throw new InvalidArgumentException(
1082
                    '$versionInfo',
1083
                    'VersionInfo does not belong to the same content as given ContentInfo'
1084
                );
1085
            }
1086
1087
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1088
1089
            switch ($versionInfo->status) {
1090
                case VersionInfo::STATUS_PUBLISHED:
1091
                case VersionInfo::STATUS_ARCHIVED:
1092
                    break;
1093
1094
                default:
1095
                    // @todo: throw an exception here, to be defined
1096
                    throw new BadStateException(
1097
                        '$versionInfo',
1098
                        'Draft can not be created from a draft version'
1099
                    );
1100
            }
1101
1102
            $versionNo = $versionInfo->versionNo;
1103
        } elseif ($contentInfo->published) {
1104
            $versionNo = $contentInfo->currentVersionNo;
1105
        } else {
1106
            // @todo: throw an exception here, to be defined
1107
            throw new BadStateException(
1108
                '$contentInfo',
1109
                'Content is not published, draft can be created only from published or archived version'
1110
            );
1111
        }
1112
1113
        if ($creator === null) {
1114
            $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...
1115
        }
1116
1117
        if (!$this->repository->canUser('content', 'edit', $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...
1118
            throw new UnauthorizedException('content', 'edit', ['contentId' => $contentInfo->id]);
1119
        }
1120
1121
        $this->repository->beginTransaction();
1122
        try {
1123
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1124
                $contentInfo->id,
1125
                $versionNo,
1126
                $creator->getUserId()
1127
            );
1128
            $this->repository->commit();
1129
        } catch (Exception $e) {
1130
            $this->repository->rollback();
1131
            throw $e;
1132
        }
1133
1134
        return $this->domainMapper->buildContentDomainObject($spiContent);
1135
    }
1136
1137
    /**
1138
     * Loads drafts for a user.
1139
     *
1140
     * If no user is given the drafts for the authenticated user a returned
1141
     *
1142
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list
1143
     *
1144
     * @param \eZ\Publish\API\Repository\Values\User\UserReference $user
1145
     *
1146
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo the drafts ({@link VersionInfo}) owned by the given user
1147
     */
1148
    public function loadContentDrafts(User $user = null)
1149
    {
1150
        if ($user === null) {
1151
            $user = $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...
1152
        }
1153
1154
        // throw early if user has absolutely no access to versionread
1155
        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...
1156
            throw new UnauthorizedException('content', 'versionread');
1157
        }
1158
1159
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser($user->getUserId());
1160
        $versionInfoList = [];
1161 View Code Duplication
        foreach ($spiVersionInfoList as $spiVersionInfo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1162
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1163
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1164
            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...
1165
                throw new UnauthorizedException('content', 'versionread', ['contentId' => $versionInfo->contentInfo->id]);
1166
            }
1167
1168
            $versionInfoList[] = $versionInfo;
1169
        }
1170
1171
        return $versionInfoList;
1172
    }
1173
1174
    /**
1175
     * Translate a version.
1176
     *
1177
     * updates the destination version given in $translationInfo with the provided translated fields in $translationValues
1178
     *
1179
     * @example Examples/translation_5x.php
1180
     *
1181
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to update this version
1182
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the given destination version is not a draft
1183
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $translationValues is not valid, or if a required field is missing or is set to an empty value.
1184
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1185
     *                                                                          or value is set for non-translatable field in language
1186
     *                                                                          other than main.
1187
     *
1188
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1189
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationValues $translationValues
1190
     * @param \eZ\Publish\API\Repository\Values\User\User $modifier If set, this user is taken as modifier of the version
1191
     *
1192
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the translated fields
1193
     *
1194
     * @since 5.0
1195
     */
1196
    public function translateVersion(TranslationInfo $translationInfo, APITranslationValues $translationValues, User $modifier = null)
1197
    {
1198
        throw new NotImplementedException(__METHOD__);
1199
    }
1200
1201
    /**
1202
     * Updates the fields of a draft.
1203
     *
1204
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1205
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1206
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1207
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1208
     *                                                                               or if a required field is missing / set to an empty value.
1209
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1210
     *                                                                          or value is set for non-translatable field in language
1211
     *                                                                          other than main.
1212
     *
1213
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1214
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1215
     *
1216
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1217
     */
1218
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1219
    {
1220
        $contentUpdateStruct = clone $contentUpdateStruct;
1221
1222
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1223
        $content = $this->loadContent(
1224
            $versionInfo->getContentInfo()->id,
1225
            null,
1226
            $versionInfo->versionNo
1227
        );
1228
        if ($content->versionInfo->status !== APIVersionInfo::STATUS_DRAFT) {
1229
            throw new BadStateException(
1230
                '$versionInfo',
1231
                'Version is not a draft and can not be updated'
1232
            );
1233
        }
1234
1235
        if (!$this->repository->canUser('content', 'edit', $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...
1236
            throw new UnauthorizedException('content', 'edit', ['contentId' => $content->id]);
1237
        }
1238
1239
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1240
        if ($contentUpdateStruct->initialLanguageCode === null) {
1241
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1242
        }
1243
1244
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1245
        foreach ($allLanguageCodes as $languageCode) {
1246
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode($languageCode);
1247
        }
1248
1249
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1250
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1251
            $content->contentInfo->contentTypeId
1252
        );
1253
        $fields = $this->mapFieldsForUpdate(
1254
            $contentUpdateStruct,
1255
            $contentType,
1256
            $mainLanguageCode
1257
        );
1258
1259
        $fieldValues = [];
1260
        $spiFields = [];
1261
        $allFieldErrors = [];
1262
        $inputRelations = [];
1263
        $locationIdToContentIdMapping = [];
1264
1265
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1266
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1267
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1268
                $fieldDefinition->fieldTypeIdentifier
1269
            );
1270
1271
            foreach ($allLanguageCodes as $languageCode) {
1272
                $isCopied = $isEmpty = $isRetained = false;
1273
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1274
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1275
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1276
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1277
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1278
1279
                if (!$isFieldUpdated && !$isLanguageNew) {
1280
                    $isRetained = true;
1281
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1282
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1283
                    $isCopied = true;
1284
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1285
                } elseif ($isFieldUpdated) {
1286
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1287
                } else {
1288
                    $fieldValue = $fieldDefinition->defaultValue;
1289
                }
1290
1291
                $fieldValue = $fieldType->acceptValue($fieldValue);
1292
1293 View Code Duplication
                if ($fieldType->isEmptyValue($fieldValue)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1294
                    $isEmpty = true;
1295
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1296
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1297
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1298
                            null,
1299
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1300
                            'empty'
1301
                        );
1302
                    }
1303
                } elseif ($isLanguageUpdated) {
1304
                    $fieldErrors = $fieldType->validate(
1305
                        $fieldDefinition,
1306
                        $fieldValue
1307
                    );
1308
                    if (!empty($fieldErrors)) {
1309
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1310
                    }
1311
                }
1312
1313
                if (!empty($allFieldErrors)) {
1314
                    continue;
1315
                }
1316
1317
                $this->relationProcessor->appendFieldRelations(
1318
                    $inputRelations,
1319
                    $locationIdToContentIdMapping,
1320
                    $fieldType,
1321
                    $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...
1322
                    $fieldDefinition->id
1323
                );
1324
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1325
1326
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1327
                    continue;
1328
                }
1329
1330
                $spiFields[] = new SPIField(
1331
                    [
1332
                        'id' => $isLanguageNew ?
1333
                            null :
1334
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1335
                        'fieldDefinitionId' => $fieldDefinition->id,
1336
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1337
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1338
                        'languageCode' => $languageCode,
1339
                        'versionNo' => $versionInfo->versionNo,
1340
                    ]
1341
                );
1342
            }
1343
        }
1344
1345
        if (!empty($allFieldErrors)) {
1346
            throw new ContentFieldValidationException($allFieldErrors);
1347
        }
1348
1349
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1350
            [
1351
                'name' => $this->nameSchemaService->resolveNameSchema(
1352
                    $content,
1353
                    $fieldValues,
1354
                    $allLanguageCodes,
1355
                    $contentType
1356
                ),
1357
                '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...
1358
                'fields' => $spiFields,
1359
                'modificationDate' => time(),
1360
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1361
                    $contentUpdateStruct->initialLanguageCode
1362
                )->id,
1363
            ]
1364
        );
1365
        $existingRelations = $this->loadRelations($versionInfo);
1366
1367
        $this->repository->beginTransaction();
1368
        try {
1369
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1370
                $versionInfo->getContentInfo()->id,
1371
                $versionInfo->versionNo,
1372
                $spiContentUpdateStruct
1373
            );
1374
            $this->relationProcessor->processFieldRelations(
1375
                $inputRelations,
1376
                $spiContent->versionInfo->contentInfo->id,
1377
                $spiContent->versionInfo->versionNo,
1378
                $contentType,
1379
                $existingRelations
1380
            );
1381
            $this->repository->commit();
1382
        } catch (Exception $e) {
1383
            $this->repository->rollback();
1384
            throw $e;
1385
        }
1386
1387
        return $this->domainMapper->buildContentDomainObject(
1388
            $spiContent,
1389
            $contentType
1390
        );
1391
    }
1392
1393
    /**
1394
     * Returns only updated language codes.
1395
     *
1396
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1397
     *
1398
     * @return array
1399
     */
1400 View Code Duplication
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1401
    {
1402
        $languageCodes = [
1403
            $contentUpdateStruct->initialLanguageCode => true,
1404
        ];
1405
1406
        foreach ($contentUpdateStruct->fields as $field) {
1407
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1408
                continue;
1409
            }
1410
1411
            $languageCodes[$field->languageCode] = true;
1412
        }
1413
1414
        return array_keys($languageCodes);
1415
    }
1416
1417
    /**
1418
     * Returns all language codes used in given $fields.
1419
     *
1420
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1421
     *
1422
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1423
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1424
     *
1425
     * @return array
1426
     */
1427
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1428
    {
1429
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1430
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1431
1432
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1433
        foreach ($updatedLanguageCodes as $languageCode) {
1434
            $languageCodes[$languageCode] = true;
1435
        }
1436
1437
        return array_keys($languageCodes);
1438
    }
1439
1440
    /**
1441
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1442
     *
1443
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1444
     *                                                                          or value is set for non-translatable field in language
1445
     *                                                                          other than main
1446
     *
1447
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1448
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1449
     * @param string $mainLanguageCode
1450
     *
1451
     * @return array
1452
     */
1453
    protected function mapFieldsForUpdate(
1454
        APIContentUpdateStruct $contentUpdateStruct,
1455
        ContentType $contentType,
1456
        $mainLanguageCode
1457
    ) {
1458
        $fields = [];
1459
1460
        foreach ($contentUpdateStruct->fields as $field) {
1461
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1462
1463
            if ($fieldDefinition === null) {
1464
                throw new ContentValidationException(
1465
                    "Field definition '%identifier%' does not exist in given ContentType",
1466
                    ['%identifier%' => $field->fieldDefIdentifier]
1467
                );
1468
            }
1469
1470
            if ($field->languageCode === null) {
1471
                if ($fieldDefinition->isTranslatable) {
1472
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1473
                } else {
1474
                    $languageCode = $mainLanguageCode;
1475
                }
1476
                $field = $this->cloneField($field, ['languageCode' => $languageCode]);
1477
            }
1478
1479 View Code Duplication
            if (!$fieldDefinition->isTranslatable && ($field->languageCode != $mainLanguageCode)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1480
                throw new ContentValidationException(
1481
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1482
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1483
                );
1484
            }
1485
1486
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1487
        }
1488
1489
        return $fields;
1490
    }
1491
1492
    /**
1493
     * Publishes a content version.
1494
     *
1495
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1496
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1497
     *
1498
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version
1499
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1500
     *
1501
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1502
     *
1503
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1504
     */
1505
    public function publishVersion(APIVersionInfo $versionInfo)
1506
    {
1507
        $content = $this->internalLoadContent(
1508
            $versionInfo->contentInfo->id,
1509
            null,
1510
            $versionInfo->versionNo
1511
        );
1512
1513
        if (!$content->getVersionInfo()->getContentInfo()->published) {
1514
            if (!$this->repository->canUser('content', 'create', $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...
1515
                throw new UnauthorizedException('content', 'create', ['contentId' => $content->id]);
1516
            }
1517
        } elseif (!$this->repository->canUser('content', 'edit', $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...
1518
            throw new UnauthorizedException('content', 'edit', ['contentId' => $content->id]);
1519
        }
1520
1521
        $this->repository->beginTransaction();
1522
        try {
1523
            $content = $this->internalPublishVersion($content->getVersionInfo());
1524
            $this->repository->commit();
1525
        } catch (Exception $e) {
1526
            $this->repository->rollback();
1527
            throw $e;
1528
        }
1529
1530
        return $content;
1531
    }
1532
1533
    /**
1534
     * Publishes a content version.
1535
     *
1536
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1537
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1538
     *
1539
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1540
     *
1541
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1542
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1543
     *
1544
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1545
     */
1546
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1547
    {
1548
        if ($versionInfo->status !== APIVersionInfo::STATUS_DRAFT) {
1549
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1550
        }
1551
1552
        $currentTime = time();
1553
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1554
            $publicationDate = $currentTime;
1555
        }
1556
1557
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1558
        $metadataUpdateStruct->publicationDate = $publicationDate;
1559
        $metadataUpdateStruct->modificationDate = $currentTime;
1560
1561
        $contentId = $versionInfo->getContentInfo()->id;
1562
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1563
            $contentId,
1564
            $versionInfo->versionNo,
1565
            $metadataUpdateStruct
1566
        );
1567
1568
        $content = $this->domainMapper->buildContentDomainObject($spiContent);
1569
1570
        $this->publishUrlAliasesForContent($content);
1571
1572
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1573
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1574
            $contentId,
1575
            APIVersionInfo::STATUS_ARCHIVED,
1576
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1577
        );
1578
1579
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1580
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1581
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1582
            $archiveVersion = array_shift($archiveList);
1583
            $this->persistenceHandler->contentHandler()->deleteVersion(
1584
                $contentId,
1585
                $archiveVersion->versionNo
1586
            );
1587
        }
1588
1589
        return $content;
1590
    }
1591
1592
    /**
1593
     * Removes the given version.
1594
     *
1595
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1596
     *         published state or is the last version of the Content
1597
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1598
     *
1599
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1600
     */
1601
    public function deleteVersion(APIVersionInfo $versionInfo)
1602
    {
1603
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
1604
            throw new BadStateException(
1605
                '$versionInfo',
1606
                'Version is published and can not be removed'
1607
            );
1608
        }
1609
1610 View Code Duplication
        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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1611
            throw new UnauthorizedException(
1612
                'content',
1613
                'versionremove',
1614
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1615
            );
1616
        }
1617
1618
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1619
            $versionInfo->contentInfo->id,
1620
            null,
1621
            2
1622
        );
1623
1624
        if (count($versionList) === 1) {
1625
            throw new BadStateException(
1626
                '$versionInfo',
1627
                'Version is the last version of the Content and can not be removed'
1628
            );
1629
        }
1630
1631
        $this->repository->beginTransaction();
1632
        try {
1633
            $this->persistenceHandler->contentHandler()->deleteVersion(
1634
                $versionInfo->getContentInfo()->id,
1635
                $versionInfo->versionNo
1636
            );
1637
            $this->repository->commit();
1638
        } catch (Exception $e) {
1639
            $this->repository->rollback();
1640
            throw $e;
1641
        }
1642
    }
1643
1644
    /**
1645
     * Loads all versions for the given content.
1646
     *
1647
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1648
     *
1649
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1650
     *
1651
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1652
     */
1653
    public function loadVersions(ContentInfo $contentInfo)
1654
    {
1655
        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...
1656
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1657
        }
1658
1659
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1660
1661
        $versions = [];
1662 View Code Duplication
        foreach ($spiVersionInfoList as $spiVersionInfo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1663
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1664
            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...
1665
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1666
            }
1667
1668
            $versions[] = $versionInfo;
1669
        }
1670
1671
        return $versions;
1672
    }
1673
1674
    /**
1675
     * Copies the content to a new location. If no version is given,
1676
     * all versions are copied, otherwise only the given version.
1677
     *
1678
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1679
     *
1680
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1681
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1682
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1683
     *
1684
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1685
     */
1686
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1687
    {
1688
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1689
            $destinationLocationCreateStruct->parentLocationId
1690
        );
1691 View Code Duplication
        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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1692
            throw new UnauthorizedException(
1693
                'content',
1694
                'create',
1695
                [
1696
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1697
                    'sectionId' => $contentInfo->sectionId,
1698
                ]
1699
            );
1700
        }
1701
1702
        $defaultObjectStates = $this->getDefaultObjectStates();
1703
1704
        $this->repository->beginTransaction();
1705
        try {
1706
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1707
                $contentInfo->id,
1708
                $versionInfo ? $versionInfo->versionNo : null,
1709
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1710
            );
1711
1712
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1713
                $this->persistenceHandler->objectStateHandler()->setContentState(
1714
                    $spiContent->versionInfo->contentInfo->id,
1715
                    $objectStateGroupId,
1716
                    $objectState->id
1717
                );
1718
            }
1719
1720
            $content = $this->internalPublishVersion(
1721
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1722
                $spiContent->versionInfo->creationDate
1723
            );
1724
1725
            $this->repository->getLocationService()->createLocation(
1726
                $content->getVersionInfo()->getContentInfo(),
1727
                $destinationLocationCreateStruct
1728
            );
1729
            $this->repository->commit();
1730
        } catch (Exception $e) {
1731
            $this->repository->rollback();
1732
            throw $e;
1733
        }
1734
1735
        return $this->internalLoadContent($content->id);
1736
    }
1737
1738
    /**
1739
     * Loads all outgoing relations for the given version.
1740
     *
1741
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1742
     *
1743
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1744
     *
1745
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1746
     */
1747
    public function loadRelations(APIVersionInfo $versionInfo)
1748
    {
1749
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
1750
            $function = 'read';
1751
        } else {
1752
            $function = 'versionread';
1753
        }
1754
1755
        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...
1756
            throw new UnauthorizedException('content', $function);
1757
        }
1758
1759
        $contentInfo = $versionInfo->getContentInfo();
1760
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1761
            $contentInfo->id,
1762
            $versionInfo->versionNo
1763
        );
1764
1765
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1766
        $relations = [];
1767 View Code Duplication
        foreach ($spiRelations as $spiRelation) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1768
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1769
            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...
1770
                continue;
1771
            }
1772
1773
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1774
                $spiRelation,
1775
                $contentInfo,
1776
                $destinationContentInfo
1777
            );
1778
        }
1779
1780
        return $relations;
1781
    }
1782
1783
    /**
1784
     * Loads all incoming relations for a content object.
1785
     *
1786
     * The relations come only from published versions of the source content objects
1787
     *
1788
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1789
     *
1790
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1791
     *
1792
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1793
     */
1794
    public function loadReverseRelations(ContentInfo $contentInfo)
1795
    {
1796
        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...
1797
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
1798
        }
1799
1800
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1801
            $contentInfo->id
1802
        );
1803
1804
        $returnArray = [];
1805 View Code Duplication
        foreach ($spiRelations as $spiRelation) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1806
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1807
            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...
1808
                continue;
1809
            }
1810
1811
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1812
                $spiRelation,
1813
                $sourceContentInfo,
1814
                $contentInfo
1815
            );
1816
        }
1817
1818
        return $returnArray;
1819
    }
1820
1821
    /**
1822
     * Adds a relation of type common.
1823
     *
1824
     * The source of the relation is the content and version
1825
     * referenced by $versionInfo.
1826
     *
1827
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1828
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1829
     *
1830
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1831
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1832
     *
1833
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1834
     */
1835
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1836
    {
1837
        $sourceVersion = $this->loadVersionInfoById(
1838
            $sourceVersion->contentInfo->id,
1839
            $sourceVersion->versionNo
1840
        );
1841
1842
        if ($sourceVersion->status !== APIVersionInfo::STATUS_DRAFT) {
1843
            throw new BadStateException(
1844
                '$sourceVersion',
1845
                'Relations of type common can only be added to versions of status draft'
1846
            );
1847
        }
1848
1849
        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...
1850
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
1851
        }
1852
1853
        $sourceContentInfo = $sourceVersion->getContentInfo();
1854
1855
        $this->repository->beginTransaction();
1856
        try {
1857
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1858
                new SPIRelationCreateStruct(
1859
                    [
1860
                        'sourceContentId' => $sourceContentInfo->id,
1861
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1862
                        'sourceFieldDefinitionId' => null,
1863
                        'destinationContentId' => $destinationContent->id,
1864
                        'type' => APIRelation::COMMON,
1865
                    ]
1866
                )
1867
            );
1868
            $this->repository->commit();
1869
        } catch (Exception $e) {
1870
            $this->repository->rollback();
1871
            throw $e;
1872
        }
1873
1874
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1875
    }
1876
1877
    /**
1878
     * Removes a relation of type COMMON from a draft.
1879
     *
1880
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1881
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1882
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1883
     *
1884
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1885
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1886
     */
1887
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1888
    {
1889
        $sourceVersion = $this->loadVersionInfoById(
1890
            $sourceVersion->contentInfo->id,
1891
            $sourceVersion->versionNo
1892
        );
1893
1894
        if ($sourceVersion->status !== APIVersionInfo::STATUS_DRAFT) {
1895
            throw new BadStateException(
1896
                '$sourceVersion',
1897
                'Relations of type common can only be removed from versions of status draft'
1898
            );
1899
        }
1900
1901
        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...
1902
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
1903
        }
1904
1905
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1906
            $sourceVersion->getContentInfo()->id,
1907
            $sourceVersion->versionNo,
1908
            APIRelation::COMMON
1909
        );
1910
1911
        if (empty($spiRelations)) {
1912
            throw new InvalidArgumentException(
1913
                '$sourceVersion',
1914
                'There are no relations of type COMMON for the given destination'
1915
            );
1916
        }
1917
1918
        // there should be only one relation of type COMMON for each destination,
1919
        // but in case there were ever more then one, we will remove them all
1920
        // @todo: alternatively, throw BadStateException?
1921
        $this->repository->beginTransaction();
1922
        try {
1923
            foreach ($spiRelations as $spiRelation) {
1924
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1925
                    $this->persistenceHandler->contentHandler()->removeRelation(
1926
                        $spiRelation->id,
1927
                        APIRelation::COMMON
1928
                    );
1929
                }
1930
            }
1931
            $this->repository->commit();
1932
        } catch (Exception $e) {
1933
            $this->repository->rollback();
1934
            throw $e;
1935
        }
1936
    }
1937
1938
    /**
1939
     * Adds translation information to the content object.
1940
     *
1941
     * @example Examples/translation_5x.php
1942
     *
1943
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed add a translation info
1944
     *
1945
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1946
     *
1947
     * @since 5.0
1948
     */
1949
    public function addTranslationInfo(TranslationInfo $translationInfo)
1950
    {
1951
        throw new NotImplementedException(__METHOD__);
1952
    }
1953
1954
    /**
1955
     * lists the translations done on this content object.
1956
     *
1957
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed read translation infos
1958
     *
1959
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1960
     * @param array $filter
1961
     *
1962
     * @todo TBD - filter by source version destination version and languages
1963
     *
1964
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo[]
1965
     *
1966
     * @since 5.0
1967
     */
1968
    public function loadTranslationInfos(ContentInfo $contentInfo, array $filter = [])
1969
    {
1970
        throw new NotImplementedException(__METHOD__);
1971
    }
1972
1973
    /**
1974
     * Instantiates a new content create struct object.
1975
     *
1976
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
1977
     *
1978
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1979
     * @param string $mainLanguageCode
1980
     *
1981
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
1982
     */
1983
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
1984
    {
1985
        return new ContentCreateStruct(
1986
            [
1987
                'contentType' => $contentType,
1988
                'mainLanguageCode' => $mainLanguageCode,
1989
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
1990
            ]
1991
        );
1992
    }
1993
1994
    /**
1995
     * Instantiates a new content meta data update struct.
1996
     *
1997
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
1998
     */
1999
    public function newContentMetadataUpdateStruct()
2000
    {
2001
        return new ContentMetadataUpdateStruct();
2002
    }
2003
2004
    /**
2005
     * Instantiates a new content update struct.
2006
     *
2007
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2008
     */
2009
    public function newContentUpdateStruct()
2010
    {
2011
        return new ContentUpdateStruct();
2012
    }
2013
2014
    /**
2015
     * Instantiates a new TranslationInfo object.
2016
     *
2017
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo
2018
     */
2019
    public function newTranslationInfo()
2020
    {
2021
        return new TranslationInfo();
2022
    }
2023
2024
    /**
2025
     * Instantiates a Translation object.
2026
     *
2027
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationValues
2028
     */
2029
    public function newTranslationValues()
2030
    {
2031
        return new TranslationValues();
2032
    }
2033
}
2034