Completed
Push — 6.7 ( eacd1f...2a3684 )
by
unknown
25:16
created

DoctrineDatabase::deleteUrlAliasesWithBrokenLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20

Duplication

Lines 20
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 20
loc 20
rs 9.6
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\Platforms\AbstractPlatform;
12
use eZ\Publish\Core\Base\Exceptions\BadStateException;
13
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway;
14
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
15
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
16
use eZ\Publish\Core\Persistence\Database\Query;
17
use RuntimeException;
18
19
/**
20
 * UrlAlias Gateway.
21
 */
22
class DoctrineDatabase extends Gateway
23
{
24
    /**
25
     * 2^30, since PHP_INT_MAX can cause overflows in DB systems, if PHP is run
26
     * on 64 bit systems.
27
     */
28
    const MAX_LIMIT = 1073741824;
29
30
    /**
31
     * Columns of database tables.
32
     *
33
     * @var array
34
     *
35
     * @todo remove after testing
36
     */
37
    protected $columns = array(
38
        'ezurlalias_ml' => array(
39
            'action',
40
            'action_type',
41
            'alias_redirects',
42
            'id',
43
            'is_alias',
44
            'is_original',
45
            'lang_mask',
46
            'link',
47
            'parent',
48
            'text',
49
            'text_md5',
50
        ),
51
    );
52
53
    /**
54
     * Doctrine database handler.
55
     *
56
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
57
     */
58
    protected $dbHandler;
59
60
    /**
61
     * Language mask generator.
62
     *
63
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
64
     */
65
    protected $languageMaskGenerator;
66
67
    /**
68
     * Main URL database table name.
69
     *
70
     * @var string
71
     */
72
    protected $table;
73
74
    /**
75
     * @var \Doctrine\DBAL\Connection
76
     */
77
    private $connection;
78
79
    /**
80
     * Creates a new DoctrineDatabase UrlAlias Gateway.
81
     *
82
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $dbHandler
83
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
84
     */
85
    public function __construct(
86
        DatabaseHandler $dbHandler,
87
        LanguageMaskGenerator $languageMaskGenerator
88
    ) {
89
        $this->dbHandler = $dbHandler;
90
        $this->languageMaskGenerator = $languageMaskGenerator;
91
        $this->table = static::TABLE;
92
        $this->connection = $dbHandler->getConnection();
93
    }
94
95
    public function setTable($name)
96
    {
97
        $this->table = $name;
98
    }
99
100
    /**
101
     * Loads list of aliases by given $locationId.
102
     *
103
     * @param mixed $locationId
104
     * @param bool $custom
105
     * @param mixed $languageId
106
     *
107
     * @return array
108
     */
109
    public function loadLocationEntries($locationId, $custom = false, $languageId = false)
110
    {
111
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
112
        $query = $this->dbHandler->createSelectQuery();
113
        $query->select(
114
            $this->dbHandler->quoteColumn('id'),
115
            $this->dbHandler->quoteColumn('link'),
116
            $this->dbHandler->quoteColumn('is_alias'),
117
            $this->dbHandler->quoteColumn('alias_redirects'),
118
            $this->dbHandler->quoteColumn('lang_mask'),
119
            $this->dbHandler->quoteColumn('is_original'),
120
            $this->dbHandler->quoteColumn('parent'),
121
            $this->dbHandler->quoteColumn('text'),
122
            $this->dbHandler->quoteColumn('text_md5'),
123
            $this->dbHandler->quoteColumn('action')
124
        )->from(
125
            $this->dbHandler->quoteTable($this->table)
126
        )->where(
127
            $query->expr->lAnd(
128
                $query->expr->eq(
129
                    $this->dbHandler->quoteColumn('action'),
130
                    $query->bindValue("eznode:{$locationId}", null, \PDO::PARAM_STR)
131
                ),
132
                $query->expr->eq(
133
                    $this->dbHandler->quoteColumn('is_original'),
134
                    $query->bindValue(1, null, \PDO::PARAM_INT)
135
                ),
136
                $query->expr->eq(
137
                    $this->dbHandler->quoteColumn('is_alias'),
138
                    $query->bindValue(
139
                        $custom ? 1 : 0,
140
                        null,
141
                        \PDO::PARAM_INT
142
                    )
143
                )
144
            )
145
        );
146
147 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...
148
            $query->where(
149
                $query->expr->gt(
150
                    $query->expr->bitAnd(
151
                        $this->dbHandler->quoteColumn('lang_mask'),
152
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
153
                    ),
154
                    0
155
                )
156
            );
157
        }
158
159
        $statement = $query->prepare();
160
        $statement->execute();
161
162
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
163
    }
164
165
    /**
166
     * Loads paged list of global aliases.
167
     *
168
     * @param string|null $languageCode
169
     * @param int $offset
170
     * @param int $limit
171
     *
172
     * @return array
173
     */
174
    public function listGlobalEntries($languageCode = null, $offset = 0, $limit = -1)
175
    {
176
        $limit = $limit === -1 ? self::MAX_LIMIT : $limit;
177
178
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
179
        $query = $this->dbHandler->createSelectQuery();
180
        $query->select(
181
            $this->dbHandler->quoteColumn('action'),
182
            $this->dbHandler->quoteColumn('id'),
183
            $this->dbHandler->quoteColumn('link'),
184
            $this->dbHandler->quoteColumn('is_alias'),
185
            $this->dbHandler->quoteColumn('alias_redirects'),
186
            $this->dbHandler->quoteColumn('lang_mask'),
187
            $this->dbHandler->quoteColumn('is_original'),
188
            $this->dbHandler->quoteColumn('parent'),
189
            $this->dbHandler->quoteColumn('text_md5')
190
        )->from(
191
            $this->dbHandler->quoteTable($this->table)
192
        )->where(
193
            $query->expr->lAnd(
194
                $query->expr->eq(
195
                    $this->dbHandler->quoteColumn('action_type'),
196
                    $query->bindValue('module', null, \PDO::PARAM_STR)
197
                ),
198
                $query->expr->eq(
199
                    $this->dbHandler->quoteColumn('is_original'),
200
                    $query->bindValue(1, null, \PDO::PARAM_INT)
201
                ),
202
                $query->expr->eq(
203
                    $this->dbHandler->quoteColumn('is_alias'),
204
                    $query->bindValue(1, null, \PDO::PARAM_INT)
205
                )
206
            )
207
        )->limit(
208
            $limit,
209
            $offset
210
        );
211 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...
212
            $query->where(
213
                $query->expr->gt(
214
                    $query->expr->bitAnd(
215
                        $this->dbHandler->quoteColumn('lang_mask'),
216
                        $query->bindValue(
217
                            $this->languageMaskGenerator->generateLanguageIndicator($languageCode, false),
218
                            null,
219
                            \PDO::PARAM_INT
220
                        )
221
                    ),
222
                    0
223
                )
224
            );
225
        }
