Completed
Push — master ( 03a7f8...6efe17 )
by
unknown
37:52 queued 18:09
created

createLoadContentInfoQueryBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 19
rs 9.6333
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\FetchMode;
14
use Doctrine\DBAL\ParameterType;
15
use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
16
use eZ\Publish\Core\Base\Exceptions\BadStateException;
17
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
18
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder;
19
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
20
use eZ\Publish\Core\Persistence\Database\UpdateQuery;
21
use eZ\Publish\Core\Persistence\Database\InsertQuery;
22
use eZ\Publish\Core\Persistence\Database\SelectQuery;
23
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
24
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
25
use eZ\Publish\SPI\Persistence\Content;
26
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
27
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
28
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
29
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
30
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
31
use eZ\Publish\SPI\Persistence\Content\Field;
32
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
33
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
34
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
35
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
36
use DOMXPath;
37
use DOMDocument;
38
use PDO;
39
40
/**
41
 * Doctrine database based content gateway.
42
 */
43
class DoctrineDatabase extends Gateway
44
{
45
    /**
46
     * eZ Doctrine database handler.
47
     *
48
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
49
     */
50
    protected $dbHandler;
51
52
    /**
53
     * The native Doctrine connection.
54
     *
55
     * Meant to be used to transition from eZ/Zeta interface to Doctrine.
56
     *
57
     * @var \Doctrine\DBAL\Connection
58
     */
59
    protected $connection;
60
61
    /**
62
     * Query builder.
63
     *
64
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder
65
     */
66
    protected $queryBuilder;
67
68
    /**
69
     * Caching language handler.
70
     *
71
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler
72
     */
73
    protected $languageHandler;
74
75
    /**
76
     * Language mask generator.
77
     *
78
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
79
     */
80
    protected $languageMaskGenerator;
81
82
    /**
83
     * Creates a new gateway based on $db.
84
     *
85
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db
86
     * @param \Doctrine\DBAL\Connection $connection
87
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder $queryBuilder
88
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
89
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
90
     */
91
    public function __construct(
92
        DatabaseHandler $db,
93
        Connection $connection,
94
        QueryBuilder $queryBuilder,
95
        LanguageHandler $languageHandler,
96
        LanguageMaskGenerator $languageMaskGenerator
97
    ) {
98
        $this->dbHandler = $db;
99
        $this->connection = $connection;
100
        $this->queryBuilder = $queryBuilder;
101
        $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...
102
        $this->languageMaskGenerator = $languageMaskGenerator;
103
    }
104
105
    /**
106
     * Get context definition for external storage layers.
107
     *
108
     * @return array
109
     */
110
    public function getContext()
111
    {
112
        return array(
113
            'identifier' => 'LegacyStorage',
114
            'connection' => $this->dbHandler,
115
        );
116
    }
117
118
    /**
119
     * Inserts a new content object.
120
     *
121
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
122
     * @param mixed $currentVersionNo
123
     *
124
     * @return int ID
125
     */
126
    public function insertContentObject(CreateStruct $struct, $currentVersionNo = 1)
127
    {
128
        $initialLanguageId = !empty($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId;
129
        $initialLanguageCode = $this->languageHandler->load($initialLanguageId)->languageCode;
130
131
        if (isset($struct->name[$initialLanguageCode])) {
132
            $name = $struct->name[$initialLanguageCode];
133
        } else {
134
            $name = '';
135
        }
136
137
        $q = $this->dbHandler->createInsertQuery();
138
        $q->insertInto(
139
            $this->dbHandler->quoteTable('ezcontentobject')
140
        )->set(
141
            $this->dbHandler->quoteColumn('id'),
142
            $this->dbHandler->getAutoIncrementValue('ezcontentobject', 'id')
143
        )->set(
144
            $this->dbHandler->quoteColumn('current_version'),
145
            $q->bindValue($currentVersionNo, null, \PDO::PARAM_INT)
146
        )->set(
147
            $this->dbHandler->quoteColumn('name'),
148
            $q->bindValue($name, null, \PDO::PARAM_STR)
149
        )->set(
150
            $this->dbHandler->quoteColumn('contentclass_id'),
151
            $q->bindValue($struct->typeId, null, \PDO::PARAM_INT)
152
        )->set(
153
            $this->dbHandler->quoteColumn('section_id'),
154
            $q->bindValue($struct->sectionId, null, \PDO::PARAM_INT)
155
        )->set(
156
            $this->dbHandler->quoteColumn('owner_id'),
157
            $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
158
        )->set(
159
            $this->dbHandler->quoteColumn('initial_language_id'),
160
            $q->bindValue($initialLanguageId, null, \PDO::PARAM_INT)
161
        )->set(
162
            $this->dbHandler->quoteColumn('remote_id'),
163
            $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
164
        )->set(
165
            $this->dbHandler->quoteColumn('modified'),
166
            $q->bindValue(0, null, \PDO::PARAM_INT)
167
        )->set(
168
            $this->dbHandler->quoteColumn('published'),
169
            $q->bindValue(0, null, \PDO::PARAM_INT)
170
        )->set(
171
            $this->dbHandler->quoteColumn('status'),
172
            $q->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
173
        )->set(
174
            $this->dbHandler->quoteColumn('language_mask'),
175
            $q->bindValue(
176
                $this->generateLanguageMask(
177
                    $struct->fields,
178
                    $initialLanguageCode,
179
                    $struct->alwaysAvailable
180
                ),
181
                null,
182
                \PDO::PARAM_INT
183
            )
184
        );
185
186
        $q->prepare()->execute();
187
188
        return $this->dbHandler->lastInsertId(
189
            $this->dbHandler->getSequenceName('ezcontentobject', 'id')
190
        );
191
    }
192
193
    /**
194
     * Generates a language mask for $version.
195
     *
196
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
197
     * @param string $initialLanguageCode
198
     * @param bool $alwaysAvailable
199
     *
200
     * @return int
201
     */
202
    protected function generateLanguageMask(array $fields, $initialLanguageCode, $alwaysAvailable)
203
    {
204
        $languages = array($initialLanguageCode => true);
205 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...
206
            if (isset($languages[$field->languageCode])) {
207
                continue;
208
            }
209
210
            $languages[$field->languageCode] = true;
211
        }
212
213
        if ($alwaysAvailable) {
214
            $languages['always-available'] = true;
215
        }
216
217
        return $this->languageMaskGenerator->generateLanguageMask($languages);
218
    }
219
220
    /**
221
     * Inserts a new version.
222
     *
223
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo
224
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
225
     *
226
     * @return int ID
227
     */
228
    public function insertVersion(VersionInfo $versionInfo, array $fields)
229
    {
230
        /** @var $q \eZ\Publish\Core\Persistence\Database\InsertQuery */
231
        $q = $this->dbHandler->createInsertQuery();
232
        $q->insertInto(
233
            $this->dbHandler->quoteTable('ezcontentobject_version')
234
        )->set(
235
            $this->dbHandler->quoteColumn('id'),
236
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_version', 'id')
237
        )->set(
238
            $this->dbHandler->quoteColumn('version'),
239
            $q->bindValue($versionInfo->versionNo, null, \PDO::PARAM_INT)
240
        )->set(
241
            $this->dbHandler->quoteColumn('modified'),
242
            $q->bindValue($versionInfo->modificationDate, null, \PDO::PARAM_INT)
243
        )->set(
244
            $this->dbHandler->quoteColumn('creator_id'),
245
            $q->bindValue($versionInfo->creatorId, null, \PDO::PARAM_INT)
246
        )->set(
247
            $this->dbHandler->quoteColumn('created'),
248
            $q->bindValue($versionInfo->creationDate, null, \PDO::PARAM_INT)
249
        )->set(
250
            $this->dbHandler->quoteColumn('status'),
251
            $q->bindValue($versionInfo->status, null, \PDO::PARAM_INT)
252
        )->set(
253
            $this->dbHandler->quoteColumn('initial_language_id'),
254
            $q->bindValue(
255
                $this->languageHandler->loadByLanguageCode($versionInfo->initialLanguageCode)->id,
256
                null,
257
                \PDO::PARAM_INT
258
            )
259
        )->set(
260
            $this->dbHandler->quoteColumn('contentobject_id'),
261
            $q->bindValue($versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
262
        )->set(
263
            // As described in field mapping document
264
            $this->dbHandler->quoteColumn('workflow_event_pos'),
265
            $q->bindValue(0, null, \PDO::PARAM_INT)
266
        )->set(
267
            $this->dbHandler->quoteColumn('language_mask'),
268
            $q->bindValue(
269
                $this->generateLanguageMask(
270
                    $fields,
271
                    $versionInfo->initialLanguageCode,
272
                    $versionInfo->contentInfo->alwaysAvailable
273
                ),
274
                null,
275
                \PDO::PARAM_INT
276
            )
277
        );
278
279
        $q->prepare()->execute();
280
281
        return $this->dbHandler->lastInsertId(
282
            $this->dbHandler->getSequenceName('ezcontentobject_version', 'id')
283
        );
284
    }
285
286
    /**
287
     * Updates an existing content identified by $contentId in respect to $struct.
288
     *
289
     * @param int $contentId
290
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $struct
291
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $prePublishVersionInfo Provided on publish
292
     */
293
    public function updateContent($contentId, MetadataUpdateStruct $struct, VersionInfo $prePublishVersionInfo = null)
294
    {
295
        $q = $this->dbHandler->createUpdateQuery();
296
        $q->update($this->dbHandler->quoteTable('ezcontentobject'));
297
298
        if (isset($struct->name)) {
299
            $q->set(
300
                $this->dbHandler->quoteColumn('name'),
301
                $q->bindValue($struct->name, null, \PDO::PARAM_STR)
302
            );
303
        }
304
        if (isset($struct->mainLanguageId)) {
305
            $q->set(
306
                $this->dbHandler->quoteColumn('initial_language_id'),
307
                $q->bindValue($struct->mainLanguageId, null, \PDO::PARAM_INT)
308
            );
309
        }
310
        if (isset($struct->modificationDate)) {
311
            $q->set(
312
                $this->dbHandler->quoteColumn('modified'),
313
                $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
314
            );
315
        }
316
        if (isset($struct->ownerId)) {
317
            $q->set(
318
                $this->dbHandler->quoteColumn('owner_id'),
319
                $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
320
            );
321
        }
322
        if (isset($struct->publicationDate)) {
323
            $q->set(
324
                $this->dbHandler->quoteColumn('published'),
325
                $q->bindValue($struct->publicationDate, null, \PDO::PARAM_INT)
326
            );
327
        }
328
        if (isset($struct->remoteId)) {
329
            $q->set(
330
                $this->dbHandler->quoteColumn('remote_id'),
331
                $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
332
            );
333
        }
334
        if ($prePublishVersionInfo !== null) {
335
            $languages = [];
336
            foreach ($prePublishVersionInfo->languageCodes as $languageCodes) {
337
                if (!isset($languages[$languageCodes])) {
338
                    $languages[$languageCodes] = true;
339
                }
340
            }
341
342
            $languages['always-available'] = isset($struct->alwaysAvailable) ? $struct->alwaysAvailable :
343
                $prePublishVersionInfo->contentInfo->alwaysAvailable;
344
345
            $mask = $this->languageMaskGenerator->generateLanguageMask($languages);
346
347
            $q->set(
348
                $this->dbHandler->quoteColumn('language_mask'),
349
                $q->bindValue($mask, null, \PDO::PARAM_INT)
350
            );
351
        }
352
        $q->where(
353
            $q->expr->eq(
354
                $this->dbHandler->quoteColumn('id'),
355
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
356
            )
357
        );
358
        $q->prepare()->execute();
359
360
        // Handle alwaysAvailable flag update separately as it's a more complex task and has impact on several tables
361
        if (isset($struct->alwaysAvailable) || isset($struct->mainLanguageId)) {
362
            $this->updateAlwaysAvailableFlag($contentId, $struct->alwaysAvailable);
363
        }
364
    }
365
366
    /**
367
     * Updates version $versionNo for content identified by $contentId, in respect to $struct.
368
     *
369
     * @param int $contentId
370
     * @param int $versionNo
371
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $struct
372
     */
373
    public function updateVersion($contentId, $versionNo, UpdateStruct $struct)
374
    {
375
        $q = $this->dbHandler->createUpdateQuery();
376
        $q->update(
377
            $this->dbHandler->quoteTable('ezcontentobject_version')
378
        )->set(
379
            $this->dbHandler->quoteColumn('creator_id'),
380
            $q->bindValue($struct->creatorId, null, \PDO::PARAM_INT)
381
        )->set(
382
            $this->dbHandler->quoteColumn('modified'),
383
            $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
384
        )->set(
385
            $this->dbHandler->quoteColumn('initial_language_id'),
386
            $q->bindValue($struct->initialLanguageId, null, \PDO::PARAM_INT)
387
        )->set(
388
            $this->dbHandler->quoteColumn('language_mask'),
389
            $q->expr->bitOr(
390
                $this->dbHandler->quoteColumn('language_mask'),
391
                $q->bindValue(
392
                    $this->generateLanguageMask(
393
                        $struct->fields,
394
                        $this->languageHandler->load($struct->initialLanguageId)->languageCode,
395
                        false
396
                    ),
397
                    null,
398
                    \PDO::PARAM_INT
399
                )
400
            )
401
        )->where(
402
            $q->expr->lAnd(
403
                $q->expr->eq(
404
                    $this->dbHandler->quoteColumn('contentobject_id'),
405
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
406
                ),
407
                $q->expr->eq(
408
                    $this->dbHandler->quoteColumn('version'),
409
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
410
                )
411
            )
412
        );
413
        $q->prepare()->execute();
414
    }
415
416
    /**
417
     * Updates "always available" flag for Content identified by $contentId, in respect to
418
     * Content's current main language and optionally new $alwaysAvailable state.
419
     *
420
     * @param int $contentId
421
     * @param bool|null $alwaysAvailable New "always available" value or null if not defined
422
     */
423
    public function updateAlwaysAvailableFlag($contentId, $alwaysAvailable = null)
424
    {
425
        // We will need to know some info on the current language mask to update the flag
426
        // everywhere needed
427
        $contentInfoRow = $this->loadContentInfo($contentId);
428
        if (!isset($alwaysAvailable)) {
429
            $alwaysAvailable = (bool)$contentInfoRow['language_mask'] & 1;
430
        }
431
432
        /** @var $q \eZ\Publish\Core\Persistence\Database\UpdateQuery */
433
        $q = $this->dbHandler->createUpdateQuery();
434
        $q
435
            ->update($this->dbHandler->quoteTable('ezcontentobject'))
436
            ->set(
437
                $this->dbHandler->quoteColumn('language_mask'),
438
                $alwaysAvailable ?
439
                    $q->expr->bitOr($this->dbHandler->quoteColumn('language_mask'), 1) :
440
                    $q->expr->bitAnd($this->dbHandler->quoteColumn('language_mask'), -2)
441
            )
442
            ->where(
443
                $q->expr->eq(
444
                    $this->dbHandler->quoteColumn('id'),
445
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
446
                )
447
            );
448
        $q->prepare()->execute();
449
450
        // Now we need to update ezcontentobject_name
451
        /** @var $qName \eZ\Publish\Core\Persistence\Database\UpdateQuery */
452
        $qName = $this->dbHandler->createUpdateQuery();
453
        $qName
454
            ->update($this->dbHandler->quoteTable('ezcontentobject_name'))
455
            ->set(
456
                $this->dbHandler->quoteColumn('language_id'),
457
                $alwaysAvailable ?
458
                    $qName->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
459
                    $qName->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
460
            )
461
            ->where(
462
                $qName->expr->lAnd(
463
                    $qName->expr->eq(
464
                        $this->dbHandler->quoteColumn('contentobject_id'),
465
                        $qName->bindValue($contentId, null, \PDO::PARAM_INT)
466
                    ),
467
                    $qName->expr->eq(
468
                        $this->dbHandler->quoteColumn('content_version'),
469
                        $qName->bindValue(
470
                            $contentInfoRow['current_version'],
471
                            null,
472
                            \PDO::PARAM_INT
473
                        )
474
                    )
475
                )
476
            );
477
        $qName->prepare()->execute();
478
479
        // Now update ezcontentobject_attribute for current version
480
        // Create update query that will be reused
481
        /** @var $qAttr \eZ\Publish\Core\Persistence\Database\UpdateQuery */
482
        $qAttr = $this->dbHandler->createUpdateQuery();
483
        $qAttr
484
            ->update($this->dbHandler->quoteTable('ezcontentobject_attribute'))
485
            ->where(
486
                $qAttr->expr->lAnd(
487
                    $qAttr->expr->eq(
488
                        $this->dbHandler->quoteColumn('contentobject_id'),
489
                        $qAttr->bindValue($contentId, null, \PDO::PARAM_INT)
490
                    ),
491
                    $qAttr->expr->eq(
492
                        $this->dbHandler->quoteColumn('version'),
493
                        $qAttr->bindValue(
494
                            $contentInfoRow['current_version'],
495
                            null,
496
                            \PDO::PARAM_INT
497
                        )
498
                    )
499
                )
500
            );
501
502
        // If there is only a single language, update all fields and return
503
        if (!$this->languageMaskGenerator->isLanguageMaskComposite($contentInfoRow['language_mask'])) {
504
            $qAttr->set(
505
                $this->dbHandler->quoteColumn('language_id'),
506
                $alwaysAvailable ?
507
                    $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
508
                    $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
509
            );
510
            $qAttr->prepare()->execute();
511
512
            return;
513
        }
514
515
        // Otherwise:
516
        // 1. Remove always available flag on all fields
517
        $qAttr->set(
518
            $this->dbHandler->quoteColumn('language_id'),
519
            $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
520
        );
521
        $qAttr->prepare()->execute();
522
523
        // 2. If Content is always available set the flag only on fields in main language
524
        if ($alwaysAvailable) {
525
            $qAttr->set(
526
                $this->dbHandler->quoteColumn('language_id'),
527
                $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1)
528
            );
529
            $qAttr->where(
530
                $qAttr->expr->gt(
531
                    $qAttr->expr->bitAnd(
532
                        $this->dbHandler->quoteColumn('language_id'),
533
                        $qAttr->bindValue($contentInfoRow['initial_language_id'], null, PDO::PARAM_INT)
534
                    ),
535
                    $qAttr->bindValue(0, null, PDO::PARAM_INT)
536
                )
537
            );
538
            $qAttr->prepare()->execute();
539
        }
540
    }
541
542
    /**
543
     * Sets the status of the version identified by $contentId and $version to $status.
544
     *
545
     * The $status can be one of STATUS_DRAFT, STATUS_PUBLISHED, STATUS_ARCHIVED
546
     *
547
     * @param int $contentId
548
     * @param int $version
549
     * @param int $status
550
     *
551
     * @return bool
552
     */
553
    public function setStatus($contentId, $version, $status)
554
    {
555
        $q = $this->dbHandler->createUpdateQuery();
556
        $q->update(
557
            $this->dbHandler->quoteTable('ezcontentobject_version')
558
        )->set(
559
            $this->dbHandler->quoteColumn('status'),
560
            $q->bindValue($status, null, \PDO::PARAM_INT)
561
        )->set(
562
            $this->dbHandler->quoteColumn('modified'),
563
            $q->bindValue(time(), null, \PDO::PARAM_INT)
564
        )->where(
565
            $q->expr->lAnd(
566
                $q->expr->eq(
567
                    $this->dbHandler->quoteColumn('contentobject_id'),
568
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
569
                ),
570
                $q->expr->eq(
571
                    $this->dbHandler->quoteColumn('version'),
572
                    $q->bindValue($version, null, \PDO::PARAM_INT)
573
                )
574
            )
575
        );
576
        $statement = $q->prepare();
577
        $statement->execute();
578
579
        if ((bool)$statement->rowCount() === false) {
580
            return false;
581
        }
582
583
        if ($status !== APIVersionInfo::STATUS_PUBLISHED) {
584
            return true;
585
        }
586
587
        // If the version's status is PUBLISHED, we set the content to published status as well
588
        $q = $this->dbHandler->createUpdateQuery();
589
        $q->update(
590
            $this->dbHandler->quoteTable('ezcontentobject')
591
        )->set(
592
            $this->dbHandler->quoteColumn('status'),
593
            $q->bindValue(ContentInfo::STATUS_PUBLISHED, null, \PDO::PARAM_INT)
594
        )->set(
595
            $this->dbHandler->quoteColumn('current_version'),
596
            $q->bindValue($version, null, \PDO::PARAM_INT)
597
        )->where(
598
            $q->expr->eq(
599
                $this->dbHandler->quoteColumn('id'),
600
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
601
            )
602
        );
603
        $statement = $q->prepare();
604
        $statement->execute();
605
606
        return (bool)$statement->rowCount();
607
    }
608
609
    /**
610
     * Inserts a new field.
611
     *
612
     * Only used when a new field is created (i.e. a new object or a field in a
613
     * new language!). After that, field IDs need to stay the same, only the
614
     * version number changes.
615
     *
616
     * @param \eZ\Publish\SPI\Persistence\Content $content
617
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
618
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
619
     *
620
     * @return int ID
621
     */
622
    public function insertNewField(Content $content, Field $field, StorageFieldValue $value)
623
    {
624
        $q = $this->dbHandler->createInsertQuery();
625
626
        $this->setInsertFieldValues($q, $content, $field, $value);
627
628
        // Insert with auto increment ID
629
        $q->set(
630
            $this->dbHandler->quoteColumn('id'),
631
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_attribute', 'id')
632
        );
633
634
        $q->prepare()->execute();
635
636
        return $this->dbHandler->lastInsertId(
637
            $this->dbHandler->getSequenceName('ezcontentobject_attribute', 'id')
638
        );
639
    }
640
641
    /**
642
     * Inserts an existing field.
643
     *
644
     * Used to insert a field with an exsting ID but a new version number.
645
     *
646
     * @param Content $content
647
     * @param Field $field
648
     * @param StorageFieldValue $value
649
     */
650
    public function insertExistingField(Content $content, Field $field, StorageFieldValue $value)
651
    {
652
        $q = $this->dbHandler->createInsertQuery();
653
654
        $this->setInsertFieldValues($q, $content, $field, $value);
655
656
        $q->set(
657
            $this->dbHandler->quoteColumn('id'),
658
            $q->bindValue($field->id, null, \PDO::PARAM_INT)
659
        );
660
661
        $q->prepare()->execute();
662
    }
663
664
    /**
665
     * Sets field (ezcontentobject_attribute) values to the given query.
666
     *
667
     * @param \eZ\Publish\Core\Persistence\Database\InsertQuery $q
668
     * @param Content $content
669
     * @param Field $field
670
     * @param StorageFieldValue $value
671
     */
672
    protected function setInsertFieldValues(InsertQuery $q, Content $content, Field $field, StorageFieldValue $value)
673
    {
674
        $q->insertInto(
675
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
676
        )->set(
677
            $this->dbHandler->quoteColumn('contentobject_id'),
678
            $q->bindValue($content->versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
679
        )->set(
680
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
681
            $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
682
        )->set(
683
            $this->dbHandler->quoteColumn('data_type_string'),
684
            $q->bindValue($field->type)
685
        )->set(
686
            $this->dbHandler->quoteColumn('language_code'),
687
            $q->bindValue($field->languageCode)
688
        )->set(
689
            $this->dbHandler->quoteColumn('version'),
690
            $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
691
        )->set(
692
            $this->dbHandler->quoteColumn('data_float'),
693
            $q->bindValue($value->dataFloat)
694
        )->set(
695
            $this->dbHandler->quoteColumn('data_int'),
696
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
697
        )->set(
698
            $this->dbHandler->quoteColumn('data_text'),
699
            $q->bindValue($value->dataText)
700
        )->set(
701
            $this->dbHandler->quoteColumn('sort_key_int'),
702
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
703
        )->set(
704
            $this->dbHandler->quoteColumn('sort_key_string'),
705
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
706
        )->set(
707
            $this->dbHandler->quoteColumn('language_id'),
708
            $q->bindValue(
709
                $this->languageMaskGenerator->generateLanguageIndicator(
710
                    $field->languageCode,
711
                    $this->isLanguageAlwaysAvailable($content, $field->languageCode)
712
                ),
713
                null,
714
                \PDO::PARAM_INT
715
            )
716
        );
717
    }
718
719
    /**
720
     * Checks if $languageCode is always available in $content.
721
     *
722
     * @param \eZ\Publish\SPI\Persistence\Content $content
723
     * @param string $languageCode
724
     *
725
     * @return bool
726
     */
727
    protected function isLanguageAlwaysAvailable(Content $content, $languageCode)
728
    {
729
        return
730
            $content->versionInfo->contentInfo->alwaysAvailable &&
731
            $content->versionInfo->contentInfo->mainLanguageCode === $languageCode
732
        ;
733
    }
734
735
    /**
736
     * Updates an existing field.
737
     *
738
     * @param Field $field
739
     * @param StorageFieldValue $value
740
     */
741
    public function updateField(Field $field, StorageFieldValue $value)
742
    {
743
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
744
        // cannot change on update
745
        $q = $this->dbHandler->createUpdateQuery();
746
        $this->setFieldUpdateValues($q, $value);
747
        $q->where(
748
            $q->expr->lAnd(
749
                $q->expr->eq(
750
                    $this->dbHandler->quoteColumn('id'),
751
                    $q->bindValue($field->id, null, \PDO::PARAM_INT)
752
                ),
753
                $q->expr->eq(
754
                    $this->dbHandler->quoteColumn('version'),
755
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
756
                )
757
            )
758
        );
759
        $q->prepare()->execute();
760
    }
761
762
    /**
763
     * Sets update fields for $value on $q.
764
     *
765
     * @param \eZ\Publish\Core\Persistence\Database\UpdateQuery $q
766
     * @param StorageFieldValue $value
767
     */
768
    protected function setFieldUpdateValues(UpdateQuery $q, StorageFieldValue $value)
769
    {
770
        $q->update(
771
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
772
        )->set(
773
            $this->dbHandler->quoteColumn('data_float'),
774
            $q->bindValue($value->dataFloat)
775
        )->set(
776
            $this->dbHandler->quoteColumn('data_int'),
777
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
778
        )->set(
779
            $this->dbHandler->quoteColumn('data_text'),
780
            $q->bindValue($value->dataText)
781
        )->set(
782
            $this->dbHandler->quoteColumn('sort_key_int'),
783
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
784
        )->set(
785
            $this->dbHandler->quoteColumn('sort_key_string'),
786
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
787
        );
788
    }
789
790
    /**
791
     * Updates an existing, non-translatable field.
792
     *
793
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
794
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
795
     * @param int $contentId
796
     */
797
    public function updateNonTranslatableField(
798
        Field $field,
799
        StorageFieldValue $value,
800
        $contentId
801
    ) {
802
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
803
        // cannot change on update
804
        $q = $this->dbHandler->createUpdateQuery();
805
        $this->setFieldUpdateValues($q, $value);
806
        $q->where(
807
            $q->expr->lAnd(
808
                $q->expr->eq(
809
                    $this->dbHandler->quoteColumn('contentclassattribute_id'),
810
                    $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
811
                ),
812
                $q->expr->eq(
813
                    $this->dbHandler->quoteColumn('contentobject_id'),
814
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
815
                ),
816
                $q->expr->eq(
817
                    $this->dbHandler->quoteColumn('version'),
818
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
819
                )
820
            )
821
        );
822
        $q->prepare()->execute();
823
    }
824
825
    /**
826
     * {@inheritdoc}
827
     */
828
    public function load($contentId, $version, array $translations = null)
829
    {
830
        $results = $this->internalLoadContent([$contentId], $version, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 828 can also be of type array; however, eZ\Publish\Core\Persiste...::internalLoadContent() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
831
832
        return $results;
833
    }
834
835
    /**
836
     * {@inheritdoc}
837
     */
838
    public function loadContentList(array $contentIds, array $translations = null): array
839
    {
840
        return $this->internalLoadContent($contentIds, null, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 838 can also be of type array; however, eZ\Publish\Core\Persiste...::internalLoadContent() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
841
    }
842
843
    /**
844
     * @see load(), loadContentList()
845
     *
846
     * @param array $contentIds
847
     * @param int $version
848
     * @param string[]|null $translations
849
     *
850
     * @return array
851
     */
852
    private function internalLoadContent(array $contentIds, int $version = null, array $translations = null): array
853
    {
854
        $queryBuilder = $this->connection->createQueryBuilder();
855
        $expr = $queryBuilder->expr();
856
        $queryBuilder
857
            ->select(
858
                'c.id AS ezcontentobject_id',
859
                'c.contentclass_id AS ezcontentobject_contentclass_id',
860
                'c.section_id AS ezcontentobject_section_id',
861
                'c.owner_id AS ezcontentobject_owner_id',
862
                'c.remote_id AS ezcontentobject_remote_id',
863
                'c.current_version AS ezcontentobject_current_version',
864
                'c.initial_language_id AS ezcontentobject_initial_language_id',
865
                'c.modified AS ezcontentobject_modified',
866
                'c.published AS ezcontentobject_published',
867
                'c.status AS ezcontentobject_status',
868
                'c.name AS ezcontentobject_name',
869
                'c.language_mask AS ezcontentobject_language_mask',
870
                'v.id AS ezcontentobject_version_id',
871
                'v.version AS ezcontentobject_version_version',
872
                'v.modified AS ezcontentobject_version_modified',
873
                'v.creator_id AS ezcontentobject_version_creator_id',
874
                'v.created AS ezcontentobject_version_created',
875
                'v.status AS ezcontentobject_version_status',
876
                'v.language_mask AS ezcontentobject_version_language_mask',
877
                'v.initial_language_id AS ezcontentobject_version_initial_language_id',
878
                'a.id AS ezcontentobject_attribute_id',
879
                'a.contentclassattribute_id AS ezcontentobject_attribute_contentclassattribute_id',
880
                'a.data_type_string AS ezcontentobject_attribute_data_type_string',
881
                'a.language_code AS ezcontentobject_attribute_language_code',
882
                'a.language_id AS ezcontentobject_attribute_language_id',
883
                'a.data_float AS ezcontentobject_attribute_data_float',
884
                'a.data_int AS ezcontentobject_attribute_data_int',
885
                'a.data_text AS ezcontentobject_attribute_data_text',
886
                'a.sort_key_int AS ezcontentobject_attribute_sort_key_int',
887
                'a.sort_key_string AS ezcontentobject_attribute_sort_key_string',
888
                't.main_node_id AS ezcontentobject_tree_main_node_id'
889
            )
890
            ->from('ezcontentobject', 'c')
891
            ->innerJoin(
892
                'c',
893
                'ezcontentobject_version',
894
                'v',
895
                $expr->andX(
896
                    $expr->eq('c.id', 'v.contentobject_id'),
897
                    $expr->eq('v.version', $version ?? 'c.current_version')
898
                )
899
            )
900
            ->innerJoin(
901
                'v',
902
                'ezcontentobject_attribute',
903
                'a',
904
                $expr->andX(
905
                    $expr->eq('v.contentobject_id', 'a.contentobject_id'),
906
                    $expr->eq('v.version', 'a.version')
907
                )
908
            )
909
            ->leftJoin(
910
                'c',
911
                'ezcontentobject_tree',
912
                't',
913
                $expr->andX(
914
                    $expr->eq('c.id', 't.contentobject_id'),
915
                    $expr->eq('t.node_id', 't.main_node_id')
916
                )
917
            );
918
919
        $queryBuilder->where(
920
            $expr->in(
921
                'c.id',
922
                $queryBuilder->createNamedParameter($contentIds, Connection::PARAM_INT_ARRAY)
923
            )
924
        );
925
926
        if (!empty($translations)) {
927
            $queryBuilder->andWhere(
928
                $expr->in(
929
                    'a.language_code',
930
                    $queryBuilder->createNamedParameter($translations, Connection::PARAM_STR_ARRAY)
931
                )
932
            );
933
        }
934
935
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
936
    }
937
938
    /**
939
     * Get query builder to load Content Info data.
940
     *
941
     * @see loadContentInfo(), loadContentInfoByRemoteId(), loadContentInfoList(), loadContentInfoByLocationId()
942
     *
943
     * @return \Doctrine\DBAL\Query\QueryBuilder
944
     */
945
    private function createLoadContentInfoQueryBuilder(): DoctrineQueryBuilder
946
    {
947
        $queryBuilder = $this->connection->createQueryBuilder();
948
        $expr = $queryBuilder->expr();
949
        $queryBuilder
950
            ->select('c.*', 't.main_node_id AS ezcontentobject_tree_main_node_id')
951
            ->from('ezcontentobject', 'c')
952
            ->leftJoin(
953
                'c',
954
                'ezcontentobject_tree',
955
                't',
956
                $expr->andX(
957
                    $expr->eq('c.id', 't.contentobject_id'),
958
                    $expr->eq('t.node_id', 't.main_node_id')
959
                )
960
            );
961
962
        return $queryBuilder;
963
    }
964
965
    /**
966
     * Loads info for content identified by $contentId.
967
     * Will basically return a hash containing all field values for ezcontentobject table plus some additional keys:
968
     *  - always_available => Boolean indicating if content's language mask contains alwaysAvailable bit field
969
     *  - main_language_code => Language code for main (initial) language. E.g. "eng-GB".
970
     *
971
     * @param int $contentId
972
     *
973
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
974
     *
975
     * @return array
976
     */
977 View Code Duplication
    public function loadContentInfo($contentId)
978
    {
979
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
980
        $queryBuilder
981
            ->where('c.id = :id')
982
            ->setParameter('id', $contentId, ParameterType::INTEGER);
983
984
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
985
        if (empty($results)) {
986
            throw new NotFound('content', "id: $contentId");
987
        }
988
989
        return $results[0];
990
    }
991
992
    public function loadContentInfoList(array $contentIds)
993
    {
994
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
995
        $queryBuilder
996
            ->where('c.id IN (:ids)')
997
            ->setParameter('ids', $contentIds, Connection::PARAM_INT_ARRAY);
998
999
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1000
    }
1001
1002
    /**
1003
     * Loads info for a content object identified by its remote ID.
1004
     *
1005
     * Returns an array with the relevant data.
1006
     *
1007
     * @param mixed $remoteId
1008
     *
1009
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1010
     *
1011
     * @return array
1012
     */
1013 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
1014
    {
1015
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1016
        $queryBuilder
1017
            ->where('c.remote_id = :id')
1018
            ->setParameter('id', $remoteId, ParameterType::STRING);
1019
1020
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1021
        if (empty($results)) {
1022
            throw new NotFound('content', "remote_id: $remoteId");
1023
        }
1024
1025
        return $results[0];
1026
    }
1027
1028
    /**
1029
     * Loads info for a content object identified by its location ID (node ID).
1030
     *
1031
     * Returns an array with the relevant data.
1032
     *
1033
     * @param int $locationId
1034
     *
1035
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1036
     *
1037
     * @return array
1038
     */
1039 View Code Duplication
    public function loadContentInfoByLocationId($locationId)
1040
    {
1041
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1042
        $queryBuilder
1043
            ->where('t.main_node_id = :id')
1044
            ->setParameter('id', $locationId, ParameterType::INTEGER);
1045
1046
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1047
        if (empty($results)) {
1048
            throw new NotFound('content', "main_node_id: $locationId");
1049
        }
1050
1051
        return $results[0];
1052
    }
1053
1054
    /**
1055
     * Loads version info for content identified by $contentId and $versionNo.
1056
     * Will basically return a hash containing all field values from ezcontentobject_version table plus following keys:
1057
     *  - names => Hash of content object names. Key is the language code, value is the name.
1058
     *  - 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.
1059
     *  - initial_language_code => Language code for initial language in this version.
1060
     *
1061
     * @param int $contentId
1062
     * @param int $versionNo
1063
     *
1064
     * @return array
1065
     */
1066 View Code Duplication
    public function loadVersionInfo($contentId, $versionNo)
1067
    {
1068
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1069
        $query->where(
1070
            $query->expr->lAnd(
1071
                $query->expr->eq(
1072
                    $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1073
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1074
                ),
1075
                $query->expr->eq(
1076
                    $this->dbHandler->quoteColumn('version', 'ezcontentobject_version'),
1077
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1078
                )
1079
            )
1080
        );
1081
        $statement = $query->prepare();
1082
        $statement->execute();
1083
1084
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1085
    }
1086
1087
    /**
1088
     * Returns data for all versions with given status created by the given $userId.
1089
     *
1090
     * @param int $userId
1091
     * @param int $status
1092
     *
1093
     * @return string[][]
1094
     */
1095
    public function listVersionsForUser($userId, $status = VersionInfo::STATUS_DRAFT)
1096
    {
1097
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1098
        $query->where(
1099
            $query->expr->lAnd(
1100
                $query->expr->eq(
1101
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1102
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1103
                ),
1104
                $query->expr->eq(
1105
                    $this->dbHandler->quoteColumn('creator_id', 'ezcontentobject_version'),
1106
                    $query->bindValue($userId, null, \PDO::PARAM_INT)
1107
                )
1108
            )
1109
        );
1110
1111
        return $this->listVersionsHelper($query);
1112
    }
1113
1114
    /**
1115
     * Returns all version data for the given $contentId, optionally filtered by status.
1116
     *
1117
     * Result is returned with oldest version first (using version id as it has index and is auto increment).
1118
     *
1119
     * @param mixed $contentId
1120
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
1121
     * @param int $limit Limit for items returned, -1 means none.
1122
     *
1123
     * @return string[][]
1124
     */
1125
    public function listVersions($contentId, $status = null, $limit = -1)
1126
    {
1127
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1128
1129
        $filter = $query->expr->eq(
1130
            $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1131
            $query->bindValue($contentId, null, \PDO::PARAM_INT)
1132
        );
1133
1134
        if ($status !== null) {
1135
            $filter = $query->expr->lAnd(
1136
                $filter,
1137
                $query->expr->eq(
1138
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1139
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1140
                )
1141
            );
1142
        }
1143
1144
        $query->where($filter);
1145
1146
        if ($limit > 0) {
1147
            $query->limit($limit);
1148
        }
1149
1150
        return $this->listVersionsHelper($query);
1151
    }
1152
1153
    /**
1154
     * Helper for {@see listVersions()} and {@see listVersionsForUser()} that filters duplicates
1155
     * that are the result of the cartesian product performed by createVersionInfoFindQuery().
1156
     *
1157
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
1158
     *
1159
     * @return string[][]
1160
     */
1161
    private function listVersionsHelper(SelectQuery $query)
1162
    {
1163
        $query->orderBy(
1164
            $this->dbHandler->quoteColumn('id', 'ezcontentobject_version')
1165
        );
1166
1167
        $statement = $query->prepare();
1168
        $statement->execute();
1169
1170
        $results = array();
1171
        $previousId = null;
1172
        foreach ($statement->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1173
            if ($row['ezcontentobject_version_id'] == $previousId) {
1174
                continue;
1175
            }
1176
1177
            $previousId = $row['ezcontentobject_version_id'];
1178
            $results[] = $row;
1179
        }
1180
1181
        return $results;
1182
    }
1183
1184
    /**
1185
     * Returns all version numbers for the given $contentId.
1186
     *
1187
     * @param mixed $contentId
1188
     *
1189
     * @return int[]
1190
     */
1191
    public function listVersionNumbers($contentId)
1192
    {
1193
        $query = $this->dbHandler->createSelectQuery();
1194
        $query->selectDistinct(
1195
            $this->dbHandler->quoteColumn('version')
1196
        )->from(
1197
            $this->dbHandler->quoteTable('ezcontentobject_version')
1198
        )->where(
1199
            $query->expr->eq(
1200
                $this->dbHandler->quoteColumn('contentobject_id'),
1201
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1202
            )
1203
        );
1204
1205
        $statement = $query->prepare();
1206
        $statement->execute();
1207
1208
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1209
    }
1210
1211
    /**
1212
     * Returns last version number for content identified by $contentId.
1213
     *
1214
     * @param int $contentId
1215
     *
1216
     * @return int
1217
     */
1218
    public function getLastVersionNumber($contentId)
1219
    {
1220
        $query = $this->dbHandler->createSelectQuery();
1221
        $query->select(
1222
            $query->expr->max($this->dbHandler->quoteColumn('version'))
1223
        )->from(
1224
            $this->dbHandler->quoteTable('ezcontentobject_version')
1225
        )->where(
1226
            $query->expr->eq(
1227
                $this->dbHandler->quoteColumn('contentobject_id'),
1228
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1229
            )
1230
        );
1231
1232
        $statement = $query->prepare();
1233
        $statement->execute();
1234
1235
        return (int)$statement->fetchColumn();
1236
    }
1237
1238
    /**
1239
     * Returns all IDs for locations that refer to $contentId.
1240
     *
1241
     * @param int $contentId
1242
     *
1243
     * @return int[]
1244
     */
1245
    public function getAllLocationIds($contentId)
1246
    {
1247
        $query = $this->dbHandler->createSelectQuery();
1248
        $query->select(
1249
            $this->dbHandler->quoteColumn('node_id')
1250
        )->from(
1251
            $this->dbHandler->quoteTable('ezcontentobject_tree')
1252
        )->where(
1253
            $query->expr->eq(
1254
                $this->dbHandler->quoteColumn('contentobject_id'),
1255
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1256
            )
1257
        );
1258
1259
        $statement = $query->prepare();
1260
        $statement->execute();
1261
1262
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1263
    }
1264
1265
    /**
1266
     * Returns all field IDs of $contentId grouped by their type.
1267
     * If $versionNo is set only field IDs for that version are returned.
1268
     * If $languageCode is set, only field IDs for that language are returned.
1269
     *
1270
     * @param int $contentId
1271
     * @param int|null $versionNo
1272
     * @param string|null $languageCode
1273
     *
1274
     * @return int[][]
1275
     */
1276
    public function getFieldIdsByType($contentId, $versionNo = null, $languageCode = null)
1277
    {
1278
        $query = $this->dbHandler->createSelectQuery();
1279
        $query->select(
1280
            $this->dbHandler->quoteColumn('id'),
1281
            $this->dbHandler->quoteColumn('data_type_string')
1282
        )->from(
1283
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1284
        )->where(
1285
            $query->expr->eq(
1286
                $this->dbHandler->quoteColumn('contentobject_id'),
1287
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1288
            )
1289
        );
1290
1291
        if (isset($versionNo)) {
1292
            $query->where(
1293
                $query->expr->eq(
1294
                    $this->dbHandler->quoteColumn('version'),
1295
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1296
                )
1297
            );
1298
        }
1299
1300
        if (isset($languageCode)) {
1301
            $query->where(
1302
                $query->expr->eq(
1303
                    $this->dbHandler->quoteColumn('language_code'),
1304
                    $query->bindValue($languageCode, null, \PDO::PARAM_STR)
1305
                )
1306
            );
1307
        }
1308
1309
        $statement = $query->prepare();
1310
        $statement->execute();
1311
1312
        $result = array();
1313
        foreach ($statement->fetchAll() as $row) {
1314
            if (!isset($result[$row['data_type_string']])) {
1315
                $result[$row['data_type_string']] = array();
1316
            }
1317
            $result[$row['data_type_string']][] = (int)$row['id'];
1318
        }
1319
1320
        return $result;
1321
    }
1322
1323
    /**
1324
     * Deletes relations to and from $contentId.
1325
     * If $versionNo is set only relations for that version are deleted.
1326
     *
1327
     * @param int $contentId
1328
     * @param int|null $versionNo
1329
     */
1330
    public function deleteRelations($contentId, $versionNo = null)
1331
    {
1332
        $query = $this->dbHandler->createDeleteQuery();
1333
        $query->deleteFrom(
1334
            $this->dbHandler->quoteTable('ezcontentobject_link')
1335
        );
1336
1337
        if (isset($versionNo)) {
1338
            $query->where(
1339
                $query->expr->lAnd(
1340
                    $query->expr->eq(
1341
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1342
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1343
                    ),
1344
                    $query->expr->eq(
1345
                        $this->dbHandler->quoteColumn('from_contentobject_version'),
1346
                        $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1347
                    )
1348
                )
1349
            );
1350
        } else {
1351
            $query->where(
1352
                $query->expr->lOr(
1353
                    $query->expr->eq(
1354
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1355
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1356
                    ),
1357
                    $query->expr->eq(
1358
                        $this->dbHandler->quoteColumn('to_contentobject_id'),
1359
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1360
                    )
1361
                )
1362
            );
1363
        }
1364
1365
        $query->prepare()->execute();
1366
    }
1367
1368
    /**
1369
     * Removes relations to Content with $contentId from Relation and RelationList field type fields.
1370
     *
1371
     * @param int $contentId
1372
     */
1373
    public function removeReverseFieldRelations($contentId)
1374
    {
1375
        $query = $this->dbHandler->createSelectQuery();
1376
        $query
1377
            ->select('ezcontentobject_attribute.*')
1378
            ->from('ezcontentobject_attribute')
1379
            ->innerJoin(
1380
                'ezcontentobject_link',
1381
                $query->expr->lAnd(
1382
                    $query->expr->eq(
1383
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1384
                        $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_attribute')
1385
                    ),
1386
                    $query->expr->eq(
1387
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1388
                        $this->dbHandler->quoteColumn('version', 'ezcontentobject_attribute')
1389
                    ),
1390
                    $query->expr->eq(
1391
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_link'),
1392
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_attribute')
1393
                    )
1394
                )
1395
            )
1396
            ->where(
1397
                $query->expr->eq(
1398
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1399
                    $query->bindValue($contentId, null, PDO::PARAM_INT)
1400
                ),
1401
                $query->expr->gt(
1402
                    $query->expr->bitAnd(
1403
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1404
                        $query->bindValue(8, null, PDO::PARAM_INT)
1405
                    ),
1406
                    0
1407
                )
1408
            );
1409
1410
        $statement = $query->prepare();
1411
        $statement->execute();
1412
1413
        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
1414
            if ($row['data_type_string'] === 'ezobjectrelation') {
1415
                $this->removeRelationFromRelationField($row);
1416
            }
1417
1418
            if ($row['data_type_string'] === 'ezobjectrelationlist') {
1419
                $this->removeRelationFromRelationListField($contentId, $row);
1420
            }
1421
        }
1422
    }
1423
1424
    /**
1425
     * Updates field value of RelationList field type identified by given $row data,
1426
     * removing relations toward given $contentId.
1427
     *
1428
     * @param int $contentId
1429
     * @param array $row
1430
     */
1431
    protected function removeRelationFromRelationListField($contentId, array $row)
1432
    {
1433
        $document = new DOMDocument('1.0', 'utf-8');
1434
        $document->loadXML($row['data_text']);
1435
1436
        $xpath = new DOMXPath($document);
1437
        $xpathExpression = "//related-objects/relation-list/relation-item[@contentobject-id='{$contentId}']";
1438
1439
        $relationItems = $xpath->query($xpathExpression);
1440
        foreach ($relationItems as $relationItem) {
1441
            $relationItem->parentNode->removeChild($relationItem);
1442
        }
1443
1444
        $query = $this->dbHandler->createUpdateQuery();
1445
        $query
1446
            ->update('ezcontentobject_attribute')
1447
            ->set(
1448
                'data_text',
1449
                $query->bindValue($document->saveXML(), null, PDO::PARAM_STR)
1450
            )
1451
            ->where(
1452
                $query->expr->lAnd(
1453
                    $query->expr->eq(
1454
                        $this->dbHandler->quoteColumn('id'),
1455
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1456
                    ),
1457
                    $query->expr->eq(
1458
                        $this->dbHandler->quoteColumn('version'),
1459
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1460
                    )
1461
                )
1462
            );
1463
1464
        $query->prepare()->execute();
1465
    }
1466
1467
    /**
1468
     * Updates field value of Relation field type identified by given $row data,
1469
     * removing relation data.
1470
     *
1471
     * @param array $row
1472
     */
1473
    protected function removeRelationFromRelationField(array $row)
1474
    {
1475
        $query = $this->dbHandler->createUpdateQuery();
1476
        $query
1477
            ->update('ezcontentobject_attribute')
1478
            ->set('data_int', $query->bindValue(null, null, PDO::PARAM_INT))
1479
            ->set('sort_key_int', $query->bindValue(0, null, PDO::PARAM_INT))
1480
            ->where(
1481
                $query->expr->lAnd(
1482
                    $query->expr->eq(
1483
                        $this->dbHandler->quoteColumn('id'),
1484
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1485
                    ),
1486
                    $query->expr->eq(
1487
                        $this->dbHandler->quoteColumn('version'),
1488
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1489
                    )
1490
                )
1491
            );
1492
1493
        $query->prepare()->execute();
1494
    }
1495
1496
    /**
1497
     * Deletes the field with the given $fieldId.
1498
     *
1499
     * @param int $fieldId
1500
     */
1501
    public function deleteField($fieldId)
1502
    {
1503
        $query = $this->dbHandler->createDeleteQuery();
1504
        $query->deleteFrom(
1505
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1506
        )->where(
1507
            $query->expr->eq(
1508
                $this->dbHandler->quoteColumn('id'),
1509
                $query->bindValue($fieldId, null, \PDO::PARAM_INT)
1510
            )
1511
        );
1512
1513
        $query->prepare()->execute();
1514
    }
1515
1516
    /**
1517
     * Deletes all fields of $contentId in all versions.
1518
     * If $versionNo is set only fields for that version are deleted.
1519
     *
1520
     * @param int $contentId
1521
     * @param int|null $versionNo
1522
     */
1523
    public function deleteFields($contentId, $versionNo = null)
1524
    {
1525
        $query = $this->dbHandler->createDeleteQuery();
1526
        $query->deleteFrom('ezcontentobject_attribute')
1527
            ->where(
1528
                $query->expr->eq(
1529
                    $this->dbHandler->quoteColumn('contentobject_id'),
1530
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1531
                )
1532
            );
1533
1534
        if (isset($versionNo)) {
1535
            $query->where(
1536
                $query->expr->eq(
1537
                    $this->dbHandler->quoteColumn('version'),
1538
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1539
                )
1540
            );
1541
        }
1542
1543
        $query->prepare()->execute();
1544
    }
1545
1546
    /**
1547
     * Deletes all versions of $contentId.
1548
     * If $versionNo is set only that version is deleted.
1549
     *
1550
     * @param int $contentId
1551
     * @param int|null $versionNo
1552
     */
1553
    public function deleteVersions($contentId, $versionNo = null)
1554
    {
1555
        $query = $this->dbHandler->createDeleteQuery();
1556
        $query->deleteFrom('ezcontentobject_version')
1557
            ->where(
1558
                $query->expr->eq(
1559
                    $this->dbHandler->quoteColumn('contentobject_id'),
1560
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1561
                )
1562
            );
1563
1564
        if (isset($versionNo)) {
1565
            $query->where(
1566
                $query->expr->eq(
1567
                    $this->dbHandler->quoteColumn('version'),
1568
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1569
                )
1570
            );
1571
        }
1572
1573
        $query->prepare()->execute();
1574
    }
1575
1576
    /**
1577
     * Deletes all names of $contentId.
1578
     * If $versionNo is set only names for that version are deleted.
1579
     *
1580
     * @param int $contentId
1581
     * @param int|null $versionNo
1582
     */
1583
    public function deleteNames($contentId, $versionNo = null)
1584
    {
1585
        $query = $this->dbHandler->createDeleteQuery();
1586
        $query->deleteFrom('ezcontentobject_name')
1587
            ->where(
1588
                $query->expr->eq(
1589
                    $this->dbHandler->quoteColumn('contentobject_id'),
1590
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1591
                )
1592
            );
1593
1594
        if (isset($versionNo)) {
1595
            $query->where(
1596
                $query->expr->eq(
1597
                    $this->dbHandler->quoteColumn('content_version'),
1598
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1599
                )
1600
            );
1601
        }
1602
1603
        $query->prepare()->execute();
1604
    }
1605
1606
    /**
1607
     * Sets the name for Content $contentId in version $version to $name in $language.
1608
     *
1609
     * @param int $contentId
1610
     * @param int $version
1611
     * @param string $name
1612
     * @param string $language
1613
     */
1614
    public function setName($contentId, $version, $name, $language)
1615
    {
1616
        $language = $this->languageHandler->loadByLanguageCode($language);
1617
1618
        // Is it an insert or an update ?
1619
        $qSelect = $this->dbHandler->createSelectQuery();
1620
        $qSelect
1621
            ->select(
1622
                $qSelect->alias($qSelect->expr->count('*'), 'count')
1623
            )
1624
            ->from($this->dbHandler->quoteTable('ezcontentobject_name'))
1625
            ->where(
1626
                $qSelect->expr->lAnd(
1627
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $qSelect->bindValue($contentId)),
1628
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_version'), $qSelect->bindValue($version)),
1629
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_translation'), $qSelect->bindValue($language->languageCode))
1630
                )
1631
            );
1632
        $stmt = $qSelect->prepare();
1633
        $stmt->execute();
1634
        $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
1635
1636
        $insert = $res[0]['count'] == 0;
1637
        if ($insert) {
1638
            $q = $this->dbHandler->createInsertQuery();
1639
            $q->insertInto($this->dbHandler->quoteTable('ezcontentobject_name'));
1640
        } else {
1641
            $q = $this->dbHandler->createUpdateQuery();
1642
            $q->update($this->dbHandler->quoteTable('ezcontentobject_name'))
1643
                ->where(
1644
                    $q->expr->lAnd(
1645
                        $q->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $q->bindValue($contentId)),
1646
                        $q->expr->eq($this->dbHandler->quoteColumn('content_version'), $q->bindValue($version)),
1647
                        $q->expr->eq($this->dbHandler->quoteColumn('content_translation'), $q->bindValue($language->languageCode))
1648
                    )
1649
                );
1650
        }
1651
1652
        $q->set(
1653
            $this->dbHandler->quoteColumn('contentobject_id'),
1654
            $q->bindValue($contentId, null, \PDO::PARAM_INT)
1655
        )->set(
1656
            $this->dbHandler->quoteColumn('content_version'),
1657
            $q->bindValue($version, null, \PDO::PARAM_INT)
1658
        )->set(
1659
            $this->dbHandler->quoteColumn('language_id'),
1660
            '(' . $this->getLanguageQuery()->getQuery() . ')'
1661
        )->set(
1662
            $this->dbHandler->quoteColumn('content_translation'),
1663
            $q->bindValue($language->languageCode)
1664
        )->set(
1665
            $this->dbHandler->quoteColumn('real_translation'),
1666
            $q->bindValue($language->languageCode)
1667
        )->set(
1668
            $this->dbHandler->quoteColumn('name'),
1669
            $q->bindValue($name)
1670
        );
1671
        $q->bindValue($language->id, ':languageId', \PDO::PARAM_INT);
1672
        $q->bindValue($contentId, ':contentId', \PDO::PARAM_INT);
1673
        $q->prepare()->execute();
1674
    }
1675
1676
    /**
1677
     * Returns a language sub select query for setName.
1678
     *
1679
     * Return sub select query which gets proper language mask for alwaysAvailable Content.
1680
     *
1681
     * @return \eZ\Publish\Core\Persistence\Database\SelectQuery
1682
     */
1683
    private function getLanguageQuery()
1684
    {
1685
        $languageQuery = $this->dbHandler->createSelectQuery();
1686
        $languageQuery
1687
            ->select(
1688
                $languageQuery->expr->searchedCase(
1689
                    [
1690
                        $languageQuery->expr->lAnd(
1691
                            $languageQuery->expr->eq(
1692
                                $this->dbHandler->quoteColumn('initial_language_id'),
1693
                                ':languageId'
1694
                            ),
1695
                            // wrap bitwise check into another "neq" to provide cross-DBMS compatibility
1696
                            $languageQuery->expr->neq(
1697
                                $languageQuery->expr->bitAnd(
1698
                                    $this->dbHandler->quoteColumn('language_mask'),
1699
                                    ':languageId'
1700
                                ),
1701
                                0
1702
                            )
1703
                        ),
1704
                        $languageQuery->expr->bitOr(
1705
                            ':languageId',
1706
                            1
1707
                        ),
1708
                    ],
1709
                    ':languageId'
1710
                )
1711
            )
1712
            ->from('ezcontentobject')
1713
            ->where(
1714
                $languageQuery->expr->eq(
1715
                    'id',
1716
                    ':contentId'
1717
                )
1718
            );
1719
1720
        return $languageQuery;
1721
    }
1722
1723
    /**
1724
     * Deletes the actual content object referred to by $contentId.
1725
     *
1726
     * @param int $contentId
1727
     */
1728
    public function deleteContent($contentId)
1729
    {
1730
        $query = $this->dbHandler->createDeleteQuery();
1731
        $query->deleteFrom('ezcontentobject')
1732
            ->where(
1733
                $query->expr->eq(
1734
                    $this->dbHandler->quoteColumn('id'),
1735
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1736
                )
1737
            );
1738
1739
        $query->prepare()->execute();
1740
    }
1741
1742
    /**
1743
     * Loads relations from $contentId to published content, optionally only from $contentVersionNo.
1744
     *
1745
     * $relationType can also be filtered.
1746
     *
1747
     * @param int $contentId
1748
     * @param int $contentVersionNo
1749
     * @param int $relationType
1750
     *
1751
     * @return string[][] array of relation data
1752
     */
1753
    public function loadRelations($contentId, $contentVersionNo = null, $relationType = null)
1754
    {
1755
        $query = $this->queryBuilder->createRelationFindQuery();
1756
        $query->innerJoin(
1757
            $query->alias(
1758
                $this->dbHandler->quoteTable('ezcontentobject'),
1759
                'ezcontentobject_to'
1760
            ),
1761
            $query->expr->lAnd(
1762
                $query->expr->eq(
1763
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1764
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject_to')
1765
                ),
1766
                $query->expr->eq(
1767
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_to'),
1768
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1769
                )
1770
            )
1771
        )->where(
1772
            $query->expr->eq(
1773
                $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1774
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1775
            )
1776
        );
1777
1778
        // source version number
1779
        if (isset($contentVersionNo)) {
1780
            $query->where(
1781
                $query->expr->eq(
1782
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1783
                    $query->bindValue($contentVersionNo, null, \PDO::PARAM_INT)
1784
                )
1785
            );
1786
        } else { // from published version only
1787
            $query->from(
1788
                $this->dbHandler->quoteTable('ezcontentobject')
1789
            )->where(
1790
                $query->expr->lAnd(
1791
                    $query->expr->eq(
1792
                        $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1793
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1794
                    ),
1795
                    $query->expr->eq(
1796
                        $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1797
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1798
                    )
1799
                )
1800
            );
1801
        }
1802
1803
        // relation type
1804 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...
1805
            $query->where(
1806
                $query->expr->gt(
1807
                    $query->expr->bitAnd(
1808
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1809
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1810
                    ),
1811
                    0
1812
                )
1813
            );
1814
        }
1815
1816
        $statement = $query->prepare();
1817
        $statement->execute();
1818
1819
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1820
    }
