Completed
Push — master ( 83fb2b...0f847f )
by
unknown
42:12 queued 20:32
created

ContentService::createContentDraft()   C

Complexity

Conditions 9
Paths 27

Size

Total Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 80
c 0
b 0
f 0
cc 9
nc 27
nop 3
rs 6.8808

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1536
        if ($content->contentInfo->currentVersionNo !== $versionInfo->versionNo) {
1537
            $fromContent = $this->internalLoadContent(
1538
                $content->contentInfo->id,
1539
                null,
1540
                $content->contentInfo->currentVersionNo
1541
            );
1542
            // should not occur now, might occur in case of un-publish
1543
            if (!$fromContent->contentInfo->isPublished()) {
1544
                $fromContent = null;
0 ignored issues
show
Unused Code introduced by
$fromContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1545
            }
1546
        }
1547
1548
        if (!$this->repository->getPermissionResolver()->canUser(
1549
            'content',
1550
            'publish',
1551
            $content
1552
        )) {
1553
            throw new UnauthorizedException(
1554
                'content', 'publish', array('contentId' => $content->id)
1555
            );
1556
        }
1557
1558
        $this->repository->beginTransaction();
1559
        try {
1560
            $content = $this->internalPublishVersion($content->getVersionInfo());
1561
            $this->repository->commit();
1562
        } catch (Exception $e) {
1563
            $this->repository->rollback();
1564
            throw $e;
1565
        }
1566
1567
        return $content;
1568
    }
1569
1570
    /**
1571
     * Publishes a content version.
1572
     *
1573
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1574
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1575
     *
1576
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1577
     *
1578
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1579
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1580
     *
1581
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1582
     */
1583
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1584
    {
1585
        if (!$versionInfo->isDraft()) {
1586
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1587
        }
1588
1589
        $currentTime = $this->getUnixTimestamp();
1590
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1591
            $publicationDate = $currentTime;
1592
        }
1593
1594
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1595
        $metadataUpdateStruct->publicationDate = $publicationDate;
1596
        $metadataUpdateStruct->modificationDate = $currentTime;
1597
1598
        $contentId = $versionInfo->getContentInfo()->id;
1599
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1600
            $contentId,
1601
            $versionInfo->versionNo,
1602
            $metadataUpdateStruct
1603
        );
1604
1605
        $content = $this->domainMapper->buildContentDomainObject(
1606
            $spiContent,
1607
            $this->repository->getContentTypeService()->loadContentType(
1608
                $spiContent->versionInfo->contentInfo->contentTypeId
1609
            )
1610
        );
1611
1612
        $this->publishUrlAliasesForContent($content);
1613
1614
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1615
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1616
            $contentId,
1617
            APIVersionInfo::STATUS_ARCHIVED,
1618
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1619
        );
1620
1621
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1622
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1623
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1624
            $archiveVersion = array_shift($archiveList);
1625
            $this->persistenceHandler->contentHandler()->deleteVersion(
1626
                $contentId,
1627
                $archiveVersion->versionNo
1628
            );
1629
        }
1630
1631
        return $content;
1632
    }
1633
1634
    /**
1635
     * @return int
1636
     */
1637
    protected function getUnixTimestamp()
1638
    {
1639
        return time();
1640
    }
1641
1642
    /**
1643
     * Removes the given version.
1644
     *
1645
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1646
     *         published state or is a last version of Content in non draft state
1647
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1648
     *
1649
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1650
     */
1651
    public function deleteVersion(APIVersionInfo $versionInfo)
1652
    {
1653
        if ($versionInfo->isPublished()) {
1654
            throw new BadStateException(
1655
                '$versionInfo',
1656
                'Version is published and can not be removed'
1657
            );
1658
        }
1659
1660
        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...
1661
            throw new UnauthorizedException(
1662
                'content',
1663
                'versionremove',
1664
                array('contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo)
1665
            );
1666
        }
1667
1668
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1669
            $versionInfo->contentInfo->id,
1670
            null,
1671
            2
1672
        );
1673
1674
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1675
            throw new BadStateException(
1676
                '$versionInfo',
1677
                'Version is the last version of the Content and can not be removed'
1678
            );
