PageRenderTime 223ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/wire/core/WireInput.php

http://github.com/ryancramerdesign/ProcessWire
PHP | 522 lines | 231 code | 49 blank | 242 comment | 90 complexity | a3fc2cba9253e5c7b351ded08b788077 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. * ProcessWire WireInputData and WireInput
  4. *
  5. * WireInputData and the WireInput class together form a simple
  6. * front end to PHP's $_GET, $_POST, and $_COOKIE superglobals.
  7. *
  8. * ProcessWire 2.x
  9. * Copyright (C) 2015 by Ryan Cramer
  10. * This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/
  11. *
  12. * https://processwire.com
  13. *
  14. */
  15. /**
  16. * WireInputData manages one of GET, POST, COOKIE, or whitelist
  17. *
  18. * Vars retrieved from here will not have to consider magic_quotes.
  19. * No sanitization or filtering is done, other than disallowing multi-dimensional arrays in input.
  20. *
  21. * WireInputData specifically manages one of: get, post, cookie or whitelist, whereas the Input class
  22. * provides access to the 3 InputData instances.
  23. *
  24. * Each WireInputData is not instantiated unless specifically asked for.
  25. *
  26. *
  27. * @link https://processwire.com/api/variables/input/ Offical $input API variable Documentation
  28. *
  29. * @method string name($varName) Sanitize to ProcessWire name format
  30. * @method string varName($varName) Sanitize to PHP variable name format
  31. * @method string fieldName($varName) Sanitize to ProcessWire Field name format
  32. * @method string templateName($varName) Sanitize to ProcessWire Template name format
  33. * @method string pageName($varName) Sanitize to ProcessWire Page name format
  34. * @method string pageNameTranslate($varName) Sanitize to ProcessWire Page name format with translation of non-ASCII characters to ASCII equivalents
  35. * @method string filename($varName) Sanitize to valid file basename as used by filenames in ProcessWire
  36. * @method string pagePathName($varName) Sanitize to what could be a valid page path in ProcessWire
  37. * @method string email($varName) Sanitize email address, converting to blank if invalid
  38. * @method string emailHeader($varName) Sanitize string for use in an email header
  39. * @method string text($varName) Sanitize to single line of text up to 255 characters (1024 bytes max), HTML markup is removed
  40. * @method string textarea($varName) Sanitize to multi-line text up to 16k characters (48k bytes), HTML markup is removed
  41. * @method string url($varName) Sanitize to a valid URL, or convert to blank if it can't be sanitized
  42. * @method string selectorField($varName) Sanitize a field name for use in a selector string
  43. * @method string selectorValue($varName) Sanitize a value for use in a selector string
  44. * @method string entities($varName) Return an entity encoded version of the value
  45. * @method string purify($varName) Return a value run through HTML Purifier (value assumed to contain HTML)
  46. * @method string string($varName) Return a value guaranteed to be a string, regardless of what type $varName is. Does not sanitize.
  47. * @method string date($varName, $dateFormat) Validate and return $varName in the given PHP date() or strftime() format.
  48. * @method int int($varName, $min = 0, $max = null) Sanitize value to integer with optional min and max. Unsigned if max >= 0, signed if max < 0.
  49. * @method int intUnsigned($varName, $min = null, $max = null) Sanitize value to unsigned integer with optional min and max.
  50. * @method int intSigned($varName, $min = null, $max = null) Sanitize value to signed integer with optional min and max.
  51. * @method float float($varName, $min = null, $max = null, $precision = null) Sanitize value to float with optional min and max values.
  52. * @method array array($varName, $sanitizer = null) Sanitize array or CSV String to an array, optionally running elements through specified $sanitizer.
  53. * @method array intArray($varName, $min = 0, $max = null) Sanitize array or CSV string to an array of integers with optional min and max values.
  54. * @method string|null option($varName, array $allowedValues) Return value of $varName only if it exists in $allowedValues.
  55. * @method array options($varName, array $allowedValues) Return all values in array $varName that also exist in $allowedValues.
  56. * @method bool bool($varName) Sanitize value to boolean (true or false)
  57. *
  58. *
  59. *
  60. */
  61. class WireInputData implements ArrayAccess, IteratorAggregate, Countable {
  62. protected $stripSlashes = false;
  63. protected $data = array();
  64. public function __construct(array $input = array()) {
  65. $this->stripSlashes = get_magic_quotes_gpc();
  66. $this->setArray($input);
  67. }
  68. public function setArray(array $input) {
  69. foreach($input as $key => $value) $this->__set($key, $value);
  70. return $this;
  71. }
  72. public function getArray() {
  73. return $this->data;
  74. }
  75. public function __set($key, $value) {
  76. if(is_string($value) && $this->stripSlashes) $value = stripslashes($value);
  77. if(is_array($value)) $value = $this->cleanArray($value);
  78. $this->data[$key] = $value;
  79. }
  80. protected function cleanArray(array $a) {
  81. $clean = array();
  82. foreach($a as $key => $value) {
  83. if(is_array($value)) continue; // we only allow one dimensional arrays
  84. if(is_string($value) && $this->stripSlashes) $value = stripslashes($value);
  85. $clean[$key] = $value;
  86. }
  87. return $clean;
  88. }
  89. public function setStripSlashes($stripSlashes) {
  90. $this->stripSlashes = $stripSlashes ? true : false;
  91. }
  92. public function __get($key) {
  93. if($key == 'whitelist') return $this->whitelist;
  94. return isset($this->data[$key]) ? $this->data[$key] : null;
  95. }
  96. public function getIterator() {
  97. return new ArrayObject($this->data);
  98. }
  99. public function offsetExists($key) {
  100. return isset($this->data[$key]);
  101. }
  102. public function offsetGet($key) {
  103. return $this->__get($key);
  104. }
  105. public function offsetSet($key, $value) {
  106. $this->__set($key, $value);
  107. }
  108. public function offsetUnset($key) {
  109. unset($this->data[$key]);
  110. }
  111. public function count() {
  112. return count($this->data);
  113. }
  114. public function removeAll() {
  115. $this->data = array();
  116. }
  117. public function __isset($key) {
  118. return $this->offsetExists($key);
  119. }
  120. public function __unset($key) {
  121. return $this->offsetUnset($key);
  122. }
  123. public function queryString() {
  124. return http_build_query($this->getArray());
  125. }
  126. /**
  127. * Maps to Sanitizer functions
  128. *
  129. * @param $method
  130. * @param $arguments
  131. *
  132. * @return string|int|array|float|null Returns null when input variable does not exist
  133. * @throws WireException
  134. *
  135. */
  136. public function __call($method, $arguments) {
  137. $sanitizer = wire('sanitizer');
  138. $methodName = $method;
  139. $method = ltrim($method, '_');
  140. if(!method_exists($sanitizer, $method)) {
  141. $method = "___$method";
  142. if(!method_exists($sanitizer, $method)) {
  143. $method = ltrim($method, "_");
  144. throw new WireException("Unknown method '$method' - Specify a valid Sanitizer or WireInputData method.");
  145. }
  146. }
  147. if(!isset($arguments[0])) {
  148. throw new WireException("For method '$method' specify an input variable name for first argument");
  149. }
  150. $arguments[0] = $this->__get($arguments[0]);
  151. if(is_null($arguments[0])) {
  152. // value is not present in input at all
  153. // @todo do you want to provide an alternate means of handling this situation?
  154. }
  155. return call_user_func_array(array($sanitizer, $method), $arguments);
  156. }
  157. }
  158. /**
  159. * Manages the group of GET, POST, COOKIE and whitelist vars, each of which is a WireInputData object.
  160. *
  161. * @link https://processwire.com/api/variables/input/ Offical $input API variable Documentation
  162. *
  163. * @property string[] $urlSegments Retrieve all URL segments (array). This requires url segments are enabled on the template of the requested page. You can turn it on or off under the url tab when editing a template.
  164. * @property WireInputVars $post POST variables
  165. * @property WireInputVars $get GET variables
  166. * @property WireInputVars $cookie COOKIE variables
  167. * @property WireInputVars $whitelist Whitelisted variables
  168. * @property int $pageNum Current page number (where 1 is first)
  169. * @property string $urlSegmentsStr String of current URL segments, separated by slashes, i.e. a/b/c
  170. * @property string $urlSegmentStr Alias of urlSegmentsStr
  171. * @property string $url Current requested URL including page numbers and URL segments, excluding query string.
  172. * @property string $httpUrl Like $url but includes the scheme/protcol and hostname.
  173. * @property string $queryString Current query string
  174. * @property string $scheme Current scheme/protcol, i.e. http or https
  175. *
  176. * @property string $urlSegment1 First URL segment
  177. * @property string $urlSegment2 Second URL segment
  178. * @property string $urlSegment3 Third URL segment
  179. * @property string $urlSegment4 Fourth URL segment
  180. * @property string $urlSegment5 Fifth URL segment, and so on...
  181. *
  182. */
  183. class WireInput {
  184. protected $getVars = null;
  185. protected $postVars = null;
  186. protected $cookieVars = null;
  187. protected $whitelist = null;
  188. protected $urlSegments = array();
  189. protected $pageNum = 1;
  190. /**
  191. * Retrieve a GET value or all GET values
  192. *
  193. * @param string $key
  194. * If populated, returns the value corresponding to the key or NULL if it doesn't exist.
  195. * If blank, returns reference to the WireDataInput containing all GET vars.
  196. * @return null|mixed|WireInputData
  197. *
  198. */
  199. public function get($key = '') {
  200. if(is_null($this->getVars)) {
  201. $this->getVars = new WireInputData($_GET);
  202. $this->getVars->offsetUnset('it');
  203. }
  204. return $key ? $this->getVars->__get($key) : $this->getVars;
  205. }
  206. /**
  207. * Retrieve a POST value or all POST values
  208. *
  209. * @param string $key
  210. * If populated, returns the value corresponding to the key or NULL if it doesn't exist.
  211. * If blank, returns reference to the WireDataInput containing all POST vars.
  212. * @return null|mixed|WireInputData
  213. *
  214. */
  215. public function post($key = '') {
  216. if(is_null($this->postVars)) $this->postVars = new WireInputData($_POST);
  217. return $key ? $this->postVars->__get($key) : $this->postVars;
  218. }
  219. /**
  220. * Retrieve a COOKIE value or all COOKIE values
  221. *
  222. * @param string $key
  223. * If populated, returns the value corresponding to the key or NULL if it doesn't exist.
  224. * If blank, returns reference to the WireDataInput containing all COOKIE vars.
  225. * @return null|mixed|WireInputData
  226. *
  227. */
  228. public function cookie($key = '') {
  229. if(is_null($this->cookieVars)) $this->cookieVars = new WireInputData($_COOKIE);
  230. return $key ? $this->cookieVars->__get($key) : $this->cookieVars;
  231. }
  232. /**
  233. * Get or set a whitelist var
  234. *
  235. * Whitelist vars are used by modules and templates and assumed to be clean.
  236. *
  237. * The whitelist is a list of variables specifically set by the application as clean for use elsewhere in the application.
  238. * Only the version returned from this method should be considered clean.
  239. * This whitelist is not specifically used by ProcessWire unless you populate it from your templates or the API.
  240. *
  241. * @param string $key
  242. * If $key is blank, it assumes you are asking to return the entire whitelist.
  243. * If $key and $value are populated, it adds the value to the whitelist.
  244. * If $key is an array, it adds all the values present in the array to the whitelist.
  245. * If $value is ommited, it assumes you are asking for a value with $key, in which case it returns it.
  246. * @param mixed $value
  247. * See explanation for the $key param
  248. * @return null|mixed|WireInputData
  249. * See explanation for the $key param
  250. *
  251. */
  252. public function whitelist($key = '', $value = null) {
  253. if(is_null($this->whitelist)) $this->whitelist = new WireInputData();
  254. if(!$key) return $this->whitelist;
  255. if(is_array($key)) return $this->whitelist->setArray($key);
  256. if(is_null($value)) return $this->whitelist->__get($key);
  257. $this->whitelist->__set($key, $value);
  258. return $this->whitelist;
  259. }
  260. /**
  261. * Retrieve the URL segment with index $num
  262. *
  263. * Note that the index is 1 based (not 0 based).
  264. * The maximum segments allowed can be adjusted in your /site/config.php.
  265. *
  266. * @param int $num Retrieve the $n'th URL segment (integer).
  267. * @return string Returns a blank string if the specified index is not found
  268. *
  269. */
  270. public function urlSegment($num = 1) {
  271. if($num < 1) $num = 1;
  272. return isset($this->urlSegments[$num]) ? $this->urlSegments[$num] : '';
  273. }
  274. /**
  275. * Set a URL segment value
  276. *
  277. * To unset, specify NULL as the value.
  278. *
  279. * @param int $num Number of this URL segment (1 based)
  280. * @param string|null $value
  281. *
  282. */
  283. public function setUrlSegment($num, $value) {
  284. $num = (int) $num;
  285. if(is_null($value)) {
  286. // unset
  287. $n = 0;
  288. $urlSegments = array();
  289. foreach($this->urlSegments as $k => $v) {
  290. if($k == $num) continue;
  291. $urlSegments[++$n] = $v;
  292. }
  293. $this->urlSegments = $urlSegments;
  294. } else {
  295. // set
  296. $this->urlSegments[$num] = wire('sanitizer')->name($value);
  297. }
  298. }
  299. /**
  300. * Return the current page number.
  301. *
  302. * First page number is 1 (not 0).
  303. *
  304. * @return int
  305. *
  306. */
  307. public function pageNum() {
  308. return $this->pageNum;
  309. }
  310. /**
  311. * Set the current page number.
  312. *
  313. * Note that the first page should be 1 (not 0).
  314. *
  315. * @param int $num
  316. *
  317. */
  318. public function setPageNum($num) {
  319. $this->pageNum = (int) $num;
  320. }
  321. /**
  322. * Retrieve the get, post, cookie or whitelist vars using a direct reference, i.e. $input->cookie
  323. *
  324. * Can also be used with URL segments, i.e. $input->urlSegment1, $input->urlSegment2, $input->urlSegment3, etc.
  325. * And can also be used for $input->pageNum.
  326. *
  327. * @param string $key
  328. * @return string|int|null
  329. *
  330. */
  331. public function __get($key) {
  332. if($key == 'pageNum') return $this->pageNum;
  333. if($key == 'urlSegments') return $this->urlSegments;
  334. if($key == 'urlSegmentsStr' || $key == 'urlSegmentStr') return $this->urlSegmentStr();
  335. if($key == 'url') return $this->url();
  336. if($key == 'httpUrl' || $key == 'httpURL') return $this->httpUrl();
  337. if($key == 'fragment') return $this->fragment();
  338. if($key == 'queryString') return $this->queryString();
  339. if($key == 'scheme') return $this->scheme();
  340. if(strpos($key, 'urlSegment') === 0) {
  341. if(strlen($key) > 10) $num = (int) substr($key, 10);
  342. else $num = 1;
  343. return $this->urlSegment($num);
  344. }
  345. $value = null;
  346. $gpc = array('get', 'post', 'cookie', 'whitelist');
  347. if(in_array($key, $gpc)) {
  348. $value = $this->$key();
  349. } else {
  350. // Like PHP's $_REQUEST where accessing $input->var considers get/post/cookie/whitelist
  351. // what it actually considers depends on what's set in the $config->wireInputOrder variable
  352. $order = (string) wire('config')->wireInputOrder;
  353. if(!$order) return null;
  354. $types = explode(' ', $order);
  355. foreach($types as $t) {
  356. if(!in_array($t, $gpc)) continue;
  357. $value = $this->$t($key);
  358. if(!is_null($value)) break;
  359. }
  360. }
  361. return $value;
  362. }
  363. /**
  364. * Get the string of URL segments separated by slashes
  365. *
  366. * Note that return value lacks leading or trailing slashes
  367. *
  368. * @return string
  369. *
  370. */
  371. public function urlSegmentStr() {
  372. return implode('/', $this->urlSegments);
  373. }
  374. public function __isset($key) {
  375. return $this->__get($key) !== null;
  376. }
  377. /**
  378. * URL that initiated the current request, including URL segments
  379. *
  380. * Note that this does not include query string or fragment
  381. *
  382. * @return string
  383. *
  384. */
  385. public function url() {
  386. $url = '';
  387. /** @var Page $page */
  388. $page = wire('page');
  389. if($page && $page->id) {
  390. // pull URL from page
  391. $url = wire('page')->url;
  392. $segmentStr = $this->urlSegmentStr();
  393. $pageNum = $this->pageNum();
  394. if(strlen($segmentStr) || $pageNum > 1) {
  395. if($segmentStr) $url = rtrim($url, '/') . '/' . $segmentStr;
  396. if($pageNum > 1) $url = rtrim($url, '/') . '/' . wire('config')->pageNumUrlPrefix . $pageNum;
  397. if(isset($_SERVER['REQUEST_URI'])) {
  398. $info = parse_url($_SERVER['REQUEST_URI']);
  399. if(!empty($info['path']) && substr($info['path'], -1) == '/') $url .= '/'; // trailing slash
  400. }
  401. if($pageNum > 1) {
  402. if($page->template->slashPageNum == 1) {
  403. if(substr($url, -1) != '/') $url .= '/';
  404. } else if($page->template->slashPageNum == -1) {
  405. if(substr($url, -1) == '/') $url = rtrim($url, '/');
  406. }
  407. } else if(strlen($segmentStr)) {
  408. if($page->template->slashUrlSegments == 1) {
  409. if(substr($url, -1) != '/') $url .= '/';
  410. } else if($page->template->slashUrlSegments == -1) {
  411. if(substr($url, -1) == '/') $url = rtrim($url, '/');
  412. }
  413. }
  414. }
  415. } else if(isset($_SERVER['REQUEST_URI'])) {
  416. // page not yet available, attempt to pull URL from request uri
  417. $parts = explode('/', $_SERVER['REQUEST_URI']);
  418. foreach($parts as $part) {
  419. $url .= "/" . wire('sanitizer')->pageName($part);
  420. }
  421. $info = parse_url($_SERVER['REQUEST_URI']);
  422. if(!empty($info['path']) && substr($info['path'], -1) == '/') {
  423. $url = rtrim($url, '/') . '/'; // trailing slash
  424. }
  425. }
  426. return $url;
  427. }
  428. /**
  429. * URL including scheme
  430. *
  431. * @return string
  432. *
  433. */
  434. public function httpUrl() {
  435. return $this->scheme() . '://' . wire('config')->httpHost . $this->url();
  436. }
  437. /**
  438. * Anchor/fragment for current request (i.e. #fragment)
  439. *
  440. * Note that this is not sanitized. Fragments generally can't be seen
  441. * by the server, so this function may be useless.
  442. *
  443. * @return string
  444. *
  445. */
  446. public function fragment() {
  447. if(strpos($_SERVER['REQUEST_URI'], '#') === false) return '';
  448. $info = parse_url($_SERVER['REQUEST_URI']);
  449. return empty($info['fragment']) ? '' : $info['fragment'];
  450. }
  451. /**
  452. * Return the query string that was part of this request or blank if none
  453. *
  454. * Note that this is not sanitized.
  455. *
  456. * @return string
  457. *
  458. */
  459. public function queryString() {
  460. return $this->getVars->queryString();
  461. }
  462. /**
  463. * Return the current access scheme/protocol
  464. *
  465. * Note that this is only useful for http/https, as we don't detect other schemes.
  466. *
  467. * @return string either "https" or "http"
  468. *
  469. */
  470. public function scheme() {
  471. return wire('config')->https ? 'https' : 'http';
  472. }
  473. }