PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 1.18
Independent Analytics – WordPress Analytics Plugin v1.18
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / vendor / league / uri / src / UriTemplate / Expression.php
independent-analytics / vendor / league / uri / src / UriTemplate Last commit date
Expression.php 3 years ago Template.php 3 years ago VarSpecifier.php 3 years ago VariableBag.php 3 years ago
Expression.php
287 lines
1 <?php
2
3 /**
4 * League.Uri (https://uri.thephpleague.com)
5 *
6 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 declare (strict_types=1);
12 namespace IAWP\League\Uri\UriTemplate;
13
14 use IAWP\League\Uri\Exceptions\SyntaxError;
15 use IAWP\League\Uri\Exceptions\TemplateCanNotBeExpanded;
16 use function array_filter;
17 use function array_keys;
18 use function array_map;
19 use function array_unique;
20 use function explode;
21 use function implode;
22 use function preg_match;
23 use function rawurlencode;
24 use function str_replace;
25 use function strpos;
26 use function substr;
27 final class Expression
28 {
29 /**
30 * Expression regular expression pattern.
31 *
32 * @link https://tools.ietf.org/html/rfc6570#section-2.2
33 */
34 private const REGEXP_EXPRESSION = '/^\\{
35 (?:
36 (?<operator>[\\.\\/;\\?&\\=,\\!@\\|\\+#])?
37 (?<variables>[^\\}]*)
38 )
39 \\}$/x';
40 /**
41 * Reserved Operator characters.
42 *
43 * @link https://tools.ietf.org/html/rfc6570#section-2.2
44 */
45 private const RESERVED_OPERATOR = '=,!@|';
46 /**
47 * Processing behavior according to the expression type operator.
48 *
49 * @link https://tools.ietf.org/html/rfc6570#appendix-A
50 */
51 private const OPERATOR_HASH_LOOKUP = ['' => ['prefix' => '', 'joiner' => ',', 'query' => \false], '+' => ['prefix' => '', 'joiner' => ',', 'query' => \false], '#' => ['prefix' => '#', 'joiner' => ',', 'query' => \false], '.' => ['prefix' => '.', 'joiner' => '.', 'query' => \false], '/' => ['prefix' => '/', 'joiner' => '/', 'query' => \false], ';' => ['prefix' => ';', 'joiner' => ';', 'query' => \true], '?' => ['prefix' => '?', 'joiner' => '&', 'query' => \true], '&' => ['prefix' => '&', 'joiner' => '&', 'query' => \true]];
52 /**
53 * @var string
54 */
55 private $operator;
56 /**
57 * @var string
58 */
59 private $joiner;
60 /**
61 * @var array<VarSpecifier>
62 */
63 private $varSpecifiers;
64 /**
65 * @var array<string>
66 */
67 private $variableNames;
68 /**
69 * @var string
70 */
71 private $expressionString;
72 private function __construct(string $operator, VarSpecifier ...$varSpecifiers)
73 {
74 $this->operator = $operator;
75 $this->varSpecifiers = $varSpecifiers;
76 $this->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner'];
77 $this->variableNames = $this->setVariableNames();
78 $this->expressionString = $this->setExpressionString();
79 }
80 /**
81 * @return array<string>
82 */
83 private function setVariableNames() : array
84 {
85 $mapper = static function (VarSpecifier $varSpecifier) : string {
86 return $varSpecifier->name();
87 };
88 return array_unique(array_map($mapper, $this->varSpecifiers));
89 }
90 private function setExpressionString() : string
91 {
92 $mapper = static function (VarSpecifier $variable) : string {
93 return $variable->toString();
94 };
95 $varSpecifierString = implode(',', array_map($mapper, $this->varSpecifiers));
96 return '{' . $this->operator . $varSpecifierString . '}';
97 }
98 /**
99 * {@inheritDoc}
100 */
101 public static function __set_state(array $properties) : self
102 {
103 return new self($properties['operator'], ...$properties['varSpecifiers']);
104 }
105 /**
106 * @throws SyntaxError if the expression is invalid
107 * @throws SyntaxError if the operator used in the expression is invalid
108 * @throws SyntaxError if the variable specifiers is invalid
109 */
110 public static function createFromString(string $expression) : self
111 {
112 if (1 !== preg_match(self::REGEXP_EXPRESSION, $expression, $parts)) {
113 throw new SyntaxError('The expression "' . $expression . '" is invalid.');
114 }
115 /** @var array{operator:string, variables:string} $parts */
116 $parts = $parts + ['operator' => ''];
117 if ('' !== $parts['operator'] && \false !== strpos(self::RESERVED_OPERATOR, $parts['operator'])) {
118 throw new SyntaxError('The operator used in the expression "' . $expression . '" is reserved.');
119 }
120 $mapper = static function (string $varSpec) : VarSpecifier {
121 return VarSpecifier::createFromString($varSpec);
122 };
123 return new Expression($parts['operator'], ...array_map($mapper, explode(',', $parts['variables'])));
124 }
125 /**
126 * Returns the expression string representation.
127 *
128 */
129 public function toString() : string
130 {
131 return $this->expressionString;
132 }
133 /**
134 * @return array<string>
135 */
136 public function variableNames() : array
137 {
138 return $this->variableNames;
139 }
140 public function expand(VariableBag $variables) : string
141 {
142 $parts = [];
143 foreach ($this->varSpecifiers as $varSpecifier) {
144 $parts[] = $this->replace($varSpecifier, $variables);
145 }
146 $nullFilter = static function ($value) : bool {
147 return '' !== $value;
148 };
149 $expanded = implode($this->joiner, array_filter($parts, $nullFilter));
150 if ('' === $expanded) {
151 return $expanded;
152 }
153 $prefix = self::OPERATOR_HASH_LOOKUP[$this->operator]['prefix'];
154 if ('' === $prefix) {
155 return $expanded;
156 }
157 return $prefix . $expanded;
158 }
159 /**
160 * Replaces an expression with the given variables.
161 *
162 * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
163 * @throws TemplateCanNotBeExpanded if the variables contains nested array values
164 */
165 private function replace(VarSpecifier $varSpec, VariableBag $variables) : string
166 {
167 $value = $variables->fetch($varSpec->name());
168 if (null === $value) {
169 return '';
170 }
171 $useQuery = self::OPERATOR_HASH_LOOKUP[$this->operator]['query'];
172 [$expanded, $actualQuery] = $this->inject($value, $varSpec, $useQuery);
173 if (!$actualQuery) {
174 return $expanded;
175 }
176 if ('&' !== $this->joiner && '' === $expanded) {
177 return $varSpec->name();
178 }
179 return $varSpec->name() . '=' . $expanded;
180 }
181 /**
182 * @param string|array<string> $value
183 *
184 * @return array{0:string, 1:bool}
185 */
186 private function inject($value, VarSpecifier $varSpec, bool $useQuery) : array
187 {
188 if (\is_string($value)) {
189 return $this->replaceString($value, $varSpec, $useQuery);
190 }
191 return $this->replaceList($value, $varSpec, $useQuery);
192 }
193 /**
194 * Expands an expression using a string value.
195 *
196 * @return array{0:string, 1:bool}
197 */
198 private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery) : array
199 {
200 if (':' === $varSpec->modifier()) {
201 $value = substr($value, 0, $varSpec->position());
202 }
203 $expanded = rawurlencode($value);
204 if ('+' === $this->operator || '#' === $this->operator) {
205 return [$this->decodeReserved($expanded), $useQuery];
206 }
207 return [$expanded, $useQuery];
208 }
209 /**
210 * Expands an expression using a list of values.
211 *
212 * @param array<string> $value
213 *
214 * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied
215 *
216 * @return array{0:string, 1:bool}
217 */
218 private function replaceList(array $value, VarSpecifier $varSpec, bool $useQuery) : array
219 {
220 if ([] === $value) {
221 return ['', \false];
222 }
223 if (':' === $varSpec->modifier()) {
224 throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name());
225 }
226 $pairs = [];
227 $isAssoc = $this->isAssoc($value);
228 foreach ($value as $key => $var) {
229 if ($isAssoc) {
230 $key = rawurlencode((string) $key);
231 }
232 $var = rawurlencode($var);
233 if ('+' === $this->operator || '#' === $this->operator) {
234 $var = $this->decodeReserved($var);
235 }
236 if ('*' === $varSpec->modifier()) {
237 if ($isAssoc) {
238 $var = $key . '=' . $var;
239 } elseif ($key > 0 && $useQuery) {
240 $var = $varSpec->name() . '=' . $var;
241 }
242 }
243 $pairs[$key] = $var;
244 }
245 if ('*' === $varSpec->modifier()) {
246 if ($isAssoc) {
247 // Don't prepend the value name when using the explode
248 // modifier with an associative array.
249 $useQuery = \false;
250 }
251 return [implode($this->joiner, $pairs), $useQuery];
252 }
253 if ($isAssoc) {
254 // When an associative array is encountered and the
255 // explode modifier is not set, then the result must be
256 // a comma separated list of keys followed by their
257 // respective values.
258 foreach ($pairs as $offset => &$data) {
259 $data = $offset . ',' . $data;
260 }
261 unset($data);
262 }
263 return [implode(',', $pairs), $useQuery];
264 }
265 /**
266 * Determines if an array is associative.
267 *
268 * This makes the assumption that input arrays are sequences or hashes.
269 * This assumption is a trade-off for accuracy in favor of speed, but it
270 * should work in almost every case where input is supplied for a URI
271 * template.
272 */
273 private function isAssoc(array $array) : bool
274 {
275 return [] !== $array && 0 !== array_keys($array)[0];
276 }
277 /**
278 * Removes percent encoding on reserved characters (used with + and # modifiers).
279 */
280 private function decodeReserved(string $str) : string
281 {
282 static $delimiters = [':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='];
283 static $delimitersEncoded = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D'];
284 return str_replace($delimitersEncoded, $delimiters, $str);
285 }
286 }
287