Completed
Push — EZP-31644 ( 2e0a1e...93bb44 )
by
unknown
19:12
created

DoctrineDatabase   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 1558
Duplicated Lines 6.42 %

Coupling/Cohesion

Components 2
Dependencies 14

Importance

Changes 0
Metric Value
dl 100
loc 1558
rs 0.8
c 0
b 0
f 0
wmc 83
lcom 2
cbo 14

38 Methods

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

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

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