226
        $statement = $query->prepare();
227
        $statement->execute();
228
229
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
230
    }
231
232
    /**
233
     * Returns boolean indicating if the row with given $id is special root entry.
234
     *
235
     * Special root entry entry will have parentId=0 and text=''.
236
     * In standard installation this entry will point to location with id=2.
237
     *
238
     * @param mixed $id
239
     *
240
     * @return bool
241
     */
242
    public function isRootEntry($id)
243
    {
244
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
245
        $query = $this->dbHandler->createSelectQuery();
246
        $query->select(
247
            $this->dbHandler->quoteColumn('text'),
248
            $this->dbHandler->quoteColumn('parent')
249
        )->from(
250
            $this->dbHandler->quoteTable($this->table)
251
        )->where(
252
            $query->expr->eq(
253
                $this->dbHandler->quoteColumn('id'),
254
                $query->bindValue($id, null, \PDO::PARAM_INT)
255
            )
256
        );
257
        $statement = $query->prepare();
258
        $statement->execute();
259
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
260
261
        return strlen($row['text']) == 0 && $row['parent'] == 0;
262
    }
263
264
    /**
265
     * Downgrades autogenerated entry matched by given $action and $languageId and negatively matched by
266
     * composite primary key.
267
     *
268
     * If language mask of the found entry is composite (meaning it consists of multiple language ids) given
269
     * $languageId will be removed from mask. Otherwise entry will be marked as history.
270
     *
271
     * @param string $action
272
     * @param mixed $languageId
273
     * @param mixed $newId
274
     * @param mixed $parentId
275
     * @param string $textMD5
276
     */
277
    public function cleanupAfterPublish($action, $languageId, $newId, $parentId, $textMD5)
278
    {
279
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
280
        $query = $this->dbHandler->createSelectQuery();
281
        $query->select(
282
            $this->dbHandler->quoteColumn('parent'),
283
            $this->dbHandler->quoteColumn('text_md5'),
284
            $this->dbHandler->quoteColumn('lang_mask')
285
        )->from(
286
            $this->dbHandler->quoteTable($this->table)
287
        )->where(
288
            $query->expr->lAnd(
289
                // 1) Autogenerated aliases that match action and language...
290
                $query->expr->eq(
291
                    $this->dbHandler->quoteColumn('action'),
292
                    $query->bindValue($action, null, \PDO::PARAM_STR)
293
                ),
294
                $query->expr->eq(
295
                    $this->dbHandler->quoteColumn('is_original'),
296
                    $query->bindValue(1, null, \PDO::PARAM_INT)
297
                ),
298
                $query->expr->eq(
299
                    $this->dbHandler->quoteColumn('is_alias'),
300
                    $query->bindValue(0, null, \PDO::PARAM_INT)
301
                ),
302
                $query->expr->gt(
303
                    $query->expr->bitAnd(
304
                        $this->dbHandler->quoteColumn('lang_mask'),
305
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
306
                    ),
307
                    0
308
                ),
309
                // 2) ...but not newly published entry
310
                $query->expr->not(
311
                    $query->expr->lAnd(
312
                        $query->expr->eq(
313
                            $this->dbHandler->quoteColumn('parent'),
314
                            $query->bindValue($parentId, null, \PDO::PARAM_INT)
315
                        ),
316
                        $query->expr->eq(
317
                            $this->dbHandler->quoteColumn('text_md5'),
318
                            $query->bindValue($textMD5, null, \PDO::PARAM_STR)
319
                        )
320
                    )
321
                )
322
            )
323
        );
324
325
        $statement = $query->prepare();
326
        $statement->execute();
327
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
328
329
        if (!empty($row)) {
330
            $this->archiveUrlAliasForDeletedTranslation($row['lang_mask'], $languageId, $row['parent'], $row['text_md5'], $newId);
331
        }
332
    }
333
334
    /**
335
     * Archive (remove or historize) obsolete URL aliases (for translations that were removed).
336
     *
337
     * @param int $languageMask all languages bit mask
338
     * @param int $languageId removed language Id
339
     * @param int $parent
340
     * @param string $textMD5 checksum
341
     * @param $linkId
342
     */
343
    private function archiveUrlAliasForDeletedTranslation($languageMask, $languageId, $parent, $textMD5, $linkId)
344
    {
345
        // If language mask is composite (consists of multiple languages) then remove given language from entry
346
        if ($languageMask & ~($languageId | 1)) {
347
            $this->removeTranslation($parent, $textMD5, $languageId);
348
        } else {
349
            // Otherwise mark entry as history
350
            $this->historize($parent, $textMD5, $linkId);
351
        }
352
    }
353
354
    public function historizeBeforeSwap($action, $languageMask)
