Completed
Push — 6.13 ( b4f000...d52368 )
by
unknown
69:59 queued 44:34
created

ContentService::internalLoadContent()   C

Complexity

Conditions 11
Paths 93

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 93
nop 5
dl 0
loc 58
rs 6.7696
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\TranslationInfo;
19
use eZ\Publish\API\Repository\Values\Content\TranslationValues as APITranslationValues;
20
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
21
use eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct;
22
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
23
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
24
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
25
use eZ\Publish\API\Repository\Values\User\User;
26
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
27
use eZ\Publish\API\Repository\Values\Content\Field;
28
use eZ\Publish\API\Repository\Values\Content\Relation as APIRelation;
29
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
30
use eZ\Publish\Core\Base\Exceptions\BadStateException;
31
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
32
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
33
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
34
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
35
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
36
use eZ\Publish\Core\FieldType\ValidationError;
37
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
38
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
39
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
40
use eZ\Publish\Core\Repository\Values\Content\TranslationValues;
41
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
42
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
43
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
44
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
45
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
46
use Exception;
47
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
48
49
/**
50
 * This class provides service methods for managing content.
51
 *
52
 * @example Examples/content.php
53
 */
54
class ContentService implements ContentServiceInterface
55
{
56
    /**
57
     * @var \eZ\Publish\Core\Repository\Repository
58
     */
59
    protected $repository;
60
61
    /**
62
     * @var \eZ\Publish\SPI\Persistence\Handler
63
     */
64
    protected $persistenceHandler;
65
66
    /**
67
     * @var array
68
     */
69
    protected $settings;
70
71
    /**
72
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
73
     */
74
    protected $domainMapper;
75
76
    /**
77
     * @var \eZ\Publish\Core\Repository\Helper\RelationProcessor
78
     */
79
    protected $relationProcessor;
80
81
    /**
82
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
83
     */
84
    protected $nameSchemaService;
85
86
    /**
87
     * @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry
88
     */
89
    protected $fieldTypeRegistry;
90
91
    /**
92
     * Setups service with reference to repository object that created it & corresponding handler.
93
     *
94
     * @param \eZ\Publish\API\Repository\Repository $repository
95
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
96
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
97
     * @param \eZ\Publish\Core\Repository\Helper\RelationProcessor $relationProcessor
98
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
99
     * @param \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry $fieldTypeRegistry,
0 ignored issues
show
Documentation introduced by
There is no parameter named $fieldTypeRegistry,. Did you maybe mean $fieldTypeRegistry?

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

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

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

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

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

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

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

Loading history...
260
            throw new UnauthorizedException('content', $function, array('contentId' => $contentId));
261
        }
262
263
        return $versionInfo;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
270
    {
271
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
272
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
273
            $useAlwaysAvailable = false;
274
        }
275
276
        // As we have content info we can avoid that current version is looked up using spi in loadContent() if not set
277
        if ($versionNo === null) {
278
            $versionNo = $contentInfo->currentVersionNo;
279
        }
280
281
        return $this->loadContent(
282
            $contentInfo->id,
283
            $languages,
284
            $versionNo,
285
            $useAlwaysAvailable
286
        );
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
293
    {
294
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
295
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
296
            $useAlwaysAvailable = false;
297
        }
298
299
        return $this->loadContent(
300
            $versionInfo->getContentInfo()->id,
301
            $languages,
302
            $versionInfo->versionNo,
303
            $useAlwaysAvailable
304
        );
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     */
310 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
311
    {
312
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
313
314
        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...
315
            throw new UnauthorizedException('content', 'read', array('contentId' => $contentId));
316
        }
317
        if (
318
            !$content->getVersionInfo()->isPublished()
319
            && !$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...
320
        ) {
321
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentId, 'versionNo' => $versionNo));
322
        }
323
324
        return $content;
325
    }
326
327
    /**
328
     * Loads content in a version of the given content object.
329
     *
330
     * If no version number is given, the method returns the current version
331
     *
332
     * @internal
333
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
334
     *
335
     * @param mixed $id
336
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
337
     *                         returned value object. If not given all languages are returned.
338
     * @param int|null $versionNo the version number. If not given the current version is returned
339
     * @param bool $isRemoteId
340
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
341
     *
342
     * @return \eZ\Publish\API\Repository\Values\Content\Content
343
     */
344
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
345
    {
346
        try {
347
            // Get Content ID if lookup by remote ID
348
            if ($isRemoteId) {
349
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
350
                $id = $spiContentInfo->id;
351
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
352
                $isRemoteId = false;
353
            }
354
355
            // Get current version if $versionNo is not defined
356
            if ($versionNo === null) {
357
                if (!isset($spiContentInfo)) {
358
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
359
                }
360
361
                $versionNo = $spiContentInfo->currentVersionNo;
362
            }
363
364
            $loadLanguages = $languages;
365
            $alwaysAvailableLanguageCode = null;
366
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
367
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
368
                if (!isset($spiContentInfo)) {
369
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
370
                }
371
372
                if ($spiContentInfo->alwaysAvailable) {
373
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
374
                    $loadLanguages = array_unique($loadLanguages);
375
                }
376
            }
377
378
            $spiContent = $this->persistenceHandler->contentHandler()->load(
379
                $id,
380
                $versionNo,
381
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 364 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...
382
            );
383
        } catch (APINotFoundException $e) {
384
            throw new NotFoundException(
385
                'Content',
386
                array(
387
                    $isRemoteId ? 'remoteId' : 'id' => $id,
388
                    'languages' => $languages,
389
                    'versionNo' => $versionNo,
390
                ),
391
                $e
392
            );
393
        }
394
395
        return $this->domainMapper->buildContentDomainObject(
396
            $spiContent,
397
            null,
398
            empty($languages) ? null : $languages,
399
            $alwaysAvailableLanguageCode
400
        );
401
    }
402
403
    /**
404
     * Loads content in a version for the content object reference by the given remote id.
405
     *
406
     * If no version is given, the method returns the current version
407
     *
408
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
409
     * @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
410
     *
411
     * @param string $remoteId
412
     * @param array $languages A language filter for fields. If not given all languages are returned
413
     * @param int $versionNo the version number. If not given the current version is returned
414
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
415
     *
416
     * @return \eZ\Publish\API\Repository\Values\Content\Content
417
     */
418 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
419
    {
420
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
421
422
        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...
423
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
424
        }
425
426
        if (
427
            !$content->getVersionInfo()->isPublished()
428
            && !$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...
429
        ) {
430
            throw new UnauthorizedException('content', 'versionread', array('remoteId' => $remoteId, 'versionNo' => $versionNo));
431
        }
432
433
        return $content;
434
    }
435
436
    /**
437
     * Bulk-load Content items by the list of ContentInfo Value Objects.
438
     *
439
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
440
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
441
     * allowed to access every Content on the list.
442
     *
443
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
444
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
445
     *                            returned value object. If not given all languages are returned.
446
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
447
     *                                 unless all languages have been asked for.
448
     *
449
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
450
     */
451
    public function loadContentListByContentInfo(
452
        array $contentInfoList,
453
        array $languages = [],
454
        $useAlwaysAvailable = true
455
    ) {
456
        $loadAllLanguages = $languages === Language::ALL;
457
        $contentIds = [];
458
        $translations = $languages;
459
        foreach ($contentInfoList as $contentInfo) {
460
            $contentIds[] = $contentInfo->id;
461
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
462
            // Might in some case load more languages then intended, but prioritised handling will pick right one
463
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
464
                $translations[] = $contentInfo->mainLanguageCode;
465
            }
466
        }
467
        $translations = array_unique($translations);
468
469
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
470
            $contentIds,
471
            $translations
472
        );
473
        $contentList = [];
474
        foreach ($spiContentList as $contentId => $spiContent) {
475
            $contentInfo = $spiContent->versionInfo->contentInfo;
476
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
477
                $spiContent,
478
                null,
479
                $languages,
480
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
481
            );
482
        }
483
484
        return $contentList;
485
    }
