/google_api/Utils/URITemplate.php

  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. class Google_Utils_URITemplate 
  23. const TYPE_MAP = "1"; 
  24. const TYPE_LIST = "2"; 
  25. const TYPE_SCALAR = "4"; 
  26.  
  27. /** 
  28. * @var $operators array  
  29. * These are valid at the start of a template block to 
  30. * modify the way in which the variables inside are 
  31. * processed. 
  32. */ 
  33. private $operators = array( 
  34. "+" => "reserved",  
  35. "/" => "segments",  
  36. "." => "dotprefix",  
  37. "#" => "fragment",  
  38. ";" => "semicolon",  
  39. "?" => "form",  
  40. "&" => "continuation" 
  41. ); 
  42.  
  43. /** 
  44. * @var reserved array 
  45. * These are the characters which should not be URL encoded in reserved 
  46. * strings. 
  47. */ 
  48. private $reserved = array( 
  49. "=", ", ", "!", "@", "|", ":", "/", "?", "#",  
  50. "[", "]", '$', "&", "'", "(", ")", "*", "+", ";" 
  51. ); 
  52. private $reservedEncoded = array( 
  53. "%3D", "%2C", "%21", "%40", "%7C", "%3A", "%2F", "%3F",  
  54. "%23", "%5B", "%5D", "%24", "%26", "%27", "%28", "%29",  
  55. "%2A", "%2B", "%3B" 
  56. ); 
  57.  
  58. public function parse($string, array $parameters) 
  59. return $this->resolveNextSection($string, $parameters); 
  60.  
  61. /** 
  62. * This function finds the first matching {...} block and 
  63. * executes the replacement. It then calls itself to find 
  64. * subsequent blocks, if any.  
  65. */ 
  66. private function resolveNextSection($string, $parameters) 
  67. $start = strpos($string, "{"); 
  68. if ($start === false) { 
  69. return $string; 
  70. $end = strpos($string, "}"); 
  71. if ($end === false) { 
  72. return $string; 
  73. $string = $this->replace($string, $start, $end, $parameters); 
  74. return $this->resolveNextSection($string, $parameters); 
  75.  
  76. private function replace($string, $start, $end, $parameters) 
  77. // We know a data block will have {} round it, so we can strip that. 
  78. $data = substr($string, $start + 1, $end - $start - 1); 
  79.  
  80. // If the first character is one of the reserved operators, it effects 
  81. // the processing of the stream. 
  82. if (isset($this->operators[$data[0]])) { 
  83. $op = $this->operators[$data[0]]; 
  84. $data = substr($data, 1); 
  85. $prefix = ""; 
  86. $prefix_on_missing = false; 
  87.  
  88. switch ($op) { 
  89. case "reserved": 
  90. // Reserved means certain characters should not be URL encoded 
  91. $data = $this->replaceVars($data, $parameters, ", ", null, true); 
  92. break; 
  93. case "fragment": 
  94. // Comma separated with fragment prefix. Bare values only. 
  95. $prefix = "#"; 
  96. $prefix_on_missing = true; 
  97. $data = $this->replaceVars($data, $parameters, ", ", null, true); 
  98. break; 
  99. case "segments": 
  100. // Slash separated data. Bare values only. 
  101. $prefix = "/"; 
  102. $data =$this->replaceVars($data, $parameters, "/"); 
  103. break; 
  104. case "dotprefix": 
  105. // Dot separated data. Bare values only. 
  106. $prefix = "."; 
  107. $prefix_on_missing = true; 
  108. $data = $this->replaceVars($data, $parameters, "."); 
  109. break; 
  110. case "semicolon": 
  111. // Semicolon prefixed and separated. Uses the key name 
  112. $prefix = ";"; 
  113. $data = $this->replaceVars($data, $parameters, ";", "=", false, true, false); 
  114. break; 
  115. case "form": 
  116. // Standard URL format. Uses the key name 
  117. $prefix = "?"; 
  118. $data = $this->replaceVars($data, $parameters, "&", "="); 
  119. break; 
  120. case "continuation": 
  121. // Standard URL, but with leading ampersand. Uses key name. 
  122. $prefix = "&"; 
  123. $data = $this->replaceVars($data, $parameters, "&", "="); 
  124. break; 
  125.  
  126. // Add the initial prefix character if data is valid. 
  127. if ($data || ($data !== false && $prefix_on_missing)) { 
  128. $data = $prefix . $data; 
  129.  
  130. } else { 
  131. // If no operator we replace with the defaults. 
  132. $data = $this->replaceVars($data, $parameters); 
  133. // This is chops out the {...} and replaces with the new section. 
  134. return substr($string, 0, $start) . $data . substr($string, $end + 1); 
  135.  
  136. private function replaceVars( 
  137. $section,  
  138. $parameters,  
  139. $sep = ", ",  
  140. $combine = null,  
  141. $reserved = false,  
  142. $tag_empty = false,  
  143. $combine_on_empty = true 
  144. ) { 
  145. if (strpos($section, ", ") === false) { 
  146. // If we only have a single value, we can immediately process. 
  147. return $this->combine( 
  148. $section,  
  149. $parameters,  
  150. $sep,  
  151. $combine,  
  152. $reserved,  
  153. $tag_empty,  
  154. $combine_on_empty 
  155. ); 
  156. } else { 
  157. // If we have multiple values, we need to split and loop over them. 
  158. // Each is treated individually, then glued together with the 
  159. // separator character. 
  160. $vars = explode(", ", $section); 
  161. return $this->combineList( 
  162. $vars,  
  163. $sep,  
  164. $parameters,  
  165. $combine,  
  166. $reserved,  
  167. false, // Never emit empty strings in multi-param replacements 
  168. $combine_on_empty 
  169. ); 
  170.  
  171. public function combine( 
  172. $key,  
  173. $parameters,  
  174. $sep,  
  175. $combine,  
  176. $reserved,  
  177. $tag_empty,  
  178. $combine_on_empty 
  179. ) { 
  180. $length = false; 
  181. $explode = false; 
  182. $skip_final_combine = false; 
  183. $value = false; 
  184.  
  185. // Check for length restriction. 
  186. if (strpos($key, ":") !== false) { 
  187. list($key, $length) = explode(":", $key); 
  188.  
  189. // Check for explode parameter. 
  190. if ($key[strlen($key) - 1] == "*") { 
  191. $explode = true; 
  192. $key = substr($key, 0, -1); 
  193. $skip_final_combine = true; 
  194.  
  195. // Define the list separator. 
  196. $list_sep = $explode ? $sep : ", "; 
  197.  
  198. if (isset($parameters[$key])) { 
  199. $data_type = $this->getDataType($parameters[$key]); 
  200. switch($data_type) { 
  201. case self::TYPE_SCALAR: 
  202. $value = $this->getValue($parameters[$key], $length); 
  203. break; 
  204. case self::TYPE_LIST: 
  205. $values = array(); 
  206. foreach ($parameters[$key] as $pkey => $pvalue) { 
  207. $pvalue = $this->getValue($pvalue, $length); 
  208. if ($combine && $explode) { 
  209. $values[$pkey] = $key . $combine . $pvalue; 
  210. } else { 
  211. $values[$pkey] = $pvalue; 
  212. $value = implode($list_sep, $values); 
  213. if ($value == '') { 
  214. return ''; 
  215. break; 
  216. case self::TYPE_MAP: 
  217. $values = array(); 
  218. foreach ($parameters[$key] as $pkey => $pvalue) { 
  219. $pvalue = $this->getValue($pvalue, $length); 
  220. if ($explode) { 
  221. $pkey = $this->getValue($pkey, $length); 
  222. $values[] = $pkey . "=" . $pvalue; // Explode triggers = combine. 
  223. } else { 
  224. $values[] = $pkey; 
  225. $values[] = $pvalue; 
  226. $value = implode($list_sep, $values); 
  227. if ($value == '') { 
  228. return false; 
  229. break; 
  230. } else if ($tag_empty) { 
  231. // If we are just indicating empty values with their key name, return that. 
  232. return $key; 
  233. } else { 
  234. // Otherwise we can skip this variable due to not being defined. 
  235. return false; 
  236.  
  237. if ($reserved) { 
  238. $value = str_replace($this->reservedEncoded, $this->reserved, $value); 
  239.  
  240. // If we do not need to include the key name, we just return the raw 
  241. // value. 
  242. if (!$combine || $skip_final_combine) { 
  243. return $value; 
  244.  
  245. // Else we combine the key name: foo=bar, if value is not the empty string. 
  246. return $key . ($value != '' || $combine_on_empty ? $combine . $value : ''); 
  247.  
  248. /** 
  249. * Return the type of a passed in value 
  250. */ 
  251. private function getDataType($data) 
  252. if (is_array($data)) { 
  253. reset($data); 
  254. if (key($data) !== 0) { 
  255. return self::TYPE_MAP; 
  256. return self::TYPE_LIST; 
  257. return self::TYPE_SCALAR; 
  258.  
  259. /** 
  260. * Utility function that merges multiple combine calls 
  261. * for multi-key templates. 
  262. */ 
  263. private function combineList( 
  264. $vars,  
  265. $sep,  
  266. $parameters,  
  267. $combine,  
  268. $reserved,  
  269. $tag_empty,  
  270. $combine_on_empty 
  271. ) { 
  272. $ret = array(); 
  273. foreach ($vars as $var) { 
  274. $response = $this->combine( 
  275. $var,  
  276. $parameters,  
  277. $sep,  
  278. $combine,  
  279. $reserved,  
  280. $tag_empty,  
  281. $combine_on_empty 
  282. ); 
  283. if ($response === false) { 
  284. continue; 
  285. $ret[] = $response; 
  286. return implode($sep, $ret); 
  287.  
  288. /** 
  289. * Utility function to encode and trim values 
  290. */ 
  291. private function getValue($value, $length) 
  292. if ($length) { 
  293. $value = substr($value, 0, $length); 
  294. $value = rawurlencode($value); 
  295. return $value; 
.