Completed
Push — master ( 65f512...fe0390 )
by Łukasz
26:26
created

DoctrineDatabase::loadContentList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the 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 Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
14
use eZ\Publish\Core\Base\Exceptions\BadStateException;
15
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
16
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder;
17
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
18
use eZ\Publish\Core\Persistence\Database\UpdateQuery;
19
use eZ\Publish\Core\Persistence\Database\InsertQuery;
20
use eZ\Publish\Core\Persistence\Database\SelectQuery;
21
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
22
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
23
use eZ\Publish\SPI\Persistence\Content;
24
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
25
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
26
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
27
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
28
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
29
use eZ\Publish\SPI\Persistence\Content\Field;
30
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
31
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
32
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
33
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
34
use DOMXPath;
35
use DOMDocument;
36
use PDO;
37
38
/**
39
 * Doctrine database based content gateway.
40
 */
41
class DoctrineDatabase extends Gateway
42
{
43
    /**
44
     * eZ Doctrine database handler.
45
     *
46
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
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 array(
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 = array($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
        $q = $this->dbHandler->createUpdateQuery();
294
        $q->update($this->dbHandler->quoteTable('ezcontentobject'));
295
296
        if (isset($struct->name)) {
297
            $q->set(
298
                $this->dbHandler->quoteColumn('name'),
299
                $q->bindValue($struct->name, null, \PDO::PARAM_STR)
300
            );
301
        }
302
        if (isset($struct->mainLanguageId)) {
303
            $q->set(
304
                $this->dbHandler->quoteColumn('initial_language_id'),
305
                $q->bindValue($struct->mainLanguageId, null, \PDO::PARAM_INT)
306
            );
307
        }
308
        if (isset($struct->modificationDate)) {
309
            $q->set(
310
                $this->dbHandler->quoteColumn('modified'),
311
                $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
312
            );
313
        }
314
        if (isset($struct->ownerId)) {
315
            $q->set(
316
                $this->dbHandler->quoteColumn('owner_id'),
317
                $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
318
            );
319
        }
320
        if (isset($struct->publicationDate)) {
321
            $q->set(
322
                $this->dbHandler->quoteColumn('published'),
323
                $q->bindValue($struct->publicationDate, null, \PDO::PARAM_INT)
324
            );
325
        }
326
        if (isset($struct->remoteId)) {
327
            $q->set(
328
                $this->dbHandler->quoteColumn('remote_id'),
329
                $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
330
            );
331
        }
332
        if ($prePublishVersionInfo !== null) {
333
            $languages = [];
334
            foreach ($prePublishVersionInfo->languageCodes as $languageCodes) {
335
                if (!isset($languages[$languageCodes])) {
336
                    $languages[$languageCodes] = true;
337
                }
338
            }
339
340
            $languages['always-available'] = isset($struct->alwaysAvailable) ? $struct->alwaysAvailable :
341
                $prePublishVersionInfo->contentInfo->alwaysAvailable;
342
343
            $mask = $this->languageMaskGenerator->generateLanguageMask($languages);
344
345
            $q->set(
346
                $this->dbHandler->quoteColumn('language_mask'),
347
                $q->bindValue($mask, null, \PDO::PARAM_INT)
348
            );
349
        }
350
        $q->where(
351
            $q->expr->eq(
352
                $this->dbHandler->quoteColumn('id'),
353
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
354
            )
355
        );
356
        $q->prepare()->execute();
357
358
        // Handle alwaysAvailable flag update separately as it's a more complex task and has impact on several tables
359
        if (isset($struct->alwaysAvailable) || isset($struct->mainLanguageId)) {
360
            $this->updateAlwaysAvailableFlag($contentId, $struct->alwaysAvailable);
361
        }
362
    }
363
364
    /**
365
     * Updates version $versionNo for content identified by $contentId, in respect to $struct.
366
     *
367
     * @param int $contentId
368
     * @param int $versionNo
369
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $struct
370
     */
371
    public function updateVersion($contentId, $versionNo, UpdateStruct $struct)
372
    {
373
        $q = $this->dbHandler->createUpdateQuery();
374
        $q->update(
375
            $this->dbHandler->quoteTable('ezcontentobject_version')
376
        )->set(
377
            $this->dbHandler->quoteColumn('creator_id'),
378
            $q->bindValue($struct->creatorId, null, \PDO::PARAM_INT)
379
        )->set(
380
            $this->dbHandler->quoteColumn('modified'),
381
            $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
382
        )->set(
383
            $this->dbHandler->quoteColumn('initial_language_id'),
384
            $q->bindValue($struct->initialLanguageId, null, \PDO::PARAM_INT)
385
        )->set(
386
            $this->dbHandler->quoteColumn('language_mask'),
387
            $q->expr->bitOr(
388
                $this->dbHandler->quoteColumn('language_mask'),
389
                $q->bindValue(
390
                    $this->generateLanguageMask(
391
                        $struct->fields,
392
                        $this->languageHandler->load($struct->initialLanguageId)->languageCode,
393
                        false
394
                    ),
395
                    null,
396
                    \PDO::PARAM_INT
397
                )
398
            )
399
        )->where(
400
            $q->expr->lAnd(
401
                $q->expr->eq(
402
                    $this->dbHandler->quoteColumn('contentobject_id'),
403
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
404
                ),
405
                $q->expr->eq(
406
                    $this->dbHandler->quoteColumn('version'),
407
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
408
                )
409
            )
410
        );
411
        $q->prepare()->execute();
412
    }
413
414
    /**
415
     * Updates "always available" flag for Content identified by $contentId, in respect to
416
     * Content's current main language and optionally new $alwaysAvailable state.
417
     *
418
     * @param int $contentId
419
     * @param bool|null $alwaysAvailable New "always available" value or null if not defined
420
     */
421
    public function updateAlwaysAvailableFlag($contentId, $alwaysAvailable = null)
422
    {
423
        // We will need to know some info on the current language mask to update the flag
424
        // everywhere needed
425
        $contentInfoRow = $this->loadContentInfo($contentId);
426
        if (!isset($alwaysAvailable)) {
427
            $alwaysAvailable = (bool)$contentInfoRow['language_mask'] & 1;
428
        }
429
430
        /** @var $q \eZ\Publish\Core\Persistence\Database\UpdateQuery */
431
        $q = $this->dbHandler->createUpdateQuery();
432
        $q
433
            ->update($this->dbHandler->quoteTable('ezcontentobject'))
434
            ->set(
435
                $this->dbHandler->quoteColumn('language_mask'),
436
                $alwaysAvailable ?
437
                    $q->expr->bitOr($this->dbHandler->quoteColumn('language_mask'), 1) :
438
                    $q->expr->bitAnd($this->dbHandler->quoteColumn('language_mask'), -2)
439
            )
440
            ->where(
441
                $q->expr->eq(
442
                    $this->dbHandler->quoteColumn('id'),
443
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
444
                )
445
            );
446
        $q->prepare()->execute();
447
448
        // Now we need to update ezcontentobject_name
449
        /** @var $qName \eZ\Publish\Core\Persistence\Database\UpdateQuery */
450
        $qName = $this->dbHandler->createUpdateQuery();
451
        $qName
452
            ->update($this->dbHandler->quoteTable('ezcontentobject_name'))
453
            ->set(
454
                $this->dbHandler->quoteColumn('language_id'),
455
                $alwaysAvailable ?
456
                    $qName->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
457
                    $qName->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
458
            )
459
            ->where(
460
                $qName->expr->lAnd(
461
                    $qName->expr->eq(
462
                        $this->dbHandler->quoteColumn('contentobject_id'),
463
                        $qName->bindValue($contentId, null, \PDO::PARAM_INT)
464
                    ),
465
                    $qName->expr->eq(
466
                        $this->dbHandler->quoteColumn('content_version'),
467
                        $qName->bindValue(
468
                            $contentInfoRow['current_version'],
469
                            null,
470
                            \PDO::PARAM_INT
471
                        )
472
                    )
473
                )
474
            );
475
        $qName->prepare()->execute();
476
477
        // Now update ezcontentobject_attribute for current version
478
        // Create update query that will be reused
479
        /** @var $qAttr \eZ\Publish\Core\Persistence\Database\UpdateQuery */
480
        $qAttr = $this->dbHandler->createUpdateQuery();
481
        $qAttr
482
            ->update($this->dbHandler->quoteTable('ezcontentobject_attribute'))
483
            ->where(
484
                $qAttr->expr->lAnd(
485
                    $qAttr->expr->eq(
486
                        $this->dbHandler->quoteColumn('contentobject_id'),
487
                        $qAttr->bindValue($contentId, null, \PDO::PARAM_INT)
488
                    ),
489
                    $qAttr->expr->eq(
490
                        $this->dbHandler->quoteColumn('version'),
491
                        $qAttr->bindValue(
492
                            $contentInfoRow['current_version'],
493
                            null,
494
                            \PDO::PARAM_INT
495
                        )
496
                    )
497
                )
498
            );
499
500
        // If there is only a single language, update all fields and return
501
        if (!$this->languageMaskGenerator->isLanguageMaskComposite($contentInfoRow['language_mask'])) {
502
            $qAttr->set(
503
                $this->dbHandler->quoteColumn('language_id'),
504
                $alwaysAvailable ?
505
                    $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
506
                    $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
507
            );
508
            $qAttr->prepare()->execute();
509
510
            return;
511
        }
512
513
        // Otherwise:
514
        // 1. Remove always available flag on all fields
515
        $qAttr->set(
516
            $this->dbHandler->quoteColumn('language_id'),
517
            $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
518
        );
519
        $qAttr->prepare()->execute();
520
521
        // 2. If Content is always available set the flag only on fields in main language
522
        if ($alwaysAvailable) {
523
            $qAttr->set(
524
                $this->dbHandler->quoteColumn('language_id'),
525
                $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1)
526
            );
527
            $qAttr->where(
528
                $qAttr->expr->gt(
529
                    $qAttr->expr->bitAnd(
530
                        $this->dbHandler->quoteColumn('language_id'),
531
                        $qAttr->bindValue($contentInfoRow['initial_language_id'], null, PDO::PARAM_INT)
532
                    ),
533
                    $qAttr->bindValue(0, null, PDO::PARAM_INT)
534
                )
535
            );
536
            $qAttr->prepare()->execute();
537
        }
538
    }