1821
1822
    /**
1823
     * Loads data that related to $toContentId.
1824
     *
1825
     * @param int $toContentId
1826
     * @param int $relationType
1827
     *
1828
     * @return mixed[][] Content data, array structured like {@see \eZ\Publish\Core\Persistence\Legacy\Content\Gateway::load()}
1829
     */
1830
    public function loadReverseRelations($toContentId, $relationType = null)
1831
    {
1832
        $query = $this->queryBuilder->createRelationFindQuery();
1833
        $query->where(
1834
            $query->expr->eq(
1835
                $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1836
                $query->bindValue($toContentId, null, \PDO::PARAM_INT)
1837
            )
1838
        );
1839
1840
        // ezcontentobject join
1841
        $query->from(
1842
            $this->dbHandler->quoteTable('ezcontentobject')
1843
        )->where(
1844
            $query->expr->lAnd(
1845
                $query->expr->eq(
1846
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1847
                    $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1848
                ),
1849
                $query->expr->eq(
1850
                    $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1851
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1852
                ),
1853
                $query->expr->eq(
1854
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject'),
1855
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1856
                )
1857
            )
1858
        );
1859
1860
        // relation type
1861 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...
1862
            $query->where(
1863
                $query->expr->gt(
1864
                    $query->expr->bitAnd(
1865
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1866
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1867
                    ),
1868
                    0
1869
                )
1870
            );
1871
        }
