Completed
Push — api_possible_changes ( b6d217 )
by André
14:23
created

loadContentItemsByContentInfoList()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\ContentService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\ContentService as ContentServiceInterface;
12
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
13
use eZ\Publish\SPI\Persistence\Handler;
14
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct as APIContentUpdateStruct;
15
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
16
use eZ\Publish\API\Repository\Values\Content\TranslationInfo;
17
use eZ\Publish\API\Repository\Values\Content\TranslationValues as APITranslationValues;
18
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
19
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
20
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
21
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
22
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
23
use eZ\Publish\API\Repository\Values\User\User;
24
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
25
use eZ\Publish\API\Repository\Values\Content\Field;
26
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
27
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
28
use eZ\Publish\Core\Base\Exceptions\BadStateException;
29
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
30
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
31
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
32
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
33
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
34
use eZ\Publish\Core\FieldType\ValidationError;
35
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
36
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
37
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
38
use eZ\Publish\Core\Repository\Values\Content\TranslationValues;
39
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
40
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
41
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
42
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
43
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
44
use Exception;
45
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
46
47
/**
48
 * This class provides service methods for managing content.
49
 *
50
 * @example Examples/content.php
51
 */
52
class ContentService implements ContentServiceInterface
53
{
54
    /**
55
     * @var \eZ\Publish\Core\Repository\Repository
56
     */
57
    protected $repository;
58
59
    /**
60
     * @var \eZ\Publish\SPI\Persistence\Handler
61
     */
62
    protected $persistenceHandler;
63
64
    /**
65
     * @var array
66
     */
67
    protected $settings;
68
69
    /**
70
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
71
     */
72
    protected $domainMapper;
73
74
    /**
75
     * @var \eZ\Publish\Core\Repository\Helper\RelationProcessor
76
     */
77
    protected $relationProcessor;
78
79
    /**
80
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
81
     */
82
    protected $nameSchemaService;
83
84
    /**
85
     * @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry
86
     */
87
    protected $fieldTypeRegistry;
88
89
    /**
90
     * Setups service with reference to repository object that created it & corresponding handler.
91
     *
92
     * @param \eZ\Publish\API\Repository\Repository $repository
93
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
94
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
95
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
96
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
97
     * @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...
98
     * @param array $settings
99
     */
100
    public function __construct(
101
        RepositoryInterface $repository,
102
        Handler $handler,
103
        Helper\DomainMapper $domainMapper,
104
        Helper\RelationProcessor $relationProcessor,
105
        Helper\NameSchemaService $nameSchemaService,
106
        Helper\FieldTypeRegistry $fieldTypeRegistry,
107
        array $settings = array()
108
    ) {
109
        $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...
110
        $this->persistenceHandler = $handler;
111
        $this->domainMapper = $domainMapper;
112
        $this->relationProcessor = $relationProcessor;
113
        $this->nameSchemaService = $nameSchemaService;
114
        $this->fieldTypeRegistry = $fieldTypeRegistry;
115
        // Union makes sure default settings are ignored if provided in argument
116
        $this->settings = $settings + array(
117
            // Version archive limit (0-50), only enforced on publish, not on un-publish.
118
            'default_version_archive_limit' => 5,
119
        );
120
    }
121
122
    /**
123
     * Loads a content info object.
124
     *
125
     * To load fields use loadContent
126
     *
127
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
128
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
129
     *
130
     * @param int $contentId
131
     *
132
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
133
     */
134 View Code Duplication
    public function loadContentInfo($contentId)
135
    {
136
        $contentInfo = $this->internalLoadContentInfo($contentId);
137
        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...
138
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
139
        }
140
141
        return $contentInfo;
142
    }
143
144
    /**
145
     * Loads a content info object.
146
     *
147
     * To load fields use loadContent
148
     *
149
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
150
     *
151
     * @param mixed $id
152
     * @param bool $isRemoteId
153
     *
154
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
155
     */
156
    public function internalLoadContentInfo($id, $isRemoteId = false)
157
    {
158
        try {
159
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
160
161
            return $this->domainMapper->buildContentInfoDomainObject(
162
                $this->persistenceHandler->contentHandler()->$method($id)
163
            );
164
        } catch (APINotFoundException $e) {
165
            throw new NotFoundException(
166
                'Content',
167
                $id,
168
                $e
169
            );
170
        }
171
    }
172
173
    /**
174
     * Loads a content info object for the given remoteId.
175
     *
176
     * To load fields use loadContent
177
     *
178
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
179
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
180
     *
181
     * @param string $remoteId
182
     *
183
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
184
     */
185 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
186
    {
187
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
188
189
        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...
190
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
191
        }
192
193
        return $contentInfo;
194
    }
195
196
    /**
197
     * Loads a version info of the given content object.
198
     *
199
     * If no version number is given, the method returns the current version
200
     *
201
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
202
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
203
     *
204
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
205
     * @param int $versionNo the version number. If not given the current version is returned.
206
     *
207
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
208
     */
209
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
210
    {
211
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
212
    }
213
214
    /**
215
     * Loads a version info of the given content object id.
216
     *
217
     * If no version number is given, the method returns the current version
218
     *
219
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
220
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
221
     *
222
     * @param mixed $contentId
223
     * @param int $versionNo the version number. If not given the current version is returned.
224
     *
225
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
226
     */
227
    public function loadVersionInfoById($contentId, $versionNo = null)
228
    {
229
        if ($versionNo === null) {
230
            $versionNo = $this->loadContentInfo($contentId)->currentVersionNo;
231
        }
232
233
        try {
234
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
235
                $contentId,
236
                $versionNo
237
            );
238
        } catch (APINotFoundException $e) {
239
            throw new NotFoundException(
240
                'VersionInfo',
241
                array(
242
                    'contentId' => $contentId,
243
                    'versionNo' => $versionNo,
244
                ),
245
                $e
246
            );
247
        }
