Completed
Push — EZP-29899 ( ab51a2 )
by André
24:48
created

ContentService::internalLoadContent()   B

Complexity

Conditions 9
Paths 31

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 31
nop 5
dl 0
loc 48
rs 7.5789
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\API\Repository\Values\Content\Language;
14
use eZ\Publish\Core\Repository\Values\Content\Location;
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\TranslationInfo;
19
use eZ\Publish\API\Repository\Values\Content\TranslationValues as APITranslationValues;
20
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
21
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
22
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
23
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
24
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
25
use eZ\Publish\API\Repository\Values\User\User;
26
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
27
use eZ\Publish\API\Repository\Values\Content\Field;
28
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
29
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
30
use eZ\Publish\Core\Base\Exceptions\BadStateException;
31
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
32
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
33
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
34
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
35
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
36
use eZ\Publish\Core\FieldType\ValidationError;
37
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
38
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
39
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
40
use eZ\Publish\Core\Repository\Values\Content\TranslationValues;
41
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
42
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
43
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
44
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
45
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
46
use Exception;
47
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
48
49
/**
50
 * This class provides service methods for managing content.
51
 *
52
 * @example Examples/content.php
53
 */
54
class ContentService implements ContentServiceInterface
55
{
56
    /**
57
     * @var \eZ\Publish\Core\Repository\Repository
58
     */
59
    protected $repository;
60
61
    /**
62
     * @var \eZ\Publish\SPI\Persistence\Handler
63
     */
64
    protected $persistenceHandler;
65
66
    /**
67
     * @var array
68
     */
69
    protected $settings;
70
71
    /**
72
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
73
     */
74
    protected $domainMapper;
75
76
    /**
77
     * @var \eZ\Publish\Core\Repository\Helper\RelationProcessor
78
     */
79
    protected $relationProcessor;
80
81
    /**
82
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
83
     */
84
    protected $nameSchemaService;
85
86
    /**
87
     * @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry
88
     */
89
    protected $fieldTypeRegistry;
90
91
    /**
92
     * Setups service with reference to repository object that created it & corresponding handler.
93
     *
94
     * @param \eZ\Publish\API\Repository\Repository $repository
95
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
96
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
97
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
98
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
99
     * @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...
100
     * @param array $settings
101
     */
102
    public function __construct(
103
        RepositoryInterface $repository,
104
        Handler $handler,
105
        Helper\DomainMapper $domainMapper,
106
        Helper\RelationProcessor $relationProcessor,
107
        Helper\NameSchemaService $nameSchemaService,
108
        Helper\FieldTypeRegistry $fieldTypeRegistry,
109
        array $settings = array()
110
    ) {
111
        $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...
112
        $this->persistenceHandler = $handler;
113
        $this->domainMapper = $domainMapper;
114
        $this->relationProcessor = $relationProcessor;
115
        $this->nameSchemaService = $nameSchemaService;
116
        $this->fieldTypeRegistry = $fieldTypeRegistry;
117
        // Union makes sure default settings are ignored if provided in argument
118
        $this->settings = $settings + array(
119
            // Version archive limit (0-50), only enforced on publish, not on un-publish.
120
            'default_version_archive_limit' => 5,
121
        );
122
    }
123
124
    /**
125
     * Loads a content info object.
126
     *
127
     * To load fields use loadContent
128
     *
129
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
130
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
131
     *
132
     * @param int $contentId
133
     *
134
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
135
     */
136 View Code Duplication
    public function loadContentInfo($contentId)
137
    {
138
        $contentInfo = $this->internalLoadContentInfo($contentId);
139
        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...
140
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
141
        }
142
143
        return $contentInfo;
144
    }
145
146
    /**
147
     * Loads a content info object.
148
     *
149
     * To load fields use loadContent
150
     *
151
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
152
     *
153
     * @param mixed $id
154
     * @param bool $isRemoteId
155
     *
156
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
157
     */
158
    public function internalLoadContentInfo($id, $isRemoteId = false)
159
    {
160
        try {
161
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
162
163
            return $this->domainMapper->buildContentInfoDomainObject(
164
                $this->persistenceHandler->contentHandler()->$method($id)
165
            );
166
        } catch (APINotFoundException $e) {
167
            throw new NotFoundException(
168
                'Content',
169
                $id,
170
                $e
171
            );
172
        }
173
    }
174
175
    /**
176
     * Loads a content info object for the given remoteId.
177
     *
178
     * To load fields use loadContent
179
     *
180
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
181
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
182
     *
183
     * @param string $remoteId
184
     *
185
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
186
     */
187 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
188
    {
189
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
190
191
        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...
192
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
193
        }
194
195
        return $contentInfo;
196
    }
197
198
    /**
199
     * Loads a version info of the given content object.
200
     *
201
     * If no version number is given, the method returns the current version
202
     *
203
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
204
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
205
     *
206
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
207
     * @param int $versionNo the version number. If not given the current version is returned.
208
     *
209
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
210
     */
211
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
212
    {
213
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
214
    }
215
216
    /**
217
     * Loads a version info of the given content object id.
218
     *
219
     * If no version number is given, the method returns the current version
220
     *
221
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
222
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
223
     *
224
     * @param mixed $contentId
225
     * @param int $versionNo the version number. If not given the current version is returned.
226
     *
227
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
228
     */
229
    public function loadVersionInfoById($contentId, $versionNo = null)
230
    {
231
        if ($versionNo === null) {
232
            $versionNo = $this->loadContentInfo($contentId)->currentVersionNo;
233
        }
234
235
        try {
236
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
237
                $contentId,
238
                $versionNo
239
            );
240
        } catch (APINotFoundException $e) {
241
            throw new NotFoundException(
242
                'VersionInfo',
243
                array(
244
                    'contentId' => $contentId,
245
                    'versionNo' => $versionNo,
246
                ),
247
                $e
248
            );
249
        }
250
251
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
252
253
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
254
            $function = 'read';
255
        } else {
256
            $function = 'versionread';
257
        }
258
259
        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...
260
            throw new UnauthorizedException('content', $function, array('contentId' => $contentId));
261
        }
262
263
        return $versionInfo;
264
    }
265
266
    /**
267
     * Loads content in a version for the given content info object.
268
     *
269
     * If no version number is given, the method returns the current version
270
     *
271
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if version with the given number does not exist
272
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
273
     *
274
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
275
     * @param array $languages A language filter for fields. If not given all languages are returned
276
     * @param int $versionNo the version number. If not given the current version is returned
277
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
278
     *
279
     * @return \eZ\Publish\API\Repository\Values\Content\Content
280
     */
