Completed
Push — master ( 4af5d3...2fef72 )
by André
38:49 queued 20:27
created

generateLanguageMaskFromLanguageCodes()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 17

Duplication

Lines 17
Ratio 100 %

Importance

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