539
540
    /**
541
     * Sets the status of the version identified by $contentId and $version to $status.
542
     *
543
     * The $status can be one of STATUS_DRAFT, STATUS_PUBLISHED, STATUS_ARCHIVED
544
     *
545
     * @param int $contentId
546
     * @param int $version
547
     * @param int $status
548
     *
549
     * @return bool
550
     */
551
    public function setStatus($contentId, $version, $status)
552
    {
553
        $q = $this->dbHandler->createUpdateQuery();
554
        $q->update(
555
            $this->dbHandler->quoteTable('ezcontentobject_version')
556
        )->set(
557
            $this->dbHandler->quoteColumn('status'),
558
            $q->bindValue($status, null, \PDO::PARAM_INT)
559
        )->set(
560
            $this->dbHandler->quoteColumn('modified'),
561
            $q->bindValue(time(), null, \PDO::PARAM_INT)
562
        )->where(
563
            $q->expr->lAnd(
564
                $q->expr->eq(
565
                    $this->dbHandler->quoteColumn('contentobject_id'),
566
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
567
                ),
568
                $q->expr->eq(
569
                    $this->dbHandler->quoteColumn('version'),
570
                    $q->bindValue($version, null, \PDO::PARAM_INT)
571
                )
572
            )
573
        );
574
        $statement = $q->prepare();
575
        $statement->execute();
576
577
        if ((bool)$statement->rowCount() === false) {
578
            return false;
579
        }
580
581
        if ($status !== APIVersionInfo::STATUS_PUBLISHED) {
582
            return true;
583
        }
584
585
        // If the version's status is PUBLISHED, we set the content to published status as well
586
        $q = $this->dbHandler->createUpdateQuery();
587
        $q->update(
588
            $this->dbHandler->quoteTable('ezcontentobject')
589
        )->set(
590
            $this->dbHandler->quoteColumn('status'),
591
            $q->bindValue(ContentInfo::STATUS_PUBLISHED, null, \PDO::PARAM_INT)
592
        )->set(
593
            $this->dbHandler->quoteColumn('current_version'),
594
            $q->bindValue($version, null, \PDO::PARAM_INT)
595
        )->where(
596
            $q->expr->eq(
597
                $this->dbHandler->quoteColumn('id'),
598
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
599
            )
600
        );
601
        $statement = $q->prepare();
602
        $statement->execute();
603
604
        return (bool)$statement->rowCount();
605
    }
606
607
    /**
608
     * Inserts a new field.
609
     *
610
     * Only used when a new field is created (i.e. a new object or a field in a
611
     * new language!). After that, field IDs need to stay the same, only the
612
     * version number changes.
613
     *
614
     * @param \eZ\Publish\SPI\Persistence\Content $content
615
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
616
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
617
     *
618
     * @return int ID
619
     */
620
    public function insertNewField(Content $content, Field $field, StorageFieldValue $value)
621
    {
622
        $q = $this->dbHandler->createInsertQuery();
623
624
        $this->setInsertFieldValues($q, $content, $field, $value);
625
626
        // Insert with auto increment ID
627
        $q->set(
628
            $this->dbHandler->quoteColumn('id'),
629
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_attribute', 'id')
630
        );
631
632
        $q->prepare()->execute();
633
634
        return $this->dbHandler->lastInsertId(
635
            $this->dbHandler->getSequenceName('ezcontentobject_attribute', 'id')
636
        );
637
    }
638
639
    /**
640
     * Inserts an existing field.
641
     *
642
     * Used to insert a field with an exsting ID but a new version number.
643
     *
644
     * @param Content $content
645
     * @param Field $field
646
     * @param StorageFieldValue $value
647
     */
648
    public function insertExistingField(Content $content, Field $field, StorageFieldValue $value)
649
    {
650
        $q = $this->dbHandler->createInsertQuery();
651
652
        $this->setInsertFieldValues($q, $content, $field, $value);
653
654
        $q->set(
655
            $this->dbHandler->quoteColumn('id'),
656
            $q->bindValue($field->id, null, \PDO::PARAM_INT)
657
        );
658
659
        $q->prepare()->execute();
660
    }
661
662
    /**
663
     * Sets field (ezcontentobject_attribute) values to the given query.
664
     *
665
     * @param \eZ\Publish\Core\Persistence\Database\InsertQuery $q
666
     * @param Content $content
667
     * @param Field $field
668
     * @param StorageFieldValue $value
669
     */
670
    protected function setInsertFieldValues(InsertQuery $q, Content $content, Field $field, StorageFieldValue $value)
