Completed
Push — EZP-30427 ( 8c1757...93ffe2 )
by
unknown
17:19
created

DoctrineDatabase::loadVersionsForUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

class Alien {}

class Dalek extends Alien {}

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

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

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

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

    return array();
}

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

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

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