Completed
Push — ezp-28439 ( b6528d )
by
unknown
19:13
created

DoctrineDatabase   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 1475
Duplicated Lines 6.64 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 98
loc 1475
c 0
b 0
f 0
rs 3.5897
wmc 76
lcom 1
cbo 11

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