671
    {
672
        $q->insertInto(
673
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
674
        )->set(
675
            $this->dbHandler->quoteColumn('contentobject_id'),
676
            $q->bindValue($content->versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
677
        )->set(
678
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
679
            $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
680
        )->set(
681
            $this->dbHandler->quoteColumn('data_type_string'),
682
            $q->bindValue($field->type)
683
        )->set(
684
            $this->dbHandler->quoteColumn('language_code'),
685
            $q->bindValue($field->languageCode)
686
        )->set(
687
            $this->dbHandler->quoteColumn('version'),
688
            $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
689
        )->set(
690
            $this->dbHandler->quoteColumn('data_float'),
691
            $q->bindValue($value->dataFloat)
692
        )->set(
693
            $this->dbHandler->quoteColumn('data_int'),
694
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
695
        )->set(
696
            $this->dbHandler->quoteColumn('data_text'),
697
            $q->bindValue($value->dataText)
698
        )->set(
699
            $this->dbHandler->quoteColumn('sort_key_int'),
700
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
701
        )->set(
702
            $this->dbHandler->quoteColumn('sort_key_string'),
703
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
704
        )->set(
705
            $this->dbHandler->quoteColumn('language_id'),
706
            $q->bindValue(
707
                $this->languageMaskGenerator->generateLanguageIndicator(
708
                    $field->languageCode,
709
                    $this->isLanguageAlwaysAvailable($content, $field->languageCode)
710
                ),
711
                null,
712
                \PDO::PARAM_INT
713
            )
714
        );
715
    }
716
717
    /**
718
     * Checks if $languageCode is always available in $content.
719
     *
720
     * @param \eZ\Publish\SPI\Persistence\Content $content
721
     * @param string $languageCode
722
     *
723
     * @return bool
724
     */
725
    protected function isLanguageAlwaysAvailable(Content $content, $languageCode)
726
    {
727
        return
728
            $content->versionInfo->contentInfo->alwaysAvailable &&
729
            $content->versionInfo->contentInfo->mainLanguageCode === $languageCode
730
        ;
731
    }
732
733
    /**
734
     * Updates an existing field.
735
     *
736
     * @param Field $field
737
     * @param StorageFieldValue $value
738
     */
739
    public function updateField(Field $field, StorageFieldValue $value)
740
    {
741
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
742
        // cannot change on update
743
        $q = $this->dbHandler->createUpdateQuery();
744
        $this->setFieldUpdateValues($q, $value);
745
        $q->where(
746
            $q->expr->lAnd(
747
                $q->expr->eq(
748
                    $this->dbHandler->quoteColumn('id'),
749
                    $q->bindValue($field->id, null, \PDO::PARAM_INT)
750
                ),
751
                $q->expr->eq(
752
                    $this->dbHandler->quoteColumn('version'),
753
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
754
                )
755
            )
756
        );
757
        $q->prepare()->execute();
758
    }
759
760
    /**
761
     * Sets update fields for $value on $q.
762
     *
763
     * @param \eZ\Publish\Core\Persistence\Database\UpdateQuery $q
764
     * @param StorageFieldValue $value
765
     */
766
    protected function setFieldUpdateValues(UpdateQuery $q, StorageFieldValue $value)
767
    {
768
        $q->update(
769
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
770
        )->set(
771
            $this->dbHandler->quoteColumn('data_float'),
772
            $q->bindValue($value->dataFloat)
773
        )->set(
774
            $this->dbHandler->quoteColumn('data_int'),
775
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
776
        )->set(
777
            $this->dbHandler->quoteColumn('data_text'),
778
            $q->bindValue($value->dataText)
779
        )->set(
780
            $this->dbHandler->quoteColumn('sort_key_int'),
781
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
782
        )->set(
783
            $this->dbHandler->quoteColumn('sort_key_string'),
784
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
785
        );
786
    }
787
788
    /**
789
     * Updates an existing, non-translatable field.
790
     *
791
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
792
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
793
     * @param int $contentId
794
     */
795
    public function updateNonTranslatableField(
796
        Field $field,
797
        StorageFieldValue $value,
798
        $contentId
799
    ) {
800
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
801
        // cannot change on update
802
        $q = $this->dbHandler->createUpdateQuery();
803
        $this->setFieldUpdateValues($q, $value);
804
        $q->where(
805
            $q->expr->lAnd(
806
                $q->expr->eq(
807
                    $this->dbHandler->quoteColumn('contentclassattribute_id'),
808
                    $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
809
                ),
810
                $q->expr->eq(
811
                    $this->dbHandler->quoteColumn('contentobject_id'),
812
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
813
                ),
814
                $q->expr->eq(
815
                    $this->dbHandler->quoteColumn('version'),
816
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
817
                )
818
            )
819
        );
820
        $q->prepare()->execute();
821
    }
822
823
    /**
824
     * Loads data for a content object.
825
     *
826
     * Returns an array with the relevant data.
827
     *
828
     * @param mixed $contentId
829
     * @param mixed $version
830
     * @param string[]|null $translations
831
     *
832
     * @return array
833
     */
834
    public function load($contentId, $version, array $translations = null)
835
    {
836
        $results = $this->internalLoadContent([
837
            ['id' => $contentId, 'version' => $version, 'languages' => $translations],
838
        ]);
839
840
        return $results;
841
    }
842
843
    /**
844
     * Loads current version for a list of content objects.
845
     *
846
     * @param array[] $IdVersionTranslationPairs Hashes with 'id', optionally 'version', & optionally 'languages'
847
     *                If version is not set current version will be loaded, if languages is not set ALL will be loaded.
848
     *
849
     * @return array[]
850
     */
851
    public function loadContentList(array $IdVersionTranslationPairs): array
852
    {
853
        return $this->internalLoadContent($IdVersionTranslationPairs);
854
    }
855
856
    /**
857
     * @see load(), loadContentList()
858
     *
859
     * @param array[] $IdVersionTranslationPairs Hashes with 'id', optionally 'version', & optionally 'languages'
860
     *                If version is not set current version will be loaded, if languages is not set ALL will be loaded.
861
     *
862
     * @return array
863
     */
864
    private function internalLoadContent(array $IdVersionTranslationPairs): array
865
    {
866
        $q = $this->connection->createQueryBuilder();
867
        $q
868
            ->select(
869
                'c.id AS ezcontentobject_id',
870
                'c.contentclass_id AS ezcontentobject_contentclass_id',
871
                'c.section_id AS ezcontentobject_section_id',
872
                'c.owner_id AS ezcontentobject_owner_id',
873
                'c.remote_id AS ezcontentobject_remote_id',
874
                'c.current_version AS ezcontentobject_current_version',
875
                'c.initial_language_id AS ezcontentobject_initial_language_id',
876
                'c.modified AS ezcontentobject_modified',
877
                'c.published AS ezcontentobject_published',
878
                'c.status AS ezcontentobject_status',
879
                'c.name AS ezcontentobject_name',
880
                'c.language_mask AS ezcontentobject_language_mask',
881
                'v.id AS ezcontentobject_version_id',
882
                'v.version AS ezcontentobject_version_version',
883
                'v.modified AS ezcontentobject_version_modified',
884
                'v.creator_id AS ezcontentobject_version_creator_id',
885
                'v.created AS ezcontentobject_version_created',
886
                'v.status AS ezcontentobject_version_status',
887
                'v.language_mask AS ezcontentobject_version_language_mask',
888
                'v.initial_language_id AS ezcontentobject_version_initial_language_id',
889
                'a.id AS ezcontentobject_attribute_id',
890
                'a.contentclassattribute_id AS ezcontentobject_attribute_contentclassattribute_id',
891
                'a.data_type_string AS ezcontentobject_attribute_data_type_string',
892
                'a.language_code AS ezcontentobject_attribute_language_code',
893
                'a.language_id AS ezcontentobject_attribute_language_id',
894
                'a.data_float AS ezcontentobject_attribute_data_float',
895
                'a.data_int AS ezcontentobject_attribute_data_int',
896
                'a.data_text AS ezcontentobject_attribute_data_text',
897
                'a.sort_key_int AS ezcontentobject_attribute_sort_key_int',
898
                'a.sort_key_string AS ezcontentobject_attribute_sort_key_string',
899
                't.main_node_id AS ezcontentobject_tree_main_node_id'
900
            )
901
            ->from('ezcontentobject', 'c')
902
            ->innerJoin(
903
                'c',
904
                'ezcontentobject_version',
905
                'v',
906
                'c.id = v.contentobject_id'
907
            )
908
            ->innerJoin(
909
                'v',
910
                'ezcontentobject_attribute',
911
                'a',
912
                'v.contentobject_id = a.contentobject_id AND v.version = a.version'
913
            )
914
            ->leftJoin(
915
                'c',
916
                'ezcontentobject_tree',
917
                't',
918
                'c.id = t.contentobject_id AND t.node_id = t.main_node_id'
919
            );
920
921
        $where = [];
922
        $expr = $q->expr();
923
        foreach ($IdVersionTranslationPairs as $IdVersionTranslation) {
924
            $clauses = [
925
                $expr->eq('c.id', $q->createNamedParameter($IdVersionTranslation['id'], PDO::PARAM_INT)),
926
                empty($IdVersionTranslation['version']) ?
927
                    $expr->eq('v.version', 'c.current_version') :
928
                    $expr->eq('v.version', $q->createNamedParameter($IdVersionTranslation['version'], PDO::PARAM_INT)),
929
            ];
930
931
            if (!empty($IdVersionTranslation['languages'])) {
932
                $clauses[] = $expr->in(
933
                    'a.language_code',
934
                    $q->createNamedParameter($IdVersionTranslation['languages'], Connection::PARAM_STR_ARRAY)
935
                );
936
            }
937
938
            $where[] = $expr->andX(...$clauses);
939
        }
940
941
        $q->where(
942
            $expr->orX(...$where)
943
        );
944
945
        return $q->execute()->fetchAll();
946
    }
947
948
    /**
949
     * @see loadContentInfo(), loadContentInfoByRemoteId(), loadContentInfoList(), loadContentInfoByLocationId()
950
     *
951
     * @param \Doctrine\DBAL\Query\QueryBuilder $query
952
     *
953
     * @return array
954
     */
955
    private function internalLoadContentInfo(DoctrineQueryBuilder $query): array
956
    {
957
        $query
958
            ->select('c.*', 't.main_node_id AS ezcontentobject_tree_main_node_id')
959
            ->from('ezcontentobject', 'c')
960
            ->leftJoin(
961
                'c',
962
                'ezcontentobject_tree',
963
                't',
964
                'c.id = t.contentobject_id AND t.node_id = t.main_node_id'
965
            );
966
967
        return $query->execute()->fetchAll();
968
    }
969
970
    /**
971
     * Loads info for content identified by $contentId.
972
     * Will basically return a hash containing all field values for ezcontentobject table plus some additional keys:
973
     *  - always_available => Boolean indicating if content's language mask contains alwaysAvailable bit field
974
     *  - main_language_code => Language code for main (initial) language. E.g. "eng-GB".
975
     *
976
     * @param int $contentId
977
     *
978
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
979
     *
980
     * @return array
981
     */
982 View Code Duplication
    public function loadContentInfo($contentId)
983
    {
984
        $query = $this->connection->createQueryBuilder();
985
        $query->where('c.id = :id')
986
              ->setParameter('id', $contentId, \PDO::PARAM_INT);
987
988
        $results = $this->internalLoadContentInfo($query);
989
        if (empty($results)) {
990
            throw new NotFound('content', "id: $contentId");
991
        }
992
993
        return $results[0];
994
    }
995
996
    public function loadContentInfoList(array $contentIds)
997
    {
998
        $query = $this->connection->createQueryBuilder();
999
        $query->where('c.id IN (:ids)')
1000
              ->setParameter('ids', $contentIds, Connection::PARAM_INT_ARRAY);
1001
1002
        return $this->internalLoadContentInfo($query);
1003
    }
1004
1005
    /**
1006
     * Loads info for a content object identified by its remote ID.
1007
     *
1008
     * Returns an array with the relevant data.
1009
     *
1010
     * @param mixed $remoteId
1011
     *
1012
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1013
     *
1014
     * @return array
1015
     */
1016 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
1017
    {
1018
        $query = $this->connection->createQueryBuilder();
1019
        $query->where('c.remote_id = :id')
1020
              ->setParameter('id', $remoteId, \PDO::PARAM_STR);
1021
1022
        $results = $this->internalLoadContentInfo($query);
1023
        if (empty($results)) {
1024
            throw new NotFound('content', "remote_id: $remoteId");
1025
        }
1026
1027
        return $results[0];
1028
    }
1029
1030
    /**
1031
     * Loads info for a content object identified by its location ID (node ID).
1032
     *
1033
     * Returns an array with the relevant data.
1034
     *
1035
     * @param int $locationId
1036
     *
1037
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1038
     *
1039
     * @return array
1040
     */
1041 View Code Duplication
    public function loadContentInfoByLocationId($locationId)
1042
    {
1043
        $query = $this->connection->createQueryBuilder();
1044
        $query->where('t.main_node_id = :id')
1045
              ->setParameter('id', $locationId, \PDO::PARAM_INT);
1046
1047
        $results = $this->internalLoadContentInfo($query);
1048
        if (empty($results)) {
1049
            throw new NotFound('content', "main_node_id: $locationId");
1050
        }
1051
1052
        return $results[0];
1053
    }
1054
1055
    /**
1056
     * Loads version info for content identified by $contentId and $versionNo.
1057
     * Will basically return a hash containing all field values from ezcontentobject_version table plus following keys:
1058
     *  - names => Hash of content object names. Key is the language code, value is the name.
1059
     *  - 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.
1060
     *  - initial_language_code => Language code for initial language in this version.
1061
     *
1062
     * @param int $contentId
1063
     * @param int $versionNo
1064
     *
1065
     * @return array
1066
     */
1067 View Code Duplication
    public function loadVersionInfo($contentId, $versionNo)
1068
    {
1069
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1070
        $query->where(
1071
            $query->expr->lAnd(
1072
                $query->expr->eq(
1073
                    $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1074
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1075
                ),
1076
                $query->expr->eq(
1077
                    $this->dbHandler->quoteColumn('version', 'ezcontentobject_version'),
1078
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1079
                )
1080
            )
1081
        );
1082
        $statement = $query->prepare();
1083
        $statement->execute();
1084
1085
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1086
    }
1087
1088
    /**
1089
     * Returns data for all versions with given status created by the given $userId.
1090
     *
1091
     * @param int $userId
1092
     * @param int $status
1093
     *
1094
     * @return string[][]
1095
     */
1096
    public function listVersionsForUser($userId, $status = VersionInfo::STATUS_DRAFT)
1097
    {
1098
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1099
        $query->where(
1100
            $query->expr->lAnd(
1101
                $query->expr->eq(
1102
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1103
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1104
                ),
1105
                $query->expr->eq(
1106
                    $this->dbHandler->quoteColumn('creator_id', 'ezcontentobject_version'),
1107
                    $query->bindValue($userId, null, \PDO::PARAM_INT)
1108
                )
1109
            )
1110
        );
1111
1112
        return $this->listVersionsHelper($query);
1113
    }
1114
1115
    /**
1116
     * Returns all version data for the given $contentId, optionally filtered by status.
1117
     *
1118
     * Result is returned with oldest version first (using version id as it has index and is auto increment).
1119
     *
1120
     * @param mixed $contentId
1121
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
1122
     * @param int $limit Limit for items returned, -1 means none.
1123
     *
1124
     * @return string[][]
1125
     */
1126
    public function listVersions($contentId, $status = null, $limit = -1)
1127
    {
1128
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1129
1130
        $filter = $query->expr->eq(
1131
            $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1132
            $query->bindValue($contentId, null, \PDO::PARAM_INT)
1133
        );
1134
1135
        if ($status !== null) {
1136
            $filter = $query->expr->lAnd(
1137
                $filter,
1138
                $query->expr->eq(
1139
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1140
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1141
                )
1142
            );
1143
        }
1144
1145
        $query->where($filter);
1146
1147
        if ($limit > 0) {
1148
            $query->limit($limit);
1149
        }
1150
1151
        return $this->listVersionsHelper($query);
1152
    }
1153
1154
    /**
1155
     * Helper for {@see listVersions()} and {@see listVersionsForUser()} that filters duplicates
1156
     * that are the result of the cartesian product performed by createVersionInfoFindQuery().
1157
     *
1158
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
1159
     *
1160
     * @return string[][]
1161
     */
1162
    private function listVersionsHelper(SelectQuery $query)
1163
    {
1164
        $query->orderBy(
1165
            $this->dbHandler->quoteColumn('id', 'ezcontentobject_version')
1166
        );
1167
1168
        $statement = $query->prepare();
1169
        $statement->execute();
1170
1171
        $results = array();
1172
        $previousId = null;
1173
        foreach ($statement->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1174
            if ($row['ezcontentobject_version_id'] == $previousId) {
1175
                continue;
1176
            }
1177
1178
            $previousId = $row['ezcontentobject_version_id'];
1179
            $results[] = $row;
1180
        }
1181
1182
        return $results;
1183
    }
1184
1185
    /**
1186
     * Returns all version numbers for the given $contentId.
1187
     *
1188
     * @param mixed $contentId
1189
     *
1190
     * @return int[]
1191
     */
1192
    public function listVersionNumbers($contentId)
1193
    {
1194
        $query = $this->dbHandler->createSelectQuery();
1195
        $query->selectDistinct(
1196
            $this->dbHandler->quoteColumn('version')
1197
        )->from(
1198
            $this->dbHandler->quoteTable('ezcontentobject_version')
1199
        )->where(
1200
            $query->expr->eq(
1201
                $this->dbHandler->quoteColumn('contentobject_id'),
1202
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1203
            )
1204
        );
1205
1206
        $statement = $query->prepare();
1207
        $statement->execute();
1208
1209
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1210
    }
1211
1212
    /**
1213
     * Returns last version number for content identified by $contentId.
1214
     *
1215
     * @param int $contentId
1216
     *
1217
     * @return int
1218
     */
1219
    public function getLastVersionNumber($contentId)
1220
    {
1221
        $query = $this->dbHandler->createSelectQuery();
1222
        $query->select(
1223
            $query->expr->max($this->dbHandler->quoteColumn('version'))
1224
        )->from(
1225
            $this->dbHandler->quoteTable('ezcontentobject_version')
1226
        )->where(
1227
            $query->expr->eq(
1228
                $this->dbHandler->quoteColumn('contentobject_id'),
1229
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1230
            )
1231
        );
1232
1233
        $statement = $query->prepare();
1234
        $statement->execute();
1235
1236
        return (int)$statement->fetchColumn();
1237
    }
1238
1239
    /**
1240
     * Returns all IDs for locations that refer to $contentId.
1241
     *
1242
     * @param int $contentId
1243
     *
1244
     * @return int[]
1245
     */
1246
    public function getAllLocationIds($contentId)
1247
    {
1248
        $query = $this->dbHandler->createSelectQuery();
1249
        $query->select(
1250
            $this->dbHandler->quoteColumn('node_id')
1251
        )->from(
1252
            $this->dbHandler->quoteTable('ezcontentobject_tree')
1253
        )->where(
1254
            $query->expr->eq(
1255
                $this->dbHandler->quoteColumn('contentobject_id'),
1256
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1257
            )
1258
        );
1259
1260
        $statement = $query->prepare();
1261
        $statement->execute();
1262
1263
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1264
    }
1265
1266
    /**
1267
     * Returns all field IDs of $contentId grouped by their type.
1268
     * If $versionNo is set only field IDs for that version are returned.
1269
     * If $languageCode is set, only field IDs for that language are returned.
1270
     *
1271
     * @param int $contentId
1272
     * @param int|null $versionNo
1273
     * @param string|null $languageCode
1274
     *
1275
     * @return int[][]
1276
     */
1277
    public function getFieldIdsByType($contentId, $versionNo = null, $languageCode = null)
1278
    {
1279
        $query = $this->dbHandler->createSelectQuery();
1280
        $query->select(
1281
            $this->dbHandler->quoteColumn('id'),
1282
            $this->dbHandler->quoteColumn('data_type_string')
1283
        )->from(
1284
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1285
        )->where(
1286
            $query->expr->eq(
1287
                $this->dbHandler->quoteColumn('contentobject_id'),
1288
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1289
            )
1290
        );
1291
1292
        if (isset($versionNo)) {
1293
            $query->where(
1294
                $query->expr->eq(
1295
                    $this->dbHandler->quoteColumn('version'),
1296
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1297
                )
1298
            );
1299
        }
1300
1301
        if (isset($languageCode)) {
1302
            $query->where(
1303
                $query->expr->eq(
1304
                    $this->dbHandler->quoteColumn('language_code'),
1305
                    $query->bindValue($languageCode, null, \PDO::PARAM_STR)
1306
                )
1307
            );
1308
        }
1309
1310
        $statement = $query->prepare();
1311
        $statement->execute();
1312
1313
        $result = array();
1314
        foreach ($statement->fetchAll() as $row) {
1315
            if (!isset($result[$row['data_type_string']])) {
1316
                $result[$row['data_type_string']] = array();
1317
            }
1318
            $result[$row['data_type_string']][] = (int)$row['id'];
1319
        }
1320
1321
        return $result;
1322
    }
1323
1324
    /**
1325
     * Deletes relations to and from $contentId.
1326
     * If $versionNo is set only relations for that version are deleted.
1327
     *
1328
     * @param int $contentId
1329
     * @param int|null $versionNo
1330
     */
1331
    public function deleteRelations($contentId, $versionNo = null)
1332
    {
1333
        $query = $this->dbHandler->createDeleteQuery();
1334
        $query->deleteFrom(
1335
            $this->dbHandler->quoteTable('ezcontentobject_link')
1336
        );
1337
1338
        if (isset($versionNo)) {
1339
            $query->where(
1340
                $query->expr->lAnd(
1341
                    $query->expr->eq(
1342
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1343
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1344
                    ),
1345
                    $query->expr->eq(
1346
                        $this->dbHandler->quoteColumn('from_contentobject_version'),
1347
                        $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1348
                    )
1349
                )
1350
            );
1351
        } else {
1352
            $query->where(
1353
                $query->expr->lOr(
1354
                    $query->expr->eq(
1355
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1356
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1357
                    ),
1358
                    $query->expr->eq(
1359
                        $this->dbHandler->quoteColumn('to_contentobject_id'),
1360
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1361
                    )
1362
                )
1363
            );
1364
        }
1365
1366
        $query->prepare()->execute();
1367
    }
1368
1369
    /**
1370
     * Removes relations to Content with $contentId from Relation and RelationList field type fields.
1371
     *
1372
     * @param int $contentId
1373
     */
1374
    public function removeReverseFieldRelations($contentId)
1375
    {
1376
        $query = $this->dbHandler->createSelectQuery();
1377
        $query
1378
            ->select('ezcontentobject_attribute.*')
1379
            ->from('ezcontentobject_attribute')
1380
            ->innerJoin(
1381
                'ezcontentobject_link',
1382
                $query->expr->lAnd(
1383
                    $query->expr->eq(
1384
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1385
                        $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_attribute')
1386
                    ),
1387
                    $query->expr->eq(
1388
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1389
                        $this->dbHandler->quoteColumn('version', 'ezcontentobject_attribute')
1390
                    ),
1391
                    $query->expr->eq(
1392
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_link'),
1393
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_attribute')
1394
                    )
1395
                )
1396
            )
1397
            ->where(
1398
                $query->expr->eq(
1399
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1400
                    $query->bindValue($contentId, null, PDO::PARAM_INT)
1401
                ),
1402
                $query->expr->gt(
1403
                    $query->expr->bitAnd(
1404
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1405
                        $query->bindValue(8, null, PDO::PARAM_INT)
1406
                    ),
1407
                    0
1408
                )
1409
            );
1410
1411
        $statement = $query->prepare();
1412
        $statement->execute();
1413
1414
        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
1415
            if ($row['data_type_string'] === 'ezobjectrelation') {
1416
                $this->removeRelationFromRelationField($row);
1417
            }
1418
1419
            if ($row['data_type_string'] === 'ezobjectrelationlist') {
1420
                $this->removeRelationFromRelationListField($contentId, $row);
1421
            }
1422
        }
1423
    }