281
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
282
    {
283
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
284
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
285
            $useAlwaysAvailable = false;
286
        }
287
288
        return $this->loadContent(
289
            $contentInfo->id,
290
            $languages,
291
            $versionNo,
292
            $useAlwaysAvailable
293
        );
294
    }
295
296
    /**
297
     * Loads content in the version given by version info.
298
     *
299
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
300
     *
301
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
302
     * @param array $languages A language filter for fields. If not given all languages are returned
303
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
304
     *
305
     * @return \eZ\Publish\API\Repository\Values\Content\Content
306
     */
307
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
308
    {
309
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
310
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
311
            $useAlwaysAvailable = false;
312
        }
313
314
        return $this->loadContent(
315
            $versionInfo->getContentInfo()->id,
316
            $languages,
317
            $versionInfo->versionNo,
318
            $useAlwaysAvailable
319
        );
320
    }
321
322
    /**
323
     * Loads content in a version of the given content object.
324
     *
325
     * If no version number is given, the method returns the current version
326
     *
327
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
328
     * @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
329
     *
330
     * @param int $contentId
331
     * @param array|null $languages A language filter for fields. If not given all languages are returned
332
     * @param int|null $versionNo the version number. If not given the current version is returned
333
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
334
     *
335
     * @return \eZ\Publish\API\Repository\Values\Content\Content
336
     */
337 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
338
    {
339
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
340
341
        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...
342
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
343
        }
344
345
        if (
346
            $content->getVersionInfo()->status !== APIVersionInfo::STATUS_PUBLISHED
347
            && !$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...
348
        ) {
349
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentId, 'versionNo' => $versionNo));
350
        }
351
352
        return $content;
353
    }
354
355
    /**
356
     * Loads content in a version of the given content object.
357
     *
358
     * If no version number is given, the method returns the current version
359
     *
360
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
361
     *
362
     * @param mixed $id
363
     * @param array|null $languages A language filter for fields. If not given all languages are returned
364
     * @param int|null $versionNo the version number. If not given the current version is returned
365
     * @param bool $isRemoteId
366
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
367
     *
368
     * @return \eZ\Publish\API\Repository\Values\Content\Content
369
     */
370
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
371
    {
372
        try {
373
            // Get Content ID if lookup by remote ID
374
            if ($isRemoteId) {
375
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
376
                $id = $spiContentInfo->id;
377
            }
378
379
            $loadLanguages = $languages;
380
            $alwaysAvailableLanguageCode = null;
381
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
382
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
383
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
384
                if (!isset($spiContentInfo)) {
385
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
386
                }
387
388
                if ($spiContentInfo->alwaysAvailable) {
389
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
390
                    $loadLanguages = array_unique($loadLanguages);
391
                }
392
            }
393
394
            $spiContent = $this->persistenceHandler->contentHandler()->load(
395
                $id,
396
                $versionNo,
397
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 379 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...
398
            );
399
        } catch (APINotFoundException $e) {
400
            throw new NotFoundException(
401
                'Content',
402
                array(
403
                    $isRemoteId ? 'remoteId' : 'id' => $id,
404
                    'languages' => $languages,
405
                    'versionNo' => $versionNo,
406
                ),
407
                $e
408
            );
409
        }
410
411
        return $this->domainMapper->buildContentDomainObject(
412
            $spiContent,
413
            null,
414
            empty($languages) ? null : $languages,
415
            $alwaysAvailableLanguageCode
416
        );
417
    }
418
419
    /**
420
     * Loads content in a version for the content object reference by the given remote id.
421
     *
422
     * If no version is given, the method returns the current version
423
     *
424
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
425
     * @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
426
     *
427
     * @param string $remoteId
428
     * @param array $languages A language filter for fields. If not given all languages are returned
429
     * @param int $versionNo the version number. If not given the current version is returned
430
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
431
     *
432
     * @return \eZ\Publish\API\Repository\Values\Content\Content
433
     */
434 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
435
    {
436
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
437
438
        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...
439
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
440
        }
441
442
        if (
443
            $content->getVersionInfo()->status !== APIVersionInfo::STATUS_PUBLISHED
444
            && !$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...
445
        ) {
446
            throw new UnauthorizedException('content', 'versionread', array('remoteId' => $remoteId, 'versionNo' => $versionNo));
447
        }
448
449
        return $content;
450
    }
451
452
    /**
453
     * Bulk-load Content items by the list of ContentInfo Value Objects.
454
     *
455
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
456
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
457
     * allowed to access every Content on the list.
458
     *
459
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
460
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
461
     *                            returned value object. If not given all languages are returned.
462
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
463
     *                                 unless all languages have been asked for.
464
     *
465
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
466
     */
467
    public function loadContentListByContentInfo(
468
        array $contentInfoList,
469
        array $languages = [],
470
        $useAlwaysAvailable = true
471
    ) {
472
        $loadAllLanguages = $languages === Language::ALL;
473
        $contentIds = [];
474
        $translations = $languages;
475
        foreach ($contentInfoList as $contentInfo) {
476
            $contentIds[] = $contentInfo->id;
477
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
478
            // Might in some case load more languages then intended, but prioritised handling will pick right one
479
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
480
                $translations[] = $contentInfo->mainLanguageCode;
481
            }
482
        }
483
        $translations = array_unique($translations);
484
485
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
486
            $contentIds,
487
            $translations
488
        );
489
        $contentList = [];
490
        foreach ($spiContentList as $contentId => $spiContent) {
491
            $contentInfo = $spiContent->versionInfo->contentInfo;
492
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
493
                $spiContent,
494
                null,
495
                $languages,
496
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
497
            );
498
        }
499
500
        return $contentList;
501
    }
502
503
    /**
504
     * Creates a new content draft assigned to the authenticated user.
505
     *
506
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
507
     * but this required special rights for the authenticated user
508
     * (this is useful for content staging where the transfer process does not
509
     * have to authenticate with the user which created the content object in the source server).
510
     * The user has to publish the draft if it should be visible.
511
     * In 4.x at least one location has to be provided in the location creation array.
512
     *
513
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
514
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
515
     *                                                                        struct are missing or invalid, or if multiple locations are under the
516
     *                                                                        same parent.
517
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
518
     *                                                                               or if a required field is missing / set to an empty value.
519
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
520
     *                                                                          or value is set for non-translatable field in language
521
     *                                                                          other than main.
522
     *
523
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
524
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
525
     *
526
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
527
     */
