Completed
Push — master ( 0f847f...a708aa )
by
unknown
61:46 queued 39:27
created

Handler::removeRawContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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