Completed
Push — EZP-31644 ( 2e0a1e...93bb44 )
by
unknown
19:12
created

ContentService::getUpdatedLanguageCodes()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 16
Ratio 100 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 16
loc 16
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\ContentService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\ContentService as ContentServiceInterface;
12
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
13
use eZ\Publish\Core\Repository\Values\Content\Location;
14
use eZ\Publish\API\Repository\Values\Content\Language;
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->isPublished()) {
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
     * {@inheritdoc}
264
     */
265
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
266
    {
267
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
268
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
269
            $useAlwaysAvailable = false;
270
        }
271
272
        return $this->loadContent(
273
            $contentInfo->id,
274
            $languages,
275
            $versionNo,// On purpose pass as-is and not use $contentInfo, to make sure to return actual current version on null
276
            $useAlwaysAvailable
277
        );
278
    }
279
280
    /**
281
     * {@inheritdoc}
282
     */
283
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
284
    {
285
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
286
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
287
            $useAlwaysAvailable = false;
288
        }
289
290
        return $this->loadContent(
291
            $versionInfo->getContentInfo()->id,
292
            $languages,
293
            $versionInfo->versionNo,
294
            $useAlwaysAvailable
295
        );
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
302
    {
303
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
304
305
        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...
306
            throw new UnauthorizedException('content', 'read', ['contentId' => $contentId]);
307
        }
308
        if (
309
            !$content->getVersionInfo()->isPublished()
310
            && !$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...
311
        ) {
312
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentId, 'versionNo' => $versionNo]);
313
        }
314
315
        return $content;
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
     * @internal
324
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
325
     *
326
     * @param mixed $id
327
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
328
     *                         returned value object. If not given all languages are returned.
329
     * @param int|null $versionNo the version number. If not given the current version is returned
330
     * @param bool $isRemoteId
331
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
332
     *
333
     * @return \eZ\Publish\API\Repository\Values\Content\Content
334
     */
335
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
336
    {
337
        try {
338
            // Get Content ID if lookup by remote ID
339
            if ($isRemoteId) {
340
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
341
                $id = $spiContentInfo->id;
342
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
343
                $isRemoteId = false;
344
            }
345
346
            $loadLanguages = $languages;
347
            $alwaysAvailableLanguageCode = null;
348
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
349
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
350
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
351
                if (!isset($spiContentInfo)) {
352
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
353
                }
354
355
                if ($spiContentInfo->alwaysAvailable) {
356
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
357
                    $loadLanguages = array_unique($loadLanguages);
358
                }
359
            }
360
361
            $spiContent = $this->persistenceHandler->contentHandler()->load(
362
                $id,
363
                $versionNo,
364
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 346 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...
365
            );
366
        } catch (APINotFoundException $e) {
367
            throw new NotFoundException(
368
                'Content',
369
                [
370
                    $isRemoteId ? 'remoteId' : 'id' => $id,
371
                    'languages' => $languages,
372
                    'versionNo' => $versionNo,
373
                ],
374
                $e
375
            );
376
        }
377
378
        return $this->domainMapper->buildContentDomainObject(
379
            $spiContent,
380
            null,
381
            empty($languages) ? null : $languages,
382
            $alwaysAvailableLanguageCode
383
        );
384
    }
385
386
    /**
387
     * Loads content in a version for the content object reference by the given remote id.
388
     *
389
     * If no version is given, the method returns the current version
390
     *
391
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
392
     * @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
393
     *
394
     * @param string $remoteId
395
     * @param array $languages A language filter for fields. If not given all languages are returned
396
     * @param int $versionNo the version number. If not given the current version is returned
397
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
398
     *
399
     * @return \eZ\Publish\API\Repository\Values\Content\Content
400
     */
401 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
402
    {
403
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
404
405
        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...
406
            throw new UnauthorizedException('content', 'read', ['remoteId' => $remoteId]);
407
        }
408
409
        if (
410
            !$content->getVersionInfo()->isPublished()
411
            && !$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...
412
        ) {
413
            throw new UnauthorizedException('content', 'versionread', ['remoteId' => $remoteId, 'versionNo' => $versionNo]);
414
        }
415
416
        return $content;
417
    }
418
419
    /**
420
     * Bulk-load Content items by the list of ContentInfo Value Objects.
421
     *
422
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
423
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
424
     * allowed to access every Content on the list.
425
     *
426
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
427
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
428
     *                            returned value object. If not given all languages are returned.
429
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
430
     *                                 unless all languages have been asked for.
431
     *
432
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
433
     */
434
    public function loadContentListByContentInfo(
435
        array $contentInfoList,
436
        array $languages = [],
437
        $useAlwaysAvailable = true
438
    ) {
439
        $loadAllLanguages = $languages === Language::ALL;
440
        $contentIds = [];
441
        $translations = $languages;
442
        foreach ($contentInfoList as $contentInfo) {
443
            $contentIds[] = $contentInfo->id;
444
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
445
            // Might in some case load more languages then intended, but prioritised handling will pick right one
446
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
447
                $translations[] = $contentInfo->mainLanguageCode;
448
            }
449
        }
450
        $translations = array_unique($translations);
451
452
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
453
            $contentIds,
454
            $translations
455
        );
456
        $contentList = [];
457
        foreach ($spiContentList as $contentId => $spiContent) {
458
            $contentInfo = $spiContent->versionInfo->contentInfo;
459
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
460
                $spiContent,
461
                null,
462
                $languages,
463
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
464
            );
465
        }
466
467
        return $contentList;
468
    }
469
470
    /**
471
     * Creates a new content draft assigned to the authenticated user.
472
     *
473
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
474
     * but this required special rights for the authenticated user
475
     * (this is useful for content staging where the transfer process does not
476
     * have to authenticate with the user which created the content object in the source server).
477
     * The user has to publish the draft if it should be visible.
478
     * In 4.x at least one location has to be provided in the location creation array.
479
     *
480
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
481
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
482
     *                                                                        struct are missing or invalid, or if multiple locations are under the
483
     *                                                                        same parent.
484
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
485
     *                                                                               or if a required field is missing / set to an empty value.
486
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
487
     *                                                                          or value is set for non-translatable field in language
488
     *                                                                          other than main.
489
     *
490
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
491
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
492
     *
493
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
494
     */
