Completed
Push — master ( 2e0639...5db28d )
by
unknown
35:20 queued 16:31
created

ContentService::loadContent()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 16
Ratio 100 %

Importance

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

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

Loading history...
136
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
137
        }
138
139
        return $contentInfo;
140
    }
141
142
    /**
143
     * Loads a content info object.
144
     *
145
     * To load fields use loadContent
146
     *
147
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given id does not exist
148
     *
149
     * @param mixed $id
150
     * @param bool $isRemoteId
151
     *
152
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
153
     */
154
    public function internalLoadContentInfo($id, $isRemoteId = false)
155
    {
156
        try {
157
            $method = $isRemoteId ? 'loadContentInfoByRemoteId' : 'loadContentInfo';
158
159
            return $this->domainMapper->buildContentInfoDomainObject(
160
                $this->persistenceHandler->contentHandler()->$method($id)
161
            );
162
        } catch (APINotFoundException $e) {
163
            throw new NotFoundException(
164
                'Content',
165
                $id,
166
                $e
167
            );
168
        }
169
    }
170
171
    /**
172
     * Loads a content info object for the given remoteId.
173
     *
174
     * To load fields use loadContent
175
     *
176
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content
177
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist
178
     *
179
     * @param string $remoteId
180
     *
181
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
182
     */
183 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
184
    {
185
        $contentInfo = $this->internalLoadContentInfo($remoteId, true);
186
187
        if (!$this->repository->canUser('content', 'read', $contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
188
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
189
        }
190
191
        return $contentInfo;
192
    }
193
194
    /**
195
     * Loads a version info of the given content object.
196
     *
197
     * If no version number is given, the method returns the current version
198
     *
199
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
200
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
201
     *
202
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
203
     * @param int $versionNo the version number. If not given the current version is returned.
204
     *
205
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
206
     */
207
    public function loadVersionInfo(ContentInfo $contentInfo, $versionNo = null)
208
    {
209
        return $this->loadVersionInfoById($contentInfo->id, $versionNo);
210
    }
211
212
    /**
213
     * Loads a version info of the given content object id.
214
     *
215
     * If no version number is given, the method returns the current version
216
     *
217
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the version with the given number does not exist
218
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version
219
     *
220
     * @param mixed $contentId
221
     * @param int $versionNo the version number. If not given the current version is returned.
222
     *
223
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo
224
     */
225
    public function loadVersionInfoById($contentId, $versionNo = null)
226
    {
227
        if ($versionNo === null) {
228
            $versionNo = $this->loadContentInfo($contentId)->currentVersionNo;
229
        }
230
231
        try {
232
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
233
                $contentId,
234
                $versionNo
235
            );
236
        } catch (APINotFoundException $e) {
237
            throw new NotFoundException(
238
                'VersionInfo',
239
                array(
240
                    'contentId' => $contentId,
241
                    'versionNo' => $versionNo,
242
                ),
243
                $e
244
            );
245
        }
246
247
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
248
249
        if ($versionInfo->isPublished()) {
250
            $function = 'read';
251
        } else {
252
            $function = 'versionread';
253
        }
254
255
        if (!$this->repository->canUser('content', $function, $versionInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
256
            throw new UnauthorizedException('content', $function, array('contentId' => $contentId));
257
        }
258
259
        return $versionInfo;
260
    }
261
262
    /**
263
     * {@inheritdoc}
264
     */
265
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
266
    {
267
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
268
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
269
            $useAlwaysAvailable = false;
270
        }
271
272
        // As we have content info we can avoid that current version is looked up using spi in loadContent() if not set
273
        if ($versionNo === null) {
274
            $versionNo = $contentInfo->currentVersionNo;
275
        }
276
277
        return $this->loadContent(
278
            $contentInfo->id,
279
            $languages,
280
            $versionNo,
281
            $useAlwaysAvailable
282
        );
283
    }
284
285
    /**
286
     * {@inheritdoc}
287
     */
288
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
289
    {
290
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
291
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
292
            $useAlwaysAvailable = false;
293
        }
294
295
        return $this->loadContent(
296
            $versionInfo->getContentInfo()->id,
297
            $languages,
298
            $versionInfo->versionNo,
299
            $useAlwaysAvailable
300
        );
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
307
    {
308
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
309
310
        if (!$this->repository->canUser('content', 'read', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
311
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
312
        }
313
        if (
314
            !$content->getVersionInfo()->isPublished()
315
            && !$this->repository->canUser('content', 'versionread', $content)
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
316
        ) {
317
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentId, 'versionNo' => $versionNo));
318
        }
319
320
        return $content;
321
    }
322
323
    /**
324
     * Loads content in a version of the given content object.
325
     *
326
     * If no version number is given, the method returns the current version
327
     *
328
     * @internal
329
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
330
     *
331
     * @param mixed $id
332
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
333
     *                         returned value object. If not given all languages are returned.
334
     * @param int|null $versionNo the version number. If not given the current version is returned
335
     * @param bool $isRemoteId
336
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
337
     *
338
     * @return \eZ\Publish\API\Repository\Values\Content\Content
339
     */
340
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
341
    {
342
        try {
343
            // Get Content ID if lookup by remote ID
344
            if ($isRemoteId) {
345
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
346
                $id = $spiContentInfo->id;
347
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
348
                $isRemoteId = false;
349
            }
350
351
            // Get current version if $versionNo is not defined
352
            if ($versionNo === null) {
353
                if (!isset($spiContentInfo)) {
354
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
355
                }
356
357
                $versionNo = $spiContentInfo->currentVersionNo;
358
            }
359
360
            $loadLanguages = $languages;
361
            $alwaysAvailableLanguageCode = null;
362
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
363
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
364
                if (!isset($spiContentInfo)) {
365
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
366
                }
367
368
                if ($spiContentInfo->alwaysAvailable) {
369
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
370
                    $loadLanguages = array_unique($loadLanguages);
371
                }
372
            }
373
374
            $spiContent = $this->persistenceHandler->contentHandler()->load(
375
                $id,
376
                $versionNo,
377
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 360 can also be of type array; however, eZ\Publish\SPI\Persistence\Content\Handler::load() does only seem to accept null|array<integer,string>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
378
            );
379
        } catch (APINotFoundException $e) {
380
            throw new NotFoundException(
381
                'Content',
382
                array(
383
                    $isRemoteId ? 'remoteId' : 'id' => $id,
384
                    'languages' => $languages,
385
                    'versionNo' => $versionNo,
386
                ),
387
                $e
388
            );
389
        }