1872
1873
        $statement = $query->prepare();
1874
1875
        $statement->execute();
1876
1877
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1878
    }
1879
1880
    /**
1881
     * Inserts a new relation database record.
1882
     *
1883
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
1884
     *
1885
     * @return int ID the inserted ID
1886
     */
1887 View Code Duplication
    public function insertRelation(RelationCreateStruct $createStruct)
1888
    {
1889
        $q = $this->dbHandler->createInsertQuery();
1890
        $q->insertInto(
1891
            $this->dbHandler->quoteTable('ezcontentobject_link')
1892
        )->set(
1893
            $this->dbHandler->quoteColumn('id'),
1894
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_link', 'id')
1895
        )->set(
1896
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
1897
            $q->bindValue((int)$createStruct->sourceFieldDefinitionId, null, \PDO::PARAM_INT)
1898
        )->set(
1899
            $this->dbHandler->quoteColumn('from_contentobject_id'),
1900
            $q->bindValue($createStruct->sourceContentId, null, \PDO::PARAM_INT)
1901
        )->set(
1902
            $this->dbHandler->quoteColumn('from_contentobject_version'),
1903
            $q->bindValue($createStruct->sourceContentVersionNo, null, \PDO::PARAM_INT)
1904
        )->set(
1905
            $this->dbHandler->quoteColumn('relation_type'),
1906
            $q->bindValue($createStruct->type, null, \PDO::PARAM_INT)
1907
        )->set(
1908
            $this->dbHandler->quoteColumn('to_contentobject_id'),
1909
            $q->bindValue($createStruct->destinationContentId, null, \PDO::PARAM_INT)
1910
        );
1911
1912
        $q->prepare()->execute();
1913
1914
        return $this->dbHandler->lastInsertId(
1915
            $this->dbHandler->getSequenceName('ezcontentobject_link', 'id')
1916
        );
1917
    }