248
249
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
250
251
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
252
            $function = 'read';
253
        } else {
254
            $function = 'versionread';
255
        }
256
257
        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...
258
            throw new UnauthorizedException('content', $function, array('contentId' => $contentId));
259
        }
260
261
        return $versionInfo;
262
    }
263
264
    /**
265
     * Loads content in a version for the given content info object.
266
     *
267
     * If no version number is given, the method returns the current version
268
     *
269
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if version with the given number does not exist
270
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
271
     *
272
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
273
     * @param array $languages A language filter for fields. If not given all languages are returned
274
     * @param int $versionNo the version number. If not given the current version is returned
275
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
276
     *
277
     * @return \eZ\Publish\API\Repository\Values\Content\Content
278
     */
279
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
280
    {
281
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
282
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
283
            $useAlwaysAvailable = false;
284
        }
285
286
        return $this->loadContent(
287
            $contentInfo->id,
288
            $languages,
289
            $versionNo,
290
            $useAlwaysAvailable
291
        );
292
    }
293
294
    /**
295
     * Loads content items in a version for a set of given content info objects.
296
     *
297
     * @uses ::loadContentByContentInfo()
298
     *
299
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
300
     * @param array $languages A language filter for fields. If not given all languages are returned
301
     *
302
     * @return \eZ\Publish\API\Repository\Values\Content\Content[]
303
     */
304
    public function loadContentItemsByContentInfoList(array $contentInfoList, array $languages = null)
305
    {
306
        foreach ($contentInfoList as $contentInfo) {
307
            yield $this->loadContentByContentInfo($contentInfo, $languages);
308
        }
309
    }
310
311
    /**
312
     * Loads content in the version given by version info.
313
     *
314
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
315
     *
316
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
317
     * @param array $languages A language filter for fields. If not given all languages are returned
318
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
319
     *
320
     * @return \eZ\Publish\API\Repository\Values\Content\Content
321
     */
322
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
323
    {
324
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
325
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
326
            $useAlwaysAvailable = false;
327
        }
328
329
        return $this->loadContent(
330
            $versionInfo->getContentInfo()->id,
331
            $languages,
332
            $versionInfo->versionNo,
333
            $useAlwaysAvailable
334
        );
335
    }
336
337
    /**
338
     * Loads content in a version of the given content object.
339
     *
340
     * If no version number is given, the method returns the current version
341
     *
342
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
343
     * @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
344
     *
345
     * @param int $contentId
346
     * @param array|null $languages A language filter for fields. If not given all languages are returned
347
     * @param int|null $versionNo the version number. If not given the current version is returned
348
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
349
     *
350
     * @return \eZ\Publish\API\Repository\Values\Content\Content
351
     */
352 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
353
    {
354
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
355
356
        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...
357
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
358
        }
359
360
        if (
361
            $content->getVersionInfo()->status !== APIVersionInfo::STATUS_PUBLISHED
362
            && !$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...
363
        ) {
364
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentId, 'versionNo' => $versionNo));
365
        }
366
367
        return $content;
368
    }
369
370
    /**
371
     * Loads content in a version of the given content object.
372
     *
373
     * If no version number is given, the method returns the current version
374
     *
375
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
376
     *
377
     * @param mixed $id
378
     * @param array|null $languages A language filter for fields. If not given all languages are returned
379
     * @param int|null $versionNo the version number. If not given the current version is returned
380
     * @param bool $isRemoteId
381
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
382
     *
383
     * @return \eZ\Publish\API\Repository\Values\Content\Content
384
     */
385
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
386
    {
387
        try {
388
            // Get Content ID if lookup by remote ID
389
            if ($isRemoteId) {
390
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
391
                $id = $spiContentInfo->id;
392
            }
393
394
            // Get current version if $versionNo is not defined
395
            if ($versionNo === null) {
396
                if (!isset($spiContentInfo)) {
397
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
398
                }
399
400
                $versionNo = $spiContentInfo->currentVersionNo;
401
            }
402
403
            $loadLanguages = $languages;
404
            $alwaysAvailableLanguageCode = null;
405
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
406
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
407
                if (!isset($spiContentInfo)) {
408
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
409
                }
410
411
                if ($spiContentInfo->alwaysAvailable) {
412
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
413
                    $loadLanguages = array_unique($loadLanguages);
414
                }
415
            }
416
417
            $spiContent = $this->persistenceHandler->contentHandler()->load(
418
                $id,
419
                $versionNo,
420
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 403 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...
421
            );
422
        } catch (APINotFoundException $e) {
423
            throw new NotFoundException(
424
                'Content',
425
                array(
426
                    $isRemoteId ? 'remoteId' : 'id' => $id,
427
                    'languages' => $languages,
428
                    'versionNo' => $versionNo,
429
                ),
430
                $e
431
            );
432
        }
433
434
        return $this->domainMapper->buildContentDomainObject(
435
            $spiContent,
436
            null,
437
            empty($languages) ? null : $languages,
438
            $alwaysAvailableLanguageCode
439
        );
440
    }
441
442
    /**
443
     * Loads content in a version for the content object reference by the given remote id.
444
     *
445
     * If no version is given, the method returns the current version
446
     *
447
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
448
     * @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
449
     *
450
     * @param string $remoteId
451
     * @param array $languages A language filter for fields. If not given all languages are returned
452
     * @param int $versionNo the version number. If not given the current version is returned
453
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
454
     *
455
     * @return \eZ\Publish\API\Repository\Values\Content\Content
456
     */
457 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
458
    {
459
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
460
461
        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...
462
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
463
        }
