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 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 |
||
32 | class DoctrineDatabase extends Gateway |
||
33 | { |
||
34 | /** |
||
35 | * 2^30, since PHP_INT_MAX can cause overflows in DB systems, if PHP is run |
||
36 | * on 64 bit systems. |
||
37 | */ |
||
38 | const MAX_LIMIT = 1073741824; |
||
39 | |||
40 | /** |
||
41 | * Database handler. |
||
42 | * |
||
43 | * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler |
||
44 | */ |
||
45 | protected $handler; |
||
46 | |||
47 | /** |
||
48 | * @var \Doctrine\DBAL\Connection |
||
49 | */ |
||
50 | protected $connection; |
||
51 | |||
52 | /** |
||
53 | * Language mask generator. |
||
54 | * |
||
55 | * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator |
||
56 | */ |
||
57 | protected $languageMaskGenerator; |
||
58 | |||
59 | /** |
||
60 | * Construct from database handler. |
||
61 | * |
||
62 | * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $handler |
||
63 | * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator |
||
64 | */ |
||
65 | public function __construct(DatabaseHandler $handler, MaskGenerator $languageMaskGenerator) |
||
66 | { |
||
67 | $this->handler = $handler; |
||
68 | $this->connection = $handler->getConnection(); |
||
69 | $this->languageMaskGenerator = $languageMaskGenerator; |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * {@inheritdoc} |
||
74 | */ |
||
75 | View Code Duplication | public function getBasicNodeData($nodeId, array $translations = null, bool $useAlwaysAvailable = true) |
|
76 | { |
||
77 | $q = $this->createNodeQueryBuilder($translations, $useAlwaysAvailable); |
||
78 | $q->where( |
||
79 | $q->expr()->eq('t.node_id', $q->createNamedParameter($nodeId, PDO::PARAM_INT)) |
||
80 | ); |
||
81 | |||
82 | if ($row = $q->execute()->fetch(FetchMode::ASSOCIATIVE)) { |
||
83 | return $row; |
||
84 | } |
||
85 | |||
86 | throw new NotFound('location', $nodeId); |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * {@inheritdoc} |
||
91 | */ |
||
92 | public function getNodeDataList(array $locationIds, array $translations = null, bool $useAlwaysAvailable = true): iterable |
||
93 | { |
||
94 | $q = $this->createNodeQueryBuilder($translations, $useAlwaysAvailable); |
||
95 | $q->where( |
||
96 | $q->expr()->in( |
||
97 | 't.node_id', |
||
98 | $q->createNamedParameter($locationIds, Connection::PARAM_INT_ARRAY) |
||
99 | ) |
||
100 | ); |
||
101 | |||
102 | return $q->execute()->fetchAll(FetchMode::ASSOCIATIVE); |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * {@inheritdoc} |
||
107 | */ |
||
108 | View Code Duplication | public function getBasicNodeDataByRemoteId($remoteId, array $translations = null, bool $useAlwaysAvailable = true) |
|
109 | { |
||
110 | $q = $this->createNodeQueryBuilder($translations, $useAlwaysAvailable); |
||
111 | $q->where( |
||
112 | $q->expr()->eq('t.remote_id', $q->createNamedParameter($remoteId, PDO::PARAM_STR)) |
||
113 | ); |
||
114 | |||
115 | if ($row = $q->execute()->fetch(FetchMode::ASSOCIATIVE)) { |
||
116 | return $row; |
||
117 | } |
||
118 | |||
119 | throw new NotFound('location', $remoteId); |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Loads data for all Locations for $contentId, optionally only in the |
||
124 | * subtree starting at $rootLocationId. |
||
125 | * |
||
126 | * @param int $contentId |
||
127 | * @param int $rootLocationId |
||
128 | * |
||
129 | * @return array |
||
130 | */ |
||
131 | public function loadLocationDataByContent($contentId, $rootLocationId = null) |
||
132 | { |
||
133 | $query = $this->handler->createSelectQuery(); |
||
134 | $query |
||
135 | ->select('*') |
||
136 | ->from($this->handler->quoteTable('ezcontentobject_tree')) |
||
137 | ->where( |
||
138 | $query->expr->eq( |
||
139 | $this->handler->quoteColumn('contentobject_id'), |
||
140 | $query->bindValue($contentId) |
||
141 | ) |
||
142 | ); |
||
143 | |||
144 | if ($rootLocationId !== null) { |
||
145 | $this->applySubtreeLimitation($query, $rootLocationId); |
||
146 | } |
||
147 | |||
148 | $statement = $query->prepare(); |
||
149 | $statement->execute(); |
||
150 | |||
151 | return $statement->fetchAll(\PDO::FETCH_ASSOC); |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * @see \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway::loadParentLocationsDataForDraftContent |
||
156 | */ |
||
157 | public function loadParentLocationsDataForDraftContent($contentId, $drafts = null) |
||
158 | { |
||
159 | /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */ |
||
160 | $query = $this->handler->createSelectQuery(); |
||
161 | $query->selectDistinct( |
||
162 | 'ezcontentobject_tree.*' |
||
163 | )->from( |
||
164 | $this->handler->quoteTable('ezcontentobject_tree') |
||
165 | )->innerJoin( |
||
166 | $this->handler->quoteTable('eznode_assignment'), |
||
167 | $query->expr->lAnd( |
||
168 | $query->expr->eq( |
||
169 | $this->handler->quoteColumn('node_id', 'ezcontentobject_tree'), |
||
170 | $this->handler->quoteColumn('parent_node', 'eznode_assignment') |
||
171 | ), |
||
172 | $query->expr->eq( |
||
173 | $this->handler->quoteColumn('contentobject_id', 'eznode_assignment'), |
||
174 | $query->bindValue($contentId, null, \PDO::PARAM_INT) |
||
175 | ), |
||
176 | $query->expr->eq( |
||
177 | $this->handler->quoteColumn('op_code', 'eznode_assignment'), |
||
178 | $query->bindValue(self::NODE_ASSIGNMENT_OP_CODE_CREATE, null, \PDO::PARAM_INT) |
||
179 | ) |
||
180 | ) |
||
181 | )->innerJoin( |
||
182 | $this->handler->quoteTable('ezcontentobject'), |
||
183 | $query->expr->lAnd( |
||
184 | $query->expr->lOr( |
||
185 | $query->expr->eq( |
||
186 | $this->handler->quoteColumn('contentobject_id', 'eznode_assignment'), |
||
187 | $this->handler->quoteColumn('id', 'ezcontentobject') |
||
188 | ) |
||
189 | ), |
||
190 | $query->expr->eq( |
||
191 | $this->handler->quoteColumn('status', 'ezcontentobject'), |
||
192 | $query->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT) |
||
193 | ) |
||
194 | ) |
||
195 | ); |
||
196 | |||
197 | $statement = $query->prepare(); |
||
198 | $statement->execute(); |
||
199 | |||
200 | return $statement->fetchAll(\PDO::FETCH_ASSOC); |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Find all content in the given subtree. |
||
205 | * |
||
206 | * @param mixed $sourceId |
||
207 | * @param bool $onlyIds |
||
208 | * |
||
209 | * @return array |
||
210 | */ |
||
211 | public function getSubtreeContent($sourceId, $onlyIds = false) |
||
212 | { |
||
213 | $query = $this->handler->createSelectQuery(); |
||
214 | $query->select($onlyIds ? 'node_id, contentobject_id, depth' : '*')->from( |
||
215 | $this->handler->quoteTable('ezcontentobject_tree') |
||
216 | ); |
||
217 | $this->applySubtreeLimitation($query, $sourceId); |
||
218 | $query->orderBy( |
||
219 | $this->handler->quoteColumn('depth', 'ezcontentobject_tree') |
||
220 | )->orderBy( |
||
221 | $this->handler->quoteColumn('node_id', 'ezcontentobject_tree') |
||
222 | ); |
||
223 | $statement = $query->prepare(); |
||
224 | $statement->execute(); |
||
225 | |||
226 | $results = $statement->fetchAll($onlyIds ? (PDO::FETCH_COLUMN | PDO::FETCH_GROUP) : PDO::FETCH_ASSOC); |
||
227 | // array_map() is used to to map all elements stored as $results[$i][0] to $results[$i] |
||
228 | return $onlyIds ? array_map('reset', $results) : $results; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Limits the given $query to the subtree starting at $rootLocationId. |
||
233 | * |
||
234 | * @param \eZ\Publish\Core\Persistence\Database\Query $query |
||
235 | * @param string $rootLocationId |
||
236 | */ |
||
237 | protected function applySubtreeLimitation(DatabaseQuery $query, $rootLocationId) |
||
238 | { |
||
239 | $query->where( |
||
|
|||
240 | $query->expr->like( |
||
241 | $this->handler->quoteColumn('path_string', 'ezcontentobject_tree'), |
||
242 | $query->bindValue('%/' . $rootLocationId . '/%') |
||
243 | ) |
||
244 | ); |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * Returns data for the first level children of the location identified by given $locationId. |
||
249 | * |
||
250 | * @param mixed $locationId |
||
251 | * |
||
252 | * @return array |
||
253 | */ |
||
254 | public function getChildren($locationId) |
||
255 | { |
||
256 | $query = $this->handler->createSelectQuery(); |
||
257 | $query->select('*')->from( |
||
258 | $this->handler->quoteTable('ezcontentobject_tree') |
||
259 | )->where( |
||
260 | $query->expr->eq( |
||
261 | $this->handler->quoteColumn('parent_node_id', 'ezcontentobject_tree'), |
||
262 | $query->bindValue($locationId, null, \PDO::PARAM_INT) |
||
263 | ) |
||
264 | ); |
||
265 | $statement = $query->prepare(); |
||
266 | $statement->execute(); |
||
267 | |||
268 | return $statement->fetchAll(\PDO::FETCH_ASSOC); |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Update path strings to move nodes in the ezcontentobject_tree table. |
||
273 | * |
||
274 | * This query can likely be optimized to use some more advanced string |
||
275 | * operations, which then depend on the respective database. |
||
276 | * |
||
277 | * @todo optimize |
||
278 | * |
||
279 | * @param array $sourceNodeData |
||
280 | * @param array $destinationNodeData |
||
281 | */ |
||
282 | public function moveSubtreeNodes(array $sourceNodeData, array $destinationNodeData) |
||
283 | { |
||
284 | $fromPathString = $sourceNodeData['path_string']; |
||
285 | |||
286 | /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */ |
||
287 | $query = $this->handler->createSelectQuery(); |
||
288 | $query |
||
289 | ->select( |
||
290 | $this->handler->quoteColumn('node_id'), |
||
291 | $this->handler->quoteColumn('parent_node_id'), |
||
292 | $this->handler->quoteColumn('path_string'), |
||
293 | $this->handler->quoteColumn('path_identification_string') |
||
294 | ) |
||
295 | ->from($this->handler->quoteTable('ezcontentobject_tree')) |
||
296 | ->where( |
||
297 | $query->expr->like( |
||
298 | $this->handler->quoteColumn('path_string'), |
||
299 | $query->bindValue($fromPathString . '%') |
||
300 | ) |
||
301 | ); |
||
302 | $statement = $query->prepare(); |
||
303 | $statement->execute(); |
||
304 | |||
305 | $rows = $statement->fetchAll(); |
||
306 | $oldParentPathString = implode('/', array_slice(explode('/', $fromPathString), 0, -2)) . '/'; |
||
307 | $oldParentPathIdentificationString = implode( |
||
308 | '/', |
||
309 | array_slice(explode('/', $sourceNodeData['path_identification_string']), 0, -1) |
||
310 | ); |
||
311 | |||
312 | foreach ($rows as $row) { |
||
313 | // Prefixing ensures correct replacement when old parent is root node |
||
314 | $newPathString = str_replace( |
||
315 | 'prefix' . $oldParentPathString, |
||
316 | $destinationNodeData['path_string'], |
||
317 | 'prefix' . $row['path_string'] |
||
318 | ); |
||
319 | $newPathIdentificationString = str_replace( |
||
320 | 'prefix' . $oldParentPathIdentificationString, |
||
321 | $destinationNodeData['path_identification_string'] . '/', |
||
322 | 'prefix' . $row['path_identification_string'] |
||
323 | ); |
||
324 | |||
325 | $newParentId = $row['parent_node_id']; |
||
326 | if ($row['path_string'] === $fromPathString) { |
||
327 | $newParentId = (int)implode('', array_slice(explode('/', $newPathString), -3, 1)); |
||
328 | } |
||
329 | |||
330 | /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */ |
||
331 | $query = $this->handler->createUpdateQuery(); |
||
332 | $query |
||
333 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
334 | ->set( |
||
335 | $this->handler->quoteColumn('path_string'), |
||
336 | $query->bindValue($newPathString) |
||
337 | ) |
||
338 | ->set( |
||
339 | $this->handler->quoteColumn('path_identification_string'), |
||
340 | $query->bindValue($newPathIdentificationString) |
||
341 | ) |
||
342 | ->set( |
||
343 | $this->handler->quoteColumn('depth'), |
||
344 | $query->bindValue(substr_count($newPathString, '/') - 2) |
||
345 | ) |
||
346 | ->set( |
||
347 | $this->handler->quoteColumn('parent_node_id'), |
||
348 | $query->bindValue($newParentId) |
||
349 | ); |
||
350 | |||
351 | if ($destinationNodeData['is_hidden'] || $destinationNodeData['is_invisible']) { |
||
352 | // CASE 1: Mark whole tree as invisible if destination is invisible and/or hidden |
||
353 | $query->set( |
||
354 | $this->handler->quoteColumn('is_invisible'), |
||
355 | $query->bindValue(1) |
||
356 | ); |
||
357 | } elseif (!$sourceNodeData['is_hidden'] && $sourceNodeData['is_invisible']) { |
||
358 | // CASE 2: source is only invisible, we will need to re-calculate whole moved tree visibility |
||
359 | $query->set( |
||
360 | $this->handler->quoteColumn('is_invisible'), |
||
361 | $query->bindValue($this->isHiddenByParent($newPathString, $rows) ? 1 : 0) |
||
362 | ); |
||
363 | } else { |
||
364 | // CASE 3: keep invisible flags as is (source is either hidden or not hidden/invisible at all) |
||
365 | } |
||
366 | |||
367 | $query->where( |
||
368 | $query->expr->eq( |
||
369 | $this->handler->quoteColumn('node_id'), |
||
370 | $query->bindValue($row['node_id']) |
||
371 | ) |
||
372 | ); |
||
373 | $query->prepare()->execute(); |
||
374 | } |
||
375 | } |
||
376 | |||
377 | private function isHiddenByParent($pathString, array $rows) |
||
378 | { |
||
379 | $parentNodeIds = explode('/', trim($pathString, '/')); |
||
380 | array_pop($parentNodeIds); // remove self |
||
381 | foreach ($rows as $row) { |
||
382 | if ($row['is_hidden'] && in_array($row['node_id'], $parentNodeIds)) { |
||
383 | return true; |
||
384 | } |
||
385 | } |
||
386 | |||
387 | return false; |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Updated subtree modification time for all nodes on path. |
||
392 | * |
||
393 | * @param string $pathString |
||
394 | * @param int|null $timestamp |
||
395 | */ |
||
396 | public function updateSubtreeModificationTime($pathString, $timestamp = null) |
||
397 | { |
||
398 | $nodes = array_filter(explode('/', $pathString)); |
||
399 | $query = $this->handler->createUpdateQuery(); |
||
400 | $query |
||
401 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
402 | ->set( |
||
403 | $this->handler->quoteColumn('modified_subnode'), |
||
404 | $query->bindValue( |
||
405 | $timestamp ?: time() |
||
406 | ) |
||
407 | ) |
||
408 | ->where( |
||
409 | $query->expr->in( |
||
410 | $this->handler->quoteColumn('node_id'), |
||
411 | $nodes |
||
412 | ) |
||
413 | ); |
||
414 | $query->prepare()->execute(); |
||
415 | } |
||
416 | |||
417 | /** |
||
418 | * Sets a location to be hidden, and it self + all children to invisible. |
||
419 | * |
||
420 | * @param string $pathString |
||
421 | */ |
||
422 | public function hideSubtree($pathString) |
||
423 | { |
||
424 | $query = $this->handler->createUpdateQuery(); |
||
425 | $query |
||
426 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
427 | ->set( |
||
428 | $this->handler->quoteColumn('is_invisible'), |
||
429 | $query->bindValue(1) |
||
430 | ) |
||
431 | ->set( |
||
432 | $this->handler->quoteColumn('modified_subnode'), |
||
433 | $query->bindValue(time()) |
||
434 | ) |
||
435 | ->where( |
||
436 | $query->expr->like( |
||
437 | $this->handler->quoteColumn('path_string'), |
||
438 | $query->bindValue($pathString . '%') |
||
439 | ) |
||
440 | ); |
||
441 | $query->prepare()->execute(); |
||
442 | |||
443 | $query = $this->handler->createUpdateQuery(); |
||
444 | $query |
||
445 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
446 | ->set( |
||
447 | $this->handler->quoteColumn('is_hidden'), |
||
448 | $query->bindValue(1) |
||
449 | ) |
||
450 | ->where( |
||
451 | $query->expr->eq( |
||
452 | $this->handler->quoteColumn('path_string'), |
||
453 | $query->bindValue($pathString) |
||
454 | ) |
||
455 | ); |
||
456 | $query->prepare()->execute(); |
||
457 | } |
||
458 | |||
459 | /** |
||
460 | * Sets a location to be unhidden, and self + children to visible unless a parent is hiding the tree. |
||
461 | * If not make sure only children down to first hidden node is marked visible. |
||
462 | * |
||
463 | * @param string $pathString |
||
464 | */ |
||
465 | public function unHideSubtree($pathString) |
||
466 | { |
||
467 | // Unhide the requested node |
||
468 | $query = $this->handler->createUpdateQuery(); |
||
469 | $query |
||
470 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
471 | ->set( |
||
472 | $this->handler->quoteColumn('is_hidden'), |
||
473 | $query->bindValue(0) |
||
474 | ) |
||
475 | ->where( |
||
476 | $query->expr->eq( |
||
477 | $this->handler->quoteColumn('path_string'), |
||
478 | $query->bindValue($pathString) |
||
479 | ) |
||
480 | ); |
||
481 | $query->prepare()->execute(); |
||
482 | |||
483 | // Check if any parent nodes are explicitly hidden |
||
484 | $query = $this->handler->createSelectQuery(); |
||
485 | $query |
||
486 | ->select($this->handler->quoteColumn('path_string')) |
||
487 | ->from($this->handler->quoteTable('ezcontentobject_tree')) |
||
488 | ->where( |
||
489 | $query->expr->lAnd( |
||
490 | $query->expr->eq( |
||
491 | $this->handler->quoteColumn('is_hidden'), |
||
492 | $query->bindValue(1) |
||
493 | ), |
||
494 | $query->expr->in( |
||
495 | $this->handler->quoteColumn('node_id'), |
||
496 | array_filter(explode('/', $pathString)) |
||
497 | ) |
||
498 | ) |
||
499 | ); |
||
500 | $statement = $query->prepare(); |
||
501 | $statement->execute(); |
||
502 | if (count($statement->fetchAll(\PDO::FETCH_COLUMN))) { |
||
503 | // There are parent nodes set hidden, so that we can skip marking |
||
504 | // something visible again. |
||
505 | return; |
||
506 | } |
||
507 | |||
508 | // Find nodes of explicitly hidden subtrees in the subtree which |
||
509 | // should be unhidden |
||
510 | $query = $this->handler->createSelectQuery(); |
||
511 | $query |
||
512 | ->select($this->handler->quoteColumn('path_string')) |
||
513 | ->from($this->handler->quoteTable('ezcontentobject_tree')) |
||
514 | ->where( |
||
515 | $query->expr->lAnd( |
||
516 | $query->expr->eq( |
||
517 | $this->handler->quoteColumn('is_hidden'), |
||
518 | $query->bindValue(1) |
||
519 | ), |
||
520 | $query->expr->like( |
||
521 | $this->handler->quoteColumn('path_string'), |
||
522 | $query->bindValue($pathString . '%') |
||
523 | ) |
||
524 | ) |
||
525 | ); |
||
526 | $statement = $query->prepare(); |
||
527 | $statement->execute(); |
||
528 | $hiddenSubtrees = $statement->fetchAll(\PDO::FETCH_COLUMN); |
||
529 | |||
530 | $query = $this->handler->createUpdateQuery(); |
||
531 | $query |
||
532 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
533 | ->set( |
||
534 | $this->handler->quoteColumn('is_invisible'), |
||
535 | $query->bindValue(0) |
||
536 | ) |
||
537 | ->set( |
||
538 | $this->handler->quoteColumn('modified_subnode'), |
||
539 | $query->bindValue(time()) |
||
540 | ); |
||
541 | |||
542 | // Build where expression selecting the nodes, which should be made |
||
543 | // visible again |
||
544 | $where = $query->expr->like( |
||
545 | $this->handler->quoteColumn('path_string'), |
||
546 | $query->bindValue($pathString . '%') |
||
547 | ); |
||
548 | if (count($hiddenSubtrees)) { |
||
549 | $handler = $this->handler; |
||
550 | $where = $query->expr->lAnd( |
||
551 | $where, |
||
552 | $query->expr->lAnd( |
||
553 | array_map( |
||
554 | function ($pathString) use ($query, $handler) { |
||
555 | return $query->expr->not( |
||
556 | $query->expr->like( |
||
557 | $handler->quoteColumn('path_string'), |
||
558 | $query->bindValue($pathString . '%') |
||
559 | ) |
||
560 | ); |
||
561 | }, |
||
562 | $hiddenSubtrees |
||
563 | ) |
||
564 | ) |
||
565 | ); |
||
566 | } |
||
567 | $query->where($where); |
||
568 | $statement = $query->prepare()->execute(); |
||
569 | } |
||
570 | |||
571 | /** |
||
572 | * Swaps the content object being pointed to by a location object. |
||
573 | * |
||
574 | * Make the location identified by $locationId1 refer to the Content |
||
575 | * referred to by $locationId2 and vice versa. |
||
576 | * |
||
577 | * @param mixed $locationId1 |
||
578 | * @param mixed $locationId2 |
||
579 | * |
||
580 | * @return bool |
||
581 | */ |
||
582 | public function swap($locationId1, $locationId2) |
||
583 | { |
||
584 | $query = $this->handler->createSelectQuery(); |
||
585 | $query |
||
586 | ->select( |
||
587 | $this->handler->quoteColumn('node_id'), |
||
588 | $this->handler->quoteColumn('contentobject_id'), |
||
589 | $this->handler->quoteColumn('contentobject_version') |
||
590 | ) |
||
591 | ->from($this->handler->quoteTable('ezcontentobject_tree')) |
||
592 | ->where( |
||
593 | $query->expr->in( |
||
594 | $this->handler->quoteColumn('node_id'), |
||
595 | array($locationId1, $locationId2) |
||
596 | ) |
||
597 | ); |
||
598 | $statement = $query->prepare(); |
||
599 | $statement->execute(); |
||
600 | foreach ($statement->fetchAll() as $row) { |
||
601 | $contentObjects[$row['node_id']] = $row; |
||
602 | } |
||
603 | |||
604 | $query = $this->handler->createUpdateQuery(); |
||
605 | $query |
||
606 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
607 | ->set( |
||
608 | $this->handler->quoteColumn('contentobject_id'), |
||
609 | $query->bindValue($contentObjects[$locationId2]['contentobject_id']) |
||
610 | ) |
||
611 | ->set( |
||
612 | $this->handler->quoteColumn('contentobject_version'), |
||
613 | $query->bindValue($contentObjects[$locationId2]['contentobject_version']) |
||
614 | ) |
||
615 | ->where( |
||
616 | $query->expr->eq( |
||
617 | $this->handler->quoteColumn('node_id'), |
||
618 | $query->bindValue($locationId1) |
||
619 | ) |
||
620 | ); |
||
621 | $query->prepare()->execute(); |
||
622 | |||
623 | $query = $this->handler->createUpdateQuery(); |
||
624 | $query |
||
625 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
626 | ->set( |
||
627 | $this->handler->quoteColumn('contentobject_id'), |
||
628 | $query->bindValue($contentObjects[$locationId1]['contentobject_id']) |
||
629 | ) |
||
630 | ->set( |
||
631 | $this->handler->quoteColumn('contentobject_version'), |
||
632 | $query->bindValue($contentObjects[$locationId1]['contentobject_version']) |
||
633 | ) |
||
634 | ->where( |
||
635 | $query->expr->eq( |
||
636 | $this->handler->quoteColumn('node_id'), |
||
637 | $query->bindValue($locationId2) |
||
638 | ) |
||
639 | ); |
||
640 | $query->prepare()->execute(); |
||
641 | } |
||
642 | |||
643 | /** |
||
644 | * Creates a new location in given $parentNode. |
||
645 | * |
||
646 | * @param \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct $createStruct |
||
647 | * @param array $parentNode |
||
648 | * |
||
649 | * @return \eZ\Publish\SPI\Persistence\Content\Location |
||
650 | */ |
||
651 | public function create(CreateStruct $createStruct, array $parentNode) |
||
652 | { |
||
653 | $location = new Location(); |
||
654 | /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */ |
||
655 | $query = $this->handler->createInsertQuery(); |
||
656 | $query |
||
657 | ->insertInto($this->handler->quoteTable('ezcontentobject_tree')) |
||
658 | ->set( |
||
659 | $this->handler->quoteColumn('contentobject_id'), |
||
660 | $query->bindValue($location->contentId = $createStruct->contentId, null, \PDO::PARAM_INT) |
||
661 | )->set( |
||
662 | $this->handler->quoteColumn('contentobject_is_published'), |
||
663 | $query->bindValue(1, null, \PDO::PARAM_INT) |
||
664 | )->set( |
||
665 | $this->handler->quoteColumn('contentobject_version'), |
||
666 | $query->bindValue($createStruct->contentVersion, null, \PDO::PARAM_INT) |
||
667 | )->set( |
||
668 | $this->handler->quoteColumn('depth'), |
||
669 | $query->bindValue($location->depth = $parentNode['depth'] + 1, null, \PDO::PARAM_INT) |
||
670 | )->set( |
||
671 | $this->handler->quoteColumn('is_hidden'), |
||
672 | $query->bindValue($location->hidden = $createStruct->hidden, null, \PDO::PARAM_INT) |
||
673 | )->set( |
||
674 | $this->handler->quoteColumn('is_invisible'), |
||
675 | $query->bindValue($location->invisible = $createStruct->invisible, null, \PDO::PARAM_INT) |
||
676 | )->set( |
||
677 | $this->handler->quoteColumn('modified_subnode'), |
||
678 | $query->bindValue(time(), null, \PDO::PARAM_INT) |
||
679 | )->set( |
||
680 | $this->handler->quoteColumn('node_id'), |
||
681 | $this->handler->getAutoIncrementValue('ezcontentobject_tree', 'node_id') |
||
682 | )->set( |
||
683 | $this->handler->quoteColumn('parent_node_id'), |
||
684 | $query->bindValue($location->parentId = $parentNode['node_id'], null, \PDO::PARAM_INT) |
||
685 | )->set( |
||
686 | $this->handler->quoteColumn('path_identification_string'), |
||
687 | $query->bindValue($location->pathIdentificationString = $createStruct->pathIdentificationString, null, \PDO::PARAM_STR) |
||
688 | )->set( |
||
689 | $this->handler->quoteColumn('path_string'), |
||
690 | $query->bindValue('dummy') // Set later |
||
691 | )->set( |
||
692 | $this->handler->quoteColumn('priority'), |
||
693 | $query->bindValue($location->priority = $createStruct->priority, null, \PDO::PARAM_INT) |
||
694 | )->set( |
||
695 | $this->handler->quoteColumn('remote_id'), |
||
696 | $query->bindValue($location->remoteId = $createStruct->remoteId, null, \PDO::PARAM_STR) |
||
697 | )->set( |
||
698 | $this->handler->quoteColumn('sort_field'), |
||
699 | $query->bindValue($location->sortField = $createStruct->sortField, null, \PDO::PARAM_INT) |
||
700 | )->set( |
||
701 | $this->handler->quoteColumn('sort_order'), |
||
702 | $query->bindValue($location->sortOrder = $createStruct->sortOrder, null, \PDO::PARAM_INT) |
||
703 | ); |
||
704 | $query->prepare()->execute(); |
||
705 | |||
706 | $location->id = $this->handler->lastInsertId($this->handler->getSequenceName('ezcontentobject_tree', 'node_id')); |
||
707 | |||
708 | $mainLocationId = $createStruct->mainLocationId === true ? $location->id : $createStruct->mainLocationId; |
||
709 | $location->pathString = $parentNode['path_string'] . $location->id . '/'; |
||
710 | /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */ |
||
711 | $query = $this->handler->createUpdateQuery(); |
||
712 | $query |
||
713 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
714 | ->set( |
||
715 | $this->handler->quoteColumn('path_string'), |
||
716 | $query->bindValue($location->pathString) |
||
717 | ) |
||
718 | ->set( |
||
719 | $this->handler->quoteColumn('main_node_id'), |
||
720 | $query->bindValue($mainLocationId, null, \PDO::PARAM_INT) |
||
721 | ) |
||
722 | ->where( |
||
723 | $query->expr->eq( |
||
724 | $this->handler->quoteColumn('node_id'), |
||
725 | $query->bindValue($location->id, null, \PDO::PARAM_INT) |
||
726 | ) |
||
727 | ); |
||
728 | $query->prepare()->execute(); |
||
729 | |||
730 | return $location; |
||
731 | } |
||
732 | |||
733 | /** |
||
734 | * Create an entry in the node assignment table. |
||
735 | * |
||
736 | * @param \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct $createStruct |
||
737 | * @param mixed $parentNodeId |
||
738 | * @param int $type |
||
739 | */ |
||
740 | public function createNodeAssignment(CreateStruct $createStruct, $parentNodeId, $type = self::NODE_ASSIGNMENT_OP_CODE_CREATE_NOP) |
||
741 | { |
||
742 | $isMain = ($createStruct->mainLocationId === true ? 1 : 0); |
||
743 | |||
744 | $query = $this->handler->createInsertQuery(); |
||
745 | $query |
||
746 | ->insertInto($this->handler->quoteTable('eznode_assignment')) |
||
747 | ->set( |
||
748 | $this->handler->quoteColumn('contentobject_id'), |
||
749 | $query->bindValue($createStruct->contentId, null, \PDO::PARAM_INT) |
||
750 | )->set( |
||
751 | $this->handler->quoteColumn('contentobject_version'), |
||
752 | $query->bindValue($createStruct->contentVersion, null, \PDO::PARAM_INT) |
||
753 | )->set( |
||
754 | $this->handler->quoteColumn('from_node_id'), |
||
755 | $query->bindValue(0, null, \PDO::PARAM_INT) // unused field |
||
756 | )->set( |
||
757 | $this->handler->quoteColumn('id'), |
||
758 | $this->handler->getAutoIncrementValue('eznode_assignment', 'id') |
||
759 | )->set( |
||
760 | $this->handler->quoteColumn('is_main'), |
||
761 | $query->bindValue($isMain, null, \PDO::PARAM_INT) // Changed by the business layer, later |
||
762 | )->set( |
||
763 | $this->handler->quoteColumn('op_code'), |
||
764 | $query->bindValue($type, null, \PDO::PARAM_INT) |
||
765 | )->set( |
||
766 | $this->handler->quoteColumn('parent_node'), |
||
767 | $query->bindValue($parentNodeId, null, \PDO::PARAM_INT) |
||
768 | )->set( |
||
769 | // parent_remote_id column should contain the remote id of the corresponding Location |
||
770 | $this->handler->quoteColumn('parent_remote_id'), |
||
771 | $query->bindValue($createStruct->remoteId, null, \PDO::PARAM_STR) |
||
772 | )->set( |
||
773 | // remote_id column should contain the remote id of the node assignment itself, |
||
774 | // however this was never implemented completely in Legacy Stack, so we just set |
||
775 | // it to default value '0' |
||
776 | $this->handler->quoteColumn('remote_id'), |
||
777 | $query->bindValue('0', null, \PDO::PARAM_STR) |
||
778 | )->set( |
||
779 | $this->handler->quoteColumn('sort_field'), |
||
780 | $query->bindValue($createStruct->sortField, null, \PDO::PARAM_INT) |
||
781 | )->set( |
||
782 | $this->handler->quoteColumn('sort_order'), |
||
783 | $query->bindValue($createStruct->sortOrder, null, \PDO::PARAM_INT) |
||
784 | )->set( |
||
785 | $this->handler->quoteColumn('priority'), |
||
786 | $query->bindValue($createStruct->priority, null, \PDO::PARAM_INT) |
||
787 | )->set( |
||
788 | $this->handler->quoteColumn('is_hidden'), |
||
789 | $query->bindValue($createStruct->hidden, null, \PDO::PARAM_INT) |
||
790 | ); |
||
791 | $query->prepare()->execute(); |
||
792 | } |
||
793 | |||
794 | /** |
||
795 | * Deletes node assignment for given $contentId and $versionNo. |
||
796 | * |
||
797 | * If $versionNo is not passed all node assignments for given $contentId are deleted |
||
798 | * |
||
799 | * @param int $contentId |
||
800 | * @param int|null $versionNo |
||
801 | */ |
||
802 | public function deleteNodeAssignment($contentId, $versionNo = null) |
||
803 | { |
||
804 | $query = $this->handler->createDeleteQuery(); |
||
805 | $query->deleteFrom( |
||
806 | 'eznode_assignment' |
||
807 | )->where( |
||
808 | $query->expr->eq( |
||
809 | $this->handler->quoteColumn('contentobject_id'), |
||
810 | $query->bindValue($contentId, null, \PDO::PARAM_INT) |
||
811 | ) |
||
812 | ); |
||
813 | if (isset($versionNo)) { |
||
814 | $query->where( |
||
815 | $query->expr->eq( |
||
816 | $this->handler->quoteColumn('contentobject_version'), |
||
817 | $query->bindValue($versionNo, null, \PDO::PARAM_INT) |
||
818 | ) |
||
819 | ); |
||
820 | } |
||
821 | $query->prepare()->execute(); |
||
822 | } |
||
823 | |||
824 | /** |
||
825 | * Update node assignment table. |
||
826 | * |
||
827 | * @param int $contentObjectId |
||
828 | * @param int $oldParent |
||
829 | * @param int $newParent |
||
830 | * @param int $opcode |
||
831 | */ |
||
832 | public function updateNodeAssignment($contentObjectId, $oldParent, $newParent, $opcode) |
||
833 | { |
||
834 | $query = $this->handler->createUpdateQuery(); |
||
835 | $query |
||
836 | ->update($this->handler->quoteTable('eznode_assignment')) |
||
837 | ->set( |
||
838 | $this->handler->quoteColumn('parent_node'), |
||
839 | $query->bindValue($newParent, null, \PDO::PARAM_INT) |
||
840 | ) |
||
841 | ->set( |
||
842 | $this->handler->quoteColumn('op_code'), |
||
843 | $query->bindValue($opcode, null, \PDO::PARAM_INT) |
||
844 | ) |
||
845 | ->where( |
||
846 | $query->expr->lAnd( |
||
847 | $query->expr->eq( |
||
848 | $this->handler->quoteColumn('contentobject_id'), |
||
849 | $query->bindValue($contentObjectId, null, \PDO::PARAM_INT) |
||
850 | ), |
||
851 | $query->expr->eq( |
||
852 | $this->handler->quoteColumn('parent_node'), |
||
853 | $query->bindValue($oldParent, null, \PDO::PARAM_INT) |
||
854 | ) |
||
855 | ) |
||
856 | ); |
||
857 | $query->prepare()->execute(); |
||
858 | } |
||
859 | |||
860 | /** |
||
861 | * Create locations from node assignments. |
||
862 | * |
||
863 | * Convert existing node assignments into real locations. |
||
864 | * |
||
865 | * @param mixed $contentId |
||
866 | * @param mixed $versionNo |
||
867 | */ |
||
868 | public function createLocationsFromNodeAssignments($contentId, $versionNo) |
||
869 | { |
||
870 | // select all node assignments with OP_CODE_CREATE (3) for this content |
||
871 | $query = $this->handler->createSelectQuery(); |
||
872 | $query |
||
873 | ->select('*') |
||
874 | ->from($this->handler->quoteTable('eznode_assignment')) |
||
875 | ->where( |
||
876 | $query->expr->lAnd( |
||
877 | $query->expr->eq( |
||
878 | $this->handler->quoteColumn('contentobject_id'), |
||
879 | $query->bindValue($contentId, null, \PDO::PARAM_INT) |
||
880 | ), |
||
881 | $query->expr->eq( |
||
882 | $this->handler->quoteColumn('contentobject_version'), |
||
883 | $query->bindValue($versionNo, null, \PDO::PARAM_INT) |
||
884 | ), |
||
885 | $query->expr->eq( |
||
886 | $this->handler->quoteColumn('op_code'), |
||
887 | $query->bindValue(self::NODE_ASSIGNMENT_OP_CODE_CREATE, null, \PDO::PARAM_INT) |
||
888 | ) |
||
889 | ) |
||
890 | )->orderBy('id'); |
||
891 | $statement = $query->prepare(); |
||
892 | $statement->execute(); |
||
893 | |||
894 | // convert all these assignments to nodes |
||
895 | |||
896 | while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) { |
||
897 | if ((bool)$row['is_main'] === true) { |
||
898 | $mainLocationId = true; |
||
899 | } else { |
||
900 | $mainLocationId = $this->getMainNodeId($contentId); |
||
901 | } |
||
902 | |||
903 | $parentLocationData = $this->getBasicNodeData($row['parent_node']); |
||
904 | $isInvisible = $row['is_hidden'] || $parentLocationData['is_hidden'] || $parentLocationData['is_invisible']; |
||
905 | $this->create( |
||
906 | new CreateStruct( |
||
907 | array( |
||
908 | 'contentId' => $row['contentobject_id'], |
||
909 | 'contentVersion' => $row['contentobject_version'], |
||
910 | 'mainLocationId' => $mainLocationId, |
||
911 | 'remoteId' => $row['parent_remote_id'], |
||
912 | 'sortField' => $row['sort_field'], |
||
913 | 'sortOrder' => $row['sort_order'], |
||
914 | 'priority' => $row['priority'], |
||
915 | 'hidden' => $row['is_hidden'], |
||
916 | 'invisible' => $isInvisible, |
||
917 | ) |
||
918 | ), |
||
919 | $parentLocationData |
||
920 | ); |
||
921 | |||
922 | $this->updateNodeAssignment( |
||
923 | $row['contentobject_id'], |
||
924 | $row['parent_node'], |
||
925 | $row['parent_node'], |
||
926 | self::NODE_ASSIGNMENT_OP_CODE_CREATE_NOP |
||
927 | ); |
||
928 | } |
||
929 | } |
||
930 | |||
931 | /** |
||
932 | * Updates all Locations of content identified with $contentId with $versionNo. |
||
933 | * |
||
934 | * @param mixed $contentId |
||
935 | * @param mixed $versionNo |
||
936 | */ |
||
937 | View Code Duplication | public function updateLocationsContentVersionNo($contentId, $versionNo) |
|
938 | { |
||
939 | $query = $this->handler->createUpdateQuery(); |
||
940 | $query->update( |
||
941 | $this->handler->quoteTable('ezcontentobject_tree') |
||
942 | )->set( |
||
943 | $this->handler->quoteColumn('contentobject_version'), |
||
944 | $query->bindValue($versionNo, null, \PDO::PARAM_INT) |
||
945 | )->where( |
||
946 | $query->expr->eq( |
||
947 | $this->handler->quoteColumn('contentobject_id'), |
||
948 | $contentId |
||
949 | ) |
||
950 | ); |
||
951 | $query->prepare()->execute(); |
||
952 | } |
||
953 | |||
954 | /** |
||
955 | * Searches for the main nodeId of $contentId in $versionId. |
||
956 | * |
||
957 | * @param int $contentId |
||
958 | * |
||
959 | * @return int|bool |
||
960 | */ |
||
961 | private function getMainNodeId($contentId) |
||
962 | { |
||
963 | $query = $this->handler->createSelectQuery(); |
||
964 | $query |
||
965 | ->select('node_id') |
||
966 | ->from($this->handler->quoteTable('ezcontentobject_tree')) |
||
967 | ->where( |
||
968 | $query->expr->lAnd( |
||
969 | $query->expr->eq( |
||
970 | $this->handler->quoteColumn('contentobject_id'), |
||
971 | $query->bindValue($contentId, null, \PDO::PARAM_INT) |
||
972 | ), |
||
973 | $query->expr->eq( |
||
974 | $this->handler->quoteColumn('node_id'), |
||
975 | $this->handler->quoteColumn('main_node_id') |
||
976 | ) |
||
977 | ) |
||
978 | ); |
||
979 | $statement = $query->prepare(); |
||
980 | $statement->execute(); |
||
981 | |||
982 | $result = $statement->fetchAll(\PDO::FETCH_ASSOC); |
||
983 | if (count($result) === 1) { |
||
984 | return (int)$result[0]['node_id']; |
||
985 | } else { |
||
986 | return false; |
||
987 | } |
||
988 | } |
||
989 | |||
990 | /** |
||
991 | * Updates an existing location. |
||
992 | * |
||
993 | * Will not throw anything if location id is invalid or no entries are affected. |
||
994 | * |
||
995 | * @param \eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct $location |
||
996 | * @param int $locationId |
||
997 | */ |
||
998 | public function update(UpdateStruct $location, $locationId) |
||
999 | { |
||
1000 | $query = $this->handler->createUpdateQuery(); |
||
1001 | |||
1002 | $query |
||
1003 | ->update($this->handler->quoteTable('ezcontentobject_tree')) |
||
1004 | ->set( |
||
1005 | $this->handler->quoteColumn('priority'), |
||
1006 | $query->bindValue($location->priority) |
||
1007 | ) |
||
1008 | ->set( |
||
1009 | $this->handler->quoteColumn('remote_id'), |
||
1010 | $query->bindValue($location->remoteId) |
||
1011 | ) |
||
1012 | ->set( |
||
1013 | $this->handler->quoteColumn('sort_order'), |
||
1014 | $query->bindValue($location->sortOrder) |
||
1015 | ) |
||
1016 | ->set( |
||
1017 | $this->handler->quoteColumn('sort_field'), |
||
1018 | $query->bindValue($location->sortField) |
||
1019 | ) |
||
1020 | ->where( |
||
1021 | $query->expr->eq( |
||
1022 | $this->handler->quoteColumn('node_id'), |
||
1023 | $locationId |
||
1024 | ) |
||
1025 | ); |
||
1026 | $statement = $query->prepare(); |
||
1027 | $statement->execute(); |
||
1028 | |||
1029 | // Commented due to EZP-23302: Update Location fails if no change is performed with the update |
||
1030 | // Should be fixed with PDO::MYSQL_ATTR_FOUND_ROWS instead |
||
1031 | /*if ( $statement->rowCount() < 1 ) |
||
1032 | { |
||
1033 | throw new NotFound( 'location', $locationId ); |
||
1034 | }*/ |
||
1035 | } |
||
1036 | |||
1037 | /** |
||
1038 | * Updates path identification string for given $locationId. |
||
1039 | * |
||
1040 | * @param mixed $locationId |
||
1041 | * @param mixed $parentLocationId |
||
1042 | * @param string $text |
||
1043 | */ |
||
1044 | public function updatePathIdentificationString($locationId, $parentLocationId, $text) |
||
1045 | { |
||
1046 | $parentData = $this->getBasicNodeData($parentLocationId); |
||
1047 | |||
1048 | $newPathIdentificationString = empty($parentData['path_identification_string']) ? |
||
1049 | $text : |
||
1050 | $parentData['path_identification_string'] . '/' . $text; |
||
1051 | |||
1052 | /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */ |
||
1053 | $query = $this->handler->createUpdateQuery(); |
||
1054 | $query->update( |
||
1055 | 'ezcontentobject_tree' |
||
1056 | )->set( |
||
1057 | $this->handler->quoteColumn('path_identification_string'), |
||
1058 | $query->bindValue($newPathIdentificationString, null, \PDO::PARAM_STR) |
||
1059 | )->where( |
||
1060 | $query->expr->eq( |
||
1061 | $this->handler->quoteColumn('node_id'), |
||
1062 | $query->bindValue($locationId, null, \PDO::PARAM_INT) |
||
1063 | ) |
||
1064 | ); |
||
1065 | $query->prepare()->execute(); |
||
1066 | } |
||
1067 | |||
1068 | /** |
||
1069 | * Deletes ezcontentobject_tree row for given $locationId (node_id). |
||
1070 | * |
||
1071 | * @param mixed $locationId |
||
1072 | */ |
||
1073 | public function removeLocation($locationId) |
||
1074 | { |
||
1075 | $query = $this->handler->createDeleteQuery(); |
||
1076 | $query->deleteFrom( |
||
1077 | 'ezcontentobject_tree' |
||
1078 | )->where( |
||
1079 | $query->expr->eq( |
||
1080 | $this->handler->quoteColumn('node_id'), |
||
1081 | $query->bindValue($locationId, null, \PDO::PARAM_INT) |
||
1082 | ) |
||
1083 | ); |
||
1084 | $query->prepare()->execute(); |
||
1085 | } |
||
1086 | |||
1087 | /** |
||
1088 | * Returns id of the next in line node to be set as a new main node. |
||
1089 | * |
||
1090 | * This returns lowest node id for content identified by $contentId, and not of |
||
1091 | * the node identified by given $locationId (current main node). |
||
1092 | * Assumes that content has more than one location. |
||
1093 | * |
||
1094 | * @param mixed $contentId |
||
1095 | * @param mixed $locationId |
||
1096 | * |
||
1097 | * @return array |
||
1098 | */ |
||
1099 | public function getFallbackMainNodeData($contentId, $locationId) |
||
1100 | { |
||
1101 | $query = $this->handler->createSelectQuery(); |
||
1102 | $query->select( |
||
1103 | $this->handler->quoteColumn('node_id'), |
||
1104 | $this->handler->quoteColumn('contentobject_version'), |
||
1105 | $this->handler->quoteColumn('parent_node_id') |
||
1106 | )->from( |
||
1107 | $this->handler->quoteTable('ezcontentobject_tree') |
||
1108 | )->where( |
||
1109 | $query->expr->lAnd( |
||
1110 | $query->expr->eq( |
||
1111 | $this->handler->quoteColumn('contentobject_id'), |
||
1112 | $query->bindValue($contentId, null, \PDO::PARAM_INT) |
||
1113 | ), |
||
1114 | $query->expr->neq( |
||
1115 | $this->handler->quoteColumn('node_id'), |
||
1116 | $query->bindValue($locationId, null, \PDO::PARAM_INT) |
||
1117 | ) |
||
1118 | ) |
||
1119 | )->orderBy('node_id', SelectQuery::ASC)->limit(1); |
||
1120 | $statement = $query->prepare(); |
||
1121 | $statement->execute(); |
||
1122 | |||
1123 | return $statement->fetch(\PDO::FETCH_ASSOC); |
||
1124 | } |
||
1125 | |||
1126 | /** |
||
1127 | * Sends a single location identified by given $locationId to the trash. |
||
1128 | * |
||
1129 | * The associated content object is left untouched. |
||
1130 | * |
||
1131 | * @param mixed $locationId |
||
1132 | * |
||
1133 | * @return bool |
||
1134 | */ |
||
1135 | public function trashLocation($locationId) |
||
1136 | { |
||
1137 | $locationRow = $this->getBasicNodeData($locationId); |
||
1138 | |||
1139 | /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */ |
||
1140 | $query = $this->handler->createInsertQuery(); |
||
1141 | $query->insertInto($this->handler->quoteTable('ezcontentobject_trash')); |
||
1142 | |||
1143 | unset($locationRow['contentobject_is_published']); |
||
1144 | foreach ($locationRow as $key => $value) { |
||
1145 | $query->set($key, $query->bindValue($value)); |
||
1146 | } |
||
1147 | |||
1148 | $query->prepare()->execute(); |
||
1149 | |||
1150 | $this->removeLocation($locationRow['node_id']); |
||
1151 | $this->setContentStatus($locationRow['contentobject_id'], ContentInfo::STATUS_TRASHED); |
||
1152 | } |
||
1153 | |||
1154 | /** |
||
1155 | * Returns a trashed location to normal state. |
||
1156 | * |
||
1157 | * Recreates the originally trashed location in the new position. If no new |
||
1158 | * position has been specified, it will be tried to re-create the location |
||
1159 | * at the old position. If this is not possible ( because the old location |
||
1160 | * does not exist any more) and exception is thrown. |
||
1161 | * |
||
1162 | * @param mixed $locationId |
||
1163 | * @param mixed|null $newParentId |
||
1164 | * |
||
1165 | * @return \eZ\Publish\SPI\Persistence\Content\Location |
||
1166 | */ |
||
1167 | public function untrashLocation($locationId, $newParentId = null) |
||
1168 | { |
||
1169 | $row = $this->loadTrashByLocation($locationId); |
||
1170 | |||
1171 | $newLocation = $this->create( |
||
1172 | new CreateStruct( |
||
1173 | array( |
||
1174 | 'priority' => $row['priority'], |
||
1175 | 'hidden' => $row['is_hidden'], |
||
1176 | 'invisible' => $row['is_invisible'], |
||
1177 | 'remoteId' => $row['remote_id'], |
||
1178 | 'contentId' => $row['contentobject_id'], |
||
1179 | 'contentVersion' => $row['contentobject_version'], |
||
1180 | 'mainLocationId' => true, // Restored location is always main location |
||
1181 | 'sortField' => $row['sort_field'], |
||
1182 | 'sortOrder' => $row['sort_order'], |
||
1183 | ) |
||
1184 | ), |
||
1185 | $this->getBasicNodeData($newParentId ?: $row['parent_node_id']) |
||
1186 | ); |
||
1187 | |||
1188 | $this->removeElementFromTrash($locationId); |
||
1189 | $this->setContentStatus($row['contentobject_id'], ContentInfo::STATUS_PUBLISHED); |
||
1190 | |||
1191 | return $newLocation; |
||
1192 | } |
||
1193 | |||
1194 | /** |
||
1195 | * @param mixed $contentId |
||
1196 | * @param int $status |
||
1197 | */ |
||
1198 | protected function setContentStatus($contentId, $status) |
||
1199 | { |
||
1200 | /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */ |
||
1201 | $query = $this->handler->createUpdateQuery(); |
||
1202 | $query->update( |
||
1203 | 'ezcontentobject' |
||
1204 | )->set( |
||
1205 | $this->handler->quoteColumn('status'), |
||
1206 | $query->bindValue($status, null, \PDO::PARAM_INT) |
||
1207 | )->where( |
||
1208 | $query->expr->eq( |
||
1209 | $this->handler->quoteColumn('id'), |
||
1210 | $query->bindValue($contentId, null, \PDO::PARAM_INT) |
||
1211 | ) |
||
1212 | ); |
||
1213 | $query->prepare()->execute(); |
||
1214 | } |
||
1215 | |||
1216 | /** |
||
1217 | * Loads trash data specified by location ID. |
||
1218 | * |
||
1219 | * @param mixed $locationId |
||
1220 | * |
||
1221 | * @return array |
||
1222 | */ |
||
1223 | View Code Duplication | public function loadTrashByLocation($locationId) |
|
1224 | { |
||
1225 | $query = $this->handler->createSelectQuery(); |
||
1226 | $query |
||
1227 | ->select('*') |
||
1228 | ->from($this->handler->quoteTable('ezcontentobject_trash')) |
||
1229 | ->where( |
||
1230 | $query->expr->eq( |
||
1231 | $this->handler->quoteColumn('node_id'), |
||
1232 | $query->bindValue($locationId) |
||
1233 | ) |
||
1234 | ); |
||
1235 | $statement = $query->prepare(); |
||
1236 | $statement->execute(); |
||
1237 | |||
1238 | if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) { |
||
1239 | return $row; |
||
1240 | } |
||
1241 | |||
1242 | throw new NotFound('trash', $locationId); |
||
1243 | } |
||
1244 | |||
1245 | /** |
||
1246 | * List trashed items. |
||
1247 | * |
||
1248 | * @param int $offset |
||
1249 | * @param int $limit |
||
1250 | * @param array $sort |
||
1251 | * |
||
1252 | * @return array |
||
1253 | */ |
||
1254 | public function listTrashed($offset, $limit, array $sort = null) |
||
1255 | { |
||
1256 | $query = $this->handler->createSelectQuery(); |
||
1257 | $query |
||
1258 | ->select('*') |
||
1259 | ->from($this->handler->quoteTable('ezcontentobject_trash')); |
||
1260 | |||
1261 | $sort = $sort ?: array(); |
||
1262 | foreach ($sort as $condition) { |
||
1263 | $sortDirection = $condition->direction === Query::SORT_ASC ? SelectQuery::ASC : SelectQuery::DESC; |
||
1264 | switch (true) { |
||
1265 | case $condition instanceof SortClause\Location\Depth: |
||
1266 | $query->orderBy('depth', $sortDirection); |
||
1267 | break; |
||
1268 | |||
1269 | case $condition instanceof SortClause\Location\Path: |
||
1270 | $query->orderBy('path_string', $sortDirection); |
||
1271 | break; |
||
1272 | |||
1273 | case $condition instanceof SortClause\Location\Priority: |
||
1274 | $query->orderBy('priority', $sortDirection); |
||
1275 | break; |
||
1276 | |||
1277 | default: |
||
1278 | // Only handle location related sort clauses. The others |
||
1279 | // require data aggregation which is not sensible here. |
||
1280 | // Since also criteria are yet ignored, because they are |
||
1281 | // simply not used yet in eZ Publish, we skip that for now. |
||
1282 | throw new RuntimeException('Unhandled sort clause: ' . get_class($condition)); |
||
1283 | } |
||
1284 | } |
||
1285 | |||
1286 | if ($limit !== null) { |
||
1287 | $query->limit($limit, $offset); |
||
1288 | } |
||
1289 | |||
1290 | $statement = $query->prepare(); |
||
1291 | $statement->execute(); |
||
1292 | |||
1293 | $rows = array(); |
||
1294 | while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) { |
||
1295 | $rows[] = $row; |
||
1296 | } |
||
1297 | |||
1298 | return $rows; |
||
1299 | } |
||
1300 | |||
1301 | /** |
||
1302 | * Removes every entries in the trash. |
||
1303 | * Will NOT remove associated content objects nor attributes. |
||
1304 | * |
||
1305 | * Basically truncates ezcontentobject_trash table. |
||
1306 | */ |
||
1307 | public function cleanupTrash() |
||
1308 | { |
||
1309 | $query = $this->handler->createDeleteQuery(); |
||
1310 | $query->deleteFrom('ezcontentobject_trash'); |
||
1311 | $query->prepare()->execute(); |
||
1312 | } |
||
1313 | |||
1314 | /** |
||
1315 | * Removes trashed element identified by $id from trash. |
||
1316 | * Will NOT remove associated content object nor attributes. |
||
1317 | * |
||
1318 | * @param int $id The trashed location Id |
||
1319 | */ |
||
1320 | public function removeElementFromTrash($id) |
||
1321 | { |
||
1322 | $query = $this->handler->createDeleteQuery(); |
||
1323 | $query |
||
1324 | ->deleteFrom('ezcontentobject_trash') |
||
1325 | ->where( |
||
1326 | $query->expr->eq( |
||
1327 | $this->handler->quoteColumn('node_id'), |
||
1328 | $query->bindValue($id, null, \PDO::PARAM_INT) |
||
1329 | ) |
||
1330 | ); |
||
1331 | $query->prepare()->execute(); |
||
1332 | } |
||
1333 | |||
1334 | /** |
||
1335 | * Set section on all content objects in the subtree. |
||
1336 | * |
||
1337 | * @param string $pathString |
||
1338 | * @param int $sectionId |
||
1339 | * |
||
1340 | * @return bool |
||
1341 | */ |
||
1342 | public function setSectionForSubtree($pathString, $sectionId) |
||
1343 | { |
||
1344 | $selectContentIdsQuery = $this->connection->createQueryBuilder(); |
||
1345 | $selectContentIdsQuery |
||
1346 | ->select('t.contentobject_id') |
||
1347 | ->from('ezcontentobject_tree', 't') |
||
1348 | ->where( |
||
1349 | $selectContentIdsQuery->expr()->like( |
||
1350 | 't.path_string', |
||
1351 | $selectContentIdsQuery->createPositionalParameter("{$pathString}%") |
||
1352 | ) |
||
1353 | ); |
||
1354 | |||
1355 | $contentIds = array_map( |
||
1356 | 'intval', |
||
1357 | $selectContentIdsQuery->execute()->fetchAll(PDO::FETCH_COLUMN) |
||
1358 | ); |
||
1359 | |||
1360 | if (empty($contentIds)) { |
||
1361 | return false; |
||
1362 | } |
||
1363 | |||
1364 | $updateSectionQuery = $this->connection->createQueryBuilder(); |
||
1365 | $updateSectionQuery |
||
1366 | ->update('ezcontentobject') |
||
1367 | ->set( |
||
1368 | 'section_id', |
||
1369 | $updateSectionQuery->createPositionalParameter($sectionId, PDO::PARAM_INT) |
||
1370 | ) |
||
1371 | ->where( |
||
1372 | $updateSectionQuery->expr()->in( |
||
1373 | 'id', |
||
1374 | $contentIds |
||
1375 | ) |
||
1376 | ); |
||
1377 | $affectedRows = $updateSectionQuery->execute(); |
||
1378 | |||
1379 | return $affectedRows > 0; |
||
1380 | } |
||
1381 | |||
1382 | /** |
||
1383 | * Returns how many locations given content object identified by $contentId has. |
||
1384 | * |
||
1385 | * @param int $contentId |
||
1386 | * |
||
1387 | * @return int |
||
1388 | */ |
||
1389 | public function countLocationsByContentId($contentId) |
||
1390 | { |
||
1391 | $q = $this->handler->createSelectQuery(); |
||
1392 | $q |
||
1393 | ->select( |
||
1394 | $q->alias($q->expr->count('*'), 'count') |
||
1395 | ) |
||
1396 | ->from($this->handler->quoteTable('ezcontentobject_tree')) |
||
1397 | ->where( |
||
1398 | $q->expr->eq( |
||
1399 | $this->handler->quoteColumn('contentobject_id'), |
||
1400 | $q->bindValue($contentId, null, \PDO::PARAM_INT) |
||
1401 | ) |
||
1402 | ); |
||
1403 | $stmt = $q->prepare(); |
||
1404 | $stmt->execute(); |
||
1405 | $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); |
||
1406 | |||
1407 | return (int)$res[0]['count']; |
||
1408 | } |
||
1409 | |||
1410 | /** |
||
1411 | * Changes main location of content identified by given $contentId to location identified by given $locationId. |
||
1412 | * |
||
1413 | * Updates ezcontentobject_tree table for the given $contentId and eznode_assignment table for the given |
||
1414 | * $contentId, $parentLocationId and $versionNo |
||
1415 | * |
||
1416 | * @param mixed $contentId |
||
1417 | * @param mixed $locationId |
||
1418 | * @param mixed $versionNo version number, needed to update eznode_assignment table |
||
1419 | * @param mixed $parentLocationId parent location of location identified by $locationId, needed to update |
||
1420 | * eznode_assignment table |
||
1421 | */ |
||
1422 | public function changeMainLocation($contentId, $locationId, $versionNo, $parentLocationId) |
||
1423 | { |
||
1424 | // Update ezcontentobject_tree table |
||
1425 | $q = $this->handler->createUpdateQuery(); |
||
1426 | $q->update( |
||
1427 | $this->handler->quoteTable('ezcontentobject_tree') |
||
1428 | )->set( |
||
1429 | $this->handler->quoteColumn('main_node_id'), |
||
1430 | $q->bindValue($locationId, null, \PDO::PARAM_INT) |
||
1431 | )->where( |
||
1432 | $q->expr->eq( |
||
1433 | $this->handler->quoteColumn('contentobject_id'), |
||
1434 | $q->bindValue($contentId, null, \PDO::PARAM_INT) |
||
1435 | ) |
||
1436 | ); |
||
1437 | $q->prepare()->execute(); |
||
1438 | |||
1439 | // Erase is_main in eznode_assignment table |
||
1440 | $q = $this->handler->createUpdateQuery(); |
||
1441 | $q->update( |
||
1442 | $this->handler->quoteTable('eznode_assignment') |
||
1443 | )->set( |
||
1444 | $this->handler->quoteColumn('is_main'), |
||
1445 | $q->bindValue(0, null, \PDO::PARAM_INT) |
||
1446 | )->where( |
||
1447 | $q->expr->lAnd( |
||
1448 | $q->expr->eq( |
||
1449 | $this->handler->quoteColumn('contentobject_id'), |
||
1450 | $q->bindValue($contentId, null, \PDO::PARAM_INT) |
||
1451 | ), |
||
1452 | $q->expr->eq( |
||
1453 | $this->handler->quoteColumn('contentobject_version'), |
||
1454 | $q->bindValue($versionNo, null, \PDO::PARAM_INT) |
||
1455 | ), |
||
1456 | $q->expr->neq( |
||
1457 | $this->handler->quoteColumn('parent_node'), |
||
1458 | $q->bindValue($parentLocationId, null, \PDO::PARAM_INT) |
||
1459 | ) |
||
1460 | ) |
||
1461 | ); |
||
1462 | $q->prepare()->execute(); |
||
1463 | |||
1464 | // Set new is_main in eznode_assignment table |
||
1465 | $q = $this->handler->createUpdateQuery(); |
||
1466 | $q->update( |
||
1467 | $this->handler->quoteTable('eznode_assignment') |
||
1468 | )->set( |
||
1469 | $this->handler->quoteColumn('is_main'), |
||
1470 | $q->bindValue(1, null, \PDO::PARAM_INT) |
||
1471 | )->where( |
||
1472 | $q->expr->lAnd( |
||
1473 | $q->expr->eq( |
||
1474 | $this->handler->quoteColumn('contentobject_id'), |
||
1475 | $q->bindValue($contentId, null, \PDO::PARAM_INT) |
||
1476 | ), |
||
1477 | $q->expr->eq( |
||
1478 | $this->handler->quoteColumn('contentobject_version'), |
||
1479 | $q->bindValue($versionNo, null, \PDO::PARAM_INT) |
||
1480 | ), |
||
1481 | $q->expr->eq( |
||
1482 | $this->handler->quoteColumn('parent_node'), |
||
1483 | $q->bindValue($parentLocationId, null, \PDO::PARAM_INT) |
||
1484 | ) |
||
1485 | ) |
||
1486 | ); |
||
1487 | $q->prepare()->execute(); |
||
1488 | } |
||
1489 | |||
1490 | /** |
||
1491 | * Get the total number of all Locations, except the Root node. |
||
1492 | * |
||
1493 | * @see loadAllLocationsData |
||
1494 | * |
||
1495 | * @return int |
||
1496 | */ |
||
1497 | public function countAllLocations() |
||
1498 | { |
||
1499 | $query = $this->getAllLocationsQueryBuilder(['count(node_id)']); |
||
1500 | |||
1501 | $statement = $query->execute(); |
||
1502 | |||
1503 | return (int) $statement->fetch(PDO::FETCH_COLUMN); |
||
1504 | } |
||
1505 | |||
1506 | /** |
||
1507 | * Load data of every Location, except the Root node. |
||
1508 | * |
||
1509 | * @param int $offset Paginator offset |
||
1510 | * @param int $limit Paginator limit |
||
1511 | * |
||
1512 | * @return array |
||
1513 | */ |
||
1514 | public function loadAllLocationsData($offset, $limit) |
||
1515 | { |
||
1516 | $query = $this |
||
1517 | ->getAllLocationsQueryBuilder( |
||
1518 | [ |
||
1519 | 'node_id', |
||
1520 | 'priority', |
||
1521 | 'is_hidden', |
||
1522 | 'is_invisible', |
||
1523 | 'remote_id', |
||
1524 | 'contentobject_id', |
||
1525 | 'parent_node_id', |
||
1526 | 'path_identification_string', |
||
1527 | 'path_string', |
||
1528 | 'depth', |
||
1529 | 'sort_field', |
||
1530 | 'sort_order', |
||
1531 | ] |
||
1532 | ) |
||
1533 | ->setFirstResult($offset) |
||
1534 | ->setMaxResults($limit) |
||
1535 | ->orderBy('depth', 'ASC') |
||
1536 | ->addOrderBy('node_id', 'ASC') |
||
1537 | ; |
||
1538 | |||
1539 | $statement = $query->execute(); |
||
1540 | |||
1541 | return $statement->fetchAll(PDO::FETCH_ASSOC); |
||
1542 | } |
||
1543 | |||
1544 | /** |
||
1545 | * Get Query Builder for fetching data of all Locations except the Root node. |
||
1546 | * |
||
1547 | * @todo Align with createNodeQueryBuilder, removing the need for both(?) |
||
1548 | * |
||
1549 | * @param array $columns list of columns to fetch |
||
1550 | * |
||
1551 | * @return \Doctrine\DBAL\Query\QueryBuilder |
||
1552 | */ |
||
1553 | private function getAllLocationsQueryBuilder(array $columns) |
||
1564 | |||
1565 | /** |
||
1566 | * Create QueryBuilder for selecting node data. |
||
1567 | * |
||
1568 | * @param array|null $translations Filters on language mask of content if provided. |
||
1569 | * @param bool $useAlwaysAvailable Respect always available flag on content when filtering on $translations. |
||
1570 | * |
||
1571 | * @return \Doctrine\DBAL\Query\QueryBuilder |
||
1572 | */ |
||
1573 | private function createNodeQueryBuilder(array $translations = null, bool $useAlwaysAvailable = true): QueryBuilder |
||
1574 | { |
||
1575 | $queryBuilder = $this->connection->createQueryBuilder(); |
||
1576 | $queryBuilder |
||
1577 | ->select('t.*') |
||
1578 | ->from('ezcontentobject_tree', 't'); |
||
1579 | |||
1580 | if (!empty($translations)) { |
||
1581 | $dbPlatform = $this->connection->getDatabasePlatform(); |
||
1582 | $expr = $queryBuilder->expr(); |
||
1583 | $mask = $this->generateLanguageMaskFromLanguageCodes($translations, $useAlwaysAvailable); |
||
1584 | |||
1585 | $queryBuilder->innerJoin( |
||
1586 | 't', |
||
1587 | 'ezcontentobject', |
||
1588 | 'c', |
||
1589 | $expr->andX( |
||
1590 | $expr->eq('t.contentobject_id', 'c.id'), |
||
1591 | // Won't work on Oracle, consider contribute bitwise features to query builder, and |
||
1592 | // contribute support for it handling DBM differences. Or detect Oracle here and do bitand() |
||
1593 | $expr->gt( |
||
1594 | $dbPlatform->getBitAndComparisonExpression('c.language_mask', $mask), |
||
1595 | 0 |
||
1596 | ) |
||
1597 | ) |
||
1598 | ); |
||
1599 | } |
||
1600 | |||
1601 | return $queryBuilder; |
||
1602 | } |
||
1603 | |||
1604 | /** |
||
1605 | * Generates a language mask for $translations argument. |
||
1606 | * |
||
1607 | * @todo Move logic to languageMaskGenerator in master. |
||
1608 | */ |
||
1609 | View Code Duplication | private function generateLanguageMaskFromLanguageCodes(array $translations, bool $useAlwaysAvailable = true): int |
|
1626 | } |
||
1627 |
Let’s take a look at an example:
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
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: