Completed
Push — bulk_load_content_info_api ( d8de10 )
by André
17:00
created

ContentService::loadVersions()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 8
Ratio 40 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 8
loc 20
rs 9.6
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\ContentService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\ContentService as ContentServiceInterface;
12
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
13
use eZ\Publish\Core\Repository\Values\Content\Location;
14
use eZ\Publish\API\Repository\Values\Content\Language;
15
use eZ\Publish\SPI\Persistence\Handler;
16
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct as APIContentUpdateStruct;
17
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
18
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
19
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
20
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
21
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
22
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
23
use eZ\Publish\API\Repository\Values\User\User;
24
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
25
use eZ\Publish\API\Repository\Values\Content\Field;
26
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
27
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
28
use eZ\Publish\Core\Base\Exceptions\BadStateException;
29
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
30
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
31
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
32
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
33
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
34
use eZ\Publish\Core\FieldType\ValidationError;
35
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
36
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
37
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
38
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
39
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
40
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
41
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
42
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
43
use Exception;
44
45
/**
46
 * This class provides service methods for managing content.
47
 *
48
 * @example Examples/content.php
49
 */
50
class ContentService implements ContentServiceInterface
51
{
52
    /**
53
     * @var \eZ\Publish\Core\Repository\Repository
54
     */
55
    protected $repository;
56
57
    /**
58
     * @var \eZ\Publish\SPI\Persistence\Handler
59
     */
60
    protected $persistenceHandler;
61
62
    /**
63
     * @var array
64
     */
65
    protected $settings;
66
67
    /**
68
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
69
     */
70
    protected $domainMapper;
71
72
    /**
73
     * @var \eZ\Publish\Core\Repository\Helper\RelationProcessor
74
     */
75
    protected $relationProcessor;
76
77
    /**
78
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
79
     */
80
    protected $nameSchemaService;
81
82
    /**
83
     * @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry
84
     */
85
    protected $fieldTypeRegistry;
86
87
    /**
88
     * Setups service with reference to repository object that created it & corresponding handler.
89
     *
90
     * @param \eZ\Publish\API\Repository\Repository $repository
91
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
92
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
93
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
94
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
95
     * @param \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry $fieldTypeRegistry,
0 ignored issues
show
Documentation introduced by
There is no parameter named $fieldTypeRegistry,. Did you maybe mean $fieldTypeRegistry?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
96
     * @param array $settings
97
     */
98
    public function __construct(
99
        RepositoryInterface $repository,
100
        Handler $handler,
101
        Helper\DomainMapper $domainMapper,
102
        Helper\RelationProcessor $relationProcessor,
103
        Helper\NameSchemaService $nameSchemaService,
104
        Helper\FieldTypeRegistry $fieldTypeRegistry,
105
        array $settings = array()
106
    ) {
107
        $this->repository = $repository;
0 ignored issues
show
Documentation Bug introduced by
$repository is of type object<eZ\Publish\API\Repository\Repository>, but the property $repository was declared to be of type object<eZ\Publish\Core\Repository\Repository>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
108
        $this->persistenceHandler = $handler;
109
        $this->domainMapper = $domainMapper;
110
        $this->relationProcessor = $relationProcessor;
111
        $this->nameSchemaService = $nameSchemaService;
112
        $this->fieldTypeRegistry = $fieldTypeRegistry;
113
        // Union makes sure default settings are ignored if provided in argument
114
        $this->settings = $settings + array(
115
            // Version archive limit (0-50), only enforced on publish, not on un-publish.
116
            'default_version_archive_limit' => 5,
117
        );
118
    }
119
120
    /**
121
     * Loads a content info object.
122
     *
123
     * To load fields use loadContent
124
     *
125
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
126
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
127
     *
128
     * @param int $contentId
129
     *
130
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
131
     */
132 View Code Duplication
    public function loadContentInfo($contentId)
133
    {
134
        $contentInfo = $this->internalLoadContentInfo($contentId);
135
        if (!$this->repository->canUser('content', 'read', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
136
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
137
        }
138
139
        return $contentInfo;
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function loadContentInfoList(array $contentIds, bool $filterOnUserPermissions = true): iterable
146
    {
147
        $contentInfoList = [];
148
        $spiInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList($contentIds);
149 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...
150
            $contentInfo = $this->domainMapper->buildContentInfoDomainObject($spiInfo);
151
            if ($filterOnUserPermissions && !$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...
152
                continue;
153
            }
154
155
            $contentInfoList[$id] = $contentInfo;
156
        }
157
158
        return $contentInfoList;
159
    }
160
161
    /**
162
     * Loads a content info object.
163
     *
164
     * To load fields use loadContent
165
     *
166
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
167
     *
168
     * @param mixed $id
169
     * @param bool $isRemoteId
170
     *
171
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
172
     */
173
    public function internalLoadContentInfo($id, $isRemoteId = false)
174
    {
175
        try {
176
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
177
178
            return $this->domainMapper->buildContentInfoDomainObject(
179
                $this->persistenceHandler->contentHandler()->$method($id)
180
            );
181
        } catch (APINotFoundException $e) {
182
            throw new NotFoundException(
183
                'Content',
184
                $id,
185
                $e
186
            );
187
        }
188
    }
189
190
    /**
191
     * Loads a content info object for the given remoteId.
192
     *
193
     * To load fields use loadContent
194
     *
195
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
196
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
197
     *
198
     * @param string $remoteId
199
     *
200
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
201
     */
202 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
203
    {
204
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
205
206
        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...
207
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
208
        }
209
210
        return $contentInfo;
211
    }
212
213
    /**
214
     * Loads a version info of the given content object.
215
     *
216
     * If no version number is given, the method returns the current version
217
     *
218
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
219
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
220
     *
221
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
222
     * @param int $versionNo the version number. If not given the current version is returned.
223
     *
224
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
225
     */
226
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
227
    {
228
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
229
    }
230
231
    /**
232
     * Loads a version info of the given content object id.
233
     *
234
     * If no version number is given, the method returns the current version
235
     *
236
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
237
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
238
     *
239
     * @param mixed $contentId
240
     * @param int $versionNo the version number. If not given the current version is returned.
241
     *
242
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
243
     */
244
    public function loadVersionInfoById($contentId, $versionNo = null)
245
    {
246
        // @todo SPI should also support null to avoid concurrency issues
247
        if ($versionNo === null) {
248
            $versionNo = $this->loadContentInfo($contentId)->currentVersionNo;
249
        }
250
251
        try {
252
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
253
                $contentId,
254
                $versionNo
255
            );
256
        } catch (APINotFoundException $e) {
257
            throw new NotFoundException(
258
                'VersionInfo',
259
                array(
260
                    'contentId' => $contentId,
261
                    'versionNo' => $versionNo,
262
                ),
263
                $e
264
            );
265
        }
266
267
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
268
269
        if ($versionInfo->isPublished()) {
270
            $function = 'read';
271
        } else {
272
            $function = 'versionread';
273
        }
274
275
        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...
276
            throw new UnauthorizedException('content', $function, array('contentId' => $contentId));
277
        }