528
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = array())
529
    {
530
        if ($contentCreateStruct->mainLanguageCode === null) {
531
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
532
        }
533
534
        if ($contentCreateStruct->contentType === null) {
535
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
536
        }
537
538
        $contentCreateStruct = clone $contentCreateStruct;
539
540
        if ($contentCreateStruct->ownerId === null) {
541
            $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...
542
        }
543
544
        if ($contentCreateStruct->alwaysAvailable === null) {
545
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
546
        }
547
548
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
549
            $contentCreateStruct->contentType->id
550
        );
551
552
        if (empty($contentCreateStruct->sectionId)) {
553
            if (isset($locationCreateStructs[0])) {
554
                $location = $this->repository->getLocationService()->loadLocation(
555
                    $locationCreateStructs[0]->parentLocationId
556
                );
557
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
558
            } else {
559
                $contentCreateStruct->sectionId = 1;
560
            }
561
        }
562
563
        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...
564
            throw new UnauthorizedException(
565
                'content',
566
                'create',
567
                array(
568
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
569
                            $locationCreateStructs[0]->parentLocationId :
570
                            null,
571
                    'sectionId' => $contentCreateStruct->sectionId,
572
                )
573
            );
574
        }
575
576
        if (!empty($contentCreateStruct->remoteId)) {
577
            try {
578
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
579
580
                throw new InvalidArgumentException(
581
                    '$contentCreateStruct',
582
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
583
                );
584
            } catch (APINotFoundException $e) {
585
                // Do nothing
586
            }
587
        } else {
588
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
589
        }
590
591
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
592
593
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
594
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
595
596
        $fieldValues = array();
597
        $spiFields = array();
598
        $allFieldErrors = array();
599
        $inputRelations = array();
600
        $locationIdToContentIdMapping = array();
601
602
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
603
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
604
            $fieldType = $this->fieldTypeRegistry->getFieldType(
605
                $fieldDefinition->fieldTypeIdentifier
606
            );
607
608
            foreach ($languageCodes as $languageCode) {
609
                $isEmptyValue = false;
610
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
611
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
612
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
613
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
614
                } else {
615
                    $fieldValue = $fieldDefinition->defaultValue;
616
                }
617
618
                $fieldValue = $fieldType->acceptValue($fieldValue);
619
620 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...
621
                    $isEmptyValue = true;
622
                    if ($fieldDefinition->isRequired) {
623
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
624
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
625
                            null,
626
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
627
                            'empty'
628
                        );
629
                    }
630
                } else {
631
                    $fieldErrors = $fieldType->validate(
632
                        $fieldDefinition,
633
                        $fieldValue
634
                    );
635
                    if (!empty($fieldErrors)) {
636
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
637
                    }
638
                }
639
640
                if (!empty($allFieldErrors)) {
641
                    continue;
642
                }
643
644
                $this->relationProcessor->appendFieldRelations(
645
                    $inputRelations,
646
                    $locationIdToContentIdMapping,
647
                    $fieldType,
648
                    $fieldValue,
649
                    $fieldDefinition->id
650
                );
651
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
652
653
                // Only non-empty value for: translatable field or in main language
654
                if (
655
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
656
                    (!$isEmptyValue && $isLanguageMain)
657
                ) {
658
                    $spiFields[] = new SPIField(
659
                        array(
660
                            'id' => null,
661
                            'fieldDefinitionId' => $fieldDefinition->id,
662
                            'type' => $fieldDefinition->fieldTypeIdentifier,
663
                            'value' => $fieldType->toPersistenceValue($fieldValue),
664
                            'languageCode' => $languageCode,
665
                            'versionNo' => null,
666
                        )
667
                    );
668
                }
669
            }
670
        }
671
672
        if (!empty($allFieldErrors)) {
673
            throw new ContentFieldValidationException($allFieldErrors);
674
        }
675
676
        $spiContentCreateStruct = new SPIContentCreateStruct(
677
            array(
678
                'name' => $this->nameSchemaService->resolve(
679
                    $contentCreateStruct->contentType->nameSchema,
680
                    $contentCreateStruct->contentType,
681
                    $fieldValues,
682
                    $languageCodes
683
                ),
684
                'typeId' => $contentCreateStruct->contentType->id,
685
                'sectionId' => $contentCreateStruct->sectionId,
686
                'ownerId' => $contentCreateStruct->ownerId,
687
                'locations' => $spiLocationCreateStructs,
688
                'fields' => $spiFields,
689
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
690
                'remoteId' => $contentCreateStruct->remoteId,
691
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
692
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
693
                    $contentCreateStruct->mainLanguageCode
694
                )->id,
695
            )
696
        );
697
698
        $defaultObjectStates = $this->getDefaultObjectStates();
699
700
        $this->repository->beginTransaction();
701
        try {
702
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
703
            $this->relationProcessor->processFieldRelations(
704
                $inputRelations,
705
                $spiContent->versionInfo->contentInfo->id,
706
                $spiContent->versionInfo->versionNo,
707
                $contentCreateStruct->contentType
708
            );
709
710
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
711
                $this->persistenceHandler->objectStateHandler()->setContentState(
712
                    $spiContent->versionInfo->contentInfo->id,
713
                    $objectStateGroupId,
714
                    $objectState->id
715
                );
716
            }
717
718
            $this->repository->commit();
719
        } catch (Exception $e) {
720
            $this->repository->rollback();
721
            throw $e;
722
        }
723
724
        return $this->domainMapper->buildContentDomainObject($spiContent);
725
    }
726
727
    /**
728
     * Returns an array of default content states with content state group id as key.
729
     *
730
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
731
     */
732
    protected function getDefaultObjectStates()
733
    {
734
        $defaultObjectStatesMap = array();
735
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
736
737
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
738
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
739
                // Only register the first object state which is the default one.
740
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
741
                break;
742
            }
743
        }
744
745
        return $defaultObjectStatesMap;
746
    }
747
748
    /**
749
     * Returns all language codes used in given $fields.
750
     *
751
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
752
     *
753
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
754
     *
755
     * @return string[]
756
     */
757
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
758
    {
759
        $languageCodes = array();
760
761
        foreach ($contentCreateStruct->fields as $field) {
762
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
763
                continue;
764
            }
765
766
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
767
                $field->languageCode
768
            );
769
            $languageCodes[$field->languageCode] = true;
770
        }
771
772
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
773
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
774
                $contentCreateStruct->mainLanguageCode
775
            );
776
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
777
        }
778
779
        return array_keys($languageCodes);
780
    }