390
391
        return $this->domainMapper->buildContentDomainObject(
392
            $spiContent,
393
            null,
394
            $languages ?? [],
395
            $alwaysAvailableLanguageCode
396
        );
397
    }
398
399
    /**
400
     * Loads content in a version for the content object reference by the given remote id.
401
     *
402
     * If no version is given, the method returns the current version
403
     *
404
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
405
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the user has no access to read content and in case of un-published content: read versions
406
     *
407
     * @param string $remoteId
408
     * @param array $languages A language filter for fields. If not given all languages are returned
409
     * @param int $versionNo the version number. If not given the current version is returned
410
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
411
     *
412
     * @return \eZ\Publish\API\Repository\Values\Content\Content
413
     */
414 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
415
    {
416
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
417
418
        if (!$this->repository->canUser('content', 'read', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
419
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
420
        }
421
422
        if (
423
            !$content->getVersionInfo()->isPublished()
424
            && !$this->repository->canUser('content', 'versionread', $content)
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
425
        ) {
426
            throw new UnauthorizedException('content', 'versionread', array('remoteId' => $remoteId, 'versionNo' => $versionNo));
427
        }
428
429
        return $content;
430
    }
431
432
    /**
433
     * Bulk-load Content items by the list of ContentInfo Value Objects.
434
     *
435
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
436
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
437
     * allowed to access every Content on the list.
438
     *
439
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
440
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
441
     *                            returned value object. If not given all languages are returned.
442
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
443
     *                                 unless all languages have been asked for.
444
     *
445
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
446
     */
447
    public function loadContentListByContentInfo(
448
        array $contentInfoList,
449
        array $languages = [],
450
        $useAlwaysAvailable = true
451
    ) {
452
        $loadAllLanguages = $languages === Language::ALL;
453
        $contentIds = [];
454
        $translations = $languages;
455 View Code Duplication
        foreach ($contentInfoList as $contentInfo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
456
            $contentIds[] = $contentInfo->id;
457
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
458
            // Might in some case load more languages then intended, but prioritised handling will pick right one
459
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
460
                $translations[] = $contentInfo->mainLanguageCode;
461
            }
462
        }
463
        $translations = array_unique($translations);
464
465
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
466
            $contentIds,
467
            $translations
468
        );
469
        $contentList = [];
470
        foreach ($spiContentList as $contentId => $spiContent) {
471
            $contentInfo = $spiContent->versionInfo->contentInfo;
472
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
473
                $spiContent,
474
                null,
475
                $languages,
476
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
477
            );
478
        }
479
480
        return $contentList;
481
    }
482
483
    /**
484
     * Creates a new content draft assigned to the authenticated user.
485
     *
486
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
487
     * but this required special rights for the authenticated user
488
     * (this is useful for content staging where the transfer process does not
489
     * have to authenticate with the user which created the content object in the source server).
490
     * The user has to publish the draft if it should be visible.
491
     * In 4.x at least one location has to be provided in the location creation array.
492
     *
493
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
494
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
495
     *                                                                        struct are missing or invalid, or if multiple locations are under the
496
     *                                                                        same parent.
497
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
498
     *                                                                               or if a required field is missing / set to an empty value.
499
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
500
     *                                                                          or value is set for non-translatable field in language
501
     *                                                                          other than main.
502
     *
503
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
504
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
505
     *
506
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
507
     */
508
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = array())
509
    {
510
        if ($contentCreateStruct->mainLanguageCode === null) {
511
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
512
        }
513
514
        if ($contentCreateStruct->contentType === null) {
515
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
516
        }
517
518
        $contentCreateStruct = clone $contentCreateStruct;
519
520
        if ($contentCreateStruct->ownerId === null) {
521
            $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...
522
        }
523
524
        if ($contentCreateStruct->alwaysAvailable === null) {
525
            $contentCreateStruct->alwaysAvailable = false;
526
        }
527
528
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
529
            $contentCreateStruct->contentType->id
530
        );
531
532
        if (empty($contentCreateStruct->sectionId)) {
533
            if (isset($locationCreateStructs[0])) {
534
                $location = $this->repository->getLocationService()->loadLocation(
535
                    $locationCreateStructs[0]->parentLocationId
536
                );
537
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
538
            } else {
539
                $contentCreateStruct->sectionId = 1;
540
            }
541
        }
542
543
        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...
544
            throw new UnauthorizedException(
545
                'content',
546
                'create',
547
                array(
548
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
549
                            $locationCreateStructs[0]->parentLocationId :
550
                            null,
551
                    'sectionId' => $contentCreateStruct->sectionId,
552
                )
553
            );
554
        }
555
556
        if (!empty($contentCreateStruct->remoteId)) {
557
            try {
558
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
559
560
                throw new InvalidArgumentException(
561
                    '$contentCreateStruct',
562
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
563
                );
564
            } catch (APINotFoundException $e) {
565
                // Do nothing
566
            }
567
        } else {
568
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
569
        }
570
571
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
572
573
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
574
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
575
576
        $fieldValues = array();
577
        $spiFields = array();
578
        $allFieldErrors = array();
579
        $inputRelations = array();
580
        $locationIdToContentIdMapping = array();
581
582
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
583
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
584
            $fieldType = $this->fieldTypeRegistry->getFieldType(
585
                $fieldDefinition->fieldTypeIdentifier
586
            );
587
588
            foreach ($languageCodes as $languageCode) {
589
                $isEmptyValue = false;
590
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
591
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
592
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
593
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
594
                } else {
595
                    $fieldValue = $fieldDefinition->defaultValue;
596
                }
597
598
                $fieldValue = $fieldType->acceptValue($fieldValue);
599
600 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...
601
                    $isEmptyValue = true;
602
                    if ($fieldDefinition->isRequired) {
603
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
604
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
605
                            null,
606
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
607
                            'empty'
608
                        );
609
                    }
610
                } else {
611
                    $fieldErrors = $fieldType->validate(
612
                        $fieldDefinition,
613
                        $fieldValue
614
                    );
615
                    if (!empty($fieldErrors)) {
616
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
617
                    }
618
                }
619
620
                if (!empty($allFieldErrors)) {
621
                    continue;
622
                }
623
624
                $this->relationProcessor->appendFieldRelations(
625
                    $inputRelations,
626
                    $locationIdToContentIdMapping,
627
                    $fieldType,
628
                    $fieldValue,
629
                    $fieldDefinition->id
630
                );