355
    {
356
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
357
        $query = $this->dbHandler->createUpdateQuery();
358
        $query->update(
359
            $this->dbHandler->quoteTable($this->table)
360
        )->set(
361
            $this->dbHandler->quoteColumn('is_original'),
362
            $query->bindValue(0, null, \PDO::PARAM_INT)
363
        )->set(
364
            $this->dbHandler->quoteColumn('id'),
365
            $query->bindValue(
366
                $this->getNextId(),
367
                null,
368
                \PDO::PARAM_INT
369
            )
370
        )->where(
371
            $query->expr->lAnd(
372
                $query->expr->eq(
373
                    $this->dbHandler->quoteColumn('action'),
374
                    $query->bindValue($action, null, \PDO::PARAM_STR)
375
                ),
376
                $query->expr->eq(
377
                    $this->dbHandler->quoteColumn('is_original'),
378
                    $query->bindValue(1, null, \PDO::PARAM_INT)
379
                ),
380
                $query->expr->gt(
381
                    $query->expr->bitAnd(
382
                        $this->dbHandler->quoteColumn('lang_mask'),
383
                        $query->bindValue($languageMask & ~1, null, \PDO::PARAM_INT)
384
                    ),
385
                    0
386
                )
387
            )
388
        );
389
        $query->prepare()->execute();
390
    }
391
392
    /**
393
     * Updates single row matched by composite primary key.
394
     *
395
     * Sets "is_original" to 0 thus marking entry as history.
396
     *
397
     * Re-links history entries.
398
     *
399
     * When location alias is published we need to check for new history entries created with self::downgrade()
400
     * with the same action and language, update their "link" column with id of the published entry.
401
     * History entry "id" column is moved to next id value so that all active (non-history) entries are kept
402
     * under the same id.
403
     *
404
     * @param int $parentId
405
     * @param string $textMD5
406
     * @param int $newId
407
     */
408
    protected function historize($parentId, $textMD5, $newId)
409
    {
410
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
411
        $query = $this->dbHandler->createUpdateQuery();
412
        $query->update(
413
            $this->dbHandler->quoteTable($this->table)
414
        )->set(
415
            $this->dbHandler->quoteColumn('is_original'),
416
            $query->bindValue(0, null, \PDO::PARAM_INT)
417
        )->set(
418
            $this->dbHandler->quoteColumn('link'),
419
            $query->bindValue($newId, null, \PDO::PARAM_INT)
420
        )->set(
421
            $this->dbHandler->quoteColumn('id'),
422
            $query->bindValue(
423
                $this->getNextId(),
424
                null,
425
                \PDO::PARAM_INT
426
            )
427
        )->where(
428
            $query->expr->lAnd(
429
                $query->expr->eq(
430
                    $this->dbHandler->quoteColumn('parent'),
431
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
432
                ),
433
                $query->expr->eq(
434
                    $this->dbHandler->quoteColumn('text_md5'),
435
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
436
                )
437
            )
438
        );
439
        $query->prepare()->execute();
440
    }
441
442
    /**
443
     * Updates single row data matched by composite primary key.
444
     *
445
     * Removes given $languageId from entry's language mask
446
     *
447
     * @param mixed $parentId
448
     * @param string $textMD5
449
     * @param mixed $languageId
450
     */
451 View Code Duplication
    protected function removeTranslation($parentId, $textMD5, $languageId)
452
    {
453
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
454
        $query = $this->dbHandler->createUpdateQuery();
455
        $query->update(
456
            $this->dbHandler->quoteTable($this->table)
457
        )->set(
458
            $this->dbHandler->quoteColumn('lang_mask'),
459
            $query->expr->bitAnd(
460
                $this->dbHandler->quoteColumn('lang_mask'),
461
                $query->bindValue(~$languageId, null, \PDO::PARAM_INT)
462
            )
463
        )->where(
464
            $query->expr->lAnd(
465
                $query->expr->eq(
466
                    $this->dbHandler->quoteColumn('parent'),
467
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
468
                ),
469
                $query->expr->eq(
470
                    $this->dbHandler->quoteColumn('text_md5'),
471
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
472
                )
473
            )
474
        );
475
        $query->prepare()->execute();
476
    }
477
478
    /**
479
     * Marks all entries with given $id as history entries.
480
     *
481
     * This method is used by Handler::locationMoved(). Each row is separately historized
482
     * because future publishing needs to be able to take over history entries safely.
483
     *
484
     * @param mixed $id
485
     * @param mixed $link
486
     */
487
    public function historizeId($id, $link)
488
    {
489
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
490
        $query = $this->dbHandler->createSelectQuery();
491
        $query->select(
492
            $this->dbHandler->quoteColumn('parent'),
493
            $this->dbHandler->quoteColumn('text_md5')
494
        )->from(
495
            $this->dbHandler->quoteTable($this->table)
496
        )->where(
497
            $query->expr->lAnd(
498
                $query->expr->eq(
499
                    $this->dbHandler->quoteColumn('is_alias'),
500
                    $query->bindValue(0, null, \PDO::PARAM_INT)
501
                ),
502
                $query->expr->eq(
503
                    $this->dbHandler->quoteColumn('is_original'),
504
                    $query->bindValue(1, null, \PDO::PARAM_INT)
505
                ),
506
                $query->expr->eq(
507
                    $this->dbHandler->quoteColumn('action_type'),
508
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
509
                ),
510
                $query->expr->eq(
511
                    $this->dbHandler->quoteColumn('link'),
512
                    $query->bindValue($id, null, \PDO::PARAM_INT)
513
                )
514
            )
515
        );
516
517
        $statement = $query->prepare();
518
        $statement->execute();
519
520
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
521
522
        foreach ($rows as $row) {
523
            $this->historize($row['parent'], $row['text_md5'], $link);
524
        }
525
    }
526
527
    /**
528
     * Updates parent id of autogenerated entries.
529
     *
530
     * Update includes history entries.
531
     *
532
     * @param mixed $oldParentId
533
     * @param mixed $newParentId
534
     */
535 View Code Duplication
    public function reparent($oldParentId, $newParentId)
