Completed
Push — master ( 915f3b...c955a4 )
by
unknown
123:31 queued 89:18
created

Handler::load()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 3
dl 0
loc 19
rs 9.6333
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 Exception;
12
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
13
use eZ\Publish\SPI\Persistence\Content\Handler as BaseContentHandler;
14
use eZ\Publish\SPI\Persistence\Content\Type\Handler as ContentTypeHandler;
15
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter;
16
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway as UrlAliasGateway;
17
use eZ\Publish\SPI\Persistence\Content;
18
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
19
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
20
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
21
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
22
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
23
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
24
use Psr\Log\LoggerInterface;
25
use Psr\Log\NullLogger;
26
27
/**
28
 * The Content Handler stores Content and ContentType objects.
29
 */
30
class Handler implements BaseContentHandler
31
{
32
    /**
33
     * Content gateway.
34
     *
35
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway
36
     */
37
    protected $contentGateway;
38
39
    /**
40
     * Location gateway.
41
     *
42
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway
43
     */
44
    protected $locationGateway;
45
46
    /**
47
     * Mapper.
48
     *
49
     * @var Mapper
50
     */
51
    protected $mapper;
52
53
    /**
54
     * FieldHandler.
55
     *
56
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\FieldHandler
57
     */
58
    protected $fieldHandler;
59
60
    /**
61
     * URL slug converter.
62
     *
63
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter
64
     */
65
    protected $slugConverter;
66
67
    /**
68
     * UrlAlias gateway.
69
     *
70
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway
71
     */
72
    protected $urlAliasGateway;
73
74
    /**
75
     * ContentType handler.
76
     *
77
     * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
78
     */
79
    protected $contentTypeHandler;
80
81
    /**
82
     * Tree handler.
83
     *
84
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler
85
     */
86
    protected $treeHandler;
87
88
    /**
89
     * @var \Psr\Log\LoggerInterface
90
     */
91
    private $logger;
92
93
    /**
94
     * Creates a new content handler.
95
     *
96
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway $contentGateway
97
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway $locationGateway
98
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Mapper $mapper
99
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\FieldHandler $fieldHandler
100
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter $slugConverter
101
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway $urlAliasGateway
102
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
103
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler $treeHandler
104
     * @param \Psr\Log\LoggerInterface|null $logger
105
     */
106
    public function __construct(
107
        Gateway $contentGateway,
108
        LocationGateway $locationGateway,
109
        Mapper $mapper,
110
        FieldHandler $fieldHandler,
111
        SlugConverter $slugConverter,
112
        UrlAliasGateway $urlAliasGateway,
113
        ContentTypeHandler $contentTypeHandler,
114
        TreeHandler $treeHandler,
115
        LoggerInterface $logger = null
116
    ) {
117
        $this->contentGateway = $contentGateway;
118
        $this->locationGateway = $locationGateway;
119
        $this->mapper = $mapper;
120
        $this->fieldHandler = $fieldHandler;
121
        $this->slugConverter = $slugConverter;
122
        $this->urlAliasGateway = $urlAliasGateway;
123
        $this->contentTypeHandler = $contentTypeHandler;
124
        $this->treeHandler = $treeHandler;
125
        $this->logger = null !== $logger ? $logger : new NullLogger();
126
    }
127
128
    /**
129
     * Creates a new Content entity in the storage engine.
130
     *
131
     * The values contained inside the $content will form the basis of stored
132
     * entity.
133
     *
134
     * Will contain always a complete list of fields.
135
     *
136
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct Content creation struct.
137
     *
138
     * @return \eZ\Publish\SPI\Persistence\Content Content value object
139
     */
140
    public function create(CreateStruct $struct)
141
    {
142
        return $this->internalCreate($struct);
143
    }
144
145
    /**
146
     * Creates a new Content entity in the storage engine.
147
     *
148
     * The values contained inside the $content will form the basis of stored
149
     * entity.
150
     *
151
     * Will contain always a complete list of fields.
152
     *
153
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct Content creation struct.
154
     * @param mixed $versionNo Used by self::copy() to maintain version numbers
155
     *
156
     * @return \eZ\Publish\SPI\Persistence\Content Content value object
157
     */
158
    protected function internalCreate(CreateStruct $struct, $versionNo = 1)
159
    {
160
        $content = new Content();
161
162
        $content->fields = $struct->fields;
163
        $content->versionInfo = $this->mapper->createVersionInfoFromCreateStruct($struct, $versionNo);
164
165
        $content->versionInfo->contentInfo->id = $this->contentGateway->insertContentObject($struct, $versionNo);
166
        $content->versionInfo->id = $this->contentGateway->insertVersion(
167
            $content->versionInfo,
168
            $struct->fields
169
        );
170
171
        $contentType = $this->contentTypeHandler->load($struct->typeId);
172
        $this->fieldHandler->createNewFields($content, $contentType);
173
174
        // Create node assignments
175
        foreach ($struct->locations as $location) {
176
            $location->contentId = $content->versionInfo->contentInfo->id;
177
            $location->contentVersion = $content->versionInfo->versionNo;
178
            $this->locationGateway->createNodeAssignment(
179
                $location,
180
                $location->parentId,
181
                LocationGateway::NODE_ASSIGNMENT_OP_CODE_CREATE
182
            );
183
        }
184
185
        // Create names
186 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...
187
            $this->contentGateway->setName(
188
                $content->versionInfo->contentInfo->id,
189
                $content->versionInfo->versionNo,
190
                $name,
191
                $language
192
            );
193
        }
194
195
        return $content;
196
    }
