Completed
Push — tree_move_visibility ( 733279...093ea4 )
by André
21:39
created

DoctrineDatabase   D

Complexity

Total Complexity 78

Size/Duplication

Total Lines 1455
Duplicated Lines 7.29 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 78
c 2
b 1
f 0
lcom 1
cbo 11
dl 106
loc 1455
rs 4.6358

35 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getBasicNodeData() 21 21 2
A getBasicNodeDataByRemoteId() 21 21 2
A loadLocationDataByContent() 0 22 2
B loadParentLocationsDataForDraftContent() 0 46 1
A getSubtreeContent() 0 19 4
A applySubtreeLimitation() 0 9 1
A getChildren() 0 16 1
C moveSubtreeNodes() 0 101 11
A isHiddenByParent() 0 11 4
A updateSubtreeModificationTime() 0 20 2
B hideSubtree() 0 36 1
B unHideSubtree() 0 105 3
A swap() 0 60 2
B create() 0 81 2
A createNodeAssignment() 0 53 2
A deleteNodeAssignment() 0 21 2
B updateNodeAssignment() 27 27 1
B createLocationsFromNodeAssignments() 0 62 5
A updateLocationsContentVersionNo() 16 16 1
B getMainNodeId() 0 28 2
B update() 0 38 1
A updatePathIdentificationString() 0 23 2
A removeLocation() 0 13 1
B getFallbackMainNodeData() 0 26 1
A trashLocation() 0 18 2
B untrashLocation() 0 26 2
A setContentStatus() 0 17 1
A loadTrashByLocation() 21 21 2
C listTrashed() 0 46 9
A cleanupTrash() 0 6 1
A removeElementFromTrash() 0 13 1
B setSectionForSubtree() 0 29 1
A countLocationsByContentId() 0 20 1
A changeMainLocation() 0 67 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
3
/**
4
 * File containing the DoctrineDatabase Location 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
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway;
12
13
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway;
14
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
15
use eZ\Publish\Core\Persistence\Database\SelectQuery;
16
use eZ\Publish\Core\Persistence\Database\Query as DatabaseQuery;
17
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
18
use eZ\Publish\SPI\Persistence\Content\Location;
19
use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
20
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct;
21
use eZ\Publish\API\Repository\Values\Content\Query;
22
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
23
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound;
24
use RuntimeException;
25
use PDO;
26
27
/**
28
 * Location gateway implementation using the Doctrine database.
29
 */
30
class DoctrineDatabase extends Gateway
31
{
32
    /**
33
     * 2^30, since PHP_INT_MAX can cause overflows in DB systems, if PHP is run
34
     * on 64 bit systems.
35
     */
36
    const MAX_LIMIT = 1073741824;
37
38
    /**
39
     * Database handler.
40
     *
41
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
42
     */
43
    protected $handler;
44
45
    /**
46
     * Construct from database handler.
47
     *
48
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $handler
49
     */
50
    public function __construct(DatabaseHandler $handler)
51
    {
52
        $this->handler = $handler;
53
    }
54
55
    /**
56
     * Returns an array with basic node data.
57
     *
58
     * We might want to cache this, since this method is used by about every
59
     * method in the location handler.
60
     *
61
     * @todo optimize
62
     *
63
     * @param mixed $nodeId
64
     *
65
     * @return array
66
     */
67 View Code Duplication
    public function getBasicNodeData($nodeId)
68
    {
69
        $query = $this->handler->createSelectQuery();
70
        $query
71
            ->select('*')
72
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
73
            ->where(
74
                $query->expr->eq(
75
                    $this->handler->quoteColumn('node_id'),
76
                    $query->bindValue($nodeId)
77
                )
78
            );
79
        $statement = $query->prepare();
80
        $statement->execute();
81
82
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
83
            return $row;
84
        }
85
86
        throw new NotFound('location', $nodeId);
87
    }
88
89
    /**
90
     * Returns an array with basic node data.
91
     *
92
     * @todo optimize
93
     *
94
     * @param mixed $remoteId
95
     *
96
     * @return array
97
     */
98 View Code Duplication
    public function getBasicNodeDataByRemoteId($remoteId)
99
    {
100
        $query = $this->handler->createSelectQuery();
101
        $query
102
            ->select('*')
103
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
104
            ->where(
105
                $query->expr->eq(
106
                    $this->handler->quoteColumn('remote_id'),
107
                    $query->bindValue($remoteId)
108
                )
109
            );
110
        $statement = $query->prepare();
111
        $statement->execute();
112
113
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
114
            return $row;
115
        }
116
117
        throw new NotFound('location', $remoteId);
118
    }
119
120
    /**
121
     * Loads data for all Locations for $contentId, optionally only in the
122
     * subtree starting at $rootLocationId.
123
     *
124
     * @param int $contentId
125
     * @param int $rootLocationId
126
     *
127
     * @return array
128
     */
129
    public function loadLocationDataByContent($contentId, $rootLocationId = null)
130
    {
131
        $query = $this->handler->createSelectQuery();
132
        $query
133
            ->select('*')
134
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
135
            ->where(
136
                $query->expr->eq(
137
                    $this->handler->quoteColumn('contentobject_id'),
138
                    $query->bindValue($contentId)
139
                )
140
            );
141
142
        if ($rootLocationId !== null) {
143
            $this->applySubtreeLimitation($query, $rootLocationId);
144
        }
145
146
        $statement = $query->prepare();
147
        $statement->execute();
148
149
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
150
    }
151
152
    /**
153
     * @see \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway::loadParentLocationsDataForDraftContent
154
     */
155
    public function loadParentLocationsDataForDraftContent($contentId, $drafts = null)
156
    {
157
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
158
        $query = $this->handler->createSelectQuery();
159
        $query->selectDistinct(
160
            'ezcontentobject_tree.*'
161
        )->from(
162
            $this->handler->quoteTable('ezcontentobject_tree')
163
        )->innerJoin(
164
            $this->handler->quoteTable('eznode_assignment'),
165
            $query->expr->lAnd(
166
167
                $query->expr->eq(
168
                    $this->handler->quoteColumn('node_id', 'ezcontentobject_tree'),
169
                    $this->handler->quoteColumn('parent_node', 'eznode_assignment')
170
                ),
171
                $query->expr->eq(
172
                    $this->handler->quoteColumn('contentobject_id', 'eznode_assignment'),
173
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
174
                ),
175
                $query->expr->eq(
176
                    $this->handler->quoteColumn('op_code', 'eznode_assignment'),
177
                    $query->bindValue(self::NODE_ASSIGNMENT_OP_CODE_CREATE, null, \PDO::PARAM_INT)
178
                )
179
            )
180
        )->innerJoin(
181
            $this->handler->quoteTable('ezcontentobject'),
182
            $query->expr->lAnd(
183
                $query->expr->lOr(
184
                    $query->expr->eq(
185
                        $this->handler->quoteColumn('contentobject_id', 'eznode_assignment'),
186
                        $this->handler->quoteColumn('id', 'ezcontentobject')
187
                    )
188
                ),
189
                $query->expr->eq(
190
                    $this->handler->quoteColumn('status', 'ezcontentobject'),
191
                    $query->bindValue(ContentInfo::STATUS_DRAFT, null, \PDO::PARAM_INT)
192
                )
193
            )
194
        );
195
196
        $statement = $query->prepare();
197
        $statement->execute();
198
199
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
200
    }