1918
1919
    /**
1920
     * Deletes the relation with the given $relationId.
1921
     *
1922
     * @param int $relationId
1923
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
1924
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
1925
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
1926
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
1927
     */
1928
    public function deleteRelation($relationId, $type)
1929
    {
1930
        // Legacy Storage stores COMMON, LINK and EMBED types using bitmask, therefore first load
1931
        // existing relation type by given $relationId for comparison
1932
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1933
        $query = $this->dbHandler->createSelectQuery();
1934
        $query->select(
1935
            $this->dbHandler->quoteColumn('relation_type')
1936
        )->from(
1937
            $this->dbHandler->quoteTable('ezcontentobject_link')
1938
        )->where(
1939
            $query->expr->eq(
1940
                $this->dbHandler->quoteColumn('id'),
1941
                $query->bindValue($relationId, null, \PDO::PARAM_INT)
1942
            )
1943
        );
1944
1945
        $statement = $query->prepare();
1946
        $statement->execute();
1947
        $loadedRelationType = $statement->fetchColumn();
1948
1949
        if (!$loadedRelationType) {
1950
            return;
1951
        }
1952
1953
        // If relation type matches then delete
1954
        if ($loadedRelationType == $type) {
1955
            /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1956
            $query = $this->dbHandler->createDeleteQuery();
1957
            $query->deleteFrom(
1958
                'ezcontentobject_link'
1959
            )->where(
1960
                $query->expr->eq(
1961
                    $this->dbHandler->quoteColumn('id'),
1962
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1963
                )
1964
            );
1965
1966
            $query->prepare()->execute();
1967
        } elseif ($loadedRelationType & $type) { // If relation type is composite update bitmask
1968
            /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
1969
            $query = $this->dbHandler->createUpdateQuery();
1970
            $query->update(
1971
                $this->dbHandler->quoteTable('ezcontentobject_link')
1972
            )->set(
1973
                $this->dbHandler->quoteColumn('relation_type'),
1974
                $query->expr->bitAnd(
1975
                    $this->dbHandler->quoteColumn('relation_type'),
1976
                    $query->bindValue(~$type, null, \PDO::PARAM_INT)
1977
                )
1978
            )->where(
1979
                $query->expr->eq(
1980
                    $this->dbHandler->quoteColumn('id'),
1981
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1982
                )
1983
            );
1984
1985
            $query->prepare()->execute();
1986
        } else {
1987
            // No match, do nothing
1988
        }
1989
    }