781
782
    /**
783
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
784
     *
785
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
786
     *                                                                          or value is set for non-translatable field in language
787
     *                                                                          other than main
788
     *
789
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
790
     *
791
     * @return array
792
     */
793
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
794
    {
795
        $fields = array();
796
797
        foreach ($contentCreateStruct->fields as $field) {
798
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
799
800
            if ($fieldDefinition === null) {
801
                throw new ContentValidationException(
802
                    "Field definition '%identifier%' does not exist in given ContentType",
803
                    ['%identifier%' => $field->fieldDefIdentifier]
804
                );
805
            }
806
807
            if ($field->languageCode === null) {
808
                $field = $this->cloneField(
809
                    $field,
810
                    array('languageCode' => $contentCreateStruct->mainLanguageCode)
811
                );
812
            }
813
814 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...
815
                throw new ContentValidationException(
816
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
817
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
818
                );
819
            }
820
821
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
822
        }
823
824
        return $fields;
825
    }
826
827
    /**
828
     * Clones $field with overriding specific properties from given $overrides array.
829
     *
830
     * @param Field $field
831
     * @param array $overrides
832
     *
833
     * @return Field
834
     */
835
    private function cloneField(Field $field, array $overrides = array())
836
    {
837
        $fieldData = array_merge(
838
            array(
839
                'id' => $field->id,
840
                'value' => $field->value,
841
                'languageCode' => $field->languageCode,
842
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
843
            ),
844
            $overrides
845
        );
846
847
        return new Field($fieldData);
848
    }
849
850
    /**
851
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
852
     *
853
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
854
     *
855
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
856
     */
857
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
858
    {
859
        $spiLocationCreateStructs = array();
860
        $parentLocationIdSet = array();
861
        $mainLocation = true;
862
863
        foreach ($locationCreateStructs as $locationCreateStruct) {
864
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
865
                throw new InvalidArgumentException(
866
                    '$locationCreateStructs',
867
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
868
                );
869
            }
870
871
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
872
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
873
            }
874
875
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
876
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
877
            }
878
879
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
880
            $parentLocation = $this->repository->getLocationService()->loadLocation(
881
                $locationCreateStruct->parentLocationId
882
            );
883
884
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
885
                $locationCreateStruct,
886
                $parentLocation,
887
                $mainLocation,
888
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
889
                null,
890
                null
891
            );
892
893
            // First Location in the list will be created as main Location
894
            $mainLocation = false;
895
        }
896
897
        return $spiLocationCreateStructs;
898
    }
899
900
    /**
901
     * Updates the metadata.
902
     *
903
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
904
     *
905
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
906
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
907
     *
908
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
909
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
910
     *
911
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
912
     */
913
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
914
    {
915
        $propertyCount = 0;
916
        foreach ($contentMetadataUpdateStruct as $propertyName => $propertyValue) {
0 ignored issues
show
Bug introduced by
The expression $contentMetadataUpdateStruct of type object<eZ\Publish\API\Re...ntMetadataUpdateStruct> is not traversable.
Loading history...
917
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
918
                $propertyCount += 1;
919
            }
920
        }
921
        if ($propertyCount === 0) {
922
            throw new InvalidArgumentException(
923
                '$contentMetadataUpdateStruct',
924
                'At least one property must be set'
925
            );
926
        }
927
928
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
929
930
        if (!$this->repository->canUser('content', 'edit', $loadedContentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
931
            throw new UnauthorizedException('content', 'edit', array('contentId' => $loadedContentInfo->id));
932
        }
933
934
        if (isset($contentMetadataUpdateStruct->remoteId)) {
935
            try {
936
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
937
938
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
939
                    throw new InvalidArgumentException(
940
                        '$contentMetadataUpdateStruct',
941
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
942
                    );
943
                }
944
            } catch (APINotFoundException $e) {
945
                // Do nothing
946
            }
947
        }
948
949
        $this->repository->beginTransaction();
950
        try {
951
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
952
                $this->persistenceHandler->contentHandler()->updateMetadata(
953
                    $loadedContentInfo->id,
954
                    new SPIMetadataUpdateStruct(
955
                        array(
956
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
957
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
958
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
959
                                null,
960
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
961
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
962
                                null,
963
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
964
                                $this->repository->getContentLanguageService()->loadLanguage(
965
                                    $contentMetadataUpdateStruct->mainLanguageCode
966
                                )->id :
967
                                null,
968
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
969
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
970
                        )
971
                    )
972
                );
973
            }
974
975
            // Change main location
976
            if (isset($contentMetadataUpdateStruct->mainLocationId)
977
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
978
                $this->persistenceHandler->locationHandler()->changeMainLocation(
979
                    $loadedContentInfo->id,
980
                    $contentMetadataUpdateStruct->mainLocationId
981
                );
982
            }
983
984
            // Republish URL aliases to update always-available flag
985
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
986
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
987
                $content = $this->loadContent($loadedContentInfo->id);
988
                $this->publishUrlAliasesForContent($content, false);
989
            }
990
991
            $this->repository->commit();
992
        } catch (Exception $e) {
993
            $this->repository->rollback();
994
            throw $e;
995
        }
996
997
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
998
    }
999
1000
    /**
1001
     * Publishes URL aliases for all locations of a given content.
1002
     *
1003
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1004
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
1005
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
1006
     */
1007
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
1008
    {
1009
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
1010
        $locations = $this->repository->getLocationService()->loadLocations(
1011
            $content->getVersionInfo()->getContentInfo()
1012
        );
1013
        foreach ($locations as $location) {
1014 View Code Duplication
            foreach ($urlAliasNames as $languageCode => $name) {
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...
1015
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
1016
                    $location->id,
1017
                    $location->parentLocationId,
1018
                    $name,
1019
                    $languageCode,
1020
                    $content->contentInfo->alwaysAvailable,
1021
                    $updatePathIdentificationString ? $languageCode === $content->contentInfo->mainLanguageCode : false
1022
                );
1023
            }
1024
        }
1025
    }
1026
1027
    /**
1028
     * Deletes a content object including all its versions and locations including their subtrees.
1029
     *
1030
     * @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)
1031
     *
1032
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1033
     *
1034
     * @return mixed[] Affected Location Id's
1035
     */
1036
    public function deleteContent(ContentInfo $contentInfo)
1037
    {
1038
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1039
1040
        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...
1041
            throw new UnauthorizedException('content', 'remove', array('contentId' => $contentInfo->id));
1042
        }
1043
1044
        $affectedLocations = [];
1045
        $this->repository->beginTransaction();
1046
        try {
1047
            // Load Locations first as deleting Content also deletes belonging Locations
1048
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1049
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1050
            foreach ($spiLocations as $spiLocation) {
1051
                $this->persistenceHandler->urlAliasHandler()->locationDeleted($spiLocation->id);
1052
                $affectedLocations[] = $spiLocation->id;
1053
            }
1054
            $this->repository->commit();
1055
        } catch (Exception $e) {
1056
            $this->repository->rollback();
1057
            throw $e;
1058
        }
1059
1060
        return $affectedLocations;
1061
    }
1062
1063
    /**
1064
     * Creates a draft from a published or archived version.
1065
     *
1066
     * If no version is given, the current published version is used.
1067
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1068
     * It can be changed on updating the version.
1069
     *
1070
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1071
     *
1072
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1073
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1074
     * @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
1075
     *
1076
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1077
     */
1078
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1079
    {
1080
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1081
1082
        if ($versionInfo !== null) {
1083
            // Check that given $contentInfo and $versionInfo belong to the same content
1084
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1085
                throw new InvalidArgumentException(
1086
                    '$versionInfo',
1087
                    'VersionInfo does not belong to the same content as given ContentInfo'
1088
                );
1089
            }
1090
1091
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1092
1093
            switch ($versionInfo->status) {
1094
                case VersionInfo::STATUS_PUBLISHED:
1095
                case VersionInfo::STATUS_ARCHIVED:
1096
                    break;
1097
1098
                default:
1099
                    // @todo: throw an exception here, to be defined
1100
                    throw new BadStateException(
1101
                        '$versionInfo',
1102
                        'Draft can not be created from a draft version'
1103
                    );
1104
            }
1105
1106
            $versionNo = $versionInfo->versionNo;
1107
        } elseif ($contentInfo->published) {
1108
            $versionNo = $contentInfo->currentVersionNo;
1109
        } else {
1110
            // @todo: throw an exception here, to be defined
1111
            throw new BadStateException(
1112
                '$contentInfo',
1113
                'Content is not published, draft can be created only from published or archived version'
1114
            );
1115
        }
1116
1117
        if ($creator === null) {
1118
            $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...
1119
        }
1120
1121
        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...
1122
            throw new UnauthorizedException('content', 'edit', array('contentId' => $contentInfo->id));
1123
        }
1124
1125
        $this->repository->beginTransaction();
1126
        try {
1127
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1128
                $contentInfo->id,
1129
                $versionNo,
1130
                $creator->getUserId()
1131
            );
1132
            $this->repository->commit();
1133
        } catch (Exception $e) {
1134
            $this->repository->rollback();
1135
            throw $e;
1136
        }
1137
1138
        return $this->domainMapper->buildContentDomainObject($spiContent);
1139
    }
1140
1141
    /**
1142
     * Loads drafts for a user.
1143
     *
1144
     * If no user is given the drafts for the authenticated user a returned
1145
     *
1146
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list
1147
     *
1148
     * @param \eZ\Publish\API\Repository\Values\User\UserReference $user
1149
     *
1150
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo the drafts ({@link VersionInfo}) owned by the given user
1151
     */
1152
    public function loadContentDrafts(User $user = null)
1153
    {
1154
        if ($user === null) {
1155
            $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...
1156
        }
1157
1158
        // throw early if user has absolutely no access to versionread
1159
        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...
1160
            throw new UnauthorizedException('content', 'versionread');
1161
        }
1162
1163
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser($user->getUserId());
1164
        $versionInfoList = array();
1165 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...
1166
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1167
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1168
            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...
1169
                throw new UnauthorizedException('content', 'versionread', array('contentId' => $versionInfo->contentInfo->id));
1170
            }
1171
1172
            $versionInfoList[] = $versionInfo;
1173
        }
1174
1175
        return $versionInfoList;
1176
    }
1177
1178
    /**
1179
     * Translate a version.
1180
     *
1181
     * updates the destination version given in $translationInfo with the provided translated fields in $translationValues
1182
     *
1183
     * @example Examples/translation_5x.php
1184
     *
1185
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to update this version
1186
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the given destination version is not a draft
1187
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $translationValues is not valid, or if a required field is missing or is set to an empty value.
1188
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1189
     *                                                                          or value is set for non-translatable field in language
1190
     *                                                                          other than main.
1191
     *
1192
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1193
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationValues $translationValues
1194
     * @param \eZ\Publish\API\Repository\Values\User\User $modifier If set, this user is taken as modifier of the version
1195
     *
1196
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the translated fields
1197
     *
1198
     * @since 5.0
1199
     */
1200
    public function translateVersion(TranslationInfo $translationInfo, APITranslationValues $translationValues, User $modifier = null)
1201
    {
1202
        throw new NotImplementedException(__METHOD__);
1203
    }
1204
1205
    /**
1206
     * Updates the fields of a draft.
1207
     *
1208
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1209
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1210
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1211
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1212
     *                                                                               or if a required field is missing / set to an empty value.
1213
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1214
     *                                                                          or value is set for non-translatable field in language
1215
     *                                                                          other than main.
1216
     *
1217
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1218
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1219
     *
1220
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1221
     */
1222
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1223
    {
1224
        $contentUpdateStruct = clone $contentUpdateStruct;
1225
1226
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1227
        $content = $this->loadContent(
1228
            $versionInfo->getContentInfo()->id,
1229
            null,
1230
            $versionInfo->versionNo
1231
        );
1232
        if ($content->versionInfo->status !== APIVersionInfo::STATUS_DRAFT) {
1233
            throw new BadStateException(
1234
                '$versionInfo',
1235
                'Version is not a draft and can not be updated'
1236
            );
1237
        }
1238
1239
        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...
1240
            throw new UnauthorizedException('content', 'edit', array('contentId' => $content->id));
1241
        }
1242
1243
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1244
        if ($contentUpdateStruct->initialLanguageCode === null) {
1245
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1246
        }
1247
1248
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1249
        foreach ($allLanguageCodes as $languageCode) {
1250
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode($languageCode);
1251
        }
1252
1253
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1254
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1255
            $content->contentInfo->contentTypeId
1256
        );
1257
        $fields = $this->mapFieldsForUpdate(
1258
            $contentUpdateStruct,
1259
            $contentType,
1260
            $mainLanguageCode
1261
        );
1262
1263
        $fieldValues = array();
1264
        $spiFields = array();
1265
        $allFieldErrors = array();
1266
        $inputRelations = array();
1267
        $locationIdToContentIdMapping = array();
1268
1269
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1270
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1271
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1272
                $fieldDefinition->fieldTypeIdentifier
1273
            );
1274
1275
            foreach ($allLanguageCodes as $languageCode) {
1276
                $isCopied = $isEmpty = $isRetained = false;
1277
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1278
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1279
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1280
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1281
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1282
1283
                if (!$isFieldUpdated && !$isLanguageNew) {
1284
                    $isRetained = true;
1285
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1286
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1287
                    $isCopied = true;
1288
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1289
                } elseif ($isFieldUpdated) {
1290
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1291
                } else {
1292
                    $fieldValue = $fieldDefinition->defaultValue;
1293
                }
1294
1295
                $fieldValue = $fieldType->acceptValue($fieldValue);
1296
1297 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...
1298
                    $isEmpty = true;
1299
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1300
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1301
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1302
                            null,
1303
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1304
                            'empty'
1305
                        );
1306
                    }
1307
                } elseif ($isLanguageUpdated) {
1308
                    $fieldErrors = $fieldType->validate(
1309
                        $fieldDefinition,
1310
                        $fieldValue
1311
                    );
1312
                    if (!empty($fieldErrors)) {
1313
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1314
                    }
1315
                }
1316
1317
                if (!empty($allFieldErrors)) {
1318
                    continue;
1319
                }
1320
1321
                $this->relationProcessor->appendFieldRelations(
1322
                    $inputRelations,
1323
                    $locationIdToContentIdMapping,
1324
                    $fieldType,
1325
                    $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...
1326
                    $fieldDefinition->id
1327
                );
1328
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1329
1330
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1331
                    continue;
1332
                }
1333
1334
                $spiFields[] = new SPIField(
1335
                    array(
1336
                        'id' => $isLanguageNew ?
1337
                            null :
1338
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1339
                        'fieldDefinitionId' => $fieldDefinition->id,
1340
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1341
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1342
                        'languageCode' => $languageCode,
1343
                        'versionNo' => $versionInfo->versionNo,
1344
                    )
1345
                );
1346
            }
1347
        }
1348
1349
        if (!empty($allFieldErrors)) {
1350
            throw new ContentFieldValidationException($allFieldErrors);
1351
        }
1352
1353
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1354
            array(
1355
                'name' => $this->nameSchemaService->resolveNameSchema(
1356
                    $content,
1357
                    $fieldValues,
1358
                    $allLanguageCodes,
1359
                    $contentType
1360
                ),
1361
                '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...
1362
                'fields' => $spiFields,
1363
                'modificationDate' => time(),
1364
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1365
                    $contentUpdateStruct->initialLanguageCode
1366
                )->id,
1367
            )
1368
        );
1369
        $existingRelations = $this->loadRelations($versionInfo);
1370
1371
        $this->repository->beginTransaction();
1372
        try {
1373
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1374
                $versionInfo->getContentInfo()->id,
1375
                $versionInfo->versionNo,
1376
                $spiContentUpdateStruct
1377
            );
1378
            $this->relationProcessor->processFieldRelations(
1379
                $inputRelations,
1380
                $spiContent->versionInfo->contentInfo->id,
1381
                $spiContent->versionInfo->versionNo,
1382
                $contentType,
1383
                $existingRelations
1384
            );
1385
            $this->repository->commit();
1386
        } catch (Exception $e) {
1387
            $this->repository->rollback();
1388
            throw $e;
1389
        }
1390
1391
        return $this->domainMapper->buildContentDomainObject(
1392
            $spiContent,
1393
            $contentType
1394
        );
1395
    }
1396
1397
    /**
1398
     * Returns only updated language codes.
1399
     *
1400
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1401
     *
1402
     * @return array
1403
     */
1404 View Code Duplication
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1405
    {
1406
        $languageCodes = [
1407
            $contentUpdateStruct->initialLanguageCode => true,
1408
        ];
1409
1410
        foreach ($contentUpdateStruct->fields as $field) {
1411
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1412
                continue;
1413
            }
1414
1415
            $languageCodes[$field->languageCode] = true;
1416
        }
1417
1418
        return array_keys($languageCodes);
1419
    }
1420
1421
    /**
1422
     * Returns all language codes used in given $fields.
1423
     *
1424
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1425
     *
1426
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1427
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1428
     *
1429
     * @return array
1430
     */
1431
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1432
    {
1433
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1434
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1435
1436
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1437
        foreach ($updatedLanguageCodes as $languageCode) {
1438
            $languageCodes[$languageCode] = true;
1439
        }
1440
1441
        return array_keys($languageCodes);
1442
    }
1443
1444
    /**
1445
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1446
     *
1447
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1448
     *                                                                          or value is set for non-translatable field in language
1449
     *                                                                          other than main
1450
     *
1451
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1452
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1453
     * @param string $mainLanguageCode
1454
     *
1455
     * @return array
1456
     */
1457
    protected function mapFieldsForUpdate(
1458
        APIContentUpdateStruct $contentUpdateStruct,
1459
        ContentType $contentType,
1460
        $mainLanguageCode
1461
    ) {
1462
        $fields = array();
1463
1464
        foreach ($contentUpdateStruct->fields as $field) {
1465
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1466
1467
            if ($fieldDefinition === null) {
1468
                throw new ContentValidationException(
1469
                    "Field definition '%identifier%' does not exist in given ContentType",
1470
                    ['%identifier%' => $field->fieldDefIdentifier]
1471
                );
1472
            }
1473
1474
            if ($field->languageCode === null) {
1475
                if ($fieldDefinition->isTranslatable) {
1476
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1477
                } else {
1478
                    $languageCode = $mainLanguageCode;
1479
                }
1480
                $field = $this->cloneField($field, array('languageCode' => $languageCode));
1481
            }
1482
1483 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...
1484
                throw new ContentValidationException(
1485
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1486
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1487
                );
1488
            }
1489
1490
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1491
        }
1492
1493
        return $fields;
1494
    }
1495
1496
    /**
1497
     * Publishes a content version.
1498
     *
1499
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1500
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1501
     *
1502
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version
1503
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1504
     *
1505
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1506
     *
1507
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1508
     */
1509
    public function publishVersion(APIVersionInfo $versionInfo)