464
465
        if (
466
            $content->getVersionInfo()->status !== APIVersionInfo::STATUS_PUBLISHED
467
            && !$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...
468
        ) {
469
            throw new UnauthorizedException('content', 'versionread', array('remoteId' => $remoteId, 'versionNo' => $versionNo));
470
        }
471
472
        return $content;
473
    }
474
475
    /**
476
     * Creates a new content draft assigned to the authenticated user.
477
     *
478
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
479
     * but this required special rights for the authenticated user
480
     * (this is useful for content staging where the transfer process does not
481
     * have to authenticate with the user which created the content object in the source server).
482
     * The user has to publish the draft if it should be visible.
483
     * In 4.x at least one location has to be provided in the location creation array.
484
     *
485
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
486
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
487
     *                                                                        struct are missing or invalid, or if multiple locations are under the
488
     *                                                                        same parent.
489
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
490
     *                                                                               or if a required field is missing / set to an empty value.
491
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
492
     *                                                                          or value is set for non-translatable field in language
493
     *                                                                          other than main.
494
     *
495
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
496
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
497
     *
498
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
499
     */
500
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = array())
501
    {
502
        if ($contentCreateStruct->mainLanguageCode === null) {
503
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
504
        }
505
506
        if ($contentCreateStruct->contentType === null) {
507
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
508
        }
509
510
        $contentCreateStruct = clone $contentCreateStruct;
511
512
        if ($contentCreateStruct->ownerId === null) {
513
            $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...
514
        }
515
516
        if ($contentCreateStruct->alwaysAvailable === null) {
517
            $contentCreateStruct->alwaysAvailable = false;
518
        }
519
520
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
521
            $contentCreateStruct->contentType->id
522
        );
523
524
        if (empty($contentCreateStruct->sectionId)) {
525
            if (isset($locationCreateStructs[0])) {
526
                $location = $this->repository->getLocationService()->loadLocation(
527
                    $locationCreateStructs[0]->parentLocationId
528
                );
529
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
530
            } else {
531
                $contentCreateStruct->sectionId = 1;
532
            }
533
        }
534
535
        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...
536
            throw new UnauthorizedException(
537
                'content',
538
                'create',
539
                array(
540
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
541
                            $locationCreateStructs[0]->parentLocationId :
542
                            null,
543
                    'sectionId' => $contentCreateStruct->sectionId,
544
                )
545
            );
546
        }
547
548
        if (!empty($contentCreateStruct->remoteId)) {
549
            try {
550
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
551
552
                throw new InvalidArgumentException(
553
                    '$contentCreateStruct',
554
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
555
                );
556
            } catch (APINotFoundException $e) {
557
                // Do nothing
558
            }
559
        } else {
560
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
561
        }
562
563
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
0 ignored issues
show
Documentation introduced by
$locationCreateStructs is of type array<integer,object<eZ\...tionCreateStruct>|null>, but the function expects a array<integer,object<eZ\...\LocationCreateStruct>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
564
565
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
566
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
567
568
        $fieldValues = array();
569
        $spiFields = array();
570
        $allFieldErrors = array();
571
        $inputRelations = array();
572
        $locationIdToContentIdMapping = array();
573
574
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
575
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
576
            $fieldType = $this->fieldTypeRegistry->getFieldType(
577
                $fieldDefinition->fieldTypeIdentifier
578
            );
579
580
            foreach ($languageCodes as $languageCode) {
581
                $isEmptyValue = false;
582
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
583
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
584
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
585
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
586
                } else {
587
                    $fieldValue = $fieldDefinition->defaultValue;
0 ignored issues
show
Documentation introduced by
The property $defaultValue is declared protected in eZ\Publish\API\Repositor...entType\FieldDefinition. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
588
                }
589
590
                $fieldValue = $fieldType->acceptValue($fieldValue);
591
592 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...
593
                    $isEmptyValue = true;
594
                    if ($fieldDefinition->isRequired) {
595
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
596
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
597
                            null,
598
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
599
                            'empty'
600
                        );
601
                    }
602
                } else {
603
                    $fieldErrors = $fieldType->validate(
604
                        $fieldDefinition,
605
                        $fieldValue
606
                    );
607
                    if (!empty($fieldErrors)) {
608
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
609
                    }
610
                }
611
612
                if (!empty($allFieldErrors)) {
613
                    continue;
614
                }
615
616
                $this->relationProcessor->appendFieldRelations(
617
                    $inputRelations,
618
                    $locationIdToContentIdMapping,
619
                    $fieldType,
620
                    $fieldValue,
621
                    $fieldDefinition->id
622
                );
623
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
624
625
                // Only non-empty value for: translatable field or in main language
626
                if (
627
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
628
                    (!$isEmptyValue && $isLanguageMain)
629
                ) {
630
                    $spiFields[] = new SPIField(
631
                        array(
632
                            'id' => null,
633
                            'fieldDefinitionId' => $fieldDefinition->id,
634
                            'type' => $fieldDefinition->fieldTypeIdentifier,
635
                            'value' => $fieldType->toPersistenceValue($fieldValue),
636
                            'languageCode' => $languageCode,
637
                            'versionNo' => null,
638
                        )
639
                    );
640
                }
641
            }
642
        }
643
644
        if (!empty($allFieldErrors)) {
645
            throw new ContentFieldValidationException($allFieldErrors);
646
        }
647
648
        $spiContentCreateStruct = new SPIContentCreateStruct(
649
            array(
650
                'name' => $this->nameSchemaService->resolve(
651
                    $contentCreateStruct->contentType->nameSchema,
652
                    $contentCreateStruct->contentType,
653
                    $fieldValues,
654
                    $languageCodes
655
                ),
656
                'typeId' => $contentCreateStruct->contentType->id,
657
                'sectionId' => $contentCreateStruct->sectionId,
658
                'ownerId' => $contentCreateStruct->ownerId,
659
                'locations' => $spiLocationCreateStructs,
660
                'fields' => $spiFields,
661
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
662
                'remoteId' => $contentCreateStruct->remoteId,
663
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
664
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
665
                    $contentCreateStruct->mainLanguageCode
666
                )->id,
667
            )