631
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
632
633
                // Only non-empty value for: translatable field or in main language
634
                if (
635
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
636
                    (!$isEmptyValue && $isLanguageMain)
637
                ) {
638
                    $spiFields[] = new SPIField(
639
                        array(
640
                            'id' => null,
641
                            'fieldDefinitionId' => $fieldDefinition->id,
642
                            'type' => $fieldDefinition->fieldTypeIdentifier,
643
                            'value' => $fieldType->toPersistenceValue($fieldValue),
644
                            'languageCode' => $languageCode,
645
                            'versionNo' => null,
646
                        )
647
                    );
648
                }
649
            }
650
        }
651
652
        if (!empty($allFieldErrors)) {
653
            throw new ContentFieldValidationException($allFieldErrors);
654
        }
655
656
        $spiContentCreateStruct = new SPIContentCreateStruct(
657
            array(
658
                'name' => $this->nameSchemaService->resolve(
659
                    $contentCreateStruct->contentType->nameSchema,
660
                    $contentCreateStruct->contentType,
661
                    $fieldValues,
662
                    $languageCodes
663
                ),
664
                'typeId' => $contentCreateStruct->contentType->id,
665
                'sectionId' => $contentCreateStruct->sectionId,
666
                'ownerId' => $contentCreateStruct->ownerId,
667
                'locations' => $spiLocationCreateStructs,
668
                'fields' => $spiFields,
669
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
670
                'remoteId' => $contentCreateStruct->remoteId,
671
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
672
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
673
                    $contentCreateStruct->mainLanguageCode
674
                )->id,
675
            )
676
        );
677
678
        $defaultObjectStates = $this->getDefaultObjectStates();
679
680
        $this->repository->beginTransaction();
681
        try {
682
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
683
            $this->relationProcessor->processFieldRelations(
684
                $inputRelations,
685
                $spiContent->versionInfo->contentInfo->id,
686
                $spiContent->versionInfo->versionNo,
687
                $contentCreateStruct->contentType
688
            );
689
690
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
691
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
692
                $objectStateHandler->setContentState(
693
                    $spiContent->versionInfo->contentInfo->id,
694
                    $objectStateGroupId,
695
                    $objectState->id
696
                );
697
            }
698
699
            $this->repository->commit();
700
        } catch (Exception $e) {
701
            $this->repository->rollback();
702
            throw $e;
703
        }
704
705
        return $this->domainMapper->buildContentDomainObject($spiContent);
706
    }
707
708
    /**
709
     * Returns an array of default content states with content state group id as key.
710
     *
711
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
712
     */
713
    protected function getDefaultObjectStates()
714
    {
715
        $defaultObjectStatesMap = array();
716
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
717
718
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
719
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
720
                // Only register the first object state which is the default one.
721
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
722
                break;
723
            }
724
        }
725
726
        return $defaultObjectStatesMap;
727
    }
728
729
    /**
730
     * Returns all language codes used in given $fields.
731
     *
732
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
733
     *
734
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
735
     *
736
     * @return string[]
737
     */
738
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
739
    {
740
        $languageCodes = array();
741
742
        foreach ($contentCreateStruct->fields as $field) {
743
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
744
                continue;
745
            }
746
747
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
748
                $field->languageCode
749
            );
750
            $languageCodes[$field->languageCode] = true;
751
        }
752
753
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
754
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
755
                $contentCreateStruct->mainLanguageCode
756
            );
757
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
758
        }
759
760
        return array_keys($languageCodes);
761
    }
762
763
    /**
764
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
765
     *
766
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
767
     *                                                                          or value is set for non-translatable field in language
768
     *                                                                          other than main
769
     *
770
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
771
     *
772
     * @return array
773
     */
774
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
775
    {
776
        $fields = array();
777
778
        foreach ($contentCreateStruct->fields as $field) {
779
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
780
781
            if ($fieldDefinition === null) {
782
                throw new ContentValidationException(
783
                    "Field definition '%identifier%' does not exist in given ContentType",
784
                    ['%identifier%' => $field->fieldDefIdentifier]
785
                );
786
            }
787
788
            if ($field->languageCode === null) {
789
                $field = $this->cloneField(
790
                    $field,
791
                    array('languageCode' => $contentCreateStruct->mainLanguageCode)
792
                );
793
            }
794
795 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...
796
                throw new ContentValidationException(
797
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
798
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
799
                );
800
            }
801
802
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
803
        }
804
805
        return $fields;
806
    }
807
808
    /**
809
     * Clones $field with overriding specific properties from given $overrides array.
810
     *
811
     * @param Field $field
812
     * @param array $overrides
813
     *
814
     * @return Field
815
     */
816
    private function cloneField(Field $field, array $overrides = [])
817
    {
818
        $fieldData = array_merge(
819
            [
820
                'id' => $field->id,
821
                'value' => $field->value,
822
                'languageCode' => $field->languageCode,
823
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
824
                'fieldTypeIdentifier' => $field->fieldTypeIdentifier,
825
            ],
826
            $overrides
827
        );
828
829
        return new Field($fieldData);
830
    }
831
832
    /**
833
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
834
     *
835
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
836
     *
837
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
838
     */
839
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
840
    {
841
        $spiLocationCreateStructs = array();
842
        $parentLocationIdSet = array();
843
        $mainLocation = true;
844
845
        foreach ($locationCreateStructs as $locationCreateStruct) {
846
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
847
                throw new InvalidArgumentException(
848
                    '$locationCreateStructs',
849
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
850
                );
851
            }
852
853
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
854
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
855
            }
856
857
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
858
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
859
            }
860
861
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
862
            $parentLocation = $this->repository->getLocationService()->loadLocation(
863
                $locationCreateStruct->parentLocationId
864
            );
865
866
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
867
                $locationCreateStruct,
868
                $parentLocation,
869
                $mainLocation,
870
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
871
                null,
872
                null
873
            );
874
875
            // First Location in the list will be created as main Location
876
            $mainLocation = false;
877
        }
878
879
        return $spiLocationCreateStructs;
880
    }
881
882
    /**
883
     * Updates the metadata.
884
     *
885
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
886
     *
887
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
888
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
889
     *
890
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
891
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
892
     *
893
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
894
     */
895
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
896
    {
897
        $propertyCount = 0;
898
        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...
899
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
900
                $propertyCount += 1;
901
            }
902
        }
903
        if ($propertyCount === 0) {
904
            throw new InvalidArgumentException(
905
                '$contentMetadataUpdateStruct',
906
                'At least one property must be set'
907
            );
908
        }