1679
        }
1680
1681
        $this->repository->beginTransaction();
1682
        try {
1683
            $this->persistenceHandler->contentHandler()->deleteVersion(
1684
                $versionInfo->getContentInfo()->id,
1685
                $versionInfo->versionNo
1686
            );
1687
            $this->repository->commit();
1688
        } catch (Exception $e) {
1689
            $this->repository->rollback();
1690
            throw $e;
1691
        }
1692
    }
1693
1694
    /**
1695
     * Loads all versions for the given content.
1696
     *
1697
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1698
     *
1699
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1700
     *
1701
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1702
     */
1703
    public function loadVersions(ContentInfo $contentInfo)
1704
    {
1705
        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...
1706
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentInfo->id));
1707
        }
1708
1709
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1710
1711
        $versions = array();
1712 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...
1713
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1714
            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...
1715
                throw new UnauthorizedException('content', 'versionread', array('versionId' => $versionInfo->id));
1716
            }
1717
1718
            $versions[] = $versionInfo;
1719
        }
1720
1721
        return $versions;
1722
    }
1723
1724
    /**
1725
     * Copies the content to a new location. If no version is given,
1726
     * all versions are copied, otherwise only the given version.
1727
     *
1728
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1729
     *
1730
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1731
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1732
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1733
     *
1734
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1735
     */
1736
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1737
    {
1738
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1739
            $destinationLocationCreateStruct->parentLocationId
1740
        );
1741
        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...
1742
            throw new UnauthorizedException(
1743
                'content',
1744
                'create',
1745
                [
1746
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1747
                    'sectionId' => $contentInfo->sectionId,
1748
                ]
1749
            );
1750
        }
1751
1752
        $defaultObjectStates = $this->getDefaultObjectStates();
1753
1754
        $this->repository->beginTransaction();
1755
        try {
1756
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1757
                $contentInfo->id,
1758
                $versionInfo ? $versionInfo->versionNo : null,
1759
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1760
            );
1761
1762
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1763
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1764
                $objectStateHandler->setContentState(
1765
                    $spiContent->versionInfo->contentInfo->id,
1766
                    $objectStateGroupId,
1767
                    $objectState->id
1768
                );
1769
            }
1770
1771
            $content = $this->internalPublishVersion(
1772
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1773
                $spiContent->versionInfo->creationDate
1774
            );
1775
1776
            $this->repository->getLocationService()->createLocation(
1777
                $content->getVersionInfo()->getContentInfo(),
1778
                $destinationLocationCreateStruct
1779
            );
1780
            $this->repository->commit();
1781
        } catch (Exception $e) {
1782
            $this->repository->rollback();
1783
            throw $e;
1784
        }
1785
1786
        return $this->internalLoadContent($content->id);
1787
    }
1788
1789
    /**
1790
     * Loads all outgoing relations for the given version.
1791
     *
1792
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1793
     *
1794
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1795
     *
1796
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1797
     */
1798
    public function loadRelations(APIVersionInfo $versionInfo)
1799
    {
1800
        if ($versionInfo->isPublished()) {
1801
            $function = 'read';
1802
        } else {
1803
            $function = 'versionread';
1804
        }
1805
1806
        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...
1807
            throw new UnauthorizedException('content', $function);
1808
        }
1809
1810
        $contentInfo = $versionInfo->getContentInfo();
1811
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1812
            $contentInfo->id,
1813
            $versionInfo->versionNo
1814
        );
1815
1816
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1817
        $relations = array();
1818 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...
1819
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1820
            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...
1821
                continue;
1822
            }
1823
1824
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1825
                $spiRelation,
1826
                $contentInfo,
1827
                $destinationContentInfo
1828
            );
1829
        }
1830
1831
        return $relations;
1832
    }
1833
1834
    /**
1835
     * Loads all incoming relations for a content object.
1836
     *
1837
     * The relations come only from published versions of the source content objects
1838
     *
1839
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1840
     *
1841
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1842
     *
1843
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1844
     */
1845
    public function loadReverseRelations(ContentInfo $contentInfo)