278
279
        return $versionInfo;
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
286
    {
287
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
288
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
289
            $useAlwaysAvailable = false;
290
        }
291
292
        return $this->loadContent(
293
            $contentInfo->id,
294
            $languages,
295
            $versionNo,// On purpose pass as-is and not use $contentInfo, to make sure to return actual current version on null
296
            $useAlwaysAvailable
297
        );
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
304
    {
305
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
306
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
307
            $useAlwaysAvailable = false;
308
        }
309
310
        return $this->loadContent(
311
            $versionInfo->getContentInfo()->id,
312
            $languages,
313
            $versionInfo->versionNo,
314
            $useAlwaysAvailable
315
        );
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
322
    {
323
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
324
325
        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...
326
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
327
        }
328
        if (
329
            !$content->getVersionInfo()->isPublished()
330
            && !$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...
331
        ) {
332
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentId, 'versionNo' => $versionNo));
333
        }
334
335
        return $content;
336
    }
337
338
    /**
339
     * Loads content in a version of the given content object.
340
     *
341
     * If no version number is given, the method returns the current version
342
     *
343
     * @internal
344
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
345
     *
346
     * @param mixed $id
347
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
348
     *                         returned value object. If not given all languages are returned.
349
     * @param int|null $versionNo the version number. If not given the current version is returned
350
     * @param bool $isRemoteId
351
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
352
     *
353
     * @return \eZ\Publish\API\Repository\Values\Content\Content
354
     */
355
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
356
    {
357
        try {
358
            // Get Content ID if lookup by remote ID
359
            if ($isRemoteId) {
360
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
361
                $id = $spiContentInfo->id;
362
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
363
                $isRemoteId = false;
364
            }
365
366
            $loadLanguages = $languages;
367
            $alwaysAvailableLanguageCode = null;
368
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
369
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
370
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
371
                if (!isset($spiContentInfo)) {
372
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
373
                }
374
375
                if ($spiContentInfo->alwaysAvailable) {
376
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
377
                    $loadLanguages = array_unique($loadLanguages);
378
                }
379
            }
380
381
            $spiContent = $this->persistenceHandler->contentHandler()->load(
382
                $id,
383
                $versionNo,
384
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 366 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...
385
            );
386
        } catch (APINotFoundException $e) {
387
            throw new NotFoundException(
388
                'Content',
389
                array(
390
                    $isRemoteId ? 'remoteId' : 'id' => $id,
391
                    'languages' => $languages,
392
                    'versionNo' => $versionNo,
393
                ),
394
                $e
395
            );
396
        }
397
398
        return $this->domainMapper->buildContentDomainObject(
399
            $spiContent,
400
            $this->repository->getContentTypeService()->loadContentType(
401
                $spiContent->versionInfo->contentInfo->contentTypeId
402
            ),
403
            $languages ?? [],
404
            $alwaysAvailableLanguageCode
405
        );
406
    }
407
408
    /**
409
     * Loads content in a version for the content object reference by the given remote id.
410
     *
411
     * If no version is given, the method returns the current version
412
     *
413
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
414
     * @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
415
     *
416
     * @param string $remoteId
417
     * @param array $languages A language filter for fields. If not given all languages are returned
418
     * @param int $versionNo the version number. If not given the current version is returned
419
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
420
     *
421
     * @return \eZ\Publish\API\Repository\Values\Content\Content
422
     */
423 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
424
    {
425
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
426
427
        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...
428
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
429
        }
430
431
        if (
432
            !$content->getVersionInfo()->isPublished()
433
            && !$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...
434
        ) {
435
            throw new UnauthorizedException('content', 'versionread', array('remoteId' => $remoteId, 'versionNo' => $versionNo));
436
        }
437
438
        return $content;
439
    }
440
441
    /**
442
     * Bulk-load Content items by the list of ContentInfo Value Objects.
443
     *
444
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
445
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
446
     * allowed to access every Content on the list.
447
     *
448
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
449
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
450
     *                            returned value object. If not given all languages are returned.
451
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
452
     *                                 unless all languages have been asked for.
453
     *
454
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
455
     */
456
    public function loadContentListByContentInfo(
457
        array $contentInfoList,
458
        array $languages = [],
459
        $useAlwaysAvailable = true
460
    ) {
461
        $loadAllLanguages = $languages === Language::ALL;
462
        $contentIds = [];
463
        $contentTypeIds = [];
464
        $translations = $languages;
465 View Code Duplication
        foreach ($contentInfoList as $contentInfo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
466
            $contentIds[] = $contentInfo->id;
467
            $contentTypeIds[] = $contentInfo->contentTypeId;
468
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
469
            // Might in some case load more languages then intended, but prioritised handling will pick right one
470
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
471
                $translations[] = $contentInfo->mainLanguageCode;
472
            }
473
        }
474
475
        $contentList = [];
476
        $translations = array_unique($translations);
477
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
478
            $contentIds,
479
            $translations
480
        );
481
        $contentTypeList = $this->repository->getContentTypeService()->loadContentTypeList(
482
            array_unique($contentTypeIds),
483
            $languages
484
        );
485
        foreach ($spiContentList as $contentId => $spiContent) {
486
            $contentInfo = $spiContent->versionInfo->contentInfo;
487
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
488
                $spiContent,
489
                $contentTypeList[$contentInfo->contentTypeId],
490
                $languages,
491
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
492
            );
493
        }
494
495
        return $contentList;
496
    }
497
498
    /**
499
     * Creates a new content draft assigned to the authenticated user.
500
     *
501
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
502
     * but this required special rights for the authenticated user
503
     * (this is useful for content staging where the transfer process does not
504
     * have to authenticate with the user which created the content object in the source server).
505
     * The user has to publish the draft if it should be visible.
506
     * In 4.x at least one location has to be provided in the location creation array.
507
     *
508
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
509
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
510
     *                                                                        struct are missing or invalid, or if multiple locations are under the
511
     *                                                                        same parent.
512
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
513
     *                                                                               or if a required field is missing / set to an empty value.
514
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
515
     *                                                                          or value is set for non-translatable field in language
516
     *                                                                          other than main.
517
     *
518
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
519
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
520
     *
521
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
522
     */