486
487
    /**
488
     * Creates a new content draft assigned to the authenticated user.
489
     *
490
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
491
     * but this required special rights for the authenticated user
492
     * (this is useful for content staging where the transfer process does not
493
     * have to authenticate with the user which created the content object in the source server).
494
     * The user has to publish the draft if it should be visible.
495
     * In 4.x at least one location has to be provided in the location creation array.
496
     *
497
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
498
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
499
     *                                                                        struct are missing or invalid, or if multiple locations are under the
500
     *                                                                        same parent.
501
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
502
     *                                                                               or if a required field is missing / set to an empty value.
503
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
504
     *                                                                          or value is set for non-translatable field in language
505
     *                                                                          other than main.
506
     *
507
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
508
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
509
     *
510
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
511
     */
512
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = array())
513
    {
514
        if ($contentCreateStruct->mainLanguageCode === null) {
515
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
516
        }
517
518
        if ($contentCreateStruct->contentType === null) {
519
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
520
        }
521
522
        $contentCreateStruct = clone $contentCreateStruct;
523
524
        if ($contentCreateStruct->ownerId === null) {
525
            $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...
526
        }
527
528
        if ($contentCreateStruct->alwaysAvailable === null) {
529
            $contentCreateStruct->alwaysAvailable = false;
530
        }
531
532
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
533
            $contentCreateStruct->contentType->id
534
        );
535
536
        if (empty($contentCreateStruct->sectionId)) {
537
            if (isset($locationCreateStructs[0])) {
538
                $location = $this->repository->getLocationService()->loadLocation(
539
                    $locationCreateStructs[0]->parentLocationId
540
                );
541
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
542
            } else {
543
                $contentCreateStruct->sectionId = 1;
544
            }
545
        }
546
547
        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...
548
            throw new UnauthorizedException(
549
                'content',
550
                'create',
551
                array(
552
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
553
                            $locationCreateStructs[0]->parentLocationId :
554
                            null,
555
                    'sectionId' => $contentCreateStruct->sectionId,
556
                )
557
            );
558
        }
559
560
        if (!empty($contentCreateStruct->remoteId)) {
561
            try {
562
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
563
564
                throw new InvalidArgumentException(
565
                    '$contentCreateStruct',
566
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
567
                );
568
            } catch (APINotFoundException $e) {
569
                // Do nothing
570
            }
571
        } else {
572
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
573
        }
574
575
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
576
577
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
578
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
579
580
        $fieldValues = array();
581
        $spiFields = array();
582
        $allFieldErrors = array();
583
        $inputRelations = array();
584
        $locationIdToContentIdMapping = array();
585
586
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
587
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
588
            $fieldType = $this->fieldTypeRegistry->getFieldType(
589
                $fieldDefinition->fieldTypeIdentifier
590
            );
591
592
            foreach ($languageCodes as $languageCode) {
593
                $isEmptyValue = false;
594
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
595
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
596
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
597
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
598
                } else {
599
                    $fieldValue = $fieldDefinition->defaultValue;
600
                }
601
602
                $fieldValue = $fieldType->acceptValue($fieldValue);
603
604 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...
605
                    $isEmptyValue = true;
606
                    if ($fieldDefinition->isRequired) {
607
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
608
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
609
                            null,
610
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
611
                            'empty'
612
                        );
613
                    }
614
                } else {
615
                    $fieldErrors = $fieldType->validate(
616
                        $fieldDefinition,
617
                        $fieldValue
618
                    );
619
                    if (!empty($fieldErrors)) {
620
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
621
                    }
622
                }
623
624
                if (!empty($allFieldErrors)) {
625
                    continue;
626
                }
627
628
                $this->relationProcessor->appendFieldRelations(
629
                    $inputRelations,
630
                    $locationIdToContentIdMapping,
631
                    $fieldType,
632
                    $fieldValue,
633
                    $fieldDefinition->id
634
                );
635
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
636
637
                // Only non-empty value for: translatable field or in main language
638
                if (
639
                    (!$isEmptyValue && $fieldDefinition->isTranslatable) ||
640
                    (!$isEmptyValue && $isLanguageMain)
641
                ) {
642
                    $spiFields[] = new SPIField(
643
                        array(
644
                            'id' => null,
645
                            'fieldDefinitionId' => $fieldDefinition->id,
646
                            'type' => $fieldDefinition->fieldTypeIdentifier,
647
                            'value' => $fieldType->toPersistenceValue($fieldValue),
648
                            'languageCode' => $languageCode,
649
                            'versionNo' => null,
650
                        )
651
                    );
652
                }
653
            }
654
        }
655
656
        if (!empty($allFieldErrors)) {
657
            throw new ContentFieldValidationException($allFieldErrors);
658
        }
659
660
        $spiContentCreateStruct = new SPIContentCreateStruct(
661
            array(
662
                'name' => $this->nameSchemaService->resolve(
663
                    $contentCreateStruct->contentType->nameSchema,
664
                    $contentCreateStruct->contentType,
665
                    $fieldValues,
666
                    $languageCodes
667
                ),
668
                'typeId' => $contentCreateStruct->contentType->id,
669
                'sectionId' => $contentCreateStruct->sectionId,
670
                'ownerId' => $contentCreateStruct->ownerId,
671
                'locations' => $spiLocationCreateStructs,
672
                'fields' => $spiFields,
673
                'alwaysAvailable' => $contentCreateStruct->alwaysAvailable,
674
                'remoteId' => $contentCreateStruct->remoteId,
675
                'modified' => isset($contentCreateStruct->modificationDate) ? $contentCreateStruct->modificationDate->getTimestamp() : time(),
676
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
677
                    $contentCreateStruct->mainLanguageCode
678
                )->id,
679
            )
680
        );
681
682
        $defaultObjectStates = $this->getDefaultObjectStates();
683
684
        $this->repository->beginTransaction();
685
        try {
686
            $spiContent = $this->persistenceHandler->contentHandler()->create($spiContentCreateStruct);
687
            $this->relationProcessor->processFieldRelations(
688
                $inputRelations,
689
                $spiContent->versionInfo->contentInfo->id,
690
                $spiContent->versionInfo->versionNo,
691
                $contentCreateStruct->contentType
692
            );
693
694
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
695
                $this->persistenceHandler->objectStateHandler()->setContentState(
696
                    $spiContent->versionInfo->contentInfo->id,
697
                    $objectStateGroupId,
698
                    $objectState->id
699
                );
700
            }
701
702
            $this->repository->commit();
703
        } catch (Exception $e) {
704
            $this->repository->rollback();
705
            throw $e;
706
        }
707
708
        return $this->domainMapper->buildContentDomainObject($spiContent);
709
    }
710
711
    /**
712
     * Returns an array of default content states with content state group id as key.
713
     *
714
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
715
     */
716
    protected function getDefaultObjectStates()
717
    {
718
        $defaultObjectStatesMap = array();
719
        $objectStateHandler = $this->persistenceHandler->objectStateHandler();
720
721
        foreach ($objectStateHandler->loadAllGroups() as $objectStateGroup) {
722
            foreach ($objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
723
                // Only register the first object state which is the default one.
724
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
725
                break;
726
            }
727
        }
728
729
        return $defaultObjectStatesMap;
730
    }
731
732
    /**
733
     * Returns all language codes used in given $fields.
734
     *
735
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value is set in main language
736
     *
737
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
738
     *
739
     * @return string[]
740
     */
741
    protected function getLanguageCodesForCreate(APIContentCreateStruct $contentCreateStruct)
742
    {
743
        $languageCodes = array();
744
745
        foreach ($contentCreateStruct->fields as $field) {
746
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
747
                continue;
748
            }
749
750
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
751
                $field->languageCode
752
            );
753
            $languageCodes[$field->languageCode] = true;
754
        }
755
756
        if (!isset($languageCodes[$contentCreateStruct->mainLanguageCode])) {
757
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
758
                $contentCreateStruct->mainLanguageCode
759
            );
760
            $languageCodes[$contentCreateStruct->mainLanguageCode] = true;
761
        }
762
763
        return array_keys($languageCodes);
764
    }
765
766
    /**
767
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
768
     *
769
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
770
     *                                                                          or value is set for non-translatable field in language
771
     *                                                                          other than main
772
     *
773
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
774
     *
775
     * @return array
776
     */
777
    protected function mapFieldsForCreate(APIContentCreateStruct $contentCreateStruct)