495
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = [])
496
    {
497
        if ($contentCreateStruct->mainLanguageCode === null) {
498
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
499
        }
500
501
        if ($contentCreateStruct->contentType === null) {
502
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
503
        }
504
505
        $contentCreateStruct = clone $contentCreateStruct;
506
507
        if ($contentCreateStruct->ownerId === null) {
508
            $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...
509
        }
510
511
        if ($contentCreateStruct->alwaysAvailable === null) {
512
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
513
        }
514
515
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
516
            $contentCreateStruct->contentType->id
517
        );
518
519
        if (empty($contentCreateStruct->sectionId)) {
520
            if (isset($locationCreateStructs[0])) {
521
                $location = $this->repository->getLocationService()->loadLocation(
522
                    $locationCreateStructs[0]->parentLocationId
523
                );
524
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
525
            } else {
526
                $contentCreateStruct->sectionId = 1;
527
            }
528
        }
529
530
        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...
531
            throw new UnauthorizedException(
532
                'content',
533
                'create',
534
                [
535
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
536
                            $locationCreateStructs[0]->parentLocationId :
537
                            null,
538
                    'sectionId' => $contentCreateStruct->sectionId,
539
                ]
540
            );
541
        }
542
543
        if (!empty($contentCreateStruct->remoteId)) {
544
            try {
545
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
546
547
                throw new InvalidArgumentException(
548
                    '$contentCreateStruct',
549
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
550
                );
551
            } catch (APINotFoundException $e) {
552
                // Do nothing
553
            }
554
        } else {
555
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
556
        }
557
558
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
559
560
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
561
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
562
563
        $fieldValues = [];
564
        $spiFields = [];
565
        $allFieldErrors = [];
566
        $inputRelations = [];
567
        $locationIdToContentIdMapping = [];
568
569
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
570
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
571
            $fieldType = $this->fieldTypeRegistry->getFieldType(
572
                $fieldDefinition->fieldTypeIdentifier
573
            );
574
575
            foreach ($languageCodes as $languageCode) {
576
                $isEmptyValue = false;
577
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
578
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
579
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
580
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
581
                } else {
582
                    $fieldValue = $fieldDefinition->defaultValue;
583
                }
584
585
                $fieldValue = $fieldType->acceptValue($fieldValue);
586
587 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...
588
                    $isEmptyValue = true;
589
                    if ($fieldDefinition->isRequired) {
590
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
591
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
592
                            null,
593
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
594
                            'empty'
595
                        );
596
                    }
597
                } else {
598
                    $fieldErrors = $fieldType->validate(
599
                        $fieldDefinition,
600
                        $fieldValue
601
                    );
602
                    if (!empty($fieldErrors)) {
603
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
604
                    }
605
                }
606
607
                if (!empty($allFieldErrors)) {
608
                    continue;
609
                }
610
611
                $this->relationProcessor->appendFieldRelations(
612
                    $inputRelations,
613
                    $locationIdToContentIdMapping,
614
                    $fieldType,
615
                    $fieldValue,
616
                    $fieldDefinition->id
617
                );
618
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
619
620
                // Only non-empty value for: translatable field or in main language
621
                if (
622
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
623
                    (!$isEmptyValue && $isLanguageMain)
624
                ) {
625
                    $spiFields[] = new SPIField(
626
                        [
627
                            'id' => null,
628
                            'fieldDefinitionId' => $fieldDefinition->id,
629
                            'type' => $fieldDefinition->fieldTypeIdentifier,
630
                            'value' => $fieldType->toPersistenceValue($fieldValue),
631
                            'languageCode' => $languageCode,
632
                            'versionNo' => null,
633
                        ]
634
                    );
635
                }
636
            }
637
        }
638
639
        if (!empty($allFieldErrors)) {
640
            throw new ContentFieldValidationException($allFieldErrors);
641
        }
642
643
        $spiContentCreateStruct = new SPIContentCreateStruct(
644
            [
645
                'name' => $this->nameSchemaService->resolve(
646
                    $contentCreateStruct->contentType->nameSchema,
647
                    $contentCreateStruct->contentType,
648
                    $fieldValues,
649
                    $languageCodes
650
                ),
651
                'typeId' => $contentCreateStruct->contentType->id,
652
                'sectionId' => $contentCreateStruct->sectionId,
653
                'ownerId' => $contentCreateStruct->ownerId,
654
                'locations' => $spiLocationCreateStructs,
655
                'fields' => $spiFields,
656
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
657
                'remoteId' => $contentCreateStruct->remoteId,
658
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
659
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
660
                    $contentCreateStruct->mainLanguageCode
661
                )->id,
662
            ]
663
        );
664
665
        $defaultObjectStates = $this->getDefaultObjectStates();
666
667
        $this->repository->beginTransaction();
668
        try {
669
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
670
            $this->relationProcessor->processFieldRelations(
671
                $inputRelations,
672
                $spiContent->versionInfo->contentInfo->id,
673
                $spiContent->versionInfo->versionNo,
674
                $contentCreateStruct->contentType
675
            );
676
677
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
678
                $this->persistenceHandler->objectStateHandler()->setContentState(
679
                    $spiContent->versionInfo->contentInfo->id,
680
                    $objectStateGroupId,
681
                    $objectState->id
682
                );
683
            }
684
685
            $this->repository->commit();
686
        } catch (Exception $e) {
687
            $this->repository->rollback();
688
            throw $e;
689
        }
690
691
        return $this->domainMapper->buildContentDomainObject($spiContent);
692
    }
693
694
    /**
695
     * Returns an array of default content states with content state group id as key.
696
     *
697
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
698
     */
699
    protected function getDefaultObjectStates()
700
    {
701
        $defaultObjectStatesMap = [];
702
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
703
704
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
705
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
706
                // Only register the first object state which is the default one.
707
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
708
                break;
709
            }
710
        }
711
712
        return $defaultObjectStatesMap;
713
    }
714
715
    /**
716
     * Returns all language codes used in given $fields.
717
     *
718
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
719
     *
720
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
721
     *
722
     * @return string[]
723
     */
724
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
725
    {
726
        $languageCodes = [];
727
728
        foreach ($contentCreateStruct->fields as $field) {
729
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
730
                continue;
731
            }
732
733
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
734
                $field->languageCode
735
            );
736
            $languageCodes[$field->languageCode] = true;
737
        }
738
739
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
740
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
741
                $contentCreateStruct->mainLanguageCode
742
            );
743
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
744
        }
745
746
        return array_keys($languageCodes);
747
    }
748
749
    /**
750
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
751
     *
752
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
753
     *                                                                          or value is set for non-translatable field in language
754
     *                                                                          other than main
755
     *
756
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
757
     *
758
     * @return array
759
     */
760
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
761
    {
762
        $fields = [];
763
764
        foreach ($contentCreateStruct->fields as $field) {
765
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
766
767
            if ($fieldDefinition === null) {
768
                throw new ContentValidationException(
769
                    "Field definition '%identifier%' does not exist in given ContentType",
770
                    ['%identifier%' => $field->fieldDefIdentifier]
771
                );
772
            }
773
774
            if ($field->languageCode === null) {
775
                $field = $this->cloneField(
776
                    $field,
777
                    ['languageCode' => $contentCreateStruct->mainLanguageCode]
778
                );
779
            }
780
781 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...
782
                throw new ContentValidationException(
783
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
784
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
785
                );
786
            }
787
788
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
789
        }
790
791
        return $fields;
792
    }
793
794
    /**
795
     * Clones $field with overriding specific properties from given $overrides array.
796
     *
797
     * @param Field $field
798
     * @param array $overrides
799
     *
800
     * @return Field
801
     */
802
    private function cloneField(Field $field, array $overrides = [])
803
    {
804
        $fieldData = array_merge(
805
            [
806
                'id' => $field->id,
807
                'value' => $field->value,
808
                'languageCode' => $field->languageCode,
809
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
810
                'fieldTypeIdentifier' => $field->fieldTypeIdentifier,
811
            ],
812
            $overrides
813
        );
814
815
        return new Field($fieldData);
816
    }
817
818
    /**
819
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
820
     *
821
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
822
     *
823
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
824
     */
825
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
826
    {
827
        $spiLocationCreateStructs = [];
828
        $parentLocationIdSet = [];
829
        $mainLocation = true;
830
831
        foreach ($locationCreateStructs as $locationCreateStruct) {
832
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
833
                throw new InvalidArgumentException(
834
                    '$locationCreateStructs',
835
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
836
                );
837
            }
838
839
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
840
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
841
            }
842
843
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
844
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
845
            }
846
847
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
848
            $parentLocation = $this->repository->getLocationService()->loadLocation(
849
                $locationCreateStruct->parentLocationId
850
            );
851
852
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
853
                $locationCreateStruct,
854
                $parentLocation,
855
                $mainLocation,
856
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
857
                null,
858
                null
859
            );
860
861
            // First Location in the list will be created as main Location
862
            $mainLocation = false;
863
        }
864
865
        return $spiLocationCreateStructs;
866
    }
867
868
    /**
869
     * Updates the metadata.
870
     *
871
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
872
     *
873
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
874
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
875
     *
876
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
877
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
878
     *
879
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
880
     */
881
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
882
    {
883
        $propertyCount = 0;
884
        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...
885
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
886
                $propertyCount += 1;
887
            }
888
        }
889
        if ($propertyCount === 0) {
890
            throw new InvalidArgumentException(
891
                '$contentMetadataUpdateStruct',
892
                'At least one property must be set'
893
            );
894
        }
895
896
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
897
898
        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...
899
            throw new UnauthorizedException('content', 'edit', ['contentId' => $loadedContentInfo->id]);
900
        }
901
902
        if (isset($contentMetadataUpdateStruct->remoteId)) {
903
            try {
904
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
905
906
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
907
                    throw new InvalidArgumentException(
908
                        '$contentMetadataUpdateStruct',
909
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
910
                    );
911
                }
912
            } catch (APINotFoundException $e) {
913
                // Do nothing
914
            }
915
        }
916
917
        $this->repository->beginTransaction();
918
        try {
919
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
920
                $this->persistenceHandler->contentHandler()->updateMetadata(
921
                    $loadedContentInfo->id,
922
                    new SPIMetadataUpdateStruct(
923
                        [
924
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
925
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
926
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
927
                                null,
928
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
929
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
930
                                null,
931
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
932
                                $this->repository->getContentLanguageService()->loadLanguage(
933
                                    $contentMetadataUpdateStruct->mainLanguageCode
934
                                )->id :
935
                                null,
936
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
937
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
938
                        ]
939
                    )
940
                );
941
            }
942
943
            // Change main location
944
            if (isset($contentMetadataUpdateStruct->mainLocationId)
945
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
946
                $this->persistenceHandler->locationHandler()->changeMainLocation(
947
                    $loadedContentInfo->id,
948
                    $contentMetadataUpdateStruct->mainLocationId
949
                );
950
            }
951
952
            // Republish URL aliases to update always-available flag
953
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
954
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
955
                $content = $this->loadContent($loadedContentInfo->id);
956
                $this->publishUrlAliasesForContent($content, false);
957
            }
958
959
            $this->repository->commit();
960
        } catch (Exception $e) {
961
            $this->repository->rollback();
962
            throw $e;
963
        }
964
965
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
966
    }
967
968
    /**
969
     * Publishes URL aliases for all locations of a given content.
970
     *
971
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
972
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
973
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
974
     */
975
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
976
    {
977
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
978
        $locations = $this->repository->getLocationService()->loadLocations(
979
            $content->getVersionInfo()->getContentInfo()
980
        );
981
        foreach ($locations as $location) {
982 View Code Duplication
            foreach ($urlAliasNames as $languageCode => $name) {
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...
983
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
984
                    $location->id,
985
                    $location->parentLocationId,
986
                    $name,
987
                    $languageCode,
988
                    $content->contentInfo->alwaysAvailable,
989
                    $updatePathIdentificationString ? $languageCode === $content->contentInfo->mainLanguageCode : false
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $languageCode (integer) and $content->contentInfo->mainLanguageCode (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
990
                );
991
            }
992
            // archive URL aliases of Translations that got deleted
993
            $this->persistenceHandler->urlAliasHandler()->archiveUrlAliasesForDeletedTranslations(
994
                $location->id,
995
                $location->parentLocationId,
996
                $content->versionInfo->languageCodes
997
            );
998
        }
999
    }