1990
1991
    /**
1992
     * Returns all Content IDs for a given $contentTypeId.
1993
     *
1994
     * @param int $contentTypeId
1995
     *
1996
     * @return int[]
1997
     */
1998
    public function getContentIdsByContentTypeId($contentTypeId)
1999
    {
2000
        $query = $this->dbHandler->createSelectQuery();
2001
        $query
2002
            ->select($this->dbHandler->quoteColumn('id'))
2003
            ->from($this->dbHandler->quoteTable('ezcontentobject'))
2004
            ->where(
2005
                $query->expr->eq(
2006
                    $this->dbHandler->quoteColumn('contentclass_id'),
2007
                    $query->bindValue($contentTypeId, null, PDO::PARAM_INT)
2008
                )
2009
            );
2010
2011
        $statement = $query->prepare();
2012
        $statement->execute();
2013
2014
        return $statement->fetchAll(PDO::FETCH_COLUMN);
2015
    }
2016
2017
    /**
2018
     * Load name data for set of content id's and corresponding version number.
2019
     *
2020
     * @param array[] $rows array of hashes with 'id' and 'version' to load names for
2021
     *
2022
     * @return array
2023
     */
2024
    public function loadVersionedNameData($rows)
2025
    {
2026
        $query = $this->queryBuilder->createNamesQuery();
2027
        $conditions = array();
2028
        foreach ($rows as $row) {
2029
            $conditions[] = $query->expr->lAnd(
2030
                $query->expr->eq(
2031
                    $this->dbHandler->quoteColumn('contentobject_id'),
2032
                    $query->bindValue($row['id'], null, \PDO::PARAM_INT)
2033
                ),
2034
                $query->expr->eq(
2035
                    $this->dbHandler->quoteColumn('content_version'),
2036
                    $query->bindValue($row['version'], null, \PDO::PARAM_INT)
2037
                )
2038
            );
2039
        }
2040
2041
        $query->where($query->expr->lOr($conditions));
2042
        $stmt = $query->prepare();
2043
        $stmt->execute();
2044
2045
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
2046
    }
