Completed
Push — master ( f2dfe9...84d91c )
by André
35:37 queued 16:15
created

DoctrineDatabase::updateContent()   C

Complexity

Conditions 10
Paths 256

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 256
nop 3
dl 0
loc 65
rs 5.6703
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

class Alien {}

class Dalek extends Alien {}

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

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
103
        $this->languageMaskGenerator = $languageMaskGenerator;
104
    }
105
106
    /**
107
     * Get context definition for external storage layers.
108
     *
109
     * @return array
110
     */
111
    public function getContext()
112
    {
113
        return array(
114
            'identifier' => 'LegacyStorage',
115
            'connection' => $this->dbHandler,
116
        );
117
    }
118
119
    /**
120
     * Inserts a new content object.
121
     *
122
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
123
     * @param mixed $currentVersionNo
124
     *
125
     * @return int ID
126
     */
127
    public function insertContentObject(CreateStruct $struct, $currentVersionNo = 1)
128
    {
129
        $initialLanguageId = !empty($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId;
130
        $initialLanguageCode = $this->languageHandler->load($initialLanguageId)->languageCode;
131
132
        if (isset($struct->name[$initialLanguageCode])) {
133
            $name = $struct->name[$initialLanguageCode];
134
        } else {
135
            $name = '';
136
        }
137
138
        $q = $this->dbHandler->createInsertQuery();
139
        $q->insertInto(
140
            $this->dbHandler->quoteTable('ezcontentobject')
141
        )->set(
142
            $this->dbHandler->quoteColumn('id'),
143
            $this->dbHandler->getAutoIncrementValue('ezcontentobject', 'id')
144
        )->set(
145
            $this->dbHandler->quoteColumn('current_version'),
146
            $q->bindValue($currentVersionNo, null, \PDO::PARAM_INT)
147
        )->set(
148
            $this->dbHandler->quoteColumn('name'),
149
            $q->bindValue($name, null, \PDO::PARAM_STR)
150
        )->set(
151
            $this->dbHandler->quoteColumn('contentclass_id'),
152
            $q->bindValue($struct->typeId, null, \PDO::PARAM_INT)
153
        )->set(
154
            $this->dbHandler->quoteColumn('section_id'),
155
            $q->bindValue($struct->sectionId, null, \PDO::PARAM_INT)
156
        )->set(
157
            $this->dbHandler->quoteColumn('owner_id'),
158
            $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
159
        )->set(
160
            $this->dbHandler->quoteColumn('initial_language_id'),
161
            $q->bindValue($initialLanguageId, null, \PDO::PARAM_INT)
162
        )->set(
163
            $this->dbHandler->quoteColumn('remote_id'),
164
            $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
165
        )->set(
166
            $this->dbHandler->quoteColumn('modified'),
167
            $q->bindValue(0, null, \PDO::PARAM_INT)
168
        )->set(
169
            $this->dbHandler->quoteColumn('published'),
170
            $q->bindValue(0, null, \PDO::PARAM_INT)
171
        )->set(
172
            $this->dbHandler->quoteColumn('status'),
173
            $q->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
174
        )->set(
175
            $this->dbHandler->quoteColumn('language_mask'),
176
            $q->bindValue(
177
                $this->generateLanguageMask(
178
                    $struct->fields,
179
                    $initialLanguageCode,
180
                    $struct->alwaysAvailable
181
                ),
182
                null,
183
                \PDO::PARAM_INT
184
            )
185
        );
186
187
        $q->prepare()->execute();
188
189
        return $this->dbHandler->lastInsertId(
190
            $this->dbHandler->getSequenceName('ezcontentobject', 'id')
191
        );
192
    }
193
194
    /**
195
     * Generates a language mask for $fields.
196
     *
197
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
198
     * @param string $initialLanguageCode
199
     * @param bool $isAlwaysAvailable
200
     *
201
     * @return int
202
     */
203
    protected function generateLanguageMask(array $fields, string $initialLanguageCode, bool $isAlwaysAvailable): int
204
    {
205
        $languages = array($initialLanguageCode => true);
206
        foreach ($fields as $field) {
207
            if (isset($languages[$field->languageCode])) {
208
                continue;
209
            }
210
211
            $languages[$field->languageCode] = true;
212
        }
213
214
        return $this->languageMaskGenerator->generateLanguageMaskFromLanguageCodes(array_keys($languages), $isAlwaysAvailable);
215
    }
216
217
    /**
218
     * Inserts a new version.
219
     *
220
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo
221
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
222
     *
223
     * @return int ID
224
     */
225
    public function insertVersion(VersionInfo $versionInfo, array $fields)
226
    {
227
        /** @var $q \eZ\Publish\Core\Persistence\Database\InsertQuery */
228
        $q = $this->dbHandler->createInsertQuery();
229
        $q->insertInto(
230
            $this->dbHandler->quoteTable('ezcontentobject_version')
231
        )->set(
232
            $this->dbHandler->quoteColumn('id'),
233
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_version', 'id')
234
        )->set(
235
            $this->dbHandler->quoteColumn('version'),
236
            $q->bindValue($versionInfo->versionNo, null, \PDO::PARAM_INT)
237
        )->set(
238
            $this->dbHandler->quoteColumn('modified'),
239
            $q->bindValue($versionInfo->modificationDate, null, \PDO::PARAM_INT)
240
        )->set(
241
            $this->dbHandler->quoteColumn('creator_id'),
242
            $q->bindValue($versionInfo->creatorId, null, \PDO::PARAM_INT)
243
        )->set(
244
            $this->dbHandler->quoteColumn('created'),
245
            $q->bindValue($versionInfo->creationDate, null, \PDO::PARAM_INT)
246
        )->set(
247
            $this->dbHandler->quoteColumn('status'),
248
            $q->bindValue($versionInfo->status, null, \PDO::PARAM_INT)
249
        )->set(
250
            $this->dbHandler->quoteColumn('initial_language_id'),
251
            $q->bindValue(
252
                $this->languageHandler->loadByLanguageCode($versionInfo->initialLanguageCode)->id,
253
                null,
254
                \PDO::PARAM_INT
255
            )
256
        )->set(
257
            $this->dbHandler->quoteColumn('contentobject_id'),
258
            $q->bindValue($versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
259
        )->set(
260
            // As described in field mapping document
261
            $this->dbHandler->quoteColumn('workflow_event_pos'),
262
            $q->bindValue(0, null, \PDO::PARAM_INT)
263
        )->set(
264
            $this->dbHandler->quoteColumn('language_mask'),
265
            $q->bindValue(
266
                $this->generateLanguageMask(
267
                    $fields,
268
                    $versionInfo->initialLanguageCode,
269
                    $versionInfo->contentInfo->alwaysAvailable
270
                ),
271
                null,
272
                \PDO::PARAM_INT
273
            )
274
        );
275
276
        $q->prepare()->execute();
277
278
        return $this->dbHandler->lastInsertId(
279
            $this->dbHandler->getSequenceName('ezcontentobject_version', 'id')
280
        );
281
    }
282
283
    /**
284
     * Updates an existing content identified by $contentId in respect to $struct.
285
     *
286
     * @param int $contentId
287
     * @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $struct
288
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $prePublishVersionInfo Provided on publish
289
     */
290
    public function updateContent($contentId, MetadataUpdateStruct $struct, VersionInfo $prePublishVersionInfo = null)
291
    {
292
        $q = $this->dbHandler->createUpdateQuery();
293
        $q->update($this->dbHandler->quoteTable('ezcontentobject'));
294
295
        if (isset($struct->name)) {
296
            $q->set(
297
                $this->dbHandler->quoteColumn('name'),
298
                $q->bindValue($struct->name, null, \PDO::PARAM_STR)
299
            );
300
        }
301
        if (isset($struct->mainLanguageId)) {
302
            $q->set(
303
                $this->dbHandler->quoteColumn('initial_language_id'),
304
                $q->bindValue($struct->mainLanguageId, null, \PDO::PARAM_INT)
305
            );
306
        }
307
        if (isset($struct->modificationDate)) {
308
            $q->set(
309
                $this->dbHandler->quoteColumn('modified'),
310
                $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
311
            );
312
        }
313
        if (isset($struct->ownerId)) {
314
            $q->set(
315
                $this->dbHandler->quoteColumn('owner_id'),
316
                $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
317
            );
318
        }
319
        if (isset($struct->publicationDate)) {
320
            $q->set(
321
                $this->dbHandler->quoteColumn('published'),
322
                $q->bindValue($struct->publicationDate, null, \PDO::PARAM_INT)
323
            );
324
        }
325
        if (isset($struct->remoteId)) {
326
            $q->set(
327
                $this->dbHandler->quoteColumn('remote_id'),
328
                $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
329
            );
330
        }
331
        if ($prePublishVersionInfo !== null) {
332
            $mask = $this->languageMaskGenerator->generateLanguageMaskFromLanguageCodes(
333
                $prePublishVersionInfo->languageCodes,
334
                $struct->alwaysAvailable ?? $prePublishVersionInfo->contentInfo->alwaysAvailable
335
            );
336
337
            $q->set(
338
                $this->dbHandler->quoteColumn('language_mask'),
339
                $q->bindValue($mask, null, \PDO::PARAM_INT)
340
            );
341
        }
342
        $q->where(
343
            $q->expr->eq(
344
                $this->dbHandler->quoteColumn('id'),
345
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
346
            )
347
        );
348
        $q->prepare()->execute();
349
350
        // Handle alwaysAvailable flag update separately as it's a more complex task and has impact on several tables
351
        if (isset($struct->alwaysAvailable) || isset($struct->mainLanguageId)) {
352
            $this->updateAlwaysAvailableFlag($contentId, $struct->alwaysAvailable);
353
        }
354
    }
355
356
    /**
357
     * Updates version $versionNo for content identified by $contentId, in respect to $struct.
358
     *
359
     * @param int $contentId
360
     * @param int $versionNo
361
     * @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $struct
362
     */
363
    public function updateVersion($contentId, $versionNo, UpdateStruct $struct)
364
    {
365
        $q = $this->dbHandler->createUpdateQuery();
366
        $q->update(
367
            $this->dbHandler->quoteTable('ezcontentobject_version')
368
        )->set(
369
            $this->dbHandler->quoteColumn('creator_id'),
370
            $q->bindValue($struct->creatorId, null, \PDO::PARAM_INT)
371
        )->set(
372
            $this->dbHandler->quoteColumn('modified'),
373
            $q->bindValue($struct->modificationDate, null, \PDO::PARAM_INT)
374
        )->set(
375
            $this->dbHandler->quoteColumn('initial_language_id'),
376
            $q->bindValue($struct->initialLanguageId, null, \PDO::PARAM_INT)
377
        )->set(
378
            $this->dbHandler->quoteColumn('language_mask'),
379
            $q->expr->bitOr(
380
                $this->dbHandler->quoteColumn('language_mask'),
381
                $q->bindValue(
382
                    $this->generateLanguageMask(
383
                        $struct->fields,
384
                        $this->languageHandler->load($struct->initialLanguageId)->languageCode,
385
                        false
386
                    ),
387
                    null,
388
                    \PDO::PARAM_INT
389
                )
390
            )
391
        )->where(
392
            $q->expr->lAnd(
393
                $q->expr->eq(
394
                    $this->dbHandler->quoteColumn('contentobject_id'),
395
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
396
                ),
397
                $q->expr->eq(
398
                    $this->dbHandler->quoteColumn('version'),
399
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
400
                )
401
            )
402
        );
403
        $q->prepare()->execute();
404
    }
405
406
    /**
407
     * Updates "always available" flag for Content identified by $contentId, in respect to
408
     * Content's current main language and optionally new $alwaysAvailable state.
409
     *
410
     * @param int $contentId
411
     * @param bool|null $alwaysAvailable New "always available" value or null if not defined
412
     */
413
    public function updateAlwaysAvailableFlag($contentId, $alwaysAvailable = null)
414
    {
415
        // We will need to know some info on the current language mask to update the flag
416
        // everywhere needed
417
        $contentInfoRow = $this->loadContentInfo($contentId);
418
        if (!isset($alwaysAvailable)) {
419
            $alwaysAvailable = (bool)$contentInfoRow['language_mask'] & 1;
420
        }
421
422
        /** @var $q \eZ\Publish\Core\Persistence\Database\UpdateQuery */
423
        $q = $this->dbHandler->createUpdateQuery();
424
        $q
425
            ->update($this->dbHandler->quoteTable('ezcontentobject'))
426
            ->set(
427
                $this->dbHandler->quoteColumn('language_mask'),
428
                $alwaysAvailable ?
429
                    $q->expr->bitOr($this->dbHandler->quoteColumn('language_mask'), 1) :
430
                    $q->expr->bitAnd($this->dbHandler->quoteColumn('language_mask'), -2)
431
            )
432
            ->where(
433
                $q->expr->eq(
434
                    $this->dbHandler->quoteColumn('id'),
435
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
436
                )
437
            );
438
        $q->prepare()->execute();
439
440
        // Now we need to update ezcontentobject_name
441
        /** @var $qName \eZ\Publish\Core\Persistence\Database\UpdateQuery */
442
        $qName = $this->dbHandler->createUpdateQuery();
443
        $qName
444
            ->update($this->dbHandler->quoteTable('ezcontentobject_name'))
445
            ->set(
446
                $this->dbHandler->quoteColumn('language_id'),
447
                $alwaysAvailable ?
448
                    $qName->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
449
                    $qName->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
450
            )
451
            ->where(
452
                $qName->expr->lAnd(
453
                    $qName->expr->eq(
454
                        $this->dbHandler->quoteColumn('contentobject_id'),
455
                        $qName->bindValue($contentId, null, \PDO::PARAM_INT)
456
                    ),
457
                    $qName->expr->eq(
458
                        $this->dbHandler->quoteColumn('content_version'),
459
                        $qName->bindValue(
460
                            $contentInfoRow['current_version'],
461
                            null,
462
                            \PDO::PARAM_INT
463
                        )
464
                    )
465
                )
466
            );
467
        $qName->prepare()->execute();
468
469
        // Now update ezcontentobject_attribute for current version
470
        // Create update query that will be reused
471
        /** @var $qAttr \eZ\Publish\Core\Persistence\Database\UpdateQuery */
472
        $qAttr = $this->dbHandler->createUpdateQuery();
473
        $qAttr
474
            ->update($this->dbHandler->quoteTable('ezcontentobject_attribute'))
475
            ->where(
476
                $qAttr->expr->lAnd(
477
                    $qAttr->expr->eq(
478
                        $this->dbHandler->quoteColumn('contentobject_id'),
479
                        $qAttr->bindValue($contentId, null, \PDO::PARAM_INT)
480
                    ),
481
                    $qAttr->expr->eq(
482
                        $this->dbHandler->quoteColumn('version'),
483
                        $qAttr->bindValue(
484
                            $contentInfoRow['current_version'],
485
                            null,
486
                            \PDO::PARAM_INT
487
                        )
488
                    )
489
                )
490
            );
491
492
        // If there is only a single language, update all fields and return
493
        if (!$this->languageMaskGenerator->isLanguageMaskComposite($contentInfoRow['language_mask'])) {
494
            $qAttr->set(
495
                $this->dbHandler->quoteColumn('language_id'),
496
                $alwaysAvailable ?
497
                    $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1) :
498
                    $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
499
            );
500
            $qAttr->prepare()->execute();
501
502
            return;
503
        }
504
505
        // Otherwise:
506
        // 1. Remove always available flag on all fields
507
        $qAttr->set(
508
            $this->dbHandler->quoteColumn('language_id'),
509
            $qAttr->expr->bitAnd($this->dbHandler->quoteColumn('language_id'), -2)
510
        );
511
        $qAttr->prepare()->execute();
512
513
        // 2. If Content is always available set the flag only on fields in main language
514
        if ($alwaysAvailable) {
515
            $qAttr->set(
516
                $this->dbHandler->quoteColumn('language_id'),
517
                $qAttr->expr->bitOr($this->dbHandler->quoteColumn('language_id'), 1)
518
            );
519
            $qAttr->where(
520
                $qAttr->expr->gt(
521
                    $qAttr->expr->bitAnd(
522
                        $this->dbHandler->quoteColumn('language_id'),
523
                        $qAttr->bindValue($contentInfoRow['initial_language_id'], null, PDO::PARAM_INT)
524
                    ),
525
                    $qAttr->bindValue(0, null, PDO::PARAM_INT)
526
                )
527
            );
528
            $qAttr->prepare()->execute();
529
        }
530
    }
531
532
    /**
533
     * Sets the status of the version identified by $contentId and $version to $status.
534
     *
535
     * The $status can be one of STATUS_DRAFT, STATUS_PUBLISHED, STATUS_ARCHIVED
536
     *
537
     * @param int $contentId
538
     * @param int $version
539
     * @param int $status
540
     *
541
     * @return bool
542
     */
543
    public function setStatus($contentId, $version, $status)
544
    {
545
        $q = $this->dbHandler->createUpdateQuery();
546
        $q->update(
547
            $this->dbHandler->quoteTable('ezcontentobject_version')
548
        )->set(
549
            $this->dbHandler->quoteColumn('status'),
550
            $q->bindValue($status, null, \PDO::PARAM_INT)
551
        )->set(
552
            $this->dbHandler->quoteColumn('modified'),
553
            $q->bindValue(time(), null, \PDO::PARAM_INT)
554
        )->where(
555
            $q->expr->lAnd(
556
                $q->expr->eq(
557
                    $this->dbHandler->quoteColumn('contentobject_id'),
558
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
559
                ),
560
                $q->expr->eq(
561
                    $this->dbHandler->quoteColumn('version'),
562
                    $q->bindValue($version, null, \PDO::PARAM_INT)
563
                )
564
            )
565
        );
566
        $statement = $q->prepare();
567
        $statement->execute();
568
569
        if ((bool)$statement->rowCount() === false) {
570
            return false;
571
        }
572
573
        if ($status !== APIVersionInfo::STATUS_PUBLISHED) {
574
            return true;
575
        }
576
577
        // If the version's status is PUBLISHED, we set the content to published status as well
578
        $q = $this->dbHandler->createUpdateQuery();
579
        $q->update(
580
            $this->dbHandler->quoteTable('ezcontentobject')
581
        )->set(
582
            $this->dbHandler->quoteColumn('status'),
583
            $q->bindValue(ContentInfo::STATUS_PUBLISHED, null, \PDO::PARAM_INT)
584
        )->set(
585
            $this->dbHandler->quoteColumn('current_version'),
586
            $q->bindValue($version, null, \PDO::PARAM_INT)
587
        )->where(
588
            $q->expr->eq(
589
                $this->dbHandler->quoteColumn('id'),
590
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
591
            )
592
        );
593
        $statement = $q->prepare();
594
        $statement->execute();
595
596
        return (bool)$statement->rowCount();
597
    }
598
599
    /**
600
     * Inserts a new field.
601
     *
602
     * Only used when a new field is created (i.e. a new object or a field in a
603
     * new language!). After that, field IDs need to stay the same, only the
604
     * version number changes.
605
     *
606
     * @param \eZ\Publish\SPI\Persistence\Content $content
607
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
608
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
609
     *
610
     * @return int ID
611
     */
612
    public function insertNewField(Content $content, Field $field, StorageFieldValue $value)
613
    {
614
        $q = $this->dbHandler->createInsertQuery();
615
616
        $this->setInsertFieldValues($q, $content, $field, $value);
617
618
        // Insert with auto increment ID
619
        $q->set(
620
            $this->dbHandler->quoteColumn('id'),
621
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_attribute', 'id')
622
        );
623
624
        $q->prepare()->execute();
625
626
        return $this->dbHandler->lastInsertId(
627
            $this->dbHandler->getSequenceName('ezcontentobject_attribute', 'id')
628
        );
629
    }
630
631
    /**
632
     * Inserts an existing field.
633
     *
634
     * Used to insert a field with an exsting ID but a new version number.
635
     *
636
     * @param Content $content
637
     * @param Field $field
638
     * @param StorageFieldValue $value
639
     */
640
    public function insertExistingField(Content $content, Field $field, StorageFieldValue $value)
641
    {
642
        $q = $this->dbHandler->createInsertQuery();
643
644
        $this->setInsertFieldValues($q, $content, $field, $value);
645
646
        $q->set(
647
            $this->dbHandler->quoteColumn('id'),
648
            $q->bindValue($field->id, null, \PDO::PARAM_INT)
649
        );
650
651
        $q->prepare()->execute();
652
    }
653
654
    /**
655
     * Sets field (ezcontentobject_attribute) values to the given query.
656
     *
657
     * @param \eZ\Publish\Core\Persistence\Database\InsertQuery $q
658
     * @param Content $content
659
     * @param Field $field
660
     * @param StorageFieldValue $value
661
     */
662
    protected function setInsertFieldValues(InsertQuery $q, Content $content, Field $field, StorageFieldValue $value)
663
    {
664
        $q->insertInto(
665
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
666
        )->set(
667
            $this->dbHandler->quoteColumn('contentobject_id'),
668
            $q->bindValue($content->versionInfo->contentInfo->id, null, \PDO::PARAM_INT)
669
        )->set(
670
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
671
            $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
672
        )->set(
673
            $this->dbHandler->quoteColumn('data_type_string'),
674
            $q->bindValue($field->type)
675
        )->set(
676
            $this->dbHandler->quoteColumn('language_code'),
677
            $q->bindValue($field->languageCode)
678
        )->set(
679
            $this->dbHandler->quoteColumn('version'),
680
            $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
681
        )->set(
682
            $this->dbHandler->quoteColumn('data_float'),
683
            $q->bindValue($value->dataFloat)
684
        )->set(
685
            $this->dbHandler->quoteColumn('data_int'),
686
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
687
        )->set(
688
            $this->dbHandler->quoteColumn('data_text'),
689
            $q->bindValue($value->dataText)
690
        )->set(
691
            $this->dbHandler->quoteColumn('sort_key_int'),
692
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
693
        )->set(
694
            $this->dbHandler->quoteColumn('sort_key_string'),
695
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
696
        )->set(
697
            $this->dbHandler->quoteColumn('language_id'),
698
            $q->bindValue(
699
                $this->languageMaskGenerator->generateLanguageIndicator(
700
                    $field->languageCode,
701
                    $this->isLanguageAlwaysAvailable($content, $field->languageCode)
702
                ),
703
                null,
704
                \PDO::PARAM_INT
705
            )
706
        );
707
    }
708
709
    /**
710
     * Checks if $languageCode is always available in $content.
711
     *
712
     * @param \eZ\Publish\SPI\Persistence\Content $content
713
     * @param string $languageCode
714
     *
715
     * @return bool
716
     */
717
    protected function isLanguageAlwaysAvailable(Content $content, $languageCode)
718
    {
719
        return
720
            $content->versionInfo->contentInfo->alwaysAvailable &&
721
            $content->versionInfo->contentInfo->mainLanguageCode === $languageCode
722
        ;
723
    }
724
725
    /**
726
     * Updates an existing field.
727
     *
728
     * @param Field $field
729
     * @param StorageFieldValue $value
730
     */
731
    public function updateField(Field $field, StorageFieldValue $value)
732
    {
733
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
734
        // cannot change on update
735
        $q = $this->dbHandler->createUpdateQuery();
736
        $this->setFieldUpdateValues($q, $value);
737
        $q->where(
738
            $q->expr->lAnd(
739
                $q->expr->eq(
740
                    $this->dbHandler->quoteColumn('id'),
741
                    $q->bindValue($field->id, null, \PDO::PARAM_INT)
742
                ),
743
                $q->expr->eq(
744
                    $this->dbHandler->quoteColumn('version'),
745
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
746
                )
747
            )
748
        );
749
        $q->prepare()->execute();
750
    }
751
752
    /**
753
     * Sets update fields for $value on $q.
754
     *
755
     * @param \eZ\Publish\Core\Persistence\Database\UpdateQuery $q
756
     * @param StorageFieldValue $value
757
     */
758
    protected function setFieldUpdateValues(UpdateQuery $q, StorageFieldValue $value)
759
    {
760
        $q->update(
761
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
762
        )->set(
763
            $this->dbHandler->quoteColumn('data_float'),
764
            $q->bindValue($value->dataFloat)
765
        )->set(
766
            $this->dbHandler->quoteColumn('data_int'),
767
            $q->bindValue($value->dataInt, null, \PDO::PARAM_INT)
768
        )->set(
769
            $this->dbHandler->quoteColumn('data_text'),
770
            $q->bindValue($value->dataText)
771
        )->set(
772
            $this->dbHandler->quoteColumn('sort_key_int'),
773
            $q->bindValue($value->sortKeyInt, null, \PDO::PARAM_INT)
774
        )->set(
775
            $this->dbHandler->quoteColumn('sort_key_string'),
776
            $q->bindValue(mb_substr($value->sortKeyString, 0, 255))
777
        );
778
    }
779
780
    /**
781
     * Updates an existing, non-translatable field.
782
     *
783
     * @param \eZ\Publish\SPI\Persistence\Content\Field $field
784
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
785
     * @param int $contentId
786
     */
787
    public function updateNonTranslatableField(
788
        Field $field,
789
        StorageFieldValue $value,
790
        $contentId
791
    ) {
792
        // Note, no need to care for language_id here, since Content->$alwaysAvailable
793
        // cannot change on update
794
        $q = $this->dbHandler->createUpdateQuery();
795
        $this->setFieldUpdateValues($q, $value);
796
        $q->where(
797
            $q->expr->lAnd(
798
                $q->expr->eq(
799
                    $this->dbHandler->quoteColumn('contentclassattribute_id'),
800
                    $q->bindValue($field->fieldDefinitionId, null, \PDO::PARAM_INT)
801
                ),
802
                $q->expr->eq(
803
                    $this->dbHandler->quoteColumn('contentobject_id'),
804
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
805
                ),
806
                $q->expr->eq(
807
                    $this->dbHandler->quoteColumn('version'),
808
                    $q->bindValue($field->versionNo, null, \PDO::PARAM_INT)
809
                )
810
            )
811
        );
812
        $q->prepare()->execute();
813
    }
814
815
    /**
816
     * {@inheritdoc}
817
     */
818
    public function load($contentId, $version = null, array $translations = null)
819
    {
820
        return $this->internalLoadContent([$contentId], $version, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 818 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...
821
    }
822
823
    /**
824
     * {@inheritdoc}
825
     */
826
    public function loadContentList(array $contentIds, array $translations = null): array
827
    {
828
        return $this->internalLoadContent($contentIds, null, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 826 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...
829
    }
830
831
    /**
832
     * @see load(), loadContentList()
833
     *
834
     * @param array $contentIds
835
     * @param int|null $version
836
     * @param string[]|null $translations
837
     *
838
     * @return array
839
     */
840
    private function internalLoadContent(array $contentIds, int $version = null, array $translations = null): array
841
    {
842
        $queryBuilder = $this->connection->createQueryBuilder();
843
        $expr = $queryBuilder->expr();
844
        $queryBuilder
845
            ->select(
846
                'c.id AS ezcontentobject_id',
847
                'c.contentclass_id AS ezcontentobject_contentclass_id',
848
                'c.section_id AS ezcontentobject_section_id',
849
                'c.owner_id AS ezcontentobject_owner_id',
850
                'c.remote_id AS ezcontentobject_remote_id',
851
                'c.current_version AS ezcontentobject_current_version',
852
                'c.initial_language_id AS ezcontentobject_initial_language_id',
853
                'c.modified AS ezcontentobject_modified',
854
                'c.published AS ezcontentobject_published',
855
                'c.status AS ezcontentobject_status',
856
                'c.name AS ezcontentobject_name',
857
                'c.language_mask AS ezcontentobject_language_mask',
858
                'v.id AS ezcontentobject_version_id',
859
                'v.version AS ezcontentobject_version_version',
860
                'v.modified AS ezcontentobject_version_modified',
861
                'v.creator_id AS ezcontentobject_version_creator_id',
862
                'v.created AS ezcontentobject_version_created',
863
                'v.status AS ezcontentobject_version_status',
864
                'v.language_mask AS ezcontentobject_version_language_mask',
865
                'v.initial_language_id AS ezcontentobject_version_initial_language_id',
866
                'a.id AS ezcontentobject_attribute_id',
867
                'a.contentclassattribute_id AS ezcontentobject_attribute_contentclassattribute_id',
868
                'a.data_type_string AS ezcontentobject_attribute_data_type_string',
869
                'a.language_code AS ezcontentobject_attribute_language_code',
870
                'a.language_id AS ezcontentobject_attribute_language_id',
871
                'a.data_float AS ezcontentobject_attribute_data_float',
872
                'a.data_int AS ezcontentobject_attribute_data_int',
873
                'a.data_text AS ezcontentobject_attribute_data_text',
874
                'a.sort_key_int AS ezcontentobject_attribute_sort_key_int',
875
                'a.sort_key_string AS ezcontentobject_attribute_sort_key_string',
876
                't.main_node_id AS ezcontentobject_tree_main_node_id'
877
            )
878
            ->from('ezcontentobject', 'c')
879
            ->innerJoin(
880
                'c',
881
                'ezcontentobject_version',
882
                'v',
883
                $expr->andX(
884
                    $expr->eq('c.id', 'v.contentobject_id'),
885
                    $expr->eq('v.version', $version ?? 'c.current_version')
886
                )
887
            )
888
            ->innerJoin(
889
                'v',
890
                'ezcontentobject_attribute',
891
                'a',
892
                $expr->andX(
893
                    $expr->eq('v.contentobject_id', 'a.contentobject_id'),
894
                    $expr->eq('v.version', 'a.version')
895
                )
896
            )
897
            ->leftJoin(
898
                'c',
899
                'ezcontentobject_tree',
900
                't',
901
                $expr->andX(
902
                    $expr->eq('c.id', 't.contentobject_id'),
903
                    $expr->eq('t.node_id', 't.main_node_id')
904
                )
905
            );
906
907
        $queryBuilder->where(
908
            $expr->in(
909
                'c.id',
910
                $queryBuilder->createNamedParameter($contentIds, Connection::PARAM_INT_ARRAY)
911
            )
912
        );
913
914
        if (!empty($translations)) {
915
            $queryBuilder->andWhere(
916
                $expr->in(
917
                    'a.language_code',
918
                    $queryBuilder->createNamedParameter($translations, Connection::PARAM_STR_ARRAY)
919
                )
920
            );
921
        }
922
923
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
924
    }
925
926
    /**
927
     * Get query builder to load Content Info data.
928
     *
929
     * @see loadContentInfo(), loadContentInfoByRemoteId(), loadContentInfoList(), loadContentInfoByLocationId()
930
     *
931
     * @param bool $joinMainLocation
932
     *
933
     * @return \Doctrine\DBAL\Query\QueryBuilder
934
     */
935
    private function createLoadContentInfoQueryBuilder(bool $joinMainLocation = true): DoctrineQueryBuilder
936
    {
937
        $queryBuilder = $this->connection->createQueryBuilder();
938
        $expr = $queryBuilder->expr();
939
940
        $joinCondition = $expr->eq('c.id', 't.contentobject_id');
941
        if ($joinMainLocation) {
942
            // wrap join condition with AND operator and join by a Main Location
943
            $joinCondition = $expr->andX(
944
                $joinCondition,
945
                $expr->eq('t.node_id', 't.main_node_id')
946
            );
947
        }
948
949
        $queryBuilder
950
            ->select('c.*', 't.main_node_id AS ezcontentobject_tree_main_node_id')
951
            ->from('ezcontentobject', 'c')
952
            ->leftJoin(
953
                'c',
954
                'ezcontentobject_tree',
955
                't',
956
                $joinCondition
0 ignored issues
show
Bug introduced by
It seems like $joinCondition defined by $expr->andX($joinConditi...id', 't.main_node_id')) on line 943 can also be of type object<Doctrine\DBAL\Que...on\CompositeExpression>; however, Doctrine\DBAL\Query\QueryBuilder::leftJoin() does only seem to accept string|null, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
957
            );
958
959
        return $queryBuilder;
960
    }
961
962
    /**
963
     * Loads info for content identified by $contentId.
964
     * Will basically return a hash containing all field values for ezcontentobject table plus some additional keys:
965
     *  - always_available => Boolean indicating if content's language mask contains alwaysAvailable bit field
966
     *  - main_language_code => Language code for main (initial) language. E.g. "eng-GB".
967
     *
968
     * @param int $contentId
969
     *
970
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
971
     *
972
     * @return array
973
     */
974 View Code Duplication
    public function loadContentInfo($contentId)
975
    {
976
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
977
        $queryBuilder
978
            ->where('c.id = :id')
979
            ->setParameter('id', $contentId, ParameterType::INTEGER);
980
981
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
982
        if (empty($results)) {
983
            throw new NotFound('content', "id: $contentId");
984
        }
985
986
        return $results[0];
987
    }
988
989
    public function loadContentInfoList(array $contentIds)
990
    {
991
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
992
        $queryBuilder
993
            ->where('c.id IN (:ids)')
994
            ->setParameter('ids', $contentIds, Connection::PARAM_INT_ARRAY);
995
996
        return $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
997
    }
998
999
    /**
1000
     * Loads info for a content object identified by its remote ID.
1001
     *
1002
     * Returns an array with the relevant data.
1003
     *
1004
     * @param mixed $remoteId
1005
     *
1006
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1007
     *
1008
     * @return array
1009
     */
1010 View Code Duplication
    public function loadContentInfoByRemoteId($remoteId)
1011
    {
1012
        $queryBuilder = $this->createLoadContentInfoQueryBuilder();
1013
        $queryBuilder
1014
            ->where('c.remote_id = :id')
1015
            ->setParameter('id', $remoteId, ParameterType::STRING);
1016
1017
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1018
        if (empty($results)) {
1019
            throw new NotFound('content', "remote_id: $remoteId");
1020
        }
1021
1022
        return $results[0];
1023
    }
1024
1025
    /**
1026
     * Loads info for a content object identified by its location ID (node ID).
1027
     *
1028
     * Returns an array with the relevant data.
1029
     *
1030
     * @param int $locationId
1031
     *
1032
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
1033
     *
1034
     * @return array
1035
     */
1036 View Code Duplication
    public function loadContentInfoByLocationId($locationId)
1037
    {
1038
        $queryBuilder = $this->createLoadContentInfoQueryBuilder(false);
1039
        $queryBuilder
1040
            ->where('t.node_id = :id')
1041
            ->setParameter('id', $locationId, ParameterType::INTEGER);
1042
1043
        $results = $queryBuilder->execute()->fetchAll(FetchMode::ASSOCIATIVE);
1044
        if (empty($results)) {
1045
            throw new NotFound('content', "node_id: $locationId");
1046
        }
1047
1048
        return $results[0];
1049
    }
1050
1051
    /**
1052
     * Loads version info for content identified by $contentId and $versionNo.
1053
     * Will basically return a hash containing all field values from ezcontentobject_version table plus following keys:
1054
     *  - names => Hash of content object names. Key is the language code, value is the name.
1055
     *  - 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.
1056
     *  - initial_language_code => Language code for initial language in this version.
1057
     *
1058
     * @param int $contentId
1059
     * @param int $versionNo
1060
     *
1061
     * @return array
1062
     */
1063 View Code Duplication
    public function loadVersionInfo($contentId, $versionNo)
1064
    {
1065
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1066
        $query->where(
1067
            $query->expr->lAnd(
1068
                $query->expr->eq(
1069
                    $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1070
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1071
                ),
1072
                $query->expr->eq(
1073
                    $this->dbHandler->quoteColumn('version', 'ezcontentobject_version'),
1074
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1075
                )
1076
            )
1077
        );
1078
        $statement = $query->prepare();
1079
        $statement->execute();
1080
1081
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1082
    }
1083
1084
    /**
1085
     * Returns data for all versions with given status created by the given $userId.
1086
     *
1087
     * @param int $userId
1088
     * @param int $status
1089
     *
1090
     * @return string[][]
1091
     */
1092
    public function listVersionsForUser($userId, $status = VersionInfo::STATUS_DRAFT)
1093
    {
1094
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1095
        $query->where(
1096
            $query->expr->lAnd(
1097
                $query->expr->eq(
1098
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1099
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1100
                ),
1101
                $query->expr->eq(
1102
                    $this->dbHandler->quoteColumn('creator_id', 'ezcontentobject_version'),
1103
                    $query->bindValue($userId, null, \PDO::PARAM_INT)
1104
                )
1105
            )
1106
        );
1107
1108
        return $this->listVersionsHelper($query);
1109
    }
1110
1111
    /**
1112
     * Returns all version data for the given $contentId, optionally filtered by status.
1113
     *
1114
     * Result is returned with oldest version first (using version id as it has index and is auto increment).
1115
     *
1116
     * @param mixed $contentId
1117
     * @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}.
1118
     * @param int $limit Limit for items returned, -1 means none.
1119
     *
1120
     * @return string[][]
1121
     */
1122
    public function listVersions($contentId, $status = null, $limit = -1)
1123
    {
1124
        $query = $this->queryBuilder->createVersionInfoFindQuery();
1125
1126
        $filter = $query->expr->eq(
1127
            $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_version'),
1128
            $query->bindValue($contentId, null, \PDO::PARAM_INT)
1129
        );
1130
1131
        if ($status !== null) {
1132
            $filter = $query->expr->lAnd(
1133
                $filter,
1134
                $query->expr->eq(
1135
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_version'),
1136
                    $query->bindValue($status, null, \PDO::PARAM_INT)
1137
                )
1138
            );
1139
        }
1140
1141
        $query->where($filter);
1142
1143
        if ($limit > 0) {
1144
            $query->limit($limit);
1145
        }
1146
1147
        return $this->listVersionsHelper($query);
1148
    }
1149
1150
    /**
1151
     * Helper for {@see listVersions()} and {@see listVersionsForUser()} that filters duplicates
1152
     * that are the result of the cartesian product performed by createVersionInfoFindQuery().
1153
     *
1154
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
1155
     *
1156
     * @return string[][]
1157
     */
1158
    private function listVersionsHelper(SelectQuery $query)
1159
    {
1160
        $query->orderBy(
1161
            $this->dbHandler->quoteColumn('id', 'ezcontentobject_version')
1162
        );
1163
1164
        $statement = $query->prepare();
1165
        $statement->execute();
1166
1167
        $results = array();
1168
        $previousId = null;
1169
        foreach ($statement->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1170
            if ($row['ezcontentobject_version_id'] == $previousId) {
1171
                continue;
1172
            }
1173
1174
            $previousId = $row['ezcontentobject_version_id'];
1175
            $results[] = $row;
1176
        }
1177
1178
        return $results;
1179
    }
1180
1181
    /**
1182
     * Returns all version numbers for the given $contentId.
1183
     *
1184
     * @param mixed $contentId
1185
     *
1186
     * @return int[]
1187
     */
1188
    public function listVersionNumbers($contentId)
1189
    {
1190
        $query = $this->dbHandler->createSelectQuery();
1191
        $query->selectDistinct(
1192
            $this->dbHandler->quoteColumn('version')
1193
        )->from(
1194
            $this->dbHandler->quoteTable('ezcontentobject_version')
1195
        )->where(
1196
            $query->expr->eq(
1197
                $this->dbHandler->quoteColumn('contentobject_id'),
1198
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1199
            )
1200
        );
1201
1202
        $statement = $query->prepare();
1203
        $statement->execute();
1204
1205
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1206
    }
1207
1208
    /**
1209
     * Returns last version number for content identified by $contentId.
1210
     *
1211
     * @param int $contentId
1212
     *
1213
     * @return int
1214
     */
1215
    public function getLastVersionNumber($contentId)
1216
    {
1217
        $query = $this->dbHandler->createSelectQuery();
1218
        $query->select(
1219
            $query->expr->max($this->dbHandler->quoteColumn('version'))
1220
        )->from(
1221
            $this->dbHandler->quoteTable('ezcontentobject_version')
1222
        )->where(
1223
            $query->expr->eq(
1224
                $this->dbHandler->quoteColumn('contentobject_id'),
1225
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1226
            )
1227
        );
1228
1229
        $statement = $query->prepare();
1230
        $statement->execute();
1231
1232
        return (int)$statement->fetchColumn();
1233
    }
1234
1235
    /**
1236
     * Returns all IDs for locations that refer to $contentId.
1237
     *
1238
     * @param int $contentId
1239
     *
1240
     * @return int[]
1241
     */
1242
    public function getAllLocationIds($contentId)
1243
    {
1244
        $query = $this->dbHandler->createSelectQuery();
1245
        $query->select(
1246
            $this->dbHandler->quoteColumn('node_id')
1247
        )->from(
1248
            $this->dbHandler->quoteTable('ezcontentobject_tree')
1249
        )->where(
1250
            $query->expr->eq(
1251
                $this->dbHandler->quoteColumn('contentobject_id'),
1252
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1253
            )
1254
        );
1255
1256
        $statement = $query->prepare();
1257
        $statement->execute();
1258
1259
        return $statement->fetchAll(\PDO::FETCH_COLUMN);
1260
    }
1261
1262
    /**
1263
     * Returns all field IDs of $contentId grouped by their type.
1264
     * If $versionNo is set only field IDs for that version are returned.
1265
     * If $languageCode is set, only field IDs for that language are returned.
1266
     *
1267
     * @param int $contentId
1268
     * @param int|null $versionNo
1269
     * @param string|null $languageCode
1270
     *
1271
     * @return int[][]
1272
     */
1273
    public function getFieldIdsByType($contentId, $versionNo = null, $languageCode = null)
1274
    {
1275
        $query = $this->dbHandler->createSelectQuery();
1276
        $query->select(
1277
            $this->dbHandler->quoteColumn('id'),
1278
            $this->dbHandler->quoteColumn('data_type_string')
1279
        )->from(
1280
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1281
        )->where(
1282
            $query->expr->eq(
1283
                $this->dbHandler->quoteColumn('contentobject_id'),
1284
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1285
            )
1286
        );
1287
1288
        if (isset($versionNo)) {
1289
            $query->where(
1290
                $query->expr->eq(
1291
                    $this->dbHandler->quoteColumn('version'),
1292
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1293
                )
1294
            );
1295
        }
1296
1297
        if (isset($languageCode)) {
1298
            $query->where(
1299
                $query->expr->eq(
1300
                    $this->dbHandler->quoteColumn('language_code'),
1301
                    $query->bindValue($languageCode, null, \PDO::PARAM_STR)
1302
                )
1303
            );
1304
        }
1305
1306
        $statement = $query->prepare();
1307
        $statement->execute();
1308
1309
        $result = array();
1310
        foreach ($statement->fetchAll() as $row) {
1311
            if (!isset($result[$row['data_type_string']])) {
1312
                $result[$row['data_type_string']] = array();
1313
            }
1314
            $result[$row['data_type_string']][] = (int)$row['id'];
1315
        }
1316
1317
        return $result;
1318
    }
1319
1320
    /**
1321
     * Deletes relations to and from $contentId.
1322
     * If $versionNo is set only relations for that version are deleted.
1323
     *
1324
     * @param int $contentId
1325
     * @param int|null $versionNo
1326
     */
1327
    public function deleteRelations($contentId, $versionNo = null)
1328
    {
1329
        $query = $this->dbHandler->createDeleteQuery();
1330
        $query->deleteFrom(
1331
            $this->dbHandler->quoteTable('ezcontentobject_link')
1332
        );
1333
1334
        if (isset($versionNo)) {
1335
            $query->where(
1336
                $query->expr->lAnd(
1337
                    $query->expr->eq(
1338
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1339
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1340
                    ),
1341
                    $query->expr->eq(
1342
                        $this->dbHandler->quoteColumn('from_contentobject_version'),
1343
                        $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1344
                    )
1345
                )
1346
            );
1347
        } else {
1348
            $query->where(
1349
                $query->expr->lOr(
1350
                    $query->expr->eq(
1351
                        $this->dbHandler->quoteColumn('from_contentobject_id'),
1352
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1353
                    ),
1354
                    $query->expr->eq(
1355
                        $this->dbHandler->quoteColumn('to_contentobject_id'),
1356
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
1357
                    )
1358
                )
1359
            );
1360
        }
1361
1362
        $query->prepare()->execute();
1363
    }
1364
1365
    /**
1366
     * Removes relations to Content with $contentId from Relation and RelationList field type fields.
1367
     *
1368
     * @param int $contentId
1369
     */
1370
    public function removeReverseFieldRelations($contentId)
1371
    {
1372
        $query = $this->dbHandler->createSelectQuery();
1373
        $query
1374
            ->select('ezcontentobject_attribute.*')
1375
            ->from('ezcontentobject_attribute')
1376
            ->innerJoin(
1377
                'ezcontentobject_link',
1378
                $query->expr->lAnd(
1379
                    $query->expr->eq(
1380
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1381
                        $this->dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_attribute')
1382
                    ),
1383
                    $query->expr->eq(
1384
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1385
                        $this->dbHandler->quoteColumn('version', 'ezcontentobject_attribute')
1386
                    ),
1387
                    $query->expr->eq(
1388
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_link'),
1389
                        $this->dbHandler->quoteColumn('contentclassattribute_id', 'ezcontentobject_attribute')
1390
                    )
1391
                )
1392
            )
1393
            ->where(
1394
                $query->expr->eq(
1395
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1396
                    $query->bindValue($contentId, null, PDO::PARAM_INT)
1397
                ),
1398
                $query->expr->gt(
1399
                    $query->expr->bitAnd(
1400
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1401
                        $query->bindValue(8, null, PDO::PARAM_INT)
1402
                    ),
1403
                    0
1404
                )
1405
            );
1406
1407
        $statement = $query->prepare();
1408
        $statement->execute();
1409
1410
        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
1411
            if ($row['data_type_string'] === 'ezobjectrelation') {
1412
                $this->removeRelationFromRelationField($row);
1413
            }
1414
1415
            if ($row['data_type_string'] === 'ezobjectrelationlist') {
1416
                $this->removeRelationFromRelationListField($contentId, $row);
1417
            }
1418
        }
1419
    }
1420
1421
    /**
1422
     * Updates field value of RelationList field type identified by given $row data,
1423
     * removing relations toward given $contentId.
1424
     *
1425
     * @param int $contentId
1426
     * @param array $row
1427
     */
1428
    protected function removeRelationFromRelationListField($contentId, array $row)
1429
    {
1430
        $document = new DOMDocument('1.0', 'utf-8');
1431
        $document->loadXML($row['data_text']);
1432
1433
        $xpath = new DOMXPath($document);
1434
        $xpathExpression = "//related-objects/relation-list/relation-item[@contentobject-id='{$contentId}']";
1435
1436
        $relationItems = $xpath->query($xpathExpression);
1437
        foreach ($relationItems as $relationItem) {
1438
            $relationItem->parentNode->removeChild($relationItem);
1439
        }
1440
1441
        $query = $this->dbHandler->createUpdateQuery();
1442
        $query
1443
            ->update('ezcontentobject_attribute')
1444
            ->set(
1445
                'data_text',
1446
                $query->bindValue($document->saveXML(), null, PDO::PARAM_STR)
1447
            )
1448
            ->where(
1449
                $query->expr->lAnd(
1450
                    $query->expr->eq(
1451
                        $this->dbHandler->quoteColumn('id'),
1452
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1453
                    ),
1454
                    $query->expr->eq(
1455
                        $this->dbHandler->quoteColumn('version'),
1456
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1457
                    )
1458
                )
1459
            );
1460
1461
        $query->prepare()->execute();
1462
    }
1463
1464
    /**
1465
     * Updates field value of Relation field type identified by given $row data,
1466
     * removing relation data.
1467
     *
1468
     * @param array $row
1469
     */
1470
    protected function removeRelationFromRelationField(array $row)
1471
    {
1472
        $query = $this->dbHandler->createUpdateQuery();
1473
        $query
1474
            ->update('ezcontentobject_attribute')
1475
            ->set('data_int', $query->bindValue(null, null, PDO::PARAM_INT))
1476
            ->set('sort_key_int', $query->bindValue(0, null, PDO::PARAM_INT))
1477
            ->where(
1478
                $query->expr->lAnd(
1479
                    $query->expr->eq(
1480
                        $this->dbHandler->quoteColumn('id'),
1481
                        $query->bindValue($row['id'], null, PDO::PARAM_INT)
1482
                    ),
1483
                    $query->expr->eq(
1484
                        $this->dbHandler->quoteColumn('version'),
1485
                        $query->bindValue($row['version'], null, PDO::PARAM_INT)
1486
                    )
1487
                )
1488
            );
1489
1490
        $query->prepare()->execute();
1491
    }
1492
1493
    /**
1494
     * Deletes the field with the given $fieldId.
1495
     *
1496
     * @param int $fieldId
1497
     */
1498
    public function deleteField($fieldId)
1499
    {
1500
        $query = $this->dbHandler->createDeleteQuery();
1501
        $query->deleteFrom(
1502
            $this->dbHandler->quoteTable('ezcontentobject_attribute')
1503
        )->where(
1504
            $query->expr->eq(
1505
                $this->dbHandler->quoteColumn('id'),
1506
                $query->bindValue($fieldId, null, \PDO::PARAM_INT)
1507
            )
1508
        );
1509
1510
        $query->prepare()->execute();
1511
    }
1512
1513
    /**
1514
     * Deletes all fields of $contentId in all versions.
1515
     * If $versionNo is set only fields for that version are deleted.
1516
     *
1517
     * @param int $contentId
1518
     * @param int|null $versionNo
1519
     */
1520
    public function deleteFields($contentId, $versionNo = null)
1521
    {
1522
        $query = $this->dbHandler->createDeleteQuery();
1523
        $query->deleteFrom('ezcontentobject_attribute')
1524
            ->where(
1525
                $query->expr->eq(
1526
                    $this->dbHandler->quoteColumn('contentobject_id'),
1527
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1528
                )
1529
            );
1530
1531
        if (isset($versionNo)) {
1532
            $query->where(
1533
                $query->expr->eq(
1534
                    $this->dbHandler->quoteColumn('version'),
1535
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1536
                )
1537
            );
1538
        }
1539
1540
        $query->prepare()->execute();
1541
    }
1542
1543
    /**
1544
     * Deletes all versions of $contentId.
1545
     * If $versionNo is set only that version is deleted.
1546
     *
1547
     * @param int $contentId
1548
     * @param int|null $versionNo
1549
     */
1550
    public function deleteVersions($contentId, $versionNo = null)
1551
    {
1552
        $query = $this->dbHandler->createDeleteQuery();
1553
        $query->deleteFrom('ezcontentobject_version')
1554
            ->where(
1555
                $query->expr->eq(
1556
                    $this->dbHandler->quoteColumn('contentobject_id'),
1557
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1558
                )
1559
            );
1560
1561
        if (isset($versionNo)) {
1562
            $query->where(
1563
                $query->expr->eq(
1564
                    $this->dbHandler->quoteColumn('version'),
1565
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1566
                )
1567
            );
1568
        }
1569
1570
        $query->prepare()->execute();
1571
    }
1572
1573
    /**
1574
     * Deletes all names of $contentId.
1575
     * If $versionNo is set only names for that version are deleted.
1576
     *
1577
     * @param int $contentId
1578
     * @param int|null $versionNo
1579
     */
1580
    public function deleteNames($contentId, $versionNo = null)
1581
    {
1582
        $query = $this->dbHandler->createDeleteQuery();
1583
        $query->deleteFrom('ezcontentobject_name')
1584
            ->where(
1585
                $query->expr->eq(
1586
                    $this->dbHandler->quoteColumn('contentobject_id'),
1587
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1588
                )
1589
            );
1590
1591
        if (isset($versionNo)) {
1592
            $query->where(
1593
                $query->expr->eq(
1594
                    $this->dbHandler->quoteColumn('content_version'),
1595
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
1596
                )
1597
            );
1598
        }
1599
1600
        $query->prepare()->execute();
1601
    }
1602
1603
    /**
1604
     * Sets the name for Content $contentId in version $version to $name in $language.
1605
     *
1606
     * @param int $contentId
1607
     * @param int $version
1608
     * @param string $name
1609
     * @param string $language
1610
     */
1611
    public function setName($contentId, $version, $name, $language)
1612
    {
1613
        $language = $this->languageHandler->loadByLanguageCode($language);
1614
1615
        // Is it an insert or an update ?
1616
        $qSelect = $this->dbHandler->createSelectQuery();
1617
        $qSelect
1618
            ->select(
1619
                $qSelect->alias($qSelect->expr->count('*'), 'count')
1620
            )
1621
            ->from($this->dbHandler->quoteTable('ezcontentobject_name'))
1622
            ->where(
1623
                $qSelect->expr->lAnd(
1624
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $qSelect->bindValue($contentId)),
1625
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_version'), $qSelect->bindValue($version)),
1626
                    $qSelect->expr->eq($this->dbHandler->quoteColumn('content_translation'), $qSelect->bindValue($language->languageCode))
1627
                )
1628
            );
1629
        $stmt = $qSelect->prepare();
1630
        $stmt->execute();
1631
        $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
1632
1633
        $insert = $res[0]['count'] == 0;
1634
        if ($insert) {
1635
            $q = $this->dbHandler->createInsertQuery();
1636
            $q->insertInto($this->dbHandler->quoteTable('ezcontentobject_name'));
1637
        } else {
1638
            $q = $this->dbHandler->createUpdateQuery();
1639
            $q->update($this->dbHandler->quoteTable('ezcontentobject_name'))
1640
                ->where(
1641
                    $q->expr->lAnd(
1642
                        $q->expr->eq($this->dbHandler->quoteColumn('contentobject_id'), $q->bindValue($contentId)),
1643
                        $q->expr->eq($this->dbHandler->quoteColumn('content_version'), $q->bindValue($version)),
1644
                        $q->expr->eq($this->dbHandler->quoteColumn('content_translation'), $q->bindValue($language->languageCode))
1645
                    )
1646
                );
1647
        }
1648
1649
        $q->set(
1650
            $this->dbHandler->quoteColumn('contentobject_id'),
1651
            $q->bindValue($contentId, null, \PDO::PARAM_INT)
1652
        )->set(
1653
            $this->dbHandler->quoteColumn('content_version'),
1654
            $q->bindValue($version, null, \PDO::PARAM_INT)
1655
        )->set(
1656
            $this->dbHandler->quoteColumn('language_id'),
1657
            '(' . $this->getLanguageQuery()->getQuery() . ')'
1658
        )->set(
1659
            $this->dbHandler->quoteColumn('content_translation'),
1660
            $q->bindValue($language->languageCode)
1661
        )->set(
1662
            $this->dbHandler->quoteColumn('real_translation'),
1663
            $q->bindValue($language->languageCode)
1664
        )->set(
1665
            $this->dbHandler->quoteColumn('name'),
1666
            $q->bindValue($name)
1667
        );
1668
        $q->bindValue($language->id, ':languageId', \PDO::PARAM_INT);
1669
        $q->bindValue($contentId, ':contentId', \PDO::PARAM_INT);
1670
        $q->prepare()->execute();
1671
    }
1672
1673
    /**
1674
     * Returns a language sub select query for setName.
1675
     *
1676
     * Return sub select query which gets proper language mask for alwaysAvailable Content.
1677
     *
1678
     * @return \eZ\Publish\Core\Persistence\Database\SelectQuery
1679
     */
1680
    private function getLanguageQuery()
1681
    {
1682
        $languageQuery = $this->dbHandler->createSelectQuery();
1683
        $languageQuery
1684
            ->select(
1685
                $languageQuery->expr->searchedCase(
1686
                    [
1687
                        $languageQuery->expr->lAnd(
1688
                            $languageQuery->expr->eq(
1689
                                $this->dbHandler->quoteColumn('initial_language_id'),
1690
                                ':languageId'
1691
                            ),
1692
                            // wrap bitwise check into another "neq" to provide cross-DBMS compatibility
1693
                            $languageQuery->expr->neq(
1694
                                $languageQuery->expr->bitAnd(
1695
                                    $this->dbHandler->quoteColumn('language_mask'),
1696
                                    ':languageId'
1697
                                ),
1698
                                0
1699
                            )
1700
                        ),
1701
                        $languageQuery->expr->bitOr(
1702
                            ':languageId',
1703
                            1
1704
                        ),
1705
                    ],
1706
                    ':languageId'
1707
                )
1708
            )
1709
            ->from('ezcontentobject')
1710
            ->where(
1711
                $languageQuery->expr->eq(
1712
                    'id',
1713
                    ':contentId'
1714
                )
1715
            );
1716
1717
        return $languageQuery;
1718
    }
1719
1720
    /**
1721
     * Deletes the actual content object referred to by $contentId.
1722
     *
1723
     * @param int $contentId
1724
     */
1725
    public function deleteContent($contentId)
1726
    {
1727
        $query = $this->dbHandler->createDeleteQuery();
1728
        $query->deleteFrom('ezcontentobject')
1729
            ->where(
1730
                $query->expr->eq(
1731
                    $this->dbHandler->quoteColumn('id'),
1732
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1733
                )
1734
            );
1735
1736
        $query->prepare()->execute();
1737
    }
1738
1739
    /**
1740
     * Loads relations from $contentId to published content, optionally only from $contentVersionNo.
1741
     *
1742
     * $relationType can also be filtered.
1743
     *
1744
     * @param int $contentId
1745
     * @param int $contentVersionNo
1746
     * @param int $relationType
1747
     *
1748
     * @return string[][] array of relation data
1749
     */
1750
    public function loadRelations($contentId, $contentVersionNo = null, $relationType = null)
1751
    {
1752
        $query = $this->queryBuilder->createRelationFindQuery();
1753
        $query->innerJoin(
1754
            $query->alias(
1755
                $this->dbHandler->quoteTable('ezcontentobject'),
1756
                'ezcontentobject_to'
1757
            ),
1758
            $query->expr->lAnd(
1759
                $query->expr->eq(
1760
                    $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1761
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject_to')
1762
                ),
1763
                $query->expr->eq(
1764
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject_to'),
1765
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1766
                )
1767
            )
1768
        )->where(
1769
            $query->expr->eq(
1770
                $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link'),
1771
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1772
            )
1773
        );
1774
1775
        // source version number
1776
        if (isset($contentVersionNo)) {
1777
            $query->where(
1778
                $query->expr->eq(
1779
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link'),
1780
                    $query->bindValue($contentVersionNo, null, \PDO::PARAM_INT)
1781
                )
1782
            );
1783
        } else { // from published version only
1784
            $query->from(
1785
                $this->dbHandler->quoteTable('ezcontentobject')
1786
            )->where(
1787
                $query->expr->lAnd(
1788
                    $query->expr->eq(
1789
                        $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1790
                        $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1791
                    ),
1792
                    $query->expr->eq(
1793
                        $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1794
                        $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1795
                    )
1796
                )
1797
            );
1798
        }
1799
1800
        // relation type
1801 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...
1802
            $query->where(
1803
                $query->expr->gt(
1804
                    $query->expr->bitAnd(
1805
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1806
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1807
                    ),
1808
                    0
1809
                )
1810
            );
1811
        }
1812
1813
        $statement = $query->prepare();
1814
        $statement->execute();
1815
1816
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1817
    }
1818
1819
    /**
1820
     * Loads data that related to $toContentId.
1821
     *
1822
     * @param int $toContentId
1823
     * @param int $relationType
1824
     *
1825
     * @return mixed[][] Content data, array structured like {@see \eZ\Publish\Core\Persistence\Legacy\Content\Gateway::load()}
1826
     */
1827
    public function loadReverseRelations($toContentId, $relationType = null)
1828
    {
1829
        $query = $this->queryBuilder->createRelationFindQuery();
1830
        $query->where(
1831
            $query->expr->eq(
1832
                $this->dbHandler->quoteColumn('to_contentobject_id', 'ezcontentobject_link'),
1833
                $query->bindValue($toContentId, null, \PDO::PARAM_INT)
1834
            )
1835
        );
1836
1837
        // ezcontentobject join
1838
        $query->from(
1839
            $this->dbHandler->quoteTable('ezcontentobject')
1840
        )->where(
1841
            $query->expr->lAnd(
1842
                $query->expr->eq(
1843
                    $this->dbHandler->quoteColumn('id', 'ezcontentobject'),
1844
                    $this->dbHandler->quoteColumn('from_contentobject_id', 'ezcontentobject_link')
1845
                ),
1846
                $query->expr->eq(
1847
                    $this->dbHandler->quoteColumn('current_version', 'ezcontentobject'),
1848
                    $this->dbHandler->quoteColumn('from_contentobject_version', 'ezcontentobject_link')
1849
                ),
1850
                $query->expr->eq(
1851
                    $this->dbHandler->quoteColumn('status', 'ezcontentobject'),
1852
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1853
                )
1854
            )
1855
        );
1856
1857
        // relation type
1858 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...
1859
            $query->where(
1860
                $query->expr->gt(
1861
                    $query->expr->bitAnd(
1862
                        $this->dbHandler->quoteColumn('relation_type', 'ezcontentobject_link'),
1863
                        $query->bindValue($relationType, null, \PDO::PARAM_INT)
1864
                    ),
1865
                    0
1866
                )
1867
            );
1868
        }
1869
1870
        $statement = $query->prepare();
1871
1872
        $statement->execute();
1873
1874
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1875
    }
