Completed
Push — sf_multi_get ( f94977...1d4307 )
by André
12:58
created

DoctrineDatabase::internalLoadContentInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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