909
910
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
911
912
        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...
913
            throw new UnauthorizedException('content', 'edit', array('contentId' => $loadedContentInfo->id));
914
        }
915
916
        if (isset($contentMetadataUpdateStruct->remoteId)) {
917
            try {
918
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
919
920
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
921
                    throw new InvalidArgumentException(
922
                        '$contentMetadataUpdateStruct',
923
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
924
                    );
925
                }
926
            } catch (APINotFoundException $e) {
927
                // Do nothing
928
            }
929
        }
930
931
        $this->repository->beginTransaction();
932
        try {
933
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
934
                $this->persistenceHandler->contentHandler()->updateMetadata(
935
                    $loadedContentInfo->id,
936
                    new SPIMetadataUpdateStruct(
937
                        array(
938
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
939
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
940
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
941
                                null,
942
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
943
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
944
                                null,
945
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
946
                                $this->repository->getContentLanguageService()->loadLanguage(
947
                                    $contentMetadataUpdateStruct->mainLanguageCode
948
                                )->id :
949
                                null,
950
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
951
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
952
                        )
953
                    )
954
                );
955
            }
956
957
            // Change main location
958
            if (isset($contentMetadataUpdateStruct->mainLocationId)
959
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
960
                $this->persistenceHandler->locationHandler()->changeMainLocation(
961
                    $loadedContentInfo->id,
962
                    $contentMetadataUpdateStruct->mainLocationId
963
                );
964
            }
965
966
            // Republish URL aliases to update always-available flag
967
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
968
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
969
                $content = $this->loadContent($loadedContentInfo->id);
970
                $this->publishUrlAliasesForContent($content, false);
971
            }
972
973
            $this->repository->commit();
974
        } catch (Exception $e) {
975
            $this->repository->rollback();
976
            throw $e;
977
        }
978
979
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
980
    }
981
982
    /**
983
     * Publishes URL aliases for all locations of a given content.
984
     *
985
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
986
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
987
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
988
     */
989
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
990
    {
991
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
992
        $locations = $this->repository->getLocationService()->loadLocations(
993
            $content->getVersionInfo()->getContentInfo()
994
        );
995
        $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
996
        foreach ($locations as $location) {
997
            foreach ($urlAliasNames as $languageCode => $name) {
998
                $urlAliasHandler->publishUrlAliasForLocation(
999
                    $location->id,
1000
                    $location->parentLocationId,
1001
                    $name,
1002
                    $languageCode,
1003
                    $content->contentInfo->alwaysAvailable,
1004
                    $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...
1005
                );
1006
            }
1007
            // archive URL aliases of Translations that got deleted
1008
            $urlAliasHandler->archiveUrlAliasesForDeletedTranslations(
1009
                $location->id,
1010
                $location->parentLocationId,
1011
                $content->versionInfo->languageCodes
1012
            );
1013
        }
1014
    }
1015
1016
    /**
1017
     * Deletes a content object including all its versions and locations including their subtrees.
1018
     *
1019
     * @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)
1020
     *
1021
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1022
     *
1023
     * @return mixed[] Affected Location Id's
1024
     */
1025
    public function deleteContent(ContentInfo $contentInfo)
1026
    {
1027
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1028
1029
        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...
1030
            throw new UnauthorizedException('content', 'remove', array('contentId' => $contentInfo->id));
1031
        }
1032
1033
        $affectedLocations = [];
1034
        $this->repository->beginTransaction();
1035
        try {
1036
            // Load Locations first as deleting Content also deletes belonging Locations
1037
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1038
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1039
            $urlAliasHandler = $this->persistenceHandler->urlAliasHandler();
1040
            foreach ($spiLocations as $spiLocation) {
1041
                $urlAliasHandler->locationDeleted($spiLocation->id);
1042
                $affectedLocations[] = $spiLocation->id;
1043
            }
1044
            $this->repository->commit();
1045
        } catch (Exception $e) {
1046
            $this->repository->rollback();
1047
            throw $e;
1048
        }
1049
1050
        return $affectedLocations;
1051
    }
1052
1053
    /**
1054
     * Creates a draft from a published or archived version.
1055
     *
1056
     * If no version is given, the current published version is used.
1057
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1058
     * It can be changed on updating the version.
1059
     *
1060
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1061
     *
1062
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1063
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1064
     * @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
1065
     *
1066
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1067
     */
1068
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1069
    {
1070
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1071
1072
        if ($versionInfo !== null) {
1073
            // Check that given $contentInfo and $versionInfo belong to the same content
1074
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1075
                throw new InvalidArgumentException(
1076
                    '$versionInfo',
1077
                    'VersionInfo does not belong to the same content as given ContentInfo'
1078
                );
1079
            }
1080
1081
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1082
1083
            switch ($versionInfo->status) {
1084
                case VersionInfo::STATUS_PUBLISHED:
1085
                case VersionInfo::STATUS_ARCHIVED:
1086
                    break;
1087
1088
                default:
1089
                    // @todo: throw an exception here, to be defined
1090
                    throw new BadStateException(
1091
                        '$versionInfo',
1092
                        'Draft can not be created from a draft version'
1093
                    );
1094
            }
1095
1096
            $versionNo = $versionInfo->versionNo;
1097
        } elseif ($contentInfo->published) {
1098
            $versionNo = $contentInfo->currentVersionNo;
1099
        } else {
1100
            // @todo: throw an exception here, to be defined
1101
            throw new BadStateException(
1102
                '$contentInfo',
1103
                'Content is not published, draft can be created only from published or archived version'
1104
            );
1105
        }
1106
1107
        if ($creator === null) {
1108
            $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...
1109
        }
1110
1111
        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...
1112
            throw new UnauthorizedException('content', 'edit', array('contentId' => $contentInfo->id));
1113
        }
1114
1115
        $this->repository->beginTransaction();
1116
        try {
1117
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1118
                $contentInfo->id,
1119
                $versionNo,
1120
                $creator->getUserId()
1121
            );
1122
            $this->repository->commit();
1123
        } catch (Exception $e) {
1124
            $this->repository->rollback();
1125
            throw $e;
1126
        }
1127
1128
        return $this->domainMapper->buildContentDomainObject($spiContent);
1129
    }
1130
1131
    /**
1132
     * Loads drafts for a user.
1133
     *
1134
     * If no user is given the drafts for the authenticated user a returned
1135
     *
1136
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list
1137
     *
1138
     * @param \eZ\Publish\API\Repository\Values\User\UserReference $user
1139
     *
1140
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo the drafts ({@link VersionInfo}) owned by the given user
1141
     */