1876
1877
    /**
1878
     * Inserts a new relation database record.
1879
     *
1880
     * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct
1881
     *
1882
     * @return int ID the inserted ID
1883
     */
1884 View Code Duplication
    public function insertRelation(RelationCreateStruct $createStruct)
1885
    {
1886
        $q = $this->dbHandler->createInsertQuery();
1887
        $q->insertInto(
1888
            $this->dbHandler->quoteTable('ezcontentobject_link')
1889
        )->set(
1890
            $this->dbHandler->quoteColumn('id'),
1891
            $this->dbHandler->getAutoIncrementValue('ezcontentobject_link', 'id')
1892
        )->set(
1893
            $this->dbHandler->quoteColumn('contentclassattribute_id'),
1894
            $q->bindValue((int)$createStruct->sourceFieldDefinitionId, null, \PDO::PARAM_INT)
1895
        )->set(
1896
            $this->dbHandler->quoteColumn('from_contentobject_id'),
1897
            $q->bindValue($createStruct->sourceContentId, null, \PDO::PARAM_INT)
1898
        )->set(
1899
            $this->dbHandler->quoteColumn('from_contentobject_version'),
1900
            $q->bindValue($createStruct->sourceContentVersionNo, null, \PDO::PARAM_INT)
1901
        )->set(
1902
            $this->dbHandler->quoteColumn('relation_type'),
1903
            $q->bindValue($createStruct->type, null, \PDO::PARAM_INT)
1904
        )->set(
1905
            $this->dbHandler->quoteColumn('to_contentobject_id'),
1906
            $q->bindValue($createStruct->destinationContentId, null, \PDO::PARAM_INT)
1907
        );
1908
1909
        $q->prepare()->execute();
1910
1911
        return $this->dbHandler->lastInsertId(
1912
            $this->dbHandler->getSequenceName('ezcontentobject_link', 'id')
1913
        );
1914
    }
