Completed
Push — 7.5 ( 17c267...9e0292 )
by Łukasz
47:52 queued 28:25
created

DoctrineDatabase::removeReverseFieldRelations()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 5
nop 1
dl 0
loc 50
rs 9.0909
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the DoctrineDatabase Content Gateway class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
10
11
use Doctrine\DBAL\Connection;
12
use Doctrine\DBAL\DBALException;
13
use Doctrine\DBAL\FetchMode;
14
use Doctrine\DBAL\ParameterType;
15
use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
16
use eZ\Publish\Core\Base\Exceptions\BadStateException;
17
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
18
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder;
19
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
20
use eZ\Publish\Core\Persistence\Database\UpdateQuery;
21
use eZ\Publish\Core\Persistence\Database\InsertQuery;
22
use eZ\Publish\Core\Persistence\Database\SelectQuery;
23
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
24
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
25
use eZ\Publish\SPI\Persistence\Content;
26
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
27
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
28
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
29
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
30
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
31
use eZ\Publish\SPI\Persistence\Content\Field;
32
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
33
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
34
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
35
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
36
use DOMXPath;
37
use DOMDocument;
38
use PDO;
39
40
/**
41
 * Doctrine database based content gateway.
42
 */
43
class DoctrineDatabase extends Gateway
44
{
45
    /**
46
     * eZ Doctrine database handler.
47
     *
48
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
49
     * @deprecated Start to use DBAL $connection instead.
50
     */
51
    protected $dbHandler;
52
53
    /**
54
     * The native Doctrine connection.
55
     *
56
     * Meant to be used to transition from eZ/Zeta interface to Doctrine.
57
     *
58
     * @var \Doctrine\DBAL\Connection
59
     */
60
    protected $connection;
61
62
    /**
63
     * Query builder.
64
     *
65
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder
66
     */
67
    protected $queryBuilder;
68
69
    /**
70
     * Caching language handler.
71
     *
72
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler
73
     */
74
    protected $languageHandler;
75
76
    /**
77
     * Language mask generator.
78
     *
79
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
80
     */
81
    protected $languageMaskGenerator;
82
83
    /**
84
     * Creates a new gateway based on $db.
85
     *
86
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db
87
     * @param \Doctrine\DBAL\Connection $connection
88
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder $queryBuilder
89
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
90
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
91
     */
92
    public function __construct(
93
        DatabaseHandler $db,
94
        Connection $connection,
95
        QueryBuilder $queryBuilder,
96
        LanguageHandler $languageHandler,
97
        LanguageMaskGenerator $languageMaskGenerator
98
    ) {
99
        $this->dbHandler = $db;
100
        $this->connection = $connection;
101
        $this->queryBuilder = $queryBuilder;
102
        $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...
103
        $this->languageMaskGenerator = $languageMaskGenerator;
104
    }
105
106
    /**
107
     * Get context definition for external storage layers.
108
     *
109
     * @return array
110
     */
111
    public function getContext()
112
    {
113
        return [
114
            'identifier' => 'LegacyStorage',
115
            'connection' => $this->dbHandler,
116
        ];
117
    }
118
119
    /**
120
     * Inserts a new content object.
121
     *
122
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
123
     * @param mixed $currentVersionNo
124
     *
125
     * @return int ID
126
     */
127
    public function insertContentObject(CreateStruct $struct, $currentVersionNo = 1)
128
    {
129
        $initialLanguageId = !empty($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId;
130
        $initialLanguageCode = $this->languageHandler->load($initialLanguageId)->languageCode;
131
132
        if (isset($struct->name[$initialLanguageCode])) {
133
            $name = $struct->name[$initialLanguageCode];
134
        } else {
135
            $name = '';
136
        }
137
138
        $q = $this->dbHandler->createInsertQuery();
139
        $q->insertInto(
140
            $this->dbHandler->quoteTable('ezcontentobject')
141
        )->set(
142
            $this->dbHandler->quoteColumn('id'),
143
            $this->dbHandler->getAutoIncrementValue('ezcontentobject', 'id')
144
        )->set(
145
            $this->dbHandler->quoteColumn('current_version'),
146
            $q->bindValue($currentVersionNo, null, \PDO::PARAM_INT)
147
        )->set(
148
            $this->dbHandler->quoteColumn('name'),
149
            $q->bindValue($name, null, \PDO::PARAM_STR)
150
        )->set(
151
            $this->dbHandler->quoteColumn('contentclass_id'),
152
            $q->bindValue($struct->typeId, null, \PDO::PARAM_INT)
153
        )->set(
154
            $this->dbHandler->quoteColumn('section_id'),
155
            $q->bindValue($struct->sectionId, null, \PDO::PARAM_INT)
156
        )->set(
157
            $this->dbHandler->quoteColumn('owner_id'),
158
            $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
159
        )->set(
160
            $this->dbHandler->quoteColumn('initial_language_id'),
161
            $q->bindValue($initialLanguageId, null, \PDO::PARAM_INT)
162
        )->set(
163
            $this->dbHandler->quoteColumn('remote_id'),
164
            $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
165
        )->set(
166
            $this->dbHandler->quoteColumn('modified'),
167
            $q->bindValue(0, null, \PDO::PARAM_INT)
168
        )->set(
169
            $this->dbHandler->quoteColumn('published'),
170
            $q->bindValue(0, null, \PDO::PARAM_INT)
171
        )->set(
172
            $this->dbHandler->quoteColumn('status'),
173
            $q->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
174
        )->set(
175
            $this->dbHandler->quoteColumn('language_mask'),
176
            $q->bindValue(
177
                $this->generateLanguageMask(
178
                    $struct->fields,
179
                    $initialLanguageCode,
180
                    $struct->alwaysAvailable
181
                ),
182
                null,
183
                \PDO::PARAM_INT
184
            )
185
        );
186
187
        $q->prepare()->execute();
188
189
        return $this->dbHandler->lastInsertId(
190
            $this->dbHandler->getSequenceName('ezcontentobject', 'id')
191
        );
192
    }
193
194
    /**
195
     * Generates a language mask for $fields.
196
     *
197
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
198
     * @param string $initialLanguageCode
199
     * @param bool $isAlwaysAvailable
200
     *
201
     * @return int
202
     */
203
    protected function generateLanguageMask(array $fields, string $initialLanguageCode, bool $isAlwaysAvailable): int
204
    {
205
        $languages = [$initialLanguageCode => true];
206
        foreach ($fields as $field) {
207
            if (isset($languages[$field->languageCode])) {
208
                continue;
209
            }
210
211
            $languages[$field->languageCode] = true;
212
        }
213
214
        return $this->languageMaskGenerator->generateLanguageMaskFromLanguageCodes(array_keys($languages), $isAlwaysAvailable);
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
            $mask = $this->languageMaskGenerator->generateLanguageMaskFromLanguageCodes(
333
                $prePublishVersionInfo->languageCodes,
334
                $struct->alwaysAvailable ?? $prePublishVersionInfo->contentInfo->alwaysAvailable
335
            );
336
337
            $q->set(
338
                $this->dbHandler->quoteColumn('language_mask'),
339
                $q->bindValue($mask, null, \PDO::PARAM_INT)
340
            );
341
        }
342
        if (isset($struct->isHidden)) {
343
            $q->set(
344
                $this->dbHandler->quoteColumn('is_hidden'),
345
                $q->bindValue($struct->isHidden, null, \PDO::PARAM_BOOL)
346
            );
347
        }
348
        $q->where(
349
            $q->expr->eq(
350
                $this->dbHandler->quoteColumn('id'),
351
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
352
            )
353
        );
354
        $q->prepare()->execute();
355
356
        // Handle alwaysAvailable flag update separately as it's a more complex task and has impact on several tables
357
        if (isset($struct->alwaysAvailable) || isset($struct->mainLanguageId)) {
358
            $this->updateAlwaysAvailableFlag($contentId, $struct->alwaysAvailable);
359
        }
360
    }
361
362
    /**
363
     * Updates version $versionNo for content identified by $contentId, in respect to $struct.
364
     *
365
     * @param int $contentId
366
     * @param int $versionNo
367
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $struct
368
     */
369
    public function updateVersion($contentId, $versionNo, UpdateStruct $struct)
370
    {
371
        $q = $this->dbHandler->createUpdateQuery();
372
        $q->update(
373
            $this->dbHandler->quoteTable('ezcontentobject_version')
374
        )->set(
375
            $this->dbHandler->quoteColumn('creator_id'),
376
            $q->bindValue($struct->creatorId, null, \PDO::PARAM_INT)
377
        )->set(
378
            $this->dbHandler->quoteColumn('modified'),
379
            $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
380
        )->set(
381
            $this->dbHandler->quoteColumn('initial_language_id'),
382
            $q->bindValue($struct->initialLanguageId, null, \PDO::PARAM_INT)
383
        )->set(
384
            $this->dbHandler->quoteColumn('language_mask'),
385
            $q->expr->bitOr(
386
                $this->dbHandler->quoteColumn('language_mask'),
387
                $q->bindValue(
388
                    $this->generateLanguageMask(
389
                        $struct->fields,
390
                        $this->languageHandler->load($struct->initialLanguageId)->languageCode,
391
                        false
392
                    ),
393
                    null,
394
                    \PDO::PARAM_INT
395
                )
396
            )
397
        )->where(
398
            $q->expr->lAnd(
399
                $q->expr->eq(
400
                    $this->dbHandler->quoteColumn('contentobject_id'),
401
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
402
                ),
403
                $q->expr->eq(
404
                    $this->dbHandler->quoteColumn('version'),
405
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
406
                )
407
            )
408
        );
409
        $q->prepare()->execute();
410
    }
411
412
    /**
413
     * Updates "always available" flag for Content identified by $contentId, in respect to
414
     * Content's current main language and optionally new $alwaysAvailable state.
415
     *
416
     * @param int $contentId
417
     * @param bool|null $alwaysAvailable New "always available" value or null if not defined
418
     */
419
    public function updateAlwaysAvailableFlag($contentId, $alwaysAvailable = null)
420
    {
421
        // We will need to know some info on the current language mask to update the flag
422
        // everywhere needed
423
        $contentInfoRow = $this->loadContentInfo($contentId);
424
        if (!isset($alwaysAvailable)) {
425
            $alwaysAvailable = (bool)$contentInfoRow['language_mask'] & 1;
426
        }
427
428
        /** @var $q \eZ\Publish\Core\Persistence\Database\UpdateQuery */
429
        $q = $this->dbHandler->createUpdateQuery();
430
        $q
431
            ->update($this->dbHandler->quoteTable('ezcontentobject'))
432
            ->set(
433
                $this->dbHandler->quoteColumn('language_mask'),
434
                $alwaysAvailable ?
435
                    $q->expr->bitOr($this->dbHandler->quoteColumn('language_mask'), 1) :
436
                    $q->expr->bitAnd($this->dbHandler->quoteColumn('language_mask'), -2)
437
            )
438
            ->where(
439
                $q->expr->eq(
440
                    $this->dbHandler->quoteColumn('id'),
441
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
442
                )
443
            );
444
        $q->prepare()->execute();
445
446
        // Now we need to update ezcontentobject_name
447
        /** @var $qName \eZ\Publish\Core\Persistence\Database\UpdateQuery */
448
        $qName = $this->dbHandler->createUpdateQuery();
449
        $qName
450
            ->update($this->dbHandler->quoteTable('ezcontentobject_name'))
451
            ->set(
452
                $this->dbHandler->quoteColumn('language_id'),
453
                $alwaysAvailable ?
454
                    $qName->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
455
                    $qName->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
456
            )
457
            ->where(
458
                $qName->expr->lAnd(
459
                    $qName->expr->eq(
460
                        $this->dbHandler->quoteColumn('contentobject_id'),
461
                        $qName->bindValue($contentId, null, \PDO::PARAM_INT)
462
                    ),
463
                    $qName->expr->eq(
464
                        $this->dbHandler->quoteColumn('content_version'),
465
                        $qName->bindValue(
466
                            $contentInfoRow['current_version'],
467
                            null,
468
                            \PDO::PARAM_INT
469
                        )
470
                    )
471
                )
472
            );
473
        $qName->prepare()->execute();
474
475
        // Now update ezcontentobject_attribute for current version
476
        // Create update query that will be reused
477
        /** @var $qAttr \eZ\Publish\Core\Persistence\Database\UpdateQuery */
478
        $qAttr = $this->dbHandler->createUpdateQuery();
479
        $qAttr
480
            ->update($this->dbHandler->quoteTable('ezcontentobject_attribute'))
481
            ->where(
482
                $qAttr->expr->lAnd(
483
                    $qAttr->expr->eq(
484
                        $this->dbHandler->quoteColumn('contentobject_id'),
485
                        $qAttr->bindValue($contentId, null, \PDO::PARAM_INT)
486
                    ),
487
                    $qAttr->expr->eq(
488
                        $this->dbHandler->quoteColumn('version'),
489
                        $qAttr->bindValue(
490
                            $contentInfoRow['current_version'],
491
                            null,
492
                            \PDO::PARAM_INT
493
                        )
494
                    )
495
                )
496
            );
497
498
        // If there is only a single language, update all fields and return
499
        if (!$this->languageMaskGenerator->isLanguageMaskComposite($contentInfoRow['language_mask'])) {
500
            $qAttr->set(
501
                $this->dbHandler->quoteColumn('language_id'),
502
                $alwaysAvailable ?
503
                    $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
504
                    $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
505
            );
506
            $qAttr->prepare()->execute();
507
508
            return;
509
        }
510
511
        // Otherwise:
512
        // 1. Remove always available flag on all fields
513
        $qAttr->set(
514
            $this->dbHandler->quoteColumn('language_id'),
515
            $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
516
        );
517
        $qAttr->prepare()->execute();
518
519
        // 2. If Content is always available set the flag only on fields in main language
520
        if ($alwaysAvailable) {
521
            $qAttr->set(
522
                $this->dbHandler->quoteColumn('language_id'),
523
                $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1)
524
            );
525
            $qAttr->where(
526
                $qAttr->expr->gt(
527
                    $qAttr->expr->bitAnd(
528
                        $this->dbHandler->quoteColumn('language_id'),
529
                        $qAttr->bindValue($contentInfoRow['initial_language_id'], null, PDO::PARAM_INT)
530
                    ),
531
                    $qAttr->bindValue(0, null, PDO::PARAM_INT)
532
                )
533
            );
534
            $qAttr->prepare()->execute();
535
        }
536
    }