536
    {
537
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
538
        $query = $this->dbHandler->createUpdateQuery();
539
        $query->update(
540
            $this->dbHandler->quoteTable($this->table)
541
        )->set(
542
            $this->dbHandler->quoteColumn('parent'),
543
            $query->bindValue($newParentId, null, \PDO::PARAM_INT)
544
        )->where(
545
            $query->expr->lAnd(
546
                $query->expr->eq(
547
                    $this->dbHandler->quoteColumn('is_alias'),
548
                    $query->bindValue(0, null, \PDO::PARAM_INT)
549
                ),
550
                $query->expr->eq(
551
                    $this->dbHandler->quoteColumn('parent'),
552
                    $query->bindValue($oldParentId, null, \PDO::PARAM_INT)
553
                )
554
            )
555
        );
556
557
        $query->prepare()->execute();
558
    }
559
560
    /**
561
     * Updates single row data matched by composite primary key.
562
     *
563
     * Use optional parameter $languageMaskMatch to additionally limit the query match with languages.
564
     *
565
     * @param mixed $parentId
566
     * @param string $textMD5
567
     * @param array $values associative array with column names as keys and column values as values
568
     */
569 View Code Duplication
    public function updateRow($parentId, $textMD5, array $values)
570
    {
571
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
572
        $query = $this->dbHandler->createUpdateQuery();
573
        $query->update($this->dbHandler->quoteTable($this->table));
574
        $this->setQueryValues($query, $values);
575
        $query->where(
576
            $query->expr->lAnd(
577
                $query->expr->eq(
578
                    $this->dbHandler->quoteColumn('parent'),
579
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
580
                ),
581
                $query->expr->eq(
582
                    $this->dbHandler->quoteColumn('text_md5'),
583
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
584
                )
585
            )
586
        );
587
        $query->prepare()->execute();
588
    }
589
590
    /**
591
     * Inserts new row in urlalias_ml table.
592
     *
593
     * @param array $values
594
     *
595
     * @return mixed
596
     */
597
    public function insertRow(array $values)
598
    {
599
        // @todo remove after testing
600
        if (
601
            !isset($values['text']) ||
602
            !isset($values['text_md5']) ||
603
            !isset($values['action']) ||
604
            !isset($values['parent']) ||
605
            !isset($values['lang_mask'])) {
606
            throw new \Exception('value set is incomplete: ' . var_export($values, true) . ", can't execute insert");
607
        }
608
        if (!isset($values['id'])) {
609
            $values['id'] = $this->getNextId();
610
        }
611
        if (!isset($values['link'])) {
612
            $values['link'] = $values['id'];
613
        }
614
        if (!isset($values['is_original'])) {
615
            $values['is_original'] = ($values['id'] == $values['link'] ? 1 : 0);
616
        }
617
        if (!isset($values['is_alias'])) {
618
            $values['is_alias'] = 0;
619
        }
620
        if (!isset($values['alias_redirects'])) {
621
            $values['alias_redirects'] = 0;
622
        }
623
        if (!isset($values['action_type'])) {
624
            if (preg_match('#^(.+):.*#', $values['action'], $matches)) {
625
                $values['action_type'] = $matches[1];
626
            }
627
        }
628
        if ($values['is_alias']) {
629
            $values['is_original'] = 1;
630
        }
631
        if ($values['action'] === 'nop:') {
632
            $values['is_original'] = 0;
633
        }
634
635
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
636
        $query = $this->dbHandler->createInsertQuery();
637
        $query->insertInto($this->dbHandler->quoteTable($this->table));
638
        $this->setQueryValues($query, $values);
639
        $query->prepare()->execute();
640
641
        return $values['id'];
642
    }
643
644
    /**
645
     * Sets value for insert or update query.
646
     *
647
     * @param \eZ\Publish\Core\Persistence\Database\Query|\eZ\Publish\Core\Persistence\Database\InsertQuery|\eZ\Publish\Core\Persistence\Database\UpdateQuery $query
648
     * @param array $values
649
     *
650
     * @throws \Exception
651
     */
652
    protected function setQueryValues(Query $query, $values)
653
    {
654
        foreach ($values as $column => $value) {
655
            // @todo remove after testing
656
            if (!in_array($column, $this->columns['ezurlalias_ml'])) {
657
                throw new \Exception("unknown column '$column' for table 'ezurlalias_ml'");
658
            }
659
            switch ($column) {
660
                case 'text':
661
                case 'action':
662
                case 'text_md5':
663
                case 'action_type':
664
                    $pdoDataType = \PDO::PARAM_STR;
665
                    break;
666
                default:
667
                    $pdoDataType = \PDO::PARAM_INT;
668
            }
669
            $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...
670
                $this->dbHandler->quoteColumn($column),
671
                $query->bindValue($value, null, $pdoDataType)
672
            );
673
        }
674
    }
675
676
    /**
677
     * Returns next value for "id" column.
678
     *
679
     * @return mixed
680
     */
681 View Code Duplication
    public function getNextId()
682
    {
683
        $sequence = $this->dbHandler->getSequenceName('ezurlalias_ml_incr', 'id');
684
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
685
        $query = $this->dbHandler->createInsertQuery();
686
        $query->insertInto(
687
            $this->dbHandler->quoteTable('ezurlalias_ml_incr')
688
        );
689
        // ezcDatabase does not abstract the "auto increment id"
690
        // INSERT INTO ezurlalias_ml_incr VALUES(DEFAULT) is not an option due
691
        // to this mysql bug: http://bugs.mysql.com/bug.php?id=42270
692
        // as a result we are forced to check which database is currently used
693
        // to generate the correct SQL query
694
        // see https://jira.ez.no/browse/EZP-20652
695
        if ($this->dbHandler->useSequences()) {
696
            $query->set(
697
                $this->dbHandler->quoteColumn('id'),
698
                "nextval('{$sequence}')"
699
            );
700
        } else {
701
            $query->set(
702
                $this->dbHandler->quoteColumn('id'),
703
                $query->bindValue(null, null, \PDO::PARAM_NULL)
704
            );
705
        }
706
        $query->prepare()->execute();
707
708
        return $this->dbHandler->lastInsertId($sequence);
709
    }
