Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
/*
3
 * Copyright 2013 Google Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
 
18
/**
19
 * Implementation of levels 1-3 of the URI Template spec.
20
 * @see http://tools.ietf.org/html/rfc6570
21
 */
22
#[AllowDynamicProperties]
23
class Google_Utils_URITemplate
24
{
25
  const TYPE_MAP = "1";
26
  const TYPE_LIST = "2";
27
  const TYPE_SCALAR = "4";
28
 
29
  /**
30
   * @var $operators array
31
   * These are valid at the start of a template block to
32
   * modify the way in which the variables inside are
33
   * processed.
34
   */
35
  private $operators = array(
36
      "+" => "reserved",
37
      "/" => "segments",
38
      "." => "dotprefix",
39
      "#" => "fragment",
40
      ";" => "semicolon",
41
      "?" => "form",
42
      "&" => "continuation"
43
  );
44
 
45
  /**
46
   * @var reserved array
47
   * These are the characters which should not be URL encoded in reserved
48
   * strings.
49
   */
50
  private $reserved = array(
51
      "=", ",", "!", "@", "|", ":", "/", "?", "#",
52
      "[", "]",'$', "&", "'", "(", ")", "*", "+", ";"
53
  );
54
  private $reservedEncoded = array(
55
    "%3D", "%2C", "%21", "%40", "%7C", "%3A", "%2F", "%3F",
56
    "%23", "%5B", "%5D", "%24", "%26", "%27", "%28", "%29",
57
    "%2A", "%2B", "%3B"
58
  );
59
 
60
  public function parse($string, array $parameters)
61
  {
62
    return $this->resolveNextSection($string, $parameters);
63
  }
64
 
65
  /**
66
   * This function finds the first matching {...} block and
67
   * executes the replacement. It then calls itself to find
68
   * subsequent blocks, if any.
69
   */
70
  private function resolveNextSection($string, $parameters)
71
  {
72
    $start = strpos($string, "{");
73
    if ($start === false) {
74
      return $string;
75
    }
76
    $end = strpos($string, "}");
77
    if ($end === false) {
78
      return $string;
79
    }
80
    $string = $this->replace($string, $start, $end, $parameters);
81
    return $this->resolveNextSection($string, $parameters);
82
  }
83
 
84
  private function replace($string, $start, $end, $parameters)
85
  {
86
    // We know a data block will have {} round it, so we can strip that.
87
    $data = substr($string, $start + 1, $end - $start - 1);
88
 
89
    // If the first character is one of the reserved operators, it effects
90
    // the processing of the stream.
91
    if (isset($this->operators[$data[0]])) {
92
      $op = $this->operators[$data[0]];
93
      $data = substr($data, 1);
94
      $prefix = "";
95
      $prefix_on_missing = false;
96
 
97
      switch ($op) {
98
        case "reserved":
99
          // Reserved means certain characters should not be URL encoded
100
          $data = $this->replaceVars($data, $parameters, ",", null, true);
101
          break;
102
        case "fragment":
103
          // Comma separated with fragment prefix. Bare values only.
104
          $prefix = "#";
105
          $prefix_on_missing = true;
106
          $data = $this->replaceVars($data, $parameters, ",", null, true);
107
          break;
108
        case "segments":
109
          // Slash separated data. Bare values only.
110
          $prefix = "/";
111
          $data =$this->replaceVars($data, $parameters, "/");
112
          break;
113
        case "dotprefix":
114
          // Dot separated data. Bare values only.
115
          $prefix = ".";
116
          $prefix_on_missing = true;
117
          $data = $this->replaceVars($data, $parameters, ".");
118
          break;
119
        case "semicolon":
120
          // Semicolon prefixed and separated. Uses the key name
121
          $prefix = ";";
122
          $data = $this->replaceVars($data, $parameters, ";", "=", false, true, false);
123
          break;
124
        case "form":
125
          // Standard URL format. Uses the key name
126
          $prefix = "?";
127
          $data = $this->replaceVars($data, $parameters, "&", "=");
128
          break;
129
        case "continuation":
130
          // Standard URL, but with leading ampersand. Uses key name.
131
          $prefix = "&";
132
          $data = $this->replaceVars($data, $parameters, "&", "=");
133
          break;
134
      }
135
 
136
      // Add the initial prefix character if data is valid.
137
      if ($data || ($data !== false && $prefix_on_missing)) {
138
        $data = $prefix . $data;
139
      }
140
 
141
    } else {
142
      // If no operator we replace with the defaults.
143
      $data = $this->replaceVars($data, $parameters);
144
    }
145
    // This is chops out the {...} and replaces with the new section.
146
    return substr($string, 0, $start) . $data . substr($string, $end + 1);
147
  }
148
 
149
  private function replaceVars(
150
      $section,
151
      $parameters,
152
      $sep = ",",
153
      $combine = null,
154
      $reserved = false,
155
      $tag_empty = false,
156
      $combine_on_empty = true
157
  ) {
158
    if (strpos($section, ",") === false) {
159
      // If we only have a single value, we can immediately process.
160
      return $this->combine(
161
          $section,
162
          $parameters,
163
          $sep,
164
          $combine,
165
          $reserved,
166
          $tag_empty,
167
          $combine_on_empty
168
      );
169
    } else {
170
      // If we have multiple values, we need to split and loop over them.
171
      // Each is treated individually, then glued together with the
172
      // separator character.
173
      $vars = explode(",", $section);
174
      return $this->combineList(
175
          $vars,
176
          $sep,
177
          $parameters,
178
          $combine,
179
          $reserved,
180
          false, // Never emit empty strings in multi-param replacements
181
          $combine_on_empty
182
      );
183
    }
184
  }
185
 
186
  public function combine(
187
      $key,
188
      $parameters,
189
      $sep,
190
      $combine,
191
      $reserved,
192
      $tag_empty,
193
      $combine_on_empty
194
  ) {
195
    $length = false;
196
    $explode = false;
197
    $skip_final_combine = false;
198
    $value = false;
199
 
200
    // Check for length restriction.
201
    if (strpos($key, ":") !== false) {
202
      list($key, $length) = explode(":", $key);
203
    }
204
 
205
    // Check for explode parameter.
206
    if ($key[strlen($key) - 1] == "*") {
207
      $explode = true;
208
      $key = substr($key, 0, -1);
209
      $skip_final_combine = true;
210
    }
211
 
212
    // Define the list separator.
213
    $list_sep = $explode ? $sep : ",";
214
 
215
    if (isset($parameters[$key])) {
216
      $data_type = $this->getDataType($parameters[$key]);
217
      switch ($data_type) {
218
        case self::TYPE_SCALAR:
219
          $value = $this->getValue($parameters[$key], $length);
220
          break;
221
        case self::TYPE_LIST:
222
          $values = array();
223
          foreach ($parameters[$key] as $pkey => $pvalue) {
224
            $pvalue = $this->getValue($pvalue, $length);
225
            if ($combine && $explode) {
226
              $values[$pkey] = $key . $combine . $pvalue;
227
            } else {
228
              $values[$pkey] = $pvalue;
229
            }
230
          }
231
          $value = implode($list_sep, $values);
232
          if ($value == '') {
233
            return '';
234
          }
235
          break;
236
        case self::TYPE_MAP:
237
          $values = array();
238
          foreach ($parameters[$key] as $pkey => $pvalue) {
239
            $pvalue = $this->getValue($pvalue, $length);
240
            if ($explode) {
241
              $pkey = $this->getValue($pkey, $length);
242
              $values[] = $pkey . "=" . $pvalue; // Explode triggers = combine.
243
            } else {
244
              $values[] = $pkey;
245
              $values[] = $pvalue;
246
            }
247
          }
248
          $value = implode($list_sep, $values);
249
          if ($value == '') {
250
            return false;
251
          }
252
          break;
253
      }
254
    } else if ($tag_empty) {
255
      // If we are just indicating empty values with their key name, return that.
256
      return $key;
257
    } else {
258
      // Otherwise we can skip this variable due to not being defined.
259
      return false;
260
    }
261
 
262
    if ($reserved) {
263
      $value = str_replace($this->reservedEncoded, $this->reserved, $value);
264
    }
265
 
266
    // If we do not need to include the key name, we just return the raw
267
    // value.
268
    if (!$combine || $skip_final_combine) {
269
      return $value;
270
    }
271
 
272
    // Else we combine the key name: foo=bar, if value is not the empty string.
273
    return $key . ($value != '' || $combine_on_empty ? $combine . $value : '');
274
  }
275
 
276
  /**
277
   * Return the type of a passed in value
278
   */
279
  private function getDataType($data)
280
  {
281
    if (is_array($data)) {
282
      reset($data);
283
      if (key($data) !== 0) {
284
        return self::TYPE_MAP;
285
      }
286
      return self::TYPE_LIST;
287
    }
288
    return self::TYPE_SCALAR;
289
  }
290
 
291
  /**
292
   * Utility function that merges multiple combine calls
293
   * for multi-key templates.
294
   */
295
  private function combineList(
296
      $vars,
297
      $sep,
298
      $parameters,
299
      $combine,
300
      $reserved,
301
      $tag_empty,
302
      $combine_on_empty
303
  ) {
304
    $ret = array();
305
    foreach ($vars as $var) {
306
      $response = $this->combine(
307
          $var,
308
          $parameters,
309
          $sep,
310
          $combine,
311
          $reserved,
312
          $tag_empty,
313
          $combine_on_empty
314
      );
315
      if ($response === false) {
316
        continue;
317
      }
318
      $ret[] = $response;
319
    }
320
    return implode($sep, $ret);
321
  }
322
 
323
  /**
324
   * Utility function to encode and trim values
325
   */
326
  private function getValue($value, $length)
327
  {
328
    if ($length) {
329
      $value = substr($value, 0, $length);
330
    }
331
    $value = rawurlencode($value);
332
    return $value;
333
  }
334
}