1000
1001
    /**
1002
     * Deletes a content object including all its versions and locations including their subtrees.
1003
     *
1004
     * @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)
1005
     *
1006
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1007
     *
1008
     * @return mixed[] Affected Location Id's
1009
     */
1010
    public function deleteContent(ContentInfo $contentInfo)
1011
    {
1012
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1013
1014
        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...
1015
            throw new UnauthorizedException('content', 'remove', ['contentId' => $contentInfo->id]);
1016
        }
1017
1018
        $affectedLocations = [];
1019
        $this->repository->beginTransaction();
1020
        try {
1021
            // Load Locations first as deleting Content also deletes belonging Locations
1022
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1023
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1024
            foreach ($spiLocations as $spiLocation) {
1025
                $this->persistenceHandler->urlAliasHandler()->locationDeleted($spiLocation->id);
1026
                $affectedLocations[] = $spiLocation->id;
1027
            }
1028
            $this->repository->commit();
1029
        } catch (Exception $e) {
1030
            $this->repository->rollback();
1031
            throw $e;
1032
        }
1033
1034
        return $affectedLocations;
1035
    }
1036
1037
    /**
1038
     * Creates a draft from a published or archived version.
1039
     *
1040
     * If no version is given, the current published version is used.
1041
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1042
     * It can be changed on updating the version.
1043
     *
1044
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1045
     *
1046
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1047
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1048
     * @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
1049
     *
1050
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1051
     */
1052
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1053
    {
1054
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1055
1056
        if ($versionInfo !== null) {
1057
            // Check that given $contentInfo and $versionInfo belong to the same content
1058
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1059
                throw new InvalidArgumentException(
1060
                    '$versionInfo',
1061
                    'VersionInfo does not belong to the same content as given ContentInfo'
1062
                );
1063
            }
1064
1065
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1066
1067
            switch ($versionInfo->status) {
1068
                case VersionInfo::STATUS_PUBLISHED:
1069
                case VersionInfo::STATUS_ARCHIVED:
1070
                    break;
1071
1072
                default:
1073
                    // @todo: throw an exception here, to be defined
1074
                    throw new BadStateException(
1075
                        '$versionInfo',
1076
                        'Draft can not be created from a draft version'
1077
                    );
1078
            }
1079
1080
            $versionNo = $versionInfo->versionNo;
1081
        } elseif ($contentInfo->published) {
1082
            $versionNo = $contentInfo->currentVersionNo;
1083
        } else {
1084
            // @todo: throw an exception here, to be defined
1085
            throw new BadStateException(
1086
                '$contentInfo',
1087
                'Content is not published, draft can be created only from published or archived version'
1088
            );
1089
        }
1090
1091
        if ($creator === null) {
1092
            $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...
1093
        }
1094
1095
        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...
1096
            throw new UnauthorizedException('content', 'edit', ['contentId' => $contentInfo->id]);
1097
        }
1098
1099
        $this->repository->beginTransaction();
1100
        try {
1101
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1102
                $contentInfo->id,
1103
                $versionNo,
1104
                $creator->getUserId()
1105
            );
1106
            $this->repository->commit();
1107
        } catch (Exception $e) {
1108
            $this->repository->rollback();
1109
            throw $e;
1110
        }
1111
1112
        return $this->domainMapper->buildContentDomainObject($spiContent);
1113
    }
1114
1115
    /**
1116
     * Loads drafts for a user.
1117
     *
1118
     * If no user is given the drafts for the authenticated user a returned
1119
     *
1120
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list
1121
     *
1122
     * @param \eZ\Publish\API\Repository\Values\User\UserReference $user
1123
     *
1124
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo the drafts ({@link VersionInfo}) owned by the given user
1125
     */
1126
    public function loadContentDrafts(User $user = null)
1127
    {
1128
        if ($user === null) {
1129
            $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...
1130
        }
1131
1132
        // throw early if user has absolutely no access to versionread
1133
        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...
1134
            throw new UnauthorizedException('content', 'versionread');
1135
        }
1136
1137
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser($user->getUserId());
1138
        $versionInfoList = [];
1139 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...
1140
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1141
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1142
            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...
1143
                throw new UnauthorizedException('content', 'versionread', ['contentId' => $versionInfo->contentInfo->id]);
1144
            }
1145
1146
            $versionInfoList[] = $versionInfo;
1147
        }
1148
1149
        return $versionInfoList;
1150
    }
1151
1152
    /**
1153
     * Translate a version.
1154
     *
1155
     * updates the destination version given in $translationInfo with the provided translated fields in $translationValues
1156
     *
1157
     * @example Examples/translation_5x.php
1158
     *
1159
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to update this version
1160
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the given destination version is not a draft
1161
     * @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.
1162
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1163
     *                                                                          or value is set for non-translatable field in language
1164
     *                                                                          other than main.
1165
     *
1166
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1167
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationValues $translationValues
1168
     * @param \eZ\Publish\API\Repository\Values\User\User $modifier If set, this user is taken as modifier of the version
1169
     *
1170
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the translated fields
1171
     *
1172
     * @since 5.0
1173
     */
1174
    public function translateVersion(TranslationInfo $translationInfo, APITranslationValues $translationValues, User $modifier = null)
1175
    {
1176
        throw new NotImplementedException(__METHOD__);
1177
    }
1178
1179
    /**
1180
     * Updates the fields of a draft.
1181
     *
1182
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1183
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1184
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1185
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1186
     *                                                                               or if a required field is missing / set to an empty value.
1187
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1188
     *                                                                          or value is set for non-translatable field in language
1189
     *                                                                          other than main.
1190
     *
1191
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1192
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1193
     *
1194
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1195
     */
1196
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1197
    {
1198
        $contentUpdateStruct = clone $contentUpdateStruct;
1199
1200
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1201
        $content = $this->loadContent(
1202
            $versionInfo->getContentInfo()->id,
1203
            null,
1204
            $versionInfo->versionNo
1205
        );
1206
        if (!$content->versionInfo->isDraft()) {
1207
            throw new BadStateException(
1208
                '$versionInfo',
1209
                'Version is not a draft and can not be updated'
1210
            );
1211
        }
1212
1213
        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...
1214
            throw new UnauthorizedException('content', 'edit', ['contentId' => $content->id]);
1215
        }
1216
1217
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1218
        if ($contentUpdateStruct->initialLanguageCode === null) {
1219
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1220
        }