537
538
    /**
539
     * Sets the status of the version identified by $contentId and $version to $status.
540
     *
541
     * The $status can be one of STATUS_DRAFT, STATUS_PUBLISHED, STATUS_ARCHIVED
542
     *
543
     * @param int $contentId
544
     * @param int $version
545
     * @param int $status
546
     *
547
     * @return bool
548
     */
549
    public function setStatus($contentId, $version, $status)
550
    {
551
        $q = $this->dbHandler->createUpdateQuery();
552
        $q->update(
553
            $this->dbHandler->quoteTable('ezcontentobject_version')
554
        )->set(
555
            $this->dbHandler->quoteColumn('status'),
556
            $q->bindValue($status, null, \PDO::PARAM_INT)
557
        )->set(
558
            $this->dbHandler->quoteColumn('modified'),
559
            $q->bindValue(time(), null, \PDO::PARAM_INT)
560
        )->where(
561
            $q->expr->lAnd(
562
                $q->expr->eq(
563
                    $this->dbHandler->quoteColumn('contentobject_id'),
564
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
565
                ),
566
                $q->expr->eq(
567
                    $this->dbHandler->quoteColumn('version'),
568
                    $q->bindValue($version, null, \PDO::PARAM_INT)
569
                )
570
            )
571
        );
572
        $statement = $q->prepare();
573
        $statement->execute();
574
575
        if ((bool)$statement->rowCount() === false) {
576
            return false;
577
        }
578
579
        if ($status !== APIVersionInfo::STATUS_PUBLISHED) {
580
            return true;
581
        }
582
583
        // If the version's status is PUBLISHED, we set the content to published status as well
584
        $q = $this->dbHandler->createUpdateQuery();
585
        $q->update(
586
            $this->dbHandler->quoteTable('ezcontentobject')
587
        )->set(
588
            $this->dbHandler->quoteColumn('status'),
589
            $q->bindValue(ContentInfo::STATUS_PUBLISHED, null, \PDO::PARAM_INT)
590
        )->set(
591
            $this->dbHandler->quoteColumn('current_version'),
592
            $q->bindValue($version, null, \PDO::PARAM_INT)
593
        )->where(
594
            $q->expr->eq(
595
                $this->dbHandler->quoteColumn('id'),
596
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
597
            )
598
        );
599
        $statement = $q->prepare();
600
        $statement->execute();
601
602
        return (bool)$statement->rowCount();
603
    }
604
605
    public function setPublishedStatus(int $contentId, int $versionNo): void
606
    {
607
        $query = $this->getSetVersionStatusQuery(
608
            $contentId,
609
            $versionNo,
610
            VersionInfo::STATUS_PUBLISHED
611
        );
612
613
        /* this part allows set status `published` only if there is no other published version of the content */
614
        $notExistPublishedVersion = <<< HEREDOC
615
            NOT EXISTS (
616
                SELECT 1 FROM (
617
                    SELECT 1 FROM ezcontentobject_version  WHERE contentobject_id = :contentId AND status = :status 
618
                ) as V
619
            )
620
HEREDOC;
621
622
        $query->andWhere($notExistPublishedVersion);
623
        if (0 === $query->execute()) {
624
            throw new BadStateException(
625
                '$contentId', "Someone just published another Version of the Content item {$contentId}"
626
            );
627
        }
628
        $this->markContentAsPublished($contentId, $versionNo);
629
    }
630
631
    private function getSetVersionStatusQuery(
632
        int $contentId,
633
        int $versionNo,
634
        int $versionStatus
635
    ): DoctrineQueryBuilder {
636
        $query = $this->connection->createQueryBuilder();
637
        $query
638
            ->update('ezcontentobject_version')
639
            ->set('status', ':status')
640
            ->set('modified', ':modified')
641
            ->where('contentobject_id = :contentId')
642
            ->andWhere('version = :versionNo')
643
            ->setParameter('status', $versionStatus, ParameterType::INTEGER)
644
            ->setParameter('modified', time(), ParameterType::INTEGER)
645
            ->setParameter('contentId', $contentId, ParameterType::INTEGER)
646
            ->setParameter('versionNo', $versionNo, ParameterType::INTEGER);
647
648
        return $query;
649
    }
650
651
    private function markContentAsPublished(int $contentId, int $versionNo): void
652
    {
653
        $query = $this->connection->createQueryBuilder();
654
        $query
655
            ->update('ezcontentobject')
656
            ->set('status', ':status')
657
            ->set('current_version', ':versionNo')
658
            ->where('id =:contentId')
659
            ->setParameter('status', ContentInfo::STATUS_PUBLISHED, ParameterType::INTEGER)
660
            ->setParameter('versionNo', $versionNo, ParameterType::INTEGER)
661
            ->setParameter('contentId', $contentId, ParameterType::INTEGER);
662
        $query->execute();
663
    }
664
665
    /**
666
     * Inserts a new field.
667
     *
668
     * Only used when a new field is created (i.e. a new object or a field in a
669
     * new language!). After that, field IDs need to stay the same, only the
670
     * version number changes.
671
     *
672
     * @param \eZ\Publish\SPI\Persistence\Content $content
673
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
674
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
675
     *
676
     * @return int ID
677
     */
678
    public function insertNewField(Content $content, Field $field, StorageFieldValue $value)
679
    {
680
        $q = $this->dbHandler->createInsertQuery();
681
682
        $this->setInsertFieldValues($q, $content, $field, $value);
683
684
        // Insert with auto increment ID
685
        $q->set(
686
            $this->dbHandler->quoteColumn('id'),
687
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_attribute', 'id')
688
        );
689
690
        $q->prepare()->execute();
691
692
        return $this->dbHandler->lastInsertId(
693
            $this->dbHandler->getSequenceName('ezcontentobject_attribute', 'id')
694
        );
695
    }
696
697
    /**
698
     * Inserts an existing field.
699
     *
700
     * Used to insert a field with an exsting ID but a new version number.
701
     *
702
     * @param Content $content
703
     * @param Field $field
704
     * @param StorageFieldValue $value
705
     */
706
    public function insertExistingField(Content $content, Field $field, StorageFieldValue $value)
707
    {
708
        $q = $this->dbHandler->createInsertQuery();
709
710
        $this->setInsertFieldValues($q, $content, $field, $value);
711
712
        $q->set(
713
            $this->dbHandler->quoteColumn('id'),
714
            $q->bindValue($field->id, null, \PDO::PARAM_INT)
715
        );
716
717
        $q->prepare()->execute();
718
    }
719
720
    /**
721
     * Sets field (ezcontentobject_attribute) values to the given query.
722
     *
723
     * @param \eZ\Publish\Core\Persistence\Database\InsertQuery $q
724
     * @param Content $content
725
     * @param Field $field
726
     * @param StorageFieldValue $value
727
     */
728
    protected function setInsertFieldValues(InsertQuery $q, Content $content, Field $field, StorageFieldValue $value)
729
    {
730
        $q->insertInto(
731
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
732
        )->set(
733
            $this->dbHandler->quoteColumn('contentobject_id'),
734
            $q->bindValue($content->versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
735
        )->set(
736
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
737
            $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
738
        )->set(
739
            $this->dbHandler->quoteColumn('data_type_string'),
740
            $q->bindValue($field->type)
741
        )->set(
742
            $this->dbHandler->quoteColumn('language_code'),
743
            $q->bindValue($field->languageCode)
744
        )->set(
745
            $this->dbHandler->quoteColumn('version'),
746
            $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
747
        )->set(
748
            $this->dbHandler->quoteColumn('data_float'),
749
            $q->bindValue($value->dataFloat)
750
        )->set(
751
            $this->dbHandler->quoteColumn('data_int'),
752
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
753
        )->set(
754
            $this->dbHandler->quoteColumn('data_text'),
755
            $q->bindValue($value->dataText)
756
        )->set(
757
            $this->dbHandler->quoteColumn('sort_key_int'),
758
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
759
        )->set(
760
            $this->dbHandler->quoteColumn('sort_key_string'),
761
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
762
        )->set(
763
            $this->dbHandler->quoteColumn('language_id'),
764
            $q->bindValue(
765
                $this->languageMaskGenerator->generateLanguageIndicator(
766
                    $field->languageCode,
767
                    $this->isLanguageAlwaysAvailable($content, $field->languageCode)
768
                ),
769
                null,
770
                \PDO::PARAM_INT
771
            )
772
        );
773
    }
774
775
    /**
776
     * Checks if $languageCode is always available in $content.
777
     *
778
     * @param \eZ\Publish\SPI\Persistence\Content $content
779
     * @param string $languageCode
780
     *
781
     * @return bool
782
     */
783
    protected function isLanguageAlwaysAvailable(Content $content, $languageCode)
784
    {
785
        return
786
            $content->versionInfo->contentInfo->alwaysAvailable &&
787
            $content->versionInfo->contentInfo->mainLanguageCode === $languageCode
788
        ;
789
    }
790
791
    /**
792
     * Updates an existing field.
793
     *
794
     * @param Field $field
795
     * @param StorageFieldValue $value
796
     */
797
    public function updateField(Field $field, StorageFieldValue $value)
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('id'),
807
                    $q->bindValue($field->id, null, \PDO::PARAM_INT)
808
                ),
809
                $q->expr->eq(
810
                    $this->dbHandler->quoteColumn('version'),
811
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
812
                )
813
            )
814
        );
815
        $q->prepare()->execute();
816
    }
817
818
    /**
819
     * Sets update fields for $value on $q.
820
     *
821
     * @param \eZ\Publish\Core\Persistence\Database\UpdateQuery $q
822
     * @param StorageFieldValue $value
823
     */
824
    protected function setFieldUpdateValues(UpdateQuery $q, StorageFieldValue $value)
825
    {
826
        $q->update(
827
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
828
        )->set(
829
            $this->dbHandler->quoteColumn('data_float'),
830
            $q->bindValue($value->dataFloat)
831
        )->set(
832
            $this->dbHandler->quoteColumn('data_int'),
833
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
834
        )->set(
835
            $this->dbHandler->quoteColumn('data_text'),
836
            $q->bindValue($value->dataText)
837
        )->set(
838
            $this->dbHandler->quoteColumn('sort_key_int'),
839
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
840
        )->set(
841
            $this->dbHandler->quoteColumn('sort_key_string'),
842
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
843
        );
844
    }
845
846
    /**
847
     * Updates an existing, non-translatable field.
848
     *
849
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
850
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
851
     * @param int $contentId
852
     */
853
    public function updateNonTranslatableField(
854
        Field $field,
855
        StorageFieldValue $value,
856
        $contentId
857
    ) {
858
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
859
        // cannot change on update
860
        $q = $this->dbHandler->createUpdateQuery();
861
        $this->setFieldUpdateValues($q, $value);
862
        $q->where(
863
            $q->expr->lAnd(
864
                $q->expr->eq(
865
                    $this->dbHandler->quoteColumn('contentclassattribute_id'),
866
                    $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
867
                ),
868
                $q->expr->eq(
869
                    $this->dbHandler->quoteColumn('contentobject_id'),
870
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
871
                ),
872
                $q->expr->eq(
873
                    $this->dbHandler->quoteColumn('version'),
874
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
875
                )
876
            )
877
        );
878
        $q->prepare()->execute();
879
    }
880
881
    /**
882
     * {@inheritdoc}
883
     */
884
    public function load($contentId, $version = null, array $translations = null)
