Completed
Push — 7.0 ( e06b0d...07af66 )
by André
31:05 queued 19:36
created

DoctrineDatabase::updateVersion()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 42
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

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