Completed
Push — 6.13 ( f036a2...6438a8 )
by André
50:00 queued 26:38
created

ContentService::internalLoadContent()   B

Complexity

Conditions 9
Paths 31

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 31
nop 5
dl 0
loc 50
rs 7.5353
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\ContentService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\ContentService as ContentServiceInterface;
12
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
13
use eZ\Publish\Core\Repository\Values\Content\Location;
14
use eZ\Publish\API\Repository\Values\Content\Language;
15
use eZ\Publish\SPI\Persistence\Handler;
16
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct as APIContentUpdateStruct;
17
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
18
use eZ\Publish\API\Repository\Values\Content\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
        // @todo SPI should also support null to avoid concurrency issues
232
        if ($versionNo === null) {
233
            $versionNo = $this->loadContentInfo($contentId)->currentVersionNo;
234
        }
235
236
        try {
237
            $spiVersionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo(
238
                $contentId,
239
                $versionNo
240
            );
241
        } catch (APINotFoundException $e) {
242
            throw new NotFoundException(
243
                'VersionInfo',
244
                array(
245
                    'contentId' => $contentId,
246
                    'versionNo' => $versionNo,
247
                ),
248
                $e
249
            );
250
        }
251
252
        $versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
253
254
        if ($versionInfo->isPublished()) {
255
            $function = 'read';
256
        } else {
257
            $function = 'versionread';
258
        }
259
260
        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...
261
            throw new UnauthorizedException('content', $function, array('contentId' => $contentId));
262
        }
263
264
        return $versionInfo;
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270
    public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
271
    {
272
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
273
        if ($useAlwaysAvailable && !$contentInfo->alwaysAvailable) {
274
            $useAlwaysAvailable = false;
275
        }
276
277
        return $this->loadContent(
278
            $contentInfo->id,
279
            $languages,
280
            $versionNo,// On purpose pass as-is and not use $contentInfo, to make sure to return actual current version on null
281
            $useAlwaysAvailable
282
        );
283
    }
284
285
    /**
286
     * {@inheritdoc}
287
     */
288
    public function loadContentByVersionInfo(APIVersionInfo $versionInfo, array $languages = null, $useAlwaysAvailable = true)
289
    {
290
        // Change $useAlwaysAvailable to false to avoid contentInfo lookup if we know alwaysAvailable is disabled
291
        if ($useAlwaysAvailable && !$versionInfo->getContentInfo()->alwaysAvailable) {
292
            $useAlwaysAvailable = false;
293
        }
294
295
        return $this->loadContent(
296
            $versionInfo->getContentInfo()->id,
297
            $languages,
298
            $versionInfo->versionNo,
299
            $useAlwaysAvailable
300
        );
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306 View Code Duplication
    public function loadContent($contentId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
307
    {
308
        $content = $this->internalLoadContent($contentId, $languages, $versionNo, false, $useAlwaysAvailable);
309
310
        if (!$this->repository->canUser('content', 'read', $content)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

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

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

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

Loading history...
316
        ) {
317
            throw new UnauthorizedException('content', 'versionread', array('contentId' => $contentId, 'versionNo' => $versionNo));
318
        }
319
320
        return $content;
321
    }
322
323
    /**
324
     * Loads content in a version of the given content object.
325
     *
326
     * If no version number is given, the method returns the current version
327
     *
328
     * @internal
329
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist
330
     *
331
     * @param mixed $id
332
     * @param array|null $languages A language priority, filters returned fields and is used as prioritized language code on
333
     *                         returned value object. If not given all languages are returned.
334
     * @param int|null $versionNo the version number. If not given the current version is returned
335
     * @param bool $isRemoteId
336
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
337
     *
338
     * @return \eZ\Publish\API\Repository\Values\Content\Content
339
     */
340
    public function internalLoadContent($id, array $languages = null, $versionNo = null, $isRemoteId = false, $useAlwaysAvailable = true)
341
    {
342
        try {
343
            // Get Content ID if lookup by remote ID
344
            if ($isRemoteId) {
345
                $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($id);
346
                $id = $spiContentInfo->id;
347
                // Set $isRemoteId to false as the next loads will be for content id now that we have it (for exception use now)
348
                $isRemoteId = false;
349
            }
350
351
            $loadLanguages = $languages;
352
            $alwaysAvailableLanguageCode = null;
353
            // Set main language on $languages filter if not empty (all) and $useAlwaysAvailable being true
354
            // @todo Move use always available logic to SPI load methods, like done in location handler in 7.x
355
            if (!empty($loadLanguages) && $useAlwaysAvailable) {
356
                if (!isset($spiContentInfo)) {
357
                    $spiContentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($id);
358
                }
359
360
                if ($spiContentInfo->alwaysAvailable) {
361
                    $loadLanguages[] = $alwaysAvailableLanguageCode = $spiContentInfo->mainLanguageCode;
362
                    $loadLanguages = array_unique($loadLanguages);
363
                }
364
            }
365
366
            $spiContent = $this->persistenceHandler->contentHandler()->load(
367
                $id,
368
                $versionNo,
369
                $loadLanguages
0 ignored issues
show
Bug introduced by
It seems like $loadLanguages defined by $languages on line 351 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...
370
            );
371
        } catch (APINotFoundException $e) {
372
            throw new NotFoundException(
373
                'Content',
374
                array(
375
                    $isRemoteId ? 'remoteId' : 'id' => $id,
376
                    'languages' => $languages,
377
                    'versionNo' => $versionNo,
378
                ),
379
                $e
380
            );
381
        }
382
383
        return $this->domainMapper->buildContentDomainObject(
384
            $spiContent,
385
            null,
386
            empty($languages) ? null : $languages,
387
            $alwaysAvailableLanguageCode
388
        );
389
    }
390
391
    /**
392
     * Loads content in a version for the content object reference by the given remote id.
393
     *
394
     * If no version is given, the method returns the current version
395
     *
396
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist
397
     * @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
398
     *
399
     * @param string $remoteId
400
     * @param array $languages A language filter for fields. If not given all languages are returned
401
     * @param int $versionNo the version number. If not given the current version is returned
402
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true
403
     *
404
     * @return \eZ\Publish\API\Repository\Values\Content\Content
405
     */
406 View Code Duplication
    public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true)
407
    {
408
        $content = $this->internalLoadContent($remoteId, $languages, $versionNo, true, $useAlwaysAvailable);
409
410
        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...
411
            throw new UnauthorizedException('content', 'read', array('remoteId' => $remoteId));
412
        }
413
414
        if (
415
            !$content->getVersionInfo()->isPublished()
416
            && !$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...
417
        ) {
418
            throw new UnauthorizedException('content', 'versionread', array('remoteId' => $remoteId, 'versionNo' => $versionNo));
419
        }
420
421
        return $content;
422
    }