1221
1222
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1223
        foreach ($allLanguageCodes as $languageCode) {
1224
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode($languageCode);
1225
        }
1226
1227
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1228
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1229
            $content->contentInfo->contentTypeId
1230
        );
1231
        $fields = $this->mapFieldsForUpdate(
1232
            $contentUpdateStruct,
1233
            $contentType,
1234
            $mainLanguageCode
1235
        );
1236
1237
        $fieldValues = [];
1238
        $spiFields = [];
1239
        $allFieldErrors = [];
1240
        $inputRelations = [];
1241
        $locationIdToContentIdMapping = [];
1242
1243
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1244
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1245
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1246
                $fieldDefinition->fieldTypeIdentifier
1247
            );
1248
1249
            foreach ($allLanguageCodes as $languageCode) {
1250
                $isCopied = $isEmpty = $isRetained = false;
1251
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1252
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1253
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1254
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1255
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1256
1257
                if (!$isFieldUpdated && !$isLanguageNew) {
1258
                    $isRetained = true;
1259
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1260
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1261
                    $isCopied = true;
1262
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1263
                } elseif ($isFieldUpdated) {
1264
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1265
                } else {
1266
                    $fieldValue = $fieldDefinition->defaultValue;
1267
                }
1268
1269
                $fieldValue = $fieldType->acceptValue($fieldValue);
1270
1271 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...
1272
                    $isEmpty = true;
1273
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1274
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1275
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1276
                            null,
1277
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1278
                            'empty'
1279
                        );
1280
                    }
1281
                } elseif ($isLanguageUpdated) {
1282
                    $fieldErrors = $fieldType->validate(
1283
                        $fieldDefinition,
1284
                        $fieldValue
1285
                    );
1286
                    if (!empty($fieldErrors)) {
1287
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1288
                    }
1289
                }
1290
1291
                if (!empty($allFieldErrors)) {
1292
                    continue;
1293
                }
1294
1295
                $this->relationProcessor->appendFieldRelations(
1296
                    $inputRelations,
1297
                    $locationIdToContentIdMapping,
1298
                    $fieldType,
1299
                    $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...
1300
                    $fieldDefinition->id
1301
                );
1302
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1303
1304
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1305
                    continue;
1306
                }
1307
1308
                $spiFields[] = new SPIField(
1309
                    [
1310
                        'id' => $isLanguageNew ?
1311
                            null :
1312
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1313
                        'fieldDefinitionId' => $fieldDefinition->id,
1314
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1315
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1316
                        'languageCode' => $languageCode,
1317
                        'versionNo' => $versionInfo->versionNo,
1318
                    ]
1319
                );
1320
            }
1321
        }
1322
1323
        if (!empty($allFieldErrors)) {
1324
            throw new ContentFieldValidationException($allFieldErrors);
1325
        }
1326
1327
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1328
            [
1329
                'name' => $this->nameSchemaService->resolveNameSchema(
1330
                    $content,
1331
                    $fieldValues,
1332
                    $allLanguageCodes,
1333
                    $contentType
1334
                ),
1335
                '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...
1336
                'fields' => $spiFields,
1337
                'modificationDate' => time(),
1338
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1339
                    $contentUpdateStruct->initialLanguageCode
1340
                )->id,
1341
            ]
1342
        );
1343
        $existingRelations = $this->loadRelations($versionInfo);
1344
1345
        $this->repository->beginTransaction();
1346
        try {
1347
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1348
                $versionInfo->getContentInfo()->id,
1349
                $versionInfo->versionNo,
1350
                $spiContentUpdateStruct
1351
            );
1352
            $this->relationProcessor->processFieldRelations(
1353
                $inputRelations,
1354
                $spiContent->versionInfo->contentInfo->id,
1355
                $spiContent->versionInfo->versionNo,
1356
                $contentType,
1357
                $existingRelations
1358
            );
1359
            $this->repository->commit();
1360
        } catch (Exception $e) {
1361
            $this->repository->rollback();
1362
            throw $e;
1363
        }
1364
1365
        return $this->domainMapper->buildContentDomainObject(
1366
            $spiContent,
1367
            $contentType
1368
        );
1369
    }
1370
1371
    /**
1372
     * Returns only updated language codes.
1373
     *
1374
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1375
     *
1376
     * @return array
1377
     */
1378 View Code Duplication
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1379
    {
1380
        $languageCodes = [
1381
            $contentUpdateStruct->initialLanguageCode => true,
1382
        ];
1383
1384
        foreach ($contentUpdateStruct->fields as $field) {
1385
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1386
                continue;
1387
            }
1388
1389
            $languageCodes[$field->languageCode] = true;
1390
        }
1391
1392
        return array_keys($languageCodes);
1393
    }
1394
1395
    /**
1396
     * Returns all language codes used in given $fields.
1397
     *
1398
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1399
     *
1400
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1401
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1402
     *
1403
     * @return array
1404
     */
1405
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1406
    {
1407
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1408
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1409
1410
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1411
        foreach ($updatedLanguageCodes as $languageCode) {
1412
            $languageCodes[$languageCode] = true;
1413
        }
1414
1415
        return array_keys($languageCodes);
1416
    }
1417
1418
    /**
1419
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1420
     *
1421
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1422
     *                                                                          or value is set for non-translatable field in language
1423
     *                                                                          other than main
1424
     *
1425
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1426
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1427
     * @param string $mainLanguageCode
1428
     *
1429
     * @return array
1430
     */
1431
    protected function mapFieldsForUpdate(
1432
        APIContentUpdateStruct $contentUpdateStruct,
1433
        ContentType $contentType,
1434
        $mainLanguageCode
1435
    ) {
1436
        $fields = [];
1437
1438
        foreach ($contentUpdateStruct->fields as $field) {
1439
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1440
1441
            if ($fieldDefinition === null) {
1442
                throw new ContentValidationException(
1443
                    "Field definition '%identifier%' does not exist in given ContentType",
1444
                    ['%identifier%' => $field->fieldDefIdentifier]
1445
                );
1446
            }
1447
1448
            if ($field->languageCode === null) {
1449
                if ($fieldDefinition->isTranslatable) {
1450
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1451
                } else {
1452
                    $languageCode = $mainLanguageCode;
1453
                }
1454
                $field = $this->cloneField($field, ['languageCode' => $languageCode]);
1455
            }
1456
1457 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...
1458
                throw new ContentValidationException(
1459
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1460
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1461
                );
1462
            }
1463
1464
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1465
        }