201
202
    /**
203
     * Find all content in the given subtree.
204
     *
205
     * @param mixed $sourceId
206
     * @param bool $onlyIds
207
     *
208
     * @return array
209
     */
210
    public function getSubtreeContent($sourceId, $onlyIds = false)
211
    {
212
        $query = $this->handler->createSelectQuery();
213
        $query->select($onlyIds ? 'node_id, contentobject_id, depth' : '*')->from(
214
            $this->handler->quoteTable('ezcontentobject_tree')
215
        );
216
        $this->applySubtreeLimitation($query, $sourceId);
217
        $query->orderBy(
218
            $this->handler->quoteColumn('depth', 'ezcontentobject_tree')
219
        )->orderBy(
220
            $this->handler->quoteColumn('node_id', 'ezcontentobject_tree')
221
        );
222
        $statement = $query->prepare();
223
        $statement->execute();
224
225
        $results = $statement->fetchAll($onlyIds ? (PDO::FETCH_COLUMN | PDO::FETCH_GROUP) : PDO::FETCH_ASSOC);
226
        // array_map() is used to to map all elements stored as $results[$i][0] to $results[$i]
227
        return $onlyIds ? array_map('reset', $results) : $results;
228
    }
229
230
    /**
231
     * Limits the given $query to the subtree starting at $rootLocationId.
232
     *
233
     * @param \eZ\Publish\Core\Persistence\Database\Query $query
234
     * @param string $rootLocationId
235
     */
236
    protected function applySubtreeLimitation(DatabaseQuery $query, $rootLocationId)
237
    {
238
        $query->where(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\Core\Persistence\Database\Query as the method where() does only exist in the following implementations of said interface: eZ\Publish\Core\Persiste...ine\DeleteDoctrineQuery, eZ\Publish\Core\Persiste...ine\SelectDoctrineQuery, eZ\Publish\Core\Persiste...\SubselectDoctrineQuery, eZ\Publish\Core\Persiste...ine\UpdateDoctrineQuery.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
239
            $query->expr->like(
240
                $this->handler->quoteColumn('path_string', 'ezcontentobject_tree'),
241
                $query->bindValue('%/' . $rootLocationId . '/%')
242
            )
243
        );
244
    }
245
246
    /**
247
     * Returns data for the first level children of the location identified by given $locationId.
248
     *
249
     * @param mixed $locationId
250
     *
251
     * @return array
252
     */
253
    public function getChildren($locationId)
254
    {
255
        $query = $this->handler->createSelectQuery();
256
        $query->select('*')->from(
257
            $this->handler->quoteTable('ezcontentobject_tree')
258
        )->where(
259
            $query->expr->eq(
260
                $this->handler->quoteColumn('parent_node_id', 'ezcontentobject_tree'),
261
                $query->bindValue($locationId, null, \PDO::PARAM_INT)
262
            )
263
        );
264
        $statement = $query->prepare();
265
        $statement->execute();
266
267
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
268
    }
269
270
    /**
271
     * Update path strings to move nodes in the ezcontentobject_tree table.
272
     *
273
     * This query can likely be optimized to use some more advanced string
274
     * operations, which then depend on the respective database.
275
     *
276
     * @todo optimize
277
     *
278
     * @param array $sourceNodeData
279
     * @param array $destinationNodeData
280
     */
281
    public function moveSubtreeNodes(array $sourceNodeData, array $destinationNodeData)
282
    {
283
        $fromPathString = $sourceNodeData['path_string'];
284
285
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
286
        $query = $this->handler->createSelectQuery();
287
        $query
288
            ->select(
289
                $this->handler->quoteColumn('node_id'),
290
                $this->handler->quoteColumn('parent_node_id'),
291
                $this->handler->quoteColumn('path_string'),
292
                $this->handler->quoteColumn('path_identification_string')
293
            )
294
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
295
            ->where(
296
                $query->expr->like(
297
                    $this->handler->quoteColumn('path_string'),
298
                    $query->bindValue($fromPathString . '%')
299
                )
300
            );
301
        $statement = $query->prepare();
302
        $statement->execute();
303
304
        $rows = $statement->fetchAll();
305
        $oldParentPathString = implode('/', array_slice(explode('/', $fromPathString), 0, -2)) . '/';
306
        $oldParentPathIdentificationString = implode(
307
            '/',
308
            array_slice(explode('/', $sourceNodeData['path_identification_string']), 0, -1)
309
        );
310
311
        foreach ($rows as $row) {
312
            // Prefixing ensures correct replacement when old parent is root node
313
            $newPathString = str_replace(
314
                'prefix' . $oldParentPathString,
315
                $destinationNodeData['path_string'],
316
                'prefix' . $row['path_string']
317
            );
318
            $newPathIdentificationString = str_replace(
319
                'prefix' . $oldParentPathIdentificationString,
320
                $destinationNodeData['path_identification_string'] . '/',
321
                'prefix' . $row['path_identification_string']
322
            );
323
324
            $newParentId = $row['parent_node_id'];
325
            if ($row['path_string'] === $fromPathString) {
326
                $newParentId = (int)implode('', array_slice(explode('/', $newPathString), -3, 1));
327
            }
328
329
            /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
330
            $query = $this->handler->createUpdateQuery();
331
            $query
332
                ->update($this->handler->quoteTable('ezcontentobject_tree'))
333
                ->set(
334
                    $this->handler->quoteColumn('path_string'),
335
                    $query->bindValue($newPathString)
336
                )
337
                ->set(
338
                    $this->handler->quoteColumn('path_identification_string'),
339
                    $query->bindValue($newPathIdentificationString)
340
                )
341
                ->set(
342
                    $this->handler->quoteColumn('depth'),
343
                    $query->bindValue(substr_count($newPathString, '/') - 2)
344
                )
345
                ->set(
346
                    $this->handler->quoteColumn('parent_node_id'),
347
                    $query->bindValue($newParentId)
348
                );
349
350
            if ($destinationNodeData['is_hidden'] || $destinationNodeData['is_invisible']) {
351
                // CASE 1: Mark whole tree as invisible if destination hides it
352
                $query->set(
353
                    $this->handler->quoteColumn('is_invisible'),
354
                    $query->bindValue(1)
355
                );
356
            } elseif ($sourceNodeData['is_hidden'] && $sourceNodeData['is_invisible']) {
357
                // CASE 2: source is hidden & invisible, so we just need to update source as it is no longer invisible
358
                if ($row['node_id'] == $sourceNodeData['node_id']) {
359
                    $query->set(
360
                        $this->handler->quoteColumn('is_invisible'),
361
                        $query->bindValue(0)
362
                    );
363
                }
364
            } elseif ($sourceNodeData['is_hidden'] && $sourceNodeData['is_invisible']) {
365
                // CASE 3: source is only invisible, we will need to re-calculate whole moved tree visibility
366
                $query->set(
367
                    $this->handler->quoteColumn('is_invisible'),
368
                    $query->bindValue($this->isHiddenByParent($newPathString, $rows) ? 1 : 0)
369
                );
370
            }
371
            // CASE 4: keep tree as is
372
373
            $query->where(
374
                    $query->expr->eq(
375
                        $this->handler->quoteColumn('node_id'),
376
                        $query->bindValue($row['node_id'])
377
                    )
378
                );
379
            $query->prepare()->execute();
380
        }
381
    }