197
198
    /**
199
     * Performs the publishing operations required to set the version identified by $updateStruct->versionNo and
200
     * $updateStruct->id as the published one.
201
     *
202
     * The publish procedure will:
203
     * - Create location nodes based on the node assignments
204
     * - Update the content object using the provided metadata update struct
205
     * - Update the node assignments
206
     * - Update location nodes of the content with the new published version
207
     * - Set content and version status to published
208
     *
209
     * @param int $contentId
210
     * @param int $versionNo
211
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $metaDataUpdateStruct
212
     *
213
     * @return \eZ\Publish\SPI\Persistence\Content The published Content
214
     */
215
    public function publish($contentId, $versionNo, MetadataUpdateStruct $metaDataUpdateStruct)
216
    {
217
        // Archive currently published version
218
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
219
        if ($versionInfo->contentInfo->currentVersionNo != $versionNo) {
220
            $this->setStatus(
221
                $contentId,
222
                VersionInfo::STATUS_ARCHIVED,
223
                $versionInfo->contentInfo->currentVersionNo
224
            );
225
        }
226
227
        // Set always available name for the content
228
        $metaDataUpdateStruct->name = $versionInfo->names[$versionInfo->contentInfo->mainLanguageCode];
229
230
        $this->contentGateway->updateContent($contentId, $metaDataUpdateStruct, $versionInfo);
231
        $this->locationGateway->createLocationsFromNodeAssignments(
232
            $contentId,
233
            $versionNo
234
        );
235
236
        $this->locationGateway->updateLocationsContentVersionNo($contentId, $versionNo);
237
        $this->setStatus($contentId, VersionInfo::STATUS_PUBLISHED, $versionNo);
238
239
        return $this->load($contentId, $versionNo);
240
    }
241
242
    /**
243
     * Creates a new draft version from $contentId in $version.
244
     *
245
     * Copies all fields from $contentId in $srcVersion and creates a new
246
     * version of the referred Content from it.
247
     *
248
     * Note: When creating a new draft in the old admin interface there will
249
     * also be an entry in the `eznode_assignment` created for the draft. This
250
     * is ignored in this implementation.
251
     *
252
     * @param mixed $contentId
253
     * @param mixed $srcVersion
254
     * @param mixed $userId
255
     *
256
     * @return \eZ\Publish\SPI\Persistence\Content
257
     */
258
    public function createDraftFromVersion($contentId, $srcVersion, $userId)