1466
1467
        return $fields;
1468
    }
1469
1470
    /**
1471
     * Publishes a content version.
1472
     *
1473
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1474
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1475
     *
1476
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version
1477
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1478
     *
1479
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1480
     *
1481
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1482
     */
1483
    public function publishVersion(APIVersionInfo $versionInfo)
1484
    {
1485
        $content = $this->internalLoadContent(
1486
            $versionInfo->contentInfo->id,
1487
            null,
1488
            $versionInfo->versionNo
1489
        );
1490
1491
        if (!$this->repository->canUser('content', 'publish', $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...
1492
            throw new UnauthorizedException('content', 'publish', ['contentId' => $content->id]);
1493
        }
1494
1495
        $this->repository->beginTransaction();
1496
        try {
1497
            $content = $this->internalPublishVersion($content->getVersionInfo());
1498
            $this->repository->commit();
1499
        } catch (Exception $e) {
1500
            $this->repository->rollback();
1501
            throw $e;
1502
        }
1503
1504
        return $content;
1505
    }
1506
1507
    /**
1508
     * Publishes a content version.
1509
     *
1510
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1511
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1512
     *
1513
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1514
     *
1515
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1516
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1517
     *
1518
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1519
     */
1520
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1521
    {
1522
        if (!$versionInfo->isDraft()) {
1523
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1524
        }
1525
1526
        $currentTime = time();
1527
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1528
            $publicationDate = $currentTime;
1529
        }
1530
1531
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1532
        $metadataUpdateStruct->publicationDate = $publicationDate;
1533
        $metadataUpdateStruct->modificationDate = $currentTime;
1534
1535
        $contentId = $versionInfo->getContentInfo()->id;
1536
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1537
            $contentId,
1538
            $versionInfo->versionNo,
1539
            $metadataUpdateStruct
1540
        );
1541
1542
        $content = $this->domainMapper->buildContentDomainObject($spiContent);
1543
1544
        $this->publishUrlAliasesForContent($content);
1545
1546
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1547
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1548
            $contentId,
1549
            APIVersionInfo::STATUS_ARCHIVED,
1550
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1551
        );
1552
1553
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1554
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1555
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1556
            $archiveVersion = array_shift($archiveList);
1557
            $this->persistenceHandler->contentHandler()->deleteVersion(
1558
                $contentId,
1559
                $archiveVersion->versionNo
1560
            );
1561
        }
1562
1563
        return $content;
1564
    }
1565
1566
    /**
1567
     * Removes the given version.
1568
     *
1569
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1570
     *         published state or is a last version of Content in non draft state
1571
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1572
     *
1573
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1574
     */
1575
    public function deleteVersion(APIVersionInfo $versionInfo)
1576
    {
1577
        if ($versionInfo->isPublished()) {
1578
            throw new BadStateException(
1579
                '$versionInfo',
1580
                'Version is published and can not be removed'
1581
            );
1582
        }
1583
1584 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...
1585
            throw new UnauthorizedException(
1586
                'content',
1587
                'versionremove',
1588
                ['contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1589
            );
1590
        }
1591
1592
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1593
            $versionInfo->contentInfo->id,
1594
            null,
1595
            2
1596
        );
1597
1598
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1599
            throw new BadStateException(
1600
                '$versionInfo',
1601
                'Version is the last version of the Content and can not be removed'
1602
            );
1603
        }
1604
1605
        $this->repository->beginTransaction();
1606
        try {
1607
            $this->persistenceHandler->contentHandler()->deleteVersion(
1608
                $versionInfo->getContentInfo()->id,
1609
                $versionInfo->versionNo
1610
            );
1611
            $this->repository->commit();
1612
        } catch (Exception $e) {
1613
            $this->repository->rollback();
1614
            throw $e;
1615
        }
1616
    }
1617
1618
    /**
1619
     * Loads all versions for the given content.
1620
     *
1621
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1622
     *
1623
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1624
     *
1625
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1626
     */
1627
    public function loadVersions(ContentInfo $contentInfo)
1628
    {
1629
        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...
1630
            throw new UnauthorizedException('content', 'versionread', ['contentId' => $contentInfo->id]);
1631
        }
1632
1633
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1634
1635
        $versions = [];
1636 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...
1637
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1638
            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...
1639
                throw new UnauthorizedException('content', 'versionread', ['versionId' => $versionInfo->id]);
1640
            }
1641
1642
            $versions[] = $versionInfo;
1643
        }
1644
1645
        return $versions;
1646
    }
1647
1648
    /**
1649
     * Copies the content to a new location. If no version is given,
1650
     * all versions are copied, otherwise only the given version.
1651
     *
1652
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1653
     *
1654
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1655
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1656
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1657
     *
1658
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1659
     */
1660
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1661
    {
1662
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1663
            $destinationLocationCreateStruct->parentLocationId
1664
        );
1665 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...
1666
            throw new UnauthorizedException(
1667
                'content',
1668
                'create',
1669
                [
1670
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1671
                    'sectionId' => $contentInfo->sectionId,
1672
                ]
1673
            );
1674
        }
1675
1676
        $defaultObjectStates = $this->getDefaultObjectStates();
1677
1678
        $this->repository->beginTransaction();
1679
        try {
1680
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1681
                $contentInfo->id,
1682
                $versionInfo ? $versionInfo->versionNo : null,
1683
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1684
            );
1685
1686
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1687
                $this->persistenceHandler->objectStateHandler()->setContentState(
1688
                    $spiContent->versionInfo->contentInfo->id,
1689
                    $objectStateGroupId,
1690
                    $objectState->id
1691
                );
1692
            }
1693
1694
            $content = $this->internalPublishVersion(
1695
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1696
                $spiContent->versionInfo->creationDate
1697
            );
1698
1699
            $this->repository->getLocationService()->createLocation(
1700
                $content->getVersionInfo()->getContentInfo(),
1701
                $destinationLocationCreateStruct
1702
            );
1703
            $this->repository->commit();
1704
        } catch (Exception $e) {
1705
            $this->repository->rollback();
1706
            throw $e;
1707
        }
1708
1709
        return $this->internalLoadContent($content->id);
