Completed
Push — 7.0_deprecation_removal ( e8d685...a29025 )
by André
11:11
created

ContentService::mapFieldsForCreate()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 33
Code Lines 18

Duplication

Lines 6
Ratio 18.18 %

Importance

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

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

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

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

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

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

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

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

Loading history...
1572
            throw new UnauthorizedException(
1573
                'content',
1574
                'create',
1575
                [
1576
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1577
                    'sectionId' => $contentInfo->sectionId,
1578
                ]
1579
            );
1580
        }
1581
1582
        $defaultObjectStates = $this->getDefaultObjectStates();
1583
1584
        $this->repository->beginTransaction();
1585
        try {
1586
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1587
                $contentInfo->id,
1588
                $versionInfo ? $versionInfo->versionNo : null
1589
            );
1590
1591
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1592
                $this->persistenceHandler->objectStateHandler()->setContentState(
1593
                    $spiContent->versionInfo->contentInfo->id,
1594
                    $objectStateGroupId,
1595
                    $objectState->id
1596
                );
1597
            }
1598
1599
            $content = $this->internalPublishVersion(
1600
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1601
                $spiContent->versionInfo->creationDate
1602
            );
1603
1604
            $this->repository->getLocationService()->createLocation(
1605
                $content->getVersionInfo()->getContentInfo(),
1606
                $destinationLocationCreateStruct
1607
            );
1608
            $this->repository->commit();
1609
        } catch (Exception $e) {
1610
            $this->repository->rollback();
1611
            throw $e;
1612
        }
1613
1614
        return $this->internalLoadContent($content->id);
1615
    }
1616
1617
    /**
1618
     * Loads all outgoing relations for the given version.
1619
     *
1620
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1621
     *
1622
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1623
     *
1624
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1625
     */
1626
    public function loadRelations(APIVersionInfo $versionInfo)
1627
    {
1628
        if ($versionInfo->isPublished()) {
1629
            $function = 'read';
1630
        } else {
1631
            $function = 'versionread';
1632
        }
1633
1634
        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...
1635
            throw new UnauthorizedException('content', $function);
1636
        }
1637
1638
        $contentInfo = $versionInfo->getContentInfo();
1639
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1640
            $contentInfo->id,
1641
            $versionInfo->versionNo
1642
        );
1643
1644
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1645
        $relations = array();
1646 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...
1647
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1648
            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...
1649
                continue;
1650
            }
1651
1652
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1653
                $spiRelation,
1654
                $contentInfo,
1655
                $destinationContentInfo
1656
            );
1657
        }
1658
1659
        return $relations;
1660
    }
1661
1662
    /**
1663
     * Loads all incoming relations for a content object.
1664
     *
1665
     * The relations come only from published versions of the source content objects
1666
     *
1667
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1668
     *
1669
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1670
     *
1671
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1672
     */
1673
    public function loadReverseRelations(ContentInfo $contentInfo)
1674
    {
1675
        if (!$this->repository->canUser('content', 'reverserelatedlist', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1676
            throw new UnauthorizedException('content', 'reverserelatedlist', array('contentId' => $contentInfo->id));
1677
        }
1678
1679
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1680
            $contentInfo->id
1681
        );
1682
1683
        $returnArray = array();
1684 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...
1685
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1686
            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...
1687
                continue;
1688
            }
1689
1690
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1691
                $spiRelation,
1692
                $sourceContentInfo,
1693
                $contentInfo
1694
            );
1695
        }
1696
1697
        return $returnArray;
1698
    }
1699
1700
    /**
1701
     * Adds a relation of type common.
1702
     *
1703
     * The source of the relation is the content and version
1704
     * referenced by $versionInfo.
1705
     *
1706
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1707
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1708
     *
1709
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1710
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1711
     *
1712
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1713
     */
1714
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1715
    {
1716
        $sourceVersion = $this->loadVersionInfoById(
1717
            $sourceVersion->contentInfo->id,
1718
            $sourceVersion->versionNo
1719
        );
1720
1721
        if (!$sourceVersion->isDraft()) {
1722
            throw new BadStateException(
1723
                '$sourceVersion',
1724
                'Relations of type common can only be added to versions of status draft'
1725
            );
1726
        }
1727
1728
        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...
1729
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1730
        }
1731
1732
        $sourceContentInfo = $sourceVersion->getContentInfo();