668
        );
669
670
        $defaultObjectStates = $this->getDefaultObjectStates();
671
672
        $this->repository->beginTransaction();
673
        try {
674
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
675
            $this->relationProcessor->processFieldRelations(
676
                $inputRelations,
677
                $spiContent->versionInfo->contentInfo->id,
678
                $spiContent->versionInfo->versionNo,
679
                $contentCreateStruct->contentType
680
            );
681
682
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
683
                $this->persistenceHandler->objectStateHandler()->setContentState(
684
                    $spiContent->versionInfo->contentInfo->id,
685
                    $objectStateGroupId,
686
                    $objectState->id
687
                );
688
            }
689
690
            $this->repository->commit();
691
        } catch (Exception $e) {
692
            $this->repository->rollback();
693
            throw $e;
694
        }
695
696
        return $this->domainMapper->buildContentDomainObject($spiContent);
697
    }
698
699
    /**
700
     * Returns an array of default content states with content state group id as key.
701
     *
702
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
703
     */
704
    protected function getDefaultObjectStates()
705
    {
706
        $defaultObjectStatesMap = array();
707
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
708
709
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
710
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
711
                // Only register the first object state which is the default one.
712
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
713
                break;
714
            }
715
        }
716
717
        return $defaultObjectStatesMap;
718
    }
719
720
    /**
721
     * Returns all language codes used in given $fields.
722
     *
723
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
724
     *
725
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
726
     *
727
     * @return string[]
728
     */
729
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
730
    {
731
        $languageCodes = array();
732
733 View Code Duplication
        foreach ($contentCreateStruct->fields as $field) {
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...
734
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
735
                continue;
736
            }
737
738
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
739
                $field->languageCode
740
            );
741
            $languageCodes[$field->languageCode] = true;
742
        }
743
744
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
745
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
746
                $contentCreateStruct->mainLanguageCode
747
            );
748
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
749
        }
750
751
        return array_keys($languageCodes);
752
    }
753
754
    /**
755
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
756
     *
757
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
758
     *                                                                          or value is set for non-translatable field in language
759
     *                                                                          other than main
760
     *
761
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
762
     *
763
     * @return array
764
     */
765
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
766
    {
767
        $fields = array();
768
769
        foreach ($contentCreateStruct->fields as $field) {
770
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
771
772
            if ($fieldDefinition === null) {
773
                throw new ContentValidationException(
774
                    "Field definition '%identifier%' does not exist in given ContentType",
775
                    ['%identifier%' => $field->fieldDefIdentifier]
776
                );
777
            }
778
779
            if ($field->languageCode === null) {
780
                $field = $this->cloneField(
781
                    $field,
782
                    array('languageCode' => $contentCreateStruct->mainLanguageCode)
783
                );
784
            }
785
786 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...
787
                throw new ContentValidationException(
788
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
789
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
790
                );
791
            }
792
793
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
794
        }
795
796
        return $fields;
797
    }
798
799
    /**
800
     * Clones $field with overriding specific properties from given $overrides array.
801
     *
802
     * @param Field $field
803
     * @param array $overrides
804
     *
805
     * @return Field
806
     */
807
    private function cloneField(Field $field, array $overrides = array())
808
    {
809
        $fieldData = array_merge(
810
            array(
811
                'id' => $field->id,
812
                'value' => $field->value,
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
813
                'languageCode' => $field->languageCode,
814
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
815
            ),
816
            $overrides
817
        );
818
819
        return new Field($fieldData);
820
    }
821
822
    /**
823
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
824
     *
825
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
826
     *
827
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
828
     */
829
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
830
    {
831
        $spiLocationCreateStructs = array();
832
        $parentLocationIdSet = array();
833
        $mainLocation = true;
834
835
        foreach ($locationCreateStructs as $locationCreateStruct) {
836
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
837
                throw new InvalidArgumentException(
838
                    '$locationCreateStructs',
839
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
840
                );
841
            }
842
843
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
844
            $parentLocation = $this->repository->getLocationService()->loadLocation(
845
                $locationCreateStruct->parentLocationId
846
            );
847
848
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
849
                $locationCreateStruct,
850
                $parentLocation,
851
                $mainLocation,
852
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
853
                null,
854
                null
855
            );
856
857
            // First Location in the list will be created as main Location
858
            $mainLocation = false;
859
        }
860
861
        return $spiLocationCreateStructs;
862
    }
863
864
    /**
865
     * Updates the metadata.
866
     *
867
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
868
     *
869
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
870
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
871
     *
872
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
873
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
874
     *
875
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
876
     */
877
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
878
    {
879
        $propertyCount = 0;
880
        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...
881
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
882
                $propertyCount += 1;
883
            }
884
        }
885
        if ($propertyCount === 0) {
886
            throw new InvalidArgumentException(
887
                '$contentMetadataUpdateStruct',
888
                'At least one property must be set'
889
            );
890
        }
891
892
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
893
894
        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...
895
            throw new UnauthorizedException('content', 'edit', array('contentId' => $loadedContentInfo->id));
896
        }
897
898
        if (isset($contentMetadataUpdateStruct->remoteId)) {
899
            try {
900
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
901
902
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
903
                    throw new InvalidArgumentException(
904
                        '$contentMetadataUpdateStruct',
905
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
906
                    );
907
                }
908
            } catch (APINotFoundException $e) {
909
                // Do nothing
910
            }