710
711
    /**
712
     * Loads single row data matched by composite primary key.
713
     *
714
     * @param mixed $parentId
715
     * @param string $textMD5
716
     *
717
     * @return array
718
     */
719 View Code Duplication
    public function loadRow($parentId, $textMD5)
720
    {
721
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
722
        $query = $this->dbHandler->createSelectQuery();
723
        $query->select('*')->from(
724
            $this->dbHandler->quoteTable($this->table)
725
        )->where(
726
            $query->expr->lAnd(
727
                $query->expr->eq(
728
                    $this->dbHandler->quoteColumn('parent'),
729
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
730
                ),
731
                $query->expr->eq(
732
                    $this->dbHandler->quoteColumn('text_md5'),
733
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
734
                )
735
            )
736
        );
737
738
        $statement = $query->prepare();
739
        $statement->execute();
740
741
        return $statement->fetch(\PDO::FETCH_ASSOC);
742
    }
743
744
    /**
745
     * Loads complete URL alias data by given array of path hashes.
746
     *
747
     * @param string[] $urlHashes URL string hashes
748
     *
749
     * @return array
750
     */
751
    public function loadUrlAliasData(array $urlHashes)
752
    {
753
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
754
        $query = $this->dbHandler->createSelectQuery();
755
756
        $count = count($urlHashes);
757
        foreach ($urlHashes as $level => $urlPartHash) {
758
            $tableName = $this->table . ($level === $count - 1 ? '' : $level);
759
760
            if ($level === $count - 1) {
761
                $query->select(
762
                    $this->dbHandler->quoteColumn('id', $tableName),
763
                    $this->dbHandler->quoteColumn('link', $tableName),
764
                    $this->dbHandler->quoteColumn('is_alias', $tableName),
765
                    $this->dbHandler->quoteColumn('alias_redirects', $tableName),
766
                    $this->dbHandler->quoteColumn('is_original', $tableName),
767
                    $this->dbHandler->quoteColumn('action', $tableName),
768
                    $this->dbHandler->quoteColumn('action_type', $tableName),
769
                    $this->dbHandler->quoteColumn('lang_mask', $tableName),
770
                    $this->dbHandler->quoteColumn('text', $tableName),
771
                    $this->dbHandler->quoteColumn('parent', $tableName),
772
                    $this->dbHandler->quoteColumn('text_md5', $tableName)
773
                )->from(
774
                    $this->dbHandler->quoteTable($this->table)
775
                );
776
            } else {
777
                $query->select(
778
                    $this->dbHandler->aliasedColumn($query, 'id', $tableName),
779
                    $this->dbHandler->aliasedColumn($query, 'link', $tableName),
780
                    $this->dbHandler->aliasedColumn($query, 'is_alias', $tableName),
781
                    $this->dbHandler->aliasedColumn($query, 'alias_redirects', $tableName),
782
                    $this->dbHandler->aliasedColumn($query, 'is_original', $tableName),
783
                    $this->dbHandler->aliasedColumn($query, 'action', $tableName),
784
                    $this->dbHandler->aliasedColumn($query, 'action_type', $tableName),
785
                    $this->dbHandler->aliasedColumn($query, 'lang_mask', $tableName),
786
                    $this->dbHandler->aliasedColumn($query, 'text', $tableName),
787
                    $this->dbHandler->aliasedColumn($query, 'parent', $tableName),
788
                    $this->dbHandler->aliasedColumn($query, 'text_md5', $tableName)
789
                )->from(
790
                    $query->alias($this->table, $tableName)
791
                );
792
            }
793
794
            $query->where(
795
                $query->expr->lAnd(
796
                    $query->expr->eq(
797
                        $this->dbHandler->quoteColumn('text_md5', $tableName),
798
                        $query->bindValue($urlPartHash, null, \PDO::PARAM_STR)
799
                    ),
800
                    $query->expr->eq(
801
                        $this->dbHandler->quoteColumn('parent', $tableName),
802
                        // root entry has parent column set to 0
803
                        isset($previousTableName) ? $this->dbHandler->quoteColumn('link', $previousTableName) : $query->bindValue(0, null, \PDO::PARAM_INT)
804
                    )
805
                )
806
            );
807
808
            $previousTableName = $tableName;
809
        }
810
        $query->limit(1);
811
812
        $statement = $query->prepare();
813
        $statement->execute();
814
815
        return $statement->fetch(\PDO::FETCH_ASSOC);
816
    }
817
818
    /**
819
     * Loads autogenerated entry id by given $action and optionally $parentId.
820
     *
821
     * @param string $action
822
     * @param mixed|null $parentId
823
     *
824
     * @return array
825
     */
826 View Code Duplication
    public function loadAutogeneratedEntry($action, $parentId = null)
827
    {
828
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
829
        $query = $this->dbHandler->createSelectQuery();
830
        $query->select(
831
            '*'
832
        )->from(
833
            $this->dbHandler->quoteTable($this->table)
834
        )->where(
835
            $query->expr->lAnd(
836
                $query->expr->eq(
837
                    $this->dbHandler->quoteColumn('action'),
838
                    $query->bindValue($action, null, \PDO::PARAM_STR)
839
                ),
840
                $query->expr->eq(
841
                    $this->dbHandler->quoteColumn('is_original'),
842
                    $query->bindValue(1, null, \PDO::PARAM_INT)
843
                ),
844
                $query->expr->eq(
845
                    $this->dbHandler->quoteColumn('is_alias'),
846
                    $query->bindValue(0, null, \PDO::PARAM_INT)
847
                )
848
            )
849
        );
850
851
        if (isset($parentId)) {
852
            $query->where(
853
                $query->expr->eq(
854
                    $this->dbHandler->quoteColumn('parent'),
855
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
856
                )
857
            );
858
        }
859
860
        $statement = $query->prepare();
861
        $statement->execute();
862
863
        return $statement->fetch(\PDO::FETCH_ASSOC);
864
    }
