Completed
Push — EZP-30427 ( 8c1757...93ffe2 )
by
unknown
17:19
created

Handler::deleteTranslationFromContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 9
rs 9.9666
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\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 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...
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
    public function publish($contentId, $versionNo, MetadataUpdateStruct $metaDataUpdateStruct)
215
    {
216
        // Archive currently published version
217
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
218
        if ($versionInfo->contentInfo->currentVersionNo != $versionNo) {
219
            $this->setStatus(
220
                $contentId,
221
                VersionInfo::STATUS_ARCHIVED,
222
                $versionInfo->contentInfo->currentVersionNo
223
            );
224
        }
225
226
        // Set always available name for the content
227
        $metaDataUpdateStruct->name = $versionInfo->names[$versionInfo->contentInfo->mainLanguageCode];
228
229
        $this->contentGateway->updateContent($contentId, $metaDataUpdateStruct, $versionInfo);
230
        $this->locationGateway->createLocationsFromNodeAssignments(
231
            $contentId,
232
            $versionNo
233
        );
234
235
        $this->locationGateway->updateLocationsContentVersionNo($contentId, $versionNo);
236
        $this->setStatus($contentId, VersionInfo::STATUS_PUBLISHED, $versionNo);
237
238
        return $this->load($contentId, $versionNo);
239
    }
240
241
    /**
242
     * Creates a new draft version from $contentId in $version.
243
     *
244
     * Copies all fields from $contentId in $srcVersion and creates a new
245
     * version of the referred Content from it.
246
     *
247
     * Note: When creating a new draft in the old admin interface there will
248
     * also be an entry in the `eznode_assignment` created for the draft. This
249
     * is ignored in this implementation.
250
     *
251
     * @param mixed $contentId
252
     * @param mixed $srcVersion
253
     * @param mixed $userId
254
     *
255
     * @return \eZ\Publish\SPI\Persistence\Content
256
     */
257
    public function createDraftFromVersion($contentId, $srcVersion, $userId)