1733
1734
        $this->repository->beginTransaction();
1735
        try {
1736
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1737
                new SPIRelationCreateStruct(
1738
                    array(
1739
                        'sourceContentId' => $sourceContentInfo->id,
1740
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1741
                        'sourceFieldDefinitionId' => null,
1742
                        'destinationContentId' => $destinationContent->id,
1743
                        'type' => APIRelation::COMMON,
1744
                    )
1745
                )
1746
            );
1747
            $this->repository->commit();
1748
        } catch (Exception $e) {
1749
            $this->repository->rollback();
1750
            throw $e;
1751
        }
1752
1753
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1754
    }
1755
1756
    /**
1757
     * Removes a relation of type COMMON from a draft.
1758
     *
1759
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1760
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1761
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1762
     *
1763
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1764
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1765
     */
1766
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1767
    {
1768
        $sourceVersion = $this->loadVersionInfoById(
1769
            $sourceVersion->contentInfo->id,
1770
            $sourceVersion->versionNo
1771
        );
1772
1773
        if (!$sourceVersion->isDraft()) {
1774
            throw new BadStateException(
1775
                '$sourceVersion',
1776
                'Relations of type common can only be removed from versions of status draft'
1777
            );
1778
        }
1779
1780
        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...
1781
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1782
        }
1783
1784
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1785
            $sourceVersion->getContentInfo()->id,
1786
            $sourceVersion->versionNo,
1787
            APIRelation::COMMON
1788
        );
1789
1790
        if (empty($spiRelations)) {
1791
            throw new InvalidArgumentException(
1792
                '$sourceVersion',
1793
                'There are no relations of type COMMON for the given destination'
1794
            );
1795
        }
1796
1797
        // there should be only one relation of type COMMON for each destination,
1798
        // but in case there were ever more then one, we will remove them all
1799
        // @todo: alternatively, throw BadStateException?
1800
        $this->repository->beginTransaction();
1801
        try {
1802
            foreach ($spiRelations as $spiRelation) {
1803
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1804
                    $this->persistenceHandler->contentHandler()->removeRelation(
1805
                        $spiRelation->id,
1806
                        APIRelation::COMMON
1807
                    );
1808
                }
1809
            }
1810
            $this->repository->commit();
1811
        } catch (Exception $e) {
1812
            $this->repository->rollback();
1813
            throw $e;
1814
        }
1815
    }
1816
1817
    /**
1818
     * {@inheritdoc}
1819
     */
1820
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
1821
    {
1822
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1823
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
1824
            E_USER_DEPRECATED
1825
        );
1826
        $this->deleteTranslation($contentInfo, $languageCode);
1827
    }
1828
1829
    /**
1830
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
1831
     *
1832
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
1833
     *
1834
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
1835
     *         is the Main Translation of a Content Item.
1836
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
1837
     *         to delete the content (in one of the locations of the given Content Item).
1838
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
1839
     *         is invalid for the given content.
1840
     *
1841
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1842
     * @param string $languageCode
1843
     *
1844
     * @since 6.13
1845
     */
1846
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
1847
    {
1848
        if ($contentInfo->mainLanguageCode === $languageCode) {
1849
            throw new BadStateException(
1850
                '$languageCode',
1851
                'Specified translation is the main translation of the Content Object'
1852
            );
1853
        }
1854
1855
        $translationWasFound = false;
1856
        $this->repository->beginTransaction();
1857
        try {
1858
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
1859
                if (!$this->repository->canUser('content', 'remove', $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1860
                    throw new UnauthorizedException(
1861
                        'content',
1862
                        'remove',
1863
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1864
                    );
1865
                }
1866
1867
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
1868
                    continue;
1869
                }
1870
1871
                $translationWasFound = true;
1872
1873
                // If the translation is the version's only one, delete the version
1874
                if (count($versionInfo->languageCodes) < 2) {
1875
                    $this->persistenceHandler->contentHandler()->deleteVersion(
1876
                        $versionInfo->getContentInfo()->id,
1877
                        $versionInfo->versionNo
1878
                    );
1879
                }
1880
            }
1881
1882
            if (!$translationWasFound) {
1883
                throw new InvalidArgumentException(
1884
                    '$languageCode',
1885
                    sprintf(
1886
                        '%s does not exist in the Content item(id=%d)',
1887
                        $languageCode,
1888
                        $contentInfo->id
1889
                    )
1890
                );
1891
            }
1892
1893
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
1894
                $contentInfo->id,
1895
                $languageCode
1896
            );