885
    {
886
        return $this->internalLoadContent([$contentId], $version, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 884 can also be of type array; however, eZ\Publish\Core\Persiste...::internalLoadContent() does only seem to accept null|array<integer,string>, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
887
    }
888
889
    /**
890
     * {@inheritdoc}
891
     */
892
    public function loadContentList(array $contentIds, array $translations = null): array
893
    {
894
        return $this->internalLoadContent($contentIds, null, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 892 can also be of type array; however, eZ\Publish\Core\Persiste...::internalLoadContent() does only seem to accept null|array<integer,string>, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
895
    }
896
897
    /**
898
     * @see load(), loadContentList()
899
     *
900
     * @param array $contentIds
901
     * @param int|null $version
902
     * @param string[]|null $translations
903
     *
904
     * @return array
905
     */
906
    private function internalLoadContent(array $contentIds, int $version = null, array $translations = null): array
907
    {
908
        $queryBuilder = $this->connection->createQueryBuilder();
909
        $expr = $queryBuilder->expr();
910
        $queryBuilder
911
            ->select(
912
                'c.id AS ezcontentobject_id',
913
                'c.contentclass_id AS ezcontentobject_contentclass_id',
914
                'c.section_id AS ezcontentobject_section_id',
915
                'c.owner_id AS ezcontentobject_owner_id',
916
                'c.remote_id AS ezcontentobject_remote_id',
917
                'c.current_version AS ezcontentobject_current_version',
918
                'c.initial_language_id AS ezcontentobject_initial_language_id',
919
                'c.modified AS ezcontentobject_modified',
920
                'c.published AS ezcontentobject_published',
921
                'c.status AS ezcontentobject_status',
922
                'c.name AS ezcontentobject_name',
923
                'c.language_mask AS ezcontentobject_language_mask',
924
                'c.is_hidden AS ezcontentobject_is_hidden',
925
                'v.id AS ezcontentobject_version_id',
926
                'v.version AS ezcontentobject_version_version',
927
                'v.modified AS ezcontentobject_version_modified',
928
                'v.creator_id AS ezcontentobject_version_creator_id',
929
                'v.created AS ezcontentobject_version_created',
930
                'v.status AS ezcontentobject_version_status',
931
                'v.language_mask AS ezcontentobject_version_language_mask',
932
                'v.initial_language_id AS ezcontentobject_version_initial_language_id',
933
                'a.id AS ezcontentobject_attribute_id',
934
                'a.contentclassattribute_id AS ezcontentobject_attribute_contentclassattribute_id',
935
                'a.data_type_string AS ezcontentobject_attribute_data_type_string',
936
                'a.language_code AS ezcontentobject_attribute_language_code',
937
                'a.language_id AS ezcontentobject_attribute_language_id',
938
                'a.data_float AS ezcontentobject_attribute_data_float',
939
                'a.data_int AS ezcontentobject_attribute_data_int',
940
                'a.data_text AS ezcontentobject_attribute_data_text',
941
                'a.sort_key_int AS ezcontentobject_attribute_sort_key_int',
942
                'a.sort_key_string AS ezcontentobject_attribute_sort_key_string',
943
                't.main_node_id AS ezcontentobject_tree_main_node_id'
944
            )
945
            ->from('ezcontentobject', 'c')
946
            ->innerJoin(
947
                'c',
948
                'ezcontentobject_version',
949
                'v',
950
                $expr->andX(
951
                    $expr->eq('c.id', 'v.contentobject_id'),
952
                    $expr->eq('v.version', $version ?? 'c.current_version')
953
                )
954
            )
955
            ->innerJoin(
956
                'v',
957
                'ezcontentobject_attribute',
958
                'a',
959
                $expr->andX(
960
                    $expr->eq('v.contentobject_id', 'a.contentobject_id'),
961
                    $expr->eq('v.version', 'a.version')
962
                )
963
            )
964
            ->leftJoin(
965
                'c',
966
                'ezcontentobject_tree',
967
                't',
968
                $expr->andX(
969
                    $expr->eq('c.id', 't.contentobject_id'),
970
                    $expr->eq('t.node_id', 't.main_node_id')
971
                )
972
            );
973
974
        $queryBuilder->where(
975
            $expr->in(
976
                'c.id',
977
                $queryBuilder->createNamedParameter($contentIds, Connection::PARAM_INT_ARRAY)
978
            )
979
        );
980
981
        if (!empty($translations)) {
982
            $queryBuilder->andWhere(
983
                $expr->in(
984
                    'a.language_code',
985
                    $queryBuilder->createNamedParameter($translations, Connection::PARAM_STR_ARRAY)
986
                )
987
            );
988
        }
989
990
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
991
    }
992
993
    /**
994
     * Get query builder to load Content Info data.
995
     *
996
     * @see loadContentInfo(), loadContentInfoByRemoteId(), loadContentInfoList(), loadContentInfoByLocationId()
997
     *
998
     * @param bool $joinMainLocation
999
     *
1000
     * @return \Doctrine\DBAL\Query\QueryBuilder
1001
     */
1002
    private function createLoadContentInfoQueryBuilder(bool $joinMainLocation = true): DoctrineQueryBuilder
1003
    {
1004
        $queryBuilder = $this->connection->createQueryBuilder();
1005
        $expr = $queryBuilder->expr();
1006
1007
        $joinCondition = $expr->eq('c.id', 't.contentobject_id');
1008
        if ($joinMainLocation) {
1009
            // wrap join condition with AND operator and join by a Main Location
1010
            $joinCondition = $expr->andX(
1011
                $joinCondition,
1012
                $expr->eq('t.node_id', 't.main_node_id')
1013
            );
1014
        }
1015
1016
        $queryBuilder
1017
            ->select('c.*', 't.main_node_id AS ezcontentobject_tree_main_node_id')
1018
            ->from('ezcontentobject', 'c')
1019
            ->leftJoin(
1020
                'c',
1021
                'ezcontentobject_tree',
1022
                't',
1023
                $joinCondition
0 ignored issues
show
Bug introduced by
It seems like $joinCondition defined by $expr->andX($joinConditi...id', 't.main_node_id')) on line 1010 can also be of type object<Doctrine\DBAL\Que...on\CompositeExpression>; however, Doctrine\DBAL\Query\QueryBuilder::leftJoin() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1024
            );
1025
1026
        return $queryBuilder;
1027
    }
1028
1029
    /**
1030
     * Loads info for content identified by $contentId.
1031
     * Will basically return a hash containing all field values for ezcontentobject table plus some additional keys:
1032
     *  - always_available => Boolean indicating if content's language mask contains alwaysAvailable bit field
1033
     *  - main_language_code => Language code for main (initial) language. E.g. "eng-GB".
1034
     *
1035
     * @param int $contentId
1036
     *
1037
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1038
     *
1039
     * @return array
1040
     */
1041
    public function loadContentInfo($contentId)
1042
    {
1043
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1044
        $queryBuilder
1045
            ->where('c.id = :id')
1046
            ->setParameter('id', $contentId, ParameterType::INTEGER);
1047
1048
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1049
        if (empty($results)) {
1050
            throw new NotFound('content', "id: $contentId");
1051
        }
1052
1053
        return $results[0];
1054
    }
1055
1056
    public function loadContentInfoList(array $contentIds)
1057
    {
1058
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1059
        $queryBuilder
1060
            ->where('c.id IN (:ids)')
1061
            ->setParameter('ids', $contentIds, Connection::PARAM_INT_ARRAY);
1062
1063
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1064
    }
1065
1066
    /**
1067
     * Loads info for a content object identified by its remote ID.
1068
     *
1069
     * Returns an array with the relevant data.
1070
     *
1071
     * @param mixed $remoteId
1072
     *
1073
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1074
     *
1075
     * @return array
1076
     */
1077
    public function loadContentInfoByRemoteId($remoteId)
1078
    {
1079
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1080
        $queryBuilder
1081
            ->where('c.remote_id = :id')
1082
            ->setParameter('id', $remoteId, ParameterType::STRING);
1083
1084
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1085
        if (empty($results)) {
1086
            throw new NotFound('content', "remote_id: $remoteId");
1087
        }
1088
1089
        return $results[0];
1090
    }
1091
1092
    /**
1093
     * Loads info for a content object identified by its location ID (node ID).
1094
     *
1095
     * Returns an array with the relevant data.
1096
     *
1097
     * @param int $locationId
1098
     *
1099
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1100
     *
1101
     * @return array
1102
     */
1103
    public function loadContentInfoByLocationId($locationId)
1104
    {
1105
        $queryBuilder = $this->createLoadContentInfoQueryBuilder(false);
1106
        $queryBuilder
1107
            ->where('t.node_id = :id')
1108
            ->setParameter('id', $locationId, ParameterType::INTEGER);
1109
1110
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1111
        if (empty($results)) {
1112
            throw new NotFound('content', "node_id: $locationId");
1113
        }
1114
1115
        return $results[0];
1116
    }
1117
1118
    /**
1119
     * Loads version info for content identified by $contentId and $versionNo.
1120
     * Will basically return a hash containing all field values from ezcontentobject_version table plus following keys:
1121
     *  - names => Hash of content object names. Key is the language code, value is the name.
1122
     *  - 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.
1123
     *  - initial_language_code => Language code for initial language in this version.
1124
     *
1125
     * @param int $contentId
1126
     * @param int|null $versionNo
1127
     *
1128
     * @return array
1129
     */
1130
    public function loadVersionInfo($contentId, $versionNo = null)
1131
    {
1132
        $queryBuilder = $this->queryBuilder->createVersionInfoQueryBuilder($versionNo);
1133
        $queryBuilder->where(
1134
            $queryBuilder->expr()->eq(
1135
                'c.id',
1136
                $queryBuilder->createNamedParameter($contentId, PDO::PARAM_INT)
1137
            )
1138
        );
1139
1140
        return $queryBuilder->execute()->fetchAll(PDO::FETCH_ASSOC);
1141
    }
1142
1143
    /**
1144
     * Returns the number of all versions with given status created by the given $userId for content which is not in Trash.
1145
     *
1146
     * @param int $userId
1147
     * @param int $status
1148
     *
1149
     * @return int
1150
     */
1151
    public function countVersionsForUser(int $userId, int $status = VersionInfo::STATUS_DRAFT): int
1152
    {
1153
        $platform = $this->connection->getDatabasePlatform();
1154
        $query = $this->connection->createQueryBuilder();
1155
        $expr = $query->expr();
1156
        $query
1157
            ->select($platform->getCountExpression('v.id'))
1158
            ->from('ezcontentobject_version', 'v')
1159
            ->innerJoin(
1160
                'v',
1161
                'ezcontentobject',
1162
                'c',
1163
                $expr->andX(
1164
                    $expr->eq('c.id', 'v.contentobject_id'),
1165
                    $expr->neq('c.status', ContentInfo::STATUS_TRASHED)
1166
                )
1167
            )
1168
            ->where(
1169
                $query->expr()->andX(
1170
                    $query->expr()->eq('v.status', ':status'),
1171
                    $query->expr()->eq('v.creator_id', ':user_id')
1172
                )
1173
            )
1174
            ->setParameter(':status', $status, \PDO::PARAM_INT)
1175
            ->setParameter(':user_id', $userId, \PDO::PARAM_INT);
1176
1177
        return (int) $query->execute()->fetchColumn();
1178
    }
1179
1180
    /**
1181
     * Returns data for all versions with given status created by the given $userId.
1182
     *
1183
     * @param int $userId
1184
     * @param int $status
1185
     *
1186
     * @return string[][]
1187
     */
1188
    public function listVersionsForUser($userId, $status = VersionInfo::STATUS_DRAFT)
1189
    {
1190
        $query = $this->queryBuilder->createVersionInfoFindQuery();
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Persiste...eVersionInfoFindQuery() has been deprecated with message: Move to Doctrine based query builder {@see createVersionInfoQueryBuilder}.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1191
        $query->where(
1192
            $query->expr->lAnd(
1193
                $query->expr->eq(
1194
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1195
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1196
                ),
1197
                $query->expr->eq(
1198
                    $this->dbHandler->quoteColumn('creator_id', 'ezcontentobject_version'),
1199
                    $query->bindValue($userId, null, \PDO::PARAM_INT)
1200
                )
1201
            )
1202
        );
1203
1204
        return $this->listVersionsHelper($query);
1205
    }
1206
1207
    /**
1208
     * {@inheritdoc}
1209
     */
1210
    public function loadVersionsForUser($userId, $status = VersionInfo::STATUS_DRAFT, int $offset = 0, int $limit = -1): array
1211
    {
1212
        $query = $this->createVersionInfoFindQueryBuilder();
1213
        $expr = $query->expr();
1214
        $query->where(
1215
            $expr->andX(
1216
                $expr->eq('v.status', ':status'),
1217
                $expr->eq('v.creator_id', ':user_id'),
1218
                $expr->neq('c.status', ContentInfo::STATUS_TRASHED)
1219
            )
1220
        )
1221
        ->setFirstResult($offset)
1222
        ->setParameter(':status', $status, \PDO::PARAM_INT)
1223
        ->setParameter(':user_id', $userId, \PDO::PARAM_INT);
1224
1225
        if ($limit > 0) {
1226
            $query->setMaxResults($limit);
1227
        }
1228
1229
        $query->orderBy('v.modified', 'DESC');
1230
        $query->addOrderBy('v.id', 'DESC');
1231
1232
        return $query->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1233
    }
1234
1235
    /**
1236
     * Returns all version data for the given $contentId, optionally filtered by status.
1237
     *
1238
     * Result is returned with oldest version first (using version id as it has index and is auto increment).
1239
     *
1240
     * @param mixed $contentId
1241
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
1242
     * @param int $limit Limit for items returned, -1 means none.
1243
     *
1244
     * @return string[][]
1245
     */
1246
    public function listVersions($contentId, $status = null, $limit = -1)
1247
    {
1248
        $query = $this->queryBuilder->createVersionInfoFindQuery();
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Persiste...eVersionInfoFindQuery() has been deprecated with message: Move to Doctrine based query builder {@see createVersionInfoQueryBuilder}.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1249
1250
        $filter = $query->expr->eq(
1251
            $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1252
            $query->bindValue($contentId, null, \PDO::PARAM_INT)
1253
        );
1254
1255
        if ($status !== null) {
1256
            $filter = $query->expr->lAnd(
1257
                $filter,
1258
                $query->expr->eq(
1259
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1260
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1261
                )
1262
            );
1263
        }
1264
1265
        $query->where($filter);
1266
1267
        if ($limit > 0) {
1268
            $query->limit($limit);
1269
        }
1270
1271
        return $this->listVersionsHelper($query);
1272
    }
1273
1274
    /**
1275
     * Helper for {@see listVersions()} and {@see listVersionsForUser()} that filters duplicates
1276
     * that are the result of the cartesian product performed by createVersionInfoFindQuery().
1277
     *
1278
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
1279
     *
1280
     * @return string[][]
1281
     */
1282
    private function listVersionsHelper(SelectQuery $query)
1283
    {
1284
        $query->orderBy(
1285
            $this->dbHandler->quoteColumn('id', 'ezcontentobject_version')
1286
        );
1287
1288
        $statement = $query->prepare();
1289
        $statement->execute();
1290
1291
        $results = [];
1292
        $previousId = null;
1293
        foreach ($statement->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1294
            if ($row['ezcontentobject_version_id'] == $previousId) {
1295
                continue;
1296
            }
1297
1298
            $previousId = $row['ezcontentobject_version_id'];
1299
            $results[] = $row;
1300
        }
1301
1302
        return $results;
1303
    }
1304
1305
    /**
1306
     * Returns all version numbers for the given $contentId.
1307
     *
1308
     * @param mixed $contentId
1309
     *
1310
     * @return int[]
1311
     */
1312
    public function listVersionNumbers($contentId)
1313
    {
1314
        $query = $this->dbHandler->createSelectQuery();
1315
        $query->selectDistinct(
1316
            $this->dbHandler->quoteColumn('version')
1317
        )->from(
1318
            $this->dbHandler->quoteTable('ezcontentobject_version')
1319
        )->where(
1320
            $query->expr->eq(
1321
                $this->dbHandler->quoteColumn('contentobject_id'),
1322
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1323
            )
1324
        );
1325
1326
        $statement = $query->prepare();
1327
        $statement->execute();
1328
1329
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1330
    }
1331
1332
    /**
1333
     * Returns last version number for content identified by $contentId.
1334
     *
1335
     * @param int $contentId
1336
     *
1337
     * @return int
1338
     */
1339
    public function getLastVersionNumber($contentId)
1340
    {
1341
        $query = $this->dbHandler->createSelectQuery();
1342
        $query->select(
1343
            $query->expr->max($this->dbHandler->quoteColumn('version'))
1344
        )->from(
1345
            $this->dbHandler->quoteTable('ezcontentobject_version')
1346
        )->where(
1347
            $query->expr->eq(
1348
                $this->dbHandler->quoteColumn('contentobject_id'),
1349
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1350
            )
1351
        );
1352
1353
        $statement = $query->prepare();
1354
        $statement->execute();
1355
1356
        return (int)$statement->fetchColumn();
1357
    }
1358
1359
    /**
1360
     * Returns all IDs for locations that refer to $contentId.
1361
     *
1362
     * @param int $contentId
1363
     *
1364
     * @return int[]
1365
     */
1366
    public function getAllLocationIds($contentId)
1367
    {
1368
        $query = $this->dbHandler->createSelectQuery();
1369
        $query->select(
1370
            $this->dbHandler->quoteColumn('node_id')
1371
        )->from(
1372
            $this->dbHandler->quoteTable('ezcontentobject_tree')
1373
        )->where(
1374
            $query->expr->eq(
1375
                $this->dbHandler->quoteColumn('contentobject_id'),
1376
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1377
            )
1378
        );
1379
1380
        $statement = $query->prepare();
1381
        $statement->execute();
1382
1383
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1384
    }
1385
1386
    /**
1387
     * Returns all field IDs of $contentId grouped by their type.
1388
     * If $versionNo is set only field IDs for that version are returned.
1389
     * If $languageCode is set, only field IDs for that language are returned.
1390
     *
1391
     * @param int $contentId
1392
     * @param int|null $versionNo
1393
     * @param string|null $languageCode
1394
     *
1395
     * @return int[][]
1396
     */
1397
    public function getFieldIdsByType($contentId, $versionNo = null, $languageCode = null)
1398
    {
1399
        $query = $this->dbHandler->createSelectQuery();
1400
        $query->select(
1401
            $this->dbHandler->quoteColumn('id'),
1402
            $this->dbHandler->quoteColumn('data_type_string')
1403
        )->from(
1404
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1405
        )->where(
1406
            $query->expr->eq(
1407
                $this->dbHandler->quoteColumn('contentobject_id'),
1408
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1409
            )
1410
        );
1411
1412
        if (isset($versionNo)) {
1413
            $query->where(
1414
                $query->expr->eq(
1415
                    $this->dbHandler->quoteColumn('version'),
1416
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1417
                )
1418
            );
1419
        }
1420
1421
        if (isset($languageCode)) {
1422
            $query->where(
1423
                $query->expr->eq(
1424
                    $this->dbHandler->quoteColumn('language_code'),
1425
                    $query->bindValue($languageCode, null, \PDO::PARAM_STR)
1426
                )
1427
            );
1428
        }
1429
1430
        $statement = $query->prepare();
1431
        $statement->execute();
1432
1433
        $result = [];
1434
        foreach ($statement->fetchAll() as $row) {
1435
            if (!isset($result[$row['data_type_string']])) {
1436
                $result[$row['data_type_string']] = [];
1437
            }
1438
            $result[$row['data_type_string']][] = (int)$row['id'];
1439
        }
1440
1441
        return $result;
1442
    }
1443
1444
    /**
1445
     * Deletes relations to and from $contentId.
1446
     * If $versionNo is set only relations for that version are deleted.
1447
     *
1448
     * @param int $contentId
1449
     * @param int|null $versionNo
1450
     */
1451
    public function deleteRelations($contentId, $versionNo = null)
1452
    {
1453
        $query = $this->dbHandler->createDeleteQuery();
1454
        $query->deleteFrom(
1455
            $this->dbHandler->quoteTable('ezcontentobject_link')
1456
        );
1457
1458
        if (isset($versionNo)) {
1459
            $query->where(
1460
                $query->expr->lAnd(
1461
                    $query->expr->eq(
1462
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1463
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1464
                    ),
1465
                    $query->expr->eq(
1466
                        $this->dbHandler->quoteColumn('from_contentobject_version'),
1467
                        $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1468
                    )
1469
                )
1470
            );
1471
        } else {
1472
            $query->where(
1473
                $query->expr->lOr(
1474
                    $query->expr->eq(
1475
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1476
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1477
                    ),
1478
                    $query->expr->eq(
1479
                        $this->dbHandler->quoteColumn('to_contentobject_id'),
1480
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1481
                    )
1482
                )
1483
            );
1484
        }
1485
1486
        $query->prepare()->execute();
1487
    }
1488
1489
    /**
1490
     * Removes relations to Content with $contentId from Relation and RelationList field type fields.
1491
     *
1492
     * @param int $contentId
1493
     */
1494
    public function removeReverseFieldRelations($contentId)
1495
    {
1496
        $query = $this->dbHandler->createSelectQuery();
1497
        $query
1498
            ->select('ezcontentobject_attribute.*')
1499
            ->from('ezcontentobject_attribute')
1500
            ->innerJoin(
1501
                'ezcontentobject_link',
1502
                $query->expr->lAnd(
1503
                    $query->expr->eq(
1504
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1505
                        $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_attribute')
1506
                    ),
1507
                    $query->expr->eq(
1508
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1509
                        $this->dbHandler->quoteColumn('version', 'ezcontentobject_attribute')
1510
                    ),
1511
                    $query->expr->eq(
1512
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_link'),
1513
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_attribute')
1514
                    )
1515
                )
1516
            )
1517
            ->where(
1518
                $query->expr->eq(
1519
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1520
                    $query->bindValue($contentId, null, PDO::PARAM_INT)
1521
                ),
1522
                $query->expr->gt(
1523
                    $query->expr->bitAnd(
1524
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1525
                        $query->bindValue(8, null, PDO::PARAM_INT)
1526
                    ),
1527
                    0
1528
                )
1529
            );
1530
1531
        $statement = $query->prepare();
1532
        $statement->execute();
1533
1534
        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
1535
            if ($row['data_type_string'] === 'ezobjectrelation') {
1536
                $this->removeRelationFromRelationField($row);
1537
            }
1538
1539
            if ($row['data_type_string'] === 'ezobjectrelationlist') {
1540
                $this->removeRelationFromRelationListField($contentId, $row);
1541
            }
1542
        }
1543
    }
1544
1545
    /**
1546
     * Updates field value of RelationList field type identified by given $row data,
1547
     * removing relations toward given $contentId.
1548
     *
1549
     * @param int $contentId
1550
     * @param array $row
1551
     */
1552
    protected function removeRelationFromRelationListField($contentId, array $row)
1553
    {
1554
        $document = new DOMDocument('1.0', 'utf-8');
1555
        $document->loadXML($row['data_text']);
1556
1557
        $xpath = new DOMXPath($document);
1558
        $xpathExpression = "//related-objects/relation-list/relation-item[@contentobject-id='{$contentId}']";
1559
1560
        $relationItems = $xpath->query($xpathExpression);
1561
        foreach ($relationItems as $relationItem) {
1562
            $relationItem->parentNode->removeChild($relationItem);
1563
        }
1564
1565
        $query = $this->dbHandler->createUpdateQuery();
1566
        $query
1567
            ->update('ezcontentobject_attribute')
1568
            ->set(
1569
                'data_text',
1570
                $query->bindValue($document->saveXML(), null, PDO::PARAM_STR)
1571
            )
1572
            ->where(
1573
                $query->expr->lAnd(
1574
                    $query->expr->eq(
1575
                        $this->dbHandler->quoteColumn('id'),
1576
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1577
                    ),
1578
                    $query->expr->eq(
1579
                        $this->dbHandler->quoteColumn('version'),
1580
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1581
                    )
1582
                )
1583
            );
1584
1585
        $query->prepare()->execute();
1586
    }
1587
1588
    /**
1589
     * Updates field value of Relation field type identified by given $row data,
1590
     * removing relation data.
1591
     *
1592
     * @param array $row
1593
     */
1594
    protected function removeRelationFromRelationField(array $row)
1595
    {
1596
        $query = $this->dbHandler->createUpdateQuery();
1597
        $query
1598
            ->update('ezcontentobject_attribute')
1599
            ->set('data_int', $query->bindValue(null, null, PDO::PARAM_INT))
1600
            ->set('sort_key_int', $query->bindValue(0, null, PDO::PARAM_INT))
1601
            ->where(
1602
                $query->expr->lAnd(
1603
                    $query->expr->eq(
1604
                        $this->dbHandler->quoteColumn('id'),
1605
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1606
                    ),
1607
                    $query->expr->eq(
1608
                        $this->dbHandler->quoteColumn('version'),
1609
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1610
                    )
1611
                )
1612
            );
1613
1614
        $query->prepare()->execute();
1615
    }
1616
1617
    /**
1618
     * Deletes the field with the given $fieldId.
1619
     *
1620
     * @param int $fieldId
1621
     */
1622
    public function deleteField($fieldId)
1623
    {
1624
        $query = $this->dbHandler->createDeleteQuery();
1625
        $query->deleteFrom(
1626
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1627
        )->where(
1628
            $query->expr->eq(
1629
                $this->dbHandler->quoteColumn('id'),
1630
                $query->bindValue($fieldId, null, \PDO::PARAM_INT)
1631
            )
1632
        );
1633
1634
        $query->prepare()->execute();
1635
    }
1636
1637
    /**
1638
     * Deletes all fields of $contentId in all versions.
1639
     * If $versionNo is set only fields for that version are deleted.
1640
     *
1641
     * @param int $contentId
1642
     * @param int|null $versionNo
1643
     */
1644
    public function deleteFields($contentId, $versionNo = null)
1645
    {
1646
        $query = $this->dbHandler->createDeleteQuery();
1647
        $query->deleteFrom('ezcontentobject_attribute')
1648
            ->where(
1649
                $query->expr->eq(
1650
                    $this->dbHandler->quoteColumn('contentobject_id'),
1651
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1652
                )
1653
            );
1654
1655
        if (isset($versionNo)) {
1656
            $query->where(
1657
                $query->expr->eq(
1658
                    $this->dbHandler->quoteColumn('version'),
1659
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1660
                )
1661
            );
1662
        }
1663
1664
        $query->prepare()->execute();
1665
    }
1666
1667
    /**
1668
     * Deletes all versions of $contentId.
1669
     * If $versionNo is set only that version is deleted.
1670
     *
1671
     * @param int $contentId
1672
     * @param int|null $versionNo
1673
     */
1674
    public function deleteVersions($contentId, $versionNo = null)
1675
    {
1676
        $query = $this->dbHandler->createDeleteQuery();
1677
        $query->deleteFrom('ezcontentobject_version')
1678
            ->where(
1679
                $query->expr->eq(
1680
                    $this->dbHandler->quoteColumn('contentobject_id'),
1681
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1682
                )
1683
            );
1684
1685
        if (isset($versionNo)) {
1686
            $query->where(
1687
                $query->expr->eq(
1688
                    $this->dbHandler->quoteColumn('version'),
1689
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1690
                )
1691
            );
1692
        }
1693
1694
        $query->prepare()->execute();
1695
    }
1696
1697
    /**
1698
     * Deletes all names of $contentId.
1699
     * If $versionNo is set only names for that version are deleted.
1700
     *
1701
     * @param int $contentId
1702
     * @param int|null $versionNo
1703
     */
1704
    public function deleteNames($contentId, $versionNo = null)
1705
    {
1706
        $query = $this->dbHandler->createDeleteQuery();
1707
        $query->deleteFrom('ezcontentobject_name')
1708
            ->where(
1709
                $query->expr->eq(
1710
                    $this->dbHandler->quoteColumn('contentobject_id'),
1711
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1712
                )
1713
            );
1714
1715
        if (isset($versionNo)) {
1716
            $query->where(
1717
                $query->expr->eq(
1718
                    $this->dbHandler->quoteColumn('content_version'),
1719
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1720
                )
1721
            );
1722
        }
1723
1724
        $query->prepare()->execute();
1725
    }
1726
1727
    /**
1728
     * Sets the name for Content $contentId in version $version to $name in $language.
1729
     *
1730
     * @param int $contentId
1731
     * @param int $version
1732
     * @param string $name
1733
     * @param string $language
1734
     */
1735
    public function setName($contentId, $version, $name, $language)
1736
    {
1737
        $language = $this->languageHandler->loadByLanguageCode($language);
1738
1739
        // Is it an insert or an update ?
1740
        $qSelect = $this->dbHandler->createSelectQuery();
1741
        $qSelect
1742
            ->select(
1743
                $qSelect->alias($qSelect->expr->count('*'), 'count')
1744
            )
1745
            ->from($this->dbHandler->quoteTable('ezcontentobject_name'))
1746
            ->where(
1747
                $qSelect->expr->lAnd(
1748
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $qSelect->bindValue($contentId)),
1749
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_version'), $qSelect->bindValue($version)),
1750
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_translation'), $qSelect->bindValue($language->languageCode))
1751
                )
1752
            );
1753
        $stmt = $qSelect->prepare();
1754
        $stmt->execute();
1755
        $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
1756
1757
        $insert = $res[0]['count'] == 0;
1758
        if ($insert) {
1759
            $q = $this->dbHandler->createInsertQuery();
1760
            $q->insertInto($this->dbHandler->quoteTable('ezcontentobject_name'));
1761
        } else {
1762
            $q = $this->dbHandler->createUpdateQuery();
1763
            $q->update($this->dbHandler->quoteTable('ezcontentobject_name'))
1764
                ->where(
1765
                    $q->expr->lAnd(
1766
                        $q->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $q->bindValue($contentId)),
1767
                        $q->expr->eq($this->dbHandler->quoteColumn('content_version'), $q->bindValue($version)),
1768
                        $q->expr->eq($this->dbHandler->quoteColumn('content_translation'), $q->bindValue($language->languageCode))
1769
                    )
1770
                );
1771
        }
1772
1773
        $q->set(
1774
            $this->dbHandler->quoteColumn('contentobject_id'),
1775
            $q->bindValue($contentId, null, \PDO::PARAM_INT)
1776
        )->set(
1777
            $this->dbHandler->quoteColumn('content_version'),
1778
            $q->bindValue($version, null, \PDO::PARAM_INT)
1779
        )->set(
1780
            $this->dbHandler->quoteColumn('language_id'),
1781
            '(' . $this->getLanguageQuery()->getQuery() . ')'
1782
        )->set(
1783
            $this->dbHandler->quoteColumn('content_translation'),
1784
            $q->bindValue($language->languageCode)
1785
        )->set(
1786
            $this->dbHandler->quoteColumn('real_translation'),
1787
            $q->bindValue($language->languageCode)
1788
        )->set(
1789
            $this->dbHandler->quoteColumn('name'),
1790
            $q->bindValue($name)
1791
        );
1792
        $q->bindValue($language->id, ':languageId', \PDO::PARAM_INT);
1793
        $q->bindValue($contentId, ':contentId', \PDO::PARAM_INT);
1794
        $q->prepare()->execute();
1795
    }