1510
    {
1511
        $content = $this->internalLoadContent(
1512
            $versionInfo->contentInfo->id,
1513
            null,
1514
            $versionInfo->versionNo
1515
        );
1516
1517
        if (!$content->getVersionInfo()->getContentInfo()->published) {
1518
            if (!$this->repository->canUser('content', 'create', $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...
1519
                throw new UnauthorizedException('content', 'create', array('contentId' => $content->id));
1520
            }
1521
        } elseif (!$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...
1522
            throw new UnauthorizedException('content', 'edit', array('contentId' => $content->id));
1523
        }
1524
1525
        $this->repository->beginTransaction();
1526
        try {
1527
            $content = $this->internalPublishVersion($content->getVersionInfo());
1528
            $this->repository->commit();
1529
        } catch (Exception $e) {
1530
            $this->repository->rollback();
1531
            throw $e;
1532
        }
1533
1534
        return $content;
1535
    }
1536
1537
    /**
1538
     * Publishes a content version.
1539
     *
1540
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1541
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1542
     *
1543
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1544
     *
1545
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1546
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1547
     *
1548
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1549
     */
1550
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1551
    {
1552
        if ($versionInfo->status !== APIVersionInfo::STATUS_DRAFT) {
1553
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1554
        }
1555
1556
        $currentTime = time();
1557
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1558
            $publicationDate = $currentTime;
1559
        }
1560
1561
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1562
        $metadataUpdateStruct->publicationDate = $publicationDate;
1563
        $metadataUpdateStruct->modificationDate = $currentTime;
1564
1565
        $contentId = $versionInfo->getContentInfo()->id;
1566
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1567
            $contentId,
1568
            $versionInfo->versionNo,
1569
            $metadataUpdateStruct
1570
        );
1571
1572
        $content = $this->domainMapper->buildContentDomainObject($spiContent);
1573
1574
        $this->publishUrlAliasesForContent($content);
1575
1576
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1577
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1578
            $contentId,
1579
            APIVersionInfo::STATUS_ARCHIVED,
1580
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1581
        );
1582
1583
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1584
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1585
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1586
            $archiveVersion = array_shift($archiveList);
1587
            $this->persistenceHandler->contentHandler()->deleteVersion(
1588
                $contentId,
1589
                $archiveVersion->versionNo
1590
            );
1591
        }
1592
1593
        return $content;
1594
    }
1595
1596
    /**
1597
     * Removes the given version.
1598
     *
1599
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1600
     *         published state or is the last version of the Content
1601
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1602
     *
1603
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1604
     */
1605
    public function deleteVersion(APIVersionInfo $versionInfo)
1606
    {
1607
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
1608
            throw new BadStateException(
1609
                '$versionInfo',
1610
                'Version is published and can not be removed'
1611
            );
1612
        }
1613
1614 View Code Duplication
        if (!$this->repository->canUser('content', 'versionremove', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1615
            throw new UnauthorizedException(
1616
                'content',
1617
                'versionremove',
1618
                array('contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo)
1619
            );
1620
        }
1621
1622
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1623
            $versionInfo->contentInfo->id,
1624
            null,
1625
            2
1626
        );
1627
1628
        if (count($versionList) === 1) {
1629
            throw new BadStateException(
1630
                '$versionInfo',
1631
                'Version is the last version of the Content and can not be removed'
1632
            );
1633
        }
1634
1635
        $this->repository->beginTransaction();
1636
        try {
1637
            $this->persistenceHandler->contentHandler()->deleteVersion(
1638
                $versionInfo->getContentInfo()->id,
1639
                $versionInfo->versionNo
1640
            );
1641
            $this->repository->commit();
1642
        } catch (Exception $e) {
1643
            $this->repository->rollback();
1644
            throw $e;
1645
        }
1646
    }
1647
1648
    /**
1649
     * Loads all versions for the given content.
1650
     *
1651
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1652
     *
1653
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1654
     *
1655
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1656
     */
1657
    public function loadVersions(ContentInfo $contentInfo)
1658
    {
1659
        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...
1660
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentInfo->id));
1661
        }
1662
1663
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1664
1665
        $versions = array();
1666 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...
1667
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1668
            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...
1669
                throw new UnauthorizedException('content', 'versionread', array('versionId' => $versionInfo->id));
1670
            }
1671
1672
            $versions[] = $versionInfo;
1673
        }
1674
1675
        return $versions;
1676
    }
1677
1678
    /**
1679
     * Copies the content to a new location. If no version is given,
1680
     * all versions are copied, otherwise only the given version.
1681
     *
1682
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1683
     *
1684
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1685
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1686
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1687
     *
1688
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1689
     */
1690
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1691
    {
1692
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1693
            $destinationLocationCreateStruct->parentLocationId
1694
        );
1695 View Code Duplication
        if (!$this->repository->canUser('content', 'create', $contentInfo, [$destinationLocation])) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1696
            throw new UnauthorizedException(
1697
                'content',
1698
                'create',
1699
                [
1700
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1701
                    'sectionId' => $contentInfo->sectionId,
1702
                ]
1703
            );
1704
        }
1705
1706
        $defaultObjectStates = $this->getDefaultObjectStates();
1707
1708
        $this->repository->beginTransaction();
1709
        try {
1710
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1711
                $contentInfo->id,
1712
                $versionInfo ? $versionInfo->versionNo : null,
1713
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
1714
            );
1715
1716
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1717
                $this->persistenceHandler->objectStateHandler()->setContentState(
1718
                    $spiContent->versionInfo->contentInfo->id,
1719
                    $objectStateGroupId,
1720
                    $objectState->id
1721
                );
1722
            }
1723
1724
            $content = $this->internalPublishVersion(
1725
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1726
                $spiContent->versionInfo->creationDate
1727
            );
1728
1729
            $this->repository->getLocationService()->createLocation(
1730
                $content->getVersionInfo()->getContentInfo(),
1731
                $destinationLocationCreateStruct
1732
            );
1733
            $this->repository->commit();
1734
        } catch (Exception $e) {
1735
            $this->repository->rollback();
1736
            throw $e;
1737
        }
1738
1739
        return $this->internalLoadContent($content->id);
1740
    }
1741
1742
    /**
1743
     * Loads all outgoing relations for the given version.
1744
     *
1745
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1746
     *
1747
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1748
     *
1749
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1750
     */
1751
    public function loadRelations(APIVersionInfo $versionInfo)
1752
    {
1753
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
1754
            $function = 'read';
1755
        } else {
1756
            $function = 'versionread';
1757
        }
1758
1759
        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...
1760
            throw new UnauthorizedException('content', $function);
1761
        }
1762
1763
        $contentInfo = $versionInfo->getContentInfo();
1764
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1765
            $contentInfo->id,
1766
            $versionInfo->versionNo
1767
        );
1768
1769
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1770
        $relations = array();
1771 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...
1772
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1773
            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...
1774
                continue;
1775
            }
1776
1777
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1778
                $spiRelation,
1779
                $contentInfo,
1780
                $destinationContentInfo
