Completed
Push — 7.5 ( 06837d...777d30 )
by
unknown
22:36 queued 12s
created

DoctrineDatabase::updateContent()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 24
nop 3
dl 0
loc 53
rs 8.0921
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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