258
    {
259
        $content = $this->load($contentId, $srcVersion);
260
261
        // Create new version
262
        $content->versionInfo = $this->mapper->createVersionInfoForContent(
263
            $content,
264
            $this->contentGateway->getLastVersionNumber($contentId) + 1,
265
            $userId
266
        );
267
        $content->versionInfo->id = $this->contentGateway->insertVersion(
268
            $content->versionInfo,
269
            $content->fields
270
        );
271
272
        // Clone fields from previous version and append them to the new one
273
        $this->fieldHandler->createExistingFieldsInNewVersion($content);
274
275
        // Create relations for new version
276
        $relations = $this->contentGateway->loadRelations($contentId, $srcVersion);
277
        foreach ($relations as $relation) {
278
            $this->contentGateway->insertRelation(
279
                new RelationCreateStruct(
280
                    [
281
                        'sourceContentId' => $contentId,
282
                        'sourceContentVersionNo' => $content->versionInfo->versionNo,
283
                        'sourceFieldDefinitionId' => $relation['ezcontentobject_link_contentclassattribute_id'],
284
                        'destinationContentId' => $relation['ezcontentobject_link_to_contentobject_id'],
285
                        'type' => (int)$relation['ezcontentobject_link_relation_type'],
286
                    ]
287
                )
288
            );
289
        }
290
291
        // Create content names for new version
292
        foreach ($content->versionInfo->names as $language => $name) {
293
            $this->contentGateway->setName(
294
                $contentId,
295
                $content->versionInfo->versionNo,
296
                $name,
297
                $language
298
            );
299
        }
300
301
        return $content;
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307
    public function load($id, $version = null, array $translations = null)
308
    {
309
        $rows = $this->contentGateway->load($id, $version, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 307 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...
310
311
        if (empty($rows)) {
312
            throw new NotFound('content', "contentId: $id, versionNo: $version");
313
        }
314
315
        $contentObjects = $this->mapper->extractContentFromRows(
316
            $rows,
317
            $this->contentGateway->loadVersionedNameData([[
318
                'id' => $id,
319
                'version' => $rows[0]['ezcontentobject_version_version'],
320
            ]])
321
        );
322
        $content = $contentObjects[0];
323
        unset($rows, $contentObjects);
324
325
        $this->fieldHandler->loadExternalFieldData($content);
326
327
        return $content;
328
    }
329
330
    /**
331
     * {@inheritdoc}
332
     */
333
    public function loadContentList(array $contentIds, array $translations = null): array
334
    {
335
        $rawList = $this->contentGateway->loadContentList($contentIds, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 333 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...
336
        if (empty($rawList)) {
337
            return [];
338
        }
339
340
        $idVersionPairs = [];
341
        foreach ($rawList as $row) {
342
            // As there is only one version per id, set id as key to avoid duplicates
343
            $idVersionPairs[$row['ezcontentobject_id']] = [
344
                'id' => $row['ezcontentobject_id'],
345
                'version' => $row['ezcontentobject_version_version'],
346
            ];
347
        }
348
349
        // group name data per Content Id
350
        $nameData = $this->contentGateway->loadVersionedNameData(array_values($idVersionPairs));
351
        $contentItemNameData = [];
352
        foreach ($nameData as $nameDataRow) {
353
            $contentId = $nameDataRow['ezcontentobject_name_contentobject_id'];
354
            $contentItemNameData[$contentId][] = $nameDataRow;
355
        }
356
357
        // group rows per Content Id be able to ignore Content items with erroneous data
358
        $contentItemsRows = [];
359
        foreach ($rawList as $row) {
360
            $contentId = $row['ezcontentobject_id'];
361
            $contentItemsRows[$contentId][] = $row;
362
        }
363
        unset($rawList, $idVersionPairs);
364
365
        // try to extract Content from each Content data
366
        $contentItems = [];
367
        foreach ($contentItemsRows as $contentId => $contentItemsRow) {
368
            try {
369
                $contentList = $this->mapper->extractContentFromRows(
370
                    $contentItemsRow,
371
                    $contentItemNameData[$contentId]
372
                );
373
                $contentItems[$contentId] = $contentList[0];
374
            } catch (Exception $e) {
375
                $this->logger->warning(
376
                    sprintf(
377
                        '%s: Content %d not loaded: %s',
378
                        __METHOD__,
379
                        $contentId,
380
                        $e->getMessage()
381
                    )
382
                );
383
            }
384
        }
385
386
        // try to load External Storage data for each Content, ignore Content items for which it failed
387
        foreach ($contentItems as $contentId => $content) {
388
            try {
389
                $this->fieldHandler->loadExternalFieldData($content);
390
            } catch (Exception $e) {
391
                unset($contentItems[$contentId]);
392
                $this->logger->warning(
393
                    sprintf(
394
                        '%s: Content %d not loaded: %s',
395
                        __METHOD__,
396
                        $contentId,
397
                        $e->getMessage()
398
                    )
399
                );
400
            }
401
        }
402
403
        return $contentItems;
404
    }
405
406
    /**
407
     * Returns the metadata object for a content identified by $contentId.
408
     *
409
     * @param int|string $contentId
410
     *
411
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
412
     */
413
    public function loadContentInfo($contentId)
414
    {
415
        return $this->treeHandler->loadContentInfo($contentId);
416
    }
417
418
    public function loadContentInfoList(array $contentIds)
419
    {
420
        $list = $this->mapper->extractContentInfoFromRows(
421
            $this->contentGateway->loadContentInfoList($contentIds)
422
        );
423
424
        $listByContentId = [];
425
        foreach ($list as $item) {
426
            $listByContentId[$item->id] = $item;
427
        }
428
429
        return $listByContentId;
430
    }
431
432
    /**
433
     * Returns the metadata object for a content identified by $remoteId.
434
     *
435
     * @param mixed $remoteId
436
     *
437
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
438
     */
439
    public function loadContentInfoByRemoteId($remoteId)
440
    {
441
        return $this->mapper->extractContentInfoFromRow(
442
            $this->contentGateway->loadContentInfoByRemoteId($remoteId)
443
        );
444
    }
445
446
    /**
447
     * Returns the version object for a content/version identified by $contentId and $versionNo.
448
     *
449
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If version is not found
450
     *
451
     * @param int|string $contentId
452
     * @param int $versionNo Version number to load
453
     *
454
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo
455
     */
456 View Code Duplication
    public function loadVersionInfo($contentId, $versionNo)
457
    {
458
        $rows = $this->contentGateway->loadVersionInfo($contentId, $versionNo);
459
        if (empty($rows)) {
460
            throw new NotFound('content', $contentId);
461
        }
462
463
        $versionInfo = $this->mapper->extractVersionInfoListFromRows(
464
            $rows,
465
            $this->contentGateway->loadVersionedNameData([['id' => $contentId, 'version' => $versionNo]])
466
        );
467
468
        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 468 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...
469
    }
470
471
    /**
472
     * Returns the number of versions with draft status created by the given $userId.
473
     *
474
     * @param int $userId
475
     *
476
     * @return int
477
     */
478
    public function countDraftsForUser($userId): int
479
    {
480
        return $this->contentGateway->countVersionsForUser($userId, VersionInfo::STATUS_DRAFT);
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 [];
495
        }
496
497
        $idVersionPairs = array_map(
498
            function ($row) {
499
                return [
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
     * {@inheritdoc}
513
     */
514 View Code Duplication
    public function loadDraftListForUser($userId, int $offset = 0, int $limit = -1): array
515
    {
516
        $rows = $this->contentGateway->loadVersionsForUser($userId, VersionInfo::STATUS_DRAFT, $offset, $limit);
517
        if (empty($rows)) {
518
            return [];
519
        }
520
521
        $idVersionPairs = array_map(
522
            function ($row) {
523
                return [
524
                    'id' => $row['ezcontentobject_version_contentobject_id'],
525
                    'version' => $row['ezcontentobject_version_version'],
526
                ];
527
            },
528
            $rows
529
        );
530
        $nameRows = $this->contentGateway->loadVersionedNameData($idVersionPairs);
531
532
        return $this->mapper->extractVersionInfoListFromRows($rows, $nameRows);
533
    }
534
535
    /**
536
     * Sets the status of object identified by $contentId and $version to $status.
537
     *
538
     * The $status can be one of VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED
539
     * When status is set to VersionInfo::STATUS_PUBLISHED content status is updated to ContentInfo::STATUS_PUBLISHED
540
     *
541
     * @param int $contentId
542
     * @param int $status
543
     * @param int $version
544
     *
545
     * @return bool
546
     */
547
    public function setStatus($contentId, $status, $version)
548
    {
549
        return $this->contentGateway->setStatus($contentId, $version, $status);
550
    }
551
552
    /**
553
     * Updates a content object meta data, identified by $contentId.
554
     *
555
     * @param int $contentId
556
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content
557
     *
558
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
559
     */
560
    public function updateMetadata($contentId, MetadataUpdateStruct $content)
561
    {
562
        $this->contentGateway->updateContent($contentId, $content);
563
        $this->updatePathIdentificationString($contentId, $content);
564
565
        return $this->loadContentInfo($contentId);
566
    }
567
568
    /**
569
     * Updates path identification string for locations of given $contentId if main language
570
     * is set in update struct.
571
     *
572
     * This is specific to the Legacy storage engine, as path identification string is deprecated.
573
     *
574
     * @param int $contentId
575
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content
576
     */
577
    protected function updatePathIdentificationString($contentId, MetadataUpdateStruct $content)
578
    {
579
        if (isset($content->mainLanguageId)) {
580
            $contentLocationsRows = $this->locationGateway->loadLocationDataByContent($contentId);
581
            foreach ($contentLocationsRows as $row) {
582
                $locationName = '';
583
                $urlAliasRows = $this->urlAliasGateway->loadLocationEntries(
584
                    $row['node_id'],
585
                    false,
586
                    $content->mainLanguageId
587
                );
588
                if (!empty($urlAliasRows)) {
589
                    $locationName = $urlAliasRows[0]['text'];
590
                }
591
                $this->locationGateway->updatePathIdentificationString(
592
                    $row['node_id'],
593
                    $row['parent_node_id'],
594
                    $this->slugConverter->convert(
595
                        $locationName,
596
                        'node_' . $row['node_id'],
597
                        'urlalias_compat'
598
                    )
599
                );
600
            }
601
        }
602
    }
603
604
    /**
605
     * Updates a content version, identified by $contentId and $versionNo.
606
     *
607
     * @param int $contentId
608
     * @param int $versionNo
609
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $updateStruct
610
     *
611
     * @return \eZ\Publish\SPI\Persistence\Content
612
     */
613
    public function updateContent($contentId, $versionNo, UpdateStruct $updateStruct)
614
    {
615
        $content = $this->load($contentId, $versionNo);
616
        $this->contentGateway->updateVersion($contentId, $versionNo, $updateStruct);
617
        $contentType = $this->contentTypeHandler->load($content->versionInfo->contentInfo->contentTypeId);
618
        $this->fieldHandler->updateFields($content, $updateStruct, $contentType);
619
        foreach ($updateStruct->name as $language => $name) {
620
            $this->contentGateway->setName(
621
                $contentId,
622
                $versionNo,
623
                $name,
624
                $language
625
            );
626
        }
627
628
        return $this->load($contentId, $versionNo);
629
    }
630
631
    /**
632
     * Deletes all versions and fields, all locations (subtree), and all relations.
633
     *
634
     * Removes the relations, but not the related objects. All subtrees of the
635
     * assigned nodes of this content objects are removed (recursively).
636
     *
637
     * @param int $contentId
638
     *
639
     * @return bool
640
     */
641
    public function deleteContent($contentId)
642
    {
643
        $contentLocations = $this->contentGateway->getAllLocationIds($contentId);
644
        if (empty($contentLocations)) {
645
            $this->removeRawContent($contentId);
646
        } else {
647
            foreach ($contentLocations as $locationId) {
648
                $this->treeHandler->removeSubtree($locationId);
649
            }
650
        }
651
    }
652
653
    /**
654
     * Deletes raw content data.
655
     *
656
     * @param int $contentId
657
     */
658
    public function removeRawContent($contentId)
659
    {
660
        $this->treeHandler->removeRawContent($contentId);
661
    }
662
663
    /**
664
     * Deletes given version, its fields, node assignment, relations and names.
665
     *
666
     * Removes the relations, but not the related objects.
667
     *
668
     * @param int $contentId
669
     * @param int $versionNo
670
     *
671
     * @return bool
672
     */
673
    public function deleteVersion($contentId, $versionNo)
674
    {
675
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
676
677
        $this->locationGateway->deleteNodeAssignment($contentId, $versionNo);
678
679
        $this->fieldHandler->deleteFields($contentId, $versionInfo);
680
681
        $this->contentGateway->deleteRelations($contentId, $versionNo);
682
        $this->contentGateway->deleteVersions($contentId, $versionNo);
683
        $this->contentGateway->deleteNames($contentId, $versionNo);
684
    }
685
686
    /**
687
     * Returns the versions for $contentId.
688
     *
689
     * Result is returned with oldest version first (sorted by created, alternatively version id if auto increment).
690
     *
691
     * @param int $contentId
692
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
693
     * @param int $limit Limit for items returned, -1 means none.
694
     *
695
     * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[]
696
     */
697
    public function listVersions($contentId, $status = null, $limit = -1)
698
    {
699
        return $this->treeHandler->listVersions($contentId, $status, $limit);
700
    }
701
702
    /**
703
     * Copy Content with Fields, Versions & Relations from $contentId in $version.
704
     *
705
     * {@inheritdoc}
706
     *
707
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If content or version is not found
708
     *
709
     * @param mixed $contentId
710
     * @param mixed|null $versionNo Copy all versions if left null
711
     * @param int|null $newOwnerId
712
     *
713
     * @return \eZ\Publish\SPI\Persistence\Content
714
     */
715
    public function copy($contentId, $versionNo = null, $newOwnerId = null)
716
    {
717
        $currentVersionNo = isset($versionNo) ?
718
            $versionNo :
719
            $this->loadContentInfo($contentId)->currentVersionNo;
720
721
        // Copy content in given version or current version
722
        $createStruct = $this->mapper->createCreateStructFromContent(
723
            $this->load($contentId, $currentVersionNo)
724
        );
725
        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...
726
            $createStruct->ownerId = $newOwnerId;
727
        }
728
        $content = $this->internalCreate($createStruct, $currentVersionNo);
729
730
        // If version was not passed also copy other versions
731
        if (!isset($versionNo)) {
732
            $contentType = $this->contentTypeHandler->load($createStruct->typeId);
733
734
            foreach ($this->listVersions($contentId) as $versionInfo) {
735
                if ($versionInfo->versionNo === $currentVersionNo) {
736
                    continue;
737
                }
738
739
                $versionContent = $this->load($contentId, $versionInfo->versionNo);
740
741
                $versionContent->versionInfo->contentInfo->id = $content->versionInfo->contentInfo->id;
742
                $versionContent->versionInfo->modificationDate = $createStruct->modified;
743
                $versionContent->versionInfo->creationDate = $createStruct->modified;
744
                $versionContent->versionInfo->id = $this->contentGateway->insertVersion(
745
                    $versionContent->versionInfo,
746
                    $versionContent->fields
747
                );
748
749
                $this->fieldHandler->createNewFields($versionContent, $contentType);
750
751
                // Create names
752 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...
753
                    $this->contentGateway->setName(
754
                        $content->versionInfo->contentInfo->id,
755
                        $versionInfo->versionNo,
756
                        $name,
757
                        $language
758
                    );
759
                }
760
            }
761
762
            // Batch copy relations for all versions
763
            $this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id);
764
        } else {
765
            // Batch copy relations for published version
766
            $this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id, $versionNo);
767
        }
768
769
        return $content;
770
    }
771
772
    /**
773
     * Creates a relation between $sourceContentId in $sourceContentVersionNo
774
     * and $destinationContentId with a specific $type.
775
     *
776
     * @todo Should the existence verifications happen here or is this supposed to be handled at a higher level?
777
     *
778
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
779
     *
780
     * @return \eZ\Publish\SPI\Persistence\Content\Relation
781
     */
782
    public function addRelation(RelationCreateStruct $createStruct)
783
    {
784
        $relation = $this->mapper->createRelationFromCreateStruct($createStruct);
785
786
        $relation->id = $this->contentGateway->insertRelation($createStruct);
787
788
        return $relation;
789
    }
790
791
    /**
792
     * Removes a relation by relation Id.
793
     *
794
     * @todo Should the existence verifications happen here or is this supposed to be handled at a higher level?
795
     *
796
     * @param mixed $relationId
797
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
798
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
799
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
800
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
801
     */
802
    public function removeRelation($relationId, $type)
803
    {
804
        $this->contentGateway->deleteRelation($relationId, $type);
805
    }
806
807
    /**
808
     * Loads relations from $sourceContentId. Optionally, loads only those with $type and $sourceContentVersionNo.
809
     *
810
     * @param mixed $sourceContentId Source Content ID
811
     * @param mixed|null $sourceContentVersionNo Source Content Version, null if not specified
812
     * @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
813
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
814
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
815
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
816
     *
817
     * @return \eZ\Publish\SPI\Persistence\Content\Relation[]
818
     */
819
    public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null)
820
    {
821
        return $this->mapper->extractRelationsFromRows(
822
            $this->contentGateway->loadRelations($sourceContentId, $sourceContentVersionNo, $type)
823
        );
824
    }
825
826
    /**
827
     * Loads relations from $contentId. Optionally, loads only those with $type.
828
     *
829
     * Only loads relations against published versions.
830
     *
831
     * @param mixed $destinationContentId Destination Content ID
832
     * @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
833
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
834
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
835
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
836
     *
837
     * @return \eZ\Publish\SPI\Persistence\Content\Relation[]
838
     */
839
    public function loadReverseRelations($destinationContentId, $type = null)
840
    {
841
        return $this->mapper->extractRelationsFromRows(
842
            $this->contentGateway->loadReverseRelations($destinationContentId, $type)
843
        );
844
    }
845
846
    /**
847
     * {@inheritdoc}
848
     */
849
    public function removeTranslationFromContent($contentId, $languageCode)
850
    {
851
        @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...
852
            __METHOD__ . ' is deprecated, use deleteTranslationFromContent instead',
853
            E_USER_DEPRECATED
854
        );
855
        $this->deleteTranslationFromContent($contentId, $languageCode);
856
    }
