Completed
Push — master ( 6c3e1a...f2dfe9 )
by André
17:55
created

ContentService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
1901
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1902
        }
1903
1904
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1905
            $sourceVersion->getContentInfo()->id,
1906
            $sourceVersion->versionNo,
1907
            APIRelation::COMMON
1908
        );
1909
1910
        if (empty($spiRelations)) {
1911
            throw new InvalidArgumentException(
1912
                '$sourceVersion',
1913
                'There are no relations of type COMMON for the given destination'
1914
            );
1915
        }
1916
1917
        // there should be only one relation of type COMMON for each destination,
1918
        // but in case there were ever more then one, we will remove them all
1919
        // @todo: alternatively, throw BadStateException?
1920
        $this->repository->beginTransaction();
1921
        try {
1922
            foreach ($spiRelations as $spiRelation) {
1923
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1924
                    $this->persistenceHandler->contentHandler()->removeRelation(
1925
                        $spiRelation->id,
1926
                        APIRelation::COMMON
1927
                    );
1928
                }
1929
            }
1930
            $this->repository->commit();
1931
        } catch (Exception $e) {
1932
            $this->repository->rollback();
1933
            throw $e;
1934
        }
1935
    }
1936
1937
    /**
1938
     * {@inheritdoc}
1939
     */
1940
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
1941
    {
1942
        @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...
1943
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
1944
            E_USER_DEPRECATED
1945
        );
1946
        $this->deleteTranslation($contentInfo, $languageCode);
1947
    }
1948
1949
    /**
1950
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
1951
     *
1952
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
1953
     *
1954
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
1955
     *         is the Main Translation of a Content Item.
1956
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
1957
     *         to delete the content (in one of the locations of the given Content Item).
1958
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
1959
     *         is invalid for the given content.
1960
     *
1961
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1962
     * @param string $languageCode
1963
     *
1964
     * @since 6.13
1965
     */
1966
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
1967
    {
1968
        if ($contentInfo->mainLanguageCode === $languageCode) {
1969
            throw new BadStateException(
1970
                '$languageCode',
1971
                'Specified translation is the main translation of the Content Object'
1972
            );
1973
        }
1974
1975
        $translationWasFound = false;
1976
        $this->repository->beginTransaction();
1977
        try {
1978
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
1979
                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...
1980
                    throw new UnauthorizedException(
1981
                        'content',
1982
                        'remove',
1983
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1984
                    );
1985
                }
1986
1987
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
1988
                    continue;
1989
                }
1990
1991
                $translationWasFound = true;
1992
1993
                // If the translation is the version's only one, delete the version
1994
                if (count($versionInfo->languageCodes) < 2) {
1995
                    $this->persistenceHandler->contentHandler()->deleteVersion(
1996
                        $versionInfo->getContentInfo()->id,
1997
                        $versionInfo->versionNo
1998
                    );
1999
                }
2000
            }
2001
2002
            if (!$translationWasFound) {
2003
                throw new InvalidArgumentException(
2004
                    '$languageCode',
2005
                    sprintf(
2006
                        '%s does not exist in the Content item(id=%d)',
2007
                        $languageCode,
2008
                        $contentInfo->id
2009
                    )
2010
                );
2011
            }
2012
2013
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2014
                $contentInfo->id,
2015
                $languageCode
2016
            );
2017
            $locationIds = array_map(
2018
                function (Location $location) {
2019
                    return $location->id;
2020
                },
2021
                $this->repository->getLocationService()->loadLocations($contentInfo)
2022
            );
2023
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2024
                $locationIds,
2025
                $languageCode
2026
            );
2027
            $this->repository->commit();
2028
        } catch (InvalidArgumentException $e) {
2029
            $this->repository->rollback();
2030
            throw $e;
2031
        } catch (BadStateException $e) {
2032
            $this->repository->rollback();
2033
            throw $e;
2034
        } catch (UnauthorizedException $e) {
2035
            $this->repository->rollback();
2036
            throw $e;
2037
        } catch (Exception $e) {
2038
            $this->repository->rollback();
2039
            // cover generic unexpected exception to fulfill API promise on @throws
2040
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2041
        }
2042
    }
2043
2044
    /**
2045
     * Delete specified Translation from a Content Draft.
2046
     *
2047
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2048
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2049
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2050
     *         to edit the Content (in one of the locations of the given Content Object).
2051
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2052
     *         is invalid for the given Draft.
2053
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2054
     *
2055
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2056
     * @param string $languageCode Language code of the Translation to be removed
2057
     *
2058
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2059
     *
2060
     * @since 6.12
2061
     */
2062
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2063
    {
2064
        if (!$versionInfo->isDraft()) {
2065
            throw new BadStateException(
2066
                '$versionInfo',
2067
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2068
            );
2069
        }
2070
2071
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2072
            throw new BadStateException(
2073
                '$languageCode',
2074
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2075
            );
2076
        }
2077
2078
        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...
2079
            throw new UnauthorizedException(
2080
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2081
            );
2082
        }
2083
2084
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2085
            throw new InvalidArgumentException(
2086
                '$languageCode',
2087
                sprintf(
2088
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2089
                    $versionInfo->contentInfo->id,
2090
                    $versionInfo->versionNo,
2091
                    $languageCode
2092
                )
2093
            );
2094
        }
2095
2096
        if (count($versionInfo->languageCodes) === 1) {
2097
            throw new BadStateException(
2098
                '$languageCode',
2099
                'Specified Translation is the only one Content Object Version has'
2100
            );
2101
        }
2102
2103
        $this->repository->beginTransaction();
2104
        try {
2105
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2106
                $versionInfo->contentInfo->id,
2107
                $versionInfo->versionNo,
2108
                $languageCode
2109
            );
2110
            $this->repository->commit();
2111
2112
            return $this->domainMapper->buildContentDomainObject(
2113
                $spiContent,
2114
                $this->repository->getContentTypeService()->loadContentType(
2115
                    $spiContent->versionInfo->contentInfo->contentTypeId
2116
                )
2117
            );
2118
        } catch (APINotFoundException $e) {
2119
            // avoid wrapping expected NotFoundException in BadStateException handled below
2120
            $this->repository->rollback();
2121
            throw $e;
2122
        } catch (Exception $e) {
2123
            $this->repository->rollback();
2124
            // cover generic unexpected exception to fulfill API promise on @throws
2125
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2126
        }
2127
    }
2128
2129
    /**
2130
     * Instantiates a new content create struct object.
2131
     *
2132
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2133
     *
2134
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2135
     * @param string $mainLanguageCode
2136
     *
2137
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2138
     */
2139
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2140
    {
2141
        return new ContentCreateStruct(
2142
            array(
2143
                'contentType' => $contentType,
2144
                'mainLanguageCode' => $mainLanguageCode,
2145
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2146
            )
2147
        );
2148
    }
2149
2150
    /**
2151
     * Instantiates a new content meta data update struct.
2152
     *
2153
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2154
     */
2155
    public function newContentMetadataUpdateStruct()
2156
    {
2157
        return new ContentMetadataUpdateStruct();
2158
    }
2159
2160
    /**
2161
     * Instantiates a new content update struct.
2162
     *
2163
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2164
     */
2165
    public function newContentUpdateStruct()
2166
    {
2167
        return new ContentUpdateStruct();
2168
    }
2169
}
2170