Completed
Push — master ( 0b3034...a98e2b )
by
unknown
36:06 queued 14:31
created

ContentService::loadContentListByContentInfo()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 35

Duplication

Lines 8
Ratio 22.86 %

Importance

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

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

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

Loading history...
1937
                    throw new UnauthorizedException(
1938
                        'content',
1939
                        'remove',
1940
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1941
                    );
1942
                }
1943
1944
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
1945
                    continue;
1946
                }
1947
1948
                $translationWasFound = true;
1949
1950
                // If the translation is the version's only one, delete the version
1951
                if (count($versionInfo->languageCodes) < 2) {
1952
                    $this->persistenceHandler->contentHandler()->deleteVersion(
1953
                        $versionInfo->getContentInfo()->id,
1954
                        $versionInfo->versionNo
1955
                    );
1956
                }
1957
            }
1958
1959
            if (!$translationWasFound) {
1960
                throw new InvalidArgumentException(
1961
                    '$languageCode',
1962
                    sprintf(
1963
                        '%s does not exist in the Content item(id=%d)',
1964
                        $languageCode,
1965
                        $contentInfo->id
1966
                    )
1967
                );
1968
            }
1969
1970
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
1971
                $contentInfo->id,
1972
                $languageCode
1973
            );
1974
            $locationIds = array_map(
1975
                function (Location $location) {
1976
                    return $location->id;
1977
                },
1978
                $this->repository->getLocationService()->loadLocations($contentInfo)
1979
            );
1980
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
1981
                $locationIds,
1982
                $languageCode
1983
            );
1984
            $this->repository->commit();
1985
        } catch (InvalidArgumentException $e) {
1986
            $this->repository->rollback();
1987
            throw $e;
1988
        } catch (BadStateException $e) {
1989
            $this->repository->rollback();
1990
            throw $e;
1991
        } catch (UnauthorizedException $e) {
1992
            $this->repository->rollback();
1993
            throw $e;
1994
        } catch (Exception $e) {
1995
            $this->repository->rollback();
1996
            // cover generic unexpected exception to fulfill API promise on @throws
1997
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
1998
        }
1999
    }
2000
2001
    /**
2002
     * Delete specified Translation from a Content Draft.
2003
     *
2004
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2005
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2006
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2007
     *         to edit the Content (in one of the locations of the given Content Object).
2008
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2009
     *         is invalid for the given Draft.
2010
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2011
     *
2012
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2013
     * @param string $languageCode Language code of the Translation to be removed
2014
     *
2015
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2016
     *
2017
     * @since 6.12
2018
     */
2019
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2020
    {
2021
        if (!$versionInfo->isDraft()) {
2022
            throw new BadStateException(
2023
                '$versionInfo',
2024
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2025
            );
2026
        }
2027
2028
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2029
            throw new BadStateException(
2030
                '$languageCode',
2031
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2032
            );
2033
        }
2034
2035
        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...
2036
            throw new UnauthorizedException(
2037
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2038
            );
2039
        }
2040
2041
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2042
            throw new InvalidArgumentException(
2043
                '$languageCode',
2044
                sprintf(
2045
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2046
                    $versionInfo->contentInfo->id,
2047
                    $versionInfo->versionNo,
2048
                    $languageCode
2049
                )
2050
            );
2051
        }
2052
2053
        if (count($versionInfo->languageCodes) === 1) {
2054
            throw new BadStateException(
2055
                '$languageCode',
2056
                'Specified Translation is the only one Content Object Version has'
2057
            );
2058
        }
2059
2060
        $this->repository->beginTransaction();
2061
        try {
2062
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2063
                $versionInfo->contentInfo->id,
2064
                $versionInfo->versionNo,
2065
                $languageCode
2066
            );
2067
            $this->repository->commit();
2068
2069
            return $this->domainMapper->buildContentDomainObject($spiContent);
2070
        } catch (APINotFoundException $e) {
2071
            // avoid wrapping expected NotFoundException in BadStateException handled below
2072
            $this->repository->rollback();
2073
            throw $e;
2074
        } catch (Exception $e) {
2075
            $this->repository->rollback();
2076
            // cover generic unexpected exception to fulfill API promise on @throws
2077
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2078
        }
2079
    }
2080
2081
    /**
2082
     * Instantiates a new content create struct object.
2083
     *
2084
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2085
     *
2086
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2087
     * @param string $mainLanguageCode
2088
     *
2089
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2090
     */
2091
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2092
    {
2093
        return new ContentCreateStruct(
2094
            array(
2095
                'contentType' => $contentType,
2096
                'mainLanguageCode' => $mainLanguageCode,
2097
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2098
            )
2099
        );
2100
    }
2101
2102
    /**
2103
     * Instantiates a new content meta data update struct.
2104
     *
2105
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2106
     */
2107
    public function newContentMetadataUpdateStruct()
2108
    {
2109
        return new ContentMetadataUpdateStruct();
2110
    }
2111
2112
    /**
2113
     * Instantiates a new content update struct.
2114
     *
2115
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2116
     */
2117
    public function newContentUpdateStruct()
2118
    {
2119
        return new ContentUpdateStruct();
2120
    }
2121
}
2122