Completed
Push — master ( 1de9b7...830752 )
by Kristof
38:46 queued 24:09
created

Kunstmaan/NodeBundle/Repository/NodeRepository.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Kunstmaan\NodeBundle\Repository;
4
5
use Gedmo\Tree\Entity\Repository\NestedTreeRepository;
6
use Kunstmaan\AdminBundle\Entity\BaseUser;
7
use Kunstmaan\AdminBundle\Helper\Security\Acl\AclHelper;
8
use Kunstmaan\AdminBundle\Helper\Security\Acl\AclNativeHelper;
9
use Kunstmaan\AdminBundle\Helper\Security\Acl\Permission\PermissionDefinition;
10
use Kunstmaan\NodeBundle\Entity\HasNodeInterface;
11
use Kunstmaan\NodeBundle\Entity\Node;
12
use Kunstmaan\NodeBundle\Entity\NodeTranslation;
13
use Kunstmaan\NodeBundle\Entity\NodeVersion;
14
use Kunstmaan\NodeBundle\Helper\HiddenFromNavInterface;
15
use Kunstmaan\UtilitiesBundle\Helper\ClassLookup;
16
17
/**
18
 * NodeRepository
19
 */
20
class NodeRepository extends NestedTreeRepository
21
{
22
    /**
23
     * @param string    $lang                 The locale
24
     * @param string    $permission           The permission (read, write, ...)
25
     * @param AclHelper $aclHelper            The acl helper
26
     * @param bool      $includeHiddenFromNav include the hiddenfromnav nodes
27
     *                                        or not
28
     *
29
     * @return Node[]
30
     */
31
    public function getTopNodes(
32
        $lang,
33
        $permission,
34
        AclHelper $aclHelper,
35
        $includeHiddenFromNav = false
36
    ) {
37
        $result = $this->getChildNodes(
38
            null,
39
            $lang,
40
            $permission,
41
            $aclHelper,
42
            $includeHiddenFromNav
43
        );
44
45
        return $result;
46
    }
47
48
    /**
49
     * @param int|null  $parentId             The parent node id
50
     * @param string    $lang                 The locale
51
     * @param string    $permission           The permission (read, write, ...)
52
     * @param AclHelper $aclHelper            The acl helper
53
     * @param bool      $includeHiddenFromNav Include nodes hidden from
54
     *                                        navigation or not
55
     * @param Node      $rootNode             Root node of the current tree
56
     *
57
     * @return Node[]
58
     */
59
    public function getChildNodes(
60
        $parentId,
61
        $lang,
62
        $permission,
63
        AclHelper $aclHelper,
64
        $includeHiddenFromNav = false,
65
        $includeHiddenWithInternalName = false,
66
        $rootNode = null
67
    ) {
68
        $qb = $this->createQueryBuilder('b')
69
            ->select('b', 't', 'v')
70
            ->leftJoin('b.nodeTranslations', 't', 'WITH', 't.lang = :lang')
71
            ->leftJoin(
72
                't.publicNodeVersion',
73
                'v',
74
                'WITH',
75
                't.publicNodeVersion = v.id'
76
            )
77
            ->where('b.deleted = 0')
78
            ->setParameter('lang', $lang)
79
            ->addOrderBy('t.weight', 'ASC')
80
            ->addOrderBy('t.title', 'ASC');
81
82
        if (!$includeHiddenFromNav) {
83
            if ($includeHiddenWithInternalName) {
84
                $qb->andWhere(
85
                    '(b.hiddenFromNav != true OR b.internalName IS NOT NULL)'
86
                );
87
            } else {
88
                $qb->andWhere('b.hiddenFromNav != true');
89
            }
90
        }
91
92 View Code Duplication
        if (is_null($parentId)) {
93
            $qb->andWhere('b.parent is NULL');
94
        } elseif ($parentId !== false) {
95
            $qb->andWhere('b.parent = :parent')
96
                ->setParameter('parent', $parentId);
97
        }
98
        if ($rootNode) {
99
            $qb->andWhere('b.lft >= :left')
100
                ->andWhere('b.rgt <= :right')
101
                ->setParameter('left', $rootNode->getLeft())
102
                ->setParameter('right', $rootNode->getRight());
103
        }
104
105
        $query = $aclHelper->apply(
106
            $qb,
107
            new PermissionDefinition(array($permission))
108
        );
109
110
        return $query->getResult();
111
    }
112
113
    /**
114
     * @param HasNodeInterface $hasNode
115
     *
116
     * @return Node|null
117
     */
118
    public function getNodeFor(HasNodeInterface $hasNode)
119
    {
120
        /* @var NodeVersion $nodeVersion */
121
        $nodeVersion = $this->getEntityManager()->getRepository(
122
            'KunstmaanNodeBundle:NodeVersion'
123
        )->getNodeVersionFor(
124
            $hasNode
125
        );
126
        if (!is_null($nodeVersion)) {
127
            /* @var NodeTranslation $nodeTranslation */
128
            $nodeTranslation = $nodeVersion->getNodeTranslation();
129
            if (!is_null($nodeTranslation)) {
130
                return $nodeTranslation->getNode();
131
            }
132
        }
133
134
        return null;
135
    }
136
137
    /**
138
     * @param int    $id         The id
139
     * @param string $entityName The class name
140
     *
141
     * @return Node|null
142
     */
143
    public function getNodeForIdAndEntityname($id, $entityName)
144
    {
145
        /* @var NodeVersion $nodeVersion */
146
        $nodeVersion = $this->getEntityManager()->getRepository(
147
            'KunstmaanNodeBundle:NodeVersion'
148
        )->findOneBy(
149
            array('refId' => $id, 'refEntityName' => $entityName)
150
        );
151
        if ($nodeVersion) {
152
            return $nodeVersion->getNodeTranslation()->getNode();
153
        }
154
155
        return null;
156
    }
157
158
    /**
159
     * @param Node   $parentNode The parent node (may be null)
160
     * @param string $slug       The slug
161
     *
162
     * @return Node|null
163
     */
164
    public function getNodeForSlug(Node $parentNode, $slug)
165
    {
166
        $slugParts = explode('/', $slug);
167
        $result = null;
168
        foreach ($slugParts as $slugPart) {
169
            if ($parentNode) {
170
                if ($r = $this->findOneBy(
171
                    array(
172
                        'slug' => $slugPart,
173
                        'parent.parent' => $parentNode->getId(),
174
                    )
175
                )
176
                ) {
177
                    $result = $r;
178
                }
179
            } else {
180
                if ($r = $this->findOneBy(array('slug' => $slugPart))) {
181
                    $result = $r;
182
                }
183
            }
184
        }
185
186
        return $result;
187
    }
188
189
    /**
190
     * @param HasNodeInterface $hasNode      The object to link to
191
     * @param string           $lang         The locale
192
     * @param BaseUser         $owner        The user
193
     * @param string           $internalName The internal name (may be null)
194
     *
195
     * @throws \InvalidArgumentException
196
     *
197
     * @return Node
198
     */
199
    public function createNodeFor(
200
        HasNodeInterface $hasNode,
201
        $lang,
202
        BaseUser $owner,
203
        $internalName = null
204
    ) {
205
        $em = $this->getEntityManager();
206
        $node = new Node();
207
        $node->setRef($hasNode);
208
        if (!$hasNode->getId() > 0) {
209
            throw new \InvalidArgumentException(
210
                'the entity of class '.
211
                $node->getRefEntityName(
212
                ).' has no id, maybe you forgot to flush first'
213
            );
214
        }
215
        $node->setDeleted(false);
216
        $node->setInternalName($internalName);
217
        $parent = $hasNode->getParent();
218
        if ($parent) {
219
            /* @var NodeVersion $parentNodeVersion */
220
            $parentNodeVersion = $em->getRepository(
221
                'KunstmaanNodeBundle:NodeVersion'
222
            )->findOneBy(
223
                array(
224
                    'refId' => $parent->getId(),
225
                    'refEntityName' => ClassLookup::getClass($parent),
226
                )
227
            );
228
            if ($parentNodeVersion) {
229
                $node->setParent(
230
                    $parentNodeVersion->getNodeTranslation()->getNode()
231
                );
232
            }
233
        }
234
        if ($hasNode instanceof HiddenFromNavInterface) {
235
            $node->setHiddenFromNav($hasNode->isHiddenFromNav());
236
        }
237
        $em->persist($node);
238
        $em->flush();
239
        $em->refresh($node);
240
        $em->getRepository('KunstmaanNodeBundle:NodeTranslation')
241
            ->createNodeTranslationFor(
242
                $hasNode,
243
                $lang,
244
                $node,
245
                $owner
246
            );
247
248
        return $node;
249
    }
250
251
    /**
252
     * Get all the information needed to build a menu tree with one query.
253
     * We only fetch the fields we need, instead of fetching full objects to
254
     * limit the memory usage.
255
     *
256
     * @param string          $lang                 The locale
257
     * @param string          $permission           The permission (read,
258
     *                                              write, ...)
259
     * @param AclNativeHelper $aclNativeHelper      The acl helper
260
     * @param bool            $includeHiddenFromNav Include nodes hidden from
261
     *                                              navigation or not
262
     * @param Node            $rootNode             The root node of the
263
     *                                              current site
264
     *
265
     * @return array
266
     */
267
    public function getAllMenuNodes(
268
        $lang,
269
        $permission,
270
        AclNativeHelper $aclNativeHelper,
271
        $includeHiddenFromNav = false,
272
        Node $rootNode = null
273
    ) {
274
        $connection = $this->_em->getConnection();
275
        $qb = $connection->createQueryBuilder();
276
        $databasePlatformName = $connection->getDatabasePlatform()->getName();
277
        $createIfStatement = function (
278
            $expression,
279
            $trueValue,
280
            $falseValue
281
        ) use ($databasePlatformName) {
282
            switch ($databasePlatformName) {
283
                case 'sqlite':
284
                    $statement = 'CASE WHEN %s THEN %s ELSE %s END';
285
286
                    break;
287
288
                default:
289
                    $statement = 'IF(%s, %s, %s)';
290
            }
291
292
            return sprintf($statement, $expression, $trueValue, $falseValue);
293
        };
294
295
        $sql = <<<SQL
296
n.id, n.parent_id AS parent, t.url, t.id AS nt_id,
297
{$createIfStatement('t.weight IS NULL', 'v.weight', 't.weight')} AS weight,
298
{$createIfStatement('t.title IS NULL', 'v.title', 't.title')} AS title,
299
{$createIfStatement('t.online IS NULL', '0', 't.online')} AS online,
300
n.hidden_from_nav AS hidden,
301
n.ref_entity_name AS ref_entity_name
302
SQL;
303
304
        $qb->select($sql)
305
            ->from('kuma_nodes', 'n')
306
            ->leftJoin(
307
                'n',
308
                'kuma_node_translations',
309
                't',
310
                '(t.node_id = n.id AND t.lang = :lang)'
311
            )
312
            ->leftJoin(
313
                'n',
314
                'kuma_node_translations',
315
                'v',
316
                '(v.node_id = n.id AND v.lang <> :lang)'
317
            )
318
            ->where('n.deleted = 0')
319
            ->addGroupBy('n.id')
320
            ->addOrderBy('t.weight', 'ASC')
321
            ->addOrderBy('t.title', 'ASC');
322
323
        if (!$includeHiddenFromNav) {
324
            $qb->andWhere('n.hidden_from_nav <> 0');
325
        }
326
327
        if (!is_null($rootNode)) {
328
            $qb->andWhere('n.lft >= :left')
329
                ->andWhere('n.rgt <= :right');
330
        }
331
332
        $permissionDef = new PermissionDefinition(array($permission));
333
        $permissionDef->setEntity('Kunstmaan\NodeBundle\Entity\Node');
334
        $permissionDef->setAlias('n');
335
        $qb = $aclNativeHelper->apply($qb, $permissionDef);
336
337
        $stmt = $this->_em->getConnection()->prepare($qb->getSQL());
338
        $stmt->bindValue(':lang', $lang);
339
        if (!is_null($rootNode)) {
340
            $stmt->bindValue(':left', $rootNode->getLeft());
341
            $stmt->bindValue(':right', $rootNode->getRight());
342
        }
343
        $stmt->execute();
344
345
        return $stmt->fetchAll();
346
    }
347
348
    /**
349
     * Get all parents of a given node. We can go multiple levels up.
350
     *
351
     * @param Node   $node
352
     * @param string $lang
0 ignored issues
show
Should the type for parameter $lang not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
353
     *
354
     * @return Node[]
355
     */
356 View Code Duplication
    public function getAllParents(Node $node = null, $lang = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
357
    {
358
        if (is_null($node)) {
359
            return array();
360
        }
361
362
        $qb = $this->createQueryBuilder('node');
363
364
        // Directly hydrate the nodeTranslation and nodeVersion
365
        $qb->select('node', 't', 'v')
366
            ->innerJoin('node.nodeTranslations', 't')
367
            ->leftJoin(
368
                't.publicNodeVersion',
369
                'v',
370
                'WITH',
371
                't.publicNodeVersion = v.id'
372
            )
373
            ->where('node.deleted = 0');
374
375
        if ($lang) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
376
            $qb->andWhere('t.lang = :lang')
377
                ->setParameter('lang', $lang);
378
        }
379
380
        $qb->andWhere(
381
            $qb->expr()->andX(
382
                $qb->expr()->lte('node.lft', $node->getLeft()),
383
                $qb->expr()->gte('node.rgt', $node->getRight())
384
            )
385
        );
386
387
        $qb->addOrderBy('node.lft', 'ASC');
388
389
        return $qb->getQuery()->getResult();
390
    }
391
392
    /**
393
     * Get the root node of a given node.
394
     *
395
     * @param Node   $node
396
     * @param string $lang
397
     *
398
     * @return Node
399
     */
400 View Code Duplication
    public function getRootNodeFor(Node $node = null, $lang = null)
401
    {
402
        if (is_null($node)) {
403
            return null;
404
        }
405
406
        $qb = $this->createQueryBuilder('node');
407
408
        // Directly hydrate the nodeTranslation and nodeVersion
409
        $qb->select('node', 't', 'v')
410
            ->innerJoin('node.nodeTranslations', 't')
411
            ->leftJoin(
412
                't.publicNodeVersion',
413
                'v',
414
                'WITH',
415
                't.publicNodeVersion = v.id'
416
            )
417
            ->where('node.deleted = 0')
418
            ->andWhere('node.parent IS NULL');
419
420
        if ($lang) {
421
            $qb->andWhere('t.lang = :lang')
422
                ->setParameter('lang', $lang);
423
        }
424
425
        $qb->andWhere(
426
            $qb->expr()->andX(
427
                $qb->expr()->lte('node.lft', $node->getLeft()),
428
                $qb->expr()->gte('node.rgt', $node->getRight())
429
            )
430
        );
431
432
        return $qb->getQuery()->getOneOrNullResult();
433
    }
434
435
    /**
436
     * @return Node[]
437
     */
438
    public function getAllTopNodes()
439
    {
440
        $qb = $this->createQueryBuilder('b')
441
            ->select('b', 't', 'v')
442
            ->leftJoin('b.nodeTranslations', 't')
443
            ->leftJoin(
444
                't.publicNodeVersion',
445
                'v',
446
                'WITH',
447
                't.publicNodeVersion = v.id'
448
            )
449
            ->where('b.deleted = 0')
450
            ->andWhere('b.parent IS NULL');
451
452
        $result = $qb->getQuery()->getResult();
453
454
        return $result;
455
    }
456
457
    /**
458
     * Get an array of Nodes based on the internal name.
459
     *
460
     * @param string        $internalName   The internal name of the node
461
     * @param string        $lang           The locale
462
     * @param int|null|bool $parentId       The parent id
463
     * @param bool          $includeOffline Include offline nodes
464
     *
465
     * @return Node[]
466
     */
467
    public function getNodesByInternalName(
468
        $internalName,
469
        $lang,
470
        $parentId = false,
471
        $includeOffline = false
472
    ) {
473
        $qb = $this->createQueryBuilder('n')
474
            ->select('n', 't', 'v')
475
            ->innerJoin('n.nodeTranslations', 't')
476
            ->leftJoin(
477
                't.publicNodeVersion',
478
                'v',
479
                'WITH',
480
                't.publicNodeVersion = v.id'
481
            )
482
            ->where('n.deleted = 0')
483
            ->andWhere('n.internalName = :internalName')
484
            ->setParameter('internalName', $internalName)
485
            ->andWhere('t.lang = :lang')
486
            ->setParameter('lang', $lang)
487
            ->addOrderBy('t.weight', 'ASC')
488
            ->addOrderBy('t.title', 'ASC');
489
490
        if (!$includeOffline) {
491
            $qb->andWhere('t.online = true');
492
        }
493
494 View Code Duplication
        if (is_null($parentId)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
495
            $qb->andWhere('n.parent is NULL');
496
        } elseif ($parentId === false) {
0 ignored issues
show
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
497
            // Do nothing
498
        } else {
499
            $qb->andWhere('n.parent = :parent')
500
                ->setParameter('parent', $parentId);
501
        }
502
503
        $query = $qb->getQuery();
504
505
        return $query->getResult();
506
    }
507
508
    /**
509
     * Get a single node by internal name.
510
     *
511
     * @param string $internalName The internal name of the node
512
     *
513
     * @return Node
514
     */
515
    public function getNodeByInternalName($internalName)
516
    {
517
        $qb = $this->createQueryBuilder('n')
518
            ->select('n')
519
            ->where('n.deleted = 0')
520
            ->andWhere('n.internalName = :internalName')
521
            ->setParameter('internalName', $internalName);
522
523
        return $qb->getQuery()->getOneOrNullResult();
524
    }
525
526
    /**
527
     * Finds all different page classes currently registered as nodes
528
     *
529
     * @return string[]
530
     */
531
    public function findAllDistinctPageClasses()
532
    {
533
        $qb = $this->createQueryBuilder('n')
534
            ->select('n.refEntityName')
535
            ->where('n.deleted = 0')
536
            ->distinct(true);
537
538
        return $qb->getQuery()->getArrayResult();
539
    }
540
}
541