1424
1425
    /**
1426
     * Updates field value of RelationList field type identified by given $row data,
1427
     * removing relations toward given $contentId.
1428
     *
1429
     * @param int $contentId
1430
     * @param array $row
1431
     */
1432
    protected function removeRelationFromRelationListField($contentId, array $row)
1433
    {
1434
        $document = new DOMDocument('1.0', 'utf-8');
1435
        $document->loadXML($row['data_text']);
1436
1437
        $xpath = new DOMXPath($document);
1438
        $xpathExpression = "//related-objects/relation-list/relation-item[@contentobject-id='{$contentId}']";
1439
1440
        $relationItems = $xpath->query($xpathExpression);
1441
        foreach ($relationItems as $relationItem) {
1442
            $relationItem->parentNode->removeChild($relationItem);
1443
        }
1444
1445
        $query = $this->dbHandler->createUpdateQuery();
1446
        $query
1447
            ->update('ezcontentobject_attribute')
1448
            ->set(
1449
                'data_text',
1450
                $query->bindValue($document->saveXML(), null, PDO::PARAM_STR)
1451
            )
1452
            ->where(
1453
                $query->expr->lAnd(
1454
                    $query->expr->eq(
1455
                        $this->dbHandler->quoteColumn('id'),
1456
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1457
                    ),
1458
                    $query->expr->eq(
1459
                        $this->dbHandler->quoteColumn('version'),
1460
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1461
                    )
1462
                )
1463
            );
1464
1465
        $query->prepare()->execute();
1466
    }