382
383
    private function isHiddenByParent($pathString, array $rows)
384
    {
385
        $parentNodeIds = explode('/', trim($pathString, '/'));
386
        array_pop($parentNodeIds);// remove self
387
        foreach($rows as $row) {
388
            if ($row['is_hidden'] &&  in_array($row['node_id'], $parentNodeIds)) {
389
                return true;
390
            }
391
        }
392
        return false;
393
    }
394
395
    /**
396
     * Updated subtree modification time for all nodes on path.
397
     *
398
     * @param string $pathString
399
     * @param int|null $timestamp
400
     */
401
    public function updateSubtreeModificationTime($pathString, $timestamp = null)
402
    {
403
        $nodes = array_filter(explode('/', $pathString));
404
        $query = $this->handler->createUpdateQuery();
405
        $query
406
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
407
            ->set(
408
                $this->handler->quoteColumn('modified_subnode'),
409
                $query->bindValue(
410
                    $timestamp ?: time()
411
                )
412
            )
413
            ->where(
414
                $query->expr->in(
415
                    $this->handler->quoteColumn('node_id'),
416
                    $nodes
417
                )
418
            );
419
        $query->prepare()->execute();
420
    }
421
422
    /**
423
     * Sets a location to be hidden, and it self + all children to invisible.
424
     *
425
     * @param string $pathString
426
     */
427
    public function hideSubtree($pathString)
428
    {
429
        $query = $this->handler->createUpdateQuery();
430
        $query
431
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
432
            ->set(
433
                $this->handler->quoteColumn('is_invisible'),
434
                $query->bindValue(1)
435
            )
436
            ->set(
437
                $this->handler->quoteColumn('modified_subnode'),
438
                $query->bindValue(time())
439
            )
440
            ->where(
441
                $query->expr->like(
442
                    $this->handler->quoteColumn('path_string'),
443
                    $query->bindValue($pathString . '%')
444
                )
445
            );
446
        $query->prepare()->execute();
447
448
        $query = $this->handler->createUpdateQuery();
449
        $query
450
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
451
            ->set(
452
                $this->handler->quoteColumn('is_hidden'),
453
                $query->bindValue(1)
454
            )
455
            ->where(
456
                $query->expr->eq(
457
                    $this->handler->quoteColumn('path_string'),
458
                    $query->bindValue($pathString)
459
                )
460
            );
461
        $query->prepare()->execute();
462
    }
463
464
    /**
465
     * Sets a location to be unhidden, and self + children to visible unless a parent is hiding the tree.
466
     * If not make sure only children down to first hidden node is marked visible.
467
     *
468
     * @param string $pathString
469
     */
470
    public function unHideSubtree($pathString)
471
    {
472
        // Unhide the requested node
473
        $query = $this->handler->createUpdateQuery();
474
        $query
475
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
476
            ->set(
477
                $this->handler->quoteColumn('is_hidden'),
478
                $query->bindValue(0)
479
            )
480
            ->where(
481
                $query->expr->eq(
482
                    $this->handler->quoteColumn('path_string'),
483
                    $query->bindValue($pathString)
484
                )
485
            );
486
        $query->prepare()->execute();
487
488
        // Check if any parent nodes are explicitly hidden
489
        $query = $this->handler->createSelectQuery();
490
        $query
491
            ->select($this->handler->quoteColumn('path_string'))
492
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
493
            ->where(
494
                $query->expr->lAnd(
495
                    $query->expr->eq(
496
                        $this->handler->quoteColumn('is_hidden'),
497
                        $query->bindValue(1)
498
                    ),
499
                    $query->expr->in(
500
                        $this->handler->quoteColumn('node_id'),
501
                        array_filter(explode('/', $pathString))
502
                    )
503
                )
504
            );
505
        $statement = $query->prepare();
506
        $statement->execute();
507
        if (count($statement->fetchAll(\PDO::FETCH_COLUMN))) {
508
            // There are parent nodes set hidden, so that we can skip marking
509
            // something visible again.
510
            return;
511
        }
512
513
        // Find nodes of explicitly hidden subtrees in the subtree which
514
        // should be unhidden
515
        $query = $this->handler->createSelectQuery();
516
        $query
517
            ->select($this->handler->quoteColumn('path_string'))
518
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
519
            ->where(
520
                $query->expr->lAnd(
521
                    $query->expr->eq(
522
                        $this->handler->quoteColumn('is_hidden'),
523
                        $query->bindValue(1)
524
                    ),
525
                    $query->expr->like(
526
                        $this->handler->quoteColumn('path_string'),
527
                        $query->bindValue($pathString . '%')
528
                    )
529
                )
530
            );
531
        $statement = $query->prepare();
532
        $statement->execute();
533
        $hiddenSubtrees = $statement->fetchAll(\PDO::FETCH_COLUMN);
534
535
        $query = $this->handler->createUpdateQuery();
536
        $query
537
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
538
            ->set(
539
                $this->handler->quoteColumn('is_invisible'),
540
                $query->bindValue(0)
541
            )
542
            ->set(
543
                $this->handler->quoteColumn('modified_subnode'),
544
                $query->bindValue(time())
545
            );
546
547
        // Build where expression selecting the nodes, which should be made
548
        // visible again
549
        $where = $query->expr->like(
550
            $this->handler->quoteColumn('path_string'),
551
            $query->bindValue($pathString . '%')
552
        );
553
        if (count($hiddenSubtrees)) {
554
            $handler = $this->handler;
555
            $where = $query->expr->lAnd(
556
                $where,
557
                $query->expr->lAnd(
558
                    array_map(
559
                        function ($pathString) use ($query, $handler) {
560
                            return $query->expr->not(
561
                                $query->expr->like(
562
                                    $handler->quoteColumn('path_string'),
563
                                    $query->bindValue($pathString . '%')
564
                                )
565
                            );
566
                        },
567
                        $hiddenSubtrees
568
                    )
569
                )
570
            );
571
        }
572
        $query->where($where);
573
        $statement = $query->prepare()->execute();
0 ignored issues
show
Unused Code introduced by
$statement is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
574
    }