523
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = array())
524
    {
525
        if ($contentCreateStruct->mainLanguageCode === null) {
526
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
527
        }
528
529
        if ($contentCreateStruct->contentType === null) {
530
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
531
        }
532
533
        $contentCreateStruct = clone $contentCreateStruct;
534
535
        if ($contentCreateStruct->ownerId === null) {
536
            $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...
537
        }
538
539
        if ($contentCreateStruct->alwaysAvailable === null) {
540
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
541
        }
542
543
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
544
            $contentCreateStruct->contentType->id
545
        );
546
547
        if (empty($contentCreateStruct->sectionId)) {
548
            if (isset($locationCreateStructs[0])) {
549
                $location = $this->repository->getLocationService()->loadLocation(
550
                    $locationCreateStructs[0]->parentLocationId
551
                );
552
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
553
            } else {
554
                $contentCreateStruct->sectionId = 1;
555
            }
556
        }
557
558
        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...
559
            throw new UnauthorizedException(
560
                'content',
561
                'create',
562
                array(
563
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
564
                            $locationCreateStructs[0]->parentLocationId :
565
                            null,
566
                    'sectionId' => $contentCreateStruct->sectionId,
567
                )
568
            );
569
        }
570
571
        if (!empty($contentCreateStruct->remoteId)) {
572
            try {
573
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
574
575
                throw new InvalidArgumentException(
576
                    '$contentCreateStruct',
577
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
578
                );
579
            } catch (APINotFoundException $e) {
580
                // Do nothing
581
            }
582
        } else {
583
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
584
        }
585
586
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
587
588
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
589
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
590
591
        $fieldValues = array();
592
        $spiFields = array();
593
        $allFieldErrors = array();
594
        $inputRelations = array();
595
        $locationIdToContentIdMapping = array();
596
597
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
598
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
599
            $fieldType = $this->fieldTypeRegistry->getFieldType(
600
                $fieldDefinition->fieldTypeIdentifier
601
            );
602
603
            foreach ($languageCodes as $languageCode) {
604
                $isEmptyValue = false;
605
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
606
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
607
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
608
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
609
                } else {
610
                    $fieldValue = $fieldDefinition->defaultValue;
611
                }
612
613
                $fieldValue = $fieldType->acceptValue($fieldValue);
614
615 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...
616
                    $isEmptyValue = true;
617
                    if ($fieldDefinition->isRequired) {
618
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
619
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
620
                            null,
621
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
622
                            'empty'
623
                        );
624
                    }
625
                } else {
626
                    $fieldErrors = $fieldType->validate(
627
                        $fieldDefinition,
628
                        $fieldValue
629
                    );
630
                    if (!empty($fieldErrors)) {
631
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
632
                    }
633
                }
634
635
                if (!empty($allFieldErrors)) {
636
                    continue;
637
                }
638
639
                $this->relationProcessor->appendFieldRelations(
640
                    $inputRelations,
641
                    $locationIdToContentIdMapping,
642
                    $fieldType,
643
                    $fieldValue,
644
                    $fieldDefinition->id
645
                );
646
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
647
648
                // Only non-empty value for: translatable field or in main language
649
                if (
650
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
651
                    (!$isEmptyValue && $isLanguageMain)
652
                ) {
653
                    $spiFields[] = new SPIField(
654
                        array(
655
                            'id' => null,
656
                            'fieldDefinitionId' => $fieldDefinition->id,
657
                            'type' => $fieldDefinition->fieldTypeIdentifier,
658
                            'value' => $fieldType->toPersistenceValue($fieldValue),
659
                            'languageCode' => $languageCode,
660
                            'versionNo' => null,
661
                        )
662
                    );
663
                }
664
            }
665
        }
666
667
        if (!empty($allFieldErrors)) {
668
            throw new ContentFieldValidationException($allFieldErrors);
669
        }
670
671
        $spiContentCreateStruct = new SPIContentCreateStruct(
672
            array(
673
                'name' => $this->nameSchemaService->resolve(
674
                    $contentCreateStruct->contentType->nameSchema,
675
                    $contentCreateStruct->contentType,
676
                    $fieldValues,
677
                    $languageCodes
678
                ),
679
                'typeId' => $contentCreateStruct->contentType->id,
680
                'sectionId' => $contentCreateStruct->sectionId,
681
                'ownerId' => $contentCreateStruct->ownerId,
682
                'locations' => $spiLocationCreateStructs,
683
                'fields' => $spiFields,
684
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
685
                'remoteId' => $contentCreateStruct->remoteId,
686
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
687
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
688
                    $contentCreateStruct->mainLanguageCode
689
                )->id,
690
            )
691
        );
692
693
        $defaultObjectStates = $this->getDefaultObjectStates();
694
695
        $this->repository->beginTransaction();
696
        try {
697
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
698
            $this->relationProcessor->processFieldRelations(
699
                $inputRelations,
700
                $spiContent->versionInfo->contentInfo->id,
701
                $spiContent->versionInfo->versionNo,
702
                $contentCreateStruct->contentType
703
            );
704
705
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
706
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
707
                $objectStateHandler->setContentState(
708
                    $spiContent->versionInfo->contentInfo->id,
709
                    $objectStateGroupId,
710
                    $objectState->id
711
                );
712
            }
713
714
            $this->repository->commit();
715
        } catch (Exception $e) {
716
            $this->repository->rollback();
717
            throw $e;
718
        }
719
720
        return $this->domainMapper->buildContentDomainObject(
721
            $spiContent,
722
            $contentCreateStruct->contentType
723
        );
724
    }
725
726
    /**
727
     * Returns an array of default content states with content state group id as key.
728
     *
729
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
730
     */
731
    protected function getDefaultObjectStates()
732
    {
733
        $defaultObjectStatesMap = array();
734
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
735
736
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
737
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
738
                // Only register the first object state which is the default one.
739
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
740
                break;
741
            }
742
        }
743
744
        return $defaultObjectStatesMap;
745
    }
746
747
    /**
748
     * Returns all language codes used in given $fields.
749
     *
750
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
751
     *
752
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
753
     *
754
     * @return string[]
755
     */
756
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
757
    {
758
        $languageCodes = array();
759
760
        foreach ($contentCreateStruct->fields as $field) {
761
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
762
                continue;
763
            }
764
765
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
766
                $field->languageCode
767
            );
768
            $languageCodes[$field->languageCode] = true;
769
        }
770
771
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
772
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
773
                $contentCreateStruct->mainLanguageCode
774
            );
775
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
776
        }
777
778
        return array_keys($languageCodes);