1846
    {
1847
        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...
1848
            throw new UnauthorizedException('content', 'reverserelatedlist', array('contentId' => $contentInfo->id));
1849
        }
1850
1851
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1852
            $contentInfo->id
1853
        );
1854
1855
        $returnArray = array();
1856 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...
1857
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1858
            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...
1859
                continue;
1860
            }
1861
1862
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1863
                $spiRelation,
1864
                $sourceContentInfo,
1865
                $contentInfo
1866
            );
1867
        }
1868
1869
        return $returnArray;
1870
    }
1871
1872
    /**
1873
     * Adds a relation of type common.
1874
     *
1875
     * The source of the relation is the content and version
1876
     * referenced by $versionInfo.
1877
     *
1878
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1879
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1880
     *
1881
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1882
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1883
     *
1884
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1885
     */
1886
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1887
    {
1888
        $sourceVersion = $this->loadVersionInfoById(
1889
            $sourceVersion->contentInfo->id,
1890
            $sourceVersion->versionNo
1891
        );
1892
1893
        if (!$sourceVersion->isDraft()) {
1894
            throw new BadStateException(
1895
                '$sourceVersion',
1896
                'Relations of type common can only be added to versions of status draft'
1897
            );
1898
        }
1899
1900
        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...
1901
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1902
        }
1903
1904
        $sourceContentInfo = $sourceVersion->getContentInfo();
1905
1906
        $this->repository->beginTransaction();
1907
        try {
1908
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1909
                new SPIRelationCreateStruct(
1910
                    array(
1911
                        'sourceContentId' => $sourceContentInfo->id,
1912
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1913
                        'sourceFieldDefinitionId' => null,
1914
                        'destinationContentId' => $destinationContent->id,
1915
                        'type' => APIRelation::COMMON,
1916
                    )
1917
                )
1918
            );
1919
            $this->repository->commit();
1920
        } catch (Exception $e) {
1921
            $this->repository->rollback();
1922
            throw $e;
1923
        }
1924
1925
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1926
    }
1927
1928
    /**
1929
     * Removes a relation of type COMMON from a draft.
1930
     *
1931
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1932
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1933
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1934
     *
1935
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1936
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1937
     */
1938
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1939
    {
1940
        $sourceVersion = $this->loadVersionInfoById(
1941
            $sourceVersion->contentInfo->id,
1942
            $sourceVersion->versionNo
1943
        );
1944
1945
        if (!$sourceVersion->isDraft()) {
1946
            throw new BadStateException(
1947
                '$sourceVersion',
1948
                'Relations of type common can only be removed from versions of status draft'
1949
            );
1950
        }
1951
1952
        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...
1953
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1954
        }
1955
1956
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1957
            $sourceVersion->getContentInfo()->id,
1958
            $sourceVersion->versionNo,
1959
            APIRelation::COMMON
1960
        );
1961
1962
        if (empty($spiRelations)) {
1963
            throw new InvalidArgumentException(
1964
                '$sourceVersion',
1965
                'There are no relations of type COMMON for the given destination'
1966
            );
1967
        }
1968
1969
        // there should be only one relation of type COMMON for each destination,
1970
        // but in case there were ever more then one, we will remove them all
1971
        // @todo: alternatively, throw BadStateException?
1972
        $this->repository->beginTransaction();
1973
        try {
1974
            foreach ($spiRelations as $spiRelation) {
1975
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1976
                    $this->persistenceHandler->contentHandler()->removeRelation(
1977
                        $spiRelation->id,
1978
                        APIRelation::COMMON
1979
                    );
1980
                }
1981
            }
1982
            $this->repository->commit();
1983
        } catch (Exception $e) {
1984
            $this->repository->rollback();
1985
            throw $e;
1986
        }
1987
    }
1988
1989
    /**
1990
     * {@inheritdoc}
1991
     */
1992
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
1993
    {
1994
        @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...
1995
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
1996
            E_USER_DEPRECATED
1997
        );
1998
        $this->deleteTranslation($contentInfo, $languageCode);
1999
    }
