Completed
Push — location_content_property ( b180d5 )
by André
16:28
created

Handler::loadContentList()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 43
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
nc 7
nop 2
dl 0
loc 43
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Content Handler 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\Persistence\Legacy\Content;
10
11
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
12
use eZ\Publish\SPI\Persistence\Content\Handler as BaseContentHandler;
13
use eZ\Publish\SPI\Persistence\Content\Type\Handler as ContentTypeHandler;
14
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter;
15
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway as UrlAliasGateway;
16
use eZ\Publish\SPI\Persistence\Content;
17
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
18
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
19
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
20
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
21
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
22
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
23
24
/**
25
 * The Content Handler stores Content and ContentType objects.
26
 */
27
class Handler implements BaseContentHandler
28
{
29
    /**
30
     * Content gateway.
31
     *
32
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway
33
     */
34
    protected $contentGateway;
35
36
    /**
37
     * Location gateway.
38
     *
39
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway
40
     */
41
    protected $locationGateway;
42
43
    /**
44
     * Mapper.
45
     *
46
     * @var Mapper
47
     */
48
    protected $mapper;
49
50
    /**
51
     * FieldHandler.
52
     *
53
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\FieldHandler
54
     */
55
    protected $fieldHandler;
56
57
    /**
58
     * URL slug converter.
59
     *
60
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter
61
     */
62
    protected $slugConverter;
63
64
    /**
65
     * UrlAlias gateway.
66
     *
67
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway
68
     */
69
    protected $urlAliasGateway;
70
71
    /**
72
     * ContentType handler.
73
     *
74
     * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
75
     */
76
    protected $contentTypeHandler;
77
78
    /**
79
     * Tree handler.
80
     *
81
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler
82
     */
83
    protected $treeHandler;
84
85
    /**
86
     * Creates a new content handler.
87
     *
88
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway $contentGateway
89
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway $locationGateway
90
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Mapper $mapper
91
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\FieldHandler $fieldHandler
92
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter $slugConverter
93
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway $urlAliasGateway
94
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
95
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler $treeHandler
96
     */
97
    public function __construct(
98
        Gateway $contentGateway,
99
        LocationGateway $locationGateway,
100
        Mapper $mapper,
101
        FieldHandler $fieldHandler,
102
        SlugConverter $slugConverter,
103
        UrlAliasGateway $urlAliasGateway,
104
        ContentTypeHandler $contentTypeHandler,
105
        TreeHandler $treeHandler
106
    ) {
107
        $this->contentGateway = $contentGateway;
108
        $this->locationGateway = $locationGateway;
109
        $this->mapper = $mapper;
110
        $this->fieldHandler = $fieldHandler;
111
        $this->slugConverter = $slugConverter;
112
        $this->urlAliasGateway = $urlAliasGateway;
113
        $this->contentTypeHandler = $contentTypeHandler;
114
        $this->treeHandler = $treeHandler;
115
    }
116
117
    /**
118
     * Creates a new Content entity in the storage engine.
119
     *
120
     * The values contained inside the $content will form the basis of stored
121
     * entity.
122
     *
123
     * Will contain always a complete list of fields.
124
     *
125
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct Content creation struct.
126
     *
127
     * @return \eZ\Publish\SPI\Persistence\Content Content value object
128
     */
129
    public function create(CreateStruct $struct)
130
    {
131
        return $this->internalCreate($struct);
132
    }
133
134
    /**
135
     * Creates a new Content entity in the storage engine.
136
     *
137
     * The values contained inside the $content will form the basis of stored
138
     * entity.
139
     *
140
     * Will contain always a complete list of fields.
141
     *
142
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct Content creation struct.
143
     * @param mixed $versionNo Used by self::copy() to maintain version numbers
144
     *
145
     * @return \eZ\Publish\SPI\Persistence\Content Content value object
146
     */
147
    protected function internalCreate(CreateStruct $struct, $versionNo = 1)
148
    {
149
        $content = new Content();
150
151
        $content->fields = $struct->fields;
152
        $content->versionInfo = $this->mapper->createVersionInfoFromCreateStruct($struct, $versionNo);
153
154
        $content->versionInfo->contentInfo->id = $this->contentGateway->insertContentObject($struct, $versionNo);
155
        $content->versionInfo->id = $this->contentGateway->insertVersion(
156
            $content->versionInfo,
157
            $struct->fields
158
        );
159
160
        $contentType = $this->contentTypeHandler->load($struct->typeId);
161
        $this->fieldHandler->createNewFields($content, $contentType);
162
163
        // Create node assignments
164
        foreach ($struct->locations as $location) {
165
            $location->contentId = $content->versionInfo->contentInfo->id;
166
            $location->contentVersion = $content->versionInfo->versionNo;
167
            $this->locationGateway->createNodeAssignment(
168
                $location,
169
                $location->parentId,
170
                LocationGateway::NODE_ASSIGNMENT_OP_CODE_CREATE
171
            );
172
        }
173
174
        // Create names
175 View Code Duplication
        foreach ($content->versionInfo->names as $language => $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...
176
            $this->contentGateway->setName(
177
                $content->versionInfo->contentInfo->id,
178
                $content->versionInfo->versionNo,
179
                $name,
180
                $language
181
            );
182
        }
183
184
        return $content;
185
    }
186
187
    /**
188
     * Performs the publishing operations required to set the version identified by $updateStruct->versionNo and
189
     * $updateStruct->id as the published one.
190
     *
191
     * The publish procedure will:
192
     * - Create location nodes based on the node assignments
193
     * - Update the content object using the provided metadata update struct
194
     * - Update the node assignments
195
     * - Update location nodes of the content with the new published version
196
     * - Set content and version status to published
197
     *
198
     * @param int $contentId
199
     * @param int $versionNo
200
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $metaDataUpdateStruct
201
     *
202
     * @return \eZ\Publish\SPI\Persistence\Content The published Content
203
     */
204
    public function publish($contentId, $versionNo, MetadataUpdateStruct $metaDataUpdateStruct)
205
    {
206
        // Archive currently published version
207
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
208
        if ($versionInfo->contentInfo->currentVersionNo != $versionNo) {
209
            $this->setStatus(
210
                $contentId,
211
                VersionInfo::STATUS_ARCHIVED,
212
                $versionInfo->contentInfo->currentVersionNo
213
            );
214
        }
215
216
        // Set always available name for the content
217
        $metaDataUpdateStruct->name = $versionInfo->names[$versionInfo->contentInfo->mainLanguageCode];
218
219
        $this->contentGateway->updateContent($contentId, $metaDataUpdateStruct, $versionInfo);
220
        $this->locationGateway->createLocationsFromNodeAssignments(
221
            $contentId,
222
            $versionNo
223
        );
224
225
        $this->locationGateway->updateLocationsContentVersionNo($contentId, $versionNo);
226
        $this->setStatus($contentId, VersionInfo::STATUS_PUBLISHED, $versionNo);
227
228
        return $this->load($contentId, $versionNo);
229
    }
230
231
    /**
232
     * Creates a new draft version from $contentId in $version.
233
     *
234
     * Copies all fields from $contentId in $srcVersion and creates a new
235
     * version of the referred Content from it.
236
     *
237
     * Note: When creating a new draft in the old admin interface there will
238
     * also be an entry in the `eznode_assignment` created for the draft. This
239
     * is ignored in this implementation.
240
     *
241
     * @param mixed $contentId
242
     * @param mixed $srcVersion
243
     * @param mixed $userId
244
     *
245
     * @return \eZ\Publish\SPI\Persistence\Content
246
     */
247
    public function createDraftFromVersion($contentId, $srcVersion, $userId)
248
    {
249
        $content = $this->load($contentId, $srcVersion);
250
251
        // Create new version
252
        $content->versionInfo = $this->mapper->createVersionInfoForContent(
253
            $content,
254
            $this->contentGateway->getLastVersionNumber($contentId) + 1,
255
            $userId
256
        );
257
        $content->versionInfo->id = $this->contentGateway->insertVersion(
258
            $content->versionInfo,
259
            $content->fields
260
        );
261
262
        // Clone fields from previous version and append them to the new one
263
        $this->fieldHandler->createExistingFieldsInNewVersion($content);
264
265
        // Create relations for new version
266
        $relations = $this->contentGateway->loadRelations($contentId, $srcVersion);
267
        foreach ($relations as $relation) {
268
            $this->contentGateway->insertRelation(
269
                new RelationCreateStruct(
270
                    array(
271
                        'sourceContentId' => $contentId,
272
                        'sourceContentVersionNo' => $content->versionInfo->versionNo,
273
                        'sourceFieldDefinitionId' => $relation['ezcontentobject_link_contentclassattribute_id'],
274
                        'destinationContentId' => $relation['ezcontentobject_link_to_contentobject_id'],
275
                        'type' => (int)$relation['ezcontentobject_link_relation_type'],
276
                    )
277
                )
278
            );
279
        }
280
281
        // Create content names for new version
282
        foreach ($content->versionInfo->names as $language => $name) {
283
            $this->contentGateway->setName(
284
                $contentId,
285
                $content->versionInfo->versionNo,
286
                $name,
287
                $language
288
            );
289
        }
290
291
        return $content;
292
    }
293
294
    /**
295
     * Returns the raw data of a content object identified by $id, in a struct.
296
     *
297
     * A version to load must be specified. If you want to load the current
298
     * version of a content object use SearchHandler::findSingle() with the
299
     * ContentId criterion.
300
     *
301
     * Optionally a translation filter may be specified. If specified only the
302
     * translations with the listed language codes will be retrieved. If not,
303
     * all translations will be retrieved.
304
     *
305
     * @param int|string $id
306
     * @param int|string $version
307
     * @param string[] $translations
308
     *
309
     * @return \eZ\Publish\SPI\Persistence\Content Content value object
310
     */
311
    public function load($id, $version, array $translations = null)
312
    {
313
        $rows = $this->contentGateway->load($id, $version, $translations);
314
315
        if (empty($rows)) {
316
            throw new NotFound('content', "contentId: $id, versionNo: $version");
317
        }
318
319
        $contentObjects = $this->mapper->extractContentFromRows(
320
            $rows,
321
            $this->contentGateway->loadVersionedNameData(array(array('id' => $id, 'version' => $version)))
322
        );
323
        $content = $contentObjects[0];
324
        unset($rows, $contentObjects);
325
326
        $this->fieldHandler->loadExternalFieldData($content);
327
328
        return $content;
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     *
334
     * @todo
335
     * REVIEW NOTE: This methods ALWAYS handles AlwaysAvailable, this would perhaps be better handled in API layer like
336
     *              done on load(), thus making it optional. AND faster as it can loadContentInfoList via SPI cache.
337
     *              However then we would need a argument structure like used on gateway here.
338
     *              Would that be OK for loadContentList() in SPI layer??
339
     *              If so it should also take version number  as part of the structure.
340
     */
341
    public function loadContentList(array $contentIds, array $translations): array
342
    {
343
        $infoList = $this->mapper->extractContentInfoFromRows(
344
            $this->contentGateway->loadContentInfoList($contentIds)
345
        );
346
347
        if (empty($infoList)) {
348
            return [];
349
        }
350
351
        $IdVersionPairs = [];
352
        $IdVersionTranslationPairs = [];
353
        foreach ($infoList as $info) {
354
            $IdVersionTranslation = [
355
                'id' => $info->id,
356
                'version' => $info->currentVersionNo,
357
                'languages' => $translations
358
            ];
359
360
            if ($info->alwaysAvailable && !in_array($info->mainLanguageCode, $translations)) {
361
                $IdVersionTranslation['languages'][] = $info->mainLanguageCode;
362
            }
363
364
            $IdVersionTranslationPairs[] = $IdVersionTranslation;
365
            unset($IdVersionTranslation['languages']);
366
367
            $IdVersionPairs[] = $IdVersionTranslation;
368
        }
369
        unset($infoList, $info);
370
371
        $contentObjects = $this->mapper->extractContentFromRows(
372
            $this->contentGateway->loadContentList($IdVersionTranslationPairs),
373
            $this->contentGateway->loadVersionedNameData($IdVersionPairs)
374
        );
375
376
        $result = [];
377
        foreach ($contentObjects as $content) {
378
            $this->fieldHandler->loadExternalFieldData($content);
379
            $result[$content->versionInfo->contentInfo->id] = $content;
380
        }
381
382
        return $result;
383
    }
384
385
    /**
386
     * Returns the metadata object for a content identified by $contentId.
387
     *
388
     * @param int|string $contentId
389
     *
390
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
391
     */
392
    public function loadContentInfo($contentId)
393
    {
394
        return $this->treeHandler->loadContentInfo($contentId);
395
    }
396
397
    public function loadContentInfoList(array $contentIds)
398
    {
399
        $list = $this->mapper->extractContentInfoFromRows(
400
            $this->contentGateway->loadContentInfoList($contentIds)
401
        );
402
403
        $listByContentId = [];
404
        foreach ($list as $item) {
405
            $listByContentId[$item->id] = $item;
406
        }
407
408
        return $listByContentId;
409
    }
410
411
    /**
412
     * Returns the metadata object for a content identified by $remoteId.
413
     *
414
     * @param mixed $remoteId
415
     *
416
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
417
     */
418
    public function loadContentInfoByRemoteId($remoteId)
419
    {
420
        return $this->mapper->extractContentInfoFromRow(
421
            $this->contentGateway->loadContentInfoByRemoteId($remoteId)
422
        );
423
    }
424
425
    /**
426
     * Returns the version object for a content/version identified by $contentId and $versionNo.
427
     *
428
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If version is not found
429
     *
430
     * @param int|string $contentId
431
     * @param int $versionNo Version number to load
432
     *
433
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo
434
     */
435
    public function loadVersionInfo($contentId, $versionNo)
436
    {
437
        $rows = $this->contentGateway->loadVersionInfo($contentId, $versionNo);
438
        if (empty($rows)) {
439
            throw new NotFound('content', $contentId);
440
        }
441
442
        $versionInfo = $this->mapper->extractVersionInfoListFromRows(
443
            $rows,
444
            $this->contentGateway->loadVersionedNameData(array(array('id' => $contentId, 'version' => $versionNo)))
445
        );
446
447
        return reset($versionInfo);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression reset($versionInfo); of type eZ\Publish\SPI\Persisten...ntent\VersionInfo|false adds false to the return on line 447 which is incompatible with the return type declared by the interface eZ\Publish\SPI\Persisten...andler::loadVersionInfo of type eZ\Publish\SPI\Persistence\Content\VersionInfo. It seems like you forgot to handle an error condition.
Loading history...
448
    }
449
450
    /**
451
     * Returns all versions with draft status created by the given $userId.
452
     *
453
     * @param int $userId
454
     *
455
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[]
456
     */
457 View Code Duplication
    public function loadDraftsForUser($userId)
458
    {
459
        $rows = $this->contentGateway->listVersionsForUser($userId, VersionInfo::STATUS_DRAFT);
460
        if (empty($rows)) {
461
            return array();
462
        }
463
464
        $idVersionPairs = array_map(
465
            function ($row) {
466
                return array(
467
                    'id' => $row['ezcontentobject_version_contentobject_id'],
468
                    'version' => $row['ezcontentobject_version_version'],
469
                );
470
            },
471
            $rows
472
        );
473
        $nameRows = $this->contentGateway->loadVersionedNameData($idVersionPairs);
474
475
        return $this->mapper->extractVersionInfoListFromRows($rows, $nameRows);
476
    }
477
478
    /**
479
     * Sets the status of object identified by $contentId and $version to $status.
480
     *
481
     * The $status can be one of VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED
482
     * When status is set to VersionInfo::STATUS_PUBLISHED content status is updated to ContentInfo::STATUS_PUBLISHED
483
     *
484
     * @param int $contentId
485
     * @param int $status
486
     * @param int $version
487
     *
488
     * @return bool
489
     */
490
    public function setStatus($contentId, $status, $version)
491
    {
492
        return $this->contentGateway->setStatus($contentId, $version, $status);
493
    }
494
495
    /**
496
     * Updates a content object meta data, identified by $contentId.
497
     *
498
     * @param int $contentId
499
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content
500
     *
501
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
502
     */
503
    public function updateMetadata($contentId, MetadataUpdateStruct $content)
504
    {
505
        $this->contentGateway->updateContent($contentId, $content);
506
        $this->updatePathIdentificationString($contentId, $content);
507
508
        return $this->loadContentInfo($contentId);
509
    }
510
511
    /**
512
     * Updates path identification string for locations of given $contentId if main language
513
     * is set in update struct.
514
     *
515
     * This is specific to the Legacy storage engine, as path identification string is deprecated.
516
     *
517
     * @param int $contentId
518
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content
519
     */
520
    protected function updatePathIdentificationString($contentId, MetadataUpdateStruct $content)
521
    {
522
        if (isset($content->mainLanguageId)) {
523
            $contentLocationsRows = $this->locationGateway->loadLocationDataByContent($contentId);
524
            foreach ($contentLocationsRows as $row) {
525
                $locationName = '';
526
                $urlAliasRows = $this->urlAliasGateway->loadLocationEntries(
527
                    $row['node_id'],
528
                    false,
529
                    $content->mainLanguageId
530
                );
531
                if (!empty($urlAliasRows)) {
532
                    $locationName = $urlAliasRows[0]['text'];
533
                }
534
                $this->locationGateway->updatePathIdentificationString(
535
                    $row['node_id'],
536
                    $row['parent_node_id'],
537
                    $this->slugConverter->convert(
538
                        $locationName,
539
                        'node_' . $row['node_id'],
540
                        'urlalias_compat'
541
                    )
542
                );
543
            }
544
        }
545
    }
546
547
    /**
548
     * Updates a content version, identified by $contentId and $versionNo.
549
     *
550
     * @param int $contentId
551
     * @param int $versionNo
552
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $updateStruct
553
     *
554
     * @return \eZ\Publish\SPI\Persistence\Content
555
     */
556
    public function updateContent($contentId, $versionNo, UpdateStruct $updateStruct)
557
    {
558
        $content = $this->load($contentId, $versionNo);
559
        $this->contentGateway->updateVersion($contentId, $versionNo, $updateStruct);
560
        $contentType = $this->contentTypeHandler->load($content->versionInfo->contentInfo->contentTypeId);
561
        $this->fieldHandler->updateFields($content, $updateStruct, $contentType);
562
        foreach ($updateStruct->name as $language => $name) {
563
            $this->contentGateway->setName(
564
                $contentId,
565
                $versionNo,
566
                $name,
567
                $language
568
            );
569
        }
570
571
        return $this->load($contentId, $versionNo);
572
    }
573
574
    /**
575
     * Deletes all versions and fields, all locations (subtree), and all relations.
576
     *
577
     * Removes the relations, but not the related objects. All subtrees of the
578
     * assigned nodes of this content objects are removed (recursively).
579
     *
580
     * @param int $contentId
581
     *
582
     * @return bool
583
     */
584
    public function deleteContent($contentId)
585
    {
586
        $contentLocations = $this->contentGateway->getAllLocationIds($contentId);
587
        if (empty($contentLocations)) {
588
            $this->removeRawContent($contentId);
589
        } else {
590
            foreach ($contentLocations as $locationId) {
591
                $this->treeHandler->removeSubtree($locationId);
592
            }
593
        }
594
    }
595
596
    /**
597
     * Deletes raw content data.
598
     *
599
     * @param int $contentId
600
     */
601
    public function removeRawContent($contentId)
602
    {
603
        $this->treeHandler->removeRawContent($contentId);
604
    }
605
606
    /**
607
     * Deletes given version, its fields, node assignment, relations and names.
608
     *
609
     * Removes the relations, but not the related objects.
610
     *
611
     * @param int $contentId
612
     * @param int $versionNo
613
     *
614
     * @return bool
615
     */
616
    public function deleteVersion($contentId, $versionNo)
617
    {
618
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
619
620
        $this->locationGateway->deleteNodeAssignment($contentId, $versionNo);
621
622
        $this->fieldHandler->deleteFields($contentId, $versionInfo);
623
624
        $this->contentGateway->deleteRelations($contentId, $versionNo);
625
        $this->contentGateway->deleteVersions($contentId, $versionNo);
626
        $this->contentGateway->deleteNames($contentId, $versionNo);
627
    }
628
629
    /**
630
     * Returns the versions for $contentId.
631
     *
632
     * Result is returned with oldest version first (sorted by created, alternatively version id if auto increment).
633
     *
634
     * @param int $contentId
635
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
636
     * @param int $limit Limit for items returned, -1 means none.
637
     *
638
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[]
639
     */
640
    public function listVersions($contentId, $status = null, $limit = -1)
641
    {
642
        return $this->treeHandler->listVersions($contentId, $status, $limit);
643
    }
644
645
    /**
646
     * Copy Content with Fields, Versions & Relations from $contentId in $version.
647
     *
648
     * {@inheritdoc}
649
     *
650
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If content or version is not found
651
     *
652
     * @param mixed $contentId
653
     * @param mixed|null $versionNo Copy all versions if left null
654
     *
655
     * @return \eZ\Publish\SPI\Persistence\Content
656
     */
657
    public function copy($contentId, $versionNo = null)
658
    {
659
        $currentVersionNo = isset($versionNo) ?
660
            $versionNo :
661
            $this->loadContentInfo($contentId)->currentVersionNo;
662
663
        // Copy content in given version or current version
664
        $createStruct = $this->mapper->createCreateStructFromContent(
665
            $this->load($contentId, $currentVersionNo)
666
        );
667
        $content = $this->internalCreate($createStruct, $currentVersionNo);
668
669
        // If version was not passed also copy other versions
670
        if (!isset($versionNo)) {
671
            $contentType = $this->contentTypeHandler->load($createStruct->typeId);
672
673
            foreach ($this->listVersions($contentId) as $versionInfo) {
674
                if ($versionInfo->versionNo === $currentVersionNo) {
675
                    continue;
676
                }
677
678
                $versionContent = $this->load($contentId, $versionInfo->versionNo);
679
680
                $versionContent->versionInfo->contentInfo->id = $content->versionInfo->contentInfo->id;
681
                $versionContent->versionInfo->modificationDate = $createStruct->modified;
682
                $versionContent->versionInfo->creationDate = $createStruct->modified;
683
                $versionContent->versionInfo->id = $this->contentGateway->insertVersion(
684
                    $versionContent->versionInfo,
685
                    $versionContent->fields
686
                );
687
688
                $this->fieldHandler->createNewFields($versionContent, $contentType);
689
690
                // Create names
691 View Code Duplication
                foreach ($versionContent->versionInfo->names as $language => $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...
692
                    $this->contentGateway->setName(
693
                        $content->versionInfo->contentInfo->id,
694
                        $versionInfo->versionNo,
695
                        $name,
696
                        $language
697
                    );
698
                }
699
            }
700
701
            // Batch copy relations for all versions
702
            $this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id);
703
        } else {
704
            // Batch copy relations for published version
705
            $this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id, $versionNo);
706
        }
707
708
        return $content;
709
    }
