Completed
Push — lazy-loading-not-avail-content ( 189e64...1dbb7d )
by André
17:36
created

DoctrineDatabase::removeReverseFieldRelations()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 5
nop 1
dl 0
loc 50
rs 9.0909
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the DoctrineDatabase Content Gateway class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
10
11
use Doctrine\DBAL\Connection;
12
use Doctrine\DBAL\DBALException;
13
use Doctrine\DBAL\FetchMode;
14
use Doctrine\DBAL\ParameterType;
15
use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
16
use eZ\Publish\Core\Base\Exceptions\BadStateException;
17
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
18
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder;
19
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
20
use eZ\Publish\Core\Persistence\Database\UpdateQuery;
21
use eZ\Publish\Core\Persistence\Database\InsertQuery;
22
use eZ\Publish\Core\Persistence\Database\SelectQuery;
23
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
24
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
25
use eZ\Publish\SPI\Persistence\Content;
26
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
27
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
28
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
29
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
30
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
31
use eZ\Publish\SPI\Persistence\Content\Field;
32
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
33
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
34
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
35
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
36
use DOMXPath;
37
use DOMDocument;
38
use PDO;
39
40
/**
41
 * Doctrine database based content gateway.
42
 */
43
class DoctrineDatabase extends Gateway
44
{
45
    /**
46
     * eZ Doctrine database handler.
47
     *
48
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
49
     */
50
    protected $dbHandler;
51
52
    /**
53
     * The native Doctrine connection.
54
     *
55
     * Meant to be used to transition from eZ/Zeta interface to Doctrine.
56
     *
57
     * @var \Doctrine\DBAL\Connection
58
     */
59
    protected $connection;
60
61
    /**
62
     * Query builder.
63
     *
64
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder
65
     */
66
    protected $queryBuilder;
67
68
    /**
69
     * Caching language handler.
70
     *
71
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler
72
     */
73
    protected $languageHandler;
74
75
    /**
76
     * Language mask generator.
77
     *
78
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
79
     */
80
    protected $languageMaskGenerator;
81
82
    /**
83
     * Creates a new gateway based on $db.
84
     *
85
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db
86
     * @param \Doctrine\DBAL\Connection $connection
87
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder $queryBuilder
88
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
89
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
90
     */
91
    public function __construct(
92
        DatabaseHandler $db,
93
        Connection $connection,
94
        QueryBuilder $queryBuilder,
95
        LanguageHandler $languageHandler,
96
        LanguageMaskGenerator $languageMaskGenerator
97
    ) {
98
        $this->dbHandler = $db;
99
        $this->connection = $connection;
100
        $this->queryBuilder = $queryBuilder;
101
        $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...
102
        $this->languageMaskGenerator = $languageMaskGenerator;
103
    }
104
105
    /**
106
     * Get context definition for external storage layers.
107
     *
108
     * @return array
109
     */
110
    public function getContext()
111
    {
112
        return array(
113
            'identifier' => 'LegacyStorage',
114
            'connection' => $this->dbHandler,
115
        );
116
    }
117
118
    /**
119
     * Inserts a new content object.
120
     *
121
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
122
     * @param mixed $currentVersionNo
123
     *
124
     * @return int ID
125
     */
126
    public function insertContentObject(CreateStruct $struct, $currentVersionNo = 1)
127
    {
128
        $initialLanguageId = !empty($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId;
129
        $initialLanguageCode = $this->languageHandler->load($initialLanguageId)->languageCode;
130
131
        if (isset($struct->name[$initialLanguageCode])) {
132
            $name = $struct->name[$initialLanguageCode];
133
        } else {
134
            $name = '';
135
        }
136
137
        $q = $this->dbHandler->createInsertQuery();
138
        $q->insertInto(
139
            $this->dbHandler->quoteTable('ezcontentobject')
140
        )->set(
141
            $this->dbHandler->quoteColumn('id'),
142
            $this->dbHandler->getAutoIncrementValue('ezcontentobject', 'id')
143
        )->set(
144
            $this->dbHandler->quoteColumn('current_version'),
145
            $q->bindValue($currentVersionNo, null, \PDO::PARAM_INT)
146
        )->set(
147
            $this->dbHandler->quoteColumn('name'),
148
            $q->bindValue($name, null, \PDO::PARAM_STR)
149
        )->set(
150
            $this->dbHandler->quoteColumn('contentclass_id'),
151
            $q->bindValue($struct->typeId, null, \PDO::PARAM_INT)
152
        )->set(
153
            $this->dbHandler->quoteColumn('section_id'),
154
            $q->bindValue($struct->sectionId, null, \PDO::PARAM_INT)
155
        )->set(
156
            $this->dbHandler->quoteColumn('owner_id'),
157
            $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
158
        )->set(
159
            $this->dbHandler->quoteColumn('initial_language_id'),
160
            $q->bindValue($initialLanguageId, null, \PDO::PARAM_INT)
161
        )->set(
162
            $this->dbHandler->quoteColumn('remote_id'),
163
            $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
164
        )->set(
165
            $this->dbHandler->quoteColumn('modified'),
166
            $q->bindValue(0, null, \PDO::PARAM_INT)
167
        )->set(
168
            $this->dbHandler->quoteColumn('published'),
169
            $q->bindValue(0, null, \PDO::PARAM_INT)
170
        )->set(
171
            $this->dbHandler->quoteColumn('status'),
172
            $q->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
173
        )->set(
174
            $this->dbHandler->quoteColumn('language_mask'),
175
            $q->bindValue(
176
                $this->generateLanguageMaskFromFields(
177
                    $struct->fields,
178
                    $initialLanguageCode,
179
                    $struct->alwaysAvailable
180
                ),
181
                null,
182
                \PDO::PARAM_INT
183
            )
184
        );
185
186
        $q->prepare()->execute();
187
188
        return $this->dbHandler->lastInsertId(
189
            $this->dbHandler->getSequenceName('ezcontentobject', 'id')
190
        );
191
    }
192
193
    /**
194
     * Generates a language mask for $version.
195
     *
196
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
197
     * @param string $initialLanguageCode
198
     * @param bool $alwaysAvailable
199
     *
200
     * @return int
201
     */
202
    protected function generateLanguageMaskFromFields(array $fields, $initialLanguageCode, $alwaysAvailable): int
203
    {
204
        $languages = array($initialLanguageCode => true);
205
        foreach ($fields as $field) {
206
            if (isset($languages[$field->languageCode])) {
207
                continue;
208
            }
209
210
            $languages[$field->languageCode] = true;
211
        }
212
213
        if ($alwaysAvailable) {
214
            $languages['always-available'] = true;
215
        }
216
217
        return $this->languageMaskGenerator->generateLanguageMaskFromLanguageMap($languages);
218
    }
219
220
    /**
221
     * Inserts a new version.
222
     *
223
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo
224
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
225
     *
226
     * @return int ID
227
     */
228
    public function insertVersion(VersionInfo $versionInfo, array $fields)
229
    {
230
        /** @var $q \eZ\Publish\Core\Persistence\Database\InsertQuery */
231
        $q = $this->dbHandler->createInsertQuery();
232
        $q->insertInto(
233
            $this->dbHandler->quoteTable('ezcontentobject_version')
234
        )->set(
235
            $this->dbHandler->quoteColumn('id'),
236
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_version', 'id')
237
        )->set(
238
            $this->dbHandler->quoteColumn('version'),
239
            $q->bindValue($versionInfo->versionNo, null, \PDO::PARAM_INT)
240
        )->set(
241
            $this->dbHandler->quoteColumn('modified'),
242
            $q->bindValue($versionInfo->modificationDate, null, \PDO::PARAM_INT)
243
        )->set(
244
            $this->dbHandler->quoteColumn('creator_id'),
245
            $q->bindValue($versionInfo->creatorId, null, \PDO::PARAM_INT)
246
        )->set(
247
            $this->dbHandler->quoteColumn('created'),
248
            $q->bindValue($versionInfo->creationDate, null, \PDO::PARAM_INT)
249
        )->set(
250
            $this->dbHandler->quoteColumn('status'),
251
            $q->bindValue($versionInfo->status, null, \PDO::PARAM_INT)
252
        )->set(
253
            $this->dbHandler->quoteColumn('initial_language_id'),
254
            $q->bindValue(
255
                $this->languageHandler->loadByLanguageCode($versionInfo->initialLanguageCode)->id,
256
                null,
257
                \PDO::PARAM_INT
258
            )
259
        )->set(
260
            $this->dbHandler->quoteColumn('contentobject_id'),
261
            $q->bindValue($versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
262
        )->set(
263
            // As described in field mapping document
264
            $this->dbHandler->quoteColumn('workflow_event_pos'),
265
            $q->bindValue(0, null, \PDO::PARAM_INT)
266
        )->set(
267
            $this->dbHandler->quoteColumn('language_mask'),
268
            $q->bindValue(
269
                $this->generateLanguageMaskFromFields(
270
                    $fields,
271
                    $versionInfo->initialLanguageCode,
272
                    $versionInfo->contentInfo->alwaysAvailable
273
                ),
274
                null,
275
                \PDO::PARAM_INT
276
            )
277
        );
278
279
        $q->prepare()->execute();
280
281
        return $this->dbHandler->lastInsertId(
282
            $this->dbHandler->getSequenceName('ezcontentobject_version', 'id')
283
        );
284
    }
285
286
    /**
287
     * Updates an existing content identified by $contentId in respect to $struct.
288
     *
289
     * @param int $contentId
290
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $struct
291
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $prePublishVersionInfo Provided on publish
292
     */
293
    public function updateContent($contentId, MetadataUpdateStruct $struct, VersionInfo $prePublishVersionInfo = null)
294
    {
295
        $q = $this->dbHandler->createUpdateQuery();
296
        $q->update($this->dbHandler->quoteTable('ezcontentobject'));
297
298
        if (isset($struct->name)) {
299
            $q->set(
300
                $this->dbHandler->quoteColumn('name'),
301
                $q->bindValue($struct->name, null, \PDO::PARAM_STR)
302
            );
303
        }
304
        if (isset($struct->mainLanguageId)) {
305
            $q->set(
306
                $this->dbHandler->quoteColumn('initial_language_id'),
307
                $q->bindValue($struct->mainLanguageId, null, \PDO::PARAM_INT)
308
            );
309
        }
310
        if (isset($struct->modificationDate)) {
311
            $q->set(
312
                $this->dbHandler->quoteColumn('modified'),
313
                $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
314
            );
315
        }
316
        if (isset($struct->ownerId)) {
317
            $q->set(
318
                $this->dbHandler->quoteColumn('owner_id'),
319
                $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
320
            );
321
        }
322
        if (isset($struct->publicationDate)) {
323
            $q->set(
324
                $this->dbHandler->quoteColumn('published'),
325
                $q->bindValue($struct->publicationDate, null, \PDO::PARAM_INT)
326
            );
327
        }
328
        if (isset($struct->remoteId)) {
329
            $q->set(
330
                $this->dbHandler->quoteColumn('remote_id'),
331
                $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
332
            );
333
        }
334
        if ($prePublishVersionInfo !== null) {
335
            $mask = $this->languageMaskGenerator->generateLanguageMaskFromLanguageCodes(
336
                $prePublishVersionInfo->languageCodes,
337
                $struct->alwaysAvailable ?? $prePublishVersionInfo->contentInfo->alwaysAvailable
338
            );
339
340
            $q->set(
341
                $this->dbHandler->quoteColumn('language_mask'),
342
                $q->bindValue($mask, null, \PDO::PARAM_INT)
343
            );
344
        }
345
        $q->where(
346
            $q->expr->eq(
347
                $this->dbHandler->quoteColumn('id'),
348
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
349
            )
350
        );
351
        $q->prepare()->execute();
352
353
        // Handle alwaysAvailable flag update separately as it's a more complex task and has impact on several tables
354
        if (isset($struct->alwaysAvailable) || isset($struct->mainLanguageId)) {
355
            $this->updateAlwaysAvailableFlag($contentId, $struct->alwaysAvailable);
356
        }
357
    }
358
359
    /**
360
     * Updates version $versionNo for content identified by $contentId, in respect to $struct.
361
     *
362
     * @param int $contentId
363
     * @param int $versionNo
364
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $struct
365
     */
366
    public function updateVersion($contentId, $versionNo, UpdateStruct $struct)
367
    {
368
        $q = $this->dbHandler->createUpdateQuery();
369
        $q->update(
370
            $this->dbHandler->quoteTable('ezcontentobject_version')
371
        )->set(
372
            $this->dbHandler->quoteColumn('creator_id'),
373
            $q->bindValue($struct->creatorId, null, \PDO::PARAM_INT)
374
        )->set(
375
            $this->dbHandler->quoteColumn('modified'),
376
            $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
377
        )->set(
378
            $this->dbHandler->quoteColumn('initial_language_id'),
379
            $q->bindValue($struct->initialLanguageId, null, \PDO::PARAM_INT)
380
        )->set(
381
            $this->dbHandler->quoteColumn('language_mask'),
382
            $q->expr->bitOr(
383
                $this->dbHandler->quoteColumn('language_mask'),
384
                $q->bindValue(
385
                    $this->generateLanguageMaskFromFields(
386
                        $struct->fields,
387
                        $this->languageHandler->load($struct->initialLanguageId)->languageCode,
388
                        false
389
                    ),
390
                    null,
391
                    \PDO::PARAM_INT
392
                )
393
            )
394
        )->where(
395
            $q->expr->lAnd(
396
                $q->expr->eq(
397
                    $this->dbHandler->quoteColumn('contentobject_id'),
398
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
399
                ),
400
                $q->expr->eq(
401
                    $this->dbHandler->quoteColumn('version'),
402
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
403
                )
404
            )
405
        );
406
        $q->prepare()->execute();
407
    }
408
409
    /**
410
     * Updates "always available" flag for Content identified by $contentId, in respect to
411
     * Content's current main language and optionally new $alwaysAvailable state.
412
     *
413
     * @param int $contentId
414
     * @param bool|null $alwaysAvailable New "always available" value or null if not defined
415
     */
416
    public function updateAlwaysAvailableFlag($contentId, $alwaysAvailable = null)
417
    {
418
        // We will need to know some info on the current language mask to update the flag
419
        // everywhere needed
420
        $contentInfoRow = $this->loadContentInfo($contentId);
421
        if (!isset($alwaysAvailable)) {
422
            $alwaysAvailable = (bool)$contentInfoRow['language_mask'] & 1;
423
        }
424
425
        /** @var $q \eZ\Publish\Core\Persistence\Database\UpdateQuery */
426
        $q = $this->dbHandler->createUpdateQuery();
427
        $q
428
            ->update($this->dbHandler->quoteTable('ezcontentobject'))
429
            ->set(
430
                $this->dbHandler->quoteColumn('language_mask'),
431
                $alwaysAvailable ?
432
                    $q->expr->bitOr($this->dbHandler->quoteColumn('language_mask'), 1) :
433
                    $q->expr->bitAnd($this->dbHandler->quoteColumn('language_mask'), -2)
434
            )
435
            ->where(
436
                $q->expr->eq(
437
                    $this->dbHandler->quoteColumn('id'),
438
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
439
                )
440
            );
441
        $q->prepare()->execute();
442
443
        // Now we need to update ezcontentobject_name
444
        /** @var $qName \eZ\Publish\Core\Persistence\Database\UpdateQuery */
445
        $qName = $this->dbHandler->createUpdateQuery();
446
        $qName
447
            ->update($this->dbHandler->quoteTable('ezcontentobject_name'))
448
            ->set(
449
                $this->dbHandler->quoteColumn('language_id'),
450
                $alwaysAvailable ?
451
                    $qName->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
452
                    $qName->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
453
            )
454
            ->where(
455
                $qName->expr->lAnd(
456
                    $qName->expr->eq(
457
                        $this->dbHandler->quoteColumn('contentobject_id'),
458
                        $qName->bindValue($contentId, null, \PDO::PARAM_INT)
459
                    ),
460
                    $qName->expr->eq(
461
                        $this->dbHandler->quoteColumn('content_version'),
462
                        $qName->bindValue(
463
                            $contentInfoRow['current_version'],
464
                            null,
465
                            \PDO::PARAM_INT
466
                        )
467
                    )
468
                )
469
            );
470
        $qName->prepare()->execute();
471
472
        // Now update ezcontentobject_attribute for current version
473
        // Create update query that will be reused
474
        /** @var $qAttr \eZ\Publish\Core\Persistence\Database\UpdateQuery */
475
        $qAttr = $this->dbHandler->createUpdateQuery();
476
        $qAttr
477
            ->update($this->dbHandler->quoteTable('ezcontentobject_attribute'))
478
            ->where(
479
                $qAttr->expr->lAnd(
480
                    $qAttr->expr->eq(
481
                        $this->dbHandler->quoteColumn('contentobject_id'),
482
                        $qAttr->bindValue($contentId, null, \PDO::PARAM_INT)
483
                    ),
484
                    $qAttr->expr->eq(
485
                        $this->dbHandler->quoteColumn('version'),
486
                        $qAttr->bindValue(
487
                            $contentInfoRow['current_version'],
488
                            null,
489
                            \PDO::PARAM_INT
490
                        )
491
                    )
492
                )
493
            );
494
495
        // If there is only a single language, update all fields and return
496
        if (!$this->languageMaskGenerator->isLanguageMaskComposite($contentInfoRow['language_mask'])) {
497
            $qAttr->set(
498
                $this->dbHandler->quoteColumn('language_id'),
499
                $alwaysAvailable ?
500
                    $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
501
                    $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
502
            );
503
            $qAttr->prepare()->execute();
504
505
            return;
506
        }
507
508
        // Otherwise:
509
        // 1. Remove always available flag on all fields
510
        $qAttr->set(
511
            $this->dbHandler->quoteColumn('language_id'),
512
            $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
513
        );
514
        $qAttr->prepare()->execute();
515
516
        // 2. If Content is always available set the flag only on fields in main language
517
        if ($alwaysAvailable) {
518
            $qAttr->set(
519
                $this->dbHandler->quoteColumn('language_id'),
520
                $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1)
521
            );
522
            $qAttr->where(
523
                $qAttr->expr->gt(
524
                    $qAttr->expr->bitAnd(
525
                        $this->dbHandler->quoteColumn('language_id'),
526
                        $qAttr->bindValue($contentInfoRow['initial_language_id'], null, PDO::PARAM_INT)
527
                    ),
528
                    $qAttr->bindValue(0, null, PDO::PARAM_INT)
529
                )
530
            );
531
            $qAttr->prepare()->execute();
532
        }
533
    }
534
535
    /**
536
     * Sets the status of the version identified by $contentId and $version to $status.
537
     *
538
     * The $status can be one of STATUS_DRAFT, STATUS_PUBLISHED, STATUS_ARCHIVED
539
     *
540
     * @param int $contentId
541
     * @param int $version
542
     * @param int $status
543
     *
544
     * @return bool
545
     */
546
    public function setStatus($contentId, $version, $status)
547
    {
548
        $q = $this->dbHandler->createUpdateQuery();
549
        $q->update(
550
            $this->dbHandler->quoteTable('ezcontentobject_version')
551
        )->set(
552
            $this->dbHandler->quoteColumn('status'),
553
            $q->bindValue($status, null, \PDO::PARAM_INT)
554
        )->set(
555
            $this->dbHandler->quoteColumn('modified'),
556
            $q->bindValue(time(), null, \PDO::PARAM_INT)
557
        )->where(
558
            $q->expr->lAnd(
559
                $q->expr->eq(
560
                    $this->dbHandler->quoteColumn('contentobject_id'),
561
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
562
                ),
563
                $q->expr->eq(
564
                    $this->dbHandler->quoteColumn('version'),
565
                    $q->bindValue($version, null, \PDO::PARAM_INT)
566
                )
567
            )
568
        );
569
        $statement = $q->prepare();
570
        $statement->execute();
571
572
        if ((bool)$statement->rowCount() === false) {
573
            return false;
574
        }
575
576
        if ($status !== APIVersionInfo::STATUS_PUBLISHED) {
577
            return true;
578
        }
579
580
        // If the version's status is PUBLISHED, we set the content to published status as well
581
        $q = $this->dbHandler->createUpdateQuery();
582
        $q->update(
583
            $this->dbHandler->quoteTable('ezcontentobject')
584
        )->set(
585
            $this->dbHandler->quoteColumn('status'),
586
            $q->bindValue(ContentInfo::STATUS_PUBLISHED, null, \PDO::PARAM_INT)
587
        )->set(
588
            $this->dbHandler->quoteColumn('current_version'),
589
            $q->bindValue($version, null, \PDO::PARAM_INT)
590
        )->where(
591
            $q->expr->eq(
592
                $this->dbHandler->quoteColumn('id'),
593
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
594
            )
595
        );
596
        $statement = $q->prepare();
597
        $statement->execute();
598
599
        return (bool)$statement->rowCount();
600
    }
601
602
    /**
603
     * Inserts a new field.
604
     *
605
     * Only used when a new field is created (i.e. a new object or a field in a
606
     * new language!). After that, field IDs need to stay the same, only the
607
     * version number changes.
608
     *
609
     * @param \eZ\Publish\SPI\Persistence\Content $content
610
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
611
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
612
     *
613
     * @return int ID
614
     */
615
    public function insertNewField(Content $content, Field $field, StorageFieldValue $value)
616
    {
617
        $q = $this->dbHandler->createInsertQuery();
618
619
        $this->setInsertFieldValues($q, $content, $field, $value);
620
621
        // Insert with auto increment ID
622
        $q->set(
623
            $this->dbHandler->quoteColumn('id'),
624
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_attribute', 'id')
625
        );
626
627
        $q->prepare()->execute();
628
629
        return $this->dbHandler->lastInsertId(
630
            $this->dbHandler->getSequenceName('ezcontentobject_attribute', 'id')
631
        );
632
    }
633
634
    /**
635
     * Inserts an existing field.
636
     *
637
     * Used to insert a field with an exsting ID but a new version number.
638
     *
639
     * @param Content $content
640
     * @param Field $field
641
     * @param StorageFieldValue $value
642
     */
643
    public function insertExistingField(Content $content, Field $field, StorageFieldValue $value)
644
    {
645
        $q = $this->dbHandler->createInsertQuery();
646
647
        $this->setInsertFieldValues($q, $content, $field, $value);
648
649
        $q->set(
650
            $this->dbHandler->quoteColumn('id'),
651
            $q->bindValue($field->id, null, \PDO::PARAM_INT)
652
        );
653
654
        $q->prepare()->execute();
655
    }
656
657
    /**
658
     * Sets field (ezcontentobject_attribute) values to the given query.
659
     *
660
     * @param \eZ\Publish\Core\Persistence\Database\InsertQuery $q
661
     * @param Content $content
662
     * @param Field $field
663
     * @param StorageFieldValue $value
664
     */
665
    protected function setInsertFieldValues(InsertQuery $q, Content $content, Field $field, StorageFieldValue $value)
666
    {
667
        $q->insertInto(
668
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
669
        )->set(
670
            $this->dbHandler->quoteColumn('contentobject_id'),
671
            $q->bindValue($content->versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
672
        )->set(
673
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
674
            $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
675
        )->set(
676
            $this->dbHandler->quoteColumn('data_type_string'),
677
            $q->bindValue($field->type)
678
        )->set(
679
            $this->dbHandler->quoteColumn('language_code'),
680
            $q->bindValue($field->languageCode)
681
        )->set(
682
            $this->dbHandler->quoteColumn('version'),
683
            $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
684
        )->set(
685
            $this->dbHandler->quoteColumn('data_float'),
686
            $q->bindValue($value->dataFloat)
687
        )->set(
688
            $this->dbHandler->quoteColumn('data_int'),
689
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
690
        )->set(
691
            $this->dbHandler->quoteColumn('data_text'),
692
            $q->bindValue($value->dataText)
693
        )->set(
694
            $this->dbHandler->quoteColumn('sort_key_int'),
695
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
696
        )->set(
697
            $this->dbHandler->quoteColumn('sort_key_string'),
698
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
699
        )->set(
700
            $this->dbHandler->quoteColumn('language_id'),
701
            $q->bindValue(
702
                $this->languageMaskGenerator->generateLanguageIndicator(
703
                    $field->languageCode,
704
                    $this->isLanguageAlwaysAvailable($content, $field->languageCode)
705
                ),
706
                null,
707
                \PDO::PARAM_INT
708
            )
709
        );
710
    }
711
712
    /**
713
     * Checks if $languageCode is always available in $content.
714
     *
715
     * @param \eZ\Publish\SPI\Persistence\Content $content
716
     * @param string $languageCode
717
     *
718
     * @return bool
719
     */
720
    protected function isLanguageAlwaysAvailable(Content $content, $languageCode)
721
    {
722
        return
723
            $content->versionInfo->contentInfo->alwaysAvailable &&
724
            $content->versionInfo->contentInfo->mainLanguageCode === $languageCode
725
        ;
726
    }
727
728
    /**
729
     * Updates an existing field.
730
     *
731
     * @param Field $field
732
     * @param StorageFieldValue $value
733
     */
734
    public function updateField(Field $field, StorageFieldValue $value)
735
    {
736
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
737
        // cannot change on update
738
        $q = $this->dbHandler->createUpdateQuery();
739
        $this->setFieldUpdateValues($q, $value);
740
        $q->where(
741
            $q->expr->lAnd(
742
                $q->expr->eq(
743
                    $this->dbHandler->quoteColumn('id'),
744
                    $q->bindValue($field->id, null, \PDO::PARAM_INT)
745
                ),
746
                $q->expr->eq(
747
                    $this->dbHandler->quoteColumn('version'),
748
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
749
                )
750
            )
751
        );
752
        $q->prepare()->execute();
753
    }
754
755
    /**
756
     * Sets update fields for $value on $q.
757
     *
758
     * @param \eZ\Publish\Core\Persistence\Database\UpdateQuery $q
759
     * @param StorageFieldValue $value
760
     */
761
    protected function setFieldUpdateValues(UpdateQuery $q, StorageFieldValue $value)
762
    {
763
        $q->update(
764
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
765
        )->set(
766
            $this->dbHandler->quoteColumn('data_float'),
767
            $q->bindValue($value->dataFloat)
768
        )->set(
769
            $this->dbHandler->quoteColumn('data_int'),
770
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
771
        )->set(
772
            $this->dbHandler->quoteColumn('data_text'),
773
            $q->bindValue($value->dataText)
774
        )->set(
775
            $this->dbHandler->quoteColumn('sort_key_int'),
776
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
777
        )->set(
778
            $this->dbHandler->quoteColumn('sort_key_string'),
779
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
780
        );
781
    }
782
783
    /**
784
     * Updates an existing, non-translatable field.
785
     *
786
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
787
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
788
     * @param int $contentId
789
     */
790
    public function updateNonTranslatableField(
791
        Field $field,
792
        StorageFieldValue $value,
793
        $contentId
794
    ) {
795
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
796
        // cannot change on update
797
        $q = $this->dbHandler->createUpdateQuery();
798
        $this->setFieldUpdateValues($q, $value);
799
        $q->where(
800
            $q->expr->lAnd(
801
                $q->expr->eq(
802
                    $this->dbHandler->quoteColumn('contentclassattribute_id'),
803
                    $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
804
                ),
805
                $q->expr->eq(
806
                    $this->dbHandler->quoteColumn('contentobject_id'),
807
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
808
                ),
809
                $q->expr->eq(
810
                    $this->dbHandler->quoteColumn('version'),
811
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
812
                )
813
            )
814
        );
815
        $q->prepare()->execute();
816
    }
817
818
    /**
819
     * {@inheritdoc}
820
     */
821
    public function load($contentId, $version, array $translations = null)
822
    {
823
        $results = $this->internalLoadContent([$contentId], $version, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 821 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...
824
825
        return $results;
826
    }
827
828
    /**
829
     * {@inheritdoc}
830
     */
831
    public function loadContentList(array $contentIds, array $translations = null): array
832
    {
833
        return $this->internalLoadContent($contentIds, null, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 831 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...
834
    }
835
836
    /**
837
     * @see load(), loadContentList()
838
     *
839
     * @param array $contentIds
840
     * @param int $version
841
     * @param string[]|null $translations
842
     *
843
     * @return array
844
     */
845
    private function internalLoadContent(array $contentIds, int $version = null, array $translations = null): array
846
    {
847
        $queryBuilder = $this->connection->createQueryBuilder();
848
        $expr = $queryBuilder->expr();
849
        $queryBuilder
850
            ->select(
851
                'c.id AS ezcontentobject_id',
852
                'c.contentclass_id AS ezcontentobject_contentclass_id',
853
                'c.section_id AS ezcontentobject_section_id',
854
                'c.owner_id AS ezcontentobject_owner_id',
855
                'c.remote_id AS ezcontentobject_remote_id',
856
                'c.current_version AS ezcontentobject_current_version',
857
                'c.initial_language_id AS ezcontentobject_initial_language_id',
858
                'c.modified AS ezcontentobject_modified',
859
                'c.published AS ezcontentobject_published',
860
                'c.status AS ezcontentobject_status',
861
                'c.name AS ezcontentobject_name',
862
                'c.language_mask AS ezcontentobject_language_mask',
863
                'v.id AS ezcontentobject_version_id',
864
                'v.version AS ezcontentobject_version_version',
865
                'v.modified AS ezcontentobject_version_modified',
866
                'v.creator_id AS ezcontentobject_version_creator_id',
867
                'v.created AS ezcontentobject_version_created',
868
                'v.status AS ezcontentobject_version_status',
869
                'v.language_mask AS ezcontentobject_version_language_mask',
870
                'v.initial_language_id AS ezcontentobject_version_initial_language_id',
871
                'a.id AS ezcontentobject_attribute_id',
872
                'a.contentclassattribute_id AS ezcontentobject_attribute_contentclassattribute_id',
873
                'a.data_type_string AS ezcontentobject_attribute_data_type_string',
874
                'a.language_code AS ezcontentobject_attribute_language_code',
875
                'a.language_id AS ezcontentobject_attribute_language_id',
876
                'a.data_float AS ezcontentobject_attribute_data_float',
877
                'a.data_int AS ezcontentobject_attribute_data_int',
878
                'a.data_text AS ezcontentobject_attribute_data_text',
879
                'a.sort_key_int AS ezcontentobject_attribute_sort_key_int',
880
                'a.sort_key_string AS ezcontentobject_attribute_sort_key_string',
881
                't.main_node_id AS ezcontentobject_tree_main_node_id'
882
            )
883
            ->from('ezcontentobject', 'c')
884
            ->innerJoin(
885
                'c',
886
                'ezcontentobject_version',
887
                'v',
888
                $expr->andX(
889
                    $expr->eq('c.id', 'v.contentobject_id'),
890
                    $expr->eq('v.version', $version ?? 'c.current_version')
891
                )
892
            )
893
            ->innerJoin(
894
                'v',
895
                'ezcontentobject_attribute',
896
                'a',
897
                $expr->andX(
898
                    $expr->eq('v.contentobject_id', 'a.contentobject_id'),
899
                    $expr->eq('v.version', 'a.version')
900
                )
901
            )
902
            ->leftJoin(
903
                'c',
904
                'ezcontentobject_tree',
905
                't',
906
                $expr->andX(
907
                    $expr->eq('c.id', 't.contentobject_id'),
908
                    $expr->eq('t.node_id', 't.main_node_id')
909
                )
910
            );
911
912
        $queryBuilder->where(
913
            $expr->in(
914
                'c.id',
915
                $queryBuilder->createNamedParameter($contentIds, Connection::PARAM_INT_ARRAY)
916
            )
917
        );
918
919
        if (!empty($translations)) {
920
            $queryBuilder->andWhere(
921
                $expr->in(
922
                    'a.language_code',
923
                    $queryBuilder->createNamedParameter($translations, Connection::PARAM_STR_ARRAY)
924
                )
925
            );
926
        }
927
928
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
929
    }
930
931
    /**
932
     * Get query builder to load Content Info data.
933
     *
934
     * @see loadContentInfo(), loadContentInfoByRemoteId(), loadContentInfoList(), loadContentInfoByLocationId()
935
     *
936
     * @return \Doctrine\DBAL\Query\QueryBuilder
937
     */
938
    private function createLoadContentInfoQueryBuilder(): DoctrineQueryBuilder
939
    {
940
        $queryBuilder = $this->connection->createQueryBuilder();
941
        $expr = $queryBuilder->expr();
942
        $queryBuilder
943
            ->select('c.*', 't.main_node_id AS ezcontentobject_tree_main_node_id')
944
            ->from('ezcontentobject', 'c')
945
            ->leftJoin(
946
                'c',
947
                'ezcontentobject_tree',
948
                't',
949
                $expr->andX(
950
                    $expr->eq('c.id', 't.contentobject_id'),
951
                    $expr->eq('t.node_id', 't.main_node_id')
952
                )
953
            );
954
955
        return $queryBuilder;
956
    }
957
958
    /**
959
     * Loads info for content identified by $contentId.
960
     * Will basically return a hash containing all field values for ezcontentobject table plus some additional keys:
961
     *  - always_available => Boolean indicating if content's language mask contains alwaysAvailable bit field
962
     *  - main_language_code => Language code for main (initial) language. E.g. "eng-GB".
963
     *
964
     * @param int $contentId
965
     *
966
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
967
     *
968
     * @return array
969
     */
970 View Code Duplication
    public function loadContentInfo($contentId)
971
    {
972
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
973
        $queryBuilder
974
            ->where('c.id = :id')
975
            ->setParameter('id', $contentId, ParameterType::INTEGER);
976
977
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
978
        if (empty($results)) {
979
            throw new NotFound('content', "id: $contentId");
980
        }
981
982
        return $results[0];
983
    }
984
985
    public function loadContentInfoList(array $contentIds)
986
    {
987
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
988
        $queryBuilder
989
            ->where('c.id IN (:ids)')
990
            ->setParameter('ids', $contentIds, Connection::PARAM_INT_ARRAY);
991
992
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
993
    }
994
995
    /**
996
     * Loads info for a content object identified by its remote ID.
997
     *
998
     * Returns an array with the relevant data.
999
     *
1000
     * @param mixed $remoteId
1001
     *
1002
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1003
     *
1004
     * @return array
1005
     */
1006 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
1007
    {
1008
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1009
        $queryBuilder
1010
            ->where('c.remote_id = :id')
1011
            ->setParameter('id', $remoteId, ParameterType::STRING);
1012
1013
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1014
        if (empty($results)) {
1015
            throw new NotFound('content', "remote_id: $remoteId");
1016
        }
1017
1018
        return $results[0];
1019
    }
1020
1021
    /**
1022
     * Loads info for a content object identified by its location ID (node ID).
1023
     *
1024
     * Returns an array with the relevant data.
1025
     *
1026
     * @param int $locationId
1027
     *
1028
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1029
     *
1030
     * @return array
1031
     */
1032 View Code Duplication
    public function loadContentInfoByLocationId($locationId)
1033
    {
1034
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1035
        $queryBuilder
1036
            ->where('t.main_node_id = :id')
1037
            ->setParameter('id', $locationId, ParameterType::INTEGER);
1038
1039
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1040
        if (empty($results)) {
1041
            throw new NotFound('content', "main_node_id: $locationId");
1042
        }
1043
1044
        return $results[0];
1045
    }
1046
1047
    /**
1048
     * Loads version info for content identified by $contentId and $versionNo.
1049
     * Will basically return a hash containing all field values from ezcontentobject_version table plus following keys:
1050
     *  - names => Hash of content object names. Key is the language code, value is the name.
1051
     *  - 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.
1052
     *  - initial_language_code => Language code for initial language in this version.
1053
     *
1054
     * @param int $contentId
1055
     * @param int $versionNo
1056
     *
1057
     * @return array
1058
     */
1059 View Code Duplication
    public function loadVersionInfo($contentId, $versionNo)
1060
    {
1061
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1062
        $query->where(
1063
            $query->expr->lAnd(
1064
                $query->expr->eq(
1065
                    $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1066
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1067
                ),
1068
                $query->expr->eq(
1069
                    $this->dbHandler->quoteColumn('version', 'ezcontentobject_version'),
1070
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1071
                )
1072
            )
1073
        );
1074
        $statement = $query->prepare();
1075
        $statement->execute();
1076
1077
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1078
    }
1079
1080
    /**
1081
     * Returns data for all versions with given status created by the given $userId.
1082
     *
1083
     * @param int $userId
1084
     * @param int $status
1085
     *
1086
     * @return string[][]
1087
     */
1088
    public function listVersionsForUser($userId, $status = VersionInfo::STATUS_DRAFT)
1089
    {
1090
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1091
        $query->where(
1092
            $query->expr->lAnd(
1093
                $query->expr->eq(
1094
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1095
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1096
                ),
1097
                $query->expr->eq(
1098
                    $this->dbHandler->quoteColumn('creator_id', 'ezcontentobject_version'),
1099
                    $query->bindValue($userId, null, \PDO::PARAM_INT)
1100
                )
1101
            )
1102
        );
1103
1104
        return $this->listVersionsHelper($query);
1105
    }
1106
1107
    /**
1108
     * Returns all version data for the given $contentId, optionally filtered by status.
1109
     *
1110
     * Result is returned with oldest version first (using version id as it has index and is auto increment).
1111
     *
1112
     * @param mixed $contentId
1113
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
1114
     * @param int $limit Limit for items returned, -1 means none.
1115
     *
1116
     * @return string[][]
1117
     */
1118
    public function listVersions($contentId, $status = null, $limit = -1)
1119
    {
1120
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1121
1122
        $filter = $query->expr->eq(
1123
            $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1124
            $query->bindValue($contentId, null, \PDO::PARAM_INT)
1125
        );
1126
1127
        if ($status !== null) {
1128
            $filter = $query->expr->lAnd(
1129
                $filter,
1130
                $query->expr->eq(
1131
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1132
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1133
                )
1134
            );
1135
        }
1136
1137
        $query->where($filter);
1138
1139
        if ($limit > 0) {
1140
            $query->limit($limit);
1141
        }
1142
1143
        return $this->listVersionsHelper($query);
1144
    }
1145
1146
    /**
1147
     * Helper for {@see listVersions()} and {@see listVersionsForUser()} that filters duplicates
1148
     * that are the result of the cartesian product performed by createVersionInfoFindQuery().
1149
     *
1150
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
1151
     *
1152
     * @return string[][]
1153
     */
1154
    private function listVersionsHelper(SelectQuery $query)
1155
    {
1156
        $query->orderBy(
1157
            $this->dbHandler->quoteColumn('id', 'ezcontentobject_version')
1158
        );
1159
1160
        $statement = $query->prepare();
1161
        $statement->execute();
1162
1163
        $results = array();
1164
        $previousId = null;
1165
        foreach ($statement->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1166
            if ($row['ezcontentobject_version_id'] == $previousId) {
1167
                continue;
1168
            }
1169
1170
            $previousId = $row['ezcontentobject_version_id'];
1171
            $results[] = $row;
1172
        }
1173
1174
        return $results;
1175
    }
1176
1177
    /**
1178
     * Returns all version numbers for the given $contentId.
1179
     *
1180
     * @param mixed $contentId
1181
     *
1182
     * @return int[]
1183
     */
1184
    public function listVersionNumbers($contentId)
1185
    {
1186
        $query = $this->dbHandler->createSelectQuery();
1187
        $query->selectDistinct(
1188
            $this->dbHandler->quoteColumn('version')
1189
        )->from(
1190
            $this->dbHandler->quoteTable('ezcontentobject_version')
1191
        )->where(
1192
            $query->expr->eq(
1193
                $this->dbHandler->quoteColumn('contentobject_id'),
1194
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1195
            )
1196
        );
1197
1198
        $statement = $query->prepare();
1199
        $statement->execute();
1200
1201
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1202
    }
1203
1204
    /**
1205
     * Returns last version number for content identified by $contentId.
1206
     *
1207
     * @param int $contentId
1208
     *
1209
     * @return int
1210
     */
1211
    public function getLastVersionNumber($contentId)
1212
    {
1213
        $query = $this->dbHandler->createSelectQuery();
1214
        $query->select(
1215
            $query->expr->max($this->dbHandler->quoteColumn('version'))
1216
        )->from(
1217
            $this->dbHandler->quoteTable('ezcontentobject_version')
1218
        )->where(
1219
            $query->expr->eq(
1220
                $this->dbHandler->quoteColumn('contentobject_id'),
1221
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1222
            )
1223
        );
1224
1225
        $statement = $query->prepare();
1226
        $statement->execute();
1227
1228
        return (int)$statement->fetchColumn();
1229
    }
1230
1231
    /**
1232
     * Returns all IDs for locations that refer to $contentId.
1233
     *
1234
     * @param int $contentId
1235
     *
1236
     * @return int[]
1237
     */
1238
    public function getAllLocationIds($contentId)
1239
    {
1240
        $query = $this->dbHandler->createSelectQuery();
1241
        $query->select(
1242
            $this->dbHandler->quoteColumn('node_id')
1243
        )->from(
1244
            $this->dbHandler->quoteTable('ezcontentobject_tree')
1245
        )->where(
1246
            $query->expr->eq(
1247
                $this->dbHandler->quoteColumn('contentobject_id'),
1248
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1249
            )
1250
        );
1251
1252
        $statement = $query->prepare();
1253
        $statement->execute();
1254
1255
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1256
    }
1257
1258
    /**
1259
     * Returns all field IDs of $contentId grouped by their type.
1260
     * If $versionNo is set only field IDs for that version are returned.
1261
     * If $languageCode is set, only field IDs for that language are returned.
1262
     *
1263
     * @param int $contentId
1264
     * @param int|null $versionNo
1265
     * @param string|null $languageCode
1266
     *
1267
     * @return int[][]
1268
     */
1269
    public function getFieldIdsByType($contentId, $versionNo = null, $languageCode = null)
1270
    {
1271
        $query = $this->dbHandler->createSelectQuery();
1272
        $query->select(
1273
            $this->dbHandler->quoteColumn('id'),
1274
            $this->dbHandler->quoteColumn('data_type_string')
1275
        )->from(
1276
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1277
        )->where(
1278
            $query->expr->eq(
1279
                $this->dbHandler->quoteColumn('contentobject_id'),
1280
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1281
            )
1282
        );
1283
1284
        if (isset($versionNo)) {
1285
            $query->where(
1286
                $query->expr->eq(
1287
                    $this->dbHandler->quoteColumn('version'),
1288
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1289
                )
1290
            );
1291
        }
1292
1293
        if (isset($languageCode)) {
1294
            $query->where(
1295
                $query->expr->eq(
1296
                    $this->dbHandler->quoteColumn('language_code'),
1297
                    $query->bindValue($languageCode, null, \PDO::PARAM_STR)
1298
                )
1299
            );
1300
        }
1301
1302
        $statement = $query->prepare();
1303
        $statement->execute();
1304
1305
        $result = array();
1306
        foreach ($statement->fetchAll() as $row) {
1307
            if (!isset($result[$row['data_type_string']])) {
1308
                $result[$row['data_type_string']] = array();
1309
            }
1310
            $result[$row['data_type_string']][] = (int)$row['id'];
1311
        }
1312
1313
        return $result;
1314
    }
1315
1316
    /**
1317
     * Deletes relations to and from $contentId.
1318
     * If $versionNo is set only relations for that version are deleted.
1319
     *
1320
     * @param int $contentId
1321
     * @param int|null $versionNo
1322
     */
1323
    public function deleteRelations($contentId, $versionNo = null)
1324
    {
1325
        $query = $this->dbHandler->createDeleteQuery();
1326
        $query->deleteFrom(
1327
            $this->dbHandler->quoteTable('ezcontentobject_link')
1328
        );
1329
1330
        if (isset($versionNo)) {
1331
            $query->where(
1332
                $query->expr->lAnd(
1333
                    $query->expr->eq(
1334
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1335
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1336
                    ),
1337
                    $query->expr->eq(
1338
                        $this->dbHandler->quoteColumn('from_contentobject_version'),
1339
                        $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1340
                    )
1341
                )
1342
            );
1343
        } else {
1344
            $query->where(
1345
                $query->expr->lOr(
1346
                    $query->expr->eq(
1347
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1348
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1349
                    ),
1350
                    $query->expr->eq(
1351
                        $this->dbHandler->quoteColumn('to_contentobject_id'),
1352
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1353
                    )
1354
                )
1355
            );
1356
        }
1357
1358
        $query->prepare()->execute();
1359
    }
1360
1361
    /**
1362
     * Removes relations to Content with $contentId from Relation and RelationList field type fields.
1363
     *
1364
     * @param int $contentId
1365
     */
1366
    public function removeReverseFieldRelations($contentId)
1367
    {
1368
        $query = $this->dbHandler->createSelectQuery();
1369
        $query
1370
            ->select('ezcontentobject_attribute.*')
1371
            ->from('ezcontentobject_attribute')
1372
            ->innerJoin(
1373
                'ezcontentobject_link',
1374
                $query->expr->lAnd(
1375
                    $query->expr->eq(
1376
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1377
                        $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_attribute')
1378
                    ),
1379
                    $query->expr->eq(
1380
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1381
                        $this->dbHandler->quoteColumn('version', 'ezcontentobject_attribute')
1382
                    ),
1383
                    $query->expr->eq(
1384
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_link'),
1385
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_attribute')
1386
                    )
1387
                )
1388
            )
1389
            ->where(
1390
                $query->expr->eq(
1391
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1392
                    $query->bindValue($contentId, null, PDO::PARAM_INT)
1393
                ),
1394
                $query->expr->gt(
1395
                    $query->expr->bitAnd(
1396
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1397
                        $query->bindValue(8, null, PDO::PARAM_INT)
1398
                    ),
1399
                    0
1400
                )
1401
            );
1402
1403
        $statement = $query->prepare();
1404
        $statement->execute();
1405
1406
        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
1407
            if ($row['data_type_string'] === 'ezobjectrelation') {
1408
                $this->removeRelationFromRelationField($row);
1409
            }
1410
1411
            if ($row['data_type_string'] === 'ezobjectrelationlist') {
1412
                $this->removeRelationFromRelationListField($contentId, $row);
1413
            }
1414
        }
1415
    }
1416
1417
    /**
1418
     * Updates field value of RelationList field type identified by given $row data,
1419
     * removing relations toward given $contentId.
1420
     *
1421
     * @param int $contentId
1422
     * @param array $row
1423
     */
1424
    protected function removeRelationFromRelationListField($contentId, array $row)
1425
    {
1426
        $document = new DOMDocument('1.0', 'utf-8');
1427
        $document->loadXML($row['data_text']);
1428
1429
        $xpath = new DOMXPath($document);
1430
        $xpathExpression = "//related-objects/relation-list/relation-item[@contentobject-id='{$contentId}']";
1431
1432
        $relationItems = $xpath->query($xpathExpression);
1433
        foreach ($relationItems as $relationItem) {
1434
            $relationItem->parentNode->removeChild($relationItem);
1435
        }
1436
1437
        $query = $this->dbHandler->createUpdateQuery();
1438
        $query
1439
            ->update('ezcontentobject_attribute')
1440
            ->set(
1441
                'data_text',
1442
                $query->bindValue($document->saveXML(), null, PDO::PARAM_STR)
1443
            )
1444
            ->where(
1445
                $query->expr->lAnd(
1446
                    $query->expr->eq(
1447
                        $this->dbHandler->quoteColumn('id'),
1448
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1449
                    ),
1450
                    $query->expr->eq(
1451
                        $this->dbHandler->quoteColumn('version'),
1452
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1453
                    )
1454
                )
1455
            );
1456
1457
        $query->prepare()->execute();
1458
    }
1459
1460
    /**
1461
     * Updates field value of Relation field type identified by given $row data,
1462
     * removing relation data.
1463
     *
1464
     * @param array $row
1465
     */
1466
    protected function removeRelationFromRelationField(array $row)
1467
    {
1468
        $query = $this->dbHandler->createUpdateQuery();
1469
        $query
1470
            ->update('ezcontentobject_attribute')
1471
            ->set('data_int', $query->bindValue(null, null, PDO::PARAM_INT))
1472
            ->set('sort_key_int', $query->bindValue(0, null, PDO::PARAM_INT))
1473
            ->where(
1474
                $query->expr->lAnd(
1475
                    $query->expr->eq(
1476
                        $this->dbHandler->quoteColumn('id'),
1477
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1478
                    ),
1479
                    $query->expr->eq(
1480
                        $this->dbHandler->quoteColumn('version'),
1481
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1482
                    )
1483
                )
1484
            );
1485
1486
        $query->prepare()->execute();
1487
    }
1488
1489
    /**
1490
     * Deletes the field with the given $fieldId.
1491
     *
1492
     * @param int $fieldId
1493
     */
1494
    public function deleteField($fieldId)
1495
    {
1496
        $query = $this->dbHandler->createDeleteQuery();
1497
        $query->deleteFrom(
1498
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1499
        )->where(
1500
            $query->expr->eq(
1501
                $this->dbHandler->quoteColumn('id'),
1502
                $query->bindValue($fieldId, null, \PDO::PARAM_INT)
1503
            )
1504
        );
1505
1506
        $query->prepare()->execute();
1507
    }
1508
1509
    /**
1510
     * Deletes all fields of $contentId in all versions.
1511
     * If $versionNo is set only fields for that version are deleted.
1512
     *
1513
     * @param int $contentId
1514
     * @param int|null $versionNo
1515
     */
1516
    public function deleteFields($contentId, $versionNo = null)
1517
    {
1518
        $query = $this->dbHandler->createDeleteQuery();
1519
        $query->deleteFrom('ezcontentobject_attribute')
1520
            ->where(
1521
                $query->expr->eq(
1522
                    $this->dbHandler->quoteColumn('contentobject_id'),
1523
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1524
                )
1525
            );
1526
1527
        if (isset($versionNo)) {
1528
            $query->where(
1529
                $query->expr->eq(
1530
                    $this->dbHandler->quoteColumn('version'),
1531
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1532
                )
1533
            );
1534
        }
1535
1536
        $query->prepare()->execute();
1537
    }
1538
1539
    /**
1540
     * Deletes all versions of $contentId.
1541
     * If $versionNo is set only that version is deleted.
1542
     *
1543
     * @param int $contentId
1544
     * @param int|null $versionNo
1545
     */
1546
    public function deleteVersions($contentId, $versionNo = null)
1547
    {
1548
        $query = $this->dbHandler->createDeleteQuery();
1549
        $query->deleteFrom('ezcontentobject_version')
1550
            ->where(
1551
                $query->expr->eq(
1552
                    $this->dbHandler->quoteColumn('contentobject_id'),
1553
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1554
                )
1555
            );
1556
1557
        if (isset($versionNo)) {
1558
            $query->where(
1559
                $query->expr->eq(
1560
                    $this->dbHandler->quoteColumn('version'),
1561
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1562
                )
1563
            );
1564
        }
1565
1566
        $query->prepare()->execute();
1567
    }
1568
1569
    /**
1570
     * Deletes all names of $contentId.
1571
     * If $versionNo is set only names for that version are deleted.
1572
     *
1573
     * @param int $contentId
1574
     * @param int|null $versionNo
1575
     */
1576
    public function deleteNames($contentId, $versionNo = null)
1577
    {
1578
        $query = $this->dbHandler->createDeleteQuery();
1579
        $query->deleteFrom('ezcontentobject_name')
1580
            ->where(
1581
                $query->expr->eq(
1582
                    $this->dbHandler->quoteColumn('contentobject_id'),
1583
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1584
                )
1585
            );
1586
1587
        if (isset($versionNo)) {
1588
            $query->where(
1589
                $query->expr->eq(
1590
                    $this->dbHandler->quoteColumn('content_version'),
1591
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1592
                )
1593
            );
1594
        }
1595
1596
        $query->prepare()->execute();
1597
    }
1598
1599
    /**
1600
     * Sets the name for Content $contentId in version $version to $name in $language.
1601
     *
1602
     * @param int $contentId
1603
     * @param int $version
1604
     * @param string $name
1605
     * @param string $language
1606
     */
1607
    public function setName($contentId, $version, $name, $language)
1608
    {
1609
        $language = $this->languageHandler->loadByLanguageCode($language);
1610
1611
        // Is it an insert or an update ?
1612
        $qSelect = $this->dbHandler->createSelectQuery();
1613
        $qSelect
1614
            ->select(
1615
                $qSelect->alias($qSelect->expr->count('*'), 'count')
1616
            )
1617
            ->from($this->dbHandler->quoteTable('ezcontentobject_name'))
1618
            ->where(
1619
                $qSelect->expr->lAnd(
1620
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $qSelect->bindValue($contentId)),
1621
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_version'), $qSelect->bindValue($version)),
1622
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_translation'), $qSelect->bindValue($language->languageCode))
1623
                )
1624
            );
1625
        $stmt = $qSelect->prepare();
1626
        $stmt->execute();
1627
        $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
1628
1629
        $insert = $res[0]['count'] == 0;
1630
        if ($insert) {
1631
            $q = $this->dbHandler->createInsertQuery();
1632
            $q->insertInto($this->dbHandler->quoteTable('ezcontentobject_name'));
1633
        } else {
1634
            $q = $this->dbHandler->createUpdateQuery();
1635
            $q->update($this->dbHandler->quoteTable('ezcontentobject_name'))
1636
                ->where(
1637
                    $q->expr->lAnd(
1638
                        $q->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $q->bindValue($contentId)),
1639
                        $q->expr->eq($this->dbHandler->quoteColumn('content_version'), $q->bindValue($version)),
1640
                        $q->expr->eq($this->dbHandler->quoteColumn('content_translation'), $q->bindValue($language->languageCode))
1641
                    )
1642
                );
1643
        }
1644
1645
        $q->set(
1646
            $this->dbHandler->quoteColumn('contentobject_id'),
1647
            $q->bindValue($contentId, null, \PDO::PARAM_INT)
1648
        )->set(
1649
            $this->dbHandler->quoteColumn('content_version'),
1650
            $q->bindValue($version, null, \PDO::PARAM_INT)
1651
        )->set(
1652
            $this->dbHandler->quoteColumn('language_id'),
1653
            '(' . $this->getLanguageQuery()->getQuery() . ')'
1654
        )->set(
1655
            $this->dbHandler->quoteColumn('content_translation'),
1656
            $q->bindValue($language->languageCode)
1657
        )->set(
1658
            $this->dbHandler->quoteColumn('real_translation'),
1659
            $q->bindValue($language->languageCode)
1660
        )->set(
1661
            $this->dbHandler->quoteColumn('name'),
1662
            $q->bindValue($name)
1663
        );
1664
        $q->bindValue($language->id, ':languageId', \PDO::PARAM_INT);
1665
        $q->bindValue($contentId, ':contentId', \PDO::PARAM_INT);
1666
        $q->prepare()->execute();
1667
    }
1668
1669
    /**
1670
     * Returns a language sub select query for setName.
1671
     *
1672
     * Return sub select query which gets proper language mask for alwaysAvailable Content.
1673
     *
1674
     * @return \eZ\Publish\Core\Persistence\Database\SelectQuery
1675
     */
1676
    private function getLanguageQuery()
1677
    {
1678
        $languageQuery = $this->dbHandler->createSelectQuery();
1679
        $languageQuery
1680
            ->select(
1681
                $languageQuery->expr->searchedCase(
1682
                    [
1683
                        $languageQuery->expr->lAnd(
1684
                            $languageQuery->expr->eq(
1685
                                $this->dbHandler->quoteColumn('initial_language_id'),
1686
                                ':languageId'
1687
                            ),
1688
                            // wrap bitwise check into another "neq" to provide cross-DBMS compatibility
1689
                            $languageQuery->expr->neq(
1690
                                $languageQuery->expr->bitAnd(
1691
                                    $this->dbHandler->quoteColumn('language_mask'),
1692
                                    ':languageId'
1693
                                ),
1694
                                0
1695
                            )
1696
                        ),
1697
                        $languageQuery->expr->bitOr(
1698
                            ':languageId',
1699
                            1
1700
                        ),
1701
                    ],
1702
                    ':languageId'
1703
                )
1704
            )
1705
            ->from('ezcontentobject')
1706
            ->where(
1707
                $languageQuery->expr->eq(
1708
                    'id',
1709
                    ':contentId'
1710
                )
1711
            );
1712
1713
        return $languageQuery;
1714
    }
1715
1716
    /**
1717
     * Deletes the actual content object referred to by $contentId.
1718
     *
1719
     * @param int $contentId
1720
     */
1721
    public function deleteContent($contentId)
1722
    {
1723
        $query = $this->dbHandler->createDeleteQuery();
1724
        $query->deleteFrom('ezcontentobject')
1725
            ->where(
1726
                $query->expr->eq(
1727
                    $this->dbHandler->quoteColumn('id'),
1728
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1729
                )
1730
            );
1731
1732
        $query->prepare()->execute();
1733
    }
1734
1735
    /**
1736
     * Loads relations from $contentId to published content, optionally only from $contentVersionNo.
1737
     *
1738
     * $relationType can also be filtered.
1739
     *
1740
     * @param int $contentId
1741
     * @param int $contentVersionNo
1742
     * @param int $relationType
1743
     *
1744
     * @return string[][] array of relation data
1745
     */
1746
    public function loadRelations($contentId, $contentVersionNo = null, $relationType = null)
1747
    {
1748
        $query = $this->queryBuilder->createRelationFindQuery();
1749
        $query->innerJoin(
1750
            $query->alias(
1751
                $this->dbHandler->quoteTable('ezcontentobject'),
1752
                'ezcontentobject_to'
1753
            ),
1754
            $query->expr->lAnd(
1755
                $query->expr->eq(
1756
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1757
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject_to')
1758
                ),
1759
                $query->expr->eq(
1760
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_to'),
1761
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1762
                )
1763
            )
1764
        )->where(
1765
            $query->expr->eq(
1766
                $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1767
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1768
            )
1769
        );
1770
1771
        // source version number
1772
        if (isset($contentVersionNo)) {
1773
            $query->where(
1774
                $query->expr->eq(
1775
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1776
                    $query->bindValue($contentVersionNo, null, \PDO::PARAM_INT)
1777
                )
1778
            );
1779
        } else { // from published version only
1780
            $query->from(
1781
                $this->dbHandler->quoteTable('ezcontentobject')
1782
            )->where(
1783
                $query->expr->lAnd(
1784
                    $query->expr->eq(
1785
                        $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1786
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1787
                    ),
1788
                    $query->expr->eq(
1789
                        $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1790
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1791
                    )
1792
                )
1793
            );
1794
        }
1795
1796
        // relation type
1797 View Code Duplication
        if (isset($relationType)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1798
            $query->where(
1799
                $query->expr->gt(
1800
                    $query->expr->bitAnd(
1801
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1802
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1803
                    ),
1804
                    0
1805
                )
1806
            );
1807
        }
1808
1809
        $statement = $query->prepare();
1810
        $statement->execute();
1811
1812
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1813
    }
1814
1815
    /**
1816
     * Loads data that related to $toContentId.
1817
     *
1818
     * @param int $toContentId
1819
     * @param int $relationType
1820
     *
1821
     * @return mixed[][] Content data, array structured like {@see \eZ\Publish\Core\Persistence\Legacy\Content\Gateway::load()}
1822
     */
1823
    public function loadReverseRelations($toContentId, $relationType = null)
1824
    {
1825
        $query = $this->queryBuilder->createRelationFindQuery();
1826
        $query->where(
1827
            $query->expr->eq(
1828
                $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1829
                $query->bindValue($toContentId, null, \PDO::PARAM_INT)
1830
            )
1831
        );
1832
1833
        // ezcontentobject join
1834
        $query->from(
1835
            $this->dbHandler->quoteTable('ezcontentobject')
1836
        )->where(
1837
            $query->expr->lAnd(
1838
                $query->expr->eq(
1839
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1840
                    $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1841
                ),
1842
                $query->expr->eq(
1843
                    $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1844
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1845
                ),
1846
                $query->expr->eq(
1847
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject'),
1848
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1849
                )
1850
            )
1851
        );
1852
1853
        // relation type
1854 View Code Duplication
        if (isset($relationType)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1855
            $query->where(
1856
                $query->expr->gt(
1857
                    $query->expr->bitAnd(
1858
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1859
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1860
                    ),
1861
                    0
1862
                )
1863
            );
1864
        }
1865
1866
        $statement = $query->prepare();
1867
1868
        $statement->execute();
1869
1870
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1871
    }
1872
1873
    /**
1874
     * Inserts a new relation database record.
1875
     *
1876
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
1877
     *
1878
     * @return int ID the inserted ID
1879
     */
1880 View Code Duplication
    public function insertRelation(RelationCreateStruct $createStruct)
1881
    {
1882
        $q = $this->dbHandler->createInsertQuery();
1883
        $q->insertInto(
1884
            $this->dbHandler->quoteTable('ezcontentobject_link')
1885
        )->set(
1886
            $this->dbHandler->quoteColumn('id'),
1887
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_link', 'id')
1888
        )->set(
1889
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
1890
            $q->bindValue((int)$createStruct->sourceFieldDefinitionId, null, \PDO::PARAM_INT)
1891
        )->set(
1892
            $this->dbHandler->quoteColumn('from_contentobject_id'),
1893
            $q->bindValue($createStruct->sourceContentId, null, \PDO::PARAM_INT)
1894
        )->set(
1895
            $this->dbHandler->quoteColumn('from_contentobject_version'),
1896
            $q->bindValue($createStruct->sourceContentVersionNo, null, \PDO::PARAM_INT)
1897
        )->set(
1898
            $this->dbHandler->quoteColumn('relation_type'),
1899
            $q->bindValue($createStruct->type, null, \PDO::PARAM_INT)
1900
        )->set(
1901
            $this->dbHandler->quoteColumn('to_contentobject_id'),
1902
            $q->bindValue($createStruct->destinationContentId, null, \PDO::PARAM_INT)
1903
        );
1904
1905
        $q->prepare()->execute();
1906
1907
        return $this->dbHandler->lastInsertId(
1908
            $this->dbHandler->getSequenceName('ezcontentobject_link', 'id')
1909
        );
1910
    }
1911
1912
    /**
1913
     * Deletes the relation with the given $relationId.
1914
     *
1915
     * @param int $relationId
1916
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
1917
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
1918
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
1919
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
1920
     */
1921
    public function deleteRelation($relationId, $type)
1922
    {
1923
        // Legacy Storage stores COMMON, LINK and EMBED types using bitmask, therefore first load
1924
        // existing relation type by given $relationId for comparison
1925
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1926
        $query = $this->dbHandler->createSelectQuery();
1927
        $query->select(
1928
            $this->dbHandler->quoteColumn('relation_type')
1929
        )->from(
1930
            $this->dbHandler->quoteTable('ezcontentobject_link')
1931
        )->where(
1932
            $query->expr->eq(
1933
                $this->dbHandler->quoteColumn('id'),
1934
                $query->bindValue($relationId, null, \PDO::PARAM_INT)
1935
            )
1936
        );
1937
1938
        $statement = $query->prepare();
1939
        $statement->execute();
1940
        $loadedRelationType = $statement->fetchColumn();
1941
1942
        if (!$loadedRelationType) {
1943
            return;
1944
        }
1945
1946
        // If relation type matches then delete
1947
        if ($loadedRelationType == $type) {
1948
            /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1949
            $query = $this->dbHandler->createDeleteQuery();
1950
            $query->deleteFrom(
1951
                'ezcontentobject_link'
1952
            )->where(
1953
                $query->expr->eq(
1954
                    $this->dbHandler->quoteColumn('id'),
1955
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1956
                )
1957
            );
1958
1959
            $query->prepare()->execute();
1960
        } elseif ($loadedRelationType & $type) { // If relation type is composite update bitmask
1961
            /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
1962
            $query = $this->dbHandler->createUpdateQuery();
1963
            $query->update(
1964
                $this->dbHandler->quoteTable('ezcontentobject_link')
1965
            )->set(
1966
                $this->dbHandler->quoteColumn('relation_type'),
1967
                $query->expr->bitAnd(
1968
                    $this->dbHandler->quoteColumn('relation_type'),
1969
                    $query->bindValue(~$type, null, \PDO::PARAM_INT)
1970
                )
1971
            )->where(
1972
                $query->expr->eq(
1973
                    $this->dbHandler->quoteColumn('id'),
1974
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1975
                )
1976
            );
1977
1978
            $query->prepare()->execute();
1979
        } else {
1980
            // No match, do nothing
1981
        }
1982
    }
1983
1984
    /**
1985
     * Returns all Content IDs for a given $contentTypeId.
1986
     *
1987
     * @param int $contentTypeId
1988
     *
1989
     * @return int[]
1990
     */
1991
    public function getContentIdsByContentTypeId($contentTypeId)
1992
    {
1993
        $query = $this->dbHandler->createSelectQuery();
1994
        $query
1995
            ->select($this->dbHandler->quoteColumn('id'))
1996
            ->from($this->dbHandler->quoteTable('ezcontentobject'))
1997
            ->where(
1998
                $query->expr->eq(
1999
                    $this->dbHandler->quoteColumn('contentclass_id'),
2000
                    $query->bindValue($contentTypeId, null, PDO::PARAM_INT)
2001
                )
2002
            );
2003
2004
        $statement = $query->prepare();
2005
        $statement->execute();
2006
2007
        return $statement->fetchAll(PDO::FETCH_COLUMN);
2008
    }
2009
2010
    /**
2011
     * Load name data for set of content id's and corresponding version number.
2012
     *
2013
     * @param array[] $rows array of hashes with 'id' and 'version' to load names for
2014
     *
2015
     * @return array
2016
     */
2017
    public function loadVersionedNameData($rows)
2018
    {
2019
        $query = $this->queryBuilder->createNamesQuery();
2020
        $conditions = array();
2021
        foreach ($rows as $row) {
2022
            $conditions[] = $query->expr->lAnd(
2023
                $query->expr->eq(
2024
                    $this->dbHandler->quoteColumn('contentobject_id'),
2025
                    $query->bindValue($row['id'], null, \PDO::PARAM_INT)
2026
                ),
2027
                $query->expr->eq(
2028
                    $this->dbHandler->quoteColumn('content_version'),
2029
                    $query->bindValue($row['version'], null, \PDO::PARAM_INT)
2030
                )
2031
            );
2032
        }
2033
2034
        $query->where($query->expr->lOr($conditions));
2035
        $stmt = $query->prepare();
2036
        $stmt->execute();
2037
2038
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
2039
    }
2040
2041
    /**
2042
     * Batch method for copying all relation meta data for copied Content object.
2043
     *
2044
     * {@inheritdoc}
2045
     *
2046
     * @param int $originalContentId
2047
     * @param int $copiedContentId
2048
     * @param int|null $versionNo If specified only copy for a given version number, otherwise all.
2049
     */
2050
    public function copyRelations($originalContentId, $copiedContentId, $versionNo = null)
2051
    {
2052
        // Given we can retain all columns, we just create copies with new `from_contentobject_id` using INSERT INTO SELECT
2053
        $sql = 'INSERT INTO ezcontentobject_link ( contentclassattribute_id, from_contentobject_id, from_contentobject_version, relation_type, to_contentobject_id )
2054
                SELECT  L2.contentclassattribute_id, :copied_id, L2.from_contentobject_version, L2.relation_type, L2.to_contentobject_id
2055
                FROM    ezcontentobject_link AS L2
2056
                WHERE   L2.from_contentobject_id = :original_id';
2057
2058
        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...
2059
            $stmt = $this->connection->prepare($sql . ' AND L2.from_contentobject_version = :version');
2060
            $stmt->bindValue('version', $versionNo, PDO::PARAM_INT);
2061
        } else {
2062
            $stmt = $this->connection->prepare($sql);
2063
        }
2064
2065
        $stmt->bindValue('original_id', $originalContentId, PDO::PARAM_INT);
2066
        $stmt->bindValue('copied_id', $copiedContentId, PDO::PARAM_INT);
2067
2068
        $stmt->execute();
2069
    }
2070
2071
    /**
2072
     * Remove the specified translation from the Content Object Version.
2073
     *
2074
     * @param int $contentId
2075
     * @param string $languageCode language code of the translation
2076
     * @throws \Doctrine\DBAL\DBALException
2077
     */
2078 View Code Duplication
    public function deleteTranslationFromContent($contentId, $languageCode)
2079
    {
2080
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2081
2082
        $this->connection->beginTransaction();
2083
        try {
2084
            $this->deleteTranslationFromContentVersions($contentId, $language->id);
2085
            $this->deleteTranslationFromContentNames($contentId, $languageCode);
2086
            $this->deleteTranslationFromContentObject($contentId, $language->id);
2087
2088
            $this->connection->commit();
2089
        } catch (DBALException $e) {
2090
            $this->connection->rollBack();
2091
            throw $e;
2092
        }
2093
    }
2094
2095
    /**
2096
     * Delete Content fields (attributes) for the given Translation.
2097
     * If $versionNo is given, fields for that Version only will be deleted.
2098
     *
2099
     * @param string $languageCode
2100
     * @param int $contentId
2101
     * @param int $versionNo (optional) filter by versionNo
2102
     */
2103 View Code Duplication
    public function deleteTranslatedFields($languageCode, $contentId, $versionNo = null)
2104
    {
2105
        $query = $this->connection->createQueryBuilder();
2106
        $query
2107
            ->delete('ezcontentobject_attribute')
2108
            ->where('contentobject_id = :contentId')
2109
            ->andWhere('language_code = :languageCode')
2110
            ->setParameters(
2111
                [
2112
                    ':contentId' => $contentId,
2113
                    ':languageCode' => $languageCode,
2114
                ]
2115
            )
2116
        ;
2117
2118
        if (null !== $versionNo) {
2119
            $query
2120
                ->andWhere('version = :versionNo')
2121
                ->setParameter(':versionNo', $versionNo)
2122
            ;
2123
        }
2124
2125
        $query->execute();
2126
    }
2127
2128
    /**
2129
     * Delete the specified Translation from the given Version.
2130
     *
2131
     * @param int $contentId
2132
     * @param int $versionNo
2133
     * @param string $languageCode
2134
     * @throws \Doctrine\DBAL\DBALException
2135
     */
2136 View Code Duplication
    public function deleteTranslationFromVersion($contentId, $versionNo, $languageCode)
2137
    {
2138
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2139
2140
        $this->connection->beginTransaction();
2141
        try {
2142
            $this->deleteTranslationFromContentVersions($contentId, $language->id, $versionNo);
2143
            $this->deleteTranslationFromContentNames($contentId, $languageCode, $versionNo);
2144
2145
            $this->connection->commit();
2146
        } catch (DBALException $e) {
2147
            $this->connection->rollBack();
2148
            throw $e;
2149
        }
2150
    }
2151
2152
    /**
2153
     * Delete translation from the ezcontentobject_name table.
2154
     *
2155
     * @param int $contentId
2156
     * @param string $languageCode
2157
     * @param int $versionNo optional, if specified, apply to this Version only.
2158
     */
2159 View Code Duplication
    private function deleteTranslationFromContentNames($contentId, $languageCode, $versionNo = null)
2160
    {
2161
        $query = $this->connection->createQueryBuilder();
2162
        $query
2163
            ->delete('ezcontentobject_name')
2164
            ->where('contentobject_id=:contentId')
2165
            ->andWhere('real_translation=:languageCode')
2166
            ->setParameters(
2167
                [
2168
                    ':languageCode' => $languageCode,
2169
                    ':contentId' => $contentId,
2170
                ]
2171
            )
2172
        ;
2173
2174
        if (null !== $versionNo) {
2175
            $query
2176
                ->andWhere('content_version = :versionNo')
2177
                ->setParameter(':versionNo', $versionNo)
2178
            ;
2179
        }
2180
2181
        $query->execute();
2182
    }
2183
2184
    /**
2185
     * Remove language from language_mask of ezcontentobject.
2186
     *
2187
     * @param int $contentId
2188
     * @param int $languageId
2189
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2190
     */
2191
    private function deleteTranslationFromContentObject($contentId, $languageId)
2192
    {
2193
        $query = $this->connection->createQueryBuilder();
2194
        $query->update('ezcontentobject')
2195
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2196
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2197
            ->set('modified', ':now')
2198
            ->where('id = :contentId')
2199
            ->andWhere(
2200
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2201
                $query->expr()->andX(
2202
                    'language_mask & ~ ' . $languageId . ' <> 0',
2203
                    'language_mask & ~ ' . $languageId . ' <> 1'
2204
                )
2205
            )
2206
            ->setParameter(':now', time())
2207
            ->setParameter(':contentId', $contentId)
2208
        ;
2209
2210
        $rowCount = $query->execute();
2211
2212
        // no rows updated means that most likely somehow it was the last remaining translation
2213
        if ($rowCount === 0) {
2214
            throw new BadStateException(
2215
                '$languageCode',
2216
                'Specified translation is the only one Content Object Version has'
2217
            );
2218
        }
2219
    }
2220
2221
    /**
2222
     * Remove language from language_mask of ezcontentobject_version and update initialLanguageId
2223
     * if it matches the removed one.
2224
     *
2225
     * @param int $contentId
2226
     * @param int $languageId
2227
     * @param int $versionNo optional, if specified, apply to this Version only.
2228
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2229
     */
2230
    private function deleteTranslationFromContentVersions($contentId, $languageId, $versionNo = null)
2231
    {
2232
        $query = $this->connection->createQueryBuilder();
2233
        $query->update('ezcontentobject_version')
2234
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2235
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2236
            ->set('modified', ':now')
2237
            // update initial_language_id only if it matches removed translation languageId
2238
            ->set(
2239
                'initial_language_id',
2240
                'CASE WHEN initial_language_id = :languageId ' .
2241
                'THEN (SELECT initial_language_id AS main_language_id FROM ezcontentobject c WHERE c.id = :contentId) ' .
2242
                'ELSE initial_language_id END'
2243
            )
2244
            ->where('contentobject_id = :contentId')
2245
            ->andWhere(
2246
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2247
                $query->expr()->andX(
2248
                    'language_mask & ~ ' . $languageId . ' <> 0',
2249
                    'language_mask & ~ ' . $languageId . ' <> 1'
2250
                )
2251
            )
2252
            ->setParameter(':now', time())
2253
            ->setParameter(':contentId', $contentId)
2254
            ->setParameter(':languageId', $languageId)
2255
        ;
2256
2257
        if (null !== $versionNo) {
2258
            $query
2259
                ->andWhere('version = :versionNo')
2260
                ->setParameter(':versionNo', $versionNo)
2261
            ;
2262
        }
2263
2264
        $rowCount = $query->execute();
2265
2266
        // no rows updated means that most likely somehow it was the last remaining translation
2267
        if ($rowCount === 0) {
2268
            throw new BadStateException(
2269
                '$languageCode',
2270
                'Specified translation is the only one Content Object Version has'
2271
            );
2272
        }
2273
    }
2274
}
2275