259
    {
260
        $content = $this->load($contentId, $srcVersion);
261
262
        // Create new version
263
        $content->versionInfo = $this->mapper->createVersionInfoForContent(
264
            $content,
265
            $this->contentGateway->getLastVersionNumber($contentId) + 1,
266
            $userId
267
        );
268
        $content->versionInfo->id = $this->contentGateway->insertVersion(
269
            $content->versionInfo,
270
            $content->fields
271
        );
272
273
        // Clone fields from previous version and append them to the new one
274
        $this->fieldHandler->createExistingFieldsInNewVersion($content);
275
276
        // Create relations for new version
277
        $relations = $this->contentGateway->loadRelations($contentId, $srcVersion);
278
        foreach ($relations as $relation) {
279
            $this->contentGateway->insertRelation(
280
                new RelationCreateStruct(
281
                    array(
282
                        'sourceContentId' => $contentId,
283
                        'sourceContentVersionNo' => $content->versionInfo->versionNo,
284
                        'sourceFieldDefinitionId' => $relation['ezcontentobject_link_contentclassattribute_id'],
285
                        'destinationContentId' => $relation['ezcontentobject_link_to_contentobject_id'],
286
                        'type' => (int)$relation['ezcontentobject_link_relation_type'],
287
                    )
288
                )
289
            );
290
        }
291
292
        // Create content names for new version
293
        foreach ($content->versionInfo->names as $language => $name) {
294
            $this->contentGateway->setName(
295
                $contentId,
296
                $content->versionInfo->versionNo,
297
                $name,
298
                $language
299
            );
300
        }
301
302
        return $content;
303
    }
304
305
    /**
306
     * Returns the raw data of a content object identified by $id, in a struct.
307
     *
308
     * A version to load must be specified. If you want to load the current
309
     * version of a content object use SearchHandler::findSingle() with the
310
     * ContentId criterion.
311
     *
312
     * Optionally a translation filter may be specified. If specified only the
313
     * translations with the listed language codes will be retrieved. If not,
314
     * all translations will be retrieved.
315
     *
316
     * @param int|string $id
317
     * @param int|string $version
318
     * @param string[] $translations
319
     *
320
     * @return \eZ\Publish\SPI\Persistence\Content Content value object
321
     */
322
    public function load($id, $version, array $translations = null)
