Completed
Push — 6.12 ( 1cff2f )
by Łukasz
64:51
created

deleteTranslationFromContentNames()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 14

Duplication

Lines 24
Ratio 100 %

Importance

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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