778
    {
779
        $fields = array();
780
781
        foreach ($contentCreateStruct->fields as $field) {
782
            $fieldDefinition = $contentCreateStruct->contentType->getFieldDefinition($field->fieldDefIdentifier);
783
784
            if ($fieldDefinition === null) {
785
                throw new ContentValidationException(
786
                    "Field definition '%identifier%' does not exist in given ContentType",
787
                    ['%identifier%' => $field->fieldDefIdentifier]
788
                );
789
            }
790
791
            if ($field->languageCode === null) {
792
                $field = $this->cloneField(
793
                    $field,
794
                    array('languageCode' => $contentCreateStruct->mainLanguageCode)
795
                );
796
            }
797
798 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...
799
                throw new ContentValidationException(
800
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
801
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
802
                );
803
            }
804
805
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
806
        }
807
808
        return $fields;
809
    }
810
811
    /**
812
     * Clones $field with overriding specific properties from given $overrides array.
813
     *
814
     * @param Field $field
815
     * @param array $overrides
816
     *
817
     * @return Field
818
     */
819
    private function cloneField(Field $field, array $overrides = [])
820
    {
821
        $fieldData = array_merge(
822
            [
823
                'id' => $field->id,
824
                'value' => $field->value,
825
                'languageCode' => $field->languageCode,
826
                'fieldDefIdentifier' => $field->fieldDefIdentifier,
827
                'fieldTypeIdentifier' => $field->fieldTypeIdentifier,
828
            ],
829
            $overrides
830
        );
831
832
        return new Field($fieldData);
833
    }
834
835
    /**
836
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
837
     *
838
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
839
     *
840
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct[]
841
     */
842
    protected function buildSPILocationCreateStructs(array $locationCreateStructs)
843
    {
844
        $spiLocationCreateStructs = array();
845
        $parentLocationIdSet = array();
846
        $mainLocation = true;
847
848
        foreach ($locationCreateStructs as $locationCreateStruct) {
849
            if (isset($parentLocationIdSet[$locationCreateStruct->parentLocationId])) {
850
                throw new InvalidArgumentException(
851
                    '$locationCreateStructs',
852
                    "Multiple LocationCreateStructs with the same parent Location '{$locationCreateStruct->parentLocationId}' are given"
853
                );
854
            }
855
856
            if (!array_key_exists($locationCreateStruct->sortField, Location::SORT_FIELD_MAP)) {
857
                $locationCreateStruct->sortField = Location::SORT_FIELD_NAME;
858
            }
859
860
            if (!array_key_exists($locationCreateStruct->sortOrder, Location::SORT_ORDER_MAP)) {
861
                $locationCreateStruct->sortOrder = Location::SORT_ORDER_ASC;
862
            }
863
864
            $parentLocationIdSet[$locationCreateStruct->parentLocationId] = true;
865
            $parentLocation = $this->repository->getLocationService()->loadLocation(
866
                $locationCreateStruct->parentLocationId
867
            );
868
869
            $spiLocationCreateStructs[] = $this->domainMapper->buildSPILocationCreateStruct(
870
                $locationCreateStruct,
871
                $parentLocation,
872
                $mainLocation,
873
                // For Content draft contentId and contentVersionNo are set in ContentHandler upon draft creation
874
                null,
875
                null
876
            );
877
878
            // First Location in the list will be created as main Location
879
            $mainLocation = false;
880
        }
881
882
        return $spiLocationCreateStructs;
883
    }
884
885
    /**
886
     * Updates the metadata.
887
     *
888
     * (see {@link ContentMetadataUpdateStruct}) of a content object - to update fields use updateContent
889
     *
890
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data
891
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists
892
     *
893
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
894
     * @param \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct
895
     *
896
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content with the updated attributes
897
     */
898
    public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct)
899
    {
900
        $propertyCount = 0;
901
        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...
902
            if (isset($contentMetadataUpdateStruct->$propertyName)) {
903
                $propertyCount += 1;
904
            }
905
        }
906
        if ($propertyCount === 0) {
907
            throw new InvalidArgumentException(
908
                '$contentMetadataUpdateStruct',
909
                'At least one property must be set'
910
            );
911
        }
912
913
        $loadedContentInfo = $this->loadContentInfo($contentInfo->id);
914
915
        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...
916
            throw new UnauthorizedException('content', 'edit', array('contentId' => $loadedContentInfo->id));
917
        }
918
919
        if (isset($contentMetadataUpdateStruct->remoteId)) {
920
            try {
921
                $existingContentInfo = $this->loadContentInfoByRemoteId($contentMetadataUpdateStruct->remoteId);
922
923
                if ($existingContentInfo->id !== $loadedContentInfo->id) {
924
                    throw new InvalidArgumentException(
925
                        '$contentMetadataUpdateStruct',
926
                        "Another content with remoteId '{$contentMetadataUpdateStruct->remoteId}' exists"
927
                    );
928
                }
929
            } catch (APINotFoundException $e) {
930
                // Do nothing
931
            }
932
        }
933
934
        $this->repository->beginTransaction();
935
        try {
936
            if ($propertyCount > 1 || !isset($contentMetadataUpdateStruct->mainLocationId)) {
937
                $this->persistenceHandler->contentHandler()->updateMetadata(
938
                    $loadedContentInfo->id,
939
                    new SPIMetadataUpdateStruct(
940
                        array(
941
                            'ownerId' => $contentMetadataUpdateStruct->ownerId,
942
                            'publicationDate' => isset($contentMetadataUpdateStruct->publishedDate) ?
943
                                $contentMetadataUpdateStruct->publishedDate->getTimestamp() :
944
                                null,
945
                            'modificationDate' => isset($contentMetadataUpdateStruct->modificationDate) ?
946
                                $contentMetadataUpdateStruct->modificationDate->getTimestamp() :
947
                                null,
948
                            'mainLanguageId' => isset($contentMetadataUpdateStruct->mainLanguageCode) ?
949
                                $this->repository->getContentLanguageService()->loadLanguage(
950
                                    $contentMetadataUpdateStruct->mainLanguageCode
951
                                )->id :
952
                                null,
953
                            'alwaysAvailable' => $contentMetadataUpdateStruct->alwaysAvailable,
954
                            'remoteId' => $contentMetadataUpdateStruct->remoteId,
955
                        )
956
                    )
957
                );
958
            }
959
960
            // Change main location
961
            if (isset($contentMetadataUpdateStruct->mainLocationId)
962
                && $loadedContentInfo->mainLocationId !== $contentMetadataUpdateStruct->mainLocationId) {
963
                $this->persistenceHandler->locationHandler()->changeMainLocation(
964
                    $loadedContentInfo->id,
965
                    $contentMetadataUpdateStruct->mainLocationId
966
                );
967
            }
968
969
            // Republish URL aliases to update always-available flag
970
            if (isset($contentMetadataUpdateStruct->alwaysAvailable)
971
                && $loadedContentInfo->alwaysAvailable !== $contentMetadataUpdateStruct->alwaysAvailable) {
972
                $content = $this->loadContent($loadedContentInfo->id);
973
                $this->publishUrlAliasesForContent($content, false);
974
            }
975
976
            $this->repository->commit();
977
        } catch (Exception $e) {
978
            $this->repository->rollback();
979
            throw $e;
980
        }
981
982
        return isset($content) ? $content : $this->loadContent($loadedContentInfo->id);
983
    }
984
985
    /**
986
     * Publishes URL aliases for all locations of a given content.
987
     *
988
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
989
     * @param bool $updatePathIdentificationString this parameter is legacy storage specific for updating
990
     *                      ezcontentobject_tree.path_identification_string, it is ignored by other storage engines
991
     */
992
    protected function publishUrlAliasesForContent(APIContent $content, $updatePathIdentificationString = true)
993
    {
994
        $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
995
        $locations = $this->repository->getLocationService()->loadLocations(
996
            $content->getVersionInfo()->getContentInfo()
997
        );
998
        foreach ($locations as $location) {
999 View Code Duplication
            foreach ($urlAliasNames as $languageCode => $name) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1000
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
1001
                    $location->id,
1002
                    $location->parentLocationId,
1003
                    $name,
1004
                    $languageCode,
1005
                    $content->contentInfo->alwaysAvailable,
1006
                    $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...
1007
                );
1008
            }