575
576
    /**
577
     * Swaps the content object being pointed to by a location object.
578
     *
579
     * Make the location identified by $locationId1 refer to the Content
580
     * referred to by $locationId2 and vice versa.
581
     *
582
     * @param mixed $locationId1
583
     * @param mixed $locationId2
584
     *
585
     * @return bool
586
     */
587
    public function swap($locationId1, $locationId2)
588
    {
589
        $query = $this->handler->createSelectQuery();
590
        $query
591
            ->select(
592
                $this->handler->quoteColumn('node_id'),
593
                $this->handler->quoteColumn('contentobject_id'),
594
                $this->handler->quoteColumn('contentobject_version')
595
            )
596
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
597
            ->where(
598
                $query->expr->in(
599
                    $this->handler->quoteColumn('node_id'),
600
                    array($locationId1, $locationId2)
601
                )
602
            );
603
        $statement = $query->prepare();
604
        $statement->execute();
605
        foreach ($statement->fetchAll() as $row) {
606
            $contentObjects[$row['node_id']] = $row;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$contentObjects was never initialized. Although not strictly required by PHP, it is generally a good practice to add $contentObjects = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
607
        }
608
609
        $query = $this->handler->createUpdateQuery();
610
        $query
611
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
612
            ->set(
613
                $this->handler->quoteColumn('contentobject_id'),
614
                $query->bindValue($contentObjects[$locationId2]['contentobject_id'])
0 ignored issues
show
Bug introduced by
The variable $contentObjects does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
615
            )
616
            ->set(
617
                $this->handler->quoteColumn('contentobject_version'),
618
                $query->bindValue($contentObjects[$locationId2]['contentobject_version'])
619
            )
620
            ->where(
621
                $query->expr->eq(
622
                    $this->handler->quoteColumn('node_id'),
623
                    $query->bindValue($locationId1)
624
                )
625
            );
626
        $query->prepare()->execute();
627
628
        $query = $this->handler->createUpdateQuery();
629
        $query
630
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
631
            ->set(
632
                $this->handler->quoteColumn('contentobject_id'),
633
                $query->bindValue($contentObjects[$locationId1]['contentobject_id'])
634
            )
635
            ->set(
636
                $this->handler->quoteColumn('contentobject_version'),
637
                $query->bindValue($contentObjects[$locationId1]['contentobject_version'])
638
            )
639
            ->where(
640
                $query->expr->eq(
641
                    $this->handler->quoteColumn('node_id'),
642
                    $query->bindValue($locationId2)
643
                )
644
            );
645
        $query->prepare()->execute();
646
    }
647
648
    /**
649
     * Creates a new location in given $parentNode.
650
     *
651
     * @param \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct $createStruct
652
     * @param array $parentNode
653
     *
654
     * @return \eZ\Publish\SPI\Persistence\Content\Location
655
     */
656
    public function create(CreateStruct $createStruct, array $parentNode)
657
    {
658
        $location = new Location();
659
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
660
        $query = $this->handler->createInsertQuery();
661
        $query
662
            ->insertInto($this->handler->quoteTable('ezcontentobject_tree'))
663
            ->set(
664
                $this->handler->quoteColumn('contentobject_id'),
665
                $query->bindValue($location->contentId = $createStruct->contentId, null, \PDO::PARAM_INT)
666
            )->set(
667
                $this->handler->quoteColumn('contentobject_is_published'),
668
                $query->bindValue(1, null, \PDO::PARAM_INT)
669
            )->set(
670
                $this->handler->quoteColumn('contentobject_version'),
671
                $query->bindValue($createStruct->contentVersion, null, \PDO::PARAM_INT)
672
            )->set(
673
                $this->handler->quoteColumn('depth'),
674
                $query->bindValue($location->depth = $parentNode['depth'] + 1, null, \PDO::PARAM_INT)
675
            )->set(
676
                $this->handler->quoteColumn('is_hidden'),
677
                $query->bindValue($location->hidden = $createStruct->hidden, null, \PDO::PARAM_INT)
678
            )->set(
679
                $this->handler->quoteColumn('is_invisible'),
680
                $query->bindValue($location->invisible = $createStruct->invisible, null, \PDO::PARAM_INT)
681
            )->set(
682
                $this->handler->quoteColumn('modified_subnode'),
683
                $query->bindValue(time(), null, \PDO::PARAM_INT)
684
            )->set(
685
                $this->handler->quoteColumn('node_id'),
686
                $this->handler->getAutoIncrementValue('ezcontentobject_tree', 'node_id')
687
            )->set(
688
                $this->handler->quoteColumn('parent_node_id'),
689
                $query->bindValue($location->parentId = $parentNode['node_id'], null, \PDO::PARAM_INT)
690
            )->set(
691
                $this->handler->quoteColumn('path_identification_string'),
692
                $query->bindValue($location->pathIdentificationString = $createStruct->pathIdentificationString, null, \PDO::PARAM_STR)
0 ignored issues
show
Deprecated Code introduced by
The property eZ\Publish\SPI\Persisten...athIdentificationString has been deprecated with message: Since 5.4, planned to be removed in 6.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Deprecated Code introduced by
The property eZ\Publish\SPI\Persisten...athIdentificationString has been deprecated with message: Since 5.4, planned to be removed in 6.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
693
            )->set(
694
                $this->handler->quoteColumn('path_string'),
695
                $query->bindValue('dummy') // Set later
696
            )->set(
697
                $this->handler->quoteColumn('priority'),
698
                $query->bindValue($location->priority = $createStruct->priority, null, \PDO::PARAM_INT)
699
            )->set(
700
                $this->handler->quoteColumn('remote_id'),
701
                $query->bindValue($location->remoteId = $createStruct->remoteId, null, \PDO::PARAM_STR)
702
            )->set(
703
                $this->handler->quoteColumn('sort_field'),
704
                $query->bindValue($location->sortField = $createStruct->sortField, null, \PDO::PARAM_INT)
705
            )->set(
706
                $this->handler->quoteColumn('sort_order'),
707
                $query->bindValue($location->sortOrder = $createStruct->sortOrder, null, \PDO::PARAM_INT)
708
            );
709
        $query->prepare()->execute();
710
711
        $location->id = $this->handler->lastInsertId($this->handler->getSequenceName('ezcontentobject_tree', 'node_id'));
712
713
        $mainLocationId = $createStruct->mainLocationId === true ? $location->id : $createStruct->mainLocationId;
714
        $location->pathString = $parentNode['path_string'] . $location->id . '/';
715
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
716
        $query = $this->handler->createUpdateQuery();
717
        $query
718
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
719
            ->set(
720
                $this->handler->quoteColumn('path_string'),
721
                $query->bindValue($location->pathString)
722
            )
723
            ->set(
724
                $this->handler->quoteColumn('main_node_id'),
725
                $query->bindValue($mainLocationId, null, \PDO::PARAM_INT)
726
            )
727
            ->where(
728
                $query->expr->eq(
729
                    $this->handler->quoteColumn('node_id'),
730
                    $query->bindValue($location->id, null, \PDO::PARAM_INT)
731
                )
732
            );
733
        $query->prepare()->execute();
734
735
        return $location;
736
    }