1467
1468
    /**
1469
     * Updates field value of Relation field type identified by given $row data,
1470
     * removing relation data.
1471
     *
1472
     * @param array $row
1473
     */
1474
    protected function removeRelationFromRelationField(array $row)
1475
    {
1476
        $query = $this->dbHandler->createUpdateQuery();
1477
        $query
1478
            ->update('ezcontentobject_attribute')
1479
            ->set('data_int', $query->bindValue(null, null, PDO::PARAM_INT))
1480
            ->set('sort_key_int', $query->bindValue(0, null, PDO::PARAM_INT))
1481
            ->where(
1482
                $query->expr->lAnd(
1483
                    $query->expr->eq(
1484
                        $this->dbHandler->quoteColumn('id'),
1485
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1486
                    ),
1487
                    $query->expr->eq(
1488
                        $this->dbHandler->quoteColumn('version'),
1489
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1490
                    )
1491
                )
1492
            );
1493
1494
        $query->prepare()->execute();
1495
    }
1496
1497
    /**
1498
     * Deletes the field with the given $fieldId.
1499
     *
1500
     * @param int $fieldId
1501
     */
1502
    public function deleteField($fieldId)
1503
    {
1504
        $query = $this->dbHandler->createDeleteQuery();
1505
        $query->deleteFrom(
1506
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1507
        )->where(
1508
            $query->expr->eq(
1509
                $this->dbHandler->quoteColumn('id'),
1510
                $query->bindValue($fieldId, null, \PDO::PARAM_INT)
1511
            )
1512
        );
1513
1514
        $query->prepare()->execute();
1515
    }
1516
1517
    /**
1518
     * Deletes all fields of $contentId in all versions.
1519
     * If $versionNo is set only fields for that version are deleted.
1520
     *
1521
     * @param int $contentId
1522
     * @param int|null $versionNo
1523
     */
1524
    public function deleteFields($contentId, $versionNo = null)