1009
            // archive URL aliases of Translations that got deleted
1010
            $this->persistenceHandler->urlAliasHandler()->archiveUrlAliasesForDeletedTranslations(
1011
                $location->id,
1012
                $location->parentLocationId,
1013
                $content->versionInfo->languageCodes
1014
            );
1015
        }
1016
    }
1017
1018
    /**
1019
     * Deletes a content object including all its versions and locations including their subtrees.
1020
     *
1021
     * @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)
1022
     *
1023
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1024
     *
1025
     * @return mixed[] Affected Location Id's
1026
     */
1027
    public function deleteContent(ContentInfo $contentInfo)
1028
    {
1029
        $contentInfo = $this->internalLoadContentInfo($contentInfo->id);
1030
1031
        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...
1032
            throw new UnauthorizedException('content', 'remove', array('contentId' => $contentInfo->id));
1033
        }
1034
1035
        $affectedLocations = [];
1036
        $this->repository->beginTransaction();
1037
        try {
1038
            // Load Locations first as deleting Content also deletes belonging Locations
1039
            $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
1040
            $this->persistenceHandler->contentHandler()->deleteContent($contentInfo->id);
1041
            foreach ($spiLocations as $spiLocation) {
1042
                $this->persistenceHandler->urlAliasHandler()->locationDeleted($spiLocation->id);
1043
                $affectedLocations[] = $spiLocation->id;
1044
            }
1045
            $this->repository->commit();
1046
        } catch (Exception $e) {
1047
            $this->repository->rollback();
1048
            throw $e;
1049
        }
1050
1051
        return $affectedLocations;
1052
    }
1053
1054
    /**
1055
     * Creates a draft from a published or archived version.
1056
     *
1057
     * If no version is given, the current published version is used.
1058
     * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language.
1059
     * It can be changed on updating the version.
1060
     *
1061
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft
1062
     *
1063
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1064
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1065
     * @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
1066
     *
1067
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
1068
     */
1069
    public function createContentDraft(ContentInfo $contentInfo, APIVersionInfo $versionInfo = null, User $creator = null)
1070
    {
1071
        $contentInfo = $this->loadContentInfo($contentInfo->id);
1072
1073
        if ($versionInfo !== null) {
1074
            // Check that given $contentInfo and $versionInfo belong to the same content
1075
            if ($versionInfo->getContentInfo()->id != $contentInfo->id) {
1076
                throw new InvalidArgumentException(
1077
                    '$versionInfo',
1078
                    'VersionInfo does not belong to the same content as given ContentInfo'
1079
                );
1080
            }
1081
1082
            $versionInfo = $this->loadVersionInfoById($contentInfo->id, $versionInfo->versionNo);
1083
1084
            switch ($versionInfo->status) {
1085
                case VersionInfo::STATUS_PUBLISHED:
1086
                case VersionInfo::STATUS_ARCHIVED:
1087
                    break;
1088
1089
                default:
1090
                    // @todo: throw an exception here, to be defined
1091
                    throw new BadStateException(
1092
                        '$versionInfo',
1093
                        'Draft can not be created from a draft version'
1094
                    );
1095
            }
1096
1097
            $versionNo = $versionInfo->versionNo;
1098
        } elseif ($contentInfo->published) {
1099
            $versionNo = $contentInfo->currentVersionNo;
1100
        } else {
1101
            // @todo: throw an exception here, to be defined
1102
            throw new BadStateException(
1103
                '$contentInfo',
1104
                'Content is not published, draft can be created only from published or archived version'
1105
            );
1106
        }
1107
1108
        if ($creator === null) {
1109
            $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...
1110
        }
1111
1112
        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...
1113
            throw new UnauthorizedException('content', 'edit', array('contentId' => $contentInfo->id));
1114
        }
1115
1116
        $this->repository->beginTransaction();
1117
        try {
1118
            $spiContent = $this->persistenceHandler->contentHandler()->createDraftFromVersion(
1119
                $contentInfo->id,
1120
                $versionNo,
1121
                $creator->getUserId()
1122
            );
1123
            $this->repository->commit();
1124
        } catch (Exception $e) {
1125
            $this->repository->rollback();
1126
            throw $e;
1127
        }
1128
1129
        return $this->domainMapper->buildContentDomainObject($spiContent);
1130
    }
1131
1132
    /**
1133
     * Loads drafts for a user.
1134
     *
1135
     * If no user is given the drafts for the authenticated user a returned
1136
     *
1137
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list
1138
     *
1139
     * @param \eZ\Publish\API\Repository\Values\User\UserReference $user
1140
     *
1141
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo the drafts ({@link VersionInfo}) owned by the given user
1142
     */
1143
    public function loadContentDrafts(User $user = null)
1144
    {
1145
        if ($user === null) {
1146
            $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...
1147
        }
1148
1149
        // throw early if user has absolutely no access to versionread
1150
        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...
1151
            throw new UnauthorizedException('content', 'versionread');
1152
        }
1153
1154
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->loadDraftsForUser($user->getUserId());
1155
        $versionInfoList = array();
1156 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...
1157
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1158
            // @todo: Change this to filter returned drafts by permissions instead of throwing
1159
            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...
1160
                throw new UnauthorizedException('content', 'versionread', array('contentId' => $versionInfo->contentInfo->id));
1161
            }
1162
1163
            $versionInfoList[] = $versionInfo;
1164
        }
1165
1166
        return $versionInfoList;
1167
    }
1168
1169
    /**
1170
     * Translate a version.
1171
     *
1172
     * updates the destination version given in $translationInfo with the provided translated fields in $translationValues
1173
     *
1174
     * @example Examples/translation_5x.php
1175
     *
1176
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to update this version
1177
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the given destination version is not a draft
1178
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $translationValues is not valid, or if a required field is missing or is set to an empty value.
1179
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1180
     *                                                                          or value is set for non-translatable field in language
1181
     *                                                                          other than main.
1182
     *
1183
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1184
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationValues $translationValues
1185
     * @param \eZ\Publish\API\Repository\Values\User\User $modifier If set, this user is taken as modifier of the version
1186
     *
1187
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the translated fields
1188
     *
1189
     * @since 5.0
1190
     */
1191
    public function translateVersion(TranslationInfo $translationInfo, APITranslationValues $translationValues, User $modifier = null)
1192
    {
1193
        throw new NotImplementedException(__METHOD__);
1194
    }
1195
1196
    /**
1197
     * Updates the fields of a draft.
1198
     *
1199
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version
1200
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1201
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a property on the struct is invalid.
1202
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
1203
     *                                                                               or if a required field is missing / set to an empty value.
1204
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
1205
     *                                                                          or value is set for non-translatable field in language
1206
     *                                                                          other than main.
1207
     *
1208
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1209
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1210
     *
1211
     * @return \eZ\Publish\API\Repository\Values\Content\Content the content draft with the updated fields
1212
     */
1213
    public function updateContent(APIVersionInfo $versionInfo, APIContentUpdateStruct $contentUpdateStruct)
1214
    {
1215
        $contentUpdateStruct = clone $contentUpdateStruct;
1216
1217
        /** @var $content \eZ\Publish\Core\Repository\Values\Content\Content */
1218
        $content = $this->loadContent(
1219
            $versionInfo->getContentInfo()->id,
1220
            null,
1221
            $versionInfo->versionNo
1222
        );
1223
        if (!$content->versionInfo->isDraft()) {
1224
            throw new BadStateException(
1225
                '$versionInfo',
1226
                'Version is not a draft and can not be updated'
1227
            );
1228
        }
1229
1230
        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...
1231
            throw new UnauthorizedException('content', 'edit', array('contentId' => $content->id));
1232
        }
1233
1234
        $mainLanguageCode = $content->contentInfo->mainLanguageCode;
1235
        if ($contentUpdateStruct->initialLanguageCode === null) {
1236
            $contentUpdateStruct->initialLanguageCode = $mainLanguageCode;
1237
        }
1238
1239
        $allLanguageCodes = $this->getLanguageCodesForUpdate($contentUpdateStruct, $content);
1240
        foreach ($allLanguageCodes as $languageCode) {
1241
            $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode($languageCode);
1242
        }
