Completed
Push — 6.13 ( b67141...d8f059 )
by
unknown
44:37
created

DoctrineDatabase::setName()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 4
dl 0
loc 61
rs 8.8509
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * File containing the DoctrineDatabase Content Gateway 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\Gateway;
10
11
use Doctrine\DBAL\Connection;
12
use Doctrine\DBAL\DBALException;
13
use eZ\Publish\Core\Base\Exceptions\BadStateException;
14
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
15
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder;
16
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
17
use eZ\Publish\Core\Persistence\Database\UpdateQuery;
18
use eZ\Publish\Core\Persistence\Database\InsertQuery;
19
use eZ\Publish\Core\Persistence\Database\SelectQuery;
20
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
21
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
22
use eZ\Publish\SPI\Persistence\Content;
23
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
24
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
25
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
26
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
27
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
28
use eZ\Publish\SPI\Persistence\Content\Field;
29
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
30
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
31
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
32
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
33
use DOMXPath;
34
use DOMDocument;
35
use PDO;
36
37
/**
38
 * Doctrine database based content gateway.
39
 */
40
class DoctrineDatabase extends Gateway
41
{
42
    /**
43
     * eZ Doctrine database handler.
44
     *
45
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
46
     * @deprecated Start to use DBAL $connection instead.
47
     */
48
    protected $dbHandler;
49
50
    /**
51
     * The native Doctrine connection.
52
     *
53
     * Meant to be used to transition from eZ/Zeta interface to Doctrine.
54
     *
55
     * @var \Doctrine\DBAL\Connection
56
     */
57
    protected $connection;
58
59
    /**
60
     * Query builder.
61
     *
62
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder
63
     */
64
    protected $queryBuilder;
65
66
    /**
67
     * Caching language handler.
68
     *
69
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler
70
     */
71
    protected $languageHandler;
72
73
    /**
74
     * Language mask generator.
75
     *
76
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
77
     */
78
    protected $languageMaskGenerator;
79
80
    /**
81
     * Creates a new gateway based on $db.
82
     *
83
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db
84
     * @param \Doctrine\DBAL\Connection $connection
85
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder $queryBuilder
86
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
87
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
88
     */
89
    public function __construct(
90
        DatabaseHandler $db,
91
        Connection $connection,
92
        QueryBuilder $queryBuilder,
93
        LanguageHandler $languageHandler,
94
        LanguageMaskGenerator $languageMaskGenerator
95
    ) {
96
        $this->dbHandler = $db;
97
        $this->connection = $connection;
98
        $this->queryBuilder = $queryBuilder;
99
        $this->languageHandler = $languageHandler;
0 ignored issues
show
Documentation Bug introduced by
$languageHandler is of type object<eZ\Publish\SPI\Pe...ntent\Language\Handler>, but the property $languageHandler was declared to be of type object<eZ\Publish\Core\P...anguage\CachingHandler>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
100
        $this->languageMaskGenerator = $languageMaskGenerator;
101
    }
102
103
    /**
104
     * Get context definition for external storage layers.
105
     *
106
     * @return array
107
     */
108
    public function getContext()
109
    {
110
        return [
111
            'identifier' => 'LegacyStorage',
112
            'connection' => $this->dbHandler,
113
        ];
114
    }
115
116
    /**
117
     * Inserts a new content object.
118
     *
119
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
120
     * @param mixed $currentVersionNo
121
     *
122
     * @return int ID
123
     */
124
    public function insertContentObject(CreateStruct $struct, $currentVersionNo = 1)
125
    {
126
        $initialLanguageId = !empty($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId;
127
        $initialLanguageCode = $this->languageHandler->load($initialLanguageId)->languageCode;
128
129
        if (isset($struct->name[$initialLanguageCode])) {
130
            $name = $struct->name[$initialLanguageCode];
131
        } else {
132
            $name = '';
133
        }
134
135
        $q = $this->dbHandler->createInsertQuery();
136
        $q->insertInto(
137
            $this->dbHandler->quoteTable('ezcontentobject')
138
        )->set(
139
            $this->dbHandler->quoteColumn('id'),
140
            $this->dbHandler->getAutoIncrementValue('ezcontentobject', 'id')
141
        )->set(
142
            $this->dbHandler->quoteColumn('current_version'),
143
            $q->bindValue($currentVersionNo, null, \PDO::PARAM_INT)
144
        )->set(
145
            $this->dbHandler->quoteColumn('name'),
146
            $q->bindValue($name, null, \PDO::PARAM_STR)
147
        )->set(
148
            $this->dbHandler->quoteColumn('contentclass_id'),
149
            $q->bindValue($struct->typeId, null, \PDO::PARAM_INT)
150
        )->set(
151
            $this->dbHandler->quoteColumn('section_id'),
152
            $q->bindValue($struct->sectionId, null, \PDO::PARAM_INT)
153
        )->set(
154
            $this->dbHandler->quoteColumn('owner_id'),
155
            $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
156
        )->set(
157
            $this->dbHandler->quoteColumn('initial_language_id'),
158
            $q->bindValue($initialLanguageId, null, \PDO::PARAM_INT)
159
        )->set(
160
            $this->dbHandler->quoteColumn('remote_id'),
161
            $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
162
        )->set(
163
            $this->dbHandler->quoteColumn('modified'),
164
            $q->bindValue(0, null, \PDO::PARAM_INT)
165
        )->set(
166
            $this->dbHandler->quoteColumn('published'),
167
            $q->bindValue(0, null, \PDO::PARAM_INT)
168
        )->set(
169
            $this->dbHandler->quoteColumn('status'),
170
            $q->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
171
        )->set(
172
            $this->dbHandler->quoteColumn('language_mask'),
173
            $q->bindValue(
174
                $this->generateLanguageMask(
175
                    $struct->fields,
176
                    $initialLanguageCode,
177
                    $struct->alwaysAvailable
178
                ),
179
                null,
180
                \PDO::PARAM_INT
181
            )
182
        );
183
184
        $q->prepare()->execute();
185
186
        return $this->dbHandler->lastInsertId(
187
            $this->dbHandler->getSequenceName('ezcontentobject', 'id')
188
        );
189
    }
190
191
    /**
192
     * Generates a language mask for $version.
193
     *
194
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
195
     * @param string $initialLanguageCode
196
     * @param bool $alwaysAvailable
197
     *
198
     * @return int
199
     */
200
    protected function generateLanguageMask(array $fields, $initialLanguageCode, $alwaysAvailable)
201
    {
202
        $languages = [$initialLanguageCode => true];
203 View Code Duplication
        foreach ($fields as $field) {
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...
204
            if (isset($languages[$field->languageCode])) {
205
                continue;
206
            }
207
208
            $languages[$field->languageCode] = true;
209
        }
210
211
        if ($alwaysAvailable) {
212
            $languages['always-available'] = true;
213
        }
214
215
        return $this->languageMaskGenerator->generateLanguageMask($languages);
216
    }
217
218
    /**
219
     * Inserts a new version.
220
     *
221
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo
222
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
223
     *
224
     * @return int ID
225
     */
226
    public function insertVersion(VersionInfo $versionInfo, array $fields)
227
    {
228
        /** @var $q \eZ\Publish\Core\Persistence\Database\InsertQuery */
229
        $q = $this->dbHandler->createInsertQuery();
230
        $q->insertInto(
231
            $this->dbHandler->quoteTable('ezcontentobject_version')
232
        )->set(
233
            $this->dbHandler->quoteColumn('id'),
234
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_version', 'id')
235
        )->set(
236
            $this->dbHandler->quoteColumn('version'),
237
            $q->bindValue($versionInfo->versionNo, null, \PDO::PARAM_INT)
238
        )->set(
239
            $this->dbHandler->quoteColumn('modified'),
240
            $q->bindValue($versionInfo->modificationDate, null, \PDO::PARAM_INT)
241
        )->set(
242
            $this->dbHandler->quoteColumn('creator_id'),
243
            $q->bindValue($versionInfo->creatorId, null, \PDO::PARAM_INT)
244
        )->set(
245
            $this->dbHandler->quoteColumn('created'),
246
            $q->bindValue($versionInfo->creationDate, null, \PDO::PARAM_INT)
247
        )->set(
248
            $this->dbHandler->quoteColumn('status'),
249
            $q->bindValue($versionInfo->status, null, \PDO::PARAM_INT)
250
        )->set(
251
            $this->dbHandler->quoteColumn('initial_language_id'),
252
            $q->bindValue(
253
                $this->languageHandler->loadByLanguageCode($versionInfo->initialLanguageCode)->id,
254
                null,
255
                \PDO::PARAM_INT
256
            )
257
        )->set(
258
            $this->dbHandler->quoteColumn('contentobject_id'),
259
            $q->bindValue($versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
260
        )->set(
261
            // As described in field mapping document
262
            $this->dbHandler->quoteColumn('workflow_event_pos'),
263
            $q->bindValue(0, null, \PDO::PARAM_INT)
264
        )->set(
265
            $this->dbHandler->quoteColumn('language_mask'),
266
            $q->bindValue(
267
                $this->generateLanguageMask(
268
                    $fields,
269
                    $versionInfo->initialLanguageCode,
270
                    $versionInfo->contentInfo->alwaysAvailable
271
                ),
272
                null,
273
                \PDO::PARAM_INT
274
            )
275
        );
276
277
        $q->prepare()->execute();
278
279
        return $this->dbHandler->lastInsertId(
280
            $this->dbHandler->getSequenceName('ezcontentobject_version', 'id')
281
        );
282
    }
283
284
    /**
285
     * Updates an existing content identified by $contentId in respect to $struct.
286
     *
287
     * @param int $contentId
288
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $struct
289
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $prePublishVersionInfo Provided on publish
290
     */
291
    public function updateContent($contentId, MetadataUpdateStruct $struct, VersionInfo $prePublishVersionInfo = null)
292
    {
293
        $query = $this->connection->createQueryBuilder();
294
        $query->update('ezcontentobject');
295
296
        $fieldsForUpdateMap = [
297
            'name' => ['value' => $struct->name, 'type' => PDO::PARAM_STR],
298
            'initial_language_id' => ['value' => $struct->mainLanguageId, 'type' => PDO::PARAM_INT],
299
            'modified' => ['value' => $struct->modificationDate, 'type' => PDO::PARAM_INT],
300
            'owner_id' => ['value' => $struct->ownerId, 'type' => PDO::PARAM_INT],
301
            'published' => ['value' => $struct->publicationDate, 'type' => PDO::PARAM_INT],
302
            'remote_id' => ['value' => $struct->remoteId, 'type' => PDO::PARAM_STR],
303
        ];
304
305
        foreach ($fieldsForUpdateMap as $fieldName => $field) {
306
            if (null === $field['value']) {
307
                continue;
308
            }
309
            $query->set(
310
                $fieldName,
311
                $query->createNamedParameter($field['value'], $field['type'], ":{$fieldName}")
312
            );
313
        }
314
315
        if ($prePublishVersionInfo !== null) {
316
            $languages = [];
317
            foreach ($prePublishVersionInfo->languageCodes as $languageCodes) {
318
                if (!isset($languages[$languageCodes])) {
319
                    $languages[$languageCodes] = true;
320
                }
321
            }
322
            $languages['always-available'] = isset($struct->alwaysAvailable) ? $struct->alwaysAvailable :
323
                $prePublishVersionInfo->contentInfo->alwaysAvailable;
324
325
            $mask = $this->languageMaskGenerator->generateLanguageMask($languages);
326
327
            $query->set(
328
                'language_mask',
329
                $query->createNamedParameter($mask, PDO::PARAM_INT, ':languageMask')
330
            );
331
        }
332
333
        $query->where(
334
            $query->expr()->eq(
335
                'id',
336
                $query->createNamedParameter($contentId, PDO::PARAM_INT, ':contentId')
337
            )
338
        );
339
340
        if (!empty($query->getQueryPart('set'))) {
341
            $query->execute();
342
        }
343
344
        // Handle alwaysAvailable flag update separately as it's a more complex task and has impact on several tables
345
        if (isset($struct->alwaysAvailable) || isset($struct->mainLanguageId)) {
346
            $this->updateAlwaysAvailableFlag($contentId, $struct->alwaysAvailable);
347
        }
348
    }
349
350
    /**
351
     * Updates version $versionNo for content identified by $contentId, in respect to $struct.
352
     *
353
     * @param int $contentId
354
     * @param int $versionNo
355
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $struct
356
     */
357
    public function updateVersion($contentId, $versionNo, UpdateStruct $struct)
358
    {
359
        $q = $this->dbHandler->createUpdateQuery();
360
        $q->update(
361
            $this->dbHandler->quoteTable('ezcontentobject_version')
362
        )->set(
363
            $this->dbHandler->quoteColumn('creator_id'),
364
            $q->bindValue($struct->creatorId, null, \PDO::PARAM_INT)
365
        )->set(
366
            $this->dbHandler->quoteColumn('modified'),
367
            $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
368
        )->set(
369
            $this->dbHandler->quoteColumn('initial_language_id'),
370
            $q->bindValue($struct->initialLanguageId, null, \PDO::PARAM_INT)
371
        )->set(
372
            $this->dbHandler->quoteColumn('language_mask'),
373
            $q->expr->bitOr(
374
                $this->dbHandler->quoteColumn('language_mask'),
375
                $q->bindValue(
376
                    $this->generateLanguageMask(
377
                        $struct->fields,
378
                        $this->languageHandler->load($struct->initialLanguageId)->languageCode,
379
                        false
380
                    ),
381
                    null,
382
                    \PDO::PARAM_INT
383
                )
384
            )
385
        )->where(
386
            $q->expr->lAnd(
387
                $q->expr->eq(
388
                    $this->dbHandler->quoteColumn('contentobject_id'),
389
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
390
                ),
391
                $q->expr->eq(
392
                    $this->dbHandler->quoteColumn('version'),
393
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
394
                )
395
            )
396
        );
397
        $q->prepare()->execute();
398
    }
399
400
    /**
401
     * Updates "always available" flag for Content identified by $contentId, in respect to
402
     * Content's current main language and optionally new $alwaysAvailable state.
403
     *
404
     * @param int $contentId
405
     * @param bool|null $alwaysAvailable New "always available" value or null if not defined
406
     */
407
    public function updateAlwaysAvailableFlag($contentId, $alwaysAvailable = null)
408
    {
409
        // We will need to know some info on the current language mask to update the flag
410
        // everywhere needed
411
        $contentInfoRow = $this->loadContentInfo($contentId);
412
        if (!isset($alwaysAvailable)) {
413
            $alwaysAvailable = 1 === ($contentInfoRow['language_mask'] & 1);
414
        }
415
416
        /** @var $q \eZ\Publish\Core\Persistence\Database\UpdateQuery */
417
        $q = $this->dbHandler->createUpdateQuery();
418
        $q
419
            ->update($this->dbHandler->quoteTable('ezcontentobject'))
420
            ->set(
421
                $this->dbHandler->quoteColumn('language_mask'),
422
                $alwaysAvailable ?
423
                    $q->expr->bitOr($this->dbHandler->quoteColumn('language_mask'), 1) :
424
                    $q->expr->bitAnd($this->dbHandler->quoteColumn('language_mask'), -2)
425
            )
426
            ->where(
427
                $q->expr->eq(
428
                    $this->dbHandler->quoteColumn('id'),
429
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
430
                )
431
            );
432
        $q->prepare()->execute();
433
434
        // Now we need to update ezcontentobject_name
435
        /** @var $qName \eZ\Publish\Core\Persistence\Database\UpdateQuery */
436
        $qName = $this->dbHandler->createUpdateQuery();
437
        $qName
438
            ->update($this->dbHandler->quoteTable('ezcontentobject_name'))
439
            ->set(
440
                $this->dbHandler->quoteColumn('language_id'),
441
                $alwaysAvailable ?
442
                    $qName->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
443
                    $qName->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
444
            )
445
            ->where(
446
                $qName->expr->lAnd(
447
                    $qName->expr->eq(
448
                        $this->dbHandler->quoteColumn('contentobject_id'),
449
                        $qName->bindValue($contentId, null, \PDO::PARAM_INT)
450
                    ),
451
                    $qName->expr->eq(
452
                        $this->dbHandler->quoteColumn('content_version'),
453
                        $qName->bindValue(
454
                            $contentInfoRow['current_version'],
455
                            null,
456
                            \PDO::PARAM_INT
457
                        )
458
                    )
459
                )
460
            );
461
        $qName->prepare()->execute();
462
463
        // Now update ezcontentobject_attribute for current version
464
        // Create update query that will be reused
465
        /** @var $qAttr \eZ\Publish\Core\Persistence\Database\UpdateQuery */
466
        $qAttr = $this->dbHandler->createUpdateQuery();
467
        $qAttr
468
            ->update($this->dbHandler->quoteTable('ezcontentobject_attribute'))
469
            ->where(
470
                $qAttr->expr->lAnd(
471
                    $qAttr->expr->eq(
472
                        $this->dbHandler->quoteColumn('contentobject_id'),
473
                        $qAttr->bindValue($contentId, null, \PDO::PARAM_INT)
474
                    ),
475
                    $qAttr->expr->eq(
476
                        $this->dbHandler->quoteColumn('version'),
477
                        $qAttr->bindValue(
478
                            $contentInfoRow['current_version'],
479
                            null,
480
                            \PDO::PARAM_INT
481
                        )
482
                    )
483
                )
484
            );
485
486
        // If there is only a single language, update all fields and return
487
        if (!$this->languageMaskGenerator->isLanguageMaskComposite($contentInfoRow['language_mask'])) {
488
            $qAttr->set(
489
                $this->dbHandler->quoteColumn('language_id'),
490
                $alwaysAvailable ?
491
                    $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
492
                    $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
493
            );
494
            $qAttr->prepare()->execute();
495
496
            return;
497
        }
498
499
        // Otherwise:
500
        // 1. Remove always available flag on all fields
501
        $qAttr->set(
502
            $this->dbHandler->quoteColumn('language_id'),
503
            $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
504
        );
505
        $qAttr->prepare()->execute();
506
507
        // 2. If Content is always available set the flag only on fields in main language
508
        if ($alwaysAvailable) {
509
            $qAttr->set(
510
                $this->dbHandler->quoteColumn('language_id'),
511
                $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1)
512
            );
513
            $qAttr->where(
514
                $qAttr->expr->gt(
515
                    $qAttr->expr->bitAnd(
516
                        $this->dbHandler->quoteColumn('language_id'),
517
                        $qAttr->bindValue($contentInfoRow['initial_language_id'], null, PDO::PARAM_INT)
518
                    ),
519
                    $qAttr->bindValue(0, null, PDO::PARAM_INT)
520
                )
521
            );
522
            $qAttr->prepare()->execute();
523
        }
524
    }
525
526
    /**
527
     * Sets the status of the version identified by $contentId and $version to $status.
528
     *
529
     * The $status can be one of STATUS_DRAFT, STATUS_PUBLISHED, STATUS_ARCHIVED
530
     *
531
     * @param int $contentId
532
     * @param int $version
533
     * @param int $status
534
     *
535
     * @return bool
536
     */
537
    public function setStatus($contentId, $version, $status)
538
    {
539
        $q = $this->dbHandler->createUpdateQuery();
540
        $q->update(
541
            $this->dbHandler->quoteTable('ezcontentobject_version')
542
        )->set(
543
            $this->dbHandler->quoteColumn('status'),
544
            $q->bindValue($status, null, \PDO::PARAM_INT)
545
        )->set(
546
            $this->dbHandler->quoteColumn('modified'),
547
            $q->bindValue(time(), null, \PDO::PARAM_INT)
548
        )->where(
549
            $q->expr->lAnd(
550
                $q->expr->eq(
551
                    $this->dbHandler->quoteColumn('contentobject_id'),
552
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
553
                ),
554
                $q->expr->eq(
555
                    $this->dbHandler->quoteColumn('version'),
556
                    $q->bindValue($version, null, \PDO::PARAM_INT)
557
                )
558
            )
559
        );
560
        $statement = $q->prepare();
561
        $statement->execute();
562
563
        if ((bool)$statement->rowCount() === false) {
564
            return false;
565
        }
566
567
        if ($status !== APIVersionInfo::STATUS_PUBLISHED) {
568
            return true;
569
        }
570
571
        // If the version's status is PUBLISHED, we set the content to published status as well
572
        $q = $this->dbHandler->createUpdateQuery();
573
        $q->update(
574
            $this->dbHandler->quoteTable('ezcontentobject')
575
        )->set(
576
            $this->dbHandler->quoteColumn('status'),
577
            $q->bindValue(ContentInfo::STATUS_PUBLISHED, null, \PDO::PARAM_INT)
578
        )->set(
579
            $this->dbHandler->quoteColumn('current_version'),
580
            $q->bindValue($version, null, \PDO::PARAM_INT)
581
        )->where(
582
            $q->expr->eq(
583
                $this->dbHandler->quoteColumn('id'),
584
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
585
            )
586
        );
587
        $statement = $q->prepare();
588
        $statement->execute();
589
590
        return (bool)$statement->rowCount();
591
    }
592
593
    /**
594
     * Inserts a new field.
595
     *
596
     * Only used when a new field is created (i.e. a new object or a field in a
597
     * new language!). After that, field IDs need to stay the same, only the
598
     * version number changes.
599
     *
600
     * @param \eZ\Publish\SPI\Persistence\Content $content
601
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
602
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
603
     *
604
     * @return int ID
605
     */
606
    public function insertNewField(Content $content, Field $field, StorageFieldValue $value)
607
    {
608
        $q = $this->dbHandler->createInsertQuery();
609
610
        $this->setInsertFieldValues($q, $content, $field, $value);
611
612
        // Insert with auto increment ID
613
        $q->set(
614
            $this->dbHandler->quoteColumn('id'),
615
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_attribute', 'id')
616
        );
617
618
        $q->prepare()->execute();
619
620
        return $this->dbHandler->lastInsertId(
621
            $this->dbHandler->getSequenceName('ezcontentobject_attribute', 'id')
622
        );
623
    }
624
625
    /**
626
     * Inserts an existing field.
627
     *
628
     * Used to insert a field with an exsting ID but a new version number.
629
     *
630
     * @param Content $content
631
     * @param Field $field
632
     * @param StorageFieldValue $value
633
     */
634
    public function insertExistingField(Content $content, Field $field, StorageFieldValue $value)
635
    {
636
        $q = $this->dbHandler->createInsertQuery();
637
638
        $this->setInsertFieldValues($q, $content, $field, $value);
639
640
        $q->set(
641
            $this->dbHandler->quoteColumn('id'),
642
            $q->bindValue($field->id, null, \PDO::PARAM_INT)
643
        );
644
645
        $q->prepare()->execute();
646
    }
647
648
    /**
649
     * Sets field (ezcontentobject_attribute) values to the given query.
650
     *
651
     * @param \eZ\Publish\Core\Persistence\Database\InsertQuery $q
652
     * @param Content $content
653
     * @param Field $field
654
     * @param StorageFieldValue $value
655
     */
656
    protected function setInsertFieldValues(InsertQuery $q, Content $content, Field $field, StorageFieldValue $value)
657
    {
658
        $q->insertInto(
659
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
660
        )->set(
661
            $this->dbHandler->quoteColumn('contentobject_id'),
662
            $q->bindValue($content->versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
663
        )->set(
664
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
665
            $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
666
        )->set(
667
            $this->dbHandler->quoteColumn('data_type_string'),
668
            $q->bindValue($field->type)
669
        )->set(
670
            $this->dbHandler->quoteColumn('language_code'),
671
            $q->bindValue($field->languageCode)
672
        )->set(
673
            $this->dbHandler->quoteColumn('version'),
674
            $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
675
        )->set(
676
            $this->dbHandler->quoteColumn('data_float'),
677
            $q->bindValue($value->dataFloat)
678
        )->set(
679
            $this->dbHandler->quoteColumn('data_int'),
680
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
681
        )->set(
682
            $this->dbHandler->quoteColumn('data_text'),
683
            $q->bindValue($value->dataText)
684
        )->set(
685
            $this->dbHandler->quoteColumn('sort_key_int'),
686
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
687
        )->set(
688
            $this->dbHandler->quoteColumn('sort_key_string'),
689
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
690
        )->set(
691
            $this->dbHandler->quoteColumn('language_id'),
692
            $q->bindValue(
693
                $this->languageMaskGenerator->generateLanguageIndicator(
694
                    $field->languageCode,
695
                    $this->isLanguageAlwaysAvailable($content, $field->languageCode)
696
                ),
697
                null,
698
                \PDO::PARAM_INT
699
            )
700
        );
701
    }
702
703
    /**
704
     * Checks if $languageCode is always available in $content.
705
     *
706
     * @param \eZ\Publish\SPI\Persistence\Content $content
707
     * @param string $languageCode
708
     *
709
     * @return bool
710
     */
711
    protected function isLanguageAlwaysAvailable(Content $content, $languageCode)
712
    {
713
        return
714
            $content->versionInfo->contentInfo->alwaysAvailable &&
715
            $content->versionInfo->contentInfo->mainLanguageCode === $languageCode
716
        ;
717
    }
718
719
    /**
720
     * Updates an existing field.
721
     *
722
     * @param Field $field
723
     * @param StorageFieldValue $value
724
     */
725
    public function updateField(Field $field, StorageFieldValue $value)
726
    {
727
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
728
        // cannot change on update
729
        $q = $this->dbHandler->createUpdateQuery();
730
        $this->setFieldUpdateValues($q, $value);
731
        $q->where(
732
            $q->expr->lAnd(
733
                $q->expr->eq(
734
                    $this->dbHandler->quoteColumn('id'),
735
                    $q->bindValue($field->id, null, \PDO::PARAM_INT)
736
                ),
737
                $q->expr->eq(
738
                    $this->dbHandler->quoteColumn('version'),
739
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
740
                )
741
            )
742
        );
743
        $q->prepare()->execute();
744
    }
745
746
    /**
747
     * Sets update fields for $value on $q.
748
     *
749
     * @param \eZ\Publish\Core\Persistence\Database\UpdateQuery $q
750
     * @param StorageFieldValue $value
751
     */
752
    protected function setFieldUpdateValues(UpdateQuery $q, StorageFieldValue $value)
753
    {
754
        $q->update(
755
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
756
        )->set(
757
            $this->dbHandler->quoteColumn('data_float'),
758
            $q->bindValue($value->dataFloat)
759
        )->set(
760
            $this->dbHandler->quoteColumn('data_int'),
761
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
762
        )->set(
763
            $this->dbHandler->quoteColumn('data_text'),
764
            $q->bindValue($value->dataText)
765
        )->set(
766
            $this->dbHandler->quoteColumn('sort_key_int'),
767
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
768
        )->set(
769
            $this->dbHandler->quoteColumn('sort_key_string'),
770
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
771
        );
772
    }
773
774
    /**
775
     * Updates an existing, non-translatable field.
776
     *
777
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
778
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
779
     * @param int $contentId
780
     */
781 View Code Duplication
    public function updateNonTranslatableField(
782
        Field $field,
783
        StorageFieldValue $value,
784
        $contentId
785
    ) {
786
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
787
        // cannot change on update
788
        $q = $this->dbHandler->createUpdateQuery();
789
        $this->setFieldUpdateValues($q, $value);
790
        $q->where(
791
            $q->expr->lAnd(
792
                $q->expr->eq(
793
                    $this->dbHandler->quoteColumn('contentclassattribute_id'),
794
                    $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
795
                ),
796
                $q->expr->eq(
797
                    $this->dbHandler->quoteColumn('contentobject_id'),
798
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
799
                ),
800
                $q->expr->eq(
801
                    $this->dbHandler->quoteColumn('version'),
802
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
803
                )
804
            )
805
        );
806
        $q->prepare()->execute();
807
    }
808
809
    /**
810
     * {@inheritdoc}
811
     */
812
    public function load($contentId, $version = null, array $translations = null)
813
    {
814
        return $this->internalLoadContent([$contentId], $version, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 812 can also be of type array; however, eZ\Publish\Core\Persiste...::internalLoadContent() 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...
815
    }
816
817
    /**
818
     * {@inheritdoc}
819
     */
820
    public function loadContentList(array $contentIds, array $translations = null)
821
    {
822
        return $this->internalLoadContent($contentIds, null, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 820 can also be of type array; however, eZ\Publish\Core\Persiste...::internalLoadContent() 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...
823
    }
824
825
    /**
826
     * @see loadContentList()
827
     *
828
     * @param array $contentIds
829
     * @param int|null $version
830
     * @param string[]|null $translations
831
     *
832
     * @return array
833
     */
834
    private function internalLoadContent(array $contentIds, $version = null, array $translations = null)
835
    {
836
        $queryBuilder = $this->connection->createQueryBuilder();
837
        $expr = $queryBuilder->expr();
838
        $queryBuilder
839
            ->select(
840
                'c.id AS ezcontentobject_id',
841
                'c.contentclass_id AS ezcontentobject_contentclass_id',
842
                'c.section_id AS ezcontentobject_section_id',
843
                'c.owner_id AS ezcontentobject_owner_id',
844
                'c.remote_id AS ezcontentobject_remote_id',
845
                'c.current_version AS ezcontentobject_current_version',
846
                'c.initial_language_id AS ezcontentobject_initial_language_id',
847
                'c.modified AS ezcontentobject_modified',
848
                'c.published AS ezcontentobject_published',
849
                'c.status AS ezcontentobject_status',
850
                'c.name AS ezcontentobject_name',
851
                'c.language_mask AS ezcontentobject_language_mask',
852
                'v.id AS ezcontentobject_version_id',
853
                'v.version AS ezcontentobject_version_version',
854
                'v.modified AS ezcontentobject_version_modified',
855
                'v.creator_id AS ezcontentobject_version_creator_id',
856
                'v.created AS ezcontentobject_version_created',
857
                'v.status AS ezcontentobject_version_status',
858
                'v.language_mask AS ezcontentobject_version_language_mask',
859
                'v.initial_language_id AS ezcontentobject_version_initial_language_id',
860
                'a.id AS ezcontentobject_attribute_id',
861
                'a.contentclassattribute_id AS ezcontentobject_attribute_contentclassattribute_id',
862
                'a.data_type_string AS ezcontentobject_attribute_data_type_string',
863
                'a.language_code AS ezcontentobject_attribute_language_code',
864
                'a.language_id AS ezcontentobject_attribute_language_id',
865
                'a.data_float AS ezcontentobject_attribute_data_float',
866
                'a.data_int AS ezcontentobject_attribute_data_int',
867
                'a.data_text AS ezcontentobject_attribute_data_text',
868
                'a.sort_key_int AS ezcontentobject_attribute_sort_key_int',
869
                'a.sort_key_string AS ezcontentobject_attribute_sort_key_string',
870
                't.main_node_id AS ezcontentobject_tree_main_node_id'
871
            )
872
            ->from('ezcontentobject', 'c')
873
            ->innerJoin(
874
                'c',
875
                'ezcontentobject_version',
876
                'v',
877
                $expr->andX(
878
                    $expr->eq('c.id', 'v.contentobject_id'),
879
                    $expr->eq('v.version', $version ?: 'c.current_version')
880
                )
881
            )
882
            ->innerJoin(
883
                'v',
884
                'ezcontentobject_attribute',
885
                'a',
886
                $expr->andX(
887
                    $expr->eq('v.contentobject_id', 'a.contentobject_id'),
888
                    $expr->eq('v.version', 'a.version')
889
                )
890
            )
891
            ->leftJoin(
892
                'c',
893
                'ezcontentobject_tree',
894
                't',
895
                $expr->andX(
896
                    $expr->eq('c.id', 't.contentobject_id'),
897
                    $expr->eq('t.node_id', 't.main_node_id')
898
                )
899
            );
900
901
        $queryBuilder->where(
902
            $expr->in(
903
                'c.id',
904
                $queryBuilder->createNamedParameter($contentIds, Connection::PARAM_INT_ARRAY)
905
            )
906
        );
907
908
        if (!empty($translations)) {
909
            $queryBuilder->andWhere(
910
                $expr->in(
911
                    'a.language_code',
912
                    $queryBuilder->createNamedParameter($translations, Connection::PARAM_STR_ARRAY)
913
                )
914
            );
915
        }
916
917
        return $queryBuilder->execute()->fetchAll(PDO::FETCH_ASSOC);
918
    }
919
920
    /**
921
     * Get query builder to load Content Info data.
922
     *
923
     * @see loadContentInfo(), loadContentInfoByRemoteId(), loadContentInfoList(), loadContentInfoByLocationId()
924
     *
925
     * @param bool $joinMainLocation
926
     *
927
     * @return \Doctrine\DBAL\Query\QueryBuilder
928
     */
929
    private function createLoadContentInfoQueryBuilder($joinMainLocation = true)
930
    {
931
        $queryBuilder = $this->connection->createQueryBuilder();
932
        $expr = $queryBuilder->expr();
933
934
        $joinCondition = $expr->eq('c.id', 't.contentobject_id');
935
        if ($joinMainLocation) {
936
            // wrap join condition with AND operator and join by a Main Location
937
            $joinCondition = $expr->andX(
938
                $joinCondition,
939
                $expr->eq('t.node_id', 't.main_node_id')
940
            );
941
        }
942
943
        $queryBuilder
944
            ->select('c.*', 't.main_node_id AS ezcontentobject_tree_main_node_id')
945
            ->from('ezcontentobject', 'c')
946
            ->leftJoin(
947
                'c',
948
                'ezcontentobject_tree',
949
                't',
950
                $joinCondition
0 ignored issues
show
Bug introduced by
It seems like $joinCondition defined by $expr->andX($joinConditi...id', 't.main_node_id')) on line 937 can also be of type object<Doctrine\DBAL\Que...on\CompositeExpression>; however, Doctrine\DBAL\Query\QueryBuilder::leftJoin() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
951
            );
952
953
        return $queryBuilder;
954
    }
955
956
    /**
957
     * Loads info for content identified by $contentId.
958
     * Will basically return a hash containing all field values for ezcontentobject table plus some additional keys:
959
     *  - always_available => Boolean indicating if content's language mask contains alwaysAvailable bit field
960
     *  - main_language_code => Language code for main (initial) language. E.g. "eng-GB".
961
     *
962
     * @param int $contentId
963
     *
964
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
965
     *
966
     * @return array
967
     */
968 View Code Duplication
    public function loadContentInfo($contentId)
969
    {
970
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
971
        $queryBuilder
972
            ->where('c.id = :id')
973
            ->setParameter('id', $contentId, PDO::PARAM_INT);
974
975
        $results = $queryBuilder->execute()->fetchAll(PDO::FETCH_ASSOC);
976
        if (empty($results)) {
977
            throw new NotFound('content', "id: $contentId");
978
        }
979
980
        return $results[0];
981
    }
982
983
    public function loadContentInfoList(array $contentIds)
984
    {
985
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
986
        $queryBuilder
987
            ->where('c.id IN (:ids)')
988
            ->setParameter('ids', $contentIds, Connection::PARAM_INT_ARRAY);
989
990
        return $queryBuilder->execute()->fetchAll(PDO::FETCH_ASSOC);
991
    }
992
993
    /**
994
     * Loads info for a content object identified by its remote ID.
995
     *
996
     * Returns an array with the relevant data.
997
     *
998
     * @param mixed $remoteId
999
     *
1000
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1001
     *
1002
     * @return array
1003
     */
1004 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
1005
    {
1006
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1007
        $queryBuilder
1008
            ->where('c.remote_id = :id')
1009
            ->setParameter('id', $remoteId, PDO::PARAM_STR);
1010
1011
        $results = $queryBuilder->execute()->fetchAll(PDO::FETCH_ASSOC);
1012
        if (empty($results)) {
1013
            throw new NotFound('content', "remote_id: $remoteId");
1014
        }
1015
1016
        return $results[0];
1017
    }
1018
1019
    /**
1020
     * Loads info for a content object identified by its location ID (node ID).
1021
     *
1022
     * Returns an array with the relevant data.
1023
     *
1024
     * @param int $locationId
1025
     *
1026
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1027
     *
1028
     * @return array
1029
     */
1030 View Code Duplication
    public function loadContentInfoByLocationId($locationId)
1031
    {
1032
        $queryBuilder = $this->createLoadContentInfoQueryBuilder(false);
1033
        $queryBuilder
1034
            ->where('t.node_id = :id')
1035
            ->setParameter('id', $locationId, PDO::PARAM_INT);
1036
1037
        $results = $queryBuilder->execute()->fetchAll(PDO::FETCH_ASSOC);
1038
        if (empty($results)) {
1039
            throw new NotFound('content', "node_id: $locationId");
1040
        }
1041
1042
        return $results[0];
1043
    }
1044
1045
    /**
1046
     * Loads version info for content identified by $contentId and $versionNo.
1047
     * Will basically return a hash containing all field values from ezcontentobject_version table plus following keys:
1048
     *  - names => Hash of content object names. Key is the language code, value is the name.
1049
     *  - languages => Hash of language ids. Key is the language code (e.g. "eng-GB"), value is the language numeric id without the always available bit.
1050
     *  - initial_language_code => Language code for initial language in this version.
1051
     *
1052
     * @param int $contentId
1053
     * @param int|null $versionNo
1054
     *
1055
     * @return array
1056
     */
1057
    public function loadVersionInfo($contentId, $versionNo = null)
1058
    {
1059
        $queryBuilder = $this->queryBuilder->createVersionInfoQueryBuilder($versionNo);
1060
        $queryBuilder->where(
1061
            $queryBuilder->expr()->eq(
1062
                'c.id',
1063
                $queryBuilder->createNamedParameter($contentId, PDO::PARAM_INT)
1064
            )
1065
        );
1066
1067
        return $queryBuilder->execute()->fetchAll(PDO::FETCH_ASSOC);
1068
    }
1069
1070
    /**
1071
     * Returns data for all versions with given status created by the given $userId.
1072
     *
1073
     * @param int $userId
1074
     * @param int $status
1075
     *
1076
     * @return string[][]
1077
     */
1078
    public function listVersionsForUser($userId, $status = VersionInfo::STATUS_DRAFT)
1079
    {
1080
        $query = $this->queryBuilder->createVersionInfoFindQuery();
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Persiste...eVersionInfoFindQuery() has been deprecated with message: Move to Doctrine based query builder {@see createVersionInfoQueryBuilder}.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1081
        $query->where(
1082
            $query->expr->lAnd(
1083
                $query->expr->eq(
1084
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1085
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1086
                ),
1087
                $query->expr->eq(
1088
                    $this->dbHandler->quoteColumn('creator_id', 'ezcontentobject_version'),
1089
                    $query->bindValue($userId, null, \PDO::PARAM_INT)
1090
                )
1091
            )
1092
        );
1093
1094
        return $this->listVersionsHelper($query);
1095
    }
1096
1097
    /**
1098
     * Returns all version data for the given $contentId, optionally filtered by status.
1099
     *
1100
     * Result is returned with oldest version first (using version id as it has index and is auto increment).
1101
     *
1102
     * @param mixed $contentId
1103
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
1104
     * @param int $limit Limit for items returned, -1 means none.
1105
     *
1106
     * @return string[][]
1107
     */
1108
    public function listVersions($contentId, $status = null, $limit = -1)
1109
    {
1110
        $query = $this->queryBuilder->createVersionInfoFindQuery();
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Persiste...eVersionInfoFindQuery() has been deprecated with message: Move to Doctrine based query builder {@see createVersionInfoQueryBuilder}.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1111
1112
        $filter = $query->expr->eq(
1113
            $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1114
            $query->bindValue($contentId, null, \PDO::PARAM_INT)
1115
        );
1116
1117
        if ($status !== null) {
1118
            $filter = $query->expr->lAnd(
1119
                $filter,
1120
                $query->expr->eq(
1121
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1122
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1123
                )
1124
            );
1125
        }
1126
1127
        $query->where($filter);
1128
1129
        if ($limit > 0) {
1130
            $query->limit($limit);
1131
        }
1132
1133
        return $this->listVersionsHelper($query);
1134
    }
1135
1136
    /**
1137
     * Helper for {@see listVersions()} and {@see listVersionsForUser()} that filters duplicates
1138
     * that are the result of the cartesian product performed by createVersionInfoFindQuery().
1139
     *
1140
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
1141
     *
1142
     * @return string[][]
1143
     */
1144
    private function listVersionsHelper(SelectQuery $query)
1145
    {
1146
        $query->orderBy(
1147
            $this->dbHandler->quoteColumn('id', 'ezcontentobject_version')
1148
        );
1149
1150
        $statement = $query->prepare();
1151
        $statement->execute();
1152
1153
        $results = [];
1154
        $previousId = null;
1155
        foreach ($statement->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1156
            if ($row['ezcontentobject_version_id'] == $previousId) {
1157
                continue;
1158
            }
1159
1160
            $previousId = $row['ezcontentobject_version_id'];
1161
            $results[] = $row;
1162
        }
1163
1164
        return $results;
1165
    }
1166
1167
    /**
1168
     * Returns all version numbers for the given $contentId.
1169
     *
1170
     * @param mixed $contentId
1171
     *
1172
     * @return int[]
1173
     */
1174 View Code Duplication
    public function listVersionNumbers($contentId)
1175
    {
1176
        $query = $this->dbHandler->createSelectQuery();
1177
        $query->selectDistinct(
1178
            $this->dbHandler->quoteColumn('version')
1179
        )->from(
1180
            $this->dbHandler->quoteTable('ezcontentobject_version')
1181
        )->where(
1182
            $query->expr->eq(
1183
                $this->dbHandler->quoteColumn('contentobject_id'),
1184
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1185
            )
1186
        );
1187
1188
        $statement = $query->prepare();
1189
        $statement->execute();
1190
1191
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1192
    }
1193
1194
    /**
1195
     * Returns last version number for content identified by $contentId.
1196
     *
1197
     * @param int $contentId
1198
     *
1199
     * @return int
1200
     */
1201 View Code Duplication
    public function getLastVersionNumber($contentId)
1202
    {
1203
        $query = $this->dbHandler->createSelectQuery();
1204
        $query->select(
1205
            $query->expr->max($this->dbHandler->quoteColumn('version'))
1206
        )->from(
1207
            $this->dbHandler->quoteTable('ezcontentobject_version')
1208
        )->where(
1209
            $query->expr->eq(
1210
                $this->dbHandler->quoteColumn('contentobject_id'),
1211
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1212
            )
1213
        );
1214
1215
        $statement = $query->prepare();
1216
        $statement->execute();
1217
1218
        return (int)$statement->fetchColumn();
1219
    }
1220
1221
    /**
1222
     * Returns all IDs for locations that refer to $contentId.
1223
     *
1224
     * @param int $contentId
1225
     *
1226
     * @return int[]
1227
     */
1228 View Code Duplication
    public function getAllLocationIds($contentId)
1229
    {
1230
        $query = $this->dbHandler->createSelectQuery();
1231
        $query->select(
1232
            $this->dbHandler->quoteColumn('node_id')
1233
        )->from(
1234
            $this->dbHandler->quoteTable('ezcontentobject_tree')
1235
        )->where(
1236
            $query->expr->eq(
1237
                $this->dbHandler->quoteColumn('contentobject_id'),
1238
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1239
            )
1240
        );
1241
1242
        $statement = $query->prepare();
1243
        $statement->execute();
1244
1245
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1246
    }
1247
1248
    /**
1249
     * Returns all field IDs of $contentId grouped by their type.
1250
     * If $versionNo is set only field IDs for that version are returned.
1251
     * If $languageCode is set, only field IDs for that language are returned.
1252
     *
1253
     * @param int $contentId
1254
     * @param int|null $versionNo
1255
     * @param string|null $languageCode
1256
     *
1257
     * @return int[][]
1258
     */
1259
    public function getFieldIdsByType($contentId, $versionNo = null, $languageCode = null)
1260
    {
1261
        $query = $this->dbHandler->createSelectQuery();
1262
        $query->select(
1263
            $this->dbHandler->quoteColumn('id'),
1264
            $this->dbHandler->quoteColumn('data_type_string')
1265
        )->from(
1266
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1267
        )->where(
1268
            $query->expr->eq(
1269
                $this->dbHandler->quoteColumn('contentobject_id'),
1270
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1271
            )
1272
        );
1273
1274
        if (isset($versionNo)) {
1275
            $query->where(
1276
                $query->expr->eq(
1277
                    $this->dbHandler->quoteColumn('version'),
1278
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1279
                )
1280
            );
1281
        }
1282
1283
        if (isset($languageCode)) {
1284
            $query->where(
1285
                $query->expr->eq(
1286
                    $this->dbHandler->quoteColumn('language_code'),
1287
                    $query->bindValue($languageCode, null, \PDO::PARAM_STR)
1288
                )
1289
            );
1290
        }
1291
1292
        $statement = $query->prepare();
1293
        $statement->execute();
1294
1295
        $result = [];
1296
        foreach ($statement->fetchAll() as $row) {
1297
            if (!isset($result[$row['data_type_string']])) {
1298
                $result[$row['data_type_string']] = [];
1299
            }
1300
            $result[$row['data_type_string']][] = (int)$row['id'];
1301
        }
1302
1303
        return $result;
1304
    }
1305
1306
    /**
1307
     * Deletes relations to and from $contentId.
1308
     * If $versionNo is set only relations for that version are deleted.
1309
     *
1310
     * @param int $contentId
1311
     * @param int|null $versionNo
1312
     */
1313
    public function deleteRelations($contentId, $versionNo = null)
1314
    {
1315
        $query = $this->dbHandler->createDeleteQuery();
1316
        $query->deleteFrom(
1317
            $this->dbHandler->quoteTable('ezcontentobject_link')
1318
        );
1319
1320
        if (isset($versionNo)) {
1321
            $query->where(
1322
                $query->expr->lAnd(
1323
                    $query->expr->eq(
1324
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1325
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1326
                    ),
1327
                    $query->expr->eq(
1328
                        $this->dbHandler->quoteColumn('from_contentobject_version'),
1329
                        $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1330
                    )
1331
                )
1332
            );
1333 View Code Duplication
        } else {
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...
1334
            $query->where(
1335
                $query->expr->lOr(
1336
                    $query->expr->eq(
1337
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1338
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1339
                    ),
1340
                    $query->expr->eq(
1341
                        $this->dbHandler->quoteColumn('to_contentobject_id'),
1342
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1343
                    )
1344
                )
1345
            );
1346
        }
1347
1348
        $query->prepare()->execute();
1349
    }
1350
1351
    /**
1352
     * Removes relations to Content with $contentId from Relation and RelationList field type fields.
1353
     *
1354
     * @param int $contentId
1355
     */
1356
    public function removeReverseFieldRelations($contentId)
1357
    {
1358
        $query = $this->dbHandler->createSelectQuery();
1359
        $query
1360
            ->select('ezcontentobject_attribute.*')
1361
            ->from('ezcontentobject_attribute')
1362
            ->innerJoin(
1363
                'ezcontentobject_link',
1364
                $query->expr->lAnd(
1365
                    $query->expr->eq(
1366
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1367
                        $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_attribute')
1368
                    ),
1369
                    $query->expr->eq(
1370
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1371
                        $this->dbHandler->quoteColumn('version', 'ezcontentobject_attribute')
1372
                    ),
1373
                    $query->expr->eq(
1374
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_link'),
1375
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_attribute')
1376
                    )
1377
                )
1378
            )
1379
            ->where(
1380
                $query->expr->eq(
1381
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1382
                    $query->bindValue($contentId, null, PDO::PARAM_INT)
1383
                ),
1384
                $query->expr->gt(
1385
                    $query->expr->bitAnd(
1386
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1387
                        $query->bindValue(8, null, PDO::PARAM_INT)
1388
                    ),
1389
                    0
1390
                )
1391
            );
1392
1393
        $statement = $query->prepare();
1394
        $statement->execute();
1395
1396
        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
1397
            if ($row['data_type_string'] === 'ezobjectrelation') {
1398
                $this->removeRelationFromRelationField($row);
1399
            }
1400
1401
            if ($row['data_type_string'] === 'ezobjectrelationlist') {
1402
                $this->removeRelationFromRelationListField($contentId, $row);
1403
            }
1404
        }
1405
    }
1406
1407
    /**
1408
     * Updates field value of RelationList field type identified by given $row data,
1409
     * removing relations toward given $contentId.
1410
     *
1411
     * @param int $contentId
1412
     * @param array $row
1413
     */
1414
    protected function removeRelationFromRelationListField($contentId, array $row)
1415
    {
1416
        $document = new DOMDocument('1.0', 'utf-8');
1417
        $document->loadXML($row['data_text']);
1418
1419
        $xpath = new DOMXPath($document);
1420
        $xpathExpression = "//related-objects/relation-list/relation-item[@contentobject-id='{$contentId}']";
1421
1422
        $relationItems = $xpath->query($xpathExpression);
1423
        foreach ($relationItems as $relationItem) {
1424
            $relationItem->parentNode->removeChild($relationItem);
1425
        }
1426
1427
        $query = $this->dbHandler->createUpdateQuery();
1428
        $query
1429
            ->update('ezcontentobject_attribute')
1430
            ->set(
1431
                'data_text',
1432
                $query->bindValue($document->saveXML(), null, PDO::PARAM_STR)
1433
            )
1434
            ->where(
1435
                $query->expr->lAnd(
1436
                    $query->expr->eq(
1437
                        $this->dbHandler->quoteColumn('id'),
1438
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1439
                    ),
1440
                    $query->expr->eq(
1441
                        $this->dbHandler->quoteColumn('version'),
1442
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1443
                    )
1444
                )
1445
            );
1446
1447
        $query->prepare()->execute();
1448
    }
1449
1450
    /**
1451
     * Updates field value of Relation field type identified by given $row data,
1452
     * removing relation data.
1453
     *
1454
     * @param array $row
1455
     */
1456
    protected function removeRelationFromRelationField(array $row)
1457
    {
1458
        $query = $this->dbHandler->createUpdateQuery();
1459
        $query
1460
            ->update('ezcontentobject_attribute')
1461
            ->set('data_int', $query->bindValue(null, null, PDO::PARAM_INT))
1462
            ->set('sort_key_int', $query->bindValue(0, null, PDO::PARAM_INT))
1463
            ->where(
1464
                $query->expr->lAnd(
1465
                    $query->expr->eq(
1466
                        $this->dbHandler->quoteColumn('id'),
1467
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1468
                    ),
1469
                    $query->expr->eq(
1470
                        $this->dbHandler->quoteColumn('version'),
1471
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1472
                    )
1473
                )
1474
            );
1475
1476
        $query->prepare()->execute();
1477
    }
1478
1479
    /**
1480
     * Deletes the field with the given $fieldId.
1481
     *
1482
     * @param int $fieldId
1483
     */
1484 View Code Duplication
    public function deleteField($fieldId)
1485
    {
1486
        $query = $this->dbHandler->createDeleteQuery();
1487
        $query->deleteFrom(
1488
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1489
        )->where(
1490
            $query->expr->eq(
1491
                $this->dbHandler->quoteColumn('id'),
1492
                $query->bindValue($fieldId, null, \PDO::PARAM_INT)
1493
            )
1494
        );
1495
1496
        $query->prepare()->execute();
1497
    }
1498
1499
    /**
1500
     * Deletes all fields of $contentId in all versions.
1501
     * If $versionNo is set only fields for that version are deleted.
1502
     *
1503
     * @param int $contentId
1504
     * @param int|null $versionNo
1505
     */
1506 View Code Duplication
    public function deleteFields($contentId, $versionNo = null)
1507
    {
1508
        $query = $this->dbHandler->createDeleteQuery();
1509
        $query->deleteFrom('ezcontentobject_attribute')
1510
            ->where(
1511
                $query->expr->eq(
1512
                    $this->dbHandler->quoteColumn('contentobject_id'),
1513
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1514
                )
1515
            );
1516
1517
        if (isset($versionNo)) {
1518
            $query->where(
1519
                $query->expr->eq(
1520
                    $this->dbHandler->quoteColumn('version'),
1521
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1522
                )
1523
            );
1524
        }
1525
1526
        $query->prepare()->execute();
1527
    }
1528
1529
    /**
1530
     * Deletes all versions of $contentId.
1531
     * If $versionNo is set only that version is deleted.
1532
     *
1533
     * @param int $contentId
1534
     * @param int|null $versionNo
1535
     */
1536 View Code Duplication
    public function deleteVersions($contentId, $versionNo = null)
1537
    {
1538
        $query = $this->dbHandler->createDeleteQuery();
1539
        $query->deleteFrom('ezcontentobject_version')
1540
            ->where(
1541
                $query->expr->eq(
1542
                    $this->dbHandler->quoteColumn('contentobject_id'),
1543
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1544
                )
1545
            );
1546
1547
        if (isset($versionNo)) {
1548
            $query->where(
1549
                $query->expr->eq(
1550
                    $this->dbHandler->quoteColumn('version'),
1551
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1552
                )
1553
            );
1554
        }
1555
1556
        $query->prepare()->execute();
1557
    }
1558
1559
    /**
1560
     * Deletes all names of $contentId.
1561
     * If $versionNo is set only names for that version are deleted.
1562
     *
1563
     * @param int $contentId
1564
     * @param int|null $versionNo
1565
     */
1566 View Code Duplication
    public function deleteNames($contentId, $versionNo = null)
1567
    {
1568
        $query = $this->dbHandler->createDeleteQuery();
1569
        $query->deleteFrom('ezcontentobject_name')
1570
            ->where(
1571
                $query->expr->eq(
1572
                    $this->dbHandler->quoteColumn('contentobject_id'),
1573
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1574
                )
1575
            );
1576
1577
        if (isset($versionNo)) {
1578
            $query->where(
1579
                $query->expr->eq(
1580
                    $this->dbHandler->quoteColumn('content_version'),
1581
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1582
                )
1583
            );
1584
        }
1585
1586
        $query->prepare()->execute();
1587
    }
1588
1589
    /**
1590
     * Sets the name for Content $contentId in version $version to $name in $language.
1591
     *
1592
     * @param int $contentId
1593
     * @param int $version
1594
     * @param string $name
1595
     * @param string $language
1596
     */
1597
    public function setName($contentId, $version, $name, $language)
1598
    {
1599
        $language = $this->languageHandler->loadByLanguageCode($language);
1600
1601
        // Is it an insert or an update ?
1602
        $qSelect = $this->dbHandler->createSelectQuery();
1603
        $qSelect
1604
            ->select(
1605
                $qSelect->alias($qSelect->expr->count('*'), 'count')
1606
            )
1607
            ->from($this->dbHandler->quoteTable('ezcontentobject_name'))
1608
            ->where(
1609
                $qSelect->expr->lAnd(
1610
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $qSelect->bindValue($contentId)),
1611
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_version'), $qSelect->bindValue($version)),
1612
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_translation'), $qSelect->bindValue($language->languageCode))
1613
                )
1614
            );
1615
        $stmt = $qSelect->prepare();
1616
        $stmt->execute();
1617
        $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
1618
1619
        $insert = $res[0]['count'] == 0;
1620
        if ($insert) {
1621
            $q = $this->dbHandler->createInsertQuery();
1622
            $q->insertInto($this->dbHandler->quoteTable('ezcontentobject_name'));
1623
        } else {
1624
            $q = $this->dbHandler->createUpdateQuery();
1625
            $q->update($this->dbHandler->quoteTable('ezcontentobject_name'))
1626
                ->where(
1627
                    $q->expr->lAnd(
1628
                        $q->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $q->bindValue($contentId)),
1629
                        $q->expr->eq($this->dbHandler->quoteColumn('content_version'), $q->bindValue($version)),
1630
                        $q->expr->eq($this->dbHandler->quoteColumn('content_translation'), $q->bindValue($language->languageCode))
1631
                    )
1632
                );
1633
        }
1634
1635
        $q->set(
1636
            $this->dbHandler->quoteColumn('contentobject_id'),
1637
            $q->bindValue($contentId, null, \PDO::PARAM_INT)
1638
        )->set(
1639
            $this->dbHandler->quoteColumn('content_version'),
1640
            $q->bindValue($version, null, \PDO::PARAM_INT)
1641
        )->set(
1642
            $this->dbHandler->quoteColumn('language_id'),
1643
            '(' . $this->getLanguageQuery()->getQuery() . ')'
1644
        )->set(
1645
            $this->dbHandler->quoteColumn('content_translation'),
1646
            $q->bindValue($language->languageCode)
1647
        )->set(
1648
            $this->dbHandler->quoteColumn('real_translation'),
1649
            $q->bindValue($language->languageCode)
1650
        )->set(
1651
            $this->dbHandler->quoteColumn('name'),
1652
            $q->bindValue($name)
1653
        );
1654
        $q->bindValue($language->id, ':languageId', \PDO::PARAM_INT);
1655
        $q->bindValue($contentId, ':contentId', \PDO::PARAM_INT);
1656
        $q->prepare()->execute();
1657
    }
1658
1659
    /**
1660
     * Returns a language sub select query for setName.
1661
     *
1662
     * Return sub select query which gets proper language mask for alwaysAvailable Content.
1663
     *
1664
     * @return \eZ\Publish\Core\Persistence\Database\SelectQuery
1665
     */
1666
    private function getLanguageQuery()
1667
    {
1668
        $languageQuery = $this->dbHandler->createSelectQuery();
1669
        $languageQuery
1670
            ->select(
1671
                $languageQuery->expr->searchedCase(
1672
                    [
1673
                        $languageQuery->expr->lAnd(
1674
                            $languageQuery->expr->eq(
1675
                                $this->dbHandler->quoteColumn('initial_language_id'),
1676
                                ':languageId'
1677
                            ),
1678
                            // wrap bitwise check into another "neq" to provide cross-DBMS compatibility
1679
                            $languageQuery->expr->neq(
1680
                                $languageQuery->expr->bitAnd(
1681
                                    $this->dbHandler->quoteColumn('language_mask'),
1682
                                    ':languageId'
1683
                                ),
1684
                                0
1685
                            )
1686
                        ),
1687
                        $languageQuery->expr->bitOr(
1688
                            ':languageId',
1689
                            1
1690
                        ),
1691
                    ],
1692
                    ':languageId'
1693
                )
1694
            )
1695
            ->from('ezcontentobject')
1696
            ->where(
1697
                $languageQuery->expr->eq(
1698
                    'id',
1699
                    ':contentId'
1700
                )
1701
            );
1702
1703
        return $languageQuery;
1704
    }
1705
1706
    /**
1707
     * Deletes the actual content object referred to by $contentId.
1708
     *
1709
     * @param int $contentId
1710
     */
1711 View Code Duplication
    public function deleteContent($contentId)
1712
    {
1713
        $query = $this->dbHandler->createDeleteQuery();
1714
        $query->deleteFrom('ezcontentobject')
1715
            ->where(
1716
                $query->expr->eq(
1717
                    $this->dbHandler->quoteColumn('id'),
1718
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1719
                )
1720
            );
1721
1722
        $query->prepare()->execute();
1723
    }
1724
1725
    /**
1726
     * Loads relations from $contentId to published content, optionally only from $contentVersionNo.
1727
     *
1728
     * $relationType can also be filtered.
1729
     *
1730
     * @param int $contentId
1731
     * @param int $contentVersionNo
1732
     * @param int $relationType
1733
     *
1734
     * @return string[][] array of relation data
1735
     */
1736
    public function loadRelations($contentId, $contentVersionNo = null, $relationType = null)
1737
    {
1738
        $query = $this->queryBuilder->createRelationFindQuery();
1739
        $query->innerJoin(
1740
            $query->alias(
1741
                $this->dbHandler->quoteTable('ezcontentobject'),
1742
                'ezcontentobject_to'
1743
            ),
1744
            $query->expr->lAnd(
1745
                $query->expr->eq(
1746
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1747
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject_to')
1748
                ),
1749
                $query->expr->eq(
1750
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_to'),
1751
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1752
                )
1753
            )
1754
        )->where(
1755
            $query->expr->eq(
1756
                $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1757
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1758
            )
1759
        );
1760
1761
        // source version number
1762
        if (isset($contentVersionNo)) {
1763
            $query->where(
1764
                $query->expr->eq(
1765
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1766
                    $query->bindValue($contentVersionNo, null, \PDO::PARAM_INT)
1767
                )
1768
            );
1769
        } else { // from published version only
1770
            $query->from(
1771
                $this->dbHandler->quoteTable('ezcontentobject')
1772
            )->where(
1773
                $query->expr->lAnd(
1774
                    $query->expr->eq(
1775
                        $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1776
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1777
                    ),
1778
                    $query->expr->eq(
1779
                        $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1780
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1781
                    )
1782
                )
1783
            );
1784
        }
1785
1786
        // relation type
1787 View Code Duplication
        if (isset($relationType)) {
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...
1788
            $query->where(
1789
                $query->expr->gt(
1790
                    $query->expr->bitAnd(
1791
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1792
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1793
                    ),
1794
                    0
1795
                )
1796
            );
1797
        }
1798
1799
        $statement = $query->prepare();
1800
        $statement->execute();
1801
1802
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1803
    }
1804
1805
    /**
1806
     * Loads data that related to $toContentId.
1807
     *
1808
     * @param int $toContentId
1809
     * @param int $relationType
1810
     *
1811
     * @return mixed[][] Content data, array structured like {@see \eZ\Publish\Core\Persistence\Legacy\Content\Gateway::load()}
1812
     */
1813
    public function loadReverseRelations($toContentId, $relationType = null)
1814
    {
1815
        $query = $this->queryBuilder->createRelationFindQuery();
1816
        $query->where(
1817
            $query->expr->eq(
1818
                $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1819
                $query->bindValue($toContentId, null, \PDO::PARAM_INT)
1820
            )
1821
        );
1822
1823
        // ezcontentobject join
1824
        $query->from(
1825
            $this->dbHandler->quoteTable('ezcontentobject')
1826
        )->where(
1827
            $query->expr->lAnd(
1828
                $query->expr->eq(
1829
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1830
                    $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1831
                ),
1832
                $query->expr->eq(
1833
                    $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1834
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1835
                ),
1836
                $query->expr->eq(
1837
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject'),
1838
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1839
                )
1840
            )
1841
        );
1842
1843
        // relation type
1844 View Code Duplication
        if (isset($relationType)) {
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...
1845
            $query->where(
1846
                $query->expr->gt(
1847
                    $query->expr->bitAnd(
1848
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1849
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1850
                    ),
1851
                    0
1852
                )
1853
            );
1854
        }
1855
1856
        $statement = $query->prepare();
1857
1858
        $statement->execute();
1859
1860
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1861
    }
1862
1863
    /**
1864
     * Inserts a new relation database record.
1865
     *
1866
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
1867
     *
1868
     * @return int ID the inserted ID
1869
     */
1870 View Code Duplication
    public function insertRelation(RelationCreateStruct $createStruct)
1871
    {
1872
        $q = $this->dbHandler->createInsertQuery();
1873
        $q->insertInto(
1874
            $this->dbHandler->quoteTable('ezcontentobject_link')
1875
        )->set(
1876
            $this->dbHandler->quoteColumn('id'),
1877
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_link', 'id')
1878
        )->set(
1879
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
1880
            $q->bindValue((int)$createStruct->sourceFieldDefinitionId, null, \PDO::PARAM_INT)
1881
        )->set(
1882
            $this->dbHandler->quoteColumn('from_contentobject_id'),
1883
            $q->bindValue($createStruct->sourceContentId, null, \PDO::PARAM_INT)
1884
        )->set(
1885
            $this->dbHandler->quoteColumn('from_contentobject_version'),
1886
            $q->bindValue($createStruct->sourceContentVersionNo, null, \PDO::PARAM_INT)
1887
        )->set(
1888
            $this->dbHandler->quoteColumn('relation_type'),
1889
            $q->bindValue($createStruct->type, null, \PDO::PARAM_INT)
1890
        )->set(
1891
            $this->dbHandler->quoteColumn('to_contentobject_id'),
1892
            $q->bindValue($createStruct->destinationContentId, null, \PDO::PARAM_INT)
1893
        );
1894
1895
        $q->prepare()->execute();
1896
1897
        return $this->dbHandler->lastInsertId(
1898
            $this->dbHandler->getSequenceName('ezcontentobject_link', 'id')
1899
        );
1900
    }
1901
1902
    /**
1903
     * Deletes the relation with the given $relationId.
1904
     *
1905
     * @param int $relationId
1906
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
1907
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
1908
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
1909
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
1910
     */
1911
    public function deleteRelation($relationId, $type)
1912
    {
1913
        // Legacy Storage stores COMMON, LINK and EMBED types using bitmask, therefore first load
1914
        // existing relation type by given $relationId for comparison
1915
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1916
        $query = $this->dbHandler->createSelectQuery();
1917
        $query->select(
1918
            $this->dbHandler->quoteColumn('relation_type')
1919
        )->from(
1920
            $this->dbHandler->quoteTable('ezcontentobject_link')
1921
        )->where(
1922
            $query->expr->eq(
1923
                $this->dbHandler->quoteColumn('id'),
1924
                $query->bindValue($relationId, null, \PDO::PARAM_INT)
1925
            )
1926
        );
1927
1928
        $statement = $query->prepare();
1929
        $statement->execute();
1930
        $loadedRelationType = $statement->fetchColumn();
1931
1932
        if (!$loadedRelationType) {
1933
            return;
1934
        }
1935
1936
        // If relation type matches then delete
1937
        if ($loadedRelationType == $type) {
1938
            /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1939
            $query = $this->dbHandler->createDeleteQuery();
1940
            $query->deleteFrom(
1941
                'ezcontentobject_link'
1942
            )->where(
1943
                $query->expr->eq(
1944
                    $this->dbHandler->quoteColumn('id'),
1945
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1946
                )
1947
            );
1948
1949
            $query->prepare()->execute();
1950
        } elseif ($loadedRelationType & $type) { // If relation type is composite update bitmask
1951
            /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
1952
            $query = $this->dbHandler->createUpdateQuery();
1953
            $query->update(
1954
                $this->dbHandler->quoteTable('ezcontentobject_link')
1955
            )->set(
1956
                $this->dbHandler->quoteColumn('relation_type'),
1957
                $query->expr->bitAnd(
1958
                    $this->dbHandler->quoteColumn('relation_type'),
1959
                    $query->bindValue(~$type, null, \PDO::PARAM_INT)
1960
                )
1961
            )->where(
1962
                $query->expr->eq(
1963
                    $this->dbHandler->quoteColumn('id'),
1964
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1965
                )
1966
            );
1967
1968
            $query->prepare()->execute();
1969
        } else {
1970
            // No match, do nothing
1971
        }
1972
    }
1973
1974
    /**
1975
     * Returns all Content IDs for a given $contentTypeId.
1976
     *
1977
     * @param int $contentTypeId
1978
     *
1979
     * @return int[]
1980
     */
1981 View Code Duplication
    public function getContentIdsByContentTypeId($contentTypeId)
1982
    {
1983
        $query = $this->dbHandler->createSelectQuery();
1984
        $query
1985
            ->select($this->dbHandler->quoteColumn('id'))
1986
            ->from($this->dbHandler->quoteTable('ezcontentobject'))
1987
            ->where(
1988
                $query->expr->eq(
1989
                    $this->dbHandler->quoteColumn('contentclass_id'),
1990
                    $query->bindValue($contentTypeId, null, PDO::PARAM_INT)
1991
                )
1992
            );
1993
1994
        $statement = $query->prepare();
1995
        $statement->execute();
1996
1997
        return $statement->fetchAll(PDO::FETCH_COLUMN);
1998
    }
1999
2000
    /**
2001
     * Load name data for set of content id's and corresponding version number.
2002
     *
2003
     * @param array[] $rows array of hashes with 'id' and 'version' to load names for
2004
     *
2005
     * @return array
2006
     */
2007
    public function loadVersionedNameData($rows)
2008
    {
2009
        $query = $this->queryBuilder->createNamesQuery();
2010
        $conditions = [];
2011
        foreach ($rows as $row) {
2012
            $conditions[] = $query->expr->lAnd(
2013
                $query->expr->eq(
2014
                    $this->dbHandler->quoteColumn('contentobject_id'),
2015
                    $query->bindValue($row['id'], null, \PDO::PARAM_INT)
2016
                ),
2017
                $query->expr->eq(
2018
                    $this->dbHandler->quoteColumn('content_version'),
2019
                    $query->bindValue($row['version'], null, \PDO::PARAM_INT)
2020
                )
2021
            );
2022
        }
2023
2024
        $query->where($query->expr->lOr($conditions));
2025
        $stmt = $query->prepare();
2026
        $stmt->execute();
2027
2028
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
2029
    }
2030
2031
    /**
2032
     * Batch method for copying all relation meta data for copied Content object.
2033
     *
2034
     * {@inheritdoc}
2035
     *
2036
     * @param int $originalContentId
2037
     * @param int $copiedContentId
2038
     * @param int|null $versionNo If specified only copy for a given version number, otherwise all.
2039
     */
2040
    public function copyRelations($originalContentId, $copiedContentId, $versionNo = null)
2041
    {
2042
        // Given we can retain all columns, we just create copies with new `from_contentobject_id` using INSERT INTO SELECT
2043
        $sql = 'INSERT INTO ezcontentobject_link ( contentclassattribute_id, from_contentobject_id, from_contentobject_version, relation_type, to_contentobject_id )
2044
                SELECT  L2.contentclassattribute_id, :copied_id, L2.from_contentobject_version, L2.relation_type, L2.to_contentobject_id
2045
                FROM    ezcontentobject_link AS L2
2046
                WHERE   L2.from_contentobject_id = :original_id';
2047
2048
        if ($versionNo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $versionNo 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...
2049
            $stmt = $this->connection->prepare($sql . ' AND L2.from_contentobject_version = :version');
2050
            $stmt->bindValue('version', $versionNo, PDO::PARAM_INT);
2051
        } else {
2052
            $stmt = $this->connection->prepare($sql);
2053
        }
2054
2055
        $stmt->bindValue('original_id', $originalContentId, PDO::PARAM_INT);
2056
        $stmt->bindValue('copied_id', $copiedContentId, PDO::PARAM_INT);
2057
2058
        $stmt->execute();
2059
    }
2060
2061
    /**
2062
     * Remove the specified translation from the Content Object Version.
2063
     *
2064
     * @param int $contentId
2065
     * @param string $languageCode language code of the translation
2066
     * @throws \Doctrine\DBAL\DBALException
2067
     */
2068 View Code Duplication
    public function deleteTranslationFromContent($contentId, $languageCode)
2069
    {
2070
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2071
2072
        $this->connection->beginTransaction();
2073
        try {
2074
            $this->deleteTranslationFromContentVersions($contentId, $language->id);
2075
            $this->deleteTranslationFromContentNames($contentId, $languageCode);
2076
            $this->deleteTranslationFromContentObject($contentId, $language->id);
2077
2078
            $this->connection->commit();
2079
        } catch (DBALException $e) {
2080
            $this->connection->rollBack();
2081
            throw $e;
2082
        }
2083
    }
2084
2085
    /**
2086
     * Delete Content fields (attributes) for the given Translation.
2087
     * If $versionNo is given, fields for that Version only will be deleted.
2088
     *
2089
     * @param string $languageCode
2090
     * @param int $contentId
2091
     * @param int $versionNo (optional) filter by versionNo
2092
     */
2093 View Code Duplication
    public function deleteTranslatedFields($languageCode, $contentId, $versionNo = null)
2094
    {
2095
        $query = $this->connection->createQueryBuilder();
2096
        $query
2097
            ->delete('ezcontentobject_attribute')
2098
            ->where('contentobject_id = :contentId')
2099
            ->andWhere('language_code = :languageCode')
2100
            ->setParameters(
2101
                [
2102
                    ':contentId' => $contentId,
2103
                    ':languageCode' => $languageCode,
2104
                ]
2105
            )
2106
        ;
2107
2108
        if (null !== $versionNo) {
2109
            $query
2110
                ->andWhere('version = :versionNo')
2111
                ->setParameter(':versionNo', $versionNo)
2112
            ;
2113
        }
2114
2115
        $query->execute();
2116
    }
2117
2118
    /**
2119
     * Delete the specified Translation from the given Version.
2120
     *
2121
     * @param int $contentId
2122
     * @param int $versionNo
2123
     * @param string $languageCode
2124
     * @throws \Doctrine\DBAL\DBALException
2125
     */
2126 View Code Duplication
    public function deleteTranslationFromVersion($contentId, $versionNo, $languageCode)
2127
    {
2128
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2129
2130
        $this->connection->beginTransaction();
2131
        try {
2132
            $this->deleteTranslationFromContentVersions($contentId, $language->id, $versionNo);
2133
            $this->deleteTranslationFromContentNames($contentId, $languageCode, $versionNo);
2134
2135
            $this->connection->commit();
2136
        } catch (DBALException $e) {
2137
            $this->connection->rollBack();
2138
            throw $e;
2139
        }
2140
    }
2141
2142
    /**
2143
     * Delete translation from the ezcontentobject_name table.
2144
     *
2145
     * @param int $contentId
2146
     * @param string $languageCode
2147
     * @param int $versionNo optional, if specified, apply to this Version only.
2148
     */
2149 View Code Duplication
    private function deleteTranslationFromContentNames($contentId, $languageCode, $versionNo = null)
2150
    {
2151
        $query = $this->connection->createQueryBuilder();
2152
        $query
2153
            ->delete('ezcontentobject_name')
2154
            ->where('contentobject_id=:contentId')
2155
            ->andWhere('real_translation=:languageCode')
2156
            ->setParameters(
2157
                [
2158
                    ':languageCode' => $languageCode,
2159
                    ':contentId' => $contentId,
2160
                ]
2161
            )
2162
        ;
2163
2164
        if (null !== $versionNo) {
2165
            $query
2166
                ->andWhere('content_version = :versionNo')
2167
                ->setParameter(':versionNo', $versionNo)
2168
            ;
2169
        }
2170
2171
        $query->execute();
2172
    }
2173
2174
    /**
2175
     * Remove language from language_mask of ezcontentobject.
2176
     *
2177
     * @param int $contentId
2178
     * @param int $languageId
2179
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2180
     */
2181
    private function deleteTranslationFromContentObject($contentId, $languageId)
2182
    {
2183
        $query = $this->connection->createQueryBuilder();
2184
        $query->update('ezcontentobject')
2185
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2186
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2187
            ->set('modified', ':now')
2188
            ->where('id = :contentId')
2189
            ->andWhere(
2190
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2191
                $query->expr()->andX(
2192
                    'language_mask & ~ ' . $languageId . ' <> 0',
2193
                    'language_mask & ~ ' . $languageId . ' <> 1'
2194
                )
2195
            )
2196
            ->setParameter(':now', time())
2197
            ->setParameter(':contentId', $contentId)
2198
        ;
2199
2200
        $rowCount = $query->execute();
2201
2202
        // no rows updated means that most likely somehow it was the last remaining translation
2203
        if ($rowCount === 0) {
2204
            throw new BadStateException(
2205
                '$languageCode',
2206
                'Specified translation is the only one Content Object Version has'
2207
            );
2208
        }
2209
    }
2210
2211
    /**
2212
     * Remove language from language_mask of ezcontentobject_version and update initialLanguageId
2213
     * if it matches the removed one.
2214
     *
2215
     * @param int $contentId
2216
     * @param int $languageId
2217
     * @param int $versionNo optional, if specified, apply to this Version only.
2218
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2219
     */
2220
    private function deleteTranslationFromContentVersions($contentId, $languageId, $versionNo = null)
2221
    {
2222
        $query = $this->connection->createQueryBuilder();
2223
        $query->update('ezcontentobject_version')
2224
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2225
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2226
            ->set('modified', ':now')
2227
            // update initial_language_id only if it matches removed translation languageId
2228
            ->set(
2229
                'initial_language_id',
2230
                'CASE WHEN initial_language_id = :languageId ' .
2231
                'THEN (SELECT initial_language_id AS main_language_id FROM ezcontentobject c WHERE c.id = :contentId) ' .
2232
                'ELSE initial_language_id END'
2233
            )
2234
            ->where('contentobject_id = :contentId')
2235
            ->andWhere(
2236
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2237
                $query->expr()->andX(
2238
                    'language_mask & ~ ' . $languageId . ' <> 0',
2239
                    'language_mask & ~ ' . $languageId . ' <> 1'
2240
                )
2241
            )
2242
            ->setParameter(':now', time())
2243
            ->setParameter(':contentId', $contentId)
2244
            ->setParameter(':languageId', $languageId)
2245
        ;
2246
2247
        if (null !== $versionNo) {
2248
            $query
2249
                ->andWhere('version = :versionNo')
2250
                ->setParameter(':versionNo', $versionNo)
2251
            ;
2252
        }
2253
2254
        $rowCount = $query->execute();
2255
2256
        // no rows updated means that most likely somehow it was the last remaining translation
2257
        if ($rowCount === 0) {
2258
            throw new BadStateException(
2259
                '$languageCode',
2260
                'Specified translation is the only one Content Object Version has'
2261
            );
2262
        }
2263
    }
2264
}
2265