865
866
    /**
867
     * Loads all data for the path identified by given $id.
868
     *
869
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
870
     *
871
     * @param int $id
872
     *
873
     * @return array
874
     */
875
    public function loadPathData($id)
876
    {
877
        $pathData = array();
878
879
        while ($id != 0) {
880
            /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
881
            $query = $this->dbHandler->createSelectQuery();
882
            $query->select(
883
                $this->dbHandler->quoteColumn('parent'),
884
                $this->dbHandler->quoteColumn('lang_mask'),
885
                $this->dbHandler->quoteColumn('text')
886
            )->from(
887
                $this->dbHandler->quoteTable($this->table)
888
            )->where(
889
                $query->expr->eq(
890
                    $this->dbHandler->quoteColumn('id'),
891
                    $query->bindValue($id, null, \PDO::PARAM_INT)
892
                )
893
            );
894
895
            $statement = $query->prepare();
896
            $statement->execute();
897
898
            $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
899
            if (empty($rows)) {
900
                // Normally this should never happen
901
                $pathDataArray = [];
902
                foreach ($pathData as $path) {
903
                    if (!isset($path[0]['text'])) {
904
                        continue;
905
                    }
906
907
                    $pathDataArray[] = $path[0]['text'];
908
                }
909
910
                $path = implode('/', $pathDataArray);
911
                throw new BadStateException(
912
                    'id',
913
                    "Unable to load path data, the path ...'{$path}' is broken, alias id '{$id}' not found. " .
914
                    'To fix all broken paths run the ezplatform:urls:regenerate-aliases command'
915
                );
916
            }
917
918
            $id = $rows[0]['parent'];
919
            array_unshift($pathData, $rows);
920
        }
921
922
        return $pathData;
923
    }
924
925
    /**
926
     * Loads path data identified by given ordered array of hierarchy data.
927
     *
928
     * The first entry in $hierarchyData corresponds to the top-most path element in the path, the second entry the
929
     * child of the first path element and so on.
930
     * This method is faster than self::getPath() since it can fetch all elements using only one query, but can be used
931
     * only for autogenerated paths.
932
     *
933
     * @param array $hierarchyData
934
     *
935
     * @return array
936
     */
937
    public function loadPathDataByHierarchy(array $hierarchyData)
938
    {
939
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
940
        $query = $this->dbHandler->createSelectQuery();
941
942
        $hierarchyConditions = array();
943
        foreach ($hierarchyData as $levelData) {
944
            $hierarchyConditions[] = $query->expr->lAnd(
945
                $query->expr->eq(
946
                    $this->dbHandler->quoteColumn('parent'),
947
                    $query->bindValue(
948
                        $levelData['parent'],
949
                        null,
950
                        \PDO::PARAM_INT
951
                    )
952
                ),
953
                $query->expr->eq(
954
                    $this->dbHandler->quoteColumn('action'),
955
                    $query->bindValue(
956
                        $levelData['action'],
957
                        null,
958
                        \PDO::PARAM_STR
959
                    )
960
                ),
961
                $query->expr->eq(
962
                    $this->dbHandler->quoteColumn('id'),
963
                    $query->bindValue(
964
                        $levelData['id'],
965
                        null,
966
                        \PDO::PARAM_INT
967
                    )
968
                )
969
            );
970
        }
971
972
        $query->select(
973
            $this->dbHandler->quoteColumn('action'),
974
            $this->dbHandler->quoteColumn('lang_mask'),
975
            $this->dbHandler->quoteColumn('text')
976
        )->from(
977
            $this->dbHandler->quoteTable($this->table)
978
        )->where(
979
            $query->expr->lOr($hierarchyConditions)
980
        );
981
982
        $statement = $query->prepare();
983
        $statement->execute();
984
985
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
986
        $rowsMap = array();
987
        foreach ($rows as $row) {
988
            $rowsMap[$row['action']][] = $row;
989
        }
990
991
        if (count($rowsMap) !== count($hierarchyData)) {
992
            throw new \RuntimeException('The path is corrupted.');
993
        }
994
995
        $data = array();
996
        foreach ($hierarchyData as $levelData) {
997
            $data[] = $rowsMap[$levelData['action']];
998
        }
999
1000
        return $data;
1001
    }
1002
1003
    /**
1004
     * Deletes single custom alias row matched by composite primary key.
1005
     *
1006
     * @param mixed $parentId
1007
     * @param string $textMD5
1008
     *
1009
     * @return bool
1010
     */
1011 View Code Duplication
    public function removeCustomAlias($parentId, $textMD5)
1012
    {
1013
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1014
        $query = $this->dbHandler->createDeleteQuery();
1015
        $query->deleteFrom(
1016
            $this->dbHandler->quoteTable($this->table)
1017
        )->where(
1018
            $query->expr->lAnd(
1019
                $query->expr->eq(
1020
                    $this->dbHandler->quoteColumn('parent'),
1021
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
1022
                ),
1023
                $query->expr->eq(
1024
                    $this->dbHandler->quoteColumn('text_md5'),
1025
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
1026
                ),
1027
                $query->expr->eq(
1028
                    $this->dbHandler->quoteColumn('is_alias'),
1029
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1030
                )
1031
            )
1032
        );
1033
        $statement = $query->prepare();
1034
        $statement->execute();
1035
1036
        return $statement->rowCount() === 1 ?: false;
1037
    }
1038
1039
    /**
1040
     * Deletes all rows with given $action and optionally $id.
1041
     *
1042
     * If $id is set only autogenerated entries will be removed.
1043
     *
1044
     * @param mixed $action
1045
     * @param mixed|null $id
1046
     *
1047
     * @return bool
1048
     */
1049 View Code Duplication
    public function remove($action, $id = null)