1796
1797
    /**
1798
     * Returns a language sub select query for setName.
1799
     *
1800
     * Return sub select query which gets proper language mask for alwaysAvailable Content.
1801
     *
1802
     * @return \eZ\Publish\Core\Persistence\Database\SelectQuery
1803
     */
1804
    private function getLanguageQuery()
1805
    {
1806
        $languageQuery = $this->dbHandler->createSelectQuery();
1807
        $languageQuery
1808
            ->select(
1809
                $languageQuery->expr->searchedCase(
1810
                    [
1811
                        $languageQuery->expr->lAnd(
1812
                            $languageQuery->expr->eq(
1813
                                $this->dbHandler->quoteColumn('initial_language_id'),
1814
                                ':languageId'
1815
                            ),
1816
                            // wrap bitwise check into another "neq" to provide cross-DBMS compatibility
1817
                            $languageQuery->expr->neq(
1818
                                $languageQuery->expr->bitAnd(
1819
                                    $this->dbHandler->quoteColumn('language_mask'),
1820
                                    ':languageId'
1821
                                ),
1822
                                0
1823
                            )
1824
                        ),
1825
                        $languageQuery->expr->bitOr(
1826
                            ':languageId',
1827
                            1
1828
                        ),
1829
                    ],
1830
                    ':languageId'
1831
                )
1832
            )
1833
            ->from('ezcontentobject')
1834
            ->where(
1835
                $languageQuery->expr->eq(
1836
                    'id',
1837
                    ':contentId'
1838
                )
1839
            );
1840
1841
        return $languageQuery;
1842
    }