323
    {
324
        $rows = $this->contentGateway->load($id, $version, $translations);
325
326
        if (empty($rows)) {
327
            throw new NotFound('content', "contentId: $id, versionNo: $version");
328
        }
329
330
        $contentObjects = $this->mapper->extractContentFromRows(
331
            $rows,
332
            $this->contentGateway->loadVersionedNameData(array(array('id' => $id, 'version' => $version)))
333
        );
334
        $content = $contentObjects[0];
335
        unset($rows, $contentObjects);
336
337
        $this->fieldHandler->loadExternalFieldData($content);
338
339
        return $content;
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345
    public function loadContentList(array $contentIds, array $translations = null): array
346
    {
347
        $rawList = $this->contentGateway->loadContentList($contentIds, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 345 can also be of type array; however, eZ\Publish\Core\Persiste...eway::loadContentList() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
348
        if (empty($rawList)) {
349
            return [];
350
        }
351
352
        $idVersionPairs = [];
353
        foreach ($rawList as $row) {
354
            // As there is only one version per id, set id as key to avoid duplicates
355
            $idVersionPairs[$row['ezcontentobject_id']] = [
356
                'id' => $row['ezcontentobject_id'],
357
                'version' => $row['ezcontentobject_version_version'],
358
            ];
359
        }
360
361
        // group name data per Content Id
362
        $nameData = $this->contentGateway->loadVersionedNameData(array_values($idVersionPairs));
363
        $contentItemNameData = [];
364
        foreach ($nameData as $nameDataRow) {
365
            $contentId = $nameDataRow['ezcontentobject_name_contentobject_id'];
366
            $contentItemNameData[$contentId][] = $nameDataRow;
367
        }
368
369
        // group rows per Content Id be able to ignore Content items with erroneous data
370
        $contentItemsRows = [];
371
        foreach ($rawList as $row) {
372
            $contentId = $row['ezcontentobject_id'];
373
            $contentItemsRows[$contentId][] = $row;
374
        }
375
        unset($rawList, $idVersionPairs);
376
377
        // try to extract Content from each Content data
378
        $contentItems = [];
379
        foreach ($contentItemsRows as $contentId => $contentItemsRow) {
380
            try {
381
                $contentList = $this->mapper->extractContentFromRows(
382
                    $contentItemsRow,
383
                    $contentItemNameData[$contentId]
384
                );
385
                $contentItems[$contentId] = $contentList[0];
386
            } catch (Exception $e) {
387
                $this->logger->warning(
388
                    sprintf(
389
                        '%s: Content %d not loaded: %s',
390
                        __METHOD__,
391
                        $contentId,
392
                        $e->getMessage()
393
                    )
394
                );
395
            }
396
        }
397
398
        // try to load External Storage data for each Content, ignore Content items for which it failed
399
        foreach ($contentItems as $contentId => $content) {
400
            try {
401
                $this->fieldHandler->loadExternalFieldData($content);
402
            } catch (Exception $e) {
403
                unset($contentItems[$contentId]);
404
                $this->logger->warning(
405
                    sprintf(
406
                        '%s: Content %d not loaded: %s',
407
                        __METHOD__,
408
                        $contentId,
409
                        $e->getMessage()
410
                    )
411
                );
412
            }
413
        }
414
415
        return $contentItems;
416
    }
417
418
    /**
419
     * Returns the metadata object for a content identified by $contentId.
420
     *
421
     * @param int|string $contentId
422
     *
423
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
424
     */
425
    public function loadContentInfo($contentId)
426
    {
427
        return $this->treeHandler->loadContentInfo($contentId);
428
    }
429
430
    public function loadContentInfoList(array $contentIds)
431
    {
432
        $list = $this->mapper->extractContentInfoFromRows(
433
            $this->contentGateway->loadContentInfoList($contentIds)
434
        );
435
436
        $listByContentId = [];
437
        foreach ($list as $item) {
438
            $listByContentId[$item->id] = $item;
439
        }
440
441
        return $listByContentId;
442
    }
443
444
    /**
445
     * Returns the metadata object for a content identified by $remoteId.
446
     *
447
     * @param mixed $remoteId
448
     *
449
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
450
     */
451
    public function loadContentInfoByRemoteId($remoteId)
452
    {
453
        return $this->mapper->extractContentInfoFromRow(
454
            $this->contentGateway->loadContentInfoByRemoteId($remoteId)
455
        );
456
    }
457
458
    /**
459
     * Returns the version object for a content/version identified by $contentId and $versionNo.
460
     *
461
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If version is not found
462
     *
463
     * @param int|string $contentId
464
     * @param int $versionNo Version number to load
465
     *
466
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo
467
     */
468
    public function loadVersionInfo($contentId, $versionNo)
469
    {
470
        $rows = $this->contentGateway->loadVersionInfo($contentId, $versionNo);
471
        if (empty($rows)) {
472
            throw new NotFound('content', $contentId);
473
        }
474
475
        $versionInfo = $this->mapper->extractVersionInfoListFromRows(
476
            $rows,
477
            $this->contentGateway->loadVersionedNameData(array(array('id' => $contentId, 'version' => $versionNo)))
478
        );
479
480
        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 480 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...
481
    }
482
483
    /**
484
     * Returns all versions with draft status created by the given $userId.
485
     *
486
     * @param int $userId
487
     *
488
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[]
489
     */
490 View Code Duplication
    public function loadDraftsForUser($userId)
491
    {
492
        $rows = $this->contentGateway->listVersionsForUser($userId, VersionInfo::STATUS_DRAFT);
493
        if (empty($rows)) {
494
            return array();
495
        }
496
497
        $idVersionPairs = array_map(
498
            function ($row) {
499
                return array(
500
                    'id' => $row['ezcontentobject_version_contentobject_id'],
501
                    'version' => $row['ezcontentobject_version_version'],
502
                );
503
            },
504
            $rows
505
        );
506
        $nameRows = $this->contentGateway->loadVersionedNameData($idVersionPairs);
507
508
        return $this->mapper->extractVersionInfoListFromRows($rows, $nameRows);
509
    }
510
511
    /**
512
     * Sets the status of object identified by $contentId and $version to $status.
513
     *
514
     * The $status can be one of VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED
515
     * When status is set to VersionInfo::STATUS_PUBLISHED content status is updated to ContentInfo::STATUS_PUBLISHED
516
     *
517
     * @param int $contentId
518
     * @param int $status
519
     * @param int $version
520
     *
521
     * @return bool
522
     */
523
    public function setStatus($contentId, $status, $version)
524
    {
525
        return $this->contentGateway->setStatus($contentId, $version, $status);
526
    }
527
528
    /**
529
     * Updates a content object meta data, identified by $contentId.
530
     *
531
     * @param int $contentId
532
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content
533
     *
534
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
535
     */
536
    public function updateMetadata($contentId, MetadataUpdateStruct $content)
537
    {
538
        $this->contentGateway->updateContent($contentId, $content);
539
        $this->updatePathIdentificationString($contentId, $content);
540
541
        return $this->loadContentInfo($contentId);
542
    }
543
544
    /**
545
     * Updates path identification string for locations of given $contentId if main language
546
     * is set in update struct.
547
     *
548
     * This is specific to the Legacy storage engine, as path identification string is deprecated.
549
     *
550
     * @param int $contentId
551
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content
552
     */
553
    protected function updatePathIdentificationString($contentId, MetadataUpdateStruct $content)
554
    {
555
        if (isset($content->mainLanguageId)) {
556
            $contentLocationsRows = $this->locationGateway->loadLocationDataByContent($contentId);
557
            foreach ($contentLocationsRows as $row) {
558
                $locationName = '';
559
                $urlAliasRows = $this->urlAliasGateway->loadLocationEntries(
560
                    $row['node_id'],
561
                    false,
562
                    $content->mainLanguageId
563
                );
564
                if (!empty($urlAliasRows)) {
565
                    $locationName = $urlAliasRows[0]['text'];
566
                }
567
                $this->locationGateway->updatePathIdentificationString(
568
                    $row['node_id'],
569
                    $row['parent_node_id'],
570
                    $this->slugConverter->convert(
571
                        $locationName,
572
                        'node_' . $row['node_id'],
573
                        'urlalias_compat'
574
                    )
575
                );
576
            }
577
        }
578
    }
579
580
    /**
581
     * Updates a content version, identified by $contentId and $versionNo.
582
     *
583
     * @param int $contentId
584
     * @param int $versionNo
585
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $updateStruct
586
     *
587
     * @return \eZ\Publish\SPI\Persistence\Content
588
     */
589
    public function updateContent($contentId, $versionNo, UpdateStruct $updateStruct)
590
    {
591
        $content = $this->load($contentId, $versionNo);
592
        $this->contentGateway->updateVersion($contentId, $versionNo, $updateStruct);
593
        $contentType = $this->contentTypeHandler->load($content->versionInfo->contentInfo->contentTypeId);
594
        $this->fieldHandler->updateFields($content, $updateStruct, $contentType);
595
        foreach ($updateStruct->name as $language => $name) {
596
            $this->contentGateway->setName(
597
                $contentId,
598
                $versionNo,
599
                $name,
600
                $language
601
            );
602
        }
603
604
        return $this->load($contentId, $versionNo);
605
    }
606
607
    /**
608
     * Deletes all versions and fields, all locations (subtree), and all relations.
609
     *
610
     * Removes the relations, but not the related objects. All subtrees of the
611
     * assigned nodes of this content objects are removed (recursively).
612
     *
613
     * @param int $contentId
614
     *
615
     * @return bool
616
     */
617
    public function deleteContent($contentId)
618
    {
619
        $contentLocations = $this->contentGateway->getAllLocationIds($contentId);
620
        if (empty($contentLocations)) {
621
            $this->removeRawContent($contentId);
622
        } else {
623
            foreach ($contentLocations as $locationId) {
624
                $this->treeHandler->removeSubtree($locationId);
625
            }
626
        }
627
    }
628
629
    /**
630
     * Deletes raw content data.
631
     *
632
     * @param int $contentId
633
     */
634
    public function removeRawContent($contentId)
635
    {
636
        $this->treeHandler->removeRawContent($contentId);
637
    }
638
639
    /**
640
     * Deletes given version, its fields, node assignment, relations and names.
641
     *
642
     * Removes the relations, but not the related objects.
643
     *
644
     * @param int $contentId
645
     * @param int $versionNo
646
     *
647
     * @return bool
648
     */
649
    public function deleteVersion($contentId, $versionNo)
650
    {
651
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
652
653
        $this->locationGateway->deleteNodeAssignment($contentId, $versionNo);
654
655
        $this->fieldHandler->deleteFields($contentId, $versionInfo);
656
657
        $this->contentGateway->deleteRelations($contentId, $versionNo);
658
        $this->contentGateway->deleteVersions($contentId, $versionNo);
659
        $this->contentGateway->deleteNames($contentId, $versionNo);
660
    }
661
662
    /**
663
     * Returns the versions for $contentId.
664
     *
665
     * Result is returned with oldest version first (sorted by created, alternatively version id if auto increment).
666
     *
667
     * @param int $contentId
668
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
669
     * @param int $limit Limit for items returned, -1 means none.
670
     *
671
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[]
672
     */
673
    public function listVersions($contentId, $status = null, $limit = -1)
674
    {
675
        return $this->treeHandler->listVersions($contentId, $status, $limit);
676
    }
677
678
    /**
679
     * Copy Content with Fields, Versions & Relations from $contentId in $version.
680
     *
681
     * {@inheritdoc}
682
     *
683
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If content or version is not found
684
     *
685
     * @param mixed $contentId
686
     * @param mixed|null $versionNo Copy all versions if left null
687
     * @param int|null $newOwnerId
688
     *
689
     * @return \eZ\Publish\SPI\Persistence\Content
690
     */
691
    public function copy($contentId, $versionNo = null, $newOwnerId = null)
692
    {
693
        $currentVersionNo = isset($versionNo) ?
694
            $versionNo :
695
            $this->loadContentInfo($contentId)->currentVersionNo;
696
697
        // Copy content in given version or current version
698
        $createStruct = $this->mapper->createCreateStructFromContent(
699
            $this->load($contentId, $currentVersionNo)
700
        );
701
        if ($newOwnerId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $newOwnerId of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
702
            $createStruct->ownerId = $newOwnerId;
703
        }
704
        $content = $this->internalCreate($createStruct, $currentVersionNo);
705
706
        // If version was not passed also copy other versions
707
        if (!isset($versionNo)) {
708
            $contentType = $this->contentTypeHandler->load($createStruct->typeId);
709
710
            foreach ($this->listVersions($contentId) as $versionInfo) {
711
                if ($versionInfo->versionNo === $currentVersionNo) {
712
                    continue;
713
                }
714
715
                $versionContent = $this->load($contentId, $versionInfo->versionNo);
716
717
                $versionContent->versionInfo->contentInfo->id = $content->versionInfo->contentInfo->id;
718
                $versionContent->versionInfo->modificationDate = $createStruct->modified;
719
                $versionContent->versionInfo->creationDate = $createStruct->modified;
720
                $versionContent->versionInfo->id = $this->contentGateway->insertVersion(
721
                    $versionContent->versionInfo,
722
                    $versionContent->fields
723
                );
724
725
                $this->fieldHandler->createNewFields($versionContent, $contentType);
726
727
                // Create names
728 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...
729
                    $this->contentGateway->setName(
730
                        $content->versionInfo->contentInfo->id,
731
                        $versionInfo->versionNo,
732
                        $name,
733
                        $language
734
                    );
735
                }
736
            }
737
738
            // Batch copy relations for all versions
739
            $this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id);
740
        } else {
741
            // Batch copy relations for published version
742
            $this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id, $versionNo);
743
        }