2047
2048
    /**
2049
     * Batch method for copying all relation meta data for copied Content object.
2050
     *
2051
     * {@inheritdoc}
2052
     *
2053
     * @param int $originalContentId
2054
     * @param int $copiedContentId
2055
     * @param int|null $versionNo If specified only copy for a given version number, otherwise all.
2056
     */
2057
    public function copyRelations($originalContentId, $copiedContentId, $versionNo = null)
2058
    {
2059
        // Given we can retain all columns, we just create copies with new `from_contentobject_id` using INSERT INTO SELECT
2060
        $sql = 'INSERT INTO ezcontentobject_link ( contentclassattribute_id, from_contentobject_id, from_contentobject_version, relation_type, to_contentobject_id )
2061
                SELECT  L2.contentclassattribute_id, :copied_id, L2.from_contentobject_version, L2.relation_type, L2.to_contentobject_id
2062
                FROM    ezcontentobject_link AS L2
2063
                WHERE   L2.from_contentobject_id = :original_id';
2064
2065
        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...
2066
            $stmt = $this->connection->prepare($sql . ' AND L2.from_contentobject_version = :version');
2067
            $stmt->bindValue('version', $versionNo, PDO::PARAM_INT);
2068
        } else {
2069
            $stmt = $this->connection->prepare($sql);
2070
        }