1525
    {
1526
        $query = $this->dbHandler->createDeleteQuery();
1527
        $query->deleteFrom('ezcontentobject_attribute')
1528
            ->where(
1529
                $query->expr->eq(
1530
                    $this->dbHandler->quoteColumn('contentobject_id'),
1531
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1532
                )
1533
            );
1534
1535
        if (isset($versionNo)) {
1536
            $query->where(
1537
                $query->expr->eq(
1538
                    $this->dbHandler->quoteColumn('version'),
1539
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1540
                )
1541
            );
1542
        }
1543
1544
        $query->prepare()->execute();
1545
    }
1546
1547
    /**
1548
     * Deletes all versions of $contentId.
1549
     * If $versionNo is set only that version is deleted.
1550
     *
1551
     * @param int $contentId
1552
     * @param int|null $versionNo
1553
     */
1554
    public function deleteVersions($contentId, $versionNo = null)
1555
    {
1556
        $query = $this->dbHandler->createDeleteQuery();
1557
        $query->deleteFrom('ezcontentobject_version')
1558
            ->where(
1559
                $query->expr->eq(
1560
                    $this->dbHandler->quoteColumn('contentobject_id'),
1561
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1562
                )
1563
            );
1564
1565
        if (isset($versionNo)) {
1566
            $query->where(
1567
                $query->expr->eq(
1568
                    $this->dbHandler->quoteColumn('version'),
1569
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1570
                )
1571
            );
1572
        }
1573
1574
        $query->prepare()->execute();
1575
    }
1576
1577
    /**
1578
     * Deletes all names of $contentId.
1579
     * If $versionNo is set only names for that version are deleted.
1580
     *
1581
     * @param int $contentId
1582
     * @param int|null $versionNo
1583
     */
1584
    public function deleteNames($contentId, $versionNo = null)
1585
    {
1586
        $query = $this->dbHandler->createDeleteQuery();
1587
        $query->deleteFrom('ezcontentobject_name')
1588
            ->where(
1589
                $query->expr->eq(
1590
                    $this->dbHandler->quoteColumn('contentobject_id'),
1591
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1592
                )
1593
            );
1594
1595
        if (isset($versionNo)) {
1596
            $query->where(
1597
                $query->expr->eq(
1598
                    $this->dbHandler->quoteColumn('content_version'),
1599
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1600
                )
1601
            );
1602
        }
1603
1604
        $query->prepare()->execute();
1605
    }
1606
1607
    /**
1608
     * Sets the name for Content $contentId in version $version to $name in $language.
1609
     *
1610
     * @param int $contentId
1611
     * @param int $version
1612
     * @param string $name
1613
     * @param string $language
1614
     */
1615
    public function setName($contentId, $version, $name, $language)
1616
    {
1617
        $language = $this->languageHandler->loadByLanguageCode($language);
1618
1619
        // Is it an insert or an update ?
1620
        $qSelect = $this->dbHandler->createSelectQuery();
1621
        $qSelect
1622
            ->select(
1623
                $qSelect->alias($qSelect->expr->count('*'), 'count')
1624
            )
1625
            ->from($this->dbHandler->quoteTable('ezcontentobject_name'))
1626
            ->where(
1627
                $qSelect->expr->lAnd(
1628
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $qSelect->bindValue($contentId)),
1629
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_version'), $qSelect->bindValue($version)),
1630
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_translation'), $qSelect->bindValue($language->languageCode))
1631
                )
1632
            );
1633
        $stmt = $qSelect->prepare();
1634
        $stmt->execute();
1635
        $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
1636
1637
        $insert = $res[0]['count'] == 0;
1638
        if ($insert) {
1639
            $q = $this->dbHandler->createInsertQuery();
1640
            $q->insertInto($this->dbHandler->quoteTable('ezcontentobject_name'));
1641
        } else {
1642
            $q = $this->dbHandler->createUpdateQuery();
1643
            $q->update($this->dbHandler->quoteTable('ezcontentobject_name'))
1644
                ->where(
1645
                    $q->expr->lAnd(
1646
                        $q->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $q->bindValue($contentId)),
1647
                        $q->expr->eq($this->dbHandler->quoteColumn('content_version'), $q->bindValue($version)),
1648
                        $q->expr->eq($this->dbHandler->quoteColumn('content_translation'), $q->bindValue($language->languageCode))
1649
                    )
1650
                );
1651
        }
1652
1653
        $q->set(
1654
            $this->dbHandler->quoteColumn('contentobject_id'),
1655
            $q->bindValue($contentId, null, \PDO::PARAM_INT)
1656
        )->set(
1657
            $this->dbHandler->quoteColumn('content_version'),
1658
            $q->bindValue($version, null, \PDO::PARAM_INT)
1659
        )->set(
1660
            $this->dbHandler->quoteColumn('language_id'),
1661
            '(' . $this->getLanguageQuery()->getQuery() . ')'
1662
        )->set(
1663
            $this->dbHandler->quoteColumn('content_translation'),
1664
            $q->bindValue($language->languageCode)
1665
        )->set(
1666
            $this->dbHandler->quoteColumn('real_translation'),
1667
            $q->bindValue($language->languageCode)
1668
        )->set(
1669
            $this->dbHandler->quoteColumn('name'),
1670
            $q->bindValue($name)
1671
        );
1672
        $q->bindValue($language->id, ':languageId', \PDO::PARAM_INT);
1673
        $q->bindValue($contentId, ':contentId', \PDO::PARAM_INT);
1674
        $q->prepare()->execute();
1675
    }
1676
1677
    /**
1678
     * Returns a language sub select query for setName.
1679
     *
1680
     * Return sub select query which gets proper language mask for alwaysAvailable Content.
1681
     *
1682
     * @return \eZ\Publish\Core\Persistence\Database\SelectQuery
1683
     */
1684
    private function getLanguageQuery()
1685
    {
1686
        $languageQuery = $this->dbHandler->createSelectQuery();
1687
        $languageQuery
1688
            ->select(
1689
                $languageQuery->expr->searchedCase(
1690
                    [
1691
                        $languageQuery->expr->lAnd(
1692
                            $languageQuery->expr->eq(
1693
                                $this->dbHandler->quoteColumn('initial_language_id'),
1694
                                ':languageId'
1695
                            ),
1696
                            // wrap bitwise check into another "neq" to provide cross-DBMS compatibility
1697
                            $languageQuery->expr->neq(
1698
                                $languageQuery->expr->bitAnd(
1699
                                    $this->dbHandler->quoteColumn('language_mask'),
1700
                                    ':languageId'
1701
                                ),
1702
                                0
1703
                            )
1704
                        ),
1705
                        $languageQuery->expr->bitOr(
1706
                            ':languageId',
1707
                            1
1708
                        ),
1709
                    ],
1710
                    ':languageId'
1711
                )
1712
            )
1713
            ->from('ezcontentobject')
1714
            ->where(
1715
                $languageQuery->expr->eq(
1716
                    'id',
1717
                    ':contentId'
1718
                )
1719
            );
1720
1721
        return $languageQuery;
1722
    }
1723
1724
    /**
1725
     * Deletes the actual content object referred to by $contentId.
1726
     *
1727
     * @param int $contentId
1728
     */
1729
    public function deleteContent($contentId)
1730
    {
1731
        $query = $this->dbHandler->createDeleteQuery();
1732
        $query->deleteFrom('ezcontentobject')
1733
            ->where(
1734
                $query->expr->eq(
1735
                    $this->dbHandler->quoteColumn('id'),
1736
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1737
                )
1738
            );
1739
1740
        $query->prepare()->execute();
1741
    }
1742
1743
    /**
1744
     * Loads relations from $contentId to published content, optionally only from $contentVersionNo.
1745
     *
1746
     * $relationType can also be filtered.
1747
     *
1748
     * @param int $contentId
1749
     * @param int $contentVersionNo
1750
     * @param int $relationType
1751
     *
1752
     * @return string[][] array of relation data
1753
     */
1754
    public function loadRelations($contentId, $contentVersionNo = null, $relationType = null)
