Completed
Push — master ( 6fba66...640c23 )
by
unknown
32:41 queued 12:46
created

DoctrineDatabase::loadContentInfoByLocationId()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 13
Ratio 100 %

Importance

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

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

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

class Alien {}

class Dalek extends Alien {}

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

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
100
        $this->languageMaskGenerator = $languageMaskGenerator;
101
    }
102
103
    /**
104
     * Get context definition for external storage layers.
105
     *
106
     * @return array
107
     */
108
    public function getContext()
109
    {
110
        return array(
111
            'identifier' => 'LegacyStorage',
112
            'connection' => $this->dbHandler,
113
        );
114
    }
115
116
    /**
117
     * Inserts a new content object.
118
     *
119
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
120
     * @param mixed $currentVersionNo
121
     *
122
     * @return int ID
123
     */
124
    public function insertContentObject(CreateStruct $struct, $currentVersionNo = 1)
125
    {
126
        $initialLanguageId = !empty($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId;
127
        $initialLanguageCode = $this->languageHandler->load($initialLanguageId)->languageCode;
128
129
        if (isset($struct->name[$initialLanguageCode])) {
130
            $name = $struct->name[$initialLanguageCode];
131
        } else {
132
            $name = '';
133
        }
134
135
        $q = $this->dbHandler->createInsertQuery();
136
        $q->insertInto(
137
            $this->dbHandler->quoteTable('ezcontentobject')
138
        )->set(
139
            $this->dbHandler->quoteColumn('id'),
140
            $this->dbHandler->getAutoIncrementValue('ezcontentobject', 'id')
141
        )->set(
142
            $this->dbHandler->quoteColumn('current_version'),
143
            $q->bindValue($currentVersionNo, null, \PDO::PARAM_INT)
144
        )->set(
145
            $this->dbHandler->quoteColumn('name'),
146
            $q->bindValue($name, null, \PDO::PARAM_STR)
147
        )->set(
148
            $this->dbHandler->quoteColumn('contentclass_id'),
149
            $q->bindValue($struct->typeId, null, \PDO::PARAM_INT)
150
        )->set(
151
            $this->dbHandler->quoteColumn('section_id'),
152
            $q->bindValue($struct->sectionId, null, \PDO::PARAM_INT)
153
        )->set(
154
            $this->dbHandler->quoteColumn('owner_id'),
155
            $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
156
        )->set(
157
            $this->dbHandler->quoteColumn('initial_language_id'),
158
            $q->bindValue($initialLanguageId, null, \PDO::PARAM_INT)
159
        )->set(
160
            $this->dbHandler->quoteColumn('remote_id'),
161
            $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
162
        )->set(
163
            $this->dbHandler->quoteColumn('modified'),
164
            $q->bindValue(0, null, \PDO::PARAM_INT)
165
        )->set(
166
            $this->dbHandler->quoteColumn('published'),
167
            $q->bindValue(0, null, \PDO::PARAM_INT)
168
        )->set(
169
            $this->dbHandler->quoteColumn('status'),
170
            $q->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
171
        )->set(
172
            $this->dbHandler->quoteColumn('language_mask'),
173
            $q->bindValue(
174
                $this->generateLanguageMask(
175
                    $struct->fields,
176
                    $initialLanguageCode,
177
                    $struct->alwaysAvailable
178
                ),
179
                null,
180
                \PDO::PARAM_INT
181
            )
182
        );
183
184
        $q->prepare()->execute();
185
186
        return $this->dbHandler->lastInsertId(
187
            $this->dbHandler->getSequenceName('ezcontentobject', 'id')
188
        );
189
    }
190
191
    /**
192
     * Generates a language mask for $version.
193
     *
194
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
195
     * @param string $initialLanguageCode
196
     * @param bool $alwaysAvailable
197
     *
198
     * @return int
199
     */
200
    protected function generateLanguageMask(array $fields, $initialLanguageCode, $alwaysAvailable)
201
    {
202
        $languages = array($initialLanguageCode => true);
203 View Code Duplication
        foreach ($fields as $field) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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