1915
1916
    /**
1917
     * Deletes the relation with the given $relationId.
1918
     *
1919
     * @param int $relationId
1920
     * @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON,
1921
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::EMBED,
1922
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::LINK,
1923
     *                 \eZ\Publish\API\Repository\Values\Content\Relation::FIELD}
1924
     */
1925
    public function deleteRelation($relationId, $type)
1926
    {
1927
        // Legacy Storage stores COMMON, LINK and EMBED types using bitmask, therefore first load
1928
        // existing relation type by given $relationId for comparison
1929
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1930
        $query = $this->dbHandler->createSelectQuery();
1931
        $query->select(
1932
            $this->dbHandler->quoteColumn('relation_type')
1933
        )->from(
1934
            $this->dbHandler->quoteTable('ezcontentobject_link')
1935
        )->where(
1936
            $query->expr->eq(
1937
                $this->dbHandler->quoteColumn('id'),
1938
                $query->bindValue($relationId, null, \PDO::PARAM_INT)
1939
            )
1940
        );
1941
1942
        $statement = $query->prepare();
1943
        $statement->execute();
1944
        $loadedRelationType = $statement->fetchColumn();
1945
1946
        if (!$loadedRelationType) {
1947
            return;
1948
        }
1949
1950
        // If relation type matches then delete
1951
        if ($loadedRelationType == $type) {
1952
            /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1953
            $query = $this->dbHandler->createDeleteQuery();
1954
            $query->deleteFrom(
1955
                'ezcontentobject_link'
1956
            )->where(
1957
                $query->expr->eq(
1958
                    $this->dbHandler->quoteColumn('id'),
1959
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1960
                )
1961
            );
1962
1963
            $query->prepare()->execute();
1964
        } elseif ($loadedRelationType & $type) { // If relation type is composite update bitmask
1965
            /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
1966
            $query = $this->dbHandler->createUpdateQuery();
1967
            $query->update(
1968
                $this->dbHandler->quoteTable('ezcontentobject_link')
1969
            )->set(
1970
                $this->dbHandler->quoteColumn('relation_type'),
1971
                $query->expr->bitAnd(
1972
                    $this->dbHandler->quoteColumn('relation_type'),
1973
                    $query->bindValue(~$type, null, \PDO::PARAM_INT)
1974
                )
1975
            )->where(
1976
                $query->expr->eq(
1977
                    $this->dbHandler->quoteColumn('id'),
1978
                    $query->bindValue($relationId, null, \PDO::PARAM_INT)
1979
                )
1980
            );
1981
1982
            $query->prepare()->execute();
1983
        } else {
1984
            // No match, do nothing
1985
        }
1986
    }