779
    }
780
781
    /**
782
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
783
     *
784
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
785
     *                                                                          or value is set for non-translatable field in language
786
     *                                                                          other than main
787
     *
788
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
789
     *
790
     * @return array
791
     */
792
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
793
    {
794
        $fields = array();
795
796
        foreach ($contentCreateStruct->fields as $field) {
797
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
798
799
            if ($fieldDefinition === null) {
800
                throw new ContentValidationException(
801
                    "Field definition '%identifier%' does not exist in given ContentType",
802
                    ['%identifier%' => $field->fieldDefIdentifier]
803
                );
804
            }
805
806
            if ($field->languageCode === null) {
807
                $field = $this->cloneField(
808
                    $field,
809
                    array('languageCode' => $contentCreateStruct->mainLanguageCode)
810
                );
811
            }
812
813 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...
814
                throw new ContentValidationException(
815
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
816
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
817
                );
818
            }
819
820
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
821
        }
822
823
        return $fields;
824
    }
825
826
    /**
827
     * Clones $field with overriding specific properties from given $overrides array.
828
     *
829
     * @param Field $field
830
     * @param array $overrides
831
     *
832
     * @return Field
833
     */
834
    private function cloneField(Field $field, array $overrides = [])
835
    {
836
        $fieldData = array_merge(
837
            [
838
                'id' => $field->id,
839
                'value' => $field->value,
840
                'languageCode' => $field->languageCode,
841
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
842
                'fieldTypeIdentifier' => $field->fieldTypeIdentifier,
843
            ],
844
            $overrides
845
        );
846
847
        return new Field($fieldData);
848
    }
849
850
    /**
851
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
852
     *
853
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
854
     *
855
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
856
     */
857
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
858
    {
859
        $spiLocationCreateStructs = array();
860
        $parentLocationIdSet = array();
861
        $mainLocation = true;
862
863
        foreach ($locationCreateStructs as $locationCreateStruct) {
864
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
865
                throw new InvalidArgumentException(
866
                    '$locationCreateStructs',
867
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
868
                );
869
            }
870
871
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
872
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
873
            }
874
875
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
876
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
877
            }
878
879
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
880
            $parentLocation = $this->repository->getLocationService()->loadLocation(
881
                $locationCreateStruct->parentLocationId
882
            );
883
884
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
885
                $locationCreateStruct,
886
                $parentLocation,
887
                $mainLocation,
888
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
889
                null,
890
                null
891
            );
892
893
            // First Location in the list will be created as main Location
894
            $mainLocation = false;
895
        }
896
897
        return $spiLocationCreateStructs;
898
    }
899
900
    /**
901
     * Updates the metadata.
902
     *
903
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
904
     *
905
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
906
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
907
     *
908
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
909
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
910
     *
911
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
912
     */
913
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
914
    {
915
        $propertyCount = 0;
916
        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...
917
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
918
                $propertyCount += 1;
919
            }
920
        }
921
        if ($propertyCount === 0) {
922
            throw new InvalidArgumentException(
923
                '$contentMetadataUpdateStruct',
924
                'At least one property must be set'
925
            );
926
        }
927
928
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
929
930
        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...
931
            throw new UnauthorizedException('content', 'edit', array('contentId' => $loadedContentInfo->id));
932
        }
933
934
        if (isset($contentMetadataUpdateStruct->remoteId)) {
935
            try {
936
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
937
938
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
939
                    throw new InvalidArgumentException(
940
                        '$contentMetadataUpdateStruct',
941
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
942
                    );
943
                }
944
            } catch (APINotFoundException $e) {
945
                // Do nothing
946
            }
947
        }
948
949
        $this->repository->beginTransaction();
950
        try {
951
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
952
                $this->persistenceHandler->contentHandler()->updateMetadata(
953
                    $loadedContentInfo->id,
954
                    new SPIMetadataUpdateStruct(
955
                        array(
956
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
957
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
958
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
959
                                null,
960
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
961
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
962
                                null,
963
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
964
                                $this->repository->getContentLanguageService()->loadLanguage(
965
                                    $contentMetadataUpdateStruct->mainLanguageCode
966
                                )->id :
967
                                null,
968
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
969
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
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
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1079
     *
1080
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1081
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1082
     * @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
1083
     *
1084
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1085
     */
1086
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1087
    {
1088
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1089
1090
        if ($versionInfo !== null) {
1091
            // Check that given $contentInfo and $versionInfo belong to the same content
1092
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1093
                throw new InvalidArgumentException(
1094
                    '$versionInfo',
1095
                    'VersionInfo does not belong to the same content as given ContentInfo'
1096
                );
1097
            }
1098
1099
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1100
1101
            switch ($versionInfo->status) {
1102
                case VersionInfo::STATUS_PUBLISHED:
1103
                case VersionInfo::STATUS_ARCHIVED:
1104
                    break;
1105
1106
                default:
1107
                    // @todo: throw an exception here, to be defined
1108
                    throw new BadStateException(
1109
                        '$versionInfo',
1110
                        'Draft can not be created from a draft version'
1111
                    );
1112
            }
1113
1114
            $versionNo = $versionInfo->versionNo;
1115
        } elseif ($contentInfo->published) {
1116
            $versionNo = $contentInfo->currentVersionNo;
1117
        } else {
1118
            // @todo: throw an exception here, to be defined
1119
            throw new BadStateException(
1120
                '$contentInfo',
1121
                'Content is not published, draft can be created only from published or archived version'
1122
            );
1123
        }
1124
1125
        if ($creator === null) {
1126
            $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...
1127
        }
1128
1129
        if (!$this->repository->canUser('content', 'edit', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1130
            throw new UnauthorizedException('content', 'edit', array('contentId' => $contentInfo->id));
1131
        }
1132
1133
        $this->repository->beginTransaction();
1134
        try {
1135
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1136
                $contentInfo->id,
1137
                $versionNo,
1138
                $creator->getUserId()
1139
            );
1140
            $this->repository->commit();
1141
        } catch (Exception $e) {
1142
            $this->repository->rollback();
1143
            throw $e;
1144
        }
1145
1146
        return $this->domainMapper->buildContentDomainObject(
1147
            $spiContent,
1148
            $this->repository->getContentTypeService()->loadContentType(
1149
                $spiContent->versionInfo->contentInfo->contentTypeId
1150
            )
1151
        );
1152
    }
1153
1154
    /**
1155
     * Loads drafts for a user.
1156
     *
1157
     * If no user is given the drafts for the authenticated user a returned
1158
     *
1159
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list
1160
     *
1161
     * @param \eZ\Publish\API\Repository\Values\User\UserReference $user
1162
     *
1163
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo the drafts ({@link VersionInfo}) owned by the given user
1164
     */
1165
    public function loadContentDrafts(User $user = null)
1166
    {
1167
        if ($user === null) {
1168
            $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...
1169
        }
1170
1171
        // throw early if user has absolutely no access to versionread
1172
        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...
1173
            throw new UnauthorizedException('content', 'versionread');
1174
        }
1175
1176
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser($user->getUserId());
1177
        $versionInfoList = array();
1178 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...
1179
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1180
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1181
            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...
1182
                throw new UnauthorizedException('content', 'versionread', array('contentId' => $versionInfo->contentInfo->id));
1183
            }
1184
1185
            $versionInfoList[] = $versionInfo;
1186
        }
1187
1188
        return $versionInfoList;
1189
    }
