Passed
Push — master ( a0adf5...686f36 )
by Divine Niiquaye
10:52
created

EscaperHelper::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.2 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Biurad\UI\Helper;
19
20
class EscaperHelper extends AbstractHelper
21
{
22
    /** @var array<string,callable> */
23
    protected $escapers = [];
24
25
    /** @var array<string,array<int|bool|string,mixed>> */
26
    protected static $escaperCache = [];
27
28 6
    public function __construct()
29
    {
30 6
        $this->initializeEscapers();
31 6
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36 6
    public function getName(): string
37
    {
38 6
        return 'escape';
39
    }
40
41
    /**
42
     * Adds an escaper for the given context.
43
     */
44
    public function setEscaper(string $context, callable $escaper): void
45
    {
46
        $this->escapers[$context] = $escaper;
47
        self::$escaperCache[$context] = [];
48
    }
49
50
    /**
51
     * Gets an escaper for a given context.
52
     *
53
     * @throws \InvalidArgumentException
54
     *
55
     * @return callable A PHP callable
56
     */
57 4
    public function getEscaper(string $context)
58
    {
59 4
        if (!isset($this->escapers[$context])) {
60
            throw new \InvalidArgumentException(\sprintf('No registered escaper for context "%s".', $context));
61
        }
62
63 4
        return $this->escapers[$context];
64
    }
65
66
    /**
67
     * Runs the PHP function htmlspecialchars on the value passed.
68
     *
69
     * @param mixed $value The value to escape
70
     *
71
     * @return mixed the escaped value
72
     */
73 4
    public function html($value)
74
    {
75 4
        return $this->encode($value, __FUNCTION__);
76
    }
77
78
    /**
79
     * A function that escape all non-alphanumeric characters
80
     * into their \xHH or \uHHHH representations.
81
     *
82
     * @param mixed $value The value to escape
83
     *
84
     * @return mixed the escaped value
85
     */
86
    public function js($value)
87
    {
88
        return $this->encode($value, __FUNCTION__);
89
    }
90
91
    /**
92
     * Escapes string for use inside CSS template.
93
     *
94
     * @param mixed $value The value to escape
95
     *
96
     * @return mixed the escaped value
97
     */
98
    public function css($value)
99
    {
100
        return $this->encode($value, __FUNCTION__);
101
    }
102
103
    /**
104
     * Escapes a string by using the current charset.
105
     *
106
     * @param mixed $value A variable to escape
107
     *
108
     * @return mixed The escaped value
109
     */
110 4
    public function encode($value, string $context = 'html')
111
    {
112 4
        if (\is_numeric($value)) {
113
            return $value;
114
        }
115
116
        // If we deal with a scalar value, we can cache the result to increase
117
        // the performance when the same value is escaped multiple times (e.g. loops)
118 4
        if (\is_scalar($value)) {
119 4
            if (!isset(self::$escaperCache[$context][$value])) {
120 4
                self::$escaperCache[$context][$value] = $this->getEscaper($context)($value);
121
            }
122
123 4
            return self::$escaperCache[$context][$value];
124
        }
125
126
        return $this->getEscaper($context)($value);
127
    }
128
129
    /**
130
     * Initializes the built-in escapers.
131
     *
132
     * Each function specifies a way for applying a transformation to a string
133
     * passed to it. The purpose is for the string to be "escaped" so it is
134
     * suitable for the format it is being displayed in.
135
     *
136
     * For example, the string: "It's required that you enter a username & password.\n"
137
     * If this were to be displayed as HTML it would be sensible to turn the
138
     * ampersand into '&amp;' and the apostrophe into '&aps;'. However if it were
139
     * going to be used as a string in JavaScript to be displayed in an alert box
140
     * it would be right to leave the string as-is, but c-escape the apostrophe and
141
     * the new line.
142
     *
143
     * For each function there is a define to avoid problems with strings being
144
     * incorrectly specified.
145
     */
146 6
    protected function initializeEscapers(): void
147
    {
148 6
        $flags = \ENT_NOQUOTES | \ENT_SUBSTITUTE;
149
150 6
        $this->escapers = [
151
            'html' =>
152
                /**
153
                 * Runs the PHP function htmlspecialchars on the value passed.
154
                 *
155
                 * @param string $value The value to escape
156
                 *
157
                 * @return string the escaped value
158
                 */
159 6
                function ($value) use ($flags) {
160
                    // Numbers and Boolean values get turned into strings which can cause problems
161
                    // with type comparisons (e.g. === or is_int() etc).
162 4
                    return \is_string($value) ? \htmlspecialchars($value, $flags, $this->getCharset()) : $value;
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
163 6
                },
164
165
            'js' =>
166
                /**
167
                 * A function that escape all non-alphanumeric characters
168
                 * into their \xHH or \uHHHH representations.
169
                 *
170
                 * @param string $value The value to escape
171
                 *
172
                 * @return string the escaped value
173
                 */
174 6
                function ($value) {
175
                    if ('UTF-8' != $this->getCharset()) {
176
                        $value = \iconv($this->getCharset(), 'UTF-8', $value);
177
                    }
178
179
                    $callback = static function ($matches): string {
180
                        $char = $matches[0];
181
182
                        // \xHH
183
                        if (!isset($char[1])) {
184
                            return '\\x' . \substr('00' . \bin2hex($char), -2);
185
                        }
186
187
                        // \uHHHH
188
                        $char = \iconv('UTF-8', 'UTF-16BE', $char);
189
190
                        return '\\u' . \substr('0000' . \bin2hex($char), -4);
191
                    };
192
193
                    if (null === $value = \preg_replace_callback('#[^\p{L}\p{N} ]#u', $callback, $value)) {
194
                        throw new \InvalidArgumentException('The string to escape is not a valid UTF-8 string.');
195
                    }
196
197
                    if ('UTF-8' != $this->getCharset()) {
198
                        $value = \iconv('UTF-8', $this->getCharset(), $value);
199
                    }
200
201
                    return $value;
202 6
                },
203
            'css' =>
204
                /**
205
                 * Escapes string for use inside CSS template.
206
                 *
207
                 * @param mixed $value The value to escape
208
                 */
209 6
                function ($value) {
210
                    // http://www.w3.org/TR/2006/WD-CSS21-20060411/syndata.html#q6
211
                    return \addcslashes($value, "\x00..\x1F!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~");
212 6
                },
213
        ];
214
215 6
        self::$escaperCache = [];
216 6
    }
217
}
218