1987
1988
    /**
1989
     * Returns all Content IDs for a given $contentTypeId.
1990
     *
1991
     * @param int $contentTypeId
1992
     *
1993
     * @return int[]
1994
     */
1995
    public function getContentIdsByContentTypeId($contentTypeId)
1996
    {
1997
        $query = $this->dbHandler->createSelectQuery();
1998
        $query
1999
            ->select($this->dbHandler->quoteColumn('id'))
2000
            ->from($this->dbHandler->quoteTable('ezcontentobject'))
2001
            ->where(
2002
                $query->expr->eq(
2003
                    $this->dbHandler->quoteColumn('contentclass_id'),
2004
                    $query->bindValue($contentTypeId, null, PDO::PARAM_INT)
2005
                )
2006
            );
2007
2008
        $statement = $query->prepare();
2009
        $statement->execute();
2010
2011
        return $statement->fetchAll(PDO::FETCH_COLUMN);
2012
    }
2013
2014
    /**
2015
     * Load name data for set of content id's and corresponding version number.
2016
     *
2017
     * @param array[] $rows array of hashes with 'id' and 'version' to load names for
2018
     *
2019
     * @return array
2020
     */
2021
    public function loadVersionedNameData($rows)
2022
    {
2023
        $query = $this->queryBuilder->createNamesQuery();
2024
        $conditions = array();
2025
        foreach ($rows as $row) {
2026
            $conditions[] = $query->expr->lAnd(
2027
                $query->expr->eq(
2028
                    $this->dbHandler->quoteColumn('contentobject_id'),
2029
                    $query->bindValue($row['id'], null, \PDO::PARAM_INT)
2030
                ),
2031
                $query->expr->eq(
2032
                    $this->dbHandler->quoteColumn('content_version'),
2033
                    $query->bindValue($row['version'], null, \PDO::PARAM_INT)
2034
                )
2035
            );
2036
        }
2037
2038
        $query->where($query->expr->lOr($conditions));
2039
        $stmt = $query->prepare();
2040
        $stmt->execute();
2041
2042
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
2043
    }