2000
2001
    /**
2002
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
2003
     *
2004
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
2005
     *
2006
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2007
     *         is the Main Translation of a Content Item.
2008
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2009
     *         to delete the content (in one of the locations of the given Content Item).
2010
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2011
     *         is invalid for the given content.
2012
     *
2013
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2014
     * @param string $languageCode
2015
     *
2016
     * @since 6.13
2017
     */
2018
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
2019
    {
2020
        if ($contentInfo->mainLanguageCode === $languageCode) {
2021
            throw new BadStateException(
2022
                '$languageCode',
2023
                'Specified translation is the main translation of the Content Object'
2024
            );
2025
        }
2026
2027
        $translationWasFound = false;
2028
        $this->repository->beginTransaction();
2029
        try {
2030
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2031
                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...
2032
                    throw new UnauthorizedException(
2033
                        'content',
2034
                        'remove',
2035
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2036
                    );
2037
                }
2038
2039
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2040
                    continue;
2041
                }
2042
2043
                $translationWasFound = true;
2044
2045
                // If the translation is the version's only one, delete the version
2046
                if (count($versionInfo->languageCodes) < 2) {
2047
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2048
                        $versionInfo->getContentInfo()->id,
2049
                        $versionInfo->versionNo
2050
                    );
2051
                }
2052
            }
2053
2054
            if (!$translationWasFound) {
2055
                throw new InvalidArgumentException(
2056
                    '$languageCode',
2057
                    sprintf(
2058
                        '%s does not exist in the Content item(id=%d)',
2059
                        $languageCode,
2060
                        $contentInfo->id
2061
                    )
2062
                );
2063
            }
2064
2065
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2066
                $contentInfo->id,
2067
                $languageCode
2068
            );
2069
            $locationIds = array_map(
2070
                function (Location $location) {
2071
                    return $location->id;
2072
                },
2073
                $this->repository->getLocationService()->loadLocations($contentInfo)
2074
            );
2075
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2076
                $locationIds,
2077
                $languageCode
2078
            );
2079
            $this->repository->commit();
2080
        } catch (InvalidArgumentException $e) {
2081
            $this->repository->rollback();
2082
            throw $e;
2083
        } catch (BadStateException $e) {
2084
            $this->repository->rollback();
2085
            throw $e;
2086
        } catch (UnauthorizedException $e) {
2087
            $this->repository->rollback();
2088
            throw $e;
2089
        } catch (Exception $e) {
2090
            $this->repository->rollback();
2091
            // cover generic unexpected exception to fulfill API promise on @throws
2092
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2093
        }
2094
    }
2095
2096
    /**
2097
     * Delete specified Translation from a Content Draft.
2098
     *
2099
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2100
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2101
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2102
     *         to edit the Content (in one of the locations of the given Content Object).
2103
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2104
     *         is invalid for the given Draft.
2105
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2106
     *
2107
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2108
     * @param string $languageCode Language code of the Translation to be removed
2109
     *
2110
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2111
     *
2112
     * @since 6.12
2113
     */
2114
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2115
    {
2116
        if (!$versionInfo->isDraft()) {
2117
            throw new BadStateException(
2118
                '$versionInfo',
2119
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2120
            );
2121
        }
2122
2123
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2124
            throw new BadStateException(
2125
                '$languageCode',
2126
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2127
            );
2128
        }
2129
2130
        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...
2131
            throw new UnauthorizedException(
2132
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2133
            );
2134
        }
2135
2136
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2137
            throw new InvalidArgumentException(
2138
                '$languageCode',
2139
                sprintf(
2140
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2141
                    $versionInfo->contentInfo->id,
2142
                    $versionInfo->versionNo,
2143
                    $languageCode
2144
                )
2145
            );
2146
        }
2147
2148
        if (count($versionInfo->languageCodes) === 1) {
2149
            throw new BadStateException(
2150
                '$languageCode',
2151
                'Specified Translation is the only one Content Object Version has'
2152
            );
2153
        }
2154
2155
        $this->repository->beginTransaction();
2156
        try {
2157
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2158
                $versionInfo->contentInfo->id,
2159
                $versionInfo->versionNo,
2160
                $languageCode
2161
            );
