Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body