1243
1244
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1245
        $contentType = $this->repository->getContentTypeService()->loadContentType(
1246
            $content->contentInfo->contentTypeId
1247
        );
1248
        $fields = $this->mapFieldsForUpdate(
1249
            $contentUpdateStruct,
1250
            $contentType,
1251
            $mainLanguageCode
1252
        );
1253
1254
        $fieldValues = array();
1255
        $spiFields = array();
1256
        $allFieldErrors = array();
1257
        $inputRelations = array();
1258
        $locationIdToContentIdMapping = array();
1259
1260
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
1261
            /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
1262
            $fieldType = $this->fieldTypeRegistry->getFieldType(
1263
                $fieldDefinition->fieldTypeIdentifier
1264
            );
1265
1266
            foreach ($allLanguageCodes as $languageCode) {
1267
                $isCopied = $isEmpty = $isRetained = false;
1268
                $isLanguageNew = !in_array($languageCode, $content->versionInfo->languageCodes);
1269
                $isLanguageUpdated = in_array($languageCode, $updatedLanguageCodes);
1270
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $mainLanguageCode;
1271
                $isFieldUpdated = isset($fields[$fieldDefinition->identifier][$valueLanguageCode]);
1272
                $isProcessed = isset($fieldValues[$fieldDefinition->identifier][$valueLanguageCode]);
1273
1274
                if (!$isFieldUpdated && !$isLanguageNew) {
1275
                    $isRetained = true;
1276
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1277
                } elseif (!$isFieldUpdated && $isLanguageNew && !$fieldDefinition->isTranslatable) {
1278
                    $isCopied = true;
1279
                    $fieldValue = $content->getField($fieldDefinition->identifier, $valueLanguageCode)->value;
1280
                } elseif ($isFieldUpdated) {
1281
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
1282
                } else {
1283
                    $fieldValue = $fieldDefinition->defaultValue;
1284
                }
1285
1286
                $fieldValue = $fieldType->acceptValue($fieldValue);
1287
1288 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...
1289
                    $isEmpty = true;
1290
                    if ($isLanguageUpdated && $fieldDefinition->isRequired) {
1291
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = new ValidationError(
1292
                            "Value for required field definition '%identifier%' with language '%languageCode%' is empty",
1293
                            null,
1294
                            ['%identifier%' => $fieldDefinition->identifier, '%languageCode%' => $languageCode],
1295
                            'empty'
1296
                        );
1297
                    }
1298
                } elseif ($isLanguageUpdated) {
1299
                    $fieldErrors = $fieldType->validate(
1300
                        $fieldDefinition,
1301
                        $fieldValue
1302
                    );
1303
                    if (!empty($fieldErrors)) {
1304
                        $allFieldErrors[$fieldDefinition->id][$languageCode] = $fieldErrors;
1305
                    }
1306
                }
1307
1308
                if (!empty($allFieldErrors)) {
1309
                    continue;
1310
                }
1311
1312
                $this->relationProcessor->appendFieldRelations(
1313
                    $inputRelations,
1314
                    $locationIdToContentIdMapping,
1315
                    $fieldType,
1316
                    $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...
1317
                    $fieldDefinition->id
1318
                );
1319
                $fieldValues[$fieldDefinition->identifier][$languageCode] = $fieldValue;
1320
1321
                if ($isRetained || $isCopied || ($isLanguageNew && $isEmpty) || $isProcessed) {
1322
                    continue;
1323
                }
1324
1325
                $spiFields[] = new SPIField(
1326
                    array(
1327
                        'id' => $isLanguageNew ?
1328
                            null :
1329
                            $content->getField($fieldDefinition->identifier, $languageCode)->id,
1330
                        'fieldDefinitionId' => $fieldDefinition->id,
1331
                        'type' => $fieldDefinition->fieldTypeIdentifier,
1332
                        'value' => $fieldType->toPersistenceValue($fieldValue),
1333
                        'languageCode' => $languageCode,
1334
                        'versionNo' => $versionInfo->versionNo,
1335
                    )
1336
                );
1337
            }
1338
        }
1339
1340
        if (!empty($allFieldErrors)) {
1341
            throw new ContentFieldValidationException($allFieldErrors);
1342
        }
1343
1344
        $spiContentUpdateStruct = new SPIContentUpdateStruct(
1345
            array(
1346
                'name' => $this->nameSchemaService->resolveNameSchema(
1347
                    $content,
1348
                    $fieldValues,
1349
                    $allLanguageCodes,
1350
                    $contentType
1351
                ),
1352
                '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...
1353
                'fields' => $spiFields,
1354
                'modificationDate' => time(),
1355
                'initialLanguageId' => $this->persistenceHandler->contentLanguageHandler()->loadByLanguageCode(
1356
                    $contentUpdateStruct->initialLanguageCode
1357
                )->id,
1358
            )
1359
        );
1360
        $existingRelations = $this->loadRelations($versionInfo);
1361
1362
        $this->repository->beginTransaction();
1363
        try {
1364
            $spiContent = $this->persistenceHandler->contentHandler()->updateContent(
1365
                $versionInfo->getContentInfo()->id,
1366
                $versionInfo->versionNo,
1367
                $spiContentUpdateStruct
1368
            );
1369
            $this->relationProcessor->processFieldRelations(
1370
                $inputRelations,
1371
                $spiContent->versionInfo->contentInfo->id,
1372
                $spiContent->versionInfo->versionNo,
1373
                $contentType,
1374
                $existingRelations
1375
            );
1376
            $this->repository->commit();
1377
        } catch (Exception $e) {
1378
            $this->repository->rollback();
1379
            throw $e;
1380
        }
1381
1382
        return $this->domainMapper->buildContentDomainObject(
1383
            $spiContent,
1384
            $contentType
1385
        );
1386
    }
1387
1388
    /**
1389
     * Returns only updated language codes.
1390
     *
1391
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1392
     *
1393
     * @return array
1394
     */
1395 View Code Duplication
    private function getUpdatedLanguageCodes(APIContentUpdateStruct $contentUpdateStruct)
1396
    {
1397
        $languageCodes = [
1398
            $contentUpdateStruct->initialLanguageCode => true,
1399
        ];
1400
1401
        foreach ($contentUpdateStruct->fields as $field) {
1402
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1403
                continue;
1404
            }
1405
1406
            $languageCodes[$field->languageCode] = true;
1407
        }
1408
1409
        return array_keys($languageCodes);
1410
    }
1411
1412
    /**
1413
     * Returns all language codes used in given $fields.
1414
     *
1415
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if no field value exists in initial language
1416
     *
1417
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1418
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1419
     *
1420
     * @return array
1421
     */
1422
    protected function getLanguageCodesForUpdate(APIContentUpdateStruct $contentUpdateStruct, APIContent $content)
1423
    {
1424
        $languageCodes = array_fill_keys($content->versionInfo->languageCodes, true);
1425
        $languageCodes[$contentUpdateStruct->initialLanguageCode] = true;
1426
1427
        $updatedLanguageCodes = $this->getUpdatedLanguageCodes($contentUpdateStruct);
1428
        foreach ($updatedLanguageCodes as $languageCode) {
1429
            $languageCodes[$languageCode] = true;
1430
        }
1431
1432
        return array_keys($languageCodes);
1433
    }
1434
1435
    /**
1436
     * Returns an array of fields like $fields[$field->fieldDefIdentifier][$field->languageCode].
1437
     *
1438
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType
1439
     *                                                                          or value is set for non-translatable field in language
1440
     *                                                                          other than main
1441
     *
1442
     * @param \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct
1443
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
1444
     * @param string $mainLanguageCode
1445
     *
1446
     * @return array
1447
     */