737
738
    /**
739
     * Create an entry in the node assignment table.
740
     *
741
     * @param \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct $createStruct
742
     * @param mixed $parentNodeId
743
     * @param int $type
744
     */
745
    public function createNodeAssignment(CreateStruct $createStruct, $parentNodeId, $type = self::NODE_ASSIGNMENT_OP_CODE_CREATE_NOP)
746
    {
747
        $isMain = ($createStruct->mainLocationId === true ? 1 : 0);
748
749
        $query = $this->handler->createInsertQuery();
750
        $query
751
            ->insertInto($this->handler->quoteTable('eznode_assignment'))
752
            ->set(
753
                $this->handler->quoteColumn('contentobject_id'),
754
                $query->bindValue($createStruct->contentId, null, \PDO::PARAM_INT)
755
            )->set(
756
                $this->handler->quoteColumn('contentobject_version'),
757
                $query->bindValue($createStruct->contentVersion, null, \PDO::PARAM_INT)
758
            )->set(
759
                $this->handler->quoteColumn('from_node_id'),
760
                $query->bindValue(0, null, \PDO::PARAM_INT) // unused field
761
            )->set(
762
                $this->handler->quoteColumn('id'),
763
                $this->handler->getAutoIncrementValue('eznode_assignment', 'id')
764
            )->set(
765
                $this->handler->quoteColumn('is_main'),
766
                $query->bindValue($isMain, null, \PDO::PARAM_INT) // Changed by the business layer, later
767
            )->set(
768
                $this->handler->quoteColumn('op_code'),
769
                $query->bindValue($type, null, \PDO::PARAM_INT)
770
            )->set(
771
                $this->handler->quoteColumn('parent_node'),
772
                $query->bindValue($parentNodeId, null, \PDO::PARAM_INT)
773
            )->set(
774
                // parent_remote_id column should contain the remote id of the corresponding Location
775
                $this->handler->quoteColumn('parent_remote_id'),
776
                $query->bindValue($createStruct->remoteId, null, \PDO::PARAM_STR)
777
            )->set(
778
                // remote_id column should contain the remote id of the node assignment itself,
779
                // however this was never implemented completely in Legacy Stack, so we just set
780
                // it to default value '0'
781
                $this->handler->quoteColumn('remote_id'),
782
                $query->bindValue('0', null, \PDO::PARAM_STR)
783
            )->set(
784
                $this->handler->quoteColumn('sort_field'),
785
                $query->bindValue($createStruct->sortField, null, \PDO::PARAM_INT)
786
            )->set(
787
                $this->handler->quoteColumn('sort_order'),
788
                $query->bindValue($createStruct->sortOrder, null, \PDO::PARAM_INT)
789
            )->set(
790
                $this->handler->quoteColumn('priority'),
791
                $query->bindValue($createStruct->priority, null, \PDO::PARAM_INT)
792
            )->set(
793
                $this->handler->quoteColumn('is_hidden'),
794
                $query->bindValue($createStruct->hidden, null, \PDO::PARAM_INT)
795
            );
796
        $query->prepare()->execute();
797
    }
798
799
    /**
800
     * Deletes node assignment for given $contentId and $versionNo.
801
     *
802
     * If $versionNo is not passed all node assignments for given $contentId are deleted
803
     *
804
     * @param int $contentId
805
     * @param int|null $versionNo
806
     */
807
    public function deleteNodeAssignment($contentId, $versionNo = null)
808
    {
809
        $query = $this->handler->createDeleteQuery();
810
        $query->deleteFrom(
811
            'eznode_assignment'
812
        )->where(
813
            $query->expr->eq(
814
                $this->handler->quoteColumn('contentobject_id'),
815
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
816
            )
817
        );
818
        if (isset($versionNo)) {
819
            $query->where(
820
                $query->expr->eq(
821
                    $this->handler->quoteColumn('contentobject_version'),
822
                    $query->bindValue($versionNo, null, \PDO::PARAM_INT)
823
                )
824
            );
825
        }
826
        $query->prepare()->execute();
827
    }
828
829
    /**
830
     * Update node assignment table.
831
     *
832
     * @param int $contentObjectId
833
     * @param int $oldParent
834
     * @param int $newParent
835
     * @param int $opcode
836
     */
837 View Code Duplication
    public function updateNodeAssignment($contentObjectId, $oldParent, $newParent, $opcode)
838
    {
839
        $query = $this->handler->createUpdateQuery();
840
        $query
841
            ->update($this->handler->quoteTable('eznode_assignment'))
842
            ->set(
843
                $this->handler->quoteColumn('parent_node'),
844
                $query->bindValue($newParent, null, \PDO::PARAM_INT)
845
            )
846
            ->set(
847
                $this->handler->quoteColumn('op_code'),
848
                $query->bindValue($opcode, null, \PDO::PARAM_INT)
849
            )
850
            ->where(
851
                $query->expr->lAnd(
852
                    $query->expr->eq(
853
                        $this->handler->quoteColumn('contentobject_id'),
854
                        $query->bindValue($contentObjectId, null, \PDO::PARAM_INT)
855
                    ),
856
                    $query->expr->eq(
857
                        $this->handler->quoteColumn('parent_node'),
858
                        $query->bindValue($oldParent, null, \PDO::PARAM_INT)
859
                    )
860
                )
861
            );
862
        $query->prepare()->execute();
863
    }
864
865
    /**
866
     * Create locations from node assignments.
867
     *
868
     * Convert existing node assignments into real locations.
869
     *
870
     * @param mixed $contentId
871
     * @param mixed $versionNo
872
     */
873
    public function createLocationsFromNodeAssignments($contentId, $versionNo)
