Completed
Push — master ( 0b3034...a98e2b )
by
unknown
36:06 queued 14:31
created

DoctrineDatabase::getAllUrlAliasesQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the DoctrineDatabase UrlAlias 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\UrlAlias\Gateway;
10
11
use Doctrine\DBAL\Connection;
12
use Doctrine\DBAL\Platforms\AbstractPlatform;
13
use eZ\Publish\Core\Base\Exceptions\BadStateException;
14
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway;
15
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
16
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
17
use eZ\Publish\Core\Persistence\Database\Query;
18
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Language;
19
use RuntimeException;
20
21
/**
22
 * UrlAlias Gateway.
23
 */
24
class DoctrineDatabase extends Gateway
25
{
26
    /**
27
     * 2^30, since PHP_INT_MAX can cause overflows in DB systems, if PHP is run
28
     * on 64 bit systems.
29
     */
30
    const MAX_LIMIT = 1073741824;
31
32
    /**
33
     * Columns of database tables.
34
     *
35
     * @var array
36
     *
37
     * @todo remove after testing
38
     */
39
    protected $columns = array(
40
        'ezurlalias_ml' => array(
41
            'action',
42
            'action_type',
43
            'alias_redirects',
44
            'id',
45
            'is_alias',
46
            'is_original',
47
            'lang_mask',
48
            'link',
49
            'parent',
50
            'text',
51
            'text_md5',
52
        ),
53
    );
54
55
    /**
56
     * Doctrine database handler.
57
     *
58
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
59
     */
60
    protected $dbHandler;
61
62
    /**
63
     * Language mask generator.
64
     *
65
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
66
     */
67
    protected $languageMaskGenerator;
68
69
    /**
70
     * Main URL database table name.
71
     *
72
     * @var string
73
     */
74
    protected $table;
75
76
    /**
77
     * @var \Doctrine\DBAL\Connection
78
     */
79
    private $connection;
80
81
    /**
82
     * Creates a new DoctrineDatabase UrlAlias Gateway.
83
     *
84
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $dbHandler
85
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
86
     */
87
    public function __construct(
88
        DatabaseHandler $dbHandler,
89
        LanguageMaskGenerator $languageMaskGenerator
90
    ) {
91
        $this->dbHandler = $dbHandler;
92
        $this->languageMaskGenerator = $languageMaskGenerator;
93
        $this->table = static::TABLE;
94
        $this->connection = $dbHandler->getConnection();
95
    }
96
97
    public function setTable($name)
98
    {
99
        $this->table = $name;
100
    }
101
102
    /**
103
     * Loads list of aliases by given $locationId.
104
     *
105
     * @param mixed $locationId
106
     * @param bool $custom
107
     * @param mixed $languageId
108
     *
109
     * @return array
110
     */
111
    public function loadLocationEntries($locationId, $custom = false, $languageId = false)
112
    {
113
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
114
        $query = $this->dbHandler->createSelectQuery();
115
        $query->select(
116
            $this->dbHandler->quoteColumn('id'),
117
            $this->dbHandler->quoteColumn('link'),
118
            $this->dbHandler->quoteColumn('is_alias'),
119
            $this->dbHandler->quoteColumn('alias_redirects'),
120
            $this->dbHandler->quoteColumn('lang_mask'),
121
            $this->dbHandler->quoteColumn('is_original'),
122
            $this->dbHandler->quoteColumn('parent'),
123
            $this->dbHandler->quoteColumn('text'),
124
            $this->dbHandler->quoteColumn('text_md5'),
125
            $this->dbHandler->quoteColumn('action')
126
        )->from(
127
            $this->dbHandler->quoteTable($this->table)
128
        )->where(
129
            $query->expr->lAnd(
130
                $query->expr->eq(
131
                    $this->dbHandler->quoteColumn('action'),
132
                    $query->bindValue("eznode:{$locationId}", null, \PDO::PARAM_STR)
133
                ),
134
                $query->expr->eq(
135
                    $this->dbHandler->quoteColumn('is_original'),
136
                    $query->bindValue(1, null, \PDO::PARAM_INT)
137
                ),
138
                $query->expr->eq(
139
                    $this->dbHandler->quoteColumn('is_alias'),
140
                    $query->bindValue(
141
                        $custom ? 1 : 0,
142
                        null,
143
                        \PDO::PARAM_INT
144
                    )
145
                )
146
            )
147
        );
148
149 View Code Duplication
        if ($languageId !== false) {
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...
150
            $query->where(
151
                $query->expr->gt(
152
                    $query->expr->bitAnd(
153
                        $this->dbHandler->quoteColumn('lang_mask'),
154
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
155
                    ),
156
                    0
157
                )
158
            );
159
        }
160
161
        $statement = $query->prepare();
162
        $statement->execute();
163
164
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
165
    }
166
167
    /**
168
     * Loads paged list of global aliases.
169
     *
170
     * @param string|null $languageCode
171
     * @param int $offset
172
     * @param int $limit
173
     *
174
     * @return array
175
     */
176
    public function listGlobalEntries($languageCode = null, $offset = 0, $limit = -1)
177
    {
178
        $limit = $limit === -1 ? self::MAX_LIMIT : $limit;
179
180
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
181
        $query = $this->dbHandler->createSelectQuery();
182
        $query->select(
183
            $this->dbHandler->quoteColumn('action'),
184
            $this->dbHandler->quoteColumn('id'),
185
            $this->dbHandler->quoteColumn('link'),
186
            $this->dbHandler->quoteColumn('is_alias'),
187
            $this->dbHandler->quoteColumn('alias_redirects'),
188
            $this->dbHandler->quoteColumn('lang_mask'),
189
            $this->dbHandler->quoteColumn('is_original'),
190
            $this->dbHandler->quoteColumn('parent'),
191
            $this->dbHandler->quoteColumn('text_md5')
192
        )->from(
193
            $this->dbHandler->quoteTable($this->table)
194
        )->where(
195
            $query->expr->lAnd(
196
                $query->expr->eq(
197
                    $this->dbHandler->quoteColumn('action_type'),
198
                    $query->bindValue('module', null, \PDO::PARAM_STR)
199
                ),
200
                $query->expr->eq(
201
                    $this->dbHandler->quoteColumn('is_original'),
202
                    $query->bindValue(1, null, \PDO::PARAM_INT)
203
                ),
204
                $query->expr->eq(
205
                    $this->dbHandler->quoteColumn('is_alias'),
206
                    $query->bindValue(1, null, \PDO::PARAM_INT)
207
                )
208
            )
209
        )->limit(
210
            $limit,
211
            $offset
212
        );
213 View Code Duplication
        if (isset($languageCode)) {
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...
214
            $query->where(
215
                $query->expr->gt(
216
                    $query->expr->bitAnd(
217
                        $this->dbHandler->quoteColumn('lang_mask'),
218
                        $query->bindValue(
219
                            $this->languageMaskGenerator->generateLanguageIndicator($languageCode, false),
220
                            null,
221
                            \PDO::PARAM_INT
222
                        )
223
                    ),
224
                    0
225
                )
226
            );
227
        }
228
        $statement = $query->prepare();
229
        $statement->execute();
230
231
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
232
    }
233
234
    /**
235
     * Returns boolean indicating if the row with given $id is special root entry.
236
     *
237
     * Special root entry entry will have parentId=0 and text=''.
238
     * In standard installation this entry will point to location with id=2.
239
     *
240
     * @param mixed $id
241
     *
242
     * @return bool
243
     */
244
    public function isRootEntry($id)
245
    {
246
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
247
        $query = $this->dbHandler->createSelectQuery();
248
        $query->select(
249
            $this->dbHandler->quoteColumn('text'),
250
            $this->dbHandler->quoteColumn('parent')
251
        )->from(
252
            $this->dbHandler->quoteTable($this->table)
253
        )->where(
254
            $query->expr->eq(
255
                $this->dbHandler->quoteColumn('id'),
256
                $query->bindValue($id, null, \PDO::PARAM_INT)
257
            )
258
        );
259
        $statement = $query->prepare();
260
        $statement->execute();
261
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
262
263
        return strlen($row['text']) == 0 && $row['parent'] == 0;
264
    }
265
266
    /**
267
     * Downgrades autogenerated entry matched by given $action and $languageId and negatively matched by
268
     * composite primary key.
269
     *
270
     * If language mask of the found entry is composite (meaning it consists of multiple language ids) given
271
     * $languageId will be removed from mask. Otherwise entry will be marked as history.
272
     *
273
     * @param string $action
274
     * @param mixed $languageId
275
     * @param mixed $newId
276
     * @param mixed $parentId
277
     * @param string $textMD5
278
     */
279
    public function cleanupAfterPublish($action, $languageId, $newId, $parentId, $textMD5)
280
    {
281
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
282
        $query = $this->dbHandler->createSelectQuery();
283
        $query->select(
284
            $this->dbHandler->quoteColumn('parent'),
285
            $this->dbHandler->quoteColumn('text_md5'),
286
            $this->dbHandler->quoteColumn('lang_mask')
287
        )->from(
288
            $this->dbHandler->quoteTable($this->table)
289
        )->where(
290
            $query->expr->lAnd(
291
                // 1) Autogenerated aliases that match action and language...
292
                $query->expr->eq(
293
                    $this->dbHandler->quoteColumn('action'),
294
                    $query->bindValue($action, null, \PDO::PARAM_STR)
295
                ),
296
                $query->expr->eq(
297
                    $this->dbHandler->quoteColumn('is_original'),
298
                    $query->bindValue(1, null, \PDO::PARAM_INT)
299
                ),
300
                $query->expr->eq(
301
                    $this->dbHandler->quoteColumn('is_alias'),
302
                    $query->bindValue(0, null, \PDO::PARAM_INT)
303
                ),
304
                $query->expr->gt(
305
                    $query->expr->bitAnd(
306
                        $this->dbHandler->quoteColumn('lang_mask'),
307
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
308
                    ),
309
                    0
310
                ),
311
                // 2) ...but not newly published entry
312
                $query->expr->not(
313
                    $query->expr->lAnd(
314
                        $query->expr->eq(
315
                            $this->dbHandler->quoteColumn('parent'),
316
                            $query->bindValue($parentId, null, \PDO::PARAM_INT)
317
                        ),
318
                        $query->expr->eq(
319
                            $this->dbHandler->quoteColumn('text_md5'),
320
                            $query->bindValue($textMD5, null, \PDO::PARAM_STR)
321
                        )
322
                    )
323
                )
324
            )
325
        );
326
327
        $statement = $query->prepare();
328
        $statement->execute();
329
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
330
331
        if (!empty($row)) {
332
            $this->archiveUrlAliasForDeletedTranslation($row['lang_mask'], $languageId, $row['parent'], $row['text_md5'], $newId);
333
        }
334
    }
335
336
    /**
337
     * Archive (remove or historize) obsolete URL aliases (for translations that were removed).
338
     *
339
     * @param int $languageMask all languages bit mask
340
     * @param int $languageId removed language Id
341
     * @param int $parent
342
     * @param string $textMD5 checksum
343
     * @param $linkId
344
     */
345
    private function archiveUrlAliasForDeletedTranslation($languageMask, $languageId, $parent, $textMD5, $linkId)
346
    {
347
        // If language mask is composite (consists of multiple languages) then remove given language from entry
348
        if ($languageMask & ~($languageId | 1)) {
349
            $this->removeTranslation($parent, $textMD5, $languageId);
350
        } else {
351
            // Otherwise mark entry as history
352
            $this->historize($parent, $textMD5, $linkId);
353
        }
354
    }
355
356
    public function historizeBeforeSwap($action, $languageMask)
357
    {
358
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
359
        $query = $this->dbHandler->createUpdateQuery();
360
        $query->update(
361
            $this->dbHandler->quoteTable($this->table)
362
        )->set(
363
            $this->dbHandler->quoteColumn('is_original'),
364
            $query->bindValue(0, null, \PDO::PARAM_INT)
365
        )->set(
366
            $this->dbHandler->quoteColumn('id'),
367
            $query->bindValue(
368
                $this->getNextId(),
369
                null,
370
                \PDO::PARAM_INT
371
            )
372
        )->where(
373
            $query->expr->lAnd(
374
                $query->expr->eq(
375
                    $this->dbHandler->quoteColumn('action'),
376
                    $query->bindValue($action, null, \PDO::PARAM_STR)
377
                ),
378
                $query->expr->eq(
379
                    $this->dbHandler->quoteColumn('is_original'),
380
                    $query->bindValue(1, null, \PDO::PARAM_INT)
381
                ),
382
                $query->expr->gt(
383
                    $query->expr->bitAnd(
384
                        $this->dbHandler->quoteColumn('lang_mask'),
385
                        $query->bindValue($languageMask & ~1, null, \PDO::PARAM_INT)
386
                    ),
387
                    0
388
                )
389
            )
390
        );
391
        $query->prepare()->execute();
392
    }
393
394
    /**
395
     * Updates single row matched by composite primary key.
396
     *
397
     * Sets "is_original" to 0 thus marking entry as history.
398
     *
399
     * Re-links history entries.
400
     *
401
     * When location alias is published we need to check for new history entries created with self::downgrade()
402
     * with the same action and language, update their "link" column with id of the published entry.
403
     * History entry "id" column is moved to next id value so that all active (non-history) entries are kept
404
     * under the same id.
405
     *
406
     * @param int $parentId
407
     * @param string $textMD5
408
     * @param int $newId
409
     */
410
    protected function historize($parentId, $textMD5, $newId)
411
    {
412
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
413
        $query = $this->dbHandler->createUpdateQuery();
414
        $query->update(
415
            $this->dbHandler->quoteTable($this->table)
416
        )->set(
417
            $this->dbHandler->quoteColumn('is_original'),
418
            $query->bindValue(0, null, \PDO::PARAM_INT)
419
        )->set(
420
            $this->dbHandler->quoteColumn('link'),
421
            $query->bindValue($newId, null, \PDO::PARAM_INT)
422
        )->set(
423
            $this->dbHandler->quoteColumn('id'),
424
            $query->bindValue(
425
                $this->getNextId(),
426
                null,
427
                \PDO::PARAM_INT
428
            )
429
        )->where(
430
            $query->expr->lAnd(
431
                $query->expr->eq(
432
                    $this->dbHandler->quoteColumn('parent'),
433
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
434
                ),
435
                $query->expr->eq(
436
                    $this->dbHandler->quoteColumn('text_md5'),
437
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
438
                )
439
            )
440
        );
441
        $query->prepare()->execute();
442
    }
443
444
    /**
445
     * Updates single row data matched by composite primary key.
446
     *
447
     * Removes given $languageId from entry's language mask
448
     *
449
     * @param mixed $parentId
450
     * @param string $textMD5
451
     * @param mixed $languageId
452
     */
453 View Code Duplication
    protected function removeTranslation($parentId, $textMD5, $languageId)
454
    {
455
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
456
        $query = $this->dbHandler->createUpdateQuery();
457
        $query->update(
458
            $this->dbHandler->quoteTable($this->table)
459
        )->set(
460
            $this->dbHandler->quoteColumn('lang_mask'),
461
            $query->expr->bitAnd(
462
                $this->dbHandler->quoteColumn('lang_mask'),
463
                $query->bindValue(~$languageId, null, \PDO::PARAM_INT)
464
            )
465
        )->where(
466
            $query->expr->lAnd(
467
                $query->expr->eq(
468
                    $this->dbHandler->quoteColumn('parent'),
469
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
470
                ),
471
                $query->expr->eq(
472
                    $this->dbHandler->quoteColumn('text_md5'),
473
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
474
                )
475
            )
476
        );
477
        $query->prepare()->execute();
478
    }
479
480
    /**
481
     * Marks all entries with given $id as history entries.
482
     *
483
     * This method is used by Handler::locationMoved(). Each row is separately historized
484
     * because future publishing needs to be able to take over history entries safely.
485
     *
486
     * @param mixed $id
487
     * @param mixed $link
488
     */
489
    public function historizeId($id, $link)
490
    {
491
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
492
        $query = $this->dbHandler->createSelectQuery();
493
        $query->select(
494
            $this->dbHandler->quoteColumn('parent'),
495
            $this->dbHandler->quoteColumn('text_md5')
496
        )->from(
497
            $this->dbHandler->quoteTable($this->table)
498
        )->where(
499
            $query->expr->lAnd(
500
                $query->expr->eq(
501
                    $this->dbHandler->quoteColumn('is_alias'),
502
                    $query->bindValue(0, null, \PDO::PARAM_INT)
503
                ),
504
                $query->expr->eq(
505
                    $this->dbHandler->quoteColumn('is_original'),
506
                    $query->bindValue(1, null, \PDO::PARAM_INT)
507
                ),
508
                $query->expr->eq(
509
                    $this->dbHandler->quoteColumn('action_type'),
510
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
511
                ),
512
                $query->expr->eq(
513
                    $this->dbHandler->quoteColumn('link'),
514
                    $query->bindValue($id, null, \PDO::PARAM_INT)
515
                )
516
            )
517
        );
518
519
        $statement = $query->prepare();
520
        $statement->execute();
521
522
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
523
524
        foreach ($rows as $row) {
525
            $this->historize($row['parent'], $row['text_md5'], $link);
526
        }
527
    }
528
529
    /**
530
     * Updates parent id of autogenerated entries.
531
     *
532
     * Update includes history entries.
533
     *
534
     * @param mixed $oldParentId
535
     * @param mixed $newParentId
536
     */
537 View Code Duplication
    public function reparent($oldParentId, $newParentId)
538
    {
539
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
540
        $query = $this->dbHandler->createUpdateQuery();
541
        $query->update(
542
            $this->dbHandler->quoteTable($this->table)
543
        )->set(
544
            $this->dbHandler->quoteColumn('parent'),
545
            $query->bindValue($newParentId, null, \PDO::PARAM_INT)
546
        )->where(
547
            $query->expr->lAnd(
548
                $query->expr->eq(
549
                    $this->dbHandler->quoteColumn('is_alias'),
550
                    $query->bindValue(0, null, \PDO::PARAM_INT)
551
                ),
552
                $query->expr->eq(
553
                    $this->dbHandler->quoteColumn('parent'),
554
                    $query->bindValue($oldParentId, null, \PDO::PARAM_INT)
555
                )
556
            )
557
        );
558
559
        $query->prepare()->execute();
560
    }
561
562
    /**
563
     * Updates single row data matched by composite primary key.
564
     *
565
     * Use optional parameter $languageMaskMatch to additionally limit the query match with languages.
566
     *
567
     * @param mixed $parentId
568
     * @param string $textMD5
569
     * @param array $values associative array with column names as keys and column values as values
570
     */
571 View Code Duplication
    public function updateRow($parentId, $textMD5, array $values)
572
    {
573
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
574
        $query = $this->dbHandler->createUpdateQuery();
575
        $query->update($this->dbHandler->quoteTable($this->table));
576
        $this->setQueryValues($query, $values);
577
        $query->where(
578
            $query->expr->lAnd(
579
                $query->expr->eq(
580
                    $this->dbHandler->quoteColumn('parent'),
581
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
582
                ),
583
                $query->expr->eq(
584
                    $this->dbHandler->quoteColumn('text_md5'),
585
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
586
                )
587
            )
588
        );
589
        $query->prepare()->execute();
590
    }
591
592
    /**
593
     * Inserts new row in urlalias_ml table.
594
     *
595
     * @param array $values
596
     *
597
     * @return mixed
598
     */
599
    public function insertRow(array $values)
600
    {
601
        // @todo remove after testing
602
        if (
603
            !isset($values['text']) ||
604
            !isset($values['text_md5']) ||
605
            !isset($values['action']) ||
606
            !isset($values['parent']) ||
607
            !isset($values['lang_mask'])) {
608
            throw new \Exception('value set is incomplete: ' . var_export($values, true) . ", can't execute insert");
609
        }
610
        if (!isset($values['id'])) {
611
            $values['id'] = $this->getNextId();
612
        }
613
        if (!isset($values['link'])) {
614
            $values['link'] = $values['id'];
615
        }
616
        if (!isset($values['is_original'])) {
617
            $values['is_original'] = ($values['id'] == $values['link'] ? 1 : 0);
618
        }
619
        if (!isset($values['is_alias'])) {
620
            $values['is_alias'] = 0;
621
        }
622
        if (!isset($values['alias_redirects'])) {
623
            $values['alias_redirects'] = 0;
624
        }
625
        if (!isset($values['action_type'])) {
626
            if (preg_match('#^(.+):.*#', $values['action'], $matches)) {
627
                $values['action_type'] = $matches[1];
628
            }
629
        }
630
        if ($values['is_alias']) {
631
            $values['is_original'] = 1;
632
        }
633
        if ($values['action'] === 'nop:') {
634
            $values['is_original'] = 0;
635
        }
636
637
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
638
        $query = $this->dbHandler->createInsertQuery();
639
        $query->insertInto($this->dbHandler->quoteTable($this->table));
640
        $this->setQueryValues($query, $values);
641
        $query->prepare()->execute();
642
643
        return $values['id'];
644
    }
645
646
    /**
647
     * Sets value for insert or update query.
648
     *
649
     * @param \eZ\Publish\Core\Persistence\Database\Query|\eZ\Publish\Core\Persistence\Database\InsertQuery|\eZ\Publish\Core\Persistence\Database\UpdateQuery $query
650
     * @param array $values
651
     *
652
     * @throws \Exception
653
     */
654
    protected function setQueryValues(Query $query, $values)
655
    {
656
        foreach ($values as $column => $value) {
657
            // @todo remove after testing
658
            if (!in_array($column, $this->columns['ezurlalias_ml'])) {
659
                throw new \Exception("unknown column '$column' for table 'ezurlalias_ml'");
660
            }
661
            switch ($column) {
662
                case 'text':
663
                case 'action':
664
                case 'text_md5':
665
                case 'action_type':
666
                    $pdoDataType = \PDO::PARAM_STR;
667
                    break;
668
                default:
669
                    $pdoDataType = \PDO::PARAM_INT;
670
            }
671
            $query->set(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\Core\Persistence\Database\Query as the method set() does only exist in the following implementations of said interface: eZ\Publish\Core\Persiste...ine\InsertDoctrineQuery, eZ\Publish\Core\Persiste...ine\UpdateDoctrineQuery.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
672
                $this->dbHandler->quoteColumn($column),
673
                $query->bindValue($value, null, $pdoDataType)
674
            );
675
        }
676
    }
677
678
    /**
679
     * Returns next value for "id" column.
680
     *
681
     * @return mixed
682
     */
683 View Code Duplication
    public function getNextId()
684
    {
685
        $sequence = $this->dbHandler->getSequenceName('ezurlalias_ml_incr', 'id');
686
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
687
        $query = $this->dbHandler->createInsertQuery();
688
        $query->insertInto(
689
            $this->dbHandler->quoteTable('ezurlalias_ml_incr')
690
        );
691
        // ezcDatabase does not abstract the "auto increment id"
692
        // INSERT INTO ezurlalias_ml_incr VALUES(DEFAULT) is not an option due
693
        // to this mysql bug: http://bugs.mysql.com/bug.php?id=42270
694
        // as a result we are forced to check which database is currently used
695
        // to generate the correct SQL query
696
        // see https://jira.ez.no/browse/EZP-20652
697
        if ($this->dbHandler->useSequences()) {
698
            $query->set(
699
                $this->dbHandler->quoteColumn('id'),
700
                "nextval('{$sequence}')"
701
            );
702
        } else {
703
            $query->set(
704
                $this->dbHandler->quoteColumn('id'),
705
                $query->bindValue(null, null, \PDO::PARAM_NULL)
706
            );
707
        }
708
        $query->prepare()->execute();
709
710
        return $this->dbHandler->lastInsertId($sequence);
711
    }
712
713
    /**
714
     * Loads single row data matched by composite primary key.
715
     *
716
     * @param mixed $parentId
717
     * @param string $textMD5
718
     *
719
     * @return array
720
     */
721 View Code Duplication
    public function loadRow($parentId, $textMD5)
722
    {
723
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
724
        $query = $this->dbHandler->createSelectQuery();
725
        $query->select('*')->from(
726
            $this->dbHandler->quoteTable($this->table)
727
        )->where(
728
            $query->expr->lAnd(
729
                $query->expr->eq(
730
                    $this->dbHandler->quoteColumn('parent'),
731
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
732
                ),
733
                $query->expr->eq(
734
                    $this->dbHandler->quoteColumn('text_md5'),
735
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
736
                )
737
            )
738
        );
739
740
        $statement = $query->prepare();
741
        $statement->execute();
742
743
        return $statement->fetch(\PDO::FETCH_ASSOC);
744
    }
745
746
    /**
747
     * Loads complete URL alias data by given array of path hashes.
748
     *
749
     * @param string[] $urlHashes URL string hashes
750
     *
751
     * @return array
752
     */
753
    public function loadUrlAliasData(array $urlHashes)
754
    {
755
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
756
        $query = $this->dbHandler->createSelectQuery();
757
758
        $count = count($urlHashes);
759
        foreach ($urlHashes as $level => $urlPartHash) {
760
            $tableName = $this->table . ($level === $count - 1 ? '' : $level);
761
762
            if ($level === $count - 1) {
763
                $query->select(
764
                    $this->dbHandler->quoteColumn('id', $tableName),
765
                    $this->dbHandler->quoteColumn('link', $tableName),
766
                    $this->dbHandler->quoteColumn('is_alias', $tableName),
767
                    $this->dbHandler->quoteColumn('alias_redirects', $tableName),
768
                    $this->dbHandler->quoteColumn('is_original', $tableName),
769
                    $this->dbHandler->quoteColumn('action', $tableName),
770
                    $this->dbHandler->quoteColumn('action_type', $tableName),
771
                    $this->dbHandler->quoteColumn('lang_mask', $tableName),
772
                    $this->dbHandler->quoteColumn('text', $tableName),
773
                    $this->dbHandler->quoteColumn('parent', $tableName),
774
                    $this->dbHandler->quoteColumn('text_md5', $tableName)
775
                )->from(
776
                    $this->dbHandler->quoteTable($this->table)
777
                );
778
            } else {
779
                $query->select(
780
                    $this->dbHandler->aliasedColumn($query, 'id', $tableName),
781
                    $this->dbHandler->aliasedColumn($query, 'link', $tableName),
782
                    $this->dbHandler->aliasedColumn($query, 'is_alias', $tableName),
783
                    $this->dbHandler->aliasedColumn($query, 'alias_redirects', $tableName),
784
                    $this->dbHandler->aliasedColumn($query, 'is_original', $tableName),
785
                    $this->dbHandler->aliasedColumn($query, 'action', $tableName),
786
                    $this->dbHandler->aliasedColumn($query, 'action_type', $tableName),
787
                    $this->dbHandler->aliasedColumn($query, 'lang_mask', $tableName),
788
                    $this->dbHandler->aliasedColumn($query, 'text', $tableName),
789
                    $this->dbHandler->aliasedColumn($query, 'parent', $tableName),
790
                    $this->dbHandler->aliasedColumn($query, 'text_md5', $tableName)
791
                )->from(
792
                    $query->alias($this->table, $tableName)
793
                );
794
            }
795
796
            $query->where(
797
                $query->expr->lAnd(
798
                    $query->expr->eq(
799
                        $this->dbHandler->quoteColumn('text_md5', $tableName),
800
                        $query->bindValue($urlPartHash, null, \PDO::PARAM_STR)
801
                    ),
802
                    $query->expr->eq(
803
                        $this->dbHandler->quoteColumn('parent', $tableName),
804
                        // root entry has parent column set to 0
805
                        isset($previousTableName) ? $this->dbHandler->quoteColumn('link', $previousTableName) : $query->bindValue(0, null, \PDO::PARAM_INT)
806
                    )
807
                )
808
            );
809
810
            $previousTableName = $tableName;
811
        }
812
        $query->limit(1);
813
814
        $statement = $query->prepare();
815
        $statement->execute();
816
817
        return $statement->fetch(\PDO::FETCH_ASSOC);
818
    }
819
820
    /**
821
     * Loads autogenerated entry id by given $action and optionally $parentId.
822
     *
823
     * @param string $action
824
     * @param mixed|null $parentId
825
     *
826
     * @return array
827
     */
828 View Code Duplication
    public function loadAutogeneratedEntry($action, $parentId = null)
829
    {
830
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
831
        $query = $this->dbHandler->createSelectQuery();
832
        $query->select(
833
            '*'
834
        )->from(
835
            $this->dbHandler->quoteTable($this->table)
836
        )->where(
837
            $query->expr->lAnd(
838
                $query->expr->eq(
839
                    $this->dbHandler->quoteColumn('action'),
840
                    $query->bindValue($action, null, \PDO::PARAM_STR)
841
                ),
842
                $query->expr->eq(
843
                    $this->dbHandler->quoteColumn('is_original'),
844
                    $query->bindValue(1, null, \PDO::PARAM_INT)
845
                ),
846
                $query->expr->eq(
847
                    $this->dbHandler->quoteColumn('is_alias'),
848
                    $query->bindValue(0, null, \PDO::PARAM_INT)
849
                )
850
            )
851
        );
852
853
        if (isset($parentId)) {
854
            $query->where(
855
                $query->expr->eq(
856
                    $this->dbHandler->quoteColumn('parent'),
857
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
858
                )
859
            );
860
        }
861
862
        $statement = $query->prepare();
863
        $statement->execute();
864
865
        return $statement->fetch(\PDO::FETCH_ASSOC);
866
    }
867
868
    /**
869
     * Loads all data for the path identified by given $id.
870
     *
871
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
872
     *
873
     * @param int $id
874
     *
875
     * @return array
876
     */
877
    public function loadPathData($id)
878
    {
879
        $pathData = array();
880
881
        while ($id != 0) {
882
            /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
883
            $query = $this->dbHandler->createSelectQuery();
884
            $query->select(
885
                $this->dbHandler->quoteColumn('parent'),
886
                $this->dbHandler->quoteColumn('lang_mask'),
887
                $this->dbHandler->quoteColumn('text')
888
            )->from(
889
                $this->dbHandler->quoteTable($this->table)
890
            )->where(
891
                $query->expr->eq(
892
                    $this->dbHandler->quoteColumn('id'),
893
                    $query->bindValue($id, null, \PDO::PARAM_INT)
894
                )
895
            );
896
897
            $statement = $query->prepare();
898
            $statement->execute();
899
900
            $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
901
            if (empty($rows)) {
902
                // Normally this should never happen
903
                $pathDataArray = [];
904
                foreach ($pathData as $path) {
905
                    if (!isset($path[0]['text'])) {
906
                        continue;
907
                    }
908
909
                    $pathDataArray[] = $path[0]['text'];
910
                }
911
912
                $path = implode('/', $pathDataArray);
913
                throw new BadStateException(
914
                    'id',
915
                    "Unable to load path data, the path ...'{$path}' is broken, alias id '{$id}' not found. " .
916
                    'To fix all broken paths run the ezplatform:urls:regenerate-aliases command'
917
                );
918
            }
919
920
            $id = $rows[0]['parent'];
921
            array_unshift($pathData, $rows);
922
        }
923
924
        return $pathData;
925
    }
926
927
    /**
928
     * Loads path data identified by given ordered array of hierarchy data.
929
     *
930
     * The first entry in $hierarchyData corresponds to the top-most path element in the path, the second entry the
931
     * child of the first path element and so on.
932
     * This method is faster than self::getPath() since it can fetch all elements using only one query, but can be used
933
     * only for autogenerated paths.
934
     *
935
     * @param array $hierarchyData
936
     *
937
     * @return array
938
     */
939
    public function loadPathDataByHierarchy(array $hierarchyData)
940
    {
941
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
942
        $query = $this->dbHandler->createSelectQuery();
943
944
        $hierarchyConditions = array();
945
        foreach ($hierarchyData as $levelData) {
946
            $hierarchyConditions[] = $query->expr->lAnd(
947
                $query->expr->eq(
948
                    $this->dbHandler->quoteColumn('parent'),
949
                    $query->bindValue(
950
                        $levelData['parent'],
951
                        null,
952
                        \PDO::PARAM_INT
953
                    )
954
                ),
955
                $query->expr->eq(
956
                    $this->dbHandler->quoteColumn('action'),
957
                    $query->bindValue(
958
                        $levelData['action'],
959
                        null,
960
                        \PDO::PARAM_STR
961
                    )
962
                ),
963
                $query->expr->eq(
964
                    $this->dbHandler->quoteColumn('id'),
965
                    $query->bindValue(
966
                        $levelData['id'],
967
                        null,
968
                        \PDO::PARAM_INT
969
                    )
970
                )
971
            );
972
        }
973
974
        $query->select(
975
            $this->dbHandler->quoteColumn('action'),
976
            $this->dbHandler->quoteColumn('lang_mask'),
977
            $this->dbHandler->quoteColumn('text')
978
        )->from(
979
            $this->dbHandler->quoteTable($this->table)
980
        )->where(
981
            $query->expr->lOr($hierarchyConditions)
982
        );
983
984
        $statement = $query->prepare();
985
        $statement->execute();
986
987
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
988
        $rowsMap = array();
989
        foreach ($rows as $row) {
990
            $rowsMap[$row['action']][] = $row;
991
        }
992
993
        if (count($rowsMap) !== count($hierarchyData)) {
994
            throw new \RuntimeException('The path is corrupted.');
995
        }
996
997
        $data = array();
998
        foreach ($hierarchyData as $levelData) {
999
            $data[] = $rowsMap[$levelData['action']];
1000
        }
1001
1002
        return $data;
1003
    }
1004
1005
    /**
1006
     * Deletes single custom alias row matched by composite primary key.
1007
     *
1008
     * @param mixed $parentId
1009
     * @param string $textMD5
1010
     *
1011
     * @return bool
1012
     */
1013 View Code Duplication
    public function removeCustomAlias($parentId, $textMD5)
1014
    {
1015
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1016
        $query = $this->dbHandler->createDeleteQuery();
1017
        $query->deleteFrom(
1018
            $this->dbHandler->quoteTable($this->table)
1019
        )->where(
1020
            $query->expr->lAnd(
1021
                $query->expr->eq(
1022
                    $this->dbHandler->quoteColumn('parent'),
1023
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
1024
                ),
1025
                $query->expr->eq(
1026
                    $this->dbHandler->quoteColumn('text_md5'),
1027
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
1028
                ),
1029
                $query->expr->eq(
1030
                    $this->dbHandler->quoteColumn('is_alias'),
1031
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1032
                )
1033
            )
1034
        );
1035
        $statement = $query->prepare();
1036
        $statement->execute();
1037
1038
        return $statement->rowCount() === 1 ?: false;
1039
    }
1040
1041
    /**
1042
     * Deletes all rows with given $action and optionally $id.
1043
     *
1044
     * If $id is set only autogenerated entries will be removed.
1045
     *
1046
     * @param mixed $action
1047
     * @param mixed|null $id
1048
     *
1049
     * @return bool
1050
     */
1051 View Code Duplication
    public function remove($action, $id = null)
1052
    {
1053
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1054
        $query = $this->dbHandler->createDeleteQuery();
1055
        $query->deleteFrom(
1056
            $this->dbHandler->quoteTable($this->table)
1057
        )->where(
1058
            $query->expr->eq(
1059
                $this->dbHandler->quoteColumn('action'),
1060
                $query->bindValue($action, null, \PDO::PARAM_STR)
1061
            )
1062
        );
1063
1064
        if ($id !== null) {
1065
            $query->where(
1066
                $query->expr->lAnd(
1067
                    $query->expr->eq(
1068
                        $this->dbHandler->quoteColumn('is_alias'),
1069
                        $query->bindValue(0, null, \PDO::PARAM_INT)
1070
                    ),
1071
                    $query->expr->eq(
1072
                        $this->dbHandler->quoteColumn('id'),
1073
                        $query->bindValue($id, null, \PDO::PARAM_INT)
1074
                    )
1075
                )
1076
            );
1077
        }
1078
1079
        $query->prepare()->execute();
1080
    }
1081
1082
    /**
1083
     * Loads all autogenerated entries with given $parentId with optionally included history entries.
1084
     *
1085
     * @param mixed $parentId
1086
     * @param bool $includeHistory
1087
     *
1088
     * @return array
1089
     */
1090 View Code Duplication
    public function loadAutogeneratedEntries($parentId, $includeHistory = false)
1091
    {
1092
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1093
        $query = $this->dbHandler->createSelectQuery();
1094
        $query->select(
1095
            '*'
1096
        )->from(
1097
            $this->dbHandler->quoteTable($this->table)
1098
        )->where(
1099
            $query->expr->lAnd(
1100
                $query->expr->eq(
1101
                    $this->dbHandler->quoteColumn('parent'),
1102
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
1103
                ),
1104
                $query->expr->eq(
1105
                    $this->dbHandler->quoteColumn('action_type'),
1106
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
1107
                ),
1108
                $query->expr->eq(
1109
                    $this->dbHandler->quoteColumn('is_alias'),
1110
                    $query->bindValue(0, null, \PDO::PARAM_INT)
1111
                )
1112
            )
1113
        );
1114
1115
        if (!$includeHistory) {
1116
            $query->where(
1117
                $query->expr->eq(
1118
                    $this->dbHandler->quoteColumn('is_original'),
1119
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1120
                )
1121
            );
1122
        }
1123
1124
        $statement = $query->prepare();
1125
        $statement->execute();
1126
1127
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1128
    }
1129
1130
    public function getLocationContentMainLanguageId($locationId)
1131
    {
1132
        $dbHandler = $this->dbHandler;
1133
        $query = $dbHandler->createSelectQuery();
1134
        $query
1135
            ->select($dbHandler->quoteColumn('initial_language_id', 'ezcontentobject'))
1136
            ->from($dbHandler->quoteTable('ezcontentobject'))
1137
            ->innerJoin(
1138
                $dbHandler->quoteTable('ezcontentobject_tree'),
1139
                $query->expr->lAnd(
1140
                    $query->expr->eq(
1141
                        $dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_tree'),
1142
                        $dbHandler->quoteColumn('id', 'ezcontentobject')
1143
                    ),
1144
                    $query->expr->eq(
1145
                        $dbHandler->quoteColumn('node_id', 'ezcontentobject_tree'),
1146
                        $dbHandler->quoteColumn('main_node_id', 'ezcontentobject_tree')
1147
                    ),
1148
                    $query->expr->eq(
1149
                        $dbHandler->quoteColumn('node_id', 'ezcontentobject_tree'),
1150
                        $query->bindValue($locationId, null, \PDO::PARAM_INT)
1151
                    )
1152
                )
1153
            );
1154
1155
        $statement = $query->prepare();
1156
        $statement->execute();
1157
        $languageId = $statement->fetchColumn();
1158
1159
        if ($languageId === false) {
1160
            throw new RuntimeException("Could not find Content for Location #{$locationId}");
1161
        }
1162
1163
        return $languageId;
1164
    }
1165
1166
    /**
1167
     * Removes languageId of removed translation from lang_mask and deletes single language rows for multiple Locations.
1168
     *
1169
     * Note: URL aliases are not historized as translation removal from all Versions is permanent w/o preserving history.
1170
     *
1171
     * @param int $languageId Language Id to be removed
1172
     * @param string[] $actions actions for which to perform the update
1173
     */
1174
    public function bulkRemoveTranslation($languageId, $actions)
1175
    {
1176
        $connection = $this->dbHandler->getConnection();
1177
        /** @var \Doctrine\DBAL\Connection $connection */
1178
        $query = $connection->createQueryBuilder();
1179
        $query
1180
            ->update($connection->quoteIdentifier($this->table))
1181
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
1182
            ->set('lang_mask', 'lang_mask & ~ ' . $languageId)
1183
            ->where('action IN (:actions)')
1184
            ->setParameter(':actions', $actions, Connection::PARAM_STR_ARRAY)
1185
        ;
1186
        $query->execute();
1187
1188
        // cleanup: delete single language rows (including alwaysAvailable)
1189
        $query = $connection->createQueryBuilder();
1190
        $query
1191
            ->delete($this->table)
1192
            ->where('action IN (:actions)')
1193
            ->andWhere('lang_mask IN (0, 1)')
1194
            ->setParameter(':actions', $actions, Connection::PARAM_STR_ARRAY)
1195
        ;
1196
        $query->execute();
1197
    }
1198
1199
    /**
1200
     * Archive (remove or historize) URL aliases for removed Translations.
1201
     *
1202
     * @param int $locationId
1203
     * @param int $parentId
1204
     * @param int[] $languageIds Language IDs of removed Translations
1205
     */
1206
    public function archiveUrlAliasesForDeletedTranslations($locationId, $parentId, array $languageIds)
1207
    {
1208
        // determine proper parent for linking historized entry
1209
        $existingLocationEntry = $this->loadAutogeneratedEntry(
1210
            'eznode:' . $locationId,
1211
            $parentId
1212
        );
1213
1214
        // filter existing URL alias entries by any of the specified removed languages
1215
        $rows = $this->loadLocationEntriesMatchingMultipleLanguages(
1216
            $locationId,
1217
            $languageIds
1218
        );
1219
1220
        // remove specific languages from a bit mask
1221
        foreach ($rows as $row) {
1222
            // filter mask to reduce the number of calls to storage engine
1223
            $rowLanguageMask = (int) $row['lang_mask'];
1224
            $languageIdsToBeRemoved = array_filter(
1225
                $languageIds,
1226
                function ($languageId) use ($rowLanguageMask) {
1227
                    return $languageId & $rowLanguageMask;
1228
                }
1229
            );
1230
1231
            if (empty($languageIdsToBeRemoved)) {
1232
                continue;
1233
            }
1234
1235
            // use existing entry to link archived alias or use current alias id
1236
            $linkToId = !empty($existingLocationEntry) ? $existingLocationEntry['id'] : $row['id'];
1237
            foreach ($languageIdsToBeRemoved as $languageId) {
1238
                $this->archiveUrlAliasForDeletedTranslation(
1239
                    $row['lang_mask'],
1240
                    $languageId,
1241
                    $row['parent'],
1242
                    $row['text_md5'],
1243
                    $linkToId
1244
                );
1245
            }
1246
        }
1247
    }
1248
1249
    /**
1250
     * Load list of aliases for given $locationId matching any of the Languages specified by $languageMask.
1251
     *
1252
     * @param int $locationId
1253
     * @param int[] $languageIds
1254
     *
1255
     * @return array[]|\Generator
1256
     */
1257
    private function loadLocationEntriesMatchingMultipleLanguages($locationId, array $languageIds)
1258
    {
1259
        // note: alwaysAvailable for this use case is not relevant
1260
        $languageMask = $this->languageMaskGenerator->generateLanguageMaskFromLanguageIds(
1261
            $languageIds,
1262
            false
1263
        );
1264
1265
        $connection = $this->dbHandler->getConnection();
1266
        /** @var \Doctrine\DBAL\Connection $connection */
1267
        $query = $connection->createQueryBuilder();
1268
        $query
1269
            ->select('id', 'lang_mask', 'parent', 'text_md5')
1270
            ->from($this->table)
1271
            ->where('action = :action')
1272
            // fetch rows matching any of the given Languages
1273
            ->andWhere('lang_mask & :languageMask <> 0')
1274
            ->setParameter(':action', 'eznode:' . $locationId)
1275
            ->setParameter(':languageMask', $languageMask)
1276
        ;
1277
1278
        $statement = $query->execute();
1279
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
1280
1281
        return $rows ?: [];
1282
    }
1283
1284
    /**
1285
     * Delete URL aliases pointing to non-existent Locations.
1286
     *
1287
     * @return int Number of affected rows.
1288
     *
1289
     * @throws \Doctrine\DBAL\DBALException
1290
     */
1291
    public function deleteUrlAliasesWithoutLocation()
1292
    {
1293
        $dbPlatform = $this->connection->getDatabasePlatform();
1294
1295
        $subquery = $this->connection->createQueryBuilder();
1296
        $subquery
1297
            ->select('node_id')
1298
            ->from('ezcontentobject_tree', 't')
1299
            ->where(
1300
                $subquery->expr()->eq(
1301
                    't.node_id',
1302
                    sprintf(
1303
                        'CAST(%s as %s)',
1304
                        $dbPlatform->getSubstringExpression($this->table . '.action', 8),
1305
                        $this->getIntegerType($dbPlatform)
1306
                    )
1307
                )
1308
            )
1309
        ;
1310
1311
        $deleteQuery = $this->connection->createQueryBuilder();
1312
        $deleteQuery
1313
            ->delete($this->table)
1314
            ->where(
1315
                $deleteQuery->expr()->eq(
1316
                    'action_type',
1317
                    $deleteQuery->createPositionalParameter('eznode')
1318
                )
1319
            )
1320
            ->andWhere(
1321
                sprintf('NOT EXISTS (%s)', $subquery->getSQL())
1322
            )
1323
        ;
1324
1325
        return $deleteQuery->execute();
0 ignored issues
show
Bug Compatibility introduced by
The expression $deleteQuery->execute(); of type Doctrine\DBAL\Driver\ResultStatement|integer adds the type Doctrine\DBAL\Driver\ResultStatement to the return on line 1325 which is incompatible with the return type declared by the abstract method eZ\Publish\Core\Persiste...lAliasesWithoutLocation of type integer.
Loading history...
1326
    }
1327
1328
    /**
1329
     * Delete URL aliases pointing to non-existent parent nodes.
1330
     *
1331
     * @return int Number of affected rows.
1332
     */
1333 View Code Duplication
    public function deleteUrlAliasesWithoutParent()
1334
    {
1335
        $existingAliasesQuery = $this->getAllUrlAliasesQuery();
1336
1337
        $query = $this->connection->createQueryBuilder();
1338
        $query
1339
            ->delete($this->table)
1340
            ->where(
1341
                $query->expr()->neq(
1342
                    'parent',
1343
                    $query->createPositionalParameter(0, \PDO::PARAM_INT)
1344
                )
1345
            )
1346
            ->andWhere(
1347
                $query->expr()->notIn(
1348
                    'parent',
1349
                    $existingAliasesQuery
1350
                )
1351
            )
1352
        ;
1353
1354
        return $query->execute();
0 ignored issues
show
Bug Compatibility introduced by
The expression $query->execute(); of type Doctrine\DBAL\Driver\ResultStatement|integer adds the type Doctrine\DBAL\Driver\ResultStatement to the return on line 1354 which is incompatible with the return type declared by the abstract method eZ\Publish\Core\Persiste...UrlAliasesWithoutParent of type integer.
Loading history...
1355
    }
1356
1357
    /**
1358
     * Delete URL aliases which do not link to any existing URL alias node.
1359
     *
1360
     * Note: Typically link column value is used to determine original alias for an archived entries.
1361
     */
1362 View Code Duplication
    public function deleteUrlAliasesWithBrokenLink()
1363
    {
1364
        $existingAliasesQuery = $this->getAllUrlAliasesQuery();
1365
1366
        $query = $this->connection->createQueryBuilder();
1367
        $query
1368
            ->delete($this->table)
1369
            ->where(
1370
                $query->expr()->neq('id', 'link')
1371
            )
1372
            ->andWhere(
1373
                $query->expr()->notIn(
1374
                    'link',
1375
                    $existingAliasesQuery
1376
                )
1377
            )
1378
        ;
1379
1380
        return $query->execute();
1381
    }
1382
1383
    /**
1384
     * Get subquery for IDs of all URL aliases.
1385
     *
1386
     * @return string Query
1387
     */
1388
    private function getAllUrlAliasesQuery()
1389
    {
1390
        $existingAliasesQueryBuilder = $this->connection->createQueryBuilder();
1391
        $innerQueryBuilder = $this->connection->createQueryBuilder();
1392
1393
        return $existingAliasesQueryBuilder
1394
            ->select('tmp.id')
1395
            ->from(
1396
                // nest subquery to avoid same-table update error
1397
                '(' . $innerQueryBuilder->select('id')->from($this->table)->getSQL() . ')',
1398
                'tmp'
1399
            )
1400
            ->getSQL();
1401
    }
1402
1403
    /**
1404
     * Get DBMS-specific integer type.
1405
     *
1406
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $databasePlatform
1407
     *
1408
     * @return string
1409
     */
1410
    private function getIntegerType(AbstractPlatform $databasePlatform)
1411
    {
1412
        switch ($databasePlatform->getName()) {
1413
            case 'mysql':
1414
                return 'signed';
1415
            default:
1416
                return 'integer';
1417
        }
1418
    }
1419
}
1420