911
        }
912
913
        $this->repository->beginTransaction();
914
        try {
915
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
916
                $this->persistenceHandler->contentHandler()->updateMetadata(
917
                    $loadedContentInfo->id,
918
                    new SPIMetadataUpdateStruct(
919
                        array(
920
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
921
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
922
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
923
                                null,
924
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
925
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
926
                                null,
927
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
928
                                $this->repository->getContentLanguageService()->loadLanguage(
929
                                    $contentMetadataUpdateStruct->mainLanguageCode
930
                                )->id :
931
                                null,
932
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
933
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
934
                        )
935
                    )
936
                );
937
            }
938
939
            // Change main location
940
            if (isset($contentMetadataUpdateStruct->mainLocationId)
941
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
942
                $this->persistenceHandler->locationHandler()->changeMainLocation(
943
                    $loadedContentInfo->id,
944
                    $contentMetadataUpdateStruct->mainLocationId
945
                );
946
            }
947
948
            // Republish URL aliases to update always-available flag
949
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
950
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
951
                $content = $this->loadContent($loadedContentInfo->id);
952
                $this->publishUrlAliasesForContent($content, false);
953
            }
954
955
            $this->repository->commit();
956
        } catch (Exception $e) {
957
            $this->repository->rollback();
958
            throw $e;
959
        }
960
961
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
962
    }
963
964
    /**
965
     * Publishes URL aliases for all locations of a given content.
966
     *
967
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
968
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
969
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
970
     */
971
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
972
    {
973
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
974
        $locations = $this->repository->getLocationService()->loadLocations(
975
            $content->getVersionInfo()->getContentInfo()
976
        );
977
        foreach ($locations as $location) {
978
            foreach ($urlAliasNames as $languageCode => $name) {
979
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
980
                    $location->id,
981
                    $location->parentLocationId,
982
                    $name,
983
                    $languageCode,
984
                    $content->contentInfo->alwaysAvailable,
985
                    $updatePathIdentificationString ? $languageCode === $content->contentInfo->mainLanguageCode : false
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $languageCode (integer) and $content->contentInfo->mainLanguageCode (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
986
                );
987
            }
988
        }
989
    }
990
991
    /**
992
     * Deletes a content object including all its versions and locations including their subtrees.
993
     *
994
     * @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)
995
     *
996
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
997
     *
998
     * @return mixed[] Affected Location Id's
999
     */
1000
    public function deleteContent(ContentInfo $contentInfo)
1001
    {
1002
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1003
1004
        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...
1005
            throw new UnauthorizedException('content', 'remove', array('contentId' => $contentInfo->id));
1006
        }
1007
1008
        $affectedLocations = [];
1009
        $this->repository->beginTransaction();
1010
        try {
1011
            // Load Locations first as deleting Content also deletes belonging Locations
1012
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1013
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1014
            foreach ($spiLocations as $spiLocation) {
1015
                $this->persistenceHandler->urlAliasHandler()->locationDeleted($spiLocation->id);
1016
                $affectedLocations[] = $spiLocation->id;
1017
            }
1018
            $this->repository->commit();
1019
        } catch (Exception $e) {
1020
            $this->repository->rollback();
1021
            throw $e;
1022
        }
1023
1024
        return $affectedLocations;
1025
    }
1026
1027
    /**
1028
     * Creates a draft from a published or archived version.
1029
     *
1030
     * If no version is given, the current published version is used.
1031
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1032
     * It can be changed on updating the version.
1033
     *
1034
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1035
     *
1036
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1037
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1038
     * @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
1039
     *
1040
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1041
     */
1042
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1043
    {
1044
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1045
1046
        if ($versionInfo !== null) {
1047
            // Check that given $contentInfo and $versionInfo belong to the same content
1048
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1049
                throw new InvalidArgumentException(
1050
                    '$versionInfo',
1051
                    'VersionInfo does not belong to the same content as given ContentInfo'
1052
                );
1053
            }
1054
1055
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1056
1057
            switch ($versionInfo->status) {
1058
                case VersionInfo::STATUS_PUBLISHED:
1059
                case VersionInfo::STATUS_ARCHIVED:
1060
                    break;
1061
1062
                default:
1063
                    // @todo: throw an exception here, to be defined
1064
                    throw new BadStateException(
1065
                        '$versionInfo',
1066
                        'Draft can not be created from a draft version'
1067
                    );
1068
            }
1069
1070
            $versionNo = $versionInfo->versionNo;
1071
        } elseif ($contentInfo->published) {
1072
            $versionNo = $contentInfo->currentVersionNo;
1073
        } else {
1074
            // @todo: throw an exception here, to be defined
1075
            throw new BadStateException(
1076
                '$contentInfo',
1077
                'Content is not published, draft can be created only from published or archived version'
1078
            );
1079
        }
1080
1081
        if ($creator === null) {
1082
            $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...
1083
        }
1084
1085
        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...
1086
            throw new UnauthorizedException('content', 'edit', array('contentId' => $contentInfo->id));
1087
        }
1088
1089
        $this->repository->beginTransaction();
1090
        try {
1091
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1092
                $contentInfo->id,
1093
                $versionNo,
1094
                $creator->getUserId()
1095
            );
1096
            $this->repository->commit();
1097
        } catch (Exception $e) {
1098
            $this->repository->rollback();
1099
            throw $e;
1100
        }
1101
1102
        return $this->domainMapper->buildContentDomainObject($spiContent);
1103
    }
1104
1105
    /**
1106
     * Loads drafts for a user.
1107
     *
1108
     * If no user is given the drafts for the authenticated user a returned
1109
     *
1110
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list
1111
     *
1112
     * @param \eZ\Publish\API\Repository\Values\User\UserReference $user
1113
     *
1114
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo the drafts ({@link VersionInfo}) owned by the given user
1115
     */