2162
            $this->repository->commit();
2163
2164
            return $this->domainMapper->buildContentDomainObject(
2165
                $spiContent,
2166
                $this->repository->getContentTypeService()->loadContentType(
2167
                    $spiContent->versionInfo->contentInfo->contentTypeId
2168
                )
2169
            );
2170
        } catch (APINotFoundException $e) {
2171
            // avoid wrapping expected NotFoundException in BadStateException handled below
2172
            $this->repository->rollback();
2173
            throw $e;
2174
        } catch (Exception $e) {
2175
            $this->repository->rollback();
2176
            // cover generic unexpected exception to fulfill API promise on @throws
2177
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2178
        }
2179
    }
2180
2181
    /**
2182
     * Hides Content by making all the Locations appear hidden.
2183
     * It does not persist hidden state on Location object itself.
2184
     *
2185
     * Content hidden by this API can be revealed by revealContent API.
2186
     *
2187
     * @see revealContent
2188
     *
2189
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2190
     */
2191 View Code Duplication
    public function hideContent(ContentInfo $contentInfo): void
2192
    {
2193
        if (!$this->repository->canUser('content', 'hide', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
2194
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2195
        }
2196
2197
        $this->repository->beginTransaction();
2198
        try {
2199
            $this->persistenceHandler->contentHandler()->updateMetadata(
2200
                $contentInfo->id,
2201
                new SPIMetadataUpdateStruct([
2202
                    'isHidden' => true,
2203
                ])
2204
            );
2205
            $locationHandler = $this->persistenceHandler->locationHandler();
2206
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2207
            foreach ($childLocations as $childLocation) {
2208
                $locationHandler->setInvisible($childLocation->id);
2209
            }
2210
            $this->repository->commit();
2211
        } catch (Exception $e) {
2212
            $this->repository->rollback();
2213
            throw $e;
2214
        }
2215
    }
2216
2217
    /**
2218
     * Reveals Content hidden by hideContent API.
2219
     * Locations which were hidden before hiding Content will remain hidden.
2220
     *
2221
     * @see hideContent
2222
     *
2223
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
2224
     */
2225 View Code Duplication
    public function revealContent(ContentInfo $contentInfo): void
2226
    {
2227
        if (!$this->repository->canUser('content', 'hide', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
2228
            throw new UnauthorizedException('content', 'hide', ['contentId' => $contentInfo->id]);
2229
        }
2230
2231
        $this->repository->beginTransaction();
2232
        try {
2233
            $this->persistenceHandler->contentHandler()->updateMetadata(
2234
                $contentInfo->id,
2235
                new SPIMetadataUpdateStruct([
2236
                    'isHidden' => false,
2237
                ])
2238
            );
2239
            $locationHandler = $this->persistenceHandler->locationHandler();
2240
            $childLocations = $locationHandler->loadLocationsByContent($contentInfo->id);
2241
            foreach ($childLocations as $childLocation) {
2242
                $locationHandler->setVisible($childLocation->id);
2243
            }
2244
            $this->repository->commit();
2245
        } catch (Exception $e) {
2246
            $this->repository->rollback();
2247
            throw $e;
2248
        }
2249
    }
2250
2251
    /**
2252
     * Instantiates a new content create struct object.
2253
     *
2254
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2255
     *
2256
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2257
     * @param string $mainLanguageCode
2258
     *
2259
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2260
     */
2261
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2262
    {
2263
        return new ContentCreateStruct(
2264
            array(
2265
                'contentType' => $contentType,
2266
                'mainLanguageCode' => $mainLanguageCode,
2267
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2268
            )
2269
        );
2270
    }
2271
2272
    /**
2273
     * Instantiates a new content meta data update struct.
2274
     *
2275
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2276
     */
2277
    public function newContentMetadataUpdateStruct()
2278
    {
2279
        return new ContentMetadataUpdateStruct();
2280
    }
2281
2282
    /**
2283
     * Instantiates a new content update struct.
2284
     *
2285
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2286
     */
2287
    public function newContentUpdateStruct()
2288
    {
2289
        return new ContentUpdateStruct();
2290
    }
2291
}
2292