1142
    public function loadContentDrafts(User $user = null)
1143
    {
1144
        if ($user === null) {
1145
            $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...
1146
        }
1147
1148
        // throw early if user has absolutely no access to versionread
1149
        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...
1150
            throw new UnauthorizedException('content', 'versionread');
1151
        }
1152
1153
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser($user->getUserId());
1154
        $versionInfoList = array();
1155 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...
1156
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1157
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1158
            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...
1159
                throw new UnauthorizedException('content', 'versionread', array('contentId' => $versionInfo->contentInfo->id));
1160
            }
1161
1162
            $versionInfoList[] = $versionInfo;
1163
        }
1164
1165
        return $versionInfoList;
1166
    }
1167
1168
    /**
1169
     * Updates the fields of a draft.
1170
     *
1171
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1172
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1173
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1174
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1175
     *                                                                               or if a required field is missing / set to an empty value.
1176
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1177
     *                                                                          or value is set for non-translatable field in language
1178
     *                                                                          other than main.
1179
     *
1180
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1181
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1182
     *
1183
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1184
     */
1185
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1186
    {
1187
        $contentUpdateStruct = clone $contentUpdateStruct;
1188
1189
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1190
        $content = $this->loadContent(
1191
            $versionInfo->getContentInfo()->id,
1192
            null,
1193
            $versionInfo->versionNo
1194
        );
1195
        if (!$content->versionInfo->isDraft()) {
1196
            throw new BadStateException(
1197
                '$versionInfo',
1198
                'Version is not a draft and can not be updated'
1199
            );
1200
        }
1201
1202
        if (!$this->repository->canUser('content', 'edit', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
1203
            throw new UnauthorizedException('content', 'edit', array('contentId' => $content->id));
1204
        }
1205
1206
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1207
        if ($contentUpdateStruct->initialLanguageCode === null) {
1208
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1209
        }
1210
1211
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1212
        $contentLanguageHandler = $this->persistenceHandler->contentLanguageHandler();
1213
        foreach ($allLanguageCodes as $languageCode) {
1214
            $contentLanguageHandler->loadByLanguageCode($languageCode);
1215
        }
1216
1217
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1218
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1219
            $content->contentInfo->contentTypeId
1220
        );
1221
        $fields = $this->mapFieldsForUpdate(
1222
            $contentUpdateStruct,
1223
            $contentType,
1224
            $mainLanguageCode
1225
        );
1226
1227
        $fieldValues = array();
1228
        $spiFields = array();
1229
        $allFieldErrors = array();
1230
        $inputRelations = array();
1231
        $locationIdToContentIdMapping = array();
1232
1233
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1234
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1235
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1236
                $fieldDefinition->fieldTypeIdentifier
1237
            );
1238
1239
            foreach ($allLanguageCodes as $languageCode) {
1240
                $isCopied = $isEmpty = $isRetained = false;
1241
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1242
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1243
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1244
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1245
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1246
1247
                if (!$isFieldUpdated && !$isLanguageNew) {
1248
                    $isRetained = true;
1249
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1250
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1251
                    $isCopied = true;
1252
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1253
                } elseif ($isFieldUpdated) {
1254
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1255
                } else {
1256
                    $fieldValue = $fieldDefinition->defaultValue;
1257
                }
1258
1259
                $fieldValue = $fieldType->acceptValue($fieldValue);
1260
1261 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...
1262
                    $isEmpty = true;
1263
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1264
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1265
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1266
                            null,
1267
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1268
                            'empty'
1269
                        );
1270
                    }
1271
                } elseif ($isLanguageUpdated) {
1272
                    $fieldErrors = $fieldType->validate(
1273
                        $fieldDefinition,
1274
                        $fieldValue
1275
                    );
1276
                    if (!empty($fieldErrors)) {
1277
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1278
                    }
1279
                }
1280
1281
                if (!empty($allFieldErrors)) {
1282
                    continue;
1283
                }
1284
1285
                $this->relationProcessor->appendFieldRelations(
1286
                    $inputRelations,
1287
                    $locationIdToContentIdMapping,
1288
                    $fieldType,
1289
                    $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...
1290
                    $fieldDefinition->id
1291
                );
1292
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1293
1294
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1295
                    continue;
1296
                }
1297
1298
                $spiFields[] = new SPIField(
1299
                    array(
1300
                        'id' => $isLanguageNew ?
1301
                            null :
1302
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1303
                        'fieldDefinitionId' => $fieldDefinition->id,
1304
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1305
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1306
                        'languageCode' => $languageCode,
1307
                        'versionNo' => $versionInfo->versionNo,
1308
                    )
1309
                );
1310
            }
1311
        }
1312
1313
        if (!empty($allFieldErrors)) {
1314
            throw new ContentFieldValidationException($allFieldErrors);
1315
        }
1316
1317
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1318
            array(
1319
                'name' => $this->nameSchemaService->resolveNameSchema(
1320
                    $content,
1321
                    $fieldValues,
1322
                    $allLanguageCodes,
1323
                    $contentType
1324
                ),
1325
                '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...
1326
                'fields' => $spiFields,
1327
                'modificationDate' => time(),
1328
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1329
                    $contentUpdateStruct->initialLanguageCode
1330
                )->id,
1331
            )
1332
        );
1333
        $existingRelations = $this->loadRelations($versionInfo);
1334
1335
        $this->repository->beginTransaction();
1336
        try {
1337
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1338
                $versionInfo->getContentInfo()->id,
1339
                $versionInfo->versionNo,
1340
                $spiContentUpdateStruct
1341
            );
1342
            $this->relationProcessor->processFieldRelations(
1343
                $inputRelations,
1344
                $spiContent->versionInfo->contentInfo->id,
1345
                $spiContent->versionInfo->versionNo,
1346
                $contentType,
1347
                $existingRelations
1348
            );
1349
            $this->repository->commit();
1350
        } catch (Exception $e) {
1351
            $this->repository->rollback();
1352
            throw $e;
1353
        }
1354
1355
        return $this->domainMapper->buildContentDomainObject(
1356
            $spiContent,
1357
            $contentType
1358
        );
1359
    }
1360
1361
    /**
1362
     * Returns only updated language codes.
1363
     *
1364
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1365
     *
1366
     * @return array
1367
     */