2044
2045
    /**
2046
     * Batch method for copying all relation meta data for copied Content object.
2047
     *
2048
     * {@inheritdoc}
2049
     *
2050
     * @param int $originalContentId
2051
     * @param int $copiedContentId
2052
     * @param int|null $versionNo If specified only copy for a given version number, otherwise all.
2053
     */
2054
    public function copyRelations($originalContentId, $copiedContentId, $versionNo = null)
2055
    {
2056
        // Given we can retain all columns, we just create copies with new `from_contentobject_id` using INSERT INTO SELECT
2057
        $sql = 'INSERT INTO ezcontentobject_link ( contentclassattribute_id, from_contentobject_id, from_contentobject_version, relation_type, to_contentobject_id )
2058
                SELECT  L2.contentclassattribute_id, :copied_id, L2.from_contentobject_version, L2.relation_type, L2.to_contentobject_id
2059
                FROM    ezcontentobject_link AS L2
2060
                WHERE   L2.from_contentobject_id = :original_id';
2061
2062
        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...
2063
            $stmt = $this->connection->prepare($sql . ' AND L2.from_contentobject_version = :version');
2064
            $stmt->bindValue('version', $versionNo, PDO::PARAM_INT);
2065
        } else {
2066
            $stmt = $this->connection->prepare($sql);
2067
        }