744
745
        return $content;
746
    }
747
748
    /**
749
     * Creates a relation between $sourceContentId in $sourceContentVersionNo
750
     * and $destinationContentId with a specific $type.
751
     *
752
     * @todo Should the existence verifications happen here or is this supposed to be handled at a higher level?
753
     *
754
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
755
     *
756
     * @return \eZ\Publish\SPI\Persistence\Content\Relation
757
     */
758
    public function addRelation(RelationCreateStruct $createStruct)
759
    {
760
        $relation = $this->mapper->createRelationFromCreateStruct($createStruct);
761
762
        $relation->id = $this->contentGateway->insertRelation($createStruct);
763
764
        return $relation;
765
    }
766
767
    /**
768
     * Removes a relation by relation Id.
769
     *
770
     * @todo Should the existence verifications happen here or is this supposed to be handled at a higher level?
771
     *
772
     * @param mixed $relationId
773
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
774
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
775
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
776
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
777
     */
778
    public function removeRelation($relationId, $type)
779
    {
780
        $this->contentGateway->deleteRelation($relationId, $type);
781
    }
782
783
    /**
784
     * Loads relations from $sourceContentId. Optionally, loads only those with $type and $sourceContentVersionNo.
785
     *
786
     * @param mixed $sourceContentId Source Content ID
787
     * @param mixed|null $sourceContentVersionNo Source Content Version, null if not specified
788
     * @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
789
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
790
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
791
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
792
     *
793
     * @return \eZ\Publish\SPI\Persistence\Content\Relation[]
794
     */
