Issues (20)

Security Analysis    no request data  

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/NatePage/EasyHtmlElement/HtmlElement.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 NatePage\EasyHtmlElement;
4
5
use NatePage\EasyHtmlElement\Exception\InvalidArgumentsNumberException;
6
use NatePage\EasyHtmlElement\Exception\InvalidElementException;
7
use NatePage\EasyHtmlElement\Exception\UndefinedElementException;
8
9
class HtmlElement implements HtmlElementInterface
10
{
11
    /** @var array */
12
    private $map;
13
14
    /** @var EscaperInterface */
15
    private $escaper;
16
17
    /** @var BranchValidatorInterface */
18
    private $branchValidator;
19
20
    /** @var array The already resolved elements */
21
    private $resolved = array();
22
23
    /** @var array The default values of element options */
24
    private $defaults = array(
25
        'parent' => null,
26
        'children' => array(),
27
        'extends' => array(),
28
        'attr' => array(),
29
        'text' => null,
30
        'type' => null,
31
        'class' => Element::class
32
    );
33
34
    /** @var array The mergeable attributes */
35
    private $mergeableAttributes = array('class', 'style');
36
37
    /**
38
     * HtmlElement constructor.
39
     *
40
     * @param array                         $map             The elements map
41
     * @param BranchValidatorInterface|null $branchValidator The branch validator
42
     * @param EscaperInterface|null         $escaper         The escaper, by default ZendFramework/Escaper is used
43
     * @param string                        $encoding        The encoding used for escaping, by default utf-8 is used
44
     */
45
    public function __construct(
46
        array $map = array(),
47
        BranchValidatorInterface $branchValidator = null,
48
        EscaperInterface $escaper = null,
49
        $encoding = 'utf-8')
50
    {
51
        $this->map = $map;
52
        $this->branchValidator = null !== $branchValidator ? $branchValidator : new BranchValidator($this);
53
        $this->escaper = null !== $escaper ? $escaper : new Escaper($encoding);
54
    }
55
56
    /**
57
     * Load element on dynamic calls.
58
     *
59
     * @param string $name      The element name
60
     * @param array  $arguments The arguments array to set:
61
     *                          [0] = text (string|null)
62
     *                          [1] = attributes (array)
63
     *                          [2] = parameters (array)
64
     *                          [3] = extras (array)
65
     *                          [4] = children (array)
66
     *
67
     * @return ElementInterface
68
     *
69
     * @throws InvalidArgumentsNumberException If the arguments length is more than 3
70
     */
71
    public function __call($name, $arguments)
72
    {
73
        array_unshift($arguments, $name);
74
75
        return $this->load(...$arguments);
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    public function getMap(): array
82
    {
83
        return $this->map;
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function setMap(array $map): HtmlElementInterface
90
    {
91
        $this->map = $map;
92
93
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (NatePage\EasyHtmlElement\HtmlElement) is incompatible with the return type declared by the interface NatePage\EasyHtmlElement...lementInterface::setMap of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function addManyToMap(array $elements): HtmlElementInterface
100
    {
101
        foreach ($elements as $name => $element) {
102
            $this->addOneToMap($name, $element);
103
        }
104
105
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (NatePage\EasyHtmlElement\HtmlElement) is incompatible with the return type declared by the interface NatePage\EasyHtmlElement...Interface::addManyToMap of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function addOneToMap(string $name, array $element): HtmlElementInterface
112
    {
113
        $this->map[$name] = $element;
114
115
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (NatePage\EasyHtmlElement\HtmlElement) is incompatible with the return type declared by the interface NatePage\EasyHtmlElement...tInterface::addOneToMap of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function getBranchValidator(): BranchValidatorInterface
122
    {
123
        return $this->branchValidator;
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function setBranchValidator(BranchValidatorInterface $branchValidator): HtmlElementInterface
130
    {
131
        $this->branchValidator = $branchValidator;
132
133
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (NatePage\EasyHtmlElement\HtmlElement) is incompatible with the return type declared by the interface NatePage\EasyHtmlElement...ace::setBranchValidator of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function getEscaper(): EscaperInterface
140
    {
141
        return $this->escaper;
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function setEscaper(EscaperInterface $escaper): HtmlElementInterface
148
    {
149
        $this->escaper = $escaper;
150
151
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (NatePage\EasyHtmlElement\HtmlElement) is incompatible with the return type declared by the interface NatePage\EasyHtmlElement...ntInterface::setEscaper of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function load(
158
        $name,
159
        $text = null,
160
        array $attributes = array(),
161
        array $parameters = array(),
162
        array $extras = array(),
163
        array $children = array()
164
    ): ElementInterface
165
    {
166
        $element = $this->getInstance($name, $text, $attributes, $extras, $parameters, true);
167
168
        foreach ($children as $child) {
169
            $element->addChild($this->escaper->escape($child->getRoot()));
170
        }
171
172
        return $element;
173
    }
174
175
    /**
176
     * Get the element instance.
177
     *
178
     * @param string|array $name       The element name
179
     * @param string|null  $text       The element text
180
     * @param array        $attributes The element attributes
181
     * @param array        $extras     The element extras
182
     * @param array        $parameters The parameters to replace in element
183
     * @param bool         $mainCall   Determine if it's the main(first) call of the method
184
     *
185
     * @return ElementInterface
186
     *
187
     * @throws InvalidElementException If the current instance doesn't implement ElementInterface
188
     */
189
    private function getInstance(
190
        $name,
191
        $text,
192
        array $attributes,
193
        array $extras,
194
        array $parameters,
195
        bool $mainCall = false
196
    ): ElementInterface
197
    {
198
        $element = $this->resolveElement($name, $parameters, $mainCall);
199
200
        $class = $element['class'];
201
        $type = $element['type'];
202
        $text = $text !== null ? $text : $element['text'];
203
        $attributes = $this->extendAttributes($attributes, $element['attr']);
204
205
        $children = array();
206
        foreach ((array) $element['children'] as $child) {
207
            $children[] = $this->getInstance($child, null, array(), array(), $parameters);
208
        }
209
210
        $instance = new $class($type, $text, $attributes, $extras, $children);
211
212
        if (!$instance instanceof ElementInterface) {
213
            throw new InvalidElementException(sprintf(
214
                'The element "%s" does not implement the %s',
215
                get_class($instance),
216
                ElementInterface::class
217
            ));
218
        }
219
220
        if (null !== $element['parent']) {
221
            $parent = $this->getInstance($element['parent'], null, array(), array(), $parameters);
222
223
            $parent->addChild($instance->getRoot());
224
        }
225
226
        return $this->escaper->escape($instance);
227
    }
228
229
    /**
230
     * Get the resolved element representation.
231
     *
232
     * @param string|array $name       The current element name
233
     * @param array        $parameters The parameters to replace in element
234
     * @param bool         $mainCall   Determine if it's the main(first) call of the method
235
     *
236
     * @return array
237
     */
238
    private function resolveElement($name, array $parameters, bool $mainCall = false): array
239
    {
240
        $current = $this->getCurrentElement($name);
241
242
        $name = $current['name'];
243
244
        if ($this->alreadyResolved($name)) {
245
            return $this->replaceParameters($this->resolved[$name], $parameters);
246
        }
247
248
        if ($mainCall) {
249
            $this->branchValidator->validateBranch($name);
250
        }
251
252
        foreach ($this->defaults as $default => $value) {
253
            if (!isset($current[$default])) {
254
                $current[$default] = $value;
255
            }
256
        }
257
258
        foreach ((array) $current['extends'] as $extend) {
259
            $extend = $this->resolveElement($extend, $parameters);
260
            $current = $this->extendElement($extend, $current);
261
        }
262
263
        $this->resolved[$name] = $current;
264
265
        $current = $this->replaceParameters($current, $parameters);
266
267
        return $current;
268
    }
269
270
    /**
271
     * Check if an element has been already resolved.
272
     *
273
     * @param string $name
274
     *
275
     * @return bool
276
     */
277
    private function alreadyResolved(string $name): bool
278
    {
279
        return array_key_exists($name, $this->resolved);
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function exists(string $name): bool
286
    {
287
        return array_key_exists(lcfirst($name), $this->map);
288
    }
289
290
    /**
291
     * Get the current element representation.
292
     *
293
     * @param string|array $name The element name
294
     *
295
     * @return array
296
     *
297
     * @throws InvalidElementException   If the current element is defined dynamically and doesn't define a name
298
     * @throws UndefinedElementException If the current element doesn't exist
299
     */
300
    public function getCurrentElement($name): array
301
    {
302
        if (is_array($name)) {
303
            if (!isset($name['name'])) {
304
                throw new InvalidElementException(sprintf(
305
                    'Elements defined dynamically in parent or children must define a name.'
306
                ));
307
            }
308
309
            return $name;
310
        }
311
312
        if (!$this->exists($name)) {
313
            throw new UndefinedElementException(sprintf('The element with name "%s" does not exist.', $name));
314
        }
315
316
        $current = $this->map[lcfirst($name)];
317
        $current['name'] = $name;
318
319
        return $current;
320
    }
321
322
    /**
323
     * Replace parameters in text and attr.
324
     *
325
     * @param array $element    The element with the parameters to replace
326
     * @param array $parameters The array of parameters values
327
     *
328
     * @return array
329
     */
330
    private function replaceParameters(array $element, array $parameters): array
331
    {
332
        foreach ($parameters as $parameter => $replace) {
333
            $element['text'] = str_replace('%'.$parameter.'%', $replace, (string) $element['text']);
334
        }
335
336
        $element['attr'] = $this->replaceParametersInAttributes($element['attr'], $parameters);
337
338
        return $element;
339
    }
340
341
    /**
342
     * Replace parameters in attr.
343
     *
344
     * @param array $attributes The attributes
345
     * @param array $parameters The parameters
346
     *
347
     * @return array
348
     */
349
    private function replaceParametersInAttributes(array $attributes, array $parameters): array
350
    {
351
        foreach ($attributes as $key => $value) {
352
            if (is_array($value)) {
353
                $attributes[$key] = $this->replaceParametersInAttributes($value, $parameters);
354
            }
355
356
            foreach ($parameters as $parameter => $replace) {
357
                if (in_array($key, $this->escaper->getUrlsAttributes()) && $this->escaper->isEscapeUrl()) {
358
                    $replace = $this->escaper->escapeUrlParameter($replace);
359
                }
360
361
                $value = str_replace('%'.$parameter.'%', $replace, (string) $value);
362
            }
363
364
            $attributes[$key] = $value;
365
        }
366
367
        return $attributes;
368
    }
369
370
    /**
371
     * Extend element from another one.
372
     *
373
     * @param array $extend  The array of the element to extend
374
     * @param array $current The current element which extends
375
     *
376
     * @return array
377
     */
378
    private function extendElement(array $extend, array $current): array
379
    {
380
        foreach ($this->defaults as $default => $value) {
381
            if (!in_array($default, array('attr', 'children')) && $current[$default] === $value) {
382
                $current[$default] = $extend[$default];
383
            }
384
        }
385
386
        $current['attr'] = $this->extendAttributes($extend['attr'], $current['attr']);
387
388
        foreach ($extend['children'] as $child) {
389
            $current['children'][] = $child;
390
        }
391
392
        return $current;
393
    }
394
395
    /**
396
     * Extend attributes from another element.
397
     *
398
     * @param array $from The array of attributes to extend
399
     * @param array $to   The array of attributes which extends
400
     *
401
     * @return array
402
     */
403
    private function extendAttributes(array $from, array $to): array
404
    {
405
        foreach ($from as $key => $value) {
406
            if (in_array($key, $this->mergeableAttributes) && isset($to[$key])) {
407
                $to[$key] = $this->extendMergeableAttributes($value, $to[$key], $key);
408
            } elseif (!isset($to[$key])) {
409
                $to[$key] = $value;
410
            } elseif (is_array($value)) {
411
                $to[$key] = $this->extendAttributes($value, $to[$key]);
412
            }
413
        }
414
415
        return $to;
416
    }
417
418
    /**
419
     * Extend mergeable attributes from another element.
420
     *
421
     * @param string|array $from The attribute to extend
422
     * @param string|array $to   The attribute which extends
423
     * @param string       $attr The attribute name
424
     *
425
     * @return string
426
     */
427
    private function extendMergeableAttributes($from, $to, string $attr): string
428
    {
429
        $value = array_merge((array) $to, (array) $from);
430
431
        switch ($attr) {
432
            case 'class':
433
                return implode(' ', $value);
434
            case 'style':
435
                return implode('; ', $value);
436
        }
437
    }
438
}
439