2068
2069
        $stmt->bindValue('original_id', $originalContentId, PDO::PARAM_INT);
2070
        $stmt->bindValue('copied_id', $copiedContentId, PDO::PARAM_INT);
2071
2072
        $stmt->execute();
2073
    }
2074
2075
    /**
2076
     * Remove the specified translation from the Content Object Version.
2077
     *
2078
     * @param int $contentId
2079
     * @param string $languageCode language code of the translation
2080
     * @throws \Doctrine\DBAL\DBALException
2081
     */
2082 View Code Duplication
    public function deleteTranslationFromContent($contentId, $languageCode)
2083
    {
2084
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2085
2086
        $this->connection->beginTransaction();
2087
        try {
2088
            $this->deleteTranslationFromContentVersions($contentId, $language->id);
2089
            $this->deleteTranslationFromContentNames($contentId, $languageCode);
2090
            $this->deleteTranslationFromContentObject($contentId, $language->id);
2091
2092
            $this->connection->commit();
2093
        } catch (DBALException $e) {
2094
            $this->connection->rollBack();
2095
            throw $e;
2096
        }
2097
    }
2098
2099
    /**
2100
     * Delete Content fields (attributes) for the given Translation.
2101
     * If $versionNo is given, fields for that Version only will be deleted.
2102
     *
2103
     * @param string $languageCode
2104
     * @param int $contentId
2105
     * @param int $versionNo (optional) filter by versionNo
2106
     */