1755
    {
1756
        $query = $this->queryBuilder->createRelationFindQuery();
1757
        $query->innerJoin(
1758
            $query->alias(
1759
                $this->dbHandler->quoteTable('ezcontentobject'),
1760
                'ezcontentobject_to'
1761
            ),
1762
            $query->expr->lAnd(
1763
                $query->expr->eq(
1764
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1765
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject_to')
1766
                ),
1767
                $query->expr->eq(
1768
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_to'),
1769
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1770
                )
1771
            )
1772
        )->where(
1773
            $query->expr->eq(
1774
                $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1775
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1776
            )
1777
        );
1778
1779
        // source version number
1780
        if (isset($contentVersionNo)) {
1781
            $query->where(
1782
                $query->expr->eq(
1783
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1784
                    $query->bindValue($contentVersionNo, null, \PDO::PARAM_INT)
1785
                )
1786
            );
1787
        } else { // from published version only
1788
            $query->from(
1789
                $this->dbHandler->quoteTable('ezcontentobject')
1790
            )->where(
1791
                $query->expr->lAnd(
1792
                    $query->expr->eq(
1793
                        $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1794
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1795
                    ),
1796
                    $query->expr->eq(
1797
                        $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1798
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1799
                    )
1800
                )
1801
            );
1802
        }
1803
1804
        // relation type
1805 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...
1806
            $query->where(
1807
                $query->expr->gt(
1808
                    $query->expr->bitAnd(
1809
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1810
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1811
                    ),
1812
                    0
1813
                )
1814
            );
1815
        }
1816
1817
        $statement = $query->prepare();
1818
        $statement->execute();
1819
1820
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1821
    }
1822
1823
    /**
1824
     * Loads data that related to $toContentId.
1825
     *
1826
     * @param int $toContentId
1827
     * @param int $relationType
1828
     *
1829
     * @return mixed[][] Content data, array structured like {@see \eZ\Publish\Core\Persistence\Legacy\Content\Gateway::load()}
1830
     */
1831
    public function loadReverseRelations($toContentId, $relationType = null)
1832
    {
1833
        $query = $this->queryBuilder->createRelationFindQuery();
1834
        $query->where(
1835
            $query->expr->eq(
1836
                $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1837
                $query->bindValue($toContentId, null, \PDO::PARAM_INT)
1838
            )
1839
        );
1840
1841
        // ezcontentobject join
1842
        $query->from(
1843
            $this->dbHandler->quoteTable('ezcontentobject')
1844
        )->where(
1845
            $query->expr->lAnd(
1846
                $query->expr->eq(
1847
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1848
                    $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1849
                ),
1850
                $query->expr->eq(
1851
                    $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1852
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1853
                ),
1854
                $query->expr->eq(
1855
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject'),
1856
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1857
                )
1858
            )
1859
        );
1860
1861
        // relation type
1862 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...
1863
            $query->where(
1864
                $query->expr->gt(
1865
                    $query->expr->bitAnd(
1866
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1867
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1868
                    ),
1869
                    0
1870
                )
1871
            );
1872
        }
1873
1874
        $statement = $query->prepare();
1875
1876
        $statement->execute();
1877
1878
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1879
    }
1880
1881
    /**
1882
     * Inserts a new relation database record.
1883
     *
1884
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
1885
     *
1886
     * @return int ID the inserted ID
1887
     */
1888 View Code Duplication
    public function insertRelation(RelationCreateStruct $createStruct)
1889
    {
1890
        $q = $this->dbHandler->createInsertQuery();
1891
        $q->insertInto(
1892
            $this->dbHandler->quoteTable('ezcontentobject_link')
1893
        )->set(
1894
            $this->dbHandler->quoteColumn('id'),
1895
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_link', 'id')
1896
        )->set(
1897
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
1898
            $q->bindValue((int)$createStruct->sourceFieldDefinitionId, null, \PDO::PARAM_INT)
1899
        )->set(
1900
            $this->dbHandler->quoteColumn('from_contentobject_id'),
1901
            $q->bindValue($createStruct->sourceContentId, null, \PDO::PARAM_INT)
1902
        )->set(
1903
            $this->dbHandler->quoteColumn('from_contentobject_version'),
1904
            $q->bindValue($createStruct->sourceContentVersionNo, null, \PDO::PARAM_INT)
1905
        )->set(
1906
            $this->dbHandler->quoteColumn('relation_type'),
1907
            $q->bindValue($createStruct->type, null, \PDO::PARAM_INT)
1908
        )->set(
1909
            $this->dbHandler->quoteColumn('to_contentobject_id'),
1910
            $q->bindValue($createStruct->destinationContentId, null, \PDO::PARAM_INT)
1911
        );
1912
1913
        $q->prepare()->execute();
1914
1915
        return $this->dbHandler->lastInsertId(
1916
            $this->dbHandler->getSequenceName('ezcontentobject_link', 'id')
1917
        );
1918
    }
1919
1920
    /**
1921
     * Deletes the relation with the given $relationId.
1922
     *
1923
     * @param int $relationId
1924
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
1925
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
1926
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
1927
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
1928
     */
1929
    public function deleteRelation($relationId, $type)
1930
    {
1931
        // Legacy Storage stores COMMON, LINK and EMBED types using bitmask, therefore first load
1932
        // existing relation type by given $relationId for comparison
1933
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1934
        $query = $this->dbHandler->createSelectQuery();
1935
        $query->select(
1936
            $this->dbHandler->quoteColumn('relation_type')
1937
        )->from(
1938
            $this->dbHandler->quoteTable('ezcontentobject_link')
1939
        )->where(
1940
            $query->expr->eq(
1941
                $this->dbHandler->quoteColumn('id'),
1942
                $query->bindValue($relationId, null, \PDO::PARAM_INT)
1943
            )
1944
        );
1945
1946
        $statement = $query->prepare();
1947
        $statement->execute();
1948
        $loadedRelationType = $statement->fetchColumn();
1949
1950
        if (!$loadedRelationType) {
1951
            return;
1952
        }
1953
1954
        // If relation type matches then delete
1955
        if ($loadedRelationType == $type) {
1956
            /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1957
            $query = $this->dbHandler->createDeleteQuery();
1958
            $query->deleteFrom(
1959
                'ezcontentobject_link'
1960
            )->where(
1961
                $query->expr->eq(
1962
                    $this->dbHandler->quoteColumn('id'),
1963
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1964
                )
1965
            );
1966
1967
            $query->prepare()->execute();
1968
        } elseif ($loadedRelationType & $type) { // If relation type is composite update bitmask
1969
            /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
1970
            $query = $this->dbHandler->createUpdateQuery();
1971
            $query->update(
1972
                $this->dbHandler->quoteTable('ezcontentobject_link')
1973
            )->set(
1974
                $this->dbHandler->quoteColumn('relation_type'),
1975
                $query->expr->bitAnd(
1976
                    $this->dbHandler->quoteColumn('relation_type'),
1977
                    $query->bindValue(~$type, null, \PDO::PARAM_INT)
1978
                )
1979
            )->where(
1980
                $query->expr->eq(
1981
                    $this->dbHandler->quoteColumn('id'),
1982
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1983
                )
1984
            );
1985
1986
            $query->prepare()->execute();
1987
        } else {
1988
            // No match, do nothing
1989
        }
1990
    }
1991
1992
    /**
1993
     * Returns all Content IDs for a given $contentTypeId.
1994
     *
1995
     * @param int $contentTypeId
1996
     *
1997
     * @return int[]
1998
     */
1999
    public function getContentIdsByContentTypeId($contentTypeId)
2000
    {
2001
        $query = $this->dbHandler->createSelectQuery();
2002
        $query
2003
            ->select($this->dbHandler->quoteColumn('id'))
2004
            ->from($this->dbHandler->quoteTable('ezcontentobject'))
2005
            ->where(
2006
                $query->expr->eq(
2007
                    $this->dbHandler->quoteColumn('contentclass_id'),
2008
                    $query->bindValue($contentTypeId, null, PDO::PARAM_INT)
2009
                )
2010
            );
2011
2012
        $statement = $query->prepare();
2013
        $statement->execute();
2014
2015
        return $statement->fetchAll(PDO::FETCH_COLUMN);
2016
    }
2017
2018
    /**
2019
     * Load name data for set of content id's and corresponding version number.
2020
     *
2021
     * @param array[] $rows array of hashes with 'id' and 'version' to load names for
2022
     *
2023
     * @return array
2024
     */
2025
    public function loadVersionedNameData($rows)
2026
    {
2027
        $query = $this->queryBuilder->createNamesQuery();
2028
        $conditions = array();
2029
        foreach ($rows as $row) {
2030
            $conditions[] = $query->expr->lAnd(
2031
                $query->expr->eq(
2032
                    $this->dbHandler->quoteColumn('contentobject_id'),
2033
                    $query->bindValue($row['id'], null, \PDO::PARAM_INT)
2034
                ),
2035
                $query->expr->eq(
2036
                    $this->dbHandler->quoteColumn('content_version'),
2037
                    $query->bindValue($row['version'], null, \PDO::PARAM_INT)
2038
                )
2039
            );
2040
        }
2041
2042
        $query->where($query->expr->lOr($conditions));
2043
        $stmt = $query->prepare();
2044
        $stmt->execute();
2045
2046
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
2047
    }
