Completed
Push — 6.7 ( b4417c...73d054 )
by Łukasz
20:26
created

ContentService::buildSPILocationCreateStructs()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

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

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

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

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

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

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

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