Completed
Push — 6.13 ( d5b0d9...ad14d6 )
by André
118:31 queued 98:50
created

ContentService::getLanguageCodesForUpdate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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