krz/vox-populi
A Tumblr web client.
clone: git clone https://gitbay.org/krz/vox-populi.git
main: vendor/guzzlehttp/guzzle/src/UriTemplate.php · raw
1<?php
2namespace GuzzleHttp;
3
4/**
5 * Expands URI templates. Userland implementation of PECL uri_template.
6 *
7 * @link http://tools.ietf.org/html/rfc6570
8 */
9class UriTemplate
10{
11 /** @var string URI template */
12 private $template;
13
14 /** @var array Variables to use in the template expansion */
15 private $variables;
16
17 /** @var array Hash for quick operator lookups */
18 private static $operatorHash = [
19 '' => ['prefix' => '', 'joiner' => ',', 'query' => false],
20 '+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
21 '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
22 '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
23 '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
24 ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
25 '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
26 '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
27 ];
28
29 /** @var array Delimiters */
30 private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
31 '&', '\'', '(', ')', '*', '+', ',', ';', '='];
32
33 /** @var array Percent encoded delimiters */
34 private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
35 '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
36 '%3B', '%3D'];
37
38 public function expand($template, array $variables)
39 {
40 if (false === strpos($template, '{')) {
41 return $template;
42 }
43
44 $this->template = $template;
45 $this->variables = $variables;
46
47 return preg_replace_callback(
48 '/\{([^\}]+)\}/',
49 [$this, 'expandMatch'],
50 $this->template
51 );
52 }
53
54 /**
55 * Parse an expression into parts
56 *
57 * @param string $expression Expression to parse
58 *
59 * @return array Returns an associative array of parts
60 */
61 private function parseExpression($expression)
62 {
63 $result = [];
64
65 if (isset(self::$operatorHash[$expression[0]])) {
66 $result['operator'] = $expression[0];
67 $expression = substr($expression, 1);
68 } else {
69 $result['operator'] = '';
70 }
71
72 foreach (explode(',', $expression) as $value) {
73 $value = trim($value);
74 $varspec = [];
75 if ($colonPos = strpos($value, ':')) {
76 $varspec['value'] = substr($value, 0, $colonPos);
77 $varspec['modifier'] = ':';
78 $varspec['position'] = (int) substr($value, $colonPos + 1);
79 } elseif (substr($value, -1) === '*') {
80 $varspec['modifier'] = '*';
81 $varspec['value'] = substr($value, 0, -1);
82 } else {
83 $varspec['value'] = (string) $value;
84 $varspec['modifier'] = '';
85 }
86 $result['values'][] = $varspec;
87 }
88
89 return $result;
90 }
91
92 /**
93 * Process an expansion
94 *
95 * @param array $matches Matches met in the preg_replace_callback
96 *
97 * @return string Returns the replacement string
98 */
99 private function expandMatch(array $matches)
100 {
101 static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];
102
103 $replacements = [];
104 $parsed = self::parseExpression($matches[1]);
105 $prefix = self::$operatorHash[$parsed['operator']]['prefix'];
106 $joiner = self::$operatorHash[$parsed['operator']]['joiner'];
107 $useQuery = self::$operatorHash[$parsed['operator']]['query'];
108
109 foreach ($parsed['values'] as $value) {
110 if (!isset($this->variables[$value['value']])) {
111 continue;
112 }
113
114 $variable = $this->variables[$value['value']];
115 $actuallyUseQuery = $useQuery;
116 $expanded = '';
117
118 if (is_array($variable)) {
119 $isAssoc = $this->isAssoc($variable);
120 $kvp = [];
121 foreach ($variable as $key => $var) {
122 if ($isAssoc) {
123 $key = rawurlencode($key);
124 $isNestedArray = is_array($var);
125 } else {
126 $isNestedArray = false;
127 }
128
129 if (!$isNestedArray) {
130 $var = rawurlencode($var);
131 if ($parsed['operator'] === '+' ||
132 $parsed['operator'] === '#'
133 ) {
134 $var = $this->decodeReserved($var);
135 }
136 }
137
138 if ($value['modifier'] === '*') {
139 if ($isAssoc) {
140 if ($isNestedArray) {
141 // Nested arrays must allow for deeply nested
142 // structures.
143 $var = strtr(
144 http_build_query([$key => $var]),
145 $rfc1738to3986
146 );
147 } else {
148 $var = $key . '=' . $var;
149 }
150 } elseif ($key > 0 && $actuallyUseQuery) {
151 $var = $value['value'] . '=' . $var;
152 }
153 }
154
155 $kvp[$key] = $var;
156 }
157
158 if (empty($variable)) {
159 $actuallyUseQuery = false;
160 } elseif ($value['modifier'] === '*') {
161 $expanded = implode($joiner, $kvp);
162 if ($isAssoc) {
163 // Don't prepend the value name when using the explode
164 // modifier with an associative array.
165 $actuallyUseQuery = false;
166 }
167 } else {
168 if ($isAssoc) {
169 // When an associative array is encountered and the
170 // explode modifier is not set, then the result must be
171 // a comma separated list of keys followed by their
172 // respective values.
173 foreach ($kvp as $k => &$v) {
174 $v = $k . ',' . $v;
175 }
176 }
177 $expanded = implode(',', $kvp);
178 }
179 } else {
180 if ($value['modifier'] === ':') {
181 $variable = substr($variable, 0, $value['position']);
182 }
183 $expanded = rawurlencode($variable);
184 if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
185 $expanded = $this->decodeReserved($expanded);
186 }
187 }
188
189 if ($actuallyUseQuery) {
190 if (!$expanded && $joiner !== '&') {
191 $expanded = $value['value'];
192 } else {
193 $expanded = $value['value'] . '=' . $expanded;
194 }
195 }
196
197 $replacements[] = $expanded;
198 }
199
200 $ret = implode($joiner, $replacements);
201 if ($ret && $prefix) {
202 return $prefix . $ret;
203 }
204
205 return $ret;
206 }
207
208 /**
209 * Determines if an array is associative.
210 *
211 * This makes the assumption that input arrays are sequences or hashes.
212 * This assumption is a tradeoff for accuracy in favor of speed, but it
213 * should work in almost every case where input is supplied for a URI
214 * template.
215 *
216 * @param array $array Array to check
217 *
218 * @return bool
219 */
220 private function isAssoc(array $array)
221 {
222 return $array && array_keys($array)[0] !== 0;
223 }
224
225 /**
226 * Removes percent encoding on reserved characters (used with + and #
227 * modifiers).
228 *
229 * @param string $string String to fix
230 *
231 * @return string
232 */
233 private function decodeReserved($string)
234 {
235 return str_replace(self::$delimsPct, self::$delims, $string);
236 }
237}