2071
2072
        $stmt->bindValue('original_id', $originalContentId, PDO::PARAM_INT);
2073
        $stmt->bindValue('copied_id', $copiedContentId, PDO::PARAM_INT);
2074
2075
        $stmt->execute();
2076
    }
2077
2078
    /**
2079
     * Remove the specified translation from the Content Object Version.
2080
     *
2081
     * @param int $contentId
2082
     * @param string $languageCode language code of the translation
2083
     * @throws \Doctrine\DBAL\DBALException
2084
     */
2085 View Code Duplication
    public function deleteTranslationFromContent($contentId, $languageCode)
2086
    {
2087
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2088
2089
        $this->connection->beginTransaction();
2090
        try {
2091
            $this->deleteTranslationFromContentVersions($contentId, $language->id);
2092
            $this->deleteTranslationFromContentNames($contentId, $languageCode);
2093
            $this->deleteTranslationFromContentObject($contentId, $language->id);
2094
2095
            $this->connection->commit();
2096
        } catch (DBALException $e) {
2097
            $this->connection->rollBack();
2098
            throw $e;
2099
        }
2100
    }
2101
2102
    /**
2103
     * Delete Content fields (attributes) for the given Translation.
2104
     * If $versionNo is given, fields for that Version only will be deleted.
2105
     *
2106
     * @param string $languageCode
2107
     * @param int $contentId
2108
     * @param int $versionNo (optional) filter by versionNo
2109
     */