857
858
    /**
859
     * {@inheritdoc}
860
     */
861
    public function deleteTranslationFromContent($contentId, $languageCode)
862
    {
863
        $this->fieldHandler->deleteTranslationFromContentFields(
864
            $contentId,
865
            $this->listVersions($contentId),
866
            $languageCode
867
        );
868
        $this->contentGateway->deleteTranslationFromContent($contentId, $languageCode);
869
    }
870
871
    /**
872
     * {@inheritdoc}
873
     */
874
    public function deleteTranslationFromDraft($contentId, $versionNo, $languageCode)
875
    {
876
        $versionInfo = $this->loadVersionInfo($contentId, $versionNo);
877
878
        $this->fieldHandler->deleteTranslationFromVersionFields(
879
            $versionInfo,
880
            $languageCode
881
        );
882
        $this->contentGateway->deleteTranslationFromVersion(
883
            $contentId,
884
            $versionNo,
885
            $languageCode
886
        );
887
888
        // get all [languageCode => name] entries except the removed Translation
889
        $names = array_filter(
890
            $versionInfo->names,
891
            function ($lang) use ($languageCode) {
892
                return $lang !== $languageCode;
893
            },
894
            ARRAY_FILTER_USE_KEY
895
        );
896
        // set new Content name
897
        foreach ($names as $language => $name) {
898
            $this->contentGateway->setName(
899
                $contentId,
900
                $versionNo,
901
                $name,
902
                $language
903
            );
904
        }
905
906
        // reload entire Version w/o removed Translation
907
        return $this->load($contentId, $versionNo);
908
    }
909
}
910