1843
1844
    /**
1845
     * Deletes the actual content object referred to by $contentId.
1846
     *
1847
     * @param int $contentId
1848
     */
1849
    public function deleteContent($contentId)
1850
    {
1851
        $query = $this->dbHandler->createDeleteQuery();
1852
        $query->deleteFrom('ezcontentobject')
1853
            ->where(
1854
                $query->expr->eq(
1855
                    $this->dbHandler->quoteColumn('id'),
1856
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1857
                )
1858
            );
1859
1860
        $query->prepare()->execute();
1861
    }
1862
1863
    /**
1864
     * Loads relations from $contentId to published content, optionally only from $contentVersionNo.
1865
     *
1866
     * $relationType can also be filtered.
1867
     *
1868
     * @param int $contentId
1869
     * @param int $contentVersionNo
1870
     * @param int $relationType
1871
     *
1872
     * @return string[][] array of relation data
1873
     */
1874
    public function loadRelations($contentId, $contentVersionNo = null, $relationType = null)
1875
    {
1876
        $query = $this->queryBuilder->createRelationFindQuery();
1877
        $query->innerJoin(
1878
            $query->alias(
1879
                $this->dbHandler->quoteTable('ezcontentobject'),
1880
                'ezcontentobject_to'
1881
            ),
1882
            $query->expr->lAnd(
1883
                $query->expr->eq(
1884
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1885
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject_to')
1886
                ),
1887
                $query->expr->eq(
1888
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_to'),
1889
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1890
                )
1891
            )
1892
        )->where(
1893
            $query->expr->eq(
1894
                $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1895
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1896
            )
1897
        );
1898
1899
        // source version number
1900
        if (isset($contentVersionNo)) {
1901
            $query->where(
1902
                $query->expr->eq(
1903
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1904
                    $query->bindValue($contentVersionNo, null, \PDO::PARAM_INT)
1905
                )
1906
            );
1907
        } else { // from published version only
1908
            $query->from(
1909
                $this->dbHandler->quoteTable('ezcontentobject')
1910
            )->where(
1911
                $query->expr->lAnd(
1912
                    $query->expr->eq(
1913
                        $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1914
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1915
                    ),
1916
                    $query->expr->eq(
1917
                        $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1918
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1919
                    )
1920
                )
1921
            );
1922
        }
1923
1924
        // relation type
1925
        if (isset($relationType)) {
1926
            $query->where(
1927
                $query->expr->gt(
1928
                    $query->expr->bitAnd(
1929
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1930
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1931
                    ),
1932
                    0
1933
                )
1934
            );
1935
        }
1936
1937
        $statement = $query->prepare();
1938
        $statement->execute();
1939
1940
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1941
    }
1942
1943
    /**
1944
     * {@inheritdoc}
1945
     */
1946
    public function countReverseRelations(int $toContentId, ?int $relationType = null): int
1947
    {
1948
        $platform = $this->connection->getDatabasePlatform();
1949
        $query = $this->connection->createQueryBuilder();
1950
        $expr = $query->expr();
1951
        $query
1952
            ->select($platform->getCountExpression('l.id'))
1953
            ->from('ezcontentobject_link', 'l')
1954
            ->innerJoin(
1955
                'l',
1956
                'ezcontentobject',
1957
                'c',
1958
                $expr->andX(
1959
                    $expr->eq('l.from_contentobject_id', 'c.id'),
1960
                    $expr->eq('l.from_contentobject_version', 'c.current_version'),
1961
                    $expr->eq('c.status', 1)
1962
                )
1963
            )
1964
            ->where(
1965
                $expr->eq('l.to_contentobject_id', ':toContentId')
1966
            )
1967
            ->setParameter(':toContentId', $toContentId, ParameterType::INTEGER);
1968
1969
        // relation type
1970
        if ($relationType !== null) {
1971
            $query->andWhere(
1972
                $expr->gt(
1973
                    $platform->getBitAndComparisonExpression('l.relation_type', $relationType),
1974
                    0
1975
                )
1976
            );
1977
        }
1978
1979
        return (int) $query->execute()->fetchColumn();
1980
    }
1981
1982
    /**
1983
     * Loads data that related to $toContentId.
1984
     *
1985
     * @param int $toContentId
1986
     * @param int $relationType
1987
     *
1988
     * @return mixed[][] Content data, array structured like {@see \eZ\Publish\Core\Persistence\Legacy\Content\Gateway::load()}
1989
     */
1990
    public function loadReverseRelations($toContentId, $relationType = null)