1116
    public function loadContentDrafts(User $user = null)
1117
    {
1118
        if ($user === null) {
1119
            $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...
1120
        }
1121
1122
        // throw early if user has absolutely no access to versionread
1123
        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...
1124
            throw new UnauthorizedException('content', 'versionread');
1125
        }
1126
1127
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser($user->getUserId());
1128
        $versionInfoList = array();
1129 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...
1130
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1131
            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...
1132
                throw new UnauthorizedException('content', 'versionread', array('contentId' => $versionInfo->contentInfo->id));
1133
            }
1134
1135
            $versionInfoList[] = $versionInfo;
1136
        }
1137
1138
        return $versionInfoList;
1139
    }
1140
1141
    /**
1142
     * Translate a version.
1143
     *
1144
     * updates the destination version given in $translationInfo with the provided translated fields in $translationValues
1145
     *
1146
     * @example Examples/translation_5x.php
1147
     *
1148
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to update this version
1149
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the given destination version is not a draft
1150
     * @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.
1151
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1152
     *                                                                          or value is set for non-translatable field in language
1153
     *                                                                          other than main.
1154
     *
1155
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1156
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationValues $translationValues
1157
     * @param \eZ\Publish\API\Repository\Values\User\User $modifier If set, this user is taken as modifier of the version
1158
     *
1159
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the translated fields
1160
     *
1161
     * @since 5.0
1162
     */
1163
    public function translateVersion(TranslationInfo $translationInfo, APITranslationValues $translationValues, User $modifier = null)
1164
    {
1165
        throw new NotImplementedException(__METHOD__);
1166
    }
1167
1168
    /**
1169
     * Updates the fields of a draft.
1170
     *
1171
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1172
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1173
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1174
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1175
     *                                                                               or if a required field is missing / set to an empty value.
1176
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1177
     *                                                                          or value is set for non-translatable field in language
1178
     *                                                                          other than main.
1179
     *
1180
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1181
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1182
     *
1183
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1184
     */
1185
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1186
    {
1187
        $contentUpdateStruct = clone $contentUpdateStruct;
1188
1189
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1190
        $content = $this->loadContent(
1191
            $versionInfo->getContentInfo()->id,
1192
            null,
1193
            $versionInfo->versionNo
1194
        );
1195
        if ($content->versionInfo->status !== APIVersionInfo::STATUS_DRAFT) {
1196
            throw new BadStateException(
1197
                '$versionInfo',
1198
                'Version is not a draft and can not be updated'
1199
            );
1200
        }
1201
1202
        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...
1203
            throw new UnauthorizedException('content', 'edit', array('contentId' => $content->id));
1204
        }
1205
1206
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1207
        $languageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1208
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1209
            $content->contentInfo->contentTypeId
1210
        );
1211
        $fields = $this->mapFieldsForUpdate(
1212
            $contentUpdateStruct,
1213
            $contentType,
1214
            $mainLanguageCode
1215
        );
1216
1217
        $fieldValues = array();
1218
        $spiFields = array();
1219
        $allFieldErrors = array();
1220
        $inputRelations = array();
1221
        $locationIdToContentIdMapping = array();
1222
1223
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1224
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1225
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1226
                $fieldDefinition->fieldTypeIdentifier
1227
            );
1228
1229
            foreach ($languageCodes as $languageCode) {
1230
                $isCopied = $isEmpty = $isRetained = false;
1231
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1232
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1233
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1234
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1235
1236
                if (!$isFieldUpdated && !$isLanguageNew) {
1237
                    $isRetained = true;
1238
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1239
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1240
                    $isCopied = true;
1241
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1242
                } elseif ($isFieldUpdated) {
1243
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1244
                } else {
1245
                    $fieldValue = $fieldDefinition->defaultValue;
0 ignored issues
show
Documentation introduced by
The property $defaultValue is declared protected in eZ\Publish\API\Repositor...entType\FieldDefinition. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

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

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

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

Loading history...
1461
            throw new UnauthorizedException('content', 'publish', array('contentId' => $content->id));
1462
        }
1463
1464
        $this->repository->beginTransaction();
1465
        try {
1466
            $content = $this->internalPublishVersion($content->getVersionInfo());
1467
            $this->repository->commit();
1468
        } catch (Exception $e) {
1469
            $this->repository->rollback();
1470
            throw $e;
1471
        }
1472
1473
        return $content;
1474
    }
1475
1476
    /**
1477
     * Publishes a content version.
1478
     *
1479
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1480
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1481
     *
1482
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1483
     *
1484
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1485
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1486
     *
1487
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1488
     */
1489
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1490
    {
1491
        if ($versionInfo->status !== APIVersionInfo::STATUS_DRAFT) {
1492
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1493
        }
1494
1495
        $currentTime = time();
1496
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1497
            $publicationDate = $currentTime;
1498
        }
1499
1500
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1501
        $metadataUpdateStruct->publicationDate = $publicationDate;
1502
        $metadataUpdateStruct->modificationDate = $currentTime;
1503
1504
        $contentId = $versionInfo->getContentInfo()->id;
1505
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1506
            $contentId,
1507
            $versionInfo->versionNo,
1508
            $metadataUpdateStruct
1509
        );
1510
1511
        $content = $this->domainMapper->buildContentDomainObject($spiContent);
1512
1513
        $this->publishUrlAliasesForContent($content);
1514
1515
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1516
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1517
            $contentId,
1518
            APIVersionInfo::STATUS_ARCHIVED,
1519
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1520
        );
1521
1522
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1523
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1524
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1525
            $archiveVersion = array_shift($archiveList);