1368 View Code Duplication
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1369
    {
1370
        $languageCodes = [
1371
            $contentUpdateStruct->initialLanguageCode => true,
1372
        ];
1373
1374
        foreach ($contentUpdateStruct->fields as $field) {
1375
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1376
                continue;
1377
            }
1378
1379
            $languageCodes[$field->languageCode] = true;
1380
        }
1381
1382
        return array_keys($languageCodes);
1383
    }
1384
1385
    /**
1386
     * Returns all language codes used in given $fields.
1387
     *
1388
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1389
     *
1390
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1391
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1392
     *
1393
     * @return array
1394
     */
1395
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1396
    {
1397
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1398
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1399
1400
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1401
        foreach ($updatedLanguageCodes as $languageCode) {
1402
            $languageCodes[$languageCode] = true;
1403
        }
1404
1405
        return array_keys($languageCodes);
1406
    }
1407
1408
    /**
1409
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1410
     *
1411
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1412
     *                                                                          or value is set for non-translatable field in language
1413
     *                                                                          other than main
1414
     *
1415
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1416
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1417
     * @param string $mainLanguageCode
1418
     *
1419
     * @return array
1420
     */
1421
    protected function mapFieldsForUpdate(
1422
        APIContentUpdateStruct $contentUpdateStruct,
1423
        ContentType $contentType,
1424
        $mainLanguageCode
1425
    ) {
1426
        $fields = array();
1427
1428
        foreach ($contentUpdateStruct->fields as $field) {
1429
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1430
1431
            if ($fieldDefinition === null) {
1432
                throw new ContentValidationException(
1433
                    "Field definition '%identifier%' does not exist in given ContentType",
1434
                    ['%identifier%' => $field->fieldDefIdentifier]
1435
                );
1436
            }
1437
1438
            if ($field->languageCode === null) {
1439
                if ($fieldDefinition->isTranslatable) {
1440
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1441
                } else {
1442
                    $languageCode = $mainLanguageCode;
1443
                }
1444
                $field = $this->cloneField($field, array('languageCode' => $languageCode));
1445
            }
1446
1447 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...
1448
                throw new ContentValidationException(
1449
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1450
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1451
                );
1452
            }
1453
1454
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1455
        }
1456
1457
        return $fields;
1458
    }
1459
1460
    /**
1461
     * Publishes a content version.
1462
     *
1463
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1464
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1465
     *
1466
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version
1467
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1468
     *
1469
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1470
     *
1471
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1472
     */
1473
    public function publishVersion(APIVersionInfo $versionInfo)
1474
    {
1475
        $content = $this->internalLoadContent(
1476
            $versionInfo->contentInfo->id,
1477
            null,
1478
            $versionInfo->versionNo
1479
        );
1480
1481
        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...
1482
            throw new UnauthorizedException('content', 'publish', array('contentId' => $content->id));
1483
        }
1484
1485
        $this->repository->beginTransaction();
1486
        try {
1487
            $content = $this->internalPublishVersion($content->getVersionInfo());
1488
            $this->repository->commit();
1489
        } catch (Exception $e) {
1490
            $this->repository->rollback();
1491
            throw $e;
1492
        }
1493
1494
        return $content;
1495
    }
1496
1497
    /**
1498
     * Publishes a content version.
1499
     *
1500
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1501
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1502
     *
1503
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1504
     *
1505
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1506
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1507
     *
1508
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1509
     */
1510
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1511
    {
1512
        if (!$versionInfo->isDraft()) {
1513
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1514
        }
1515
1516
        $currentTime = $this->getUnixTimestamp();
1517
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1518
            $publicationDate = $currentTime;
1519
        }
1520
1521
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1522
        $metadataUpdateStruct->publicationDate = $publicationDate;
1523
        $metadataUpdateStruct->modificationDate = $currentTime;
1524
1525
        $contentId = $versionInfo->getContentInfo()->id;
1526
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1527
            $contentId,
1528
            $versionInfo->versionNo,
1529
            $metadataUpdateStruct
1530
        );
1531
1532
        $content = $this->domainMapper->buildContentDomainObject($spiContent);
1533
1534
        $this->publishUrlAliasesForContent($content);
1535
1536
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1537
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1538
            $contentId,
1539
            APIVersionInfo::STATUS_ARCHIVED,
1540
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1541
        );
1542
1543
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1544
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1545
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1546
            $archiveVersion = array_shift($archiveList);
1547
            $this->persistenceHandler->contentHandler()->deleteVersion(
1548
                $contentId,
1549
                $archiveVersion->versionNo
1550
            );
1551
        }
1552
1553
        return $content;
1554
    }
1555
1556
    /**
1557
     * @return int
1558
     */
1559
    protected function getUnixTimestamp()
1560
    {
1561
        return time();
1562
    }
1563
1564
    /**
1565
     * Removes the given version.
1566
     *
1567
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1568
     *         published state or is the last version of the Content
1569
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1570
     *
1571
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1572
     */
1573
    public function deleteVersion(APIVersionInfo $versionInfo)
1574
    {
1575
        if ($versionInfo->isPublished()) {
1576
            throw new BadStateException(
1577
                '$versionInfo',
1578
                'Version is published and can not be removed'
1579
            );
1580
        }
1581
1582 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...
1583
            throw new UnauthorizedException(
1584
                'content',
1585
                'versionremove',
1586
                array('contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo)
1587
            );
1588
        }
1589
1590
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1591
            $versionInfo->contentInfo->id,
1592
            null,
1593
            2
1594
        );
1595
1596
        if (count($versionList) === 1) {
1597
            throw new BadStateException(
1598
                '$versionInfo',
1599
                'Version is the last version of the Content and can not be removed'
1600
            );
1601
        }
1602
1603
        $this->repository->beginTransaction();
1604
        try {
1605
            $this->persistenceHandler->contentHandler()->deleteVersion(
1606
                $versionInfo->getContentInfo()->id,
1607
                $versionInfo->versionNo
1608
            );
1609
            $this->repository->commit();
1610
        } catch (Exception $e) {
1611
            $this->repository->rollback();
1612
            throw $e;
1613
        }
1614
    }
1615
1616
    /**
1617
     * Loads all versions for the given content.
1618
     *
1619
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1620
     *
1621
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1622
     *
1623
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1624
     */
1625
    public function loadVersions(ContentInfo $contentInfo)
1626
    {
1627
        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...
1628
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentInfo->id));
1629
        }
1630
1631
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1632
1633
        $versions = array();
1634 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...
1635
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1636
            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...