1991
    {
1992
        $query = $this->queryBuilder->createRelationFindQuery();
1993
        $query->where(
1994
            $query->expr->eq(
1995
                $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1996
                $query->bindValue($toContentId, null, \PDO::PARAM_INT)
1997
            )
1998
        );
1999
2000
        // ezcontentobject join
2001
        $query->from(
2002
            $this->dbHandler->quoteTable('ezcontentobject')
2003
        )->where(
2004
            $query->expr->lAnd(
2005
                $query->expr->eq(
2006
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
2007
                    $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
2008
                ),
2009
                $query->expr->eq(
2010
                    $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
2011
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
2012
                ),
2013
                $query->expr->eq(
2014
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject'),
2015
                    $query->bindValue(1, null, \PDO::PARAM_INT)
2016
                )
2017
            )
2018
        );
2019
2020
        // relation type
2021
        if (isset($relationType)) {
2022
            $query->where(
2023
                $query->expr->gt(
2024
                    $query->expr->bitAnd(
2025
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
2026
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
2027
                    ),
2028
                    0
2029
                )
2030
            );
2031
        }
2032
2033
        $statement = $query->prepare();
2034
2035
        $statement->execute();
2036
2037
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
2038
    }
2039
2040
    /**
2041
     * {@inheritdoc}
2042
     */
2043
    public function listReverseRelations(int $toContentId, int $offset = 0, int $limit = -1, ?int $relationType = null): array
2044
    {
2045
        $platform = $this->connection->getDatabasePlatform();
2046
        $query = $this->createRelationFindQuery();
2047
        $expr = $query->expr();
2048
        $query
2049
            ->innerJoin(
2050
                'l',
2051
                'ezcontentobject',
2052
                'c',
2053
                $expr->andX(
2054
                    $expr->eq('l.from_contentobject_id', 'c.id'),
2055
                    $expr->eq('l.from_contentobject_version', 'c.current_version'),
2056
                    $expr->eq('c.status', ContentInfo::STATUS_PUBLISHED)
2057
                )
2058
            )
2059
            ->where(
2060
                $expr->eq('l.to_contentobject_id', ':toContentId')
2061
            )
2062
            ->setParameter(':toContentId', $toContentId, ParameterType::INTEGER);
2063
2064
        // relation type
2065
        if ($relationType !== null) {
2066
            $query->andWhere(
2067
                $expr->gt(
2068
                    $platform->getBitAndComparisonExpression('l.relation_type', $relationType),
2069
                    0
2070
                )
2071
            );
2072
        }
2073
        $query->setFirstResult($offset);
2074
        if ($limit > 0) {
2075
            $query->setMaxResults($limit);
2076
        }
2077
        $query->orderBy('l.id', 'DESC');
2078
2079
        return $query->execute()->fetchAll(FetchMode::ASSOCIATIVE);
2080
    }
2081
2082
    /**
2083
     * Inserts a new relation database record.
2084
     *
2085
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
2086
     *
2087
     * @return int ID the inserted ID
2088
     */
2089
    public function insertRelation(RelationCreateStruct $createStruct)
2090
    {
2091
        $q = $this->dbHandler->createInsertQuery();
2092
        $q->insertInto(
2093
            $this->dbHandler->quoteTable('ezcontentobject_link')
2094
        )->set(
2095
            $this->dbHandler->quoteColumn('id'),
2096
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_link', 'id')
2097
        )->set(
2098
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
2099
            $q->bindValue((int)$createStruct->sourceFieldDefinitionId, null, \PDO::PARAM_INT)
2100
        )->set(
2101
            $this->dbHandler->quoteColumn('from_contentobject_id'),
2102
            $q->bindValue($createStruct->sourceContentId, null, \PDO::PARAM_INT)
2103
        )->set(
2104
            $this->dbHandler->quoteColumn('from_contentobject_version'),
2105
            $q->bindValue($createStruct->sourceContentVersionNo, null, \PDO::PARAM_INT)
2106
        )->set(
2107
            $this->dbHandler->quoteColumn('relation_type'),
2108
            $q->bindValue($createStruct->type, null, \PDO::PARAM_INT)
2109
        )->set(
2110
            $this->dbHandler->quoteColumn('to_contentobject_id'),
2111
            $q->bindValue($createStruct->destinationContentId, null, \PDO::PARAM_INT)
2112
        );
2113
2114
        $q->prepare()->execute();
2115
2116
        return $this->dbHandler->lastInsertId(
2117
            $this->dbHandler->getSequenceName('ezcontentobject_link', 'id')
2118
        );
2119
    }
2120
2121
    /**
2122
     * Deletes the relation with the given $relationId.
2123
     *
2124
     * @param int $relationId
2125
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
2126
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
2127
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
2128
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
2129
     */
2130
    public function deleteRelation($relationId, $type)
2131
    {
2132
        // Legacy Storage stores COMMON, LINK and EMBED types using bitmask, therefore first load
2133
        // existing relation type by given $relationId for comparison
2134
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
2135
        $query = $this->dbHandler->createSelectQuery();
2136
        $query->select(
2137
            $this->dbHandler->quoteColumn('relation_type')
2138
        )->from(
2139
            $this->dbHandler->quoteTable('ezcontentobject_link')
2140
        )->where(
2141
            $query->expr->eq(
2142
                $this->dbHandler->quoteColumn('id'),
2143
                $query->bindValue($relationId, null, \PDO::PARAM_INT)
2144
            )
2145
        );
2146
2147
        $statement = $query->prepare();
2148
        $statement->execute();
2149
        $loadedRelationType = $statement->fetchColumn();
2150
2151
        if (!$loadedRelationType) {
2152
            return;
2153
        }
2154
2155
        // If relation type matches then delete
2156
        if ($loadedRelationType == $type) {
2157
            /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
2158
            $query = $this->dbHandler->createDeleteQuery();
2159
            $query->deleteFrom(
2160
                'ezcontentobject_link'
2161
            )->where(
2162
                $query->expr->eq(
2163
                    $this->dbHandler->quoteColumn('id'),
2164
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
2165
                )
2166
            );
2167
2168
            $query->prepare()->execute();
2169
        } elseif ($loadedRelationType & $type) { // If relation type is composite update bitmask
2170
            /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
2171
            $query = $this->dbHandler->createUpdateQuery();
2172
            $query->update(
2173
                $this->dbHandler->quoteTable('ezcontentobject_link')
2174
            )->set(
2175
                $this->dbHandler->quoteColumn('relation_type'),
2176
                $query->expr->bitAnd(
2177
                    $this->dbHandler->quoteColumn('relation_type'),
2178
                    $query->bindValue(~$type, null, \PDO::PARAM_INT)
2179
                )
2180
            )->where(
2181
                $query->expr->eq(
2182
                    $this->dbHandler->quoteColumn('id'),
2183
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
2184
                )
2185
            );
2186
2187
            $query->prepare()->execute();
2188
        } else {
2189
            // No match, do nothing
2190
        }
2191
    }
2192
2193
    /**
2194
     * Returns all Content IDs for a given $contentTypeId.
2195
     *
2196
     * @param int $contentTypeId
2197
     *
2198
     * @return int[]
2199
     */
2200
    public function getContentIdsByContentTypeId($contentTypeId)
2201
    {
2202
        $query = $this->dbHandler->createSelectQuery();
2203
        $query
2204
            ->select($this->dbHandler->quoteColumn('id'))
2205
            ->from($this->dbHandler->quoteTable('ezcontentobject'))
2206
            ->where(
2207
                $query->expr->eq(
2208
                    $this->dbHandler->quoteColumn('contentclass_id'),
2209
                    $query->bindValue($contentTypeId, null, PDO::PARAM_INT)
2210
                )
2211
            );
2212
2213
        $statement = $query->prepare();
2214
        $statement->execute();
2215
2216
        return $statement->fetchAll(PDO::FETCH_COLUMN);
2217
    }
2218
2219
    /**
2220
     * Load name data for set of content id's and corresponding version number.
2221
     *
2222
     * @param array[] $rows array of hashes with 'id' and 'version' to load names for
2223
     *
2224
     * @return array
2225
     */
2226
    public function loadVersionedNameData($rows)
2227
    {
2228
        $query = $this->queryBuilder->createNamesQuery();
2229
        $conditions = [];
2230
        foreach ($rows as $row) {
2231
            $conditions[] = $query->expr->lAnd(
2232
                $query->expr->eq(
2233
                    $this->dbHandler->quoteColumn('contentobject_id'),
2234
                    $query->bindValue($row['id'], null, \PDO::PARAM_INT)
2235
                ),
2236
                $query->expr->eq(
2237
                    $this->dbHandler->quoteColumn('content_version'),
2238
                    $query->bindValue($row['version'], null, \PDO::PARAM_INT)
2239
                )
2240
            );
2241
        }
2242
2243
        $query->where($query->expr->lOr($conditions));
2244
        $stmt = $query->prepare();
2245
        $stmt->execute();
2246
2247
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
2248
    }
2249
2250
    /**
2251
     * Batch method for copying all relation meta data for copied Content object.
2252
     *
2253
     * {@inheritdoc}
2254
     *
2255
     * @param int $originalContentId
2256
     * @param int $copiedContentId
2257
     * @param int|null $versionNo If specified only copy for a given version number, otherwise all.
2258
     */
2259
    public function copyRelations($originalContentId, $copiedContentId, $versionNo = null)
2260
    {
2261
        // Given we can retain all columns, we just create copies with new `from_contentobject_id` using INSERT INTO SELECT
2262
        $sql = 'INSERT INTO ezcontentobject_link ( contentclassattribute_id, from_contentobject_id, from_contentobject_version, relation_type, to_contentobject_id )
2263
                SELECT  L2.contentclassattribute_id, :copied_id, L2.from_contentobject_version, L2.relation_type, L2.to_contentobject_id
2264
                FROM    ezcontentobject_link AS L2
2265
                WHERE   L2.from_contentobject_id = :original_id';
2266
2267
        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...
2268
            $stmt = $this->connection->prepare($sql . ' AND L2.from_contentobject_version = :version');
2269
            $stmt->bindValue('version', $versionNo, PDO::PARAM_INT);
2270
        } else {
2271
            $stmt = $this->connection->prepare($sql);
2272
        }
2273
2274
        $stmt->bindValue('original_id', $originalContentId, PDO::PARAM_INT);
2275
        $stmt->bindValue('copied_id', $copiedContentId, PDO::PARAM_INT);
2276
2277
        $stmt->execute();
2278
    }
2279
2280
    /**
2281
     * Remove the specified translation from the Content Object Version.
2282
     *
2283
     * @param int $contentId
2284
     * @param string $languageCode language code of the translation
2285
     * @throws \Doctrine\DBAL\DBALException
2286
     */