1897
            $locationIds = array_map(
1898
                function (Location $location) {
1899
                    return $location->id;
0 ignored issues
show
Documentation introduced by
The property $id is declared protected in eZ\Publish\API\Repository\Values\Content\Location. 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...
1900
                },
1901
                $this->repository->getLocationService()->loadLocations($contentInfo)
1902
            );
1903
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
1904
                $locationIds,
1905
                $languageCode
1906
            );
1907
            $this->repository->commit();
1908
        } catch (InvalidArgumentException $e) {
1909
            $this->repository->rollback();
1910
            throw $e;
1911
        } catch (BadStateException $e) {
1912
            $this->repository->rollback();
1913
            throw $e;
1914
        } catch (UnauthorizedException $e) {
1915
            $this->repository->rollback();
1916
            throw $e;
1917
        } catch (Exception $e) {
1918
            $this->repository->rollback();
1919
            // cover generic unexpected exception to fulfill API promise on @throws
1920
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
1921
        }
1922
    }
1923
1924
    /**
1925
     * Delete specified Translation from a Content Draft.
1926
     *
1927
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
1928
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
1929
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
1930
     *         to edit the Content (in one of the locations of the given Content Object).
1931
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
1932
     *         is invalid for the given Draft.
1933
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
1934
     *
1935
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
1936
     * @param string $languageCode Language code of the Translation to be removed
1937
     *
1938
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
1939
     *
1940
     * @since 6.12
1941
     */
1942
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
1943
    {
1944
        if (!$versionInfo->isDraft()) {
1945
            throw new BadStateException(
1946
                '$versionInfo',
1947
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
1948
            );
1949
        }
1950
1951
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
1952
            throw new BadStateException(
1953
                '$languageCode',
1954
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
1955
            );
1956
        }
1957
1958
        if (!$this->repository->canUser('content', 'edit', $versionInfo->contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1959
            throw new UnauthorizedException(
1960
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
1961
            );
1962
        }
1963
1964
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
1965
            throw new InvalidArgumentException(
1966
                '$languageCode',
1967
                sprintf(
1968
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
1969
                    $versionInfo->contentInfo->id,
1970
                    $versionInfo->versionNo,
1971
                    $languageCode
1972
                )
1973
            );
1974
        }
1975
1976
        if (count($versionInfo->languageCodes) === 1) {
1977
            throw new BadStateException(
1978
                '$languageCode',
1979
                'Specified Translation is the only one Content Object Version has'
1980
            );
1981
        }
1982
1983
        $this->repository->beginTransaction();
1984
        try {
1985
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
1986
                $versionInfo->contentInfo->id,
1987
                $versionInfo->versionNo,
1988
                $languageCode
1989
            );
1990
            $this->repository->commit();
1991
1992
            return $this->domainMapper->buildContentDomainObject($spiContent);
1993
        } catch (APINotFoundException $e) {
1994
            // avoid wrapping expected NotFoundException in BadStateException handled below
1995
            $this->repository->rollback();
1996
            throw $e;
1997
        } catch (Exception $e) {
1998
            $this->repository->rollback();
1999
            // cover generic unexpected exception to fulfill API promise on @throws
2000
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2001
        }
2002
    }
2003
2004
    /**
2005
     * Instantiates a new content create struct object.
2006
     *
2007
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2008
     *
2009
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2010
     * @param string $mainLanguageCode
2011
     *
2012
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2013
     */
2014
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2015
    {
2016
        return new ContentCreateStruct(
2017
            array(
2018
                'contentType' => $contentType,
2019
                'mainLanguageCode' => $mainLanguageCode,
2020
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2021
            )
2022
        );
2023
    }
2024
2025
    /**
2026
     * Instantiates a new content meta data update struct.
2027
     *
2028
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2029
     */
2030
    public function newContentMetadataUpdateStruct()
2031
    {
2032
        return new ContentMetadataUpdateStruct();
2033
    }
2034
2035
    /**
2036
     * Instantiates a new content update struct.
2037
     *
2038
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2039
     */
2040
    public function newContentUpdateStruct()
2041
    {
2042
        return new ContentUpdateStruct();
2043
    }
2044
}
2045