1781
            );
1782
        }
1783
1784
        return $relations;
1785
    }
1786
1787
    /**
1788
     * Loads all incoming relations for a content object.
1789
     *
1790
     * The relations come only from published versions of the source content objects
1791
     *
1792
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1793
     *
1794
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1795
     *
1796
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1797
     */
1798
    public function loadReverseRelations(ContentInfo $contentInfo)
1799
    {
1800
        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...
1801
            throw new UnauthorizedException('content', 'reverserelatedlist', array('contentId' => $contentInfo->id));
1802
        }
1803
1804
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1805
            $contentInfo->id
1806
        );
1807
1808
        $returnArray = array();
1809 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...
1810
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1811
            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...
1812
                continue;
1813
            }
1814
1815
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1816
                $spiRelation,
1817
                $sourceContentInfo,
1818
                $contentInfo
1819
            );
1820
        }
1821
1822
        return $returnArray;
1823
    }
1824
1825
    /**
1826
     * Adds a relation of type common.
1827
     *
1828
     * The source of the relation is the content and version
1829
     * referenced by $versionInfo.
1830
     *
1831
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1832
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1833
     *
1834
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1835
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1836
     *
1837
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1838
     */
1839
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1840
    {
1841
        $sourceVersion = $this->loadVersionInfoById(
1842
            $sourceVersion->contentInfo->id,
1843
            $sourceVersion->versionNo
1844
        );
1845
1846
        if ($sourceVersion->status !== APIVersionInfo::STATUS_DRAFT) {
1847
            throw new BadStateException(
1848
                '$sourceVersion',
1849
                'Relations of type common can only be added to versions of status draft'
1850
            );
1851
        }
1852
1853
        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...
1854
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1855
        }
1856
1857
        $sourceContentInfo = $sourceVersion->getContentInfo();
1858
1859
        $this->repository->beginTransaction();
1860
        try {
1861
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1862
                new SPIRelationCreateStruct(
1863
                    array(
1864
                        'sourceContentId' => $sourceContentInfo->id,
1865
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1866
                        'sourceFieldDefinitionId' => null,
1867
                        'destinationContentId' => $destinationContent->id,
1868
                        'type' => APIRelation::COMMON,
1869
                    )
1870
                )
1871
            );
1872
            $this->repository->commit();
1873
        } catch (Exception $e) {
1874
            $this->repository->rollback();
1875
            throw $e;
1876
        }
1877
1878
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1879
    }
1880
1881
    /**
1882
     * Removes a relation of type COMMON from a draft.
1883
     *
1884
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1885
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1886
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1887
     *
1888
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1889
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1890
     */
1891
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1892
    {
1893
        $sourceVersion = $this->loadVersionInfoById(
1894
            $sourceVersion->contentInfo->id,
1895
            $sourceVersion->versionNo
1896
        );
1897
1898
        if ($sourceVersion->status !== APIVersionInfo::STATUS_DRAFT) {
1899
            throw new BadStateException(
1900
                '$sourceVersion',
1901
                'Relations of type common can only be removed from versions of status draft'
1902
            );
1903
        }
1904
1905
        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...
1906
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1907
        }
1908
1909
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1910
            $sourceVersion->getContentInfo()->id,
1911
            $sourceVersion->versionNo,
1912
            APIRelation::COMMON
1913
        );
1914
1915
        if (empty($spiRelations)) {
1916
            throw new InvalidArgumentException(
1917
                '$sourceVersion',
1918
                'There are no relations of type COMMON for the given destination'
1919
            );
1920
        }
1921
1922
        // there should be only one relation of type COMMON for each destination,
1923
        // but in case there were ever more then one, we will remove them all
1924
        // @todo: alternatively, throw BadStateException?
1925
        $this->repository->beginTransaction();
1926
        try {
1927
            foreach ($spiRelations as $spiRelation) {
1928
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1929
                    $this->persistenceHandler->contentHandler()->removeRelation(
1930
                        $spiRelation->id,
1931
                        APIRelation::COMMON
1932
                    );
1933
                }
1934
            }
1935
            $this->repository->commit();
1936
        } catch (Exception $e) {
1937
            $this->repository->rollback();
1938
            throw $e;
1939
        }
1940
    }
1941
1942
    /**
1943
     * Adds translation information to the content object.
1944
     *
1945
     * @example Examples/translation_5x.php
1946
     *
1947
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed add a translation info
1948
     *
1949
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1950
     *
1951
     * @since 5.0
1952
     */
1953
    public function addTranslationInfo(TranslationInfo $translationInfo)
1954
    {
1955
        throw new NotImplementedException(__METHOD__);
1956
    }
1957
1958
    /**
1959
     * lists the translations done on this content object.
1960
     *
1961
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed read translation infos
1962
     *
1963
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1964
     * @param array $filter
1965
     *
1966
     * @todo TBD - filter by source version destination version and languages
1967
     *
1968
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo[]
1969
     *
1970
     * @since 5.0
1971
     */
1972
    public function loadTranslationInfos(ContentInfo $contentInfo, array $filter = array())
1973
    {
1974
        throw new NotImplementedException(__METHOD__);
1975
    }
1976
1977
    /**
1978
     * Instantiates a new content create struct object.
1979
     *
1980
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
1981
     *
1982
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1983
     * @param string $mainLanguageCode
1984
     *
1985
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
1986
     */
1987
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
1988
    {
1989
        return new ContentCreateStruct(
1990
            array(
1991
                'contentType' => $contentType,
1992
                'mainLanguageCode' => $mainLanguageCode,
1993
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
1994
            )
1995
        );
1996
    }
1997
1998
    /**
1999
     * Instantiates a new content meta data update struct.
2000
     *
2001
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2002
     */
2003
    public function newContentMetadataUpdateStruct()
2004
    {
2005
        return new ContentMetadataUpdateStruct();
2006
    }
2007
2008
    /**
2009
     * Instantiates a new content update struct.
2010
     *
2011
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2012
     */
2013
    public function newContentUpdateStruct()
2014
    {
2015
        return new ContentUpdateStruct();
2016
    }
2017
2018
    /**
2019
     * Instantiates a new TranslationInfo object.
2020
     *
2021
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo
2022
     */
2023
    public function newTranslationInfo()
2024
    {
2025
        return new TranslationInfo();
2026
    }
2027
2028
    /**
2029
     * Instantiates a Translation object.
2030
     *
2031
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationValues
2032
     */
2033
    public function newTranslationValues()
2034
    {
2035
        return new TranslationValues();
2036
    }
2037
}
2038