710
711
    /**
712
     * Creates a relation between $sourceContentId in $sourceContentVersionNo
713
     * and $destinationContentId with a specific $type.
714
     *
715
     * @todo Should the existence verifications happen here or is this supposed to be handled at a higher level?
716
     *
717
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
718
     *
719
     * @return \eZ\Publish\SPI\Persistence\Content\Relation
720
     */
721
    public function addRelation(RelationCreateStruct $createStruct)
722
    {
723
        $relation = $this->mapper->createRelationFromCreateStruct($createStruct);
724
725
        $relation->id = $this->contentGateway->insertRelation($createStruct);
726
727
        return $relation;
728
    }
729
730
    /**
731
     * Removes a relation by relation Id.
732
     *
733
     * @todo Should the existence verifications happen here or is this supposed to be handled at a higher level?
734
     *
735
     * @param mixed $relationId
736
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
737
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
738
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
739
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
740
     */
741
    public function removeRelation($relationId, $type)
742
    {
743
        $this->contentGateway->deleteRelation($relationId, $type);
744
    }
745
746
    /**
747
     * Loads relations from $sourceContentId. Optionally, loads only those with $type and $sourceContentVersionNo.
748
     *
749
     * @param mixed $sourceContentId Source Content ID
750
     * @param mixed|null $sourceContentVersionNo Source Content Version, null if not specified
751
     * @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
752
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
753
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
754
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
755
     *
756
     * @return \eZ\Publish\SPI\Persistence\Content\Relation[]
757
     */