795
    public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null)
796
    {
797
        return $this->mapper->extractRelationsFromRows(
798
            $this->contentGateway->loadRelations($sourceContentId, $sourceContentVersionNo, $type)
799
        );
800
    }
801
802
    /**
803
     * Loads relations from $contentId. Optionally, loads only those with $type.
804
     *
805
     * Only loads relations against published versions.
806
     *
807
     * @param mixed $destinationContentId Destination Content ID
808
     * @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
809
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
810
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
811
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
812
     *
813
     * @return \eZ\Publish\SPI\Persistence\Content\Relation[]
814
     */
815
    public function loadReverseRelations($destinationContentId, $type = null)
816
    {
817
        return $this->mapper->extractRelationsFromRows(
818
            $this->contentGateway->loadReverseRelations($destinationContentId, $type)
819
        );
820
    }
821
822
    /**
823
     * {@inheritdoc}
824
     */
825
    public function removeTranslationFromContent($contentId, $languageCode)
826
    {
827
        @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...
828
            __METHOD__ . ' is deprecated, use deleteTranslationFromContent instead',
829
            E_USER_DEPRECATED
830
        );
831
        $this->deleteTranslationFromContent($contentId, $languageCode);
832
    }
833
834
    /**
835
     * {@inheritdoc}
836
     */