1190
1191
    /**
1192
     * Updates the fields of a draft.
1193
     *
1194
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1195
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1196
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1197
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1198
     *                                                                               or if a required field is missing / set to an empty value.
1199
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1200
     *                                                                          or value is set for non-translatable field in language
1201
     *                                                                          other than main.
1202
     *
1203
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1204
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1205
     *
1206
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1207
     */
1208
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1209
    {
1210
        $contentUpdateStruct = clone $contentUpdateStruct;
1211
1212
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1213
        $content = $this->loadContent(
1214
            $versionInfo->getContentInfo()->id,
1215
            null,
1216
            $versionInfo->versionNo
1217
        );
1218
        if (!$content->versionInfo->isDraft()) {
1219
            throw new BadStateException(
1220
                '$versionInfo',
1221
                'Version is not a draft and can not be updated'
1222
            );
1223
        }
1224
1225
        if (!$this->repository->canUser('content', 'edit', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1226
            throw new UnauthorizedException('content', 'edit', array('contentId' => $content->id));
1227
        }
1228
1229
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1230
        if ($contentUpdateStruct->initialLanguageCode === null) {
1231
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1232
        }
1233
1234
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1235
        $contentLanguageHandler = $this->persistenceHandler->contentLanguageHandler();
1236
        foreach ($allLanguageCodes as $languageCode) {
1237
            $contentLanguageHandler->loadByLanguageCode($languageCode);
1238
        }
1239
1240
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1241
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1242
            $content->contentInfo->contentTypeId
1243
        );
1244
        $fields = $this->mapFieldsForUpdate(
1245
            $contentUpdateStruct,
1246
            $contentType,
1247
            $mainLanguageCode
1248
        );
1249
1250
        $fieldValues = array();
1251
        $spiFields = array();
1252
        $allFieldErrors = array();
1253
        $inputRelations = array();
1254
        $locationIdToContentIdMapping = array();
1255
1256
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1257
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1258
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1259
                $fieldDefinition->fieldTypeIdentifier
1260
            );
1261
1262
            foreach ($allLanguageCodes as $languageCode) {
1263
                $isCopied = $isEmpty = $isRetained = false;
1264
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1265
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1266
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1267
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1268
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1269
1270
                if (!$isFieldUpdated && !$isLanguageNew) {
1271
                    $isRetained = true;
1272
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1273
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1274
                    $isCopied = true;
1275
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1276
                } elseif ($isFieldUpdated) {
1277
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1278
                } else {
1279
                    $fieldValue = $fieldDefinition->defaultValue;
1280
                }
1281
1282
                $fieldValue = $fieldType->acceptValue($fieldValue);
1283
1284 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...
1285
                    $isEmpty = true;
1286
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1287
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1288
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1289
                            null,
1290
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1291
                            'empty'
1292
                        );
1293
                    }
1294
                } elseif ($isLanguageUpdated) {
1295
                    $fieldErrors = $fieldType->validate(
1296
                        $fieldDefinition,
1297
                        $fieldValue
1298
                    );
1299
                    if (!empty($fieldErrors)) {
1300
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1301
                    }
1302
                }
1303
1304
                if (!empty($allFieldErrors)) {
1305
                    continue;
1306
                }
1307
1308
                $this->relationProcessor->appendFieldRelations(
1309
                    $inputRelations,
1310
                    $locationIdToContentIdMapping,
1311
                    $fieldType,
1312
                    $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...
1313
                    $fieldDefinition->id
1314
                );
1315
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1316
1317
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1318
                    continue;
1319
                }
1320
1321
                $spiFields[] = new SPIField(
1322
                    array(
1323
                        'id' => $isLanguageNew ?
1324
                            null :
1325
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1326
                        'fieldDefinitionId' => $fieldDefinition->id,
1327
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1328
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1329
                        'languageCode' => $languageCode,
1330
                        'versionNo' => $versionInfo->versionNo,
1331
                    )
1332
                );
1333
            }
1334
        }
1335
1336
        if (!empty($allFieldErrors)) {
1337
            throw new ContentFieldValidationException($allFieldErrors);
1338
        }
1339
1340
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1341
            array(
1342
                'name' => $this->nameSchemaService->resolveNameSchema(
1343
                    $content,
1344
                    $fieldValues,
1345
                    $allLanguageCodes,
1346
                    $contentType
1347
                ),
1348
                '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...
1349
                'fields' => $spiFields,
1350
                'modificationDate' => time(),
1351
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1352
                    $contentUpdateStruct->initialLanguageCode
1353
                )->id,
1354
            )
1355
        );
1356
        $existingRelations = $this->loadRelations($versionInfo);
1357
1358
        $this->repository->beginTransaction();
1359
        try {
1360
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1361
                $versionInfo->getContentInfo()->id,
1362
                $versionInfo->versionNo,
1363
                $spiContentUpdateStruct
1364
            );
1365
            $this->relationProcessor->processFieldRelations(
1366
                $inputRelations,
1367
                $spiContent->versionInfo->contentInfo->id,
1368
                $spiContent->versionInfo->versionNo,
1369
                $contentType,
1370
                $existingRelations
1371
            );
1372
            $this->repository->commit();
1373
        } catch (Exception $e) {
1374
            $this->repository->rollback();
1375
            throw $e;
1376
        }
1377
1378
        return $this->domainMapper->buildContentDomainObject(
1379
            $spiContent,
1380
            $contentType
1381
        );
1382
    }
1383
1384
    /**
1385
     * Returns only updated language codes.
1386
     *
1387
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1388
     *
1389
     * @return array
1390
     */
1391 View Code Duplication
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1392
    {
1393
        $languageCodes = [
1394
            $contentUpdateStruct->initialLanguageCode => true,
1395
        ];
1396
1397
        foreach ($contentUpdateStruct->fields as $field) {
1398
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1399
                continue;
1400
            }
1401
1402
            $languageCodes[$field->languageCode] = true;
1403
        }