874
    {
875
        // select all node assignments with OP_CODE_CREATE (3) for this content
876
        $query = $this->handler->createSelectQuery();
877
        $query
878
            ->select('*')
879
            ->from($this->handler->quoteTable('eznode_assignment'))
880
            ->where(
881
                $query->expr->lAnd(
882
                    $query->expr->eq(
883
                        $this->handler->quoteColumn('contentobject_id'),
884
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
885
                    ),
886
                    $query->expr->eq(
887
                        $this->handler->quoteColumn('contentobject_version'),
888
                        $query->bindValue($versionNo, null, \PDO::PARAM_INT)
889
                    ),
890
                    $query->expr->eq(
891
                        $this->handler->quoteColumn('op_code'),
892
                        $query->bindValue(self::NODE_ASSIGNMENT_OP_CODE_CREATE, null, \PDO::PARAM_INT)
893
                    )
894
                )
895
            )->orderBy('id');
896
        $statement = $query->prepare();
897
        $statement->execute();
898
899
        // convert all these assignments to nodes
900
901
        while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
902
            if ((bool)$row['is_main'] === true) {
903
                $mainLocationId = true;
904
            } else {
905
                $mainLocationId = $this->getMainNodeId($contentId);
906
            }
907
908
            $parentLocationData = $this->getBasicNodeData($row['parent_node']);
909
            $isInvisible = $row['is_hidden'] || $parentLocationData['is_hidden'] || $parentLocationData['is_invisible'];
910
            $this->create(
911
                new CreateStruct(
912
                    array(
913
                        'contentId' => $row['contentobject_id'],
914
                        'contentVersion' => $row['contentobject_version'],
915
                        'mainLocationId' => $mainLocationId,
916
                        'remoteId' => $row['parent_remote_id'],
917
                        'sortField' => $row['sort_field'],
918
                        'sortOrder' => $row['sort_order'],
919
                        'priority' => $row['priority'],
920
                        'hidden' => $row['is_hidden'],
921
                        'invisible' => $isInvisible,
922
                    )
923
                ),
924
                $parentLocationData
925
            );
926
927
            $this->updateNodeAssignment(
928
                $row['contentobject_id'],
929
                $row['parent_node'],
930
                $row['parent_node'],
931
                self::NODE_ASSIGNMENT_OP_CODE_CREATE_NOP
932
            );
933
        }
934
    }
935
936
    /**
937
     * Updates all Locations of content identified with $contentId with $versionNo.
938
     *
939
     * @param mixed $contentId
940
     * @param mixed $versionNo
941
     */
942 View Code Duplication
    public function updateLocationsContentVersionNo($contentId, $versionNo)
943
    {
944
        $query = $this->handler->createUpdateQuery();
945
        $query->update(
946
            $this->handler->quoteTable('ezcontentobject_tree')
947
        )->set(
948
            $this->handler->quoteColumn('contentobject_version'),
949
            $query->bindValue($versionNo, null, \PDO::PARAM_INT)
950
        )->where(
951
            $query->expr->eq(
952
                $this->handler->quoteColumn('contentobject_id'),
953
                $contentId
954
            )
955
        );
956
        $query->prepare()->execute();
957
    }
958
959
    /**
960
     * Searches for the main nodeId of $contentId in $versionId.
961
     *
962
     * @param int $contentId
963
     *
964
     * @return int|bool
965
     */
966
    private function getMainNodeId($contentId)
967
    {
968
        $query = $this->handler->createSelectQuery();
969
        $query
970
            ->select('node_id')
971
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
972
            ->where(
973
                $query->expr->lAnd(
974
                    $query->expr->eq(
975
                        $this->handler->quoteColumn('contentobject_id'),
976
                        $query->bindValue($contentId, null, \PDO::PARAM_INT)
977
                    ),
978
                    $query->expr->eq(
979
                        $this->handler->quoteColumn('node_id'),
980
                        $this->handler->quoteColumn('main_node_id')
981
                    )
982
                )
983
            );
984
        $statement = $query->prepare();
985
        $statement->execute();
986
987
        $result = $statement->fetchAll(\PDO::FETCH_ASSOC);
988
        if (count($result) === 1) {
989
            return (int)$result[0]['node_id'];
990
        } else {
991
            return false;
992
        }
993
    }
994
995
    /**
996
     * Updates an existing location.
997
     *
998
     * Will not throw anything if location id is invalid or no entries are affected.
999
     *
1000
     * @param \eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct $location
1001
     * @param int $locationId
1002
     */
1003
    public function update(UpdateStruct $location, $locationId)
1004
    {
1005
        $query = $this->handler->createUpdateQuery();
1006
1007
        $query
1008
            ->update($this->handler->quoteTable('ezcontentobject_tree'))
1009
            ->set(
1010
                $this->handler->quoteColumn('priority'),
1011
                $query->bindValue($location->priority)
1012
            )
1013
            ->set(
1014
                $this->handler->quoteColumn('remote_id'),
1015
                $query->bindValue($location->remoteId)
1016
            )
1017
            ->set(
1018
                $this->handler->quoteColumn('sort_order'),
1019
                $query->bindValue($location->sortOrder)
1020
            )
1021
            ->set(
1022
                $this->handler->quoteColumn('sort_field'),
1023
                $query->bindValue($location->sortField)
1024
            )
1025
            ->where(
1026
                $query->expr->eq(
1027
                    $this->handler->quoteColumn('node_id'),
1028
                    $locationId
1029
                )
1030
            );
1031
        $statement = $query->prepare();
1032
        $statement->execute();
1033
1034
        // Commented due to EZP-23302: Update Location fails if no change is performed with the update
1035
        // Should be fixed with PDO::MYSQL_ATTR_FOUND_ROWS instead
1036
        /*if ( $statement->rowCount() < 1 )
1037
        {
1038
            throw new NotFound( 'location', $locationId );
1039
        }*/
1040
    }
1041
1042
    /**
1043
     * Updates path identification string for given $locationId.
1044
     *
1045
     * @param mixed $locationId
1046
     * @param mixed $parentLocationId
1047
     * @param string $text
1048
     */
1049
    public function updatePathIdentificationString($locationId, $parentLocationId, $text)
1050
    {
1051
        $parentData = $this->getBasicNodeData($parentLocationId);
1052
1053
        $newPathIdentificationString = empty($parentData['path_identification_string']) ?
1054
            $text :
1055
            $parentData['path_identification_string'] . '/' . $text;
1056
1057
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
1058
        $query = $this->handler->createUpdateQuery();
1059
        $query->update(
1060
            'ezcontentobject_tree'
1061
        )->set(
1062
            $this->handler->quoteColumn('path_identification_string'),
1063
            $query->bindValue($newPathIdentificationString, null, \PDO::PARAM_STR)
1064
        )->where(
1065
            $query->expr->eq(
1066
                $this->handler->quoteColumn('node_id'),
1067
                $query->bindValue($locationId, null, \PDO::PARAM_INT)
1068
            )
1069
        );
1070
        $query->prepare()->execute();
1071
    }
1072
1073
    /**
1074
     * Deletes ezcontentobject_tree row for given $locationId (node_id).
1075
     *
1076
     * @param mixed $locationId
1077
     */
1078
    public function removeLocation($locationId)
1079
    {
1080
        $query = $this->handler->createDeleteQuery();
1081
        $query->deleteFrom(
1082
            'ezcontentobject_tree'
1083
        )->where(
1084
            $query->expr->eq(
1085
                $this->handler->quoteColumn('node_id'),
1086
                $query->bindValue($locationId, null, \PDO::PARAM_INT)
1087
            )
1088
        );
1089
        $query->prepare()->execute();
1090
    }
1091
1092
    /**
1093
     * Returns id of the next in line node to be set as a new main node.
1094
     *
1095
     * This returns lowest node id for content identified by $contentId, and not of
1096
     * the node identified by given $locationId (current main node).
1097
     * Assumes that content has more than one location.
1098
     *
1099
     * @param mixed $contentId
1100
     * @param mixed $locationId
1101
     *
1102
     * @return array
1103
     */
1104
    public function getFallbackMainNodeData($contentId, $locationId)
1105
    {
1106
        $query = $this->handler->createSelectQuery();
1107
        $query->select(
1108
            $this->handler->quoteColumn('node_id'),
1109
            $this->handler->quoteColumn('contentobject_version'),
1110
            $this->handler->quoteColumn('parent_node_id')
1111
        )->from(
1112
            $this->handler->quoteTable('ezcontentobject_tree')
1113
        )->where(
1114
            $query->expr->lAnd(
1115
                $query->expr->eq(
1116
                    $this->handler->quoteColumn('contentobject_id'),
1117
                    $query->bindValue($contentId, null, \PDO::PARAM_INT)
1118
                ),
1119
                $query->expr->neq(
1120
                    $this->handler->quoteColumn('node_id'),
1121
                    $query->bindValue($locationId, null, \PDO::PARAM_INT)
1122
                )
1123
            )
1124
        )->orderBy('node_id', SelectQuery::ASC)->limit(1);
1125
        $statement = $query->prepare();
1126
        $statement->execute();
1127
1128
        return $statement->fetch(\PDO::FETCH_ASSOC);
1129
    }
1130
1131
    /**
1132
     * Sends a single location identified by given $locationId to the trash.
1133
     *
1134
     * The associated content object is left untouched.
1135
     *
1136
     * @param mixed $locationId
1137
     *
1138
     * @return bool
1139
     */
1140
    public function trashLocation($locationId)
1141
    {
1142
        $locationRow = $this->getBasicNodeData($locationId);
1143
1144
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
1145
        $query = $this->handler->createInsertQuery();
1146
        $query->insertInto($this->handler->quoteTable('ezcontentobject_trash'));
1147
1148
        unset($locationRow['contentobject_is_published']);
1149
        foreach ($locationRow as $key => $value) {
1150
            $query->set($key, $query->bindValue($value));
1151
        }
1152
1153
        $query->prepare()->execute();
1154
1155
        $this->removeLocation($locationRow['node_id']);
1156
        $this->setContentStatus($locationRow['contentobject_id'], ContentInfo::STATUS_ARCHIVED);
1157
    }
1158
1159
    /**
1160
     * Returns a trashed location to normal state.
1161
     *
1162
     * Recreates the originally trashed location in the new position. If no new
1163
     * position has been specified, it will be tried to re-create the location
1164
     * at the old position. If this is not possible ( because the old location
1165
     * does not exist any more) and exception is thrown.
1166
     *
1167
     * @param mixed $locationId
1168
     * @param mixed|null $newParentId
1169
     *
1170
     * @return \eZ\Publish\SPI\Persistence\Content\Location
1171
     */
1172
    public function untrashLocation($locationId, $newParentId = null)
1173
    {
1174
        $row = $this->loadTrashByLocation($locationId);
1175
1176
        $newLocation = $this->create(
1177
            new CreateStruct(
1178
                array(
1179
                    'priority' => $row['priority'],
1180
                    'hidden' => $row['is_hidden'],
1181
                    'invisible' => $row['is_invisible'],
1182
                    'remoteId' => $row['remote_id'],
1183
                    'contentId' => $row['contentobject_id'],
1184
                    'contentVersion' => $row['contentobject_version'],
1185
                    'mainLocationId' => true, // Restored location is always main location
1186
                    'sortField' => $row['sort_field'],
1187
                    'sortOrder' => $row['sort_order'],
1188
                )
1189
            ),
1190
            $this->getBasicNodeData($newParentId ?: $row['parent_node_id'])
1191
        );
1192
1193
        $this->removeElementFromTrash($locationId);
1194
        $this->setContentStatus($row['contentobject_id'], ContentInfo::STATUS_PUBLISHED);
1195
1196
        return $newLocation;
1197
    }
1198
1199
    /**
1200
     * @param mixed $contentId
1201
     * @param int $status
1202
     */
1203
    protected function setContentStatus($contentId, $status)
1204
    {
1205
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
1206
        $query = $this->handler->createUpdateQuery();
1207
        $query->update(
1208
            'ezcontentobject'
1209
        )->set(
1210
            $this->handler->quoteColumn('status'),
1211
            $query->bindValue($status, null, \PDO::PARAM_INT)
1212
        )->where(
1213
            $query->expr->eq(
1214
                $this->handler->quoteColumn('id'),
1215
                $query->bindValue($contentId, null, \PDO::PARAM_INT)
1216
            )
1217
        );
1218
        $query->prepare()->execute();
1219
    }
1220
1221
    /**
1222
     * Loads trash data specified by location ID.
1223
     *
1224
     * @param mixed $locationId
1225
     *
1226
     * @return array
1227
     */
1228 View Code Duplication
    public function loadTrashByLocation($locationId)
1229
    {
1230
        $query = $this->handler->createSelectQuery();
1231
        $query
1232
            ->select('*')
1233
            ->from($this->handler->quoteTable('ezcontentobject_trash'))
1234
            ->where(
1235
                $query->expr->eq(
1236
                    $this->handler->quoteColumn('node_id'),
1237
                    $query->bindValue($locationId)
1238
                )
1239
            );
1240
        $statement = $query->prepare();
1241
        $statement->execute();
1242
1243
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
1244
            return $row;
1245
        }
1246
1247
        throw new NotFound('trash', $locationId);
1248
    }
1249
1250
    /**
1251
     * List trashed items.
1252
     *
1253
     * @param int $offset
1254
     * @param int $limit
1255
     * @param array $sort
1256
     *
1257
     * @return array
1258
     */
1259
    public function listTrashed($offset, $limit, array $sort = null)
1260
    {
1261
        $query = $this->handler->createSelectQuery();
1262
        $query
1263
            ->select('*')
1264
            ->from($this->handler->quoteTable('ezcontentobject_trash'));
1265
1266
        $sort = $sort ?: array();
1267
        foreach ($sort as $condition) {
1268
            $sortDirection = $condition->direction === Query::SORT_ASC ? SelectQuery::ASC : SelectQuery::DESC;
1269
            switch (true) {
1270
                case $condition instanceof SortClause\Location\Depth:
1271
                    $query->orderBy('depth', $sortDirection);
1272
                    break;
1273
1274
                case $condition instanceof SortClause\Location\Path:
1275
                    $query->orderBy('path_string', $sortDirection);
1276
                    break;
1277
1278
                case $condition instanceof SortClause\Location\Priority:
1279
                    $query->orderBy('priority', $sortDirection);
1280
                    break;
1281
1282
                default:
1283
                    // Only handle location related sort clauses. The others
1284
                    // require data aggregation which is not sensible here.
1285
                    // Since also criteria are yet ignored, because they are
1286
                    // simply not used yet in eZ Publish, we skip that for now.
1287
                    throw new RuntimeException('Unhandled sort clause: ' . get_class($condition));
1288
            }
1289
        }
1290
1291
        if ($limit !== null) {
1292
            $query->limit($limit, $offset);
1293
        }
1294
1295
        $statement = $query->prepare();
1296
        $statement->execute();
1297
1298
        $rows = array();
1299
        while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
1300
            $rows[] = $row;
1301
        }
