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