1710
    }
1711
1712
    /**
1713
     * Loads all outgoing relations for the given version.
1714
     *
1715
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1716
     *
1717
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1718
     *
1719
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1720
     */
1721
    public function loadRelations(APIVersionInfo $versionInfo)
1722
    {
1723
        if ($versionInfo->isPublished()) {
1724
            $function = 'read';
1725
        } else {
1726
            $function = 'versionread';
1727
        }
1728
1729
        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...
1730
            throw new UnauthorizedException('content', $function);
1731
        }
1732
1733
        $contentInfo = $versionInfo->getContentInfo();
1734
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1735
            $contentInfo->id,
1736
            $versionInfo->versionNo
1737
        );
1738
1739
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1740
        $relations = [];
1741 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...
1742
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1743
            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...
1744
                continue;
1745
            }
1746
1747
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1748
                $spiRelation,
1749
                $contentInfo,
1750
                $destinationContentInfo
1751
            );
1752
        }
1753
1754
        return $relations;
1755
    }
1756
1757
    /**
1758
     * Loads all incoming relations for a content object.
1759
     *
1760
     * The relations come only from published versions of the source content objects
1761
     *
1762
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1763
     *
1764
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1765
     *
1766
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1767
     */
1768
    public function loadReverseRelations(ContentInfo $contentInfo)
1769
    {
1770
        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...
1771
            throw new UnauthorizedException('content', 'reverserelatedlist', ['contentId' => $contentInfo->id]);
1772
        }
1773
1774
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1775
            $contentInfo->id
1776
        );
1777
1778
        $returnArray = [];
1779 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...
1780
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1781
            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...
1782
                continue;
1783
            }
1784
1785
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1786
                $spiRelation,
1787
                $sourceContentInfo,
1788
                $contentInfo
1789
            );
1790
        }
1791
1792
        return $returnArray;
1793
    }
1794
1795
    /**
1796
     * Adds a relation of type common.
1797
     *
1798
     * The source of the relation is the content and version
1799
     * referenced by $versionInfo.
1800
     *
1801
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1802
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1803
     *
1804
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1805
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1806
     *
1807
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1808
     */
1809
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1810
    {
1811
        $sourceVersion = $this->loadVersionInfoById(
1812
            $sourceVersion->contentInfo->id,
1813
            $sourceVersion->versionNo
1814
        );
1815
1816
        if (!$sourceVersion->isDraft()) {
1817
            throw new BadStateException(
1818
                '$sourceVersion',
1819
                'Relations of type common can only be added to versions of status draft'
1820
            );
1821
        }
1822
1823
        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...
1824
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
1825
        }
1826
1827
        $sourceContentInfo = $sourceVersion->getContentInfo();
1828
1829
        $this->repository->beginTransaction();
1830
        try {
1831
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1832
                new SPIRelationCreateStruct(
1833
                    [
1834
                        'sourceContentId' => $sourceContentInfo->id,
1835
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1836
                        'sourceFieldDefinitionId' => null,
1837
                        'destinationContentId' => $destinationContent->id,
1838
                        'type' => APIRelation::COMMON,
1839
                    ]
1840
                )
1841
            );
1842
            $this->repository->commit();
1843
        } catch (Exception $e) {
1844
            $this->repository->rollback();
1845
            throw $e;
1846
        }
1847
1848
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1849
    }
1850
1851
    /**
1852
     * Removes a relation of type COMMON from a draft.
1853
     *
1854
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1855
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1856
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1857
     *
1858
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1859
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1860
     */
1861
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1862
    {
1863
        $sourceVersion = $this->loadVersionInfoById(
1864
            $sourceVersion->contentInfo->id,
1865
            $sourceVersion->versionNo
1866
        );
1867
1868
        if (!$sourceVersion->isDraft()) {
1869
            throw new BadStateException(
1870
                '$sourceVersion',
1871
                'Relations of type common can only be removed from versions of status draft'
1872
            );
1873
        }
1874
1875
        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...
1876
            throw new UnauthorizedException('content', 'edit', ['contentId' => $sourceVersion->contentInfo->id]);
1877
        }
1878
1879
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1880
            $sourceVersion->getContentInfo()->id,
1881
            $sourceVersion->versionNo,
1882
            APIRelation::COMMON
1883
        );
1884
1885
        if (empty($spiRelations)) {
1886
            throw new InvalidArgumentException(
1887
                '$sourceVersion',
1888
                'There are no relations of type COMMON for the given destination'
1889
            );
1890
        }
1891
1892
        // there should be only one relation of type COMMON for each destination,
1893
        // but in case there were ever more then one, we will remove them all
1894
        // @todo: alternatively, throw BadStateException?
1895
        $this->repository->beginTransaction();
1896
        try {
1897
            foreach ($spiRelations as $spiRelation) {
1898
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1899
                    $this->persistenceHandler->contentHandler()->removeRelation(
1900
                        $spiRelation->id,
1901
                        APIRelation::COMMON
1902
                    );
1903
                }
1904
            }
1905
            $this->repository->commit();
1906
        } catch (Exception $e) {
1907
            $this->repository->rollback();
1908
            throw $e;
1909
        }
1910
    }
1911
1912
    /**
1913
     * Adds translation information to the content object.
1914
     *
1915
     * @example Examples/translation_5x.php
1916
     *
1917
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed add a translation info
1918
     *
1919
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1920
     *
1921
     * @since 5.0
1922
     */
1923
    public function addTranslationInfo(TranslationInfo $translationInfo)
1924
    {
1925
        throw new NotImplementedException(__METHOD__);
1926
    }
1927
1928
    /**
1929
     * lists the translations done on this content object.
1930
     *
1931
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed read translation infos
1932
     *
1933
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1934
     * @param array $filter
1935
     *
1936
     * @todo TBD - filter by source version destination version and languages
1937
     *
1938
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo[]
1939
     *
1940
     * @since 5.0
1941
     */
1942
    public function loadTranslationInfos(ContentInfo $contentInfo, array $filter = [])
1943
    {
1944
        throw new NotImplementedException(__METHOD__);
1945
    }
1946
1947
    /**
1948
     * {@inheritdoc}
1949
     */
1950
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
1951
    {
1952
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1953
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
1954
            E_USER_DEPRECATED
1955
        );
1956
        $this->deleteTranslation($contentInfo, $languageCode);
1957
    }
1958
1959
    /**
1960
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
1961
     *
1962
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
1963
     *
1964
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
1965
     *         is the Main Translation of a Content Item.
1966
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
1967
     *         to delete the content (in one of the locations of the given Content Item).
1968
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
1969
     *         is invalid for the given content.
1970
     *
1971
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1972
     * @param string $languageCode
1973
     *
1974
     * @since 6.13
1975
     */
1976
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
1977
    {
1978
        if ($contentInfo->mainLanguageCode === $languageCode) {
1979
            throw new BadStateException(
1980
                '$languageCode',
1981
                'Specified translation is the main translation of the Content Object'
1982
            );
1983
        }
1984
1985
        $translationWasFound = false;
1986
        $this->repository->beginTransaction();
1987
        try {
1988
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
1989 View Code Duplication
                if (!$this->repository->canUser('content', 'remove', $versionInfo)) {
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...
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...
1990
                    throw new UnauthorizedException(
1991
                        'content',
1992
                        'remove',
1993
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1994
                    );
1995
                }
1996
1997
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
1998
                    continue;
1999
                }
2000
2001
                $translationWasFound = true;
2002
2003
                // If the translation is the version's only one, delete the version
2004
                if (count($versionInfo->languageCodes) < 2) {
2005
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2006
                        $versionInfo->getContentInfo()->id,
2007
                        $versionInfo->versionNo
2008
                    );
2009
                }
2010
            }
2011
2012
            if (!$translationWasFound) {
2013
                throw new InvalidArgumentException(
2014
                    '$languageCode',
2015
                    sprintf(
2016
                        '%s does not exist in the Content item(id=%d)',
2017
                        $languageCode,
2018
                        $contentInfo->id
2019
                    )
2020
                );
2021
            }
2022
2023
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2024
                $contentInfo->id,
2025
                $languageCode
2026
            );
2027
            $locationIds = array_map(
2028
                function (Location $location) {
2029
                    return $location->id;
2030
                },
2031
                $this->repository->getLocationService()->loadLocations($contentInfo)
2032
            );
2033
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2034
                $locationIds,
2035
                $languageCode
2036
            );
2037
            $this->repository->commit();
2038
        } catch (InvalidArgumentException $e) {
2039
            $this->repository->rollback();
2040
            throw $e;
2041
        } catch (BadStateException $e) {
2042
            $this->repository->rollback();
2043
            throw $e;
2044
        } catch (UnauthorizedException $e) {
2045
            $this->repository->rollback();
2046
            throw $e;
2047
        } catch (Exception $e) {
2048
            $this->repository->rollback();
2049
            // cover generic unexpected exception to fulfill API promise on @throws
2050
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2051
        }
2052
    }
2053
2054
    /**
2055
     * Delete specified Translation from a Content Draft.
2056
     *
2057
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2058
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2059
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2060
     *         to edit the Content (in one of the locations of the given Content Object).
2061
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2062
     *         is invalid for the given Draft.
2063
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2064
     *
2065
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2066
     * @param string $languageCode Language code of the Translation to be removed
2067
     *
2068
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2069
     *
2070
     * @since 6.12
2071
     */
2072
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2073
    {
2074
        if (!$versionInfo->isDraft()) {
2075
            throw new BadStateException(
2076
                '$versionInfo',
2077
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2078
            );
2079
        }
2080
2081
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2082
            throw new BadStateException(
2083
                '$languageCode',
2084
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2085
            );
2086
        }
2087
2088
        if (!$this->repository->canUser('content', 'edit', $versionInfo->contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
2089
            throw new UnauthorizedException(
2090
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2091
            );
2092
        }
2093
2094
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2095
            throw new InvalidArgumentException(
2096
                '$languageCode',
2097
                sprintf(
2098
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2099
                    $versionInfo->contentInfo->id,
2100
                    $versionInfo->versionNo,
2101
                    $languageCode
2102
                )
2103
            );
2104
        }
2105
2106
        if (count($versionInfo->languageCodes) === 1) {
2107
            throw new BadStateException(
2108
                '$languageCode',
2109
                'Specified Translation is the only one Content Object Version has'
2110
            );
2111
        }
2112
2113
        $this->repository->beginTransaction();
2114
        try {
2115
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2116
                $versionInfo->contentInfo->id,
2117
                $versionInfo->versionNo,
2118
                $languageCode
2119
            );
2120
            $this->repository->commit();
2121
2122
            return $this->domainMapper->buildContentDomainObject($spiContent);
2123
        } catch (APINotFoundException $e) {
2124
            // avoid wrapping expected NotFoundException in BadStateException handled below
2125
            $this->repository->rollback();
2126
            throw $e;
2127
        } catch (Exception $e) {
2128
            $this->repository->rollback();
2129
            // cover generic unexpected exception to fulfill API promise on @throws
2130
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2131
        }
2132
    }
2133
2134
    /**
2135
     * Instantiates a new content create struct object.
2136
     *
2137
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2138
     *
2139
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2140
     * @param string $mainLanguageCode
2141
     *
2142
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2143
     */
2144
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2145
    {
2146
        return new ContentCreateStruct(
2147
            [
2148
                'contentType' => $contentType,
2149
                'mainLanguageCode' => $mainLanguageCode,
2150
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2151
            ]
2152
        );
2153
    }
2154
2155
    /**
2156
     * Instantiates a new content meta data update struct.
2157
     *
2158
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2159
     */
2160
    public function newContentMetadataUpdateStruct()
2161
    {
2162
        return new ContentMetadataUpdateStruct();
2163
    }
2164
2165
    /**
2166
     * Instantiates a new content update struct.
2167
     *
2168
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2169
     */
2170
    public function newContentUpdateStruct()
2171
    {
2172
        return new ContentUpdateStruct();
2173
    }
2174
2175
    /**
2176
     * Instantiates a new TranslationInfo object.
2177
     *
2178
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo
2179
     */
2180
    public function newTranslationInfo()
2181
    {
2182
        return new TranslationInfo();
2183
    }
2184
2185
    /**
2186
     * Instantiates a Translation object.
2187
     *
2188
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationValues
2189
     */
2190
    public function newTranslationValues()
2191
    {
2192
        return new TranslationValues();
2193
    }
2194
}
2195