1637
                throw new UnauthorizedException('content', 'versionread', array('versionId' => $versionInfo->id));
1638
            }
1639
1640
            $versions[] = $versionInfo;
1641
        }
1642
1643
        return $versions;
1644
    }
1645
1646
    /**
1647
     * Copies the content to a new location. If no version is given,
1648
     * all versions are copied, otherwise only the given version.
1649
     *
1650
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1651
     *
1652
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1653
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1654
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1655
     *
1656
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1657
     */
1658
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1659
    {
1660
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1661
            $destinationLocationCreateStruct->parentLocationId
1662
        );
1663 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...
1664
            throw new UnauthorizedException(
1665
                'content',
1666
                'create',
1667
                [
1668
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1669
                    'sectionId' => $contentInfo->sectionId,
1670
                ]
1671
            );
1672
        }
1673
1674
        $defaultObjectStates = $this->getDefaultObjectStates();
1675
1676
        $this->repository->beginTransaction();
1677
        try {
1678
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1679
                $contentInfo->id,
1680
                $versionInfo ? $versionInfo->versionNo : null
1681
            );
1682
1683
            $objectStateHandler = $this->persistenceHandler->objectStateHandler();
1684
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1685
                $objectStateHandler->setContentState(
1686
                    $spiContent->versionInfo->contentInfo->id,
1687
                    $objectStateGroupId,
1688
                    $objectState->id
1689
                );
1690
            }
1691
1692
            $content = $this->internalPublishVersion(
1693
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1694
                $spiContent->versionInfo->creationDate
1695
            );
1696
1697
            $this->repository->getLocationService()->createLocation(
1698
                $content->getVersionInfo()->getContentInfo(),
1699
                $destinationLocationCreateStruct
1700
            );
1701
            $this->repository->commit();
1702
        } catch (Exception $e) {
1703
            $this->repository->rollback();
1704
            throw $e;
1705
        }
1706
1707
        return $this->internalLoadContent($content->id);
1708
    }
1709
1710
    /**
1711
     * Loads all outgoing relations for the given version.
1712
     *
1713
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1714
     *
1715
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1716
     *
1717
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1718
     */
1719
    public function loadRelations(APIVersionInfo $versionInfo)
1720
    {
1721
        if ($versionInfo->isPublished()) {
1722
            $function = 'read';
1723
        } else {
1724
            $function = 'versionread';
1725
        }
1726
1727
        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...
1728
            throw new UnauthorizedException('content', $function);
1729
        }
1730
1731
        $contentInfo = $versionInfo->getContentInfo();
1732
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1733
            $contentInfo->id,
1734
            $versionInfo->versionNo
1735
        );
1736
1737
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1738
        $relations = array();
1739 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...
1740
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1741
            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...
1742
                continue;
1743
            }
1744
1745
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1746
                $spiRelation,
1747
                $contentInfo,
1748
                $destinationContentInfo
1749
            );
1750
        }
1751
1752
        return $relations;
1753
    }
1754
1755
    /**
1756
     * Loads all incoming relations for a content object.
1757
     *
1758
     * The relations come only from published versions of the source content objects
1759
     *
1760
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1761
     *
1762
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1763
     *
1764
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1765
     */
1766
    public function loadReverseRelations(ContentInfo $contentInfo)
1767
    {
1768
        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...
1769
            throw new UnauthorizedException('content', 'reverserelatedlist', array('contentId' => $contentInfo->id));
1770
        }
1771
1772
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1773
            $contentInfo->id
1774
        );
1775
1776
        $returnArray = array();
1777 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...
1778
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1779
            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...
1780
                continue;
1781
            }
1782
1783
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1784
                $spiRelation,
1785
                $sourceContentInfo,
1786
                $contentInfo
1787
            );
1788
        }
1789
1790
        return $returnArray;
1791
    }
1792
1793
    /**
1794
     * Adds a relation of type common.
1795
     *
1796
     * The source of the relation is the content and version
1797
     * referenced by $versionInfo.
1798
     *
1799
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1800
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1801
     *
1802
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1803
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1804
     *
1805
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1806
     */
1807
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1808
    {
1809
        $sourceVersion = $this->loadVersionInfoById(
1810
            $sourceVersion->contentInfo->id,
1811
            $sourceVersion->versionNo
1812
        );
1813
1814
        if (!$sourceVersion->isDraft()) {
1815
            throw new BadStateException(
1816
                '$sourceVersion',
1817
                'Relations of type common can only be added to versions of status draft'
1818
            );
1819
        }
1820
1821
        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...
1822
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1823
        }
1824
1825
        $sourceContentInfo = $sourceVersion->getContentInfo();
1826
1827
        $this->repository->beginTransaction();
1828
        try {
1829
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1830
                new SPIRelationCreateStruct(
1831
                    array(
1832
                        'sourceContentId' => $sourceContentInfo->id,
1833
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1834
                        'sourceFieldDefinitionId' => null,
1835
                        'destinationContentId' => $destinationContent->id,
1836
                        'type' => APIRelation::COMMON,
1837
                    )
1838
                )
1839
            );
1840
            $this->repository->commit();
1841
        } catch (Exception $e) {
1842
            $this->repository->rollback();
1843
            throw $e;
1844
        }
1845
1846
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1847
    }
1848
1849
    /**
1850
     * Removes a relation of type COMMON from a draft.
1851
     *
1852
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1853
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1854
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1855
     *
1856
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1857
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1858
     */
1859
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1860
    {
1861
        $sourceVersion = $this->loadVersionInfoById(
1862
            $sourceVersion->contentInfo->id,
1863
            $sourceVersion->versionNo
1864
        );
1865
1866
        if (!$sourceVersion->isDraft()) {
1867
            throw new BadStateException(
1868
                '$sourceVersion',
1869
                'Relations of type common can only be removed from versions of status draft'
1870
            );
1871
        }
1872
1873
        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...
1874
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1875
        }
1876
1877
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1878
            $sourceVersion->getContentInfo()->id,
1879
            $sourceVersion->versionNo,
1880
            APIRelation::COMMON
1881
        );
1882
1883
        if (empty($spiRelations)) {
1884
            throw new InvalidArgumentException(
1885
                '$sourceVersion',
1886
                'There are no relations of type COMMON for the given destination'
1887
            );
1888
        }
1889
1890
        // there should be only one relation of type COMMON for each destination,
1891
        // but in case there were ever more then one, we will remove them all
1892
        // @todo: alternatively, throw BadStateException?
