Completed
Push — next ( 8ef386...43bb99 )
by Thomas
11:01 queued 05:16
created

MainRenderer::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 22
nc 1
nop 3
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace Oc\Menu\Renderer;
4
5
use Knp\Menu\ItemInterface;
6
use Knp\Menu\Matcher\MatcherInterface;
7
use Knp\Menu\Renderer\Renderer;
8
use Knp\Menu\Renderer\RendererInterface;
9
10
/**
11
 * Class MainRenderer
12
 */
13
class MainRenderer extends Renderer implements RendererInterface
14
{
15
    protected $matcher;
16
    protected $defaultOptions;
17
18
    /**
19
     * @param MatcherInterface $matcher
20
     * @param array            $defaultOptions
21
     * @param string           $charset
22
     */
23
    public function __construct(MatcherInterface $matcher, array $defaultOptions = array(), $charset = null)
24
    {
25
        $this->matcher = $matcher;
26
        $this->defaultOptions = array_merge(array(
27
            'depth' => null,
28
            'matchingDepth' => null,
29
            'currentAsLink' => true,
30
            'currentClass' => 'navigation-item--current',
31
            'ancestorClass' => null,
32
            'listClass' => 'navigation',
33
            'listLevelPrefixClass' => 'navigation--level-',
34
            'itemClass' => 'navigation__item navigation-item',
35
            'textClass' => 'navigation-item__text',
36
            'linkClass' => 'navigation-item__link',
37
            'firstClass' => 'navigation-item--first',
38
            'lastClass' => 'navigation-item--last',
39
            'compressed' => false,
40
            'allow_safe_labels' => false,
41
            'clear_matcher' => true,
42
            'leaf_class' => null,
43
            'branch_class' => null,
44
        ), $defaultOptions);
45
46
        parent::__construct($charset);
47
    }
48
49
    public function render(ItemInterface $item, array $options = array())
50
    {
51
        $options = array_merge($this->defaultOptions, $options);
52
53
        $childrenClass = (array) $item->getChildrenAttribute('class');
54
55
        if ($options['listClass']) {
56
            $childrenClass[] = $options['listClass'];
57
        }
58
59
        if ($options['listLevelPrefixClass']) {
60
            $childrenClass[] = $options['listLevelPrefixClass'] . $item->getLevel();
61
        }
62
63
        $childrenAttributes = $item->getChildrenAttributes();
64
        $childrenAttributes['class'] = implode(' ', $childrenClass);
65
66
        $html = $this->renderList($item, $childrenAttributes, $options);
67
68
        if ($options['clear_matcher']) {
69
            $this->matcher->clear();
70
        }
71
72
        return $html;
73
    }
74
75
    protected function renderList(ItemInterface $item, array $attributes, array $options)
76
    {
77
        /**
78
         * Return an empty string if any of the following are true:
79
         *   a) The menu has no children eligible to be displayed
80
         *   b) The depth is 0
81
         *   c) This menu item has been explicitly set to hide its children
82
         */
83
        if (!$item->hasChildren() || 0 === $options['depth'] || !$item->getDisplayChildren()) {
84
            return '';
85
        }
86
87
        $html = $this->format('<ul' . $this->renderHtmlAttributes($attributes) . '>', 'ul', $item->getLevel(), $options);
88
        $html .= $this->renderChildren($item, $options);
89
        $html .= $this->format('</ul>', 'ul', $item->getLevel(), $options);
90
91
        return $html;
92
    }
93
94
    /**
95
     * Renders all of the children of this menu.
96
     *
97
     * This calls ->renderItem() on each menu item, which instructs each
98
     * menu item to render themselves as an <li> tag (with nested ul if it
99
     * has children).
100
     * This method updates the depth for the children.
101
     *
102
     * @param ItemInterface $item
103
     * @param array         $options The options to render the item.
104
     *
105
     * @return string
106
     */
107
    protected function renderChildren(ItemInterface $item, array $options)
108
    {
109
        // render children with a depth - 1
110
        if (null !== $options['depth']) {
111
            $options['depth'] = $options['depth'] - 1;
112
        }
113
114
        if (null !== $options['matchingDepth'] && $options['matchingDepth'] > 0) {
115
            $options['matchingDepth'] = $options['matchingDepth'] - 1;
116
        }
117
118
        $html = '';
119
        foreach ($item->getChildren() as $child) {
120
            $html .= $this->renderItem($child, $options);
121
        }
122
123
        return $html;
124
    }
125
126
    /**
127
     * Called by the parent menu item to render this menu.
128
     *
129
     * This renders the li tag to fit into the parent ul as well as its
130
     * own nested ul tag if this menu item has children
131
     *
132
     * @param ItemInterface $item
133
     * @param array         $options The options to render the item
134
     *
135
     * @return string
136
     */
137
    protected function renderItem(ItemInterface $item, array $options)
138
    {
139
        // if we don't have access or this item is marked to not be shown
140
        if (!$item->isDisplayed()) {
141
            return '';
142
        }
143
144
        // create an array than can be imploded as a class list
145
        $class = (array) $item->getAttribute('class');
146
147
        if ($options['itemClass']) {
148
            $class[] = $options['itemClass'];
149
        }
150
151
        if ($this->matcher->isCurrent($item)) {
152
            $class[] = $options['currentClass'];
153
        } elseif ($this->matcher->isAncestor($item, $options['matchingDepth'])) {
154
            $class[] = $options['ancestorClass'];
155
        }
156
157
        if ($item->actsLikeFirst()) {
158
            $class[] = $options['firstClass'];
159
        }
160
        if ($item->actsLikeLast()) {
161
            $class[] = $options['lastClass'];
162
        }
163
164
        if ($item->hasChildren() && $options['depth'] !== 0) {
165
            if (null !== $options['branch_class'] && $item->getDisplayChildren()) {
166
                $class[] = $options['branch_class'];
167
            }
168
        } elseif (null !== $options['leaf_class']) {
169
            $class[] = $options['leaf_class'];
170
        }
171
172
        // retrieve the attributes and put the final class string back on it
173
        $attributes = $item->getAttributes();
174
        if (!empty($class)) {
175
            $attributes['class'] = implode(' ', $class);
176
        }
177
178
        // opening li tag
179
        $html = $this->format('<li' . $this->renderHtmlAttributes($attributes) . '>', 'li', $item->getLevel(), $options);
180
181
        // render the text/link inside the li tag
182
        //$html .= $this->format($item->getUri() ? $item->renderLink() : $item->renderLabel(), 'link', $item->getLevel());
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
183
        $html .= $this->renderLink($item, $options);
184
185
        // renders the embedded ul
186
        $childrenClass = (array) $item->getChildrenAttribute('class');
187
        if ($options['listClass']) {
188
            $childrenClass[] = $options['listClass'];
189
        }
190
191
        if ($options['listLevelPrefixClass']) {
192
            $childrenClass[] = $options['listLevelPrefixClass'] . $item->getLevel();
193
        }
194
195
        $childrenAttributes = $item->getChildrenAttributes();
196
        $childrenAttributes['class'] = implode(' ', $childrenClass);
197
198
        $html .= $this->renderList($item, $childrenAttributes, $options);
199
200
        // closing li tag
201
        $html .= $this->format('</li>', 'li', $item->getLevel(), $options);
202
203
        return $html;
204
    }
205
206
    /**
207
     * Renders the link in a a tag with link attributes or
208
     * the label in a span tag with label attributes
209
     *
210
     * Tests if item has a an uri and if not tests if it's
211
     * the current item and if the text has to be rendered
212
     * as a link or not.
213
     *
214
     * @param ItemInterface $item    The item to render the link or label for
215
     * @param array         $options The options to render the item
216
     *
217
     * @return string
218
     */
219
    protected function renderLink(ItemInterface $item, array $options = array())
220
    {
221
        if ($item->getUri() && (!$item->isCurrent() || $options['currentAsLink'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $item->isCurrent() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
222
            $text = $this->renderLinkElement($item, $options);
223
        } else {
224
            $text = $this->renderSpanElement($item, $options);
225
        }
226
227
        return $this->format($text, 'link', $item->getLevel(), $options);
228
    }
229
230 View Code Duplication
    protected function renderLinkElement(ItemInterface $item, array $options)
0 ignored issues
show
Duplication introduced by
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...
231
    {
232
        $childrenClass = (array) $item->getChildrenAttribute('class');
233
234
        if ($options['linkClass']) {
235
            $childrenClass[] = $options['linkClass'];
236
        }
237
238
        $childrenAttributes = $item->getChildrenAttributes();
239
        $childrenAttributes['class'] = implode(' ', $childrenClass);
240
241
        return sprintf('<a href="%s"%s>%s</a>', $this->escape($item->getUri()), $this->renderHtmlAttributes($childrenAttributes), $this->renderLabel($item, $options));
242
    }
243
244 View Code Duplication
    protected function renderSpanElement(ItemInterface $item, array $options)
0 ignored issues
show
Duplication introduced by
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...
245
    {
246
        $childrenClass = (array) $item->getChildrenAttribute('class');
247
248
        if ($options['textClass']) {
249
            $childrenClass[] = $options['textClass'];
250
        }
251
252
        $childrenAttributes = $item->getChildrenAttributes();
253
        $childrenAttributes['class'] = implode(' ', $childrenClass);
254
255
        return sprintf('<span%s>%s</span>', $this->renderHtmlAttributes($childrenAttributes), $this->renderLabel($item, $options));
256
    }
257
258
    protected function renderLabel(ItemInterface $item, array $options)
259
    {
260
        if ($options['allow_safe_labels'] && $item->getExtra('safe_label', false)) {
261
            return $item->getLabel();
262
        }
263
264
        return $this->escape($item->getLabel());
265
    }
266
267
    /**
268
     * If $this->renderCompressed is on, this will apply the necessary
269
     * spacing and line-breaking so that the particular thing being rendered
270
     * makes up its part in a fully-rendered and spaced menu.
271
     *
272
     * @param string  $html    The html to render in an (un)formatted way
273
     * @param string  $type    The type [ul,link,li] of thing being rendered
274
     * @param int $level
275
     * @param array   $options
276
     *
277
     * @return string
278
     */
279
    protected function format($html, $type, $level, array $options)
280
    {
281
        if ($options['compressed']) {
282
            return $html;
283
        }
284
285
        switch ($type) {
286
            case 'ul':
287
            case 'link':
288
                $spacing = $level * 4;
289
                break;
290
291
            case 'li':
292
                $spacing = $level * 4 - 2;
293
                break;
294
        }
295
296
        return str_repeat(' ', $spacing) . $html . "\n";
0 ignored issues
show
Bug introduced by
The variable $spacing 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...
297
    }
298
}
299