Completed
Push — master ( 4339a8...5a694b )
by André
70:16 queued 52:05
created

ContentService::getUpdatedLanguageCodes()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 16
Ratio 100 %

Importance

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