1404
1405
        return array_keys($languageCodes);
1406
    }
1407
1408
    /**
1409
     * Returns all language codes used in given $fields.
1410
     *
1411
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1412
     *
1413
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1414
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1415
     *
1416
     * @return array
1417
     */
1418
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1419
    {
1420
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1421
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1422
1423
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1424
        foreach ($updatedLanguageCodes as $languageCode) {
1425
            $languageCodes[$languageCode] = true;
1426
        }
1427
1428
        return array_keys($languageCodes);
1429
    }
1430
1431
    /**
1432
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1433
     *
1434
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1435
     *                                                                          or value is set for non-translatable field in language
1436
     *                                                                          other than main
1437
     *
1438
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1439
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1440
     * @param string $mainLanguageCode
1441
     *
1442
     * @return array
1443
     */
1444
    protected function mapFieldsForUpdate(
1445
        APIContentUpdateStruct $contentUpdateStruct,
1446
        ContentType $contentType,
1447
        $mainLanguageCode
1448
    ) {
1449
        $fields = array();
1450
1451
        foreach ($contentUpdateStruct->fields as $field) {
1452
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1453
1454
            if ($fieldDefinition === null) {
1455
                throw new ContentValidationException(
1456
                    "Field definition '%identifier%' does not exist in given ContentType",
1457
                    ['%identifier%' => $field->fieldDefIdentifier]
1458
                );
1459
            }
1460
1461
            if ($field->languageCode === null) {
1462
                if ($fieldDefinition->isTranslatable) {
1463
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1464
                } else {
1465
                    $languageCode = $mainLanguageCode;
1466
                }
1467
                $field = $this->cloneField($field, array('languageCode' => $languageCode));
1468
            }
1469
1470 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...
1471
                throw new ContentValidationException(
1472
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1473
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1474
                );
1475
            }
1476
1477
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1478
        }
1479
1480
        return $fields;
1481
    }
1482
1483
    /**
1484
     * Publishes a content version.
1485
     *
1486
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1487
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1488
     *
1489
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version
1490
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1491
     *
1492
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1493
     *
1494
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1495
     */
1496
    public function publishVersion(APIVersionInfo $versionInfo)
1497
    {
1498
        $content = $this->internalLoadContent(
1499
            $versionInfo->contentInfo->id,
1500
            null,
1501
            $versionInfo->versionNo
1502
        );
1503
1504
        if (!$this->repository->canUser('content', 'publish', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1505
            throw new UnauthorizedException('content', 'publish', array('contentId' => $content->id));
1506
        }
1507
1508
        $this->repository->beginTransaction();
1509
        try {
1510
            $content = $this->internalPublishVersion($content->getVersionInfo());
1511
            $this->repository->commit();
1512
        } catch (Exception $e) {
1513
            $this->repository->rollback();
1514
            throw $e;
1515
        }
1516
1517
        return $content;
1518
    }
1519
1520
    /**
1521
     * Publishes a content version.
1522
     *
1523
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1524
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1525
     *
1526
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1527
     *
1528
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1529
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1530
     *
1531
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1532
     */
1533
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1534
    {
1535
        if (!$versionInfo->isDraft()) {
1536
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1537
        }
1538
1539
        $currentTime = $this->getUnixTimestamp();
1540
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1541
            $publicationDate = $currentTime;
1542
        }
1543
1544
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1545
        $metadataUpdateStruct->publicationDate = $publicationDate;
1546
        $metadataUpdateStruct->modificationDate = $currentTime;
1547
1548
        $contentId = $versionInfo->getContentInfo()->id;
1549
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1550
            $contentId,
1551
            $versionInfo->versionNo,
1552
            $metadataUpdateStruct
1553
        );
1554
1555
        $content = $this->domainMapper->buildContentDomainObject(
1556
            $spiContent,
1557
            $this->repository->getContentTypeService()->loadContentType(
1558
                $spiContent->versionInfo->contentInfo->contentTypeId
1559
            )
1560
        );
1561
1562
        $this->publishUrlAliasesForContent($content);
1563
1564
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1565
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1566
            $contentId,
1567
            APIVersionInfo::STATUS_ARCHIVED,
1568
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1569
        );
1570
1571
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1572
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1573
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1574
            $archiveVersion = array_shift($archiveList);
1575
            $this->persistenceHandler->contentHandler()->deleteVersion(
1576
                $contentId,
1577
                $archiveVersion->versionNo
1578
            );
1579
        }
1580
1581
        return $content;
1582
    }
1583
1584
    /**
1585
     * @return int
1586
     */
1587
    protected function getUnixTimestamp()
1588
    {
1589
        return time();
1590
    }
1591
1592
    /**
1593
     * Removes the given version.
1594
     *
1595
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1596
     *         published state or is a last version of Content in non draft state
1597
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1598
     *
1599
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1600
     */
1601
    public function deleteVersion(APIVersionInfo $versionInfo)
1602
    {
1603
        if ($versionInfo->isPublished()) {
1604
            throw new BadStateException(
1605
                '$versionInfo',
1606
                'Version is published and can not be removed'
1607
            );
1608
        }
1609
1610
        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...
1611
            throw new UnauthorizedException(
1612
                'content',
1613
                'versionremove',
1614
                array('contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo)
1615
            );
1616
        }
1617
1618
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1619
            $versionInfo->contentInfo->id,
1620
            null,
1621
            2
1622
        );
1623
1624
        if (count($versionList) === 1 && !$versionInfo->isDraft()) {
1625
            throw new BadStateException(
1626
                '$versionInfo',
1627
                'Version is the last version of the Content and can not be removed'
1628
            );
1629
        }
1630
1631
        $this->repository->beginTransaction();
1632
        try {
1633
            $this->persistenceHandler->contentHandler()->deleteVersion(
1634
                $versionInfo->getContentInfo()->id,
1635
                $versionInfo->versionNo
1636
            );
1637
            $this->repository->commit();
1638
        } catch (Exception $e) {
1639
            $this->repository->rollback();
1640
            throw $e;
1641
        }
1642
    }
1643
1644
    /**
1645
     * Loads all versions for the given content.
1646
     *
1647
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1648
     *
1649
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1650
     *
1651
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1652
     */
1653
    public function loadVersions(ContentInfo $contentInfo)
1654
    {
1655
        if (!$this->repository->canUser('content', 'versionread', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1656
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentInfo->id));
1657
        }
1658
1659
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1660
1661
        $versions = array();