423
424
    /**
425
     * Bulk-load Content items by the list of ContentInfo Value Objects.
426
     *
427
     * Note: it does not throw exceptions on load, just ignores erroneous Content item.
428
     * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
429
     * allowed to access every Content on the list.
430
     *
431
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
432
     * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
433
     *                            returned value object. If not given all languages are returned.
434
     * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
435
     *                                 unless all languages have been asked for.
436
     *
437
     * @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
438
     */
439
    public function loadContentListByContentInfo(
440
        array $contentInfoList,
441
        array $languages = [],
442
        $useAlwaysAvailable = true
443
    ) {
444
        $loadAllLanguages = $languages === Language::ALL;
445
        $contentIds = [];
446
        $translations = $languages;
447
        foreach ($contentInfoList as $contentInfo) {
448
            $contentIds[] = $contentInfo->id;
449
            // Unless we are told to load all languages, we add main language to translations so they are loaded too
450
            // Might in some case load more languages then intended, but prioritised handling will pick right one
451
            if (!$loadAllLanguages && $useAlwaysAvailable && $contentInfo->alwaysAvailable) {
452
                $translations[] = $contentInfo->mainLanguageCode;
453
            }
454
        }
455
        $translations = array_unique($translations);
456
457
        $spiContentList = $this->persistenceHandler->contentHandler()->loadContentList(
458
            $contentIds,
459
            $translations
460
        );
461
        $contentList = [];
462
        foreach ($spiContentList as $contentId => $spiContent) {
463
            $contentInfo = $spiContent->versionInfo->contentInfo;
464
            $contentList[$contentId] = $this->domainMapper->buildContentDomainObject(
465
                $spiContent,
466
                null,
467
                $languages,
468
                $contentInfo->alwaysAvailable ? $contentInfo->mainLanguageCode : null
469
            );
470
        }
471
472
        return $contentList;
473
    }
474
475
    /**
476
     * Creates a new content draft assigned to the authenticated user.
477
     *
478
     * If a different userId is given in $contentCreateStruct it is assigned to the given user
479
     * but this required special rights for the authenticated user
480
     * (this is useful for content staging where the transfer process does not
481
     * have to authenticate with the user which created the content object in the source server).
482
     * The user has to publish the draft if it should be visible.
483
     * In 4.x at least one location has to be provided in the location creation array.
484
     *
485
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location
486
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the provided remoteId exists in the system, required properties on
487
     *                                                                        struct are missing or invalid, or if multiple locations are under the
488
     *                                                                        same parent.
489
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid,
490
     *                                                                               or if a required field is missing / set to an empty value.
491
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException If field definition does not exist in the ContentType,
492
     *                                                                          or value is set for non-translatable field in language
493
     *                                                                          other than main.
494
     *
495
     * @param \eZ\Publish\API\Repository\Values\Content\ContentCreateStruct $contentCreateStruct
496
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs For each location parent under which a location should be created for the content
497
     *
498
     * @return \eZ\Publish\API\Repository\Values\Content\Content - the newly created content draft
499
     */