758
    public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null)
759
    {
760
        return $this->mapper->extractRelationsFromRows(
761
            $this->contentGateway->loadRelations($sourceContentId, $sourceContentVersionNo, $type)
762
        );
763
    }
764
765
    /**
766
     * Loads relations from $contentId. Optionally, loads only those with $type.
767
     *
768
     * Only loads relations against published versions.
769
     *
770
     * @param mixed $destinationContentId Destination Content ID
771
     * @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
772
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
773
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
774
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
775
     *
776
     * @return \eZ\Publish\SPI\Persistence\Content\Relation[]
777
     */
778
    public function loadReverseRelations($destinationContentId, $type = null)
779
    {
780
        return $this->mapper->extractRelationsFromRows(
781
            $this->contentGateway->loadReverseRelations($destinationContentId, $type)
782
        );
783
    }
784
785
    /**
786
     * {@inheritdoc}
787
     */
788
    public function removeTranslationFromContent($contentId, $languageCode)
789
    {
790
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
791
            __METHOD__ . ' is deprecated, use deleteTranslationFromContent instead',
792
            E_USER_DEPRECATED
793
        );
794
        $this->deleteTranslationFromContent($contentId, $languageCode);
795
    }
796
797
    /**
798
     * {@inheritdoc}
799
     */
