Completed
Push — 7.5 ( 17c267...9e0292 )
by Łukasz
47:52 queued 28:25
created

Handler::loadReverseRelationList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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