1050
    {
1051
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1052
        $query = $this->dbHandler->createDeleteQuery();
1053
        $query->deleteFrom(
1054
            $this->dbHandler->quoteTable($this->table)
1055
        )->where(
1056
            $query->expr->eq(
1057
                $this->dbHandler->quoteColumn('action'),
1058
                $query->bindValue($action, null, \PDO::PARAM_STR)
1059
            )
1060
        );
1061
1062
        if ($id !== null) {
1063
            $query->where(
1064
                $query->expr->lAnd(
1065
                    $query->expr->eq(
1066
                        $this->dbHandler->quoteColumn('is_alias'),
1067
                        $query->bindValue(0, null, \PDO::PARAM_INT)
1068
                    ),
1069
                    $query->expr->eq(
1070
                        $this->dbHandler->quoteColumn('id'),
1071
                        $query->bindValue($id, null, \PDO::PARAM_INT)
1072
                    )
1073
                )
1074
            );
1075
        }
1076
1077
        $query->prepare()->execute();
1078
    }
1079
1080
    /**
1081
     * Loads all autogenerated entries with given $parentId with optionally included history entries.
1082
     *
1083
     * @param mixed $parentId
1084
     * @param bool $includeHistory
1085
     *
1086
     * @return array
1087
     */
1088 View Code Duplication
    public function loadAutogeneratedEntries($parentId, $includeHistory = false)
1089
    {
1090
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1091
        $query = $this->dbHandler->createSelectQuery();
1092
        $query->select(
1093
            '*'
1094
        )->from(
1095
            $this->dbHandler->quoteTable($this->table)
1096
        )->where(
1097
            $query->expr->lAnd(
1098
                $query->expr->eq(
1099
                    $this->dbHandler->quoteColumn('parent'),
1100
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
1101
                ),
1102
                $query->expr->eq(
1103
                    $this->dbHandler->quoteColumn('action_type'),
1104
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
1105
                ),
1106
                $query->expr->eq(
1107
                    $this->dbHandler->quoteColumn('is_alias'),
1108
                    $query->bindValue(0, null, \PDO::PARAM_INT)
1109
                )
1110
            )
1111
        );
1112
1113
        if (!$includeHistory) {
1114
            $query->where(
1115
                $query->expr->eq(
1116
                    $this->dbHandler->quoteColumn('is_original'),
1117
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1118
                )
1119
            );
1120
        }
1121
1122
        $statement = $query->prepare();
1123
        $statement->execute();
1124
1125
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1126
    }
1127
1128
    public function getLocationContentMainLanguageId($locationId)
1129
    {
1130
        $dbHandler = $this->dbHandler;
1131
        $query = $dbHandler->createSelectQuery();
1132
        $query
1133
            ->select($dbHandler->quoteColumn('initial_language_id', 'ezcontentobject'))
1134
            ->from($dbHandler->quoteTable('ezcontentobject'))
1135
            ->innerJoin(
1136
                $dbHandler->quoteTable('ezcontentobject_tree'),
1137
                $query->expr->lAnd(
1138
                    $query->expr->eq(
1139
                        $dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_tree'),
1140
                        $dbHandler->quoteColumn('id', 'ezcontentobject')
1141
                    ),
1142
                    $query->expr->eq(
1143
                        $dbHandler->quoteColumn('node_id', 'ezcontentobject_tree'),
1144
                        $dbHandler->quoteColumn('main_node_id', 'ezcontentobject_tree')
1145
                    ),
1146
                    $query->expr->eq(
1147
                        $dbHandler->quoteColumn('node_id', 'ezcontentobject_tree'),
1148
                        $query->bindValue($locationId, null, \PDO::PARAM_INT)
1149
                    )
1150
                )
1151
            );
1152
1153
        $statement = $query->prepare();
1154
        $statement->execute();
1155
        $languageId = $statement->fetchColumn();
1156
1157
        if ($languageId === false) {
1158
            throw new RuntimeException("Could not find Content for Location #{$locationId}");
1159
        }
1160
1161
        return $languageId;
1162
    }
1163
1164
    /**
1165
     * Archive (remove or historize) URL aliases for removed Translations.
1166
     *
1167
     * @param int $locationId
1168
     * @param int $parentId
1169
     * @param int[] $languageIds Language IDs of removed Translations
1170
     */
1171
    public function archiveUrlAliasesForDeletedTranslations($locationId, $parentId, array $languageIds)
1172
    {
1173
        // determine proper parent for linking historized entry
1174
        $existingLocationEntry = $this->loadAutogeneratedEntry(
1175
            'eznode:' . $locationId,
1176
            $parentId
1177
        );
1178
1179
        // filter existing URL alias entries by any of the specified removed languages
1180
        $rows = $this->loadLocationEntriesMatchingMultipleLanguages(
1181
            $locationId,
1182
            $languageIds
1183
        );
1184
1185
        // remove specific languages from a bit mask
1186
        foreach ($rows as $row) {
1187
            // filter mask to reduce the number of calls to storage engine
1188
            $rowLanguageMask = (int) $row['lang_mask'];
1189
            $languageIdsToBeRemoved = array_filter(
1190
                $languageIds,
1191
                function ($languageId) use ($rowLanguageMask) {
1192
                    return $languageId & $rowLanguageMask;
1193
                }
1194
            );
1195
1196
            if (empty($languageIdsToBeRemoved)) {
1197
                continue;
1198
            }
1199
1200
            // use existing entry to link archived alias or use current alias id
1201
            $linkToId = !empty($existingLocationEntry) ? $existingLocationEntry['id'] : $row['id'];
1202
            foreach ($languageIdsToBeRemoved as $languageId) {
1203
                $this->archiveUrlAliasForDeletedTranslation(
1204
                    $row['lang_mask'],
1205
                    $languageId,
1206
                    $row['parent'],
1207
                    $row['text_md5'],
1208
                    $linkToId
1209
                );
1210
            }
1211
        }
1212
    }
