Issues (41)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Builder.php (3 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
namespace Malezha\Menu;
3
4
use Illuminate\Contracts\Config\Repository;
5
use Illuminate\Contracts\Container\Container;
6
use Malezha\Menu\Contracts\Attributes as AttributesContract;
7
use Malezha\Menu\Contracts\Builder as BuilderContract;
8
use Malezha\Menu\Contracts\Element;
9
use Malezha\Menu\Contracts\ElementFactory;
10
use Malezha\Menu\Contracts\HasActiveAttributes;
11
use Malezha\Menu\Contracts\MenuRender;
12
use Malezha\Menu\Support\FromArrayBuilder;
13
use Malezha\Menu\Traits\HasActiveAttributes as TraitHasActiveAttributes;
14
use Malezha\Menu\Traits\HasAttributes;
15
16
/**
17
 * Class Builder
18
 * @package Malezha\Menu
19
 */
20
class Builder implements BuilderContract
21
{
22
    use HasAttributes, TraitHasActiveAttributes;
23
24
    /**
25
     * @var Container
26
     */
27
    protected $app;
28
29
    /**
30
     * @var array
31
     */
32
    protected $elements;
33
34
    /**
35
     * @var array
36
     */
37
    protected $indexes = [];
38
39
    /**
40
     * @var string
41
     */
42
    protected $type;
43
44
    /**
45
     * @var MenuRender
46
     */
47
    protected $viewFactory;
48
49
    /**
50
     * @var array
51
     */
52
    protected $config;
53
54
    /**
55
     * @var string
56
     */
57
    protected $view = null;
58
    
59
    /**
60
     * @inheritDoc
61
     */
62 28
    public function __construct(Container $container, AttributesContract $attributes, 
63
                                AttributesContract $activeAttributes, 
64
                                $type = self::UL, $view = null)
65
    {
66 28
        $this->app = $container;
67 28
        $this->type = $type;
68 28
        $this->attributes = $attributes;
69 28
        $this->elements = [];
70 28
        $this->activeAttributes = $activeAttributes;
71 28
        $this->viewFactory = $this->app->make(MenuRender::class);
72 28
        $this->config = $this->app->make(Repository::class)->get('menu');
73
        try {
74 28
            $this->setView($view);
75 28
        } catch (\Exception $e) {}
76 28
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81 21
    public function create($name, $type, callable $callback = null)
82
    {
83 21
        if ($this->has($name)) {
84 1
            throw new \RuntimeException("Duplicate menu key \"${name}\"");
85
        }
86
87
        $factory = $this->getFactory($type);
88
89
        if ($this->hasActiveAttributes($type)) {
90
            $factory->activeAttributes = clone $this->getActiveAttributes();
91
        }
92
93
        $result = call_if_callable($callback, $factory);
94
95
        if (! $result instanceof Element) {
96
            throw new \RuntimeException("Result of callback must be [" . Element::class . "]");
97
        }
98
99
        $this->saveItem($name, $result);
100
        
101
        return $result;
102
    }
103
104
    /**
105
     * @inheritDoc
106
     */
107
    public function insertBefore($name, callable $callback)
108
    {
109 2
        $prepared = $this->prepareInsert($name, $callback);
110 1
        $this->insert($this->indexes[$name], $prepared);
111 1
    }
112
113
    /**
114
     * @inheritDoc
115
     */
116
    public function insertAfter($name, callable $callback)
117
    {
118 2
        $prepared = $this->prepareInsert($name, $callback);
119 1
        $this->insert($this->indexes[$name] + 1, $prepared);
120 1
    }
121
122
    /**
123
     * @inheritDoc
124
     */
125
    public function has($name)
126
    {
127 24
        return array_key_exists($name, $this->elements);
128
    }
129
130
    /**
131
     * @inheritDoc
132
     */
133
    public function get($name, $default = null)
134
    {
135 3
        if ($this->has($name)) {
136 3
            return $this->elements[$name];
137
        }
138 1
        return $default;
139
    }
140
141
    /**
142
     * @inheritDoc
143
     */
144
    public function getByIndex($index, $default = null)
145
    {
146 1
        $key = array_search($index, $this->indexes);
147
        
148 1
        return $key === false ? $default : $this->get($key, $default);
149
    }
150
151
    /**
152
     * @inheritDoc
153
     */
154
    public function all()
155
    {
156 11
        return $this->elements;
157
    }
158
159
    /**
160
     * @inheritDoc
161
     */
162
    public function forget($name)
163
    {
164 1
        if ($this->has($name)) {
165 1
            unset($this->elements[$name]);
166
        }
167 1
    }
168
169
    /**
170
     * @inheritDoc
171
     */
172
    public function getType()
173
    {
174 10
        return $this->type;
175
    }
176
177
    /**
178
     * @inheritDoc
179
     */
180
    public function setType($type)
181
    {
182 1
        $this->type = (string) $type;
183 1
    }
184
185
    /**
186
     * @inheritDoc
187
     */
188
    public function render($renderView = null)
189
    {
190 9
        $view = $this->getRenderView($renderView);
191
192 9
        $minify = $this->config['minify'];
193
194 9
        $rendered = $this->viewFactory->make($view)->with([
195 9
            'menu' => $this,
196 9
            'renderView' => $renderView,
197 9
        ])->render();
198
        
199 9
        if ($minify) {
200 9
            $rendered = $this->minifyHtmlOutput($rendered);
201
        }
202
        
203 9
        return $rendered;
204
    }
205
206
    /**
207
     * @inheritDoc
208
     */
209
    public function getView()
210
    {
211 2
        return $this->view;
212
    }
213
214
    /**
215
     * @inheritDoc
216
     */
217
    public function setView($view)
218
    {
219 28
        if (!$this->viewFactory->exists($view)) {
220 28
            throw new \Exception('View not found');
221
        }
222
        
223 3
        $this->view = $view;
224 3
    }
225
226
    /**
227
     * Minify html
228
     *
229
     * @param string $html
230
     * @return string
231
     */
232
    protected function minifyHtmlOutput($html)
233
    {
234
        $search = array(
235 9
            '/\>[^\S]+/s', // strip whitespaces after tags, except space
236
            '/[^\S]+\</s', // strip whitespaces before tags, except space
237
            '/(\s)+/s'     // shorten multiple whitespace sequences
238
        );
239
240
        $replace = array(
241 9
            '>',
242
            '<',
243
            '\\1'
244
        );
245
246 9
        return preg_replace($search, $replace, $html);
247
    }
248
249
    /**
250
     * Get view for render
251
     * 
252
     * @param string $view
253
     * @return string
254
     */
255
    protected function getRenderView($view = null)
256
    {
257 9
        $renderView = $this->config['view'];
258
        
259 9
        if (!empty($this->view)) {
260 3
            $renderView = $this->view;
261
        }
262
        
263 9
        if (!empty($view) && $this->viewFactory->exists($view)) {
264 3
            $renderView = $view;
265
        }
266
        
267 9
        return $renderView;
268
    }
269
270
    /**
271
     * @param string $name
272
     * @param Element $item
273
     */
274
    protected function saveItem($name, $item)
275
    {
276 19
        $this->elements[$name] = $item;
277 19
        $this->indexes[$name] = count($this->elements) - 1;
278 19
    }
279
280
    /**
281
     * @param string $name
282
     * @param array $attributes
283
     * @param array $activeAttributes
284
     * @param \Closure|null $callback
285
     * @return BuilderContract
286
     */
287
    protected function builderFactory($name, $attributes = [], $activeAttributes = [], $callback = null)
288
    {
289 2
        $menu = $this->app->makeWith(BuilderContract::class, [
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Container\Container as the method makeWith() does only exist in the following implementations of said interface: Illuminate\Container\Container.

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...
290 2
            'container' => $this->app,
291 2
            'name' => $name,
292 2
            'activeAttributes' => $this->app->makeWith(AttributesContract::class,
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Container\Container as the method makeWith() does only exist in the following implementations of said interface: Illuminate\Container\Container.

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...
293 2
                ['attributes' => $activeAttributes]),
294 2
            'attributes' => $this->app->makeWith(AttributesContract::class,
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Container\Container as the method makeWith() does only exist in the following implementations of said interface: Illuminate\Container\Container.

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...
295 2
                ['attributes' => $attributes]),
296 2
            'view' => $this->getView(),
297
        ]);
298
299 2
        if (is_callable($callback)) {
300 2
            call_user_func($callback, $menu);
301
        }
302
303 2
        return $menu;
304
    }
305
306
    protected function rebuildIndexesArray()
307
    {
308 1
        $this->indexes = [];
309 1
        $iterator = 0;
310
311 1
        foreach ($this->elements as $key => $value) {
312 1
            $this->indexes[$key] = $iterator++;
313
        }
314 1
    }
315
316
    /**
317
     * @param string $name
318
     * @param \Closure $callback
319
     * @return array
320
     */
321
    protected function prepareInsert($name, $callback)
322
    {
323 3
        if (!$this->has($name)) {
324 1
            throw new \RuntimeException("Menu item \"${name}\" must be exists");
325
        }
326
327 2
        $forInsert = $this->builderFactory('tmp', [], [], $callback)->all();
328 2
        $diff = array_intersect_key($this->elements, $forInsert);
329
330 2
        if (count($diff) > 0) {
331 1
            throw new \RuntimeException('Duplicated keys: ' . implode(', ', array_keys($diff)));
332
        }
333
334
//        foreach ($forInsert as &$item) {
335
//            if ($item instanceof ElementFactory) {
336
//                $item = $item->build();
337
//            }
338
//        }
339
        
340 1
        return $forInsert;
341
    }
342
343
    /**
344
     * @param int $position
345
     * @param array $values
346
     */
347
    protected function insert($position, $values)
348
    {
349 1
        $firstArray = array_splice($this->elements, 0, $position);
350 1
        $this->elements = array_merge($firstArray, $values, $this->elements);
351 1
        $this->rebuildIndexesArray();
352 1
    }
353
354
    /**
355
     * @param $element
356
     * @return ElementFactory
357
     * @throws \RuntimeException
358
     */
359
    protected function getFactory($element)
360
    {
361 21
        if (!array_key_exists($element, $this->config['elements'])) {
362 1
            throw new \RuntimeException('Not found factory for element:' . $element);
363
        }
364
365 20
        $factoryClass = $this->config['elements'][$element]['factory'];
366
        
367 20
        return $this->app->make($factoryClass);
368
    }
369
370
    /**
371
     * @inheritDoc
372
     */
373
    public function toArray()
374
    {
375 2
        $this->view = $this->getRenderView($this->view);
376 2
        $elements = [];
377
        
378 2
        foreach ($this->elements as $key => $element) {
379
//            if ($element instanceof ElementFactory) {
380
//                $element = $element->build();
381
//            }
382
383 2
            $elements[$key] = $element->toArray();
384 2
            $elements[$key]['type'] = array_search(get_class($element), $this->config['alias']);
385
        }
386
        
387
        return [
388 2
            'type' => $this->type,
389 2
            'view' => $this->view,
390 2
            'attributes' => $this->attributes->toArray(),
391 2
            'activeAttributes' => $this->activeAttributes->toArray(),
392 2
            'elements' => $elements,
393
        ];
394
    }
395
396
    /**
397
     * @inheritDoc
398
     */
399
    public function offsetExists($offset)
400
    {
401 1
        if (is_int($offset)) {
402
            return (bool) array_search($offset, $this->indexes, true);
403
        }
404 1
        return $this->has($offset);
405
    }
406
407
    /**
408
     * @inheritDoc
409
     */
410
    public function offsetGet($offset)
411
    {
412 2
        if (is_int($offset)) {
413 1
            $offset = array_search($offset, $this->indexes, true);
414 1
            if ($offset === false) {
415 1
                return null;
416
            }
417
        }
418 2
        return $this->get($offset);
419
    }
420
421
    /**
422
     * @inheritDoc
423
     */
424
    public function offsetSet($offset, $value)
425
    {
426 1
        if ($value instanceof Element) {
427 1
            $this->elements[$offset] = $value;
428
        }
429 1
    }
430
431
    /**
432
     * @inheritDoc
433
     */
434
    public function offsetUnset($offset)
435
    {
436 1
        $this->forget($offset);
437 1
    }
438
439
    /**
440
     * @inheritDoc
441
     */
442
    static public function fromArray(array $builder)
443
    {
444 2
        return FromArrayBuilder::getInstance()->build($builder);
445
    }
446
447
    /**
448
     * @param string $type
449
     * @return bool
450
     */
451
    protected function hasActiveAttributes($type)
452
    {
453 20
        $reflection = new \ReflectionClass($type);
454
455 20
        return $reflection->implementsInterface(HasActiveAttributes::class);
456
    }
457
}