2107 View Code Duplication
    public function deleteTranslatedFields($languageCode, $contentId, $versionNo = null)
2108
    {
2109
        $query = $this->connection->createQueryBuilder();
2110
        $query
2111
            ->delete('ezcontentobject_attribute')
2112
            ->where('contentobject_id = :contentId')
2113
            ->andWhere('language_code = :languageCode')
2114
            ->setParameters(
2115
                [
2116
                    ':contentId' => $contentId,
2117
                    ':languageCode' => $languageCode,
2118
                ]
2119
            )
2120
        ;
2121
2122
        if (null !== $versionNo) {
2123
            $query
2124
                ->andWhere('version = :versionNo')
2125
                ->setParameter(':versionNo', $versionNo)
2126
            ;
2127
        }
2128
2129
        $query->execute();
2130
    }
2131
2132
    /**
2133
     * Delete the specified Translation from the given Version.
2134
     *
2135
     * @param int $contentId
2136
     * @param int $versionNo
2137
     * @param string $languageCode
2138
     * @throws \Doctrine\DBAL\DBALException
2139
     */
2140 View Code Duplication
    public function deleteTranslationFromVersion($contentId, $versionNo, $languageCode)
2141
    {
2142
        $language = $this->languageHandler->loadByLanguageCode($languageCode);
2143
2144
        $this->connection->beginTransaction();
2145
        try {
2146
            $this->deleteTranslationFromContentVersions($contentId, $language->id, $versionNo);
2147
            $this->deleteTranslationFromContentNames($contentId, $languageCode, $versionNo);
2148
2149
            $this->connection->commit();
2150
        } catch (DBALException $e) {
2151
            $this->connection->rollBack();
2152
            throw $e;
2153
        }
2154
    }