1448
    protected function mapFieldsForUpdate(
1449
        APIContentUpdateStruct $contentUpdateStruct,
1450
        ContentType $contentType,
1451
        $mainLanguageCode
1452
    ) {
1453
        $fields = array();
1454
1455
        foreach ($contentUpdateStruct->fields as $field) {
1456
            $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier);
1457
1458
            if ($fieldDefinition === null) {
1459
                throw new ContentValidationException(
1460
                    "Field definition '%identifier%' does not exist in given ContentType",
1461
                    ['%identifier%' => $field->fieldDefIdentifier]
1462
                );
1463
            }
1464
1465
            if ($field->languageCode === null) {
1466
                if ($fieldDefinition->isTranslatable) {
1467
                    $languageCode = $contentUpdateStruct->initialLanguageCode;
1468
                } else {
1469
                    $languageCode = $mainLanguageCode;
1470
                }
1471
                $field = $this->cloneField($field, array('languageCode' => $languageCode));
1472
            }
1473
1474 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...
1475
                throw new ContentValidationException(
1476
                    "A value is set for non translatable field definition '%identifier%' with language '%languageCode%'",
1477
                    ['%identifier%' => $field->fieldDefIdentifier, '%languageCode%' => $field->languageCode]
1478
                );
1479
            }
1480
1481
            $fields[$field->fieldDefIdentifier][$field->languageCode] = $field;
1482
        }
1483
1484
        return $fields;
1485
    }
1486
1487
    /**
1488
     * Publishes a content version.
1489
     *
1490
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1491
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1492
     *
1493
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version
1494
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1495
     *
1496
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1497
     *
1498
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1499
     */
1500
    public function publishVersion(APIVersionInfo $versionInfo)
1501
    {
1502
        $content = $this->internalLoadContent(
1503
            $versionInfo->contentInfo->id,
1504
            null,
1505
            $versionInfo->versionNo
1506
        );
1507
1508
        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...
1509
            throw new UnauthorizedException('content', 'publish', array('contentId' => $content->id));
1510
        }
1511
1512
        $this->repository->beginTransaction();
1513
        try {
1514
            $content = $this->internalPublishVersion($content->getVersionInfo());
1515
            $this->repository->commit();
1516
        } catch (Exception $e) {
1517
            $this->repository->rollback();
1518
            throw $e;
1519
        }
1520
1521
        return $content;
1522
    }
1523
1524
    /**
1525
     * Publishes a content version.
1526
     *
1527
     * Publishes a content version and deletes archive versions if they overflow max archive versions.
1528
     * Max archive versions are currently a configuration, but might be moved to be a param of ContentType in the future.
1529
     *
1530
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1531
     *
1532
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1533
     * @param int|null $publicationDate If null existing date is kept if there is one, otherwise current time is used.
1534
     *
1535
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1536
     */
1537
    protected function internalPublishVersion(APIVersionInfo $versionInfo, $publicationDate = null)
1538
    {
1539
        if (!$versionInfo->isDraft()) {
1540
            throw new BadStateException('$versionInfo', 'Only versions in draft status can be published.');
1541
        }
1542
1543
        $currentTime = time();
1544
        if ($publicationDate === null && $versionInfo->versionNo === 1) {
1545
            $publicationDate = $currentTime;
1546
        }
1547
1548
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
1549
        $metadataUpdateStruct->publicationDate = $publicationDate;
1550
        $metadataUpdateStruct->modificationDate = $currentTime;
1551
1552
        $contentId = $versionInfo->getContentInfo()->id;
1553
        $spiContent = $this->persistenceHandler->contentHandler()->publish(
1554
            $contentId,
1555
            $versionInfo->versionNo,
1556
            $metadataUpdateStruct
1557
        );
1558
1559
        $content = $this->domainMapper->buildContentDomainObject($spiContent);
1560
1561
        $this->publishUrlAliasesForContent($content);
1562
1563
        // Delete version archive overflow if any, limit is 0-50 (however 0 will mean 1 if content is unpublished)
1564
        $archiveList = $this->persistenceHandler->contentHandler()->listVersions(
1565
            $contentId,
1566
            APIVersionInfo::STATUS_ARCHIVED,
1567
            100 // Limited to avoid publishing taking to long, besides SE limitations this is why limit is max 50
1568
        );
1569
1570
        $maxVersionArchiveCount = max(0, min(50, $this->settings['default_version_archive_limit']));
1571
        while (!empty($archiveList) && count($archiveList) > $maxVersionArchiveCount) {
1572
            /** @var \eZ\Publish\SPI\Persistence\Content\VersionInfo $archiveVersion */
1573
            $archiveVersion = array_shift($archiveList);
1574
            $this->persistenceHandler->contentHandler()->deleteVersion(
1575
                $contentId,
1576
                $archiveVersion->versionNo
1577
            );
1578
        }
1579
1580
        return $content;
1581
    }
1582
1583
    /**
1584
     * Removes the given version.
1585
     *
1586
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is in
1587
     *         published state or is the last version of the Content
1588
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version
1589
     *
1590
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1591
     */
1592
    public function deleteVersion(APIVersionInfo $versionInfo)
1593
    {
1594
        if ($versionInfo->isPublished()) {
1595
            throw new BadStateException(
1596
                '$versionInfo',
1597
                'Version is published and can not be removed'
1598
            );
1599
        }
1600
1601 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...
1602
            throw new UnauthorizedException(
1603
                'content',
1604
                'versionremove',
1605
                array('contentId' => $versionInfo->contentInfo->id, 'versionNo' => $versionInfo->versionNo)
1606
            );
1607
        }
1608
1609
        $versionList = $this->persistenceHandler->contentHandler()->listVersions(
1610
            $versionInfo->contentInfo->id,
1611
            null,
1612
            2
1613
        );
1614
1615
        if (count($versionList) === 1) {
1616
            throw new BadStateException(
1617
                '$versionInfo',
1618
                'Version is the last version of the Content and can not be removed'
1619
            );
1620
        }
1621
1622
        $this->repository->beginTransaction();
1623
        try {
1624
            $this->persistenceHandler->contentHandler()->deleteVersion(
1625
                $versionInfo->getContentInfo()->id,
1626
                $versionInfo->versionNo
1627
            );
1628
            $this->repository->commit();
1629
        } catch (Exception $e) {
1630
            $this->repository->rollback();
1631
            throw $e;
1632
        }
1633
    }
1634
1635
    /**
1636
     * Loads all versions for the given content.
1637
     *
1638
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions
1639
     *
1640
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1641
     *
1642
     * @return \eZ\Publish\API\Repository\Values\Content\VersionInfo[] Sorted by creation date
1643
     */
1644
    public function loadVersions(ContentInfo $contentInfo)
1645
    {
1646
        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...
1647
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentInfo->id));
1648
        }
1649
1650
        $spiVersionInfoList = $this->persistenceHandler->contentHandler()->listVersions($contentInfo->id);
1651
1652
        $versions = array();
1653 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...
1654
            $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
1655
            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...
1656
                throw new UnauthorizedException('content', 'versionread', array('versionId' => $versionInfo->id));
1657
            }
1658
1659
            $versions[] = $versionInfo;
1660
        }
1661
1662
        return $versions;
1663
    }
1664
1665
    /**
1666
     * Copies the content to a new location. If no version is given,
1667
     * all versions are copied, otherwise only the given version.
1668
     *
1669
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location
1670
     *
1671
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1672
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to
1673
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1674
     *
1675
     * @return \eZ\Publish\API\Repository\Values\Content\Content
1676
     */
1677
    public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, APIVersionInfo $versionInfo = null)
1678
    {
1679
        $destinationLocation = $this->repository->getLocationService()->loadLocation(
1680
            $destinationLocationCreateStruct->parentLocationId
1681
        );
1682 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...
1683
            throw new UnauthorizedException(
1684
                'content',
1685
                'create',
1686
                [
1687
                    'parentLocationId' => $destinationLocationCreateStruct->parentLocationId,
1688
                    'sectionId' => $contentInfo->sectionId,
1689
                ]
1690
            );
1691
        }
1692
1693
        $defaultObjectStates = $this->getDefaultObjectStates();
1694
1695
        $this->repository->beginTransaction();
1696
        try {
1697
            $spiContent = $this->persistenceHandler->contentHandler()->copy(
1698
                $contentInfo->id,
1699
                $versionInfo ? $versionInfo->versionNo : null
1700
            );
1701
1702
            foreach ($defaultObjectStates as $objectStateGroupId => $objectState) {
1703
                $this->persistenceHandler->objectStateHandler()->setContentState(
1704
                    $spiContent->versionInfo->contentInfo->id,
1705
                    $objectStateGroupId,
1706
                    $objectState->id
1707
                );
1708
            }
1709
1710
            $content = $this->internalPublishVersion(
1711
                $this->domainMapper->buildVersionInfoDomainObject($spiContent->versionInfo),
1712
                $spiContent->versionInfo->creationDate
1713
            );
1714
1715
            $this->repository->getLocationService()->createLocation(
1716
                $content->getVersionInfo()->getContentInfo(),
1717
                $destinationLocationCreateStruct
1718
            );
1719
            $this->repository->commit();
1720
        } catch (Exception $e) {
1721
            $this->repository->rollback();
1722
            throw $e;
1723
        }
1724
1725
        return $this->internalLoadContent($content->id);
1726
    }
1727
1728
    /**
1729
     * Loads all outgoing relations for the given version.
1730
     *
1731
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1732
     *
1733
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
1734
     *
1735
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1736
     */
1737
    public function loadRelations(APIVersionInfo $versionInfo)
1738
    {
1739
        if ($versionInfo->isPublished()) {
1740
            $function = 'read';
1741
        } else {
1742
            $function = 'versionread';
1743
        }
1744
1745
        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...
1746
            throw new UnauthorizedException('content', $function);
1747
        }
1748
1749
        $contentInfo = $versionInfo->getContentInfo();
1750
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1751
            $contentInfo->id,
1752
            $versionInfo->versionNo
1753
        );
1754
1755
        /** @var $relations \eZ\Publish\API\Repository\Values\Content\Relation[] */
1756
        $relations = array();
1757 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...
1758
            $destinationContentInfo = $this->internalLoadContentInfo($spiRelation->destinationContentId);
1759
            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...
1760
                continue;
1761
            }
1762
1763
            $relations[] = $this->domainMapper->buildRelationDomainObject(
1764
                $spiRelation,
1765
                $contentInfo,
1766
                $destinationContentInfo
1767
            );
1768
        }
1769
1770
        return $relations;
1771
    }
1772
1773
    /**
1774
     * Loads all incoming relations for a content object.
1775
     *
1776
     * The relations come only from published versions of the source content objects
1777
     *
1778
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version
1779
     *
1780
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1781
     *
1782
     * @return \eZ\Publish\API\Repository\Values\Content\Relation[]
1783
     */
1784
    public function loadReverseRelations(ContentInfo $contentInfo)
1785
    {
1786
        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...
1787
            throw new UnauthorizedException('content', 'reverserelatedlist', array('contentId' => $contentInfo->id));
1788
        }
1789
1790
        $spiRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
1791
            $contentInfo->id
1792
        );
1793
1794
        $returnArray = array();
1795 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...
1796
            $sourceContentInfo = $this->internalLoadContentInfo($spiRelation->sourceContentId);
1797
            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...
1798
                continue;
1799
            }
1800
1801
            $returnArray[] = $this->domainMapper->buildRelationDomainObject(
1802
                $spiRelation,
1803
                $sourceContentInfo,
1804
                $contentInfo
1805
            );
1806
        }
1807
1808
        return $returnArray;
1809
    }
1810
1811
    /**
1812
     * Adds a relation of type common.
1813
     *
1814
     * The source of the relation is the content and version
1815
     * referenced by $versionInfo.
1816
     *
1817
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version
1818
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1819
     *
1820
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1821
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation
1822
     *
1823
     * @return \eZ\Publish\API\Repository\Values\Content\Relation the newly created relation
1824
     */
1825
    public function addRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1826
    {
1827
        $sourceVersion = $this->loadVersionInfoById(
1828
            $sourceVersion->contentInfo->id,
1829
            $sourceVersion->versionNo
1830
        );
1831
1832
        if (!$sourceVersion->isDraft()) {
1833
            throw new BadStateException(
1834
                '$sourceVersion',
1835
                'Relations of type common can only be added to versions of status draft'
1836
            );
1837
        }
1838
1839
        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...
1840
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1841
        }
1842
1843
        $sourceContentInfo = $sourceVersion->getContentInfo();
1844
1845
        $this->repository->beginTransaction();
1846
        try {
1847
            $spiRelation = $this->persistenceHandler->contentHandler()->addRelation(
1848
                new SPIRelationCreateStruct(
1849
                    array(
1850
                        'sourceContentId' => $sourceContentInfo->id,
1851
                        'sourceContentVersionNo' => $sourceVersion->versionNo,
1852
                        'sourceFieldDefinitionId' => null,
1853
                        'destinationContentId' => $destinationContent->id,
1854
                        'type' => APIRelation::COMMON,
1855
                    )
1856
                )
1857
            );
1858
            $this->repository->commit();
1859
        } catch (Exception $e) {
1860
            $this->repository->rollback();
1861
            throw $e;
1862
        }
1863
1864
        return $this->domainMapper->buildRelationDomainObject($spiRelation, $sourceContentInfo, $destinationContent);
1865
    }
1866
1867
    /**
1868
     * Removes a relation of type COMMON from a draft.
1869
     *
1870
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version
1871
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the version is not a draft
1872
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination
1873
     *
1874
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $sourceVersion
1875
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContent
1876
     */
1877
    public function deleteRelation(APIVersionInfo $sourceVersion, ContentInfo $destinationContent)
1878
    {
1879
        $sourceVersion = $this->loadVersionInfoById(
1880
            $sourceVersion->contentInfo->id,
1881
            $sourceVersion->versionNo
1882
        );
1883
1884
        if (!$sourceVersion->isDraft()) {
1885
            throw new BadStateException(
1886
                '$sourceVersion',
1887
                'Relations of type common can only be removed from versions of status draft'
1888
            );
1889
        }
1890
1891
        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...
1892
            throw new UnauthorizedException('content', 'edit', array('contentId' => $sourceVersion->contentInfo->id));
1893
        }
1894
1895
        $spiRelations = $this->persistenceHandler->contentHandler()->loadRelations(
1896
            $sourceVersion->getContentInfo()->id,
1897
            $sourceVersion->versionNo,
1898
            APIRelation::COMMON
1899
        );
1900
1901
        if (empty($spiRelations)) {
1902
            throw new InvalidArgumentException(
1903
                '$sourceVersion',
1904
                'There are no relations of type COMMON for the given destination'
1905
            );
1906
        }
1907
1908
        // there should be only one relation of type COMMON for each destination,
1909
        // but in case there were ever more then one, we will remove them all
1910
        // @todo: alternatively, throw BadStateException?
1911
        $this->repository->beginTransaction();
1912
        try {
1913
            foreach ($spiRelations as $spiRelation) {
1914
                if ($spiRelation->destinationContentId == $destinationContent->id) {
1915
                    $this->persistenceHandler->contentHandler()->removeRelation(
1916
                        $spiRelation->id,
1917
                        APIRelation::COMMON
1918
                    );
1919
                }
1920
            }
1921
            $this->repository->commit();
1922
        } catch (Exception $e) {
1923
            $this->repository->rollback();
1924
            throw $e;
1925
        }
1926
    }
1927
1928
    /**
1929
     * Adds translation information to the content object.
1930
     *
1931
     * @example Examples/translation_5x.php
1932
     *
1933
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed add a translation info
1934
     *
1935
     * @param \eZ\Publish\API\Repository\Values\Content\TranslationInfo $translationInfo
1936
     *
1937
     * @since 5.0
1938
     */
1939
    public function addTranslationInfo(TranslationInfo $translationInfo)
1940
    {
1941
        throw new NotImplementedException(__METHOD__);
1942
    }
1943
1944
    /**
1945
     * lists the translations done on this content object.
1946
     *
1947
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed read translation infos
1948
     *
1949
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1950
     * @param array $filter
1951
     *
1952
     * @todo TBD - filter by source version destination version and languages
1953
     *
1954
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo[]
1955
     *
1956
     * @since 5.0
1957
     */
1958
    public function loadTranslationInfos(ContentInfo $contentInfo, array $filter = array())
1959
    {
1960
        throw new NotImplementedException(__METHOD__);
1961
    }
1962
1963
    /**
1964
     * {@inheritdoc}
1965
     */
1966
    public function removeTranslation(ContentInfo $contentInfo, $languageCode)
1967
    {
1968
        @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...
1969
            __METHOD__ . ' is deprecated, use deleteTranslation instead',
1970
            E_USER_DEPRECATED
1971
        );
1972
        $this->deleteTranslation($contentInfo, $languageCode);
1973
    }
1974
1975
    /**
1976
     * Delete Content item Translation from all Versions (including archived ones) of a Content Object.
1977
     *
1978
     * NOTE: this operation is risky and permanent, so user interface should provide a warning before performing it.
1979
     *
1980
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
1981
     *         is the Main Translation of a Content Item.
1982
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
1983
     *         to delete the content (in one of the locations of the given Content Item).
1984
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
1985
     *         is invalid for the given content.
1986
     *
1987
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
1988
     * @param string $languageCode
1989
     *
1990
     * @since 6.13
1991
     */
1992
    public function deleteTranslation(ContentInfo $contentInfo, $languageCode)
1993
    {
1994
        if ($contentInfo->mainLanguageCode === $languageCode) {
1995
            throw new BadStateException(
1996
                '$languageCode',
1997
                'Specified translation is the main translation of the Content Object'
1998
            );
1999
        }
2000
2001
        $translationWasFound = false;
2002
        $this->repository->beginTransaction();
2003
        try {
2004
            foreach ($this->loadVersions($contentInfo) as $versionInfo) {
2005 View Code Duplication
                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...
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...
2006
                    throw new UnauthorizedException(
2007
                        'content',
2008
                        'remove',
2009
                        ['contentId' => $contentInfo->id, 'versionNo' => $versionInfo->versionNo]
2010
                    );
2011
                }
2012
2013
                if (!in_array($languageCode, $versionInfo->languageCodes)) {
2014
                    continue;
2015
                }
2016
2017
                $translationWasFound = true;
2018
2019
                // If the translation is the version's only one, delete the version
2020
                if (count($versionInfo->languageCodes) < 2) {
2021
                    $this->persistenceHandler->contentHandler()->deleteVersion(
2022
                        $versionInfo->getContentInfo()->id,
2023
                        $versionInfo->versionNo
2024
                    );
2025
                }
2026
            }
2027
2028
            if (!$translationWasFound) {
2029
                throw new InvalidArgumentException(
2030
                    '$languageCode',
2031
                    sprintf(
2032
                        '%s does not exist in the Content item(id=%d)',
2033
                        $languageCode,
2034
                        $contentInfo->id
2035
                    )
2036
                );
2037
            }
2038
2039
            $this->persistenceHandler->contentHandler()->deleteTranslationFromContent(
2040
                $contentInfo->id,
2041
                $languageCode
2042
            );
2043
            $locationIds = array_map(
2044
                function (Location $location) {
2045
                    return $location->id;
2046
                },
2047
                $this->repository->getLocationService()->loadLocations($contentInfo)
2048
            );
2049
            $this->persistenceHandler->urlAliasHandler()->translationRemoved(
2050
                $locationIds,
2051
                $languageCode
2052
            );
2053
            $this->repository->commit();
2054
        } catch (InvalidArgumentException $e) {
2055
            $this->repository->rollback();
2056
            throw $e;
2057
        } catch (BadStateException $e) {
2058
            $this->repository->rollback();
2059
            throw $e;
2060
        } catch (UnauthorizedException $e) {
2061
            $this->repository->rollback();
2062
            throw $e;
2063
        } catch (Exception $e) {
2064
            $this->repository->rollback();
2065
            // cover generic unexpected exception to fulfill API promise on @throws
2066
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2067
        }
2068
    }
2069
2070
    /**
2071
     * Delete specified Translation from a Content Draft.
2072
     *
2073
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the specified Translation
2074
     *         is the only one the Content Draft has or it is the main Translation of a Content Object.
2075
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed
2076
     *         to edit the Content (in one of the locations of the given Content Object).
2077
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if languageCode argument
2078
     *         is invalid for the given Draft.
2079
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if specified Version was not found
2080
     *
2081
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft
2082
     * @param string $languageCode Language code of the Translation to be removed
2083
     *
2084
     * @return \eZ\Publish\API\Repository\Values\Content\Content Content Draft w/o the specified Translation
2085
     *
2086
     * @since 6.12
2087
     */
2088
    public function deleteTranslationFromDraft(APIVersionInfo $versionInfo, $languageCode)
2089
    {
2090
        if (!$versionInfo->isDraft()) {
2091
            throw new BadStateException(
2092
                '$versionInfo',
2093
                'Version is not a draft, so Translations cannot be modified. Create a Draft before proceeding'
2094
            );
2095
        }
2096
2097
        if ($versionInfo->contentInfo->mainLanguageCode === $languageCode) {
2098
            throw new BadStateException(
2099
                '$languageCode',
2100
                'Specified Translation is the main Translation of the Content Object. Change it before proceeding.'
2101
            );
2102
        }
2103
2104
        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...
2105
            throw new UnauthorizedException(
2106
                'content', 'edit', ['contentId' => $versionInfo->contentInfo->id]
2107
            );
2108
        }
2109
2110
        if (!in_array($languageCode, $versionInfo->languageCodes)) {
2111
            throw new InvalidArgumentException(
2112
                '$languageCode',
2113
                sprintf(
2114
                    'The Version (ContentId=%d, VersionNo=%d) is not translated into %s',
2115
                    $versionInfo->contentInfo->id,
2116
                    $versionInfo->versionNo,
2117
                    $languageCode
2118
                )
2119
            );
2120
        }
2121
2122
        if (count($versionInfo->languageCodes) === 1) {
2123
            throw new BadStateException(
2124
                '$languageCode',
2125
                'Specified Translation is the only one Content Object Version has'
2126
            );
2127
        }
2128
2129
        $this->repository->beginTransaction();
2130
        try {
2131
            $spiContent = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
2132
                $versionInfo->contentInfo->id,
2133
                $versionInfo->versionNo,
2134
                $languageCode
2135
            );
2136
            $this->repository->commit();
2137
2138
            return $this->domainMapper->buildContentDomainObject($spiContent);
2139
        } catch (APINotFoundException $e) {
2140
            // avoid wrapping expected NotFoundException in BadStateException handled below
2141
            $this->repository->rollback();
2142
            throw $e;
2143
        } catch (Exception $e) {
2144
            $this->repository->rollback();
2145
            // cover generic unexpected exception to fulfill API promise on @throws
2146
            throw new BadStateException('$contentInfo', 'Translation removal failed', $e);
2147
        }
2148
    }
2149
2150
    /**
2151
     * Instantiates a new content create struct object.
2152
     *
2153
     * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable
2154
     *
2155
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
2156
     * @param string $mainLanguageCode
2157
     *
2158
     * @return \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct
2159
     */
2160
    public function newContentCreateStruct(ContentType $contentType, $mainLanguageCode)
2161
    {
2162
        return new ContentCreateStruct(
2163
            array(
2164
                'contentType' => $contentType,
2165
                'mainLanguageCode' => $mainLanguageCode,
2166
                'alwaysAvailable' => $contentType->defaultAlwaysAvailable,
2167
            )
2168
        );
2169
    }
2170
2171
    /**
2172
     * Instantiates a new content meta data update struct.
2173
     *
2174
     * @return \eZ\Publish\API\Repository\Values\Content\ContentMetadataUpdateStruct
2175
     */
2176
    public function newContentMetadataUpdateStruct()
2177
    {
2178
        return new ContentMetadataUpdateStruct();
2179
    }
2180
2181
    /**
2182
     * Instantiates a new content update struct.
2183
     *
2184
     * @return \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct
2185
     */
2186
    public function newContentUpdateStruct()
2187
    {
2188
        return new ContentUpdateStruct();
2189
    }
2190
2191
    /**
2192
     * Instantiates a new TranslationInfo object.
2193
     *
2194
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationInfo
2195
     */
2196
    public function newTranslationInfo()
2197
    {
2198
        return new TranslationInfo();
2199
    }
2200
2201
    /**
2202
     * Instantiates a Translation object.
2203
     *
2204
     * @return \eZ\Publish\API\Repository\Values\Content\TranslationValues
2205
     */
2206
    public function newTranslationValues()
2207
    {
2208
        return new TranslationValues();
2209
    }
2210
}
2211