Completed
Push — master ( a6387c...f7252d )
by André
23:52 queued 09:38
created

DoctrineDatabase::isHiddenByParent()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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