837
    public function deleteTranslationFromContent($contentId, $languageCode)
838
    {
839
        $this->fieldHandler->deleteTranslationFromContentFields(
840
            $contentId,
841
            $this->listVersions($contentId),
842
            $languageCode
843
        );
844
        $this->contentGateway->deleteTranslationFromContent($contentId, $languageCode);
845
    }
846
847
    /**
848
     * {@inheritdoc}
849
     */
850
    public function deleteTranslationFromDraft($contentId, $versionNo, $languageCode)
851
    {
852
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
853
854
        $this->fieldHandler->deleteTranslationFromVersionFields(
855
            $versionInfo,
856
            $languageCode
857
        );
858
        $this->contentGateway->deleteTranslationFromVersion(
859
            $contentId,
860
            $versionNo,
861
            $languageCode
862
        );
863
864
        // get all [languageCode => name] entries except the removed Translation
865
        $names = array_filter(
866
            $versionInfo->names,
867
            function ($lang) use ($languageCode) {
868
                return $lang !== $languageCode;
869
            },
870
            ARRAY_FILTER_USE_KEY
871
        );
872
        // set new Content name
873
        foreach ($names as $language => $name) {
874
            $this->contentGateway->setName(
875
                $contentId,
876
                $versionNo,
877
                $name,
878
                $language
879
            );
880
        }
881
882
        // reload entire Version w/o removed Translation
883
        return $this->load($contentId, $versionNo);
884
    }
885
}
886