1662 View Code Duplication
        foreach ($spiVersionInfoList as $spiVersionInfo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1663
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1664
            if (!$this->repository->canUser('content', 'versionread', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1665
                throw new UnauthorizedException('content', 'versionread', array('versionId' => $versionInfo->id));
1666
            }
1667
1668
            $versions[] = $versionInfo;
1669
        }
1670
1671
        return $versions;
1672
    }
1673
1674
    /**
1675
     * Copies the content to a new location. If no version is given,
1676
     * all versions are copied, otherwise only the given version.
1677
     *
1678
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1679
     *
1680
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1681
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1682
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1683
     *
1684
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1685
     */
1686
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1687
    {
1688
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1689
            $destinationLocationCreateStruct->parentLocationId
1690
        );
1691
        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...
1692
            throw new UnauthorizedException(
1693
                'content',
1694
                'create',
1695
                [
1696
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1697
                    'sectionId' => $contentInfo->sectionId,
1698
                ]
1699
            );
1700
        }
1701
1702
        $defaultObjectStates = $this->getDefaultObjectStates();
1703
1704
        $this->repository->beginTransaction();
1705
        try {
1706
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1707
                $contentInfo->id,
1708
                $versionInfo ? $versionInfo->versionNo : null,
1709
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1710
            );
1711
1712
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1713
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1714
                $objectStateHandler->setContentState(
1715
                    $spiContent->versionInfo->contentInfo->id,
1716
                    $objectStateGroupId,
1717
                    $objectState->id
1718
                );
1719
            }
1720
1721
            $content = $this->internalPublishVersion(
1722
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1723
                $spiContent->versionInfo->creationDate
1724
            );
1725
1726
            $this->repository->getLocationService()->createLocation(
1727
                $content->getVersionInfo()->getContentInfo(),
1728
                $destinationLocationCreateStruct
1729
            );
1730
            $this->repository->commit();
1731
        } catch (Exception $e) {
1732
            $this->repository->rollback();
1733
            throw $e;
1734
        }
1735
1736
        return $this->internalLoadContent($content->id);
1737
    }
1738
1739
    /**
1740
     * Loads all outgoing relations for the given version.
1741
     *
1742
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1743
     *
1744
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1745
     *
1746
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1747
     */
1748
    public function loadRelations(APIVersionInfo $versionInfo)
1749
    {
1750
        if ($versionInfo->isPublished()) {
1751
            $function = 'read';
1752
        } else {
1753
            $function = 'versionread';
1754
        }
1755
1756
        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...
1757
            throw new UnauthorizedException('content', $function);
1758
        }
1759
1760
        $contentInfo = $versionInfo->getContentInfo();
1761
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1762
            $contentInfo->id,
1763
            $versionInfo->versionNo
1764
        );
1765
1766
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1767
        $relations = array();
1768 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...
1769
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1770
            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...
1771
                continue;
1772
            }
1773
1774
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1775
                $spiRelation,
1776
                $contentInfo,
1777
                $destinationContentInfo
1778
            );
1779
        }
1780
1781
        return $relations;
1782
    }
1783
1784
    /**
1785
     * Loads all incoming relations for a content object.
1786
     *
1787
     * The relations come only from published versions of the source content objects
1788
     *
1789
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1790
     *
1791
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1792
     *
1793
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1794
     */
1795
    public function loadReverseRelations(ContentInfo $contentInfo)
1796
    {
1797
        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...
1798
            throw new UnauthorizedException('content', 'reverserelatedlist', array('contentId' => $contentInfo->id));
1799
        }
1800
1801
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1802
            $contentInfo->id
1803
        );
1804
1805
        $returnArray = array();
1806 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...
1807
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1808
            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...
1809
                continue;
1810
            }
1811
1812
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1813
                $spiRelation,
1814
                $sourceContentInfo,
1815
                $contentInfo
1816
            );
1817
        }
1818
1819
        return $returnArray;
1820
    }
1821
1822
    /**
1823
     * Adds a relation of type common.
1824
     *
1825
     * The source of the relation is the content and version
1826
     * referenced by $versionInfo.
1827
     *
1828
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1829
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1830
     *
1831
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1832
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1833
     *
1834
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1835
     */
1836
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1837
    {
1838
        $sourceVersion = $this->loadVersionInfoById(
1839
            $sourceVersion->contentInfo->id,
1840
            $sourceVersion->versionNo
1841
        );
1842
1843
        if (!$sourceVersion->isDraft()) {
1844
            throw new BadStateException(
1845
                '$sourceVersion',
1846
                'Relations of type common can only be added to versions of status draft'
1847
            );
1848
        }
1849
1850
        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...
1851
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1852
        }
1853
1854
        $sourceContentInfo = $sourceVersion->getContentInfo();
1855
1856
        $this->repository->beginTransaction();
1857
        try {
1858
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1859
                new SPIRelationCreateStruct(
1860
                    array(
1861
                        'sourceContentId' => $sourceContentInfo->id,
1862
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1863
                        'sourceFieldDefinitionId' => null,
1864
                        'destinationContentId' => $destinationContent->id,
1865
                        'type' => APIRelation::COMMON,
1866
                    )
1867
                )
1868
            );
1869
            $this->repository->commit();
1870
        } catch (Exception $e) {
1871
            $this->repository->rollback();
1872
            throw $e;
1873
        }
1874
1875
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1876
    }
1877
1878
    /**
1879
     * Removes a relation of type COMMON from a draft.
1880
     *
1881
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1882
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1883
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1884
     *
1885
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1886
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1887
     */
1888
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1889
    {
1890
        $sourceVersion = $this->loadVersionInfoById(
1891
            $sourceVersion->contentInfo->id,
1892
            $sourceVersion->versionNo
1893
        );
1894
1895
        if (!$sourceVersion->isDraft()) {
1896
            throw new BadStateException(
1897
                '$sourceVersion',
1898
                'Relations of type common can only be removed from versions of status draft'
1899
            );
1900
        }
1901
1902
        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...
1903
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1904
        }
1905
1906
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1907
            $sourceVersion->getContentInfo()->id,
1908
            $sourceVersion->versionNo,
1909
            APIRelation::COMMON
1910
        );
1911
1912
        if (empty($spiRelations)) {
1913
            throw new InvalidArgumentException(
1914
                '$sourceVersion',
1915
                'There are no relations of type COMMON for the given destination'
1916
            );
1917
        }
1918
1919
        // there should be only one relation of type COMMON for each destination,
1920
        // but in case there were ever more then one, we will remove them all
1921
        // @todo: alternatively, throw BadStateException?
1922
        $this->repository->beginTransaction();
