Completed
Push — 6.12 ( 1cff2f )
by Łukasz
64:51
created

DoctrineDatabase   D

Complexity

Total Complexity 78

Size/Duplication

Total Lines 1242
Duplicated Lines 22.87 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 284
loc 1242
rs 4.9086
c 0
b 0
f 0
wmc 78
lcom 1
cbo 10

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A setTable() 0 4 1
A loadLocationEntries() 11 55 3
A listGlobalEntries() 15 57 3
A isRootEntry() 0 21 2
A cleanupAfterPublish() 0 56 2
A archiveUrlAliasForDeletedTranslation() 0 10 2
B historizeBeforeSwap() 0 37 1
B historize() 0 33 1
B removeTranslation() 26 26 1
B historizeId() 0 39 2
B reparent() 24 24 1
A updateRow() 20 20 1
F insertRow() 0 46 16
C setQueryValues() 0 23 7
B getNextId() 29 29 2
B loadRow() 24 24 1
B loadUrlAliasData() 0 66 5
B loadAutogeneratedEntry() 39 39 2
B loadPathData() 0 37 3
B loadPathDataByHierarchy() 0 65 5
B removeCustomAlias() 27 27 2
B remove() 30 30 2
B loadAutogeneratedEntries() 39 39 2
B getLocationContentMainLanguageId() 0 35 2
B bulkRemoveTranslation() 0 24 1
B archiveUrlAliasesForDeletedTranslations() 0 42 5
B loadLocationEntriesMatchingMultipleLanguages() 0 26 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DoctrineDatabase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DoctrineDatabase, and based on these observations, apply Extract Interface, too.

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