1526
            $this->persistenceHandler->contentHandler()->deleteVersion(
1527
                $contentId,
1528
                $archiveVersion->versionNo
1529
            );
1530
        }
1531
1532
        return $content;
1533
    }
1534
1535
    /**
1536
     * Removes the given version.
1537
     *
1538
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1539
     *         published state or is the last version of the Content
1540
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1541
     *
1542
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1543
     */
1544
    public function deleteVersion(APIVersionInfo $versionInfo)
1545
    {
1546
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
1547
            throw new BadStateException(
1548
                '$versionInfo',
1549
                'Version is published and can not be removed'
1550
            );
1551
        }
1552
1553 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...
1554
            throw new UnauthorizedException(
1555
                'content',
1556
                'versionremove',
1557
                array('contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo)
1558
            );
1559
        }
1560
1561
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1562
            $versionInfo->contentInfo->id,
1563
            null,
1564
            2
1565
        );
1566
1567
        if (count($versionList) === 1) {
1568
            throw new BadStateException(
1569
                '$versionInfo',
1570
                'Version is the last version of the Content and can not be removed'
1571
            );
1572
        }
1573
1574
        $this->repository->beginTransaction();
1575
        try {
1576
            $this->persistenceHandler->contentHandler()->deleteVersion(
1577
                $versionInfo->getContentInfo()->id,
1578
                $versionInfo->versionNo
1579
            );
1580
            $this->repository->commit();
1581
        } catch (Exception $e) {
1582
            $this->repository->rollback();
1583
            throw $e;
1584
        }
1585
    }
1586
1587
    /**
1588
     * Loads all versions for the given content.
1589
     *
1590
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1591
     *
1592
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1593
     *
1594
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1595
     */
1596
    public function loadVersions(ContentInfo $contentInfo)
1597
    {
1598
        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...
1599
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentInfo->id));
1600
        }
1601
1602
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1603
1604
        $versions = array();
1605 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...
1606
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1607
            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...
1608
                throw new UnauthorizedException('content', 'versionread', array('versionId' => $versionInfo->id));
1609
            }
1610
1611
            $versions[] = $versionInfo;
1612
        }
1613
1614
        return $versions;
1615
    }
1616
1617
    /**
1618
     * Copies the content to a new location. If no version is given,
1619
     * all versions are copied, otherwise only the given version.
1620
     *
1621
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1622
     *
1623
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1624
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1625
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1626
     *
1627
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1628
     */
1629
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1630
    {
1631 View Code Duplication
        if (!$this->repository->canUser('content', 'create', $contentInfo, $destinationLocationCreateStruct)) {
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...
1632
            throw new UnauthorizedException(
1633
                'content',
1634
                'create',
1635
                array(
1636
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1637
                    'sectionId' => $contentInfo->sectionId,
1638
                )
1639
            );
1640
        }
1641
1642
        $defaultObjectStates = $this->getDefaultObjectStates();
1643
1644
        $this->repository->beginTransaction();
1645
        try {
1646
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1647
                $contentInfo->id,
1648
                $versionInfo ? $versionInfo->versionNo : null
1649
            );
1650
1651
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1652
                $this->persistenceHandler->objectStateHandler()->setContentState(
1653
                    $spiContent->versionInfo->contentInfo->id,
1654
                    $objectStateGroupId,
1655
                    $objectState->id
1656
                );
1657
            }
1658
1659
            $content = $this->internalPublishVersion(
1660
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1661
                $spiContent->versionInfo->creationDate
1662
            );
1663
1664
            $this->repository->getLocationService()->createLocation(
1665
                $content->getVersionInfo()->getContentInfo(),
1666
                $destinationLocationCreateStruct
1667
            );
1668
            $this->repository->commit();
1669
        } catch (Exception $e) {
1670
            $this->repository->rollback();
1671
            throw $e;
1672
        }
1673
1674
        return $this->internalLoadContent($content->id);
1675
    }
1676
1677
    /**
1678
     * Loads all outgoing relations for the given version.
1679
     *
1680
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1681
     *
1682
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1683
     *
1684
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1685
     */
1686
    public function loadRelations(APIVersionInfo $versionInfo)
1687
    {
1688
        if ($versionInfo->status === APIVersionInfo::STATUS_PUBLISHED) {
1689
            $function = 'read';
1690
        } else {
1691
            $function = 'versionread';
1692
        }
1693
1694
        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...
1695
            throw new UnauthorizedException('content', $function);
1696
        }
1697
1698
        $contentInfo = $versionInfo->getContentInfo();
1699
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1700
            $contentInfo->id,
1701
            $versionInfo->versionNo
1702
        );
1703
1704
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1705
        $relations = array();
1706 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...
1707
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1708
            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...
1709
                continue;
1710
            }
1711
1712
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1713
                $spiRelation,
1714
                $contentInfo,
1715
                $destinationContentInfo
1716
            );
1717
        }
1718
1719
        return $relations;
1720
    }
1721
1722
    /**
1723
     * Loads all incoming relations for a content object.
1724
     *
1725
     * The relations come only from published versions of the source content objects
1726
     *
1727
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1728
     *
1729
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1730
     *
1731
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1732
     */
1733
    public function loadReverseRelations(ContentInfo $contentInfo)
1734
    {
1735
        if ($this->repository->hasAccess('content', 'reverserelatedlist') !== true) {
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...
1736
            throw new UnauthorizedException('content', 'reverserelatedlist', array('contentId' => $contentInfo->id));
1737
        }
1738
1739
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1740
            $contentInfo->id
1741
        );
1742
1743
        $returnArray = array();
1744 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...
1745
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1746
            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...
1747
                continue;
1748
            }
1749
1750
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1751
                $spiRelation,
1752
                $sourceContentInfo,
1753
                $contentInfo