1893
        $this->repository->beginTransaction();
1894
        try {
1895
            foreach ($spiRelations as $spiRelation) {
1896
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1897
                    $this->persistenceHandler->contentHandler()->removeRelation(
1898
                        $spiRelation->id,
1899
                        APIRelation::COMMON
1900
                    );
1901
                }
1902
            }
1903
            $this->repository->commit();
1904
        } catch (Exception $e) {
1905
            $this->repository->rollback();
1906
            throw $e;
1907
        }
1908
    }
1909
1910
    /**
1911
     * {@inheritdoc}
1912
     */
1913
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
1914
    {
1915
        @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...
1916
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
1917
            E_USER_DEPRECATED
1918
        );
1919
        $this->deleteTranslation($contentInfo, $languageCode);
1920
    }
1921
1922
    /**
1923
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
1924
     *
1925
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
1926
     *
1927
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
1928
     *         is the Main Translation of a Content Item.
1929
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
1930
     *         to delete the content (in one of the locations of the given Content Item).
1931
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
1932
     *         is invalid for the given content.
1933
     *
1934
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1935
     * @param string $languageCode
1936
     *
1937
     * @since 6.13
1938
     */
1939
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
1940
    {
1941
        if ($contentInfo->mainLanguageCode === $languageCode) {
1942
            throw new BadStateException(
1943
                '$languageCode',
1944
                'Specified translation is the main translation of the Content Object'
1945
            );
1946
        }
1947
1948
        $translationWasFound = false;
1949
        $this->repository->beginTransaction();
1950
        try {
1951
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
1952
                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...
1953
                    throw new UnauthorizedException(
1954
                        'content',
1955
                        'remove',
1956
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
1957
                    );
1958
                }
1959
1960
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
1961
                    continue;
1962
                }
1963
1964
                $translationWasFound = true;
1965
1966
                // If the translation is the version's only one, delete the version
1967
                if (count($versionInfo->languageCodes) < 2) {
1968
                    $this->persistenceHandler->contentHandler()->deleteVersion(
1969
                        $versionInfo->getContentInfo()->id,
1970
                        $versionInfo->versionNo
1971
                    );
1972
                }
1973
            }
1974
1975
            if (!$translationWasFound) {
1976
                throw new InvalidArgumentException(
1977
                    '$languageCode',
1978
                    sprintf(
1979
                        '%s does not exist in the Content item(id=%d)',
1980
                        $languageCode,
1981
                        $contentInfo->id
1982
                    )
1983
                );
1984
            }
1985
1986
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
1987
                $contentInfo->id,
1988
                $languageCode
1989
            );
1990
            $locationIds = array_map(
1991
                function (Location $location) {
1992
                    return $location->id;
1993
                },
1994
                $this->repository->getLocationService()->loadLocations($contentInfo)
1995
            );
1996
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
1997
                $locationIds,
1998
                $languageCode
1999
            );
2000
            $this->repository->commit();
2001
        } catch (InvalidArgumentException $e) {
2002
            $this->repository->rollback();
2003
            throw $e;
2004
        } catch (BadStateException $e) {
2005
            $this->repository->rollback();
2006
            throw $e;
2007
        } catch (UnauthorizedException $e) {
2008
            $this->repository->rollback();
2009
            throw $e;
2010
        } catch (Exception $e) {
2011
            $this->repository->rollback();
2012
            // cover generic unexpected exception to fulfill API promise on @throws
2013
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2014
        }
2015
    }
2016
2017
    /**
2018
     * Delete specified Translation from a Content Draft.
2019
     *
2020
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2021
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2022
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2023
     *         to edit the Content (in one of the locations of the given Content Object).
2024
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2025
     *         is invalid for the given Draft.
2026
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2027
     *
2028
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2029
     * @param string $languageCode Language code of the Translation to be removed
2030
     *
2031
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2032
     *
2033
     * @since 6.12
2034
     */
2035
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2036
    {
2037
        if (!$versionInfo->isDraft()) {
2038
            throw new BadStateException(
2039
                '$versionInfo',
2040
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2041
            );
2042
        }
2043
2044
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2045
            throw new BadStateException(
2046
                '$languageCode',
2047
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2048
            );
2049
        }
2050
2051
        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...
2052
            throw new UnauthorizedException(
2053
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2054
            );
2055
        }
2056
2057
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2058
            throw new InvalidArgumentException(
2059
                '$languageCode',
2060
                sprintf(
2061
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2062
                    $versionInfo->contentInfo->id,
2063
                    $versionInfo->versionNo,
2064
                    $languageCode
2065
                )
2066
            );
2067
        }
2068
2069
        if (count($versionInfo->languageCodes) === 1) {
2070
            throw new BadStateException(
2071
                '$languageCode',
2072
                'Specified Translation is the only one Content Object Version has'
2073
            );
2074
        }
2075
2076
        $this->repository->beginTransaction();
2077
        try {
2078
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2079
                $versionInfo->contentInfo->id,
2080
                $versionInfo->versionNo,
2081
                $languageCode
2082
            );
2083
            $this->repository->commit();
2084
2085
            return $this->domainMapper->buildContentDomainObject($spiContent);
2086
        } catch (APINotFoundException $e) {
2087
            // avoid wrapping expected NotFoundException in BadStateException handled below
2088
            $this->repository->rollback();
2089
            throw $e;
2090
        } catch (Exception $e) {
2091
            $this->repository->rollback();
2092
            // cover generic unexpected exception to fulfill API promise on @throws
2093
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2094
        }
2095
    }
2096
2097
    /**
2098
     * Instantiates a new content create struct object.
2099
     *
2100
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2101
     *
2102
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2103
     * @param string $mainLanguageCode
2104
     *
2105
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2106
     */
2107
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2108
    {
2109
        return new ContentCreateStruct(
2110
            array(
2111
                'contentType' => $contentType,
2112
                'mainLanguageCode' => $mainLanguageCode,
2113
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2114
            )
2115
        );
2116
    }
2117
2118
    /**
2119
     * Instantiates a new content meta data update struct.
2120
     *
2121
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2122
     */
2123
    public function newContentMetadataUpdateStruct()
2124
    {
2125
        return new ContentMetadataUpdateStruct();
2126
    }
2127
2128
    /**
2129
     * Instantiates a new content update struct.
2130
     *
2131
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2132
     */
2133
    public function newContentUpdateStruct()
2134
    {
2135
        return new ContentUpdateStruct();
2136
    }
2137
}
2138