1213
1214
    /**
1215
     * Delete URL aliases pointing to non-existent Locations.
1216
     *
1217
     * @return int Number of affected rows.
1218
     *
1219
     * @throws \Doctrine\DBAL\DBALException
1220
     */
1221
    public function deleteUrlAliasesWithoutLocation()
1222
    {
1223
        $dbPlatform = $this->connection->getDatabasePlatform();
1224
1225
        $subquery = $this->connection->createQueryBuilder();
1226
        $subquery
1227
            ->select('node_id')
1228
            ->from('ezcontentobject_tree', 't')
1229
            ->where(
1230
                $subquery->expr()->eq(
1231
                    't.node_id',
1232
                    sprintf(
1233
                        'CAST(%s as %s)',
1234
                        $dbPlatform->getSubstringExpression($this->table . '.action', 8),
1235
                        $this->getIntegerType($dbPlatform)
1236
                    )
1237
                )
1238
            )
1239
        ;
1240
1241
        $deleteQuery = $this->connection->createQueryBuilder();
1242
        $deleteQuery
1243
            ->delete($this->table)
1244
            ->where(
1245
                $deleteQuery->expr()->eq(
1246
                    'action_type',
1247
                    $deleteQuery->createPositionalParameter('eznode')
1248
                )
1249
            )
1250
            ->andWhere(
1251
                sprintf('NOT EXISTS (%s)', $subquery->getSQL())
1252
            )
1253
        ;
1254
1255
        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 1255 which is incompatible with the return type declared by the abstract method eZ\Publish\Core\Persiste...lAliasesWithoutLocation of type integer.
Loading history...
1256
    }
1257
1258
    /**
1259
     * Delete URL aliases pointing to non-existent parent nodes.
1260
     *
1261
     * @return int Number of affected rows.
1262
     */
1263 View Code Duplication
    public function deleteUrlAliasesWithoutParent()
1264
    {
1265
        $existingAliasesQuery = $this->getAllUrlAliasesQuery();
1266
1267
        $query = $this->connection->createQueryBuilder();
1268
        $query
1269
            ->delete($this->table)
1270
            ->where(
1271
                $query->expr()->neq(
1272
                    'parent',
1273
                    $query->createPositionalParameter(0, \PDO::PARAM_INT)
1274
                )
1275
            )
1276
            ->andWhere(
1277
                $query->expr()->notIn(
1278
                    'parent',
1279
                    $existingAliasesQuery
1280
                )
1281
            )
1282
        ;
1283
1284
        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 1284 which is incompatible with the return type declared by the abstract method eZ\Publish\Core\Persiste...UrlAliasesWithoutParent of type integer.
Loading history...
1285
    }
1286
1287
    /**
1288
     * Delete URL aliases which do not link to any existing URL alias node.
1289
     *
1290
     * Note: Typically link column value is used to determine original alias for an archived entries.
1291
     */
1292 View Code Duplication
    public function deleteUrlAliasesWithBrokenLink()
1293
    {
1294
        $existingAliasesQuery = $this->getAllUrlAliasesQuery();
1295
1296
        $query = $this->connection->createQueryBuilder();
1297
        $query
1298
            ->delete($this->table)
1299
            ->where(
1300
                $query->expr()->neq('id', 'link')
1301
            )
1302
            ->andWhere(
1303
                $query->expr()->notIn(
1304
                    'link',
1305
                    $existingAliasesQuery
1306
                )
1307
            )
1308
        ;
1309
1310
        return $query->execute();
1311
    }
1312
1313
    /**
1314
     * Get subquery for IDs of all URL aliases.
1315
     *
1316
     * @return string Query
1317
     */
1318
    private function getAllUrlAliasesQuery()
1319
    {
1320
        $existingAliasesQueryBuilder = $this->connection->createQueryBuilder();
1321
        $innerQueryBuilder = $this->connection->createQueryBuilder();
1322
1323
        return $existingAliasesQueryBuilder
1324
            ->select('tmp.id')
1325
            ->from(
1326
                // nest subquery to avoid same-table update error
1327
                '(' . $innerQueryBuilder->select('id')->from($this->table)->getSQL() . ')',
1328
                'tmp'
1329
            )
1330
            ->getSQL();
1331
    }
1332
1333
    /**
1334
     * Load list of aliases for given $locationId matching any of the Languages specified by $languageMask.
1335
     *
1336
     * @param int $locationId
1337
     * @param int[] $languageIds
1338
     *
1339
     * @return array[]
1340
     */
1341
    private function loadLocationEntriesMatchingMultipleLanguages($locationId, array $languageIds)
1342
    {
1343
        // note: alwaysAvailable for this use case is not relevant
1344
        $languageMask = $this->languageMaskGenerator->generateLanguageMaskFromLanguageIds(
1345
            $languageIds,
1346
            false
1347
        );
1348
1349
        /** @var \Doctrine\DBAL\Connection $connection */
1350
        $connection = $this->dbHandler->getConnection();
1351
        $query = $connection->createQueryBuilder();
1352
        $query
1353
            ->select('id', 'lang_mask', 'parent', 'text_md5')
1354
            ->from($this->table)
1355
            ->where('action = :action')
1356
            // fetch rows matching any of the given Languages
1357
            ->andWhere('lang_mask & :languageMask <> 0')
1358
            ->setParameter(':action', 'eznode:' . $locationId)
1359
            ->setParameter(':languageMask', $languageMask)
1360
        ;
1361
1362
        $statement = $query->execute();
1363
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
1364
1365
        return $rows ?: [];
1366
    }
1367
1368
    /**
1369
     * Get DBMS-specific integer type.
1370
     *
1371
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $databasePlatform
1372
     *
1373
     * @return string
1374
     */
1375
    private function getIntegerType(AbstractPlatform $databasePlatform)
1376
    {
1377
        switch ($databasePlatform->getName()) {
1378
            case 'mysql':
1379
                return 'signed';
1380
            default:
1381
                return 'integer';
1382
        }
1383
    }
1384
}
1385