Completed
Push — 6.13 ( 878d09...f11ecd )
by
unknown
26:52
created

DoctrineDatabase::internalLoadContent()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 3
dl 0
loc 85
rs 8.3272
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * File containing the DoctrineDatabase Content Gateway class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
10
11
use Doctrine\DBAL\Connection;
12
use Doctrine\DBAL\DBALException;
13
use eZ\Publish\Core\Base\Exceptions\BadStateException;
14
use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
15
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway;
16
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder;
17
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
18
use eZ\Publish\Core\Persistence\Database\UpdateQuery;
19
use eZ\Publish\Core\Persistence\Database\InsertQuery;
20
use eZ\Publish\Core\Persistence\Database\SelectQuery;
21
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
22
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
23
use eZ\Publish\SPI\Persistence\Content;
24
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
25
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
26
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
27
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
28
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
29
use eZ\Publish\SPI\Persistence\Content\Field;
30
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
31
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
32
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
33
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
34
use DOMXPath;
35
use DOMDocument;
36
use PDO;
37
38
/**
39
 * Doctrine database based content gateway.
40
 */
41
class DoctrineDatabase extends Gateway
42
{
43
    /**
44
     * eZ Doctrine database handler.
45
     *
46
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
47
     */
48
    protected $dbHandler;
49
50
    /**
51
     * The native Doctrine connection.
52
     *
53
     * Meant to be used to transition from eZ/Zeta interface to Doctrine.
54
     *
55
     * @var \Doctrine\DBAL\Connection
56
     */
57
    protected $connection;
58
59
    /**
60
     * Query builder.
61
     *
62
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder
63
     */
64
    protected $queryBuilder;
65
66
    /**
67
     * Caching language handler.
68
     *
69
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler
70
     */
71
    protected $languageHandler;
72
73
    /**
74
     * Language mask generator.
75
     *
76
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
77
     */
78
    protected $languageMaskGenerator;
79
80
    /**
81
     * Creates a new gateway based on $db.
82
     *
83
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db
84
     * @param \Doctrine\DBAL\Connection $connection
85
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway\DoctrineDatabase\QueryBuilder $queryBuilder
86
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
87
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
88
     */
89
    public function __construct(
90
        DatabaseHandler $db,
91
        Connection $connection,
92
        QueryBuilder $queryBuilder,
93
        LanguageHandler $languageHandler,
94
        LanguageMaskGenerator $languageMaskGenerator
95
    ) {
96
        $this->dbHandler = $db;
97
        $this->connection = $connection;
98
        $this->queryBuilder = $queryBuilder;
99
        $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...
100
        $this->languageMaskGenerator = $languageMaskGenerator;
101
    }
102
103
    /**
104
     * Get context definition for external storage layers.
105
     *
106
     * @return array
107
     */
108
    public function getContext()
109
    {
110
        return array(
111
            'identifier' => 'LegacyStorage',
112
            'connection' => $this->dbHandler,
113
        );
114
    }
115
116
    /**
117
     * Inserts a new content object.
118
     *
119
     * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct
120
     * @param mixed $currentVersionNo
121
     *
122
     * @return int ID
123
     */
124
    public function insertContentObject(CreateStruct $struct, $currentVersionNo = 1)
125
    {
126
        $initialLanguageId = !empty($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId;
127
        $initialLanguageCode = $this->languageHandler->load($initialLanguageId)->languageCode;
128
129
        if (isset($struct->name[$initialLanguageCode])) {
130
            $name = $struct->name[$initialLanguageCode];
131
        } else {
132
            $name = '';
133
        }
134
135
        $q = $this->dbHandler->createInsertQuery();
136
        $q->insertInto(
137
            $this->dbHandler->quoteTable('ezcontentobject')
138
        )->set(
139
            $this->dbHandler->quoteColumn('id'),
140
            $this->dbHandler->getAutoIncrementValue('ezcontentobject', 'id')
141
        )->set(
142
            $this->dbHandler->quoteColumn('current_version'),
143
            $q->bindValue($currentVersionNo, null, \PDO::PARAM_INT)
144
        )->set(
145
            $this->dbHandler->quoteColumn('name'),
146
            $q->bindValue($name, null, \PDO::PARAM_STR)
147
        )->set(
148
            $this->dbHandler->quoteColumn('contentclass_id'),
149
            $q->bindValue($struct->typeId, null, \PDO::PARAM_INT)
150
        )->set(
151
            $this->dbHandler->quoteColumn('section_id'),
152
            $q->bindValue($struct->sectionId, null, \PDO::PARAM_INT)
153
        )->set(
154
            $this->dbHandler->quoteColumn('owner_id'),
155
            $q->bindValue($struct->ownerId, null, \PDO::PARAM_INT)
156
        )->set(
157
            $this->dbHandler->quoteColumn('initial_language_id'),
158
            $q->bindValue($initialLanguageId, null, \PDO::PARAM_INT)
159
        )->set(
160
            $this->dbHandler->quoteColumn('remote_id'),
161
            $q->bindValue($struct->remoteId, null, \PDO::PARAM_STR)
162
        )->set(
163
            $this->dbHandler->quoteColumn('modified'),
164
            $q->bindValue(0, null, \PDO::PARAM_INT)
165
        )->set(
166
            $this->dbHandler->quoteColumn('published'),
167
            $q->bindValue(0, null, \PDO::PARAM_INT)
168
        )->set(
169
            $this->dbHandler->quoteColumn('status'),
170
            $q->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
171
        )->set(
172
            $this->dbHandler->quoteColumn('language_mask'),
173
            $q->bindValue(
174
                $this->generateLanguageMask(
175
                    $struct->fields,
176
                    $initialLanguageCode,
177
                    $struct->alwaysAvailable
178
                ),
179
                null,
180
                \PDO::PARAM_INT
181
            )
182
        );
183
184
        $q->prepare()->execute();
185
186
        return $this->dbHandler->lastInsertId(
187
            $this->dbHandler->getSequenceName('ezcontentobject', 'id')
188
        );
189
    }
190
191
    /**
192
     * Generates a language mask for $version.
193
     *
194
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $fields
195
     * @param string $initialLanguageCode
196
     * @param bool $alwaysAvailable
197
     *
198
     * @return int
199
     */
200
    protected function generateLanguageMask(array $fields, $initialLanguageCode, $alwaysAvailable)
201
    {
202
        $languages = array($initialLanguageCode => true);
203 View Code Duplication
        foreach ($fields as $field) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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