2110 View Code Duplication
    public function deleteTranslatedFields($languageCode, $contentId, $versionNo = null)
2111
    {
2112
        $query = $this->connection->createQueryBuilder();
2113
        $query
2114
            ->delete('ezcontentobject_attribute')
2115
            ->where('contentobject_id = :contentId')
2116
            ->andWhere('language_code = :languageCode')
2117
            ->setParameters(
2118
                [
2119
                    ':contentId' => $contentId,
2120
                    ':languageCode' => $languageCode,
2121
                ]
2122
            )
2123
        ;
2124
2125
        if (null !== $versionNo) {
2126
            $query
2127
                ->andWhere('version = :versionNo')
2128
                ->setParameter(':versionNo', $versionNo)
2129
            ;
2130
        }
2131
2132
        $query->execute();
2133
    }
2134
2135
    /**
2136
     * Delete the specified Translation from the given Version.
2137
     *
2138
     * @param int $contentId
2139
     * @param int $versionNo
2140
     * @param string $languageCode
2141
     * @throws \Doctrine\DBAL\DBALException
2142
     */
2143 View Code Duplication
    public function deleteTranslationFromVersion($contentId, $versionNo, $languageCode)
2144
    {
2145
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2146
2147
        $this->connection->beginTransaction();
2148
        try {
2149
            $this->deleteTranslationFromContentVersions($contentId, $language->id, $versionNo);
2150
            $this->deleteTranslationFromContentNames($contentId, $languageCode, $versionNo);
2151
2152
            $this->connection->commit();
2153
        } catch (DBALException $e) {
2154
            $this->connection->rollBack();
2155
            throw $e;
2156
        }
2157
    }
2158
2159
    /**
2160
     * Delete translation from the ezcontentobject_name table.
2161
     *
2162
     * @param int $contentId
2163
     * @param string $languageCode
2164
     * @param int $versionNo optional, if specified, apply to this Version only.
2165
     */
2166 View Code Duplication
    private function deleteTranslationFromContentNames($contentId, $languageCode, $versionNo = null)
2167
    {
2168
        $query = $this->connection->createQueryBuilder();
2169
        $query
2170
            ->delete('ezcontentobject_name')
2171
            ->where('contentobject_id=:contentId')
2172
            ->andWhere('real_translation=:languageCode')
2173
            ->setParameters(
2174
                [
2175
                    ':languageCode' => $languageCode,
2176
                    ':contentId' => $contentId,
2177
                ]
2178
            )
2179
        ;
2180
2181
        if (null !== $versionNo) {
2182
            $query
2183
                ->andWhere('content_version = :versionNo')
2184
                ->setParameter(':versionNo', $versionNo)
2185
            ;
2186
        }
2187
2188
        $query->execute();
2189
    }
2190
2191
    /**
2192
     * Remove language from language_mask of ezcontentobject.
2193
     *
2194
     * @param int $contentId
2195
     * @param int $languageId
2196
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2197
     */
2198
    private function deleteTranslationFromContentObject($contentId, $languageId)
2199
    {
2200
        $query = $this->connection->createQueryBuilder();
2201
        $query->update('ezcontentobject')
2202
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2203
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2204
            ->set('modified', ':now')
2205
            ->where('id = :contentId')
2206
            ->andWhere(
2207
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2208
                $query->expr()->andX(
2209
                    'language_mask & ~ ' . $languageId . ' <> 0',
2210
                    'language_mask & ~ ' . $languageId . ' <> 1'
2211
                )
2212
            )
2213
            ->setParameter(':now', time())
2214
            ->setParameter(':contentId', $contentId)
2215
        ;
2216
2217
        $rowCount = $query->execute();
2218
2219
        // no rows updated means that most likely somehow it was the last remaining translation
2220
        if ($rowCount === 0) {
2221
            throw new BadStateException(
2222
                '$languageCode',
2223
                'Specified translation is the only one Content Object Version has'
2224
            );
2225
        }
2226
    }
2227
2228
    /**
2229
     * Remove language from language_mask of ezcontentobject_version and update initialLanguageId
2230
     * if it matches the removed one.
2231
     *
2232
     * @param int $contentId
2233
     * @param int $languageId
2234
     * @param int $versionNo optional, if specified, apply to this Version only.
2235
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2236
     */
2237
    private function deleteTranslationFromContentVersions($contentId, $languageId, $versionNo = null)
2238
    {
2239
        $query = $this->connection->createQueryBuilder();
2240
        $query->update('ezcontentobject_version')
2241
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2242
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2243
            ->set('modified', ':now')
2244
            // update initial_language_id only if it matches removed translation languageId
2245
            ->set(
2246
                'initial_language_id',
2247
                'CASE WHEN initial_language_id = :languageId ' .
2248
                'THEN (SELECT initial_language_id AS main_language_id FROM ezcontentobject c WHERE c.id = :contentId) ' .
2249
                'ELSE initial_language_id END'
2250
            )
2251
            ->where('contentobject_id = :contentId')
2252
            ->andWhere(
2253
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2254
                $query->expr()->andX(
2255
                    'language_mask & ~ ' . $languageId . ' <> 0',
2256
                    'language_mask & ~ ' . $languageId . ' <> 1'
2257
                )
2258
            )
2259
            ->setParameter(':now', time())
2260
            ->setParameter(':contentId', $contentId)
2261
            ->setParameter(':languageId', $languageId)
2262
        ;
2263
2264
        if (null !== $versionNo) {
2265
            $query
2266
                ->andWhere('version = :versionNo')
2267
                ->setParameter(':versionNo', $versionNo)
2268
            ;
2269
        }
2270
2271
        $rowCount = $query->execute();
2272
2273
        // no rows updated means that most likely somehow it was the last remaining translation
2274
        if ($rowCount === 0) {
2275
            throw new BadStateException(
2276
                '$languageCode',
2277
                'Specified translation is the only one Content Object Version has'
2278
            );
2279
        }
2280
    }
2281
}
2282