1754
            );
1755
        }
1756
1757
        return $returnArray;
1758
    }
1759
1760
    /**
1761
     * Adds a relation of type common.
1762
     *
1763
     * The source of the relation is the content and version
1764
     * referenced by $versionInfo.
1765
     *
1766
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1767
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1768
     *
1769
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1770
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1771
     *
1772
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1773
     */
1774
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1775
    {
1776
        $sourceVersion = $this->loadVersionInfoById(
1777
            $sourceVersion->contentInfo->id,
1778
            $sourceVersion->versionNo
1779
        );
1780
1781
        if ($sourceVersion->status !== APIVersionInfo::STATUS_DRAFT) {
1782
            throw new BadStateException(
1783
                '$sourceVersion',
1784
                'Relations of type common can only be added to versions of status draft'
1785
            );
1786
        }
1787
1788
        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...
1789
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1790
        }
1791
1792
        $sourceContentInfo = $sourceVersion->getContentInfo();
1793
1794
        $this->repository->beginTransaction();
1795
        try {
1796
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1797
                new SPIRelationCreateStruct(
1798
                    array(
1799
                        'sourceContentId' => $sourceContentInfo->id,
1800
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1801
                        'sourceFieldDefinitionId' => null,
1802
                        'destinationContentId' => $destinationContent->id,
1803
                        'type' => APIRelation::COMMON,
1804
                    )
1805
                )
1806
            );
1807
            $this->repository->commit();
1808
        } catch (Exception $e) {
1809
            $this->repository->rollback();
1810
            throw $e;
1811
        }
1812
1813
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1814
    }
1815
1816
    /**
1817
     * Removes a relation of type COMMON from a draft.
1818
     *
1819
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1820
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1821
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1822
     *
1823
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1824
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1825
     */
1826
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1827
    {
1828
        $sourceVersion = $this->loadVersionInfoById(
1829
            $sourceVersion->contentInfo->id,
1830
            $sourceVersion->versionNo
1831
        );
1832
1833
        if ($sourceVersion->status !== APIVersionInfo::STATUS_DRAFT) {
1834
            throw new BadStateException(
1835
                '$sourceVersion',
1836
                'Relations of type common can only be removed from versions of status draft'
1837
            );
1838
        }
1839
1840
        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...
1841
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1842
        }
1843
1844
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1845
            $sourceVersion->getContentInfo()->id,
1846
            $sourceVersion->versionNo,
1847
            APIRelation::COMMON
1848
        );
1849
1850
        if (empty($spiRelations)) {
1851
            throw new InvalidArgumentException(
1852
                '$sourceVersion',
1853
                'There are no relations of type COMMON for the given destination'
1854
            );
1855
        }
1856
1857
        // there should be only one relation of type COMMON for each destination,
1858
        // but in case there were ever more then one, we will remove them all
1859
        // @todo: alternatively, throw BadStateException?
1860
        $this->repository->beginTransaction();
1861
        try {
1862
            foreach ($spiRelations as $spiRelation) {
1863
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1864
                    $this->persistenceHandler->contentHandler()->removeRelation(
1865
                        $spiRelation->id,
1866
                        APIRelation::COMMON
1867
                    );
1868
                }
1869
            }
1870
            $this->repository->commit();
1871
        } catch (Exception $e) {
1872
            $this->repository->rollback();
1873
            throw $e;
1874
        }
1875
    }
1876
1877
    /**
1878
     * Adds translation information to the content object.
1879
     *
1880
     * @example Examples/translation_5x.php
1881
     *
1882
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed add a translation info
1883
     *
1884
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1885
     *
1886
     * @since 5.0
1887
     */
1888
    public function addTranslationInfo(TranslationInfo $translationInfo)
1889
    {
1890
        throw new NotImplementedException(__METHOD__);
1891
    }
1892
1893
    /**
1894
     * lists the translations done on this content object.
1895
     *
1896
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed read translation infos
1897
     *
1898
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1899
     * @param array $filter
1900
     *
1901
     * @todo TBD - filter by source version destination version and languages
1902
     *
1903
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo[]
1904
     *
1905
     * @since 5.0
1906
     */
1907
    public function loadTranslationInfos(ContentInfo $contentInfo, array $filter = array())
1908
    {
1909
        throw new NotImplementedException(__METHOD__);
1910
    }
1911
1912
    /**
1913
     * Instantiates a new content create struct object.
1914
     *
1915
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
1916
     *
1917
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1918
     * @param string $mainLanguageCode
1919
     *
1920
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
1921
     */
1922
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
1923
    {
1924
        return new ContentCreateStruct(
1925
            array(
1926
                'contentType' => $contentType,
1927
                'mainLanguageCode' => $mainLanguageCode,
1928
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
1929
            )
1930
        );
1931
    }
1932
1933
    /**
1934
     * Instantiates a new content meta data update struct.
1935
     *
1936
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
1937
     */
1938
    public function newContentMetadataUpdateStruct()
1939
    {
1940
        return new ContentMetadataUpdateStruct();
1941
    }
1942
1943
    /**
1944
     * Instantiates a new content update struct.
1945
     *
1946
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
1947
     */
1948
    public function newContentUpdateStruct()
1949
    {
1950
        return new ContentUpdateStruct();
1951
    }
1952
1953
    /**
1954
     * Instantiates a new TranslationInfo object.
1955
     *
1956
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo
1957
     */
1958
    public function newTranslationInfo()
1959
    {
1960
        return new TranslationInfo();
1961
    }
1962
1963
    /**
1964
     * Instantiates a Translation object.
1965
     *
1966
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationValues
1967
     */
1968
    public function newTranslationValues()
1969
    {
1970
        return new TranslationValues();
1971
    }
1972
}
1973