800
    public function deleteTranslationFromContent($contentId, $languageCode)
801
    {
802
        $this->fieldHandler->deleteTranslationFromContentFields(
803
            $contentId,
804
            $this->listVersions($contentId),
805
            $languageCode
806
        );
807
        $this->contentGateway->deleteTranslationFromContent($contentId, $languageCode);
808
    }
809
810
    /**
811
     * {@inheritdoc}
812
     */
813
    public function deleteTranslationFromDraft($contentId, $versionNo, $languageCode)
814
    {
815
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
816
817
        $this->fieldHandler->deleteTranslationFromVersionFields(
818
            $versionInfo,
819
            $languageCode
820
        );
821
        $this->contentGateway->deleteTranslationFromVersion(
822
            $contentId,
823
            $versionNo,
824
            $languageCode
825
        );
826
827
        // get all [languageCode => name] entries except the removed Translation
828
        $names = array_filter(
829
            $versionInfo->names,
830
            function ($lang) use ($languageCode) {
831
                return $lang !== $languageCode;
832
            },
833
            ARRAY_FILTER_USE_KEY
834
        );
835
        // set new Content name
836
        foreach ($names as $language => $name) {
837
            $this->contentGateway->setName(
838
                $contentId,
839
                $versionNo,
840
                $name,
841
                $language
842
            );
843
        }
844
845
        // reload entire Version w/o removed Translation
846
        return $this->load($contentId, $versionNo);
847
    }
848
}
849