2155
2156
    /**
2157
     * Delete translation from the ezcontentobject_name table.
2158
     *
2159
     * @param int $contentId
2160
     * @param string $languageCode
2161
     * @param int $versionNo optional, if specified, apply to this Version only.
2162
     */
2163 View Code Duplication
    private function deleteTranslationFromContentNames($contentId, $languageCode, $versionNo = null)
2164
    {
2165
        $query = $this->connection->createQueryBuilder();
2166
        $query
2167
            ->delete('ezcontentobject_name')
2168
            ->where('contentobject_id=:contentId')
2169
            ->andWhere('real_translation=:languageCode')
2170
            ->setParameters(
2171
                [
2172
                    ':languageCode' => $languageCode,
2173
                    ':contentId' => $contentId,
2174
                ]
2175
            )
2176
        ;
2177
2178
        if (null !== $versionNo) {
2179
            $query
2180
                ->andWhere('content_version = :versionNo')
2181
                ->setParameter(':versionNo', $versionNo)
2182
            ;
2183
        }
2184
2185
        $query->execute();
2186
    }
2187
2188
    /**
2189
     * Remove language from language_mask of ezcontentobject.
2190
     *
2191
     * @param int $contentId
2192
     * @param int $languageId
2193
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2194
     */
2195
    private function deleteTranslationFromContentObject($contentId, $languageId)
2196
    {
2197
        $query = $this->connection->createQueryBuilder();
2198
        $query->update('ezcontentobject')
2199
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2200
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2201
            ->set('modified', ':now')
2202
            ->where('id = :contentId')
2203
            ->andWhere(
2204
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2205
                $query->expr()->andX(
2206
                    'language_mask & ~ ' . $languageId . ' <> 0',
2207
                    'language_mask & ~ ' . $languageId . ' <> 1'
2208
                )
2209
            )
2210
            ->setParameter(':now', time())
2211
            ->setParameter(':contentId', $contentId)
2212
        ;
2213
2214
        $rowCount = $query->execute();
2215
2216
        // no rows updated means that most likely somehow it was the last remaining translation
2217
        if ($rowCount === 0) {
2218
            throw new BadStateException(
2219
                '$languageCode',
2220
                'Specified translation is the only one Content Object Version has'
2221
            );
2222
        }
2223
    }
2224
2225
    /**
2226
     * Remove language from language_mask of ezcontentobject_version and update initialLanguageId
2227
     * if it matches the removed one.
2228
     *
2229
     * @param int $contentId
2230
     * @param int $languageId
2231
     * @param int $versionNo optional, if specified, apply to this Version only.
2232
     * @throws \eZ\Publish\Core\Base\Exceptions\BadStateException
2233
     */
2234
    private function deleteTranslationFromContentVersions($contentId, $languageId, $versionNo = null)
2235
    {
2236
        $query = $this->connection->createQueryBuilder();
2237
        $query->update('ezcontentobject_version')
2238
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
2239
            ->set('language_mask', 'language_mask & ~ ' . $languageId)
2240
            ->set('modified', ':now')
2241
            // update initial_language_id only if it matches removed translation languageId
2242
            ->set(
2243
                'initial_language_id',
2244
                'CASE WHEN initial_language_id = :languageId ' .
2245
                'THEN (SELECT initial_language_id AS main_language_id FROM ezcontentobject c WHERE c.id = :contentId) ' .
2246
                'ELSE initial_language_id END'
2247
            )
2248
            ->where('contentobject_id = :contentId')
2249
            ->andWhere(
2250
            // make sure removed translation is not the last one (incl. alwaysAvailable)
2251
                $query->expr()->andX(
2252
                    'language_mask & ~ ' . $languageId . ' <> 0',
2253
                    'language_mask & ~ ' . $languageId . ' <> 1'
2254
                )
2255
            )
2256
            ->setParameter(':now', time())
2257
            ->setParameter(':contentId', $contentId)
2258
            ->setParameter(':languageId', $languageId)
2259
        ;
2260
2261
        if (null !== $versionNo) {
2262
            $query
2263
                ->andWhere('version = :versionNo')
2264
                ->setParameter(':versionNo', $versionNo)
2265
            ;
2266
        }
2267
2268
        $rowCount = $query->execute();
2269
2270
        // no rows updated means that most likely somehow it was the last remaining translation
2271
        if ($rowCount === 0) {
2272
            throw new BadStateException(
2273
                '$languageCode',
2274
                'Specified translation is the only one Content Object Version has'
2275
            );
2276
        }
2277
    }
2278
}
2279