2048
2049
    /**
2050
     * Batch method for copying all relation meta data for copied Content object.
2051
     *
2052
     * {@inheritdoc}
2053
     *
2054
     * @param int $originalContentId
2055
     * @param int $copiedContentId
2056
     * @param int|null $versionNo If specified only copy for a given version number, otherwise all.
2057
     */
2058
    public function copyRelations($originalContentId, $copiedContentId, $versionNo = null)
2059
    {
2060
        // Given we can retain all columns, we just create copies with new `from_contentobject_id` using INSERT INTO SELECT
2061
        $sql = 'INSERT INTO ezcontentobject_link ( contentclassattribute_id, from_contentobject_id, from_contentobject_version, relation_type, to_contentobject_id )
2062
                SELECT  L2.contentclassattribute_id, :copied_id, L2.from_contentobject_version, L2.relation_type, L2.to_contentobject_id
2063
                FROM    ezcontentobject_link AS L2
2064
                WHERE   L2.from_contentobject_id = :original_id';
2065
2066
        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...
2067
            $stmt = $this->connection->prepare($sql . ' AND L2.from_contentobject_version = :version');
2068
            $stmt->bindValue('version', $versionNo, PDO::PARAM_INT);
2069
        } else {
2070
            $stmt = $this->connection->prepare($sql);
2071
        }
2072
2073
        $stmt->bindValue('original_id', $originalContentId, PDO::PARAM_INT);
2074
        $stmt->bindValue('copied_id', $copiedContentId, PDO::PARAM_INT);
2075
2076
        $stmt->execute();
2077
    }
2078
2079
    /**
2080
     * Remove the specified translation from the Content Object Version.
2081
     *
2082
     * @param int $contentId
2083
     * @param string $languageCode language code of the translation
2084
     * @throws \Doctrine\DBAL\DBALException
2085
     */
2086 View Code Duplication
    public function deleteTranslationFromContent($contentId, $languageCode)
2087
    {
2088
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2089
2090
        $this->connection->beginTransaction();
2091
        try {
2092
            $this->deleteTranslationFromContentVersions($contentId, $language->id);
2093
            $this->deleteTranslationFromContentNames($contentId, $languageCode);
2094
            $this->deleteTranslationFromContentObject($contentId, $language->id);
2095
2096
            $this->connection->commit();
2097
        } catch (DBALException $e) {
2098
            $this->connection->rollBack();
2099
            throw $e;
2100
        }
2101
    }
2102
2103
    /**
2104
     * Delete Content fields (attributes) for the given Translation.
2105
     * If $versionNo is given, fields for that Version only will be deleted.
2106
     *
2107
     * @param string $languageCode
2108
     * @param int $contentId
2109
     * @param int $versionNo (optional) filter by versionNo
2110
     */
2111 View Code Duplication
    public function deleteTranslatedFields($languageCode, $contentId, $versionNo = null)
2112
    {
2113
        $query = $this->connection->createQueryBuilder();
2114
        $query
2115
            ->delete('ezcontentobject_attribute')
2116
            ->where('contentobject_id = :contentId')
2117
            ->andWhere('language_code = :languageCode')
2118
            ->setParameters(
2119
                [
2120
                    ':contentId' => $contentId,
2121
                    ':languageCode' => $languageCode,
2122
                ]
2123
            )
2124
        ;
2125
2126
        if (null !== $versionNo) {
2127
            $query
2128
                ->andWhere('version = :versionNo')
2129
                ->setParameter(':versionNo', $versionNo)
2130
            ;
2131
        }
2132
2133
        $query->execute();
2134
    }
2135
2136
    /**
2137
     * Delete the specified Translation from the given Version.
2138
     *
2139
     * @param int $contentId
2140
     * @param int $versionNo
2141
     * @param string $languageCode
2142
     * @throws \Doctrine\DBAL\DBALException
2143
     */
2144 View Code Duplication
    public function deleteTranslationFromVersion($contentId, $versionNo, $languageCode)
2145
    {
2146
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2147
2148
        $this->connection->beginTransaction();
2149
        try {
2150
            $this->deleteTranslationFromContentVersions($contentId, $language->id, $versionNo);
2151
            $this->deleteTranslationFromContentNames($contentId, $languageCode, $versionNo);
2152
2153
            $this->connection->commit();
2154
        } catch (DBALException $e) {
2155
            $this->connection->rollBack();
2156
            throw $e;
2157
        }
2158
    }
2159
2160
    /**
2161
     * Delete translation from the ezcontentobject_name table.
2162
     *
2163
     * @param int $contentId
2164
     * @param string $languageCode
2165
     * @param int $versionNo optional, if specified, apply to this Version only.
2166
     */
2167 View Code Duplication
    private function deleteTranslationFromContentNames($contentId, $languageCode, $versionNo = null)
2168
    {
2169
        $query = $this->connection->createQueryBuilder();
2170
        $query
2171
            ->delete('ezcontentobject_name')
2172
            ->where('contentobject_id=:contentId')
2173
            ->andWhere('real_translation=:languageCode')
2174
            ->setParameters(
2175
                [
2176
                    ':languageCode' => $languageCode,
2177
                    ':contentId' => $contentId,
2178
                ]
2179
            )
2180
        ;
2181
2182
        if (null !== $versionNo) {
2183
            $query
2184
                ->andWhere('content_version = :versionNo')
2185
                ->setParameter(':versionNo', $versionNo)
2186
            ;
2187
        }
2188
2189
        $query->execute();
2190
    }
2191
2192
    /**
2193
     * Remove language from language_mask of ezcontentobject.
2194
     *
2195
     * @param int $contentId
2196
     * @param int $languageId
2197
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2198
     */
2199
    private function deleteTranslationFromContentObject($contentId, $languageId)
2200
    {
2201
        $query = $this->connection->createQueryBuilder();
2202
        $query->update('ezcontentobject')
2203
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2204
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2205
            ->set('modified', ':now')
2206
            ->where('id = :contentId')
2207
            ->andWhere(
2208
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2209
                $query->expr()->andX(
2210
                    'language_mask & ~ ' . $languageId . ' <> 0',
2211
                    'language_mask & ~ ' . $languageId . ' <> 1'
2212
                )
2213
            )
2214
            ->setParameter(':now', time())
2215
            ->setParameter(':contentId', $contentId)
2216
        ;
2217
2218
        $rowCount = $query->execute();
2219
2220
        // no rows updated means that most likely somehow it was the last remaining translation
2221
        if ($rowCount === 0) {
2222
            throw new BadStateException(
2223
                '$languageCode',
2224
                'Specified translation is the only one Content Object Version has'
2225
            );
2226
        }
2227
    }
2228
2229
    /**
2230
     * Remove language from language_mask of ezcontentobject_version and update initialLanguageId
2231
     * if it matches the removed one.
2232
     *
2233
     * @param int $contentId
2234
     * @param int $languageId
2235
     * @param int $versionNo optional, if specified, apply to this Version only.
2236
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2237
     */
2238
    private function deleteTranslationFromContentVersions($contentId, $languageId, $versionNo = null)
2239
    {
2240
        $query = $this->connection->createQueryBuilder();
2241
        $query->update('ezcontentobject_version')
2242
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2243
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2244
            ->set('modified', ':now')
2245
            // update initial_language_id only if it matches removed translation languageId
2246
            ->set(
2247
                'initial_language_id',
2248
                'CASE WHEN initial_language_id = :languageId ' .
2249
                'THEN (SELECT initial_language_id AS main_language_id FROM ezcontentobject c WHERE c.id = :contentId) ' .
2250
                'ELSE initial_language_id END'
2251
            )
2252
            ->where('contentobject_id = :contentId')
2253
            ->andWhere(
2254
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2255
                $query->expr()->andX(
2256
                    'language_mask & ~ ' . $languageId . ' <> 0',
2257
                    'language_mask & ~ ' . $languageId . ' <> 1'
2258
                )
2259
            )
2260
            ->setParameter(':now', time())
2261
            ->setParameter(':contentId', $contentId)
2262
            ->setParameter(':languageId', $languageId)
2263
        ;
2264
2265
        if (null !== $versionNo) {
2266
            $query
2267
                ->andWhere('version = :versionNo')
2268
                ->setParameter(':versionNo', $versionNo)
2269
            ;
2270
        }
2271
2272
        $rowCount = $query->execute();
2273
2274
        // no rows updated means that most likely somehow it was the last remaining translation
2275
        if ($rowCount === 0) {
2276
            throw new BadStateException(
2277
                '$languageCode',
2278
                'Specified translation is the only one Content Object Version has'
2279
            );
2280
        }
2281
    }
2282
}
2283