2287
    public function deleteTranslationFromContent($contentId, $languageCode)
2288
    {
2289
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2290
2291
        $this->connection->beginTransaction();
2292
        try {
2293
            $this->deleteTranslationFromContentVersions($contentId, $language->id);
2294
            $this->deleteTranslationFromContentNames($contentId, $languageCode);
2295
            $this->deleteTranslationFromContentObject($contentId, $language->id);
2296
2297
            $this->connection->commit();
2298
        } catch (DBALException $e) {
2299
            $this->connection->rollBack();
2300
            throw $e;
2301
        }
2302
    }
2303
2304
    /**
2305
     * Delete Content fields (attributes) for the given Translation.
2306
     * If $versionNo is given, fields for that Version only will be deleted.
2307
     *
2308
     * @param string $languageCode
2309
     * @param int $contentId
2310
     * @param int $versionNo (optional) filter by versionNo
2311
     */
2312
    public function deleteTranslatedFields($languageCode, $contentId, $versionNo = null)
2313
    {
2314
        $query = $this->connection->createQueryBuilder();
2315
        $query
2316
            ->delete('ezcontentobject_attribute')
2317
            ->where('contentobject_id = :contentId')
2318
            ->andWhere('language_code = :languageCode')
2319
            ->setParameters(
2320
                [
2321
                    ':contentId' => $contentId,
2322
                    ':languageCode' => $languageCode,
2323
                ]
2324
            )
2325
        ;
2326
2327
        if (null !== $versionNo) {
2328
            $query
2329
                ->andWhere('version = :versionNo')
2330
                ->setParameter(':versionNo', $versionNo)
2331
            ;
2332
        }
2333
2334
        $query->execute();
2335
    }
2336
2337
    /**
2338
     * Delete the specified Translation from the given Version.
2339
     *
2340
     * @param int $contentId
2341
     * @param int $versionNo
2342
     * @param string $languageCode
2343
     * @throws \Doctrine\DBAL\DBALException
2344
     */
2345
    public function deleteTranslationFromVersion($contentId, $versionNo, $languageCode)
2346
    {
2347
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2348
2349
        $this->connection->beginTransaction();
2350
        try {
2351
            $this->deleteTranslationFromContentVersions($contentId, $language->id, $versionNo);
2352
            $this->deleteTranslationFromContentNames($contentId, $languageCode, $versionNo);
2353
2354
            $this->connection->commit();
2355
        } catch (DBALException $e) {
2356
            $this->connection->rollBack();
2357
            throw $e;
2358
        }
2359
    }
2360
2361
    /**
2362
     * Delete translation from the ezcontentobject_name table.
2363
     *
2364
     * @param int $contentId
2365
     * @param string $languageCode
2366
     * @param int $versionNo optional, if specified, apply to this Version only.
2367
     */
2368
    private function deleteTranslationFromContentNames($contentId, $languageCode, $versionNo = null)
2369
    {
2370
        $query = $this->connection->createQueryBuilder();
2371
        $query
2372
            ->delete('ezcontentobject_name')
2373
            ->where('contentobject_id=:contentId')
2374
            ->andWhere('real_translation=:languageCode')
2375
            ->setParameters(
2376
                [
2377
                    ':languageCode' => $languageCode,
2378
                    ':contentId' => $contentId,
2379
                ]
2380
            )
2381
        ;
2382
2383
        if (null !== $versionNo) {
2384
            $query
2385
                ->andWhere('content_version = :versionNo')
2386
                ->setParameter(':versionNo', $versionNo)
2387
            ;
2388
        }
2389
2390
        $query->execute();
2391
    }
2392
2393
    /**
2394
     * Remove language from language_mask of ezcontentobject.
2395
     *
2396
     * @param int $contentId
2397
     * @param int $languageId
2398
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2399
     */
2400
    private function deleteTranslationFromContentObject($contentId, $languageId)
2401
    {
2402
        $query = $this->connection->createQueryBuilder();
2403
        $query->update('ezcontentobject')
2404
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2405
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2406
            ->set('modified', ':now')
2407
            ->where('id = :contentId')
2408
            ->andWhere(
2409
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2410
                $query->expr()->andX(
2411
                    'language_mask & ~ ' . $languageId . ' <> 0',
2412
                    'language_mask & ~ ' . $languageId . ' <> 1'
2413
                )
2414
            )
2415
            ->setParameter(':now', time())
2416
            ->setParameter(':contentId', $contentId)
2417
        ;
2418
2419
        $rowCount = $query->execute();
2420
2421
        // no rows updated means that most likely somehow it was the last remaining translation
2422
        if ($rowCount === 0) {
2423
            throw new BadStateException(
2424
                '$languageCode',
2425
                'Specified translation is the only one Content Object Version has'
2426
            );
2427
        }
2428
    }
2429
2430
    /**
2431
     * Remove language from language_mask of ezcontentobject_version and update initialLanguageId
2432
     * if it matches the removed one.
2433
     *
2434
     * @param int $contentId
2435
     * @param int $languageId
2436
     * @param int $versionNo optional, if specified, apply to this Version only.
2437
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2438
     */
2439
    private function deleteTranslationFromContentVersions($contentId, $languageId, $versionNo = null)
2440
    {
2441
        $query = $this->connection->createQueryBuilder();
2442
        $query->update('ezcontentobject_version')
2443
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2444
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2445
            ->set('modified', ':now')
2446
            // update initial_language_id only if it matches removed translation languageId
2447
            ->set(
2448
                'initial_language_id',
2449
                'CASE WHEN initial_language_id = :languageId ' .
2450
                'THEN (SELECT initial_language_id AS main_language_id FROM ezcontentobject c WHERE c.id = :contentId) ' .
2451
                'ELSE initial_language_id END'
2452
            )
2453
            ->where('contentobject_id = :contentId')
2454
            ->andWhere(
2455
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2456
                $query->expr()->andX(
2457
                    'language_mask & ~ ' . $languageId . ' <> 0',
2458
                    'language_mask & ~ ' . $languageId . ' <> 1'
2459
                )
2460
            )
2461
            ->setParameter(':now', time())
2462
            ->setParameter(':contentId', $contentId)
2463
            ->setParameter(':languageId', $languageId)
2464
        ;
2465
2466
        if (null !== $versionNo) {
2467
            $query
2468
                ->andWhere('version = :versionNo')
2469
                ->setParameter(':versionNo', $versionNo)
2470
            ;
2471
        }
2472
2473
        $rowCount = $query->execute();
2474
2475
        // no rows updated means that most likely somehow it was the last remaining translation
2476
        if ($rowCount === 0) {
2477
            throw new BadStateException(
2478
                '$languageCode',
2479
                'Specified translation is the only one Content Object Version has'
2480
            );
2481
        }
2482
    }
2483
2484
    /**
2485
     * Get query builder for content version objects, used for version loading w/o fields.
2486
     *
2487
     * Creates a select query with all necessary joins to fetch a complete
2488
     * content object. Does not apply any WHERE conditions, and does not contain
2489
     * name data as it will lead to large result set {@see createNamesQuery}.
2490
     *
2491
     * @return \Doctrine\DBAL\Query\QueryBuilder
2492
     */
2493
    private function createVersionInfoFindQueryBuilder(): DoctrineQueryBuilder
2494
    {
2495
        $query = $this->connection->createQueryBuilder();
2496
        $expr = $query->expr();
2497
2498
        $query
2499
            ->select(
2500
                'v.id AS ezcontentobject_version_id',
2501
                'v.version AS ezcontentobject_version_version',
2502
                'v.modified AS ezcontentobject_version_modified',
2503
                'v.creator_id AS ezcontentobject_version_creator_id',
2504
                'v.created AS ezcontentobject_version_created',
2505
                'v.status AS ezcontentobject_version_status',
2506
                'v.contentobject_id AS ezcontentobject_version_contentobject_id',
2507
                'v.initial_language_id AS ezcontentobject_version_initial_language_id',
2508
                'v.language_mask AS ezcontentobject_version_language_mask',
2509
                // Content main location
2510
                't.main_node_id AS ezcontentobject_tree_main_node_id',
2511
                // Content object
2512
                // @todo: remove ezcontentobject.d from query as it duplicates ezcontentobject_version.contentobject_id
2513
                'c.id AS ezcontentobject_id',
2514
                'c.contentclass_id AS ezcontentobject_contentclass_id',
2515
                'c.section_id AS ezcontentobject_section_id',
2516
                'c.owner_id AS ezcontentobject_owner_id',
2517
                'c.remote_id AS ezcontentobject_remote_id',
2518
                'c.current_version AS ezcontentobject_current_version',
2519
                'c.initial_language_id AS ezcontentobject_initial_language_id',
2520
                'c.modified AS ezcontentobject_modified',
2521
                'c.published AS ezcontentobject_published',
2522
                'c.status AS ezcontentobject_status',
2523
                'c.name AS ezcontentobject_name',
2524
                'c.language_mask AS ezcontentobject_language_mask',
2525
                'c.is_hidden AS ezcontentobject_is_hidden'
2526
            )
2527
            ->from('ezcontentobject_version', 'v')
2528
            ->innerJoin(
2529
                'v',
2530
                'ezcontentobject',
2531
                'c',
2532
                $expr->eq('c.id', 'v.contentobject_id')
2533
            )
2534
            ->leftJoin(
2535
                'v',
2536
                'ezcontentobject_tree',
2537
                't',
2538
                $expr->andX(
2539
                    $expr->eq('t.contentobject_id', 'v.contentobject_id'),
2540
                    $expr->eq('t.main_node_id', 't.node_id')
2541
                )
2542
            );
2543
2544
        return $query;
2545
    }
2546
2547
    /**
2548
     * Creates a select query for content relations.
2549
     *
2550
     * @return \Doctrine\DBAL\Query\QueryBuilder
2551
     */
2552
    public function createRelationFindQuery(): DoctrineQueryBuilder
2553
    {
2554
        $query = $this->connection->createQueryBuilder();
2555
        $query
2556
            ->select(
2557
                'l.id AS ezcontentobject_link_id',
2558
                'l.contentclassattribute_id AS ezcontentobject_link_contentclassattribute_id',
2559
                'l.from_contentobject_id AS ezcontentobject_link_from_contentobject_id',
2560
                'l.from_contentobject_version AS ezcontentobject_link_from_contentobject_version',
2561
                'l.relation_type AS ezcontentobject_link_relation_type',
2562
                'l.to_contentobject_id AS ezcontentobject_link_to_contentobject_id'
2563
            )
2564
            ->from(
2565
                'ezcontentobject_link', 'l'
2566
            );
2567
2568
        return $query;
2569
    }
2570
}
2571