1923
        try {
1924
            foreach ($spiRelations as $spiRelation) {
1925
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1926
                    $this->persistenceHandler->contentHandler()->removeRelation(
1927
                        $spiRelation->id,
1928
                        APIRelation::COMMON
1929
                    );
1930
                }
1931
            }
1932
            $this->repository->commit();
1933
        } catch (Exception $e) {
1934
            $this->repository->rollback();
1935
            throw $e;
1936
        }
1937
    }
1938
1939
    /**
1940
     * {@inheritdoc}
1941
     */
1942
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
1943
    {
1944
        @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...
1945
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
1946
            E_USER_DEPRECATED
1947
        );
1948
        $this->deleteTranslation($contentInfo, $languageCode);
1949
    }
1950
1951
    /**
1952
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
1953
     *
1954
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
1955
     *
1956
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
1957
     *         is the Main Translation of a Content Item.
1958
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
1959
     *         to delete the content (in one of the locations of the given Content Item).
1960
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
1961
     *         is invalid for the given content.
1962
     *
1963
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1964
     * @param string $languageCode
1965
     *
1966
     * @since 6.13
1967
     */
1968
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
1969
    {
1970
        if ($contentInfo->mainLanguageCode === $languageCode) {
1971
            throw new BadStateException(
1972
                '$languageCode',
1973
                'Specified translation is the main translation of the Content Object'
1974
            );
1975
        }
1976
1977
        $translationWasFound = false;
1978
        $this->repository->beginTransaction();
1979
        try {
1980
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
1981
                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...
1982
                    throw new UnauthorizedException(
1983
                        'content',
1984
                        'remove',
1985
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1986
                    );
1987
                }
1988
1989
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
1990
                    continue;
1991
                }
1992
1993
                $translationWasFound = true;
1994
1995
                // If the translation is the version's only one, delete the version
1996
                if (count($versionInfo->languageCodes) < 2) {
1997
                    $this->persistenceHandler->contentHandler()->deleteVersion(
1998
                        $versionInfo->getContentInfo()->id,
1999
                        $versionInfo->versionNo
2000
                    );
2001
                }
2002
            }
2003
2004
            if (!$translationWasFound) {
2005
                throw new InvalidArgumentException(
2006
                    '$languageCode',
2007
                    sprintf(
2008
                        '%s does not exist in the Content item(id=%d)',
2009
                        $languageCode,
2010
                        $contentInfo->id
2011
                    )
2012
                );
2013
            }
2014
2015
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2016
                $contentInfo->id,
2017
                $languageCode
2018
            );
2019
            $locationIds = array_map(
2020
                function (Location $location) {
2021
                    return $location->id;
2022
                },
2023
                $this->repository->getLocationService()->loadLocations($contentInfo)
2024
            );
2025
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2026
                $locationIds,
2027
                $languageCode
2028
            );
2029
            $this->repository->commit();
2030
        } catch (InvalidArgumentException $e) {
2031
            $this->repository->rollback();
2032
            throw $e;
2033
        } catch (BadStateException $e) {
2034
            $this->repository->rollback();
2035
            throw $e;
2036
        } catch (UnauthorizedException $e) {
2037
            $this->repository->rollback();
2038
            throw $e;
2039
        } catch (Exception $e) {
2040
            $this->repository->rollback();
2041
            // cover generic unexpected exception to fulfill API promise on @throws
2042
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2043
        }
2044
    }
2045
2046
    /**
2047
     * Delete specified Translation from a Content Draft.
2048
     *
2049
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2050
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2051
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2052
     *         to edit the Content (in one of the locations of the given Content Object).
2053
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2054
     *         is invalid for the given Draft.
2055
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2056
     *
2057
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2058
     * @param string $languageCode Language code of the Translation to be removed
2059
     *
2060
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2061
     *
2062
     * @since 6.12
2063
     */
2064
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2065
    {
2066
        if (!$versionInfo->isDraft()) {
2067
            throw new BadStateException(
2068
                '$versionInfo',
2069
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2070
            );
2071
        }
2072
2073
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2074
            throw new BadStateException(
2075
                '$languageCode',
2076
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2077
            );
2078
        }
2079
2080
        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...
2081
            throw new UnauthorizedException(
2082
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2083
            );
2084
        }
2085
2086
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2087
            throw new InvalidArgumentException(
2088
                '$languageCode',
2089
                sprintf(
2090
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2091
                    $versionInfo->contentInfo->id,
2092
                    $versionInfo->versionNo,
2093
                    $languageCode
2094
                )
2095
            );
2096
        }
2097
2098
        if (count($versionInfo->languageCodes) === 1) {
2099
            throw new BadStateException(
2100
                '$languageCode',
2101
                'Specified Translation is the only one Content Object Version has'
2102
            );
2103
        }
2104
2105
        $this->repository->beginTransaction();
2106
        try {
2107
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2108
                $versionInfo->contentInfo->id,
2109
                $versionInfo->versionNo,
2110
                $languageCode
2111
            );
2112
            $this->repository->commit();
2113
2114
            return $this->domainMapper->buildContentDomainObject(
2115
                $spiContent,
2116
                $this->repository->getContentTypeService()->loadContentType(
2117
                    $spiContent->versionInfo->contentInfo->contentTypeId
2118
                )
2119
            );
2120
        } catch (APINotFoundException $e) {
2121
            // avoid wrapping expected NotFoundException in BadStateException handled below
2122
            $this->repository->rollback();
2123
            throw $e;
2124
        } catch (Exception $e) {
2125
            $this->repository->rollback();
2126
            // cover generic unexpected exception to fulfill API promise on @throws
2127
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2128
        }
2129
    }
2130
2131
    /**
2132
     * Instantiates a new content create struct object.
2133
     *
2134
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2135
     *
2136
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2137
     * @param string $mainLanguageCode
2138
     *
2139
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2140
     */
2141
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2142
    {
2143
        return new ContentCreateStruct(
2144
            array(
2145
                'contentType' => $contentType,
2146
                'mainLanguageCode' => $mainLanguageCode,
2147
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2148
            )
2149
        );
2150
    }
2151
2152
    /**
2153
     * Instantiates a new content meta data update struct.
2154
     *
2155
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2156
     */
2157
    public function newContentMetadataUpdateStruct()
2158
    {
2159
        return new ContentMetadataUpdateStruct();
2160
    }
2161
2162
    /**
2163
     * Instantiates a new content update struct.
2164
     *
2165
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2166
     */
2167
    public function newContentUpdateStruct()
2168
    {
2169
        return new ContentUpdateStruct();
2170
    }
2171
}
2172