1302
1303
        return $rows;
1304
    }
1305
1306
    /**
1307
     * Removes every entries in the trash.
1308
     * Will NOT remove associated content objects nor attributes.
1309
     *
1310
     * Basically truncates ezcontentobject_trash table.
1311
     */
1312
    public function cleanupTrash()
1313
    {
1314
        $query = $this->handler->createDeleteQuery();
1315
        $query->deleteFrom('ezcontentobject_trash');
1316
        $query->prepare()->execute();
1317
    }
1318
1319
    /**
1320
     * Removes trashed element identified by $id from trash.
1321
     * Will NOT remove associated content object nor attributes.
1322
     *
1323
     * @param int $id The trashed location Id
1324
     */
1325
    public function removeElementFromTrash($id)
1326
    {
1327
        $query = $this->handler->createDeleteQuery();
1328
        $query
1329
            ->deleteFrom('ezcontentobject_trash')
1330
            ->where(
1331
                $query->expr->eq(
1332
                    $this->handler->quoteColumn('node_id'),
1333
                    $query->bindValue($id, null, \PDO::PARAM_INT)
1334
                )
1335
            );
1336
        $query->prepare()->execute();
1337
    }
1338
1339
    /**
1340
     * Set section on all content objects in the subtree.
1341
     *
1342
     * @param mixed $pathString
1343
     * @param mixed $sectionId
1344
     *
1345
     * @return bool
1346
     */
1347
    public function setSectionForSubtree($pathString, $sectionId)
1348
    {
1349
        $query = $this->handler->createUpdateQuery();
1350
1351
        $subSelect = $query->subSelect();
1352
        $subSelect
1353
            ->select($this->handler->quoteColumn('contentobject_id'))
1354
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
1355
            ->where(
1356
                $subSelect->expr->like(
1357
                    $this->handler->quoteColumn('path_string'),
1358
                    $subSelect->bindValue($pathString . '%')
1359
                )
1360
            );
1361
1362
        $query
1363
            ->update($this->handler->quoteTable('ezcontentobject'))
1364
            ->set(
1365
                $this->handler->quoteColumn('section_id'),
1366
                $query->bindValue($sectionId)
1367
            )
1368
            ->where(
1369
                $query->expr->in(
1370
                    $this->handler->quoteColumn('id'),
1371
                    $subSelect
1372
                )
1373
            );
1374
        $query->prepare()->execute();
1375
    }
1376
1377
    /**
1378
     * Returns how many locations given content object identified by $contentId has.
1379
     *
1380
     * @param int $contentId
1381
     *
1382
     * @return int
1383
     */
1384
    public function countLocationsByContentId($contentId)
1385
    {
1386
        $q = $this->handler->createSelectQuery();
1387
        $q
1388
            ->select(
1389
                $q->alias($q->expr->count('*'), 'count')
1390
            )
1391
            ->from($this->handler->quoteTable('ezcontentobject_tree'))
1392
            ->where(
1393
                $q->expr->eq(
1394
                    $this->handler->quoteColumn('contentobject_id'),
1395
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
1396
                )
1397
            );
1398
        $stmt = $q->prepare();
1399
        $stmt->execute();
1400
        $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
1401
1402
        return (int)$res[0]['count'];
1403
    }
1404
1405
    /**
1406
     * Changes main location of content identified by given $contentId to location identified by given $locationId.
1407
     *
1408
     * Updates ezcontentobject_tree table for the given $contentId and eznode_assignment table for the given
1409
     * $contentId, $parentLocationId and $versionNo
1410
     *
1411
     * @param mixed $contentId
1412
     * @param mixed $locationId
1413
     * @param mixed $versionNo version number, needed to update eznode_assignment table
1414
     * @param mixed $parentLocationId parent location of location identified by $locationId, needed to update
1415
     *        eznode_assignment table
1416
     */
1417
    public function changeMainLocation($contentId, $locationId, $versionNo, $parentLocationId)
1418
    {
1419
        // Update ezcontentobject_tree table
1420
        $q = $this->handler->createUpdateQuery();
1421
        $q->update(
1422
            $this->handler->quoteTable('ezcontentobject_tree')
1423
        )->set(
1424
            $this->handler->quoteColumn('main_node_id'),
1425
            $q->bindValue($locationId, null, \PDO::PARAM_INT)
1426
        )->where(
1427
            $q->expr->eq(
1428
                $this->handler->quoteColumn('contentobject_id'),
1429
                $q->bindValue($contentId, null, \PDO::PARAM_INT)
1430
            )
1431
        );
1432
        $q->prepare()->execute();
1433
1434
        // Erase is_main in eznode_assignment table
1435
        $q = $this->handler->createUpdateQuery();
1436
        $q->update(
1437
            $this->handler->quoteTable('eznode_assignment')
1438
        )->set(
1439
            $this->handler->quoteColumn('is_main'),
1440
            $q->bindValue(0, null, \PDO::PARAM_INT)
1441
        )->where(
1442
            $q->expr->lAnd(
1443
                $q->expr->eq(
1444
                    $this->handler->quoteColumn('contentobject_id'),
1445
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
1446
                ),
1447
                $q->expr->eq(
1448
                    $this->handler->quoteColumn('contentobject_version'),
1449
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
1450
                ),
1451
                $q->expr->neq(
1452
                    $this->handler->quoteColumn('parent_node'),
1453
                    $q->bindValue($parentLocationId, null, \PDO::PARAM_INT)
1454
                )
1455
            )
1456
        );
1457
        $q->prepare()->execute();
1458
1459
        // Set new is_main in eznode_assignment table
1460
        $q = $this->handler->createUpdateQuery();
1461
        $q->update(
1462
            $this->handler->quoteTable('eznode_assignment')
1463
        )->set(
1464
            $this->handler->quoteColumn('is_main'),
1465
            $q->bindValue(1, null, \PDO::PARAM_INT)
1466
        )->where(
1467
            $q->expr->lAnd(
1468
                $q->expr->eq(
1469
                    $this->handler->quoteColumn('contentobject_id'),
1470
                    $q->bindValue($contentId, null, \PDO::PARAM_INT)
1471
                ),
1472
                $q->expr->eq(
1473
                    $this->handler->quoteColumn('contentobject_version'),
1474
                    $q->bindValue($versionNo, null, \PDO::PARAM_INT)
1475
                ),
1476
                $q->expr->eq(
1477
                    $this->handler->quoteColumn('parent_node'),
1478
                    $q->bindValue($parentLocationId, null, \PDO::PARAM_INT)
1479
                )
1480
            )
1481
        );
1482
        $q->prepare()->execute();
1483
    }
1484
}
1485