500
    public function createContent(APIContentCreateStruct $contentCreateStruct, array $locationCreateStructs = array())
501
    {
502
        if ($contentCreateStruct->mainLanguageCode === null) {
503
            throw new InvalidArgumentException('$contentCreateStruct', "'mainLanguageCode' property must be set");
504
        }
505
506
        if ($contentCreateStruct->contentType === null) {
507
            throw new InvalidArgumentException('$contentCreateStruct', "'contentType' property must be set");
508
        }
509
510
        $contentCreateStruct = clone $contentCreateStruct;
511
512
        if ($contentCreateStruct->ownerId === null) {
513
            $contentCreateStruct->ownerId = $this->repository->getCurrentUserReference()->getUserId();
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Reposito...tCurrentUserReference() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::getCurrentUserReference() instead. Get current user reference.

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

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

Loading history...
514
        }
515
516
        if ($contentCreateStruct->alwaysAvailable === null) {
517
            $contentCreateStruct->alwaysAvailable = $contentCreateStruct->contentType->defaultAlwaysAvailable ?: false;
518
        }
519
520
        $contentCreateStruct->contentType = $this->repository->getContentTypeService()->loadContentType(
521
            $contentCreateStruct->contentType->id
522
        );
523
524
        if (empty($contentCreateStruct->sectionId)) {
525
            if (isset($locationCreateStructs[0])) {
526
                $location = $this->repository->getLocationService()->loadLocation(
527
                    $locationCreateStructs[0]->parentLocationId
528
                );
529
                $contentCreateStruct->sectionId = $location->contentInfo->sectionId;
530
            } else {
531
                $contentCreateStruct->sectionId = 1;
532
            }
533
        }
534
535
        if (!$this->repository->canUser('content', 'create', $contentCreateStruct, $locationCreateStructs)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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

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

Loading history...
536
            throw new UnauthorizedException(
537
                'content',
538
                'create',
539
                array(
540
                    'parentLocationId' => isset($locationCreateStructs[0]) ?
541
                            $locationCreateStructs[0]->parentLocationId :
542
                            null,
543
                    'sectionId' => $contentCreateStruct->sectionId,
544
                )
545
            );
546
        }
547
548
        if (!empty($contentCreateStruct->remoteId)) {
549
            try {
550
                $this->loadContentByRemoteId($contentCreateStruct->remoteId);
551
552
                throw new InvalidArgumentException(
553
                    '$contentCreateStruct',
554
                    "Another content with remoteId '{$contentCreateStruct->remoteId}' exists"
555
                );
556
            } catch (APINotFoundException $e) {
557
                // Do nothing
558
            }
559
        } else {
560
            $contentCreateStruct->remoteId = $this->domainMapper->getUniqueHash($contentCreateStruct);
561
        }
562
563
        $spiLocationCreateStructs = $this->buildSPILocationCreateStructs($locationCreateStructs);
564
565
        $languageCodes = $this->getLanguageCodesForCreate($contentCreateStruct);
566
        $fields = $this->mapFieldsForCreate($contentCreateStruct);
567
568
        $fieldValues = array();
569
        $spiFields = array();
570
        $allFieldErrors = array();
571
        $inputRelations = array();
572
        $locationIdToContentIdMapping = array();
573
574
        foreach ($contentCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
575
            /** @var $fieldType \eZ\Publish\Core\FieldType\FieldType */
576
            $fieldType = $this->fieldTypeRegistry->getFieldType(
577
                $fieldDefinition->fieldTypeIdentifier
578
            );
579
580
            foreach ($languageCodes as $languageCode) {
581
                $isEmptyValue = false;
582
                $valueLanguageCode = $fieldDefinition->isTranslatable ? $languageCode : $contentCreateStruct->mainLanguageCode;
583
                $isLanguageMain = $languageCode === $contentCreateStruct->mainLanguageCode;
584
                if (isset($fields[$fieldDefinition->identifier][$valueLanguageCode])) {
585
                    $fieldValue = $fields[$fieldDefinition->identifier][$valueLanguageCode]->value;
586
                } else {
587
                    $fieldValue = $fieldDefinition->defaultValue;
588
                }
589
590
                $fieldValue = $fieldType->acceptValue($fieldValue);
591
592 View Code Duplication
                if ($fieldType->isEmptyValue($fieldValue)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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