diff --git a/wire/core/WireInputData.php b/wire/core/WireInputData.php index e32e7462..71073185 100644 --- a/wire/core/WireInputData.php +++ b/wire/core/WireInputData.php @@ -189,6 +189,144 @@ class WireInputData extends Wire implements \ArrayAccess, \IteratorAggregate, \C return $this->__get($key); } + /** + * Find one input var that matches given pattern in name (or optionally value) + * + * @param string $pattern Wildcard string or PCRE regular expression + * @param array|int|string $options + * - `type` (string): Specify "value" to match input value (rather input name), OR prefix pattern with "value=". + * - `sanitizer` (string): Name of sanitizer to run values through (default='', none) + * - `arrays` (bool): Also find on input varibles that are arrays? (default=false) + * @return string|int|float|array|null $value Returns value if found or null if not. + * @since 3.0.163 + * + */ + public function findOne($pattern, $options = array()) { + if(!strlen($pattern)) return null; + if(ctype_alnum(str_replace(array('_', '-', '.'), '', $pattern))) return $this->__get($pattern); + $options['limit'] = 1; + $value = $this->find($pattern, array_merge($options, $options)); + return array_shift($value); // returns null if empty + } + + /** + * Find all input vars that match given pattern in name (or optionally value) + * + * ~~~~~ + * // find all input vars having name beginning with "title_" (i.e. title_en, title_de, title_es) + * $values = $input->post->find('title_*'); + * + * // find all input vars having name with "title" anywhere in it (i.e. title, subtitle, titles, title_de) + * $values = $input->post->find('*title*'); + * + * // find all input vars having value with the term "wire" anywhere, regardless of case + * $values = $input->post->find('/wire/i', [ 'type' => 'value' ]); + * + * // example of result from above find operation: + * $values = [ + * 'title' => 'ProcessWire CMS', + * 'subtitle' => 'Have plenty of caffeine to make sure you are wired', + * 'sidebar' => 'Learn how to rewire a flux capacitor...', + * 'summary' => 'All about the $wire API variable', + * ]; + * ~~~~~ + * + * @param string $pattern Wildcard string or PCRE regular expression + * @param array $options + * - `type` (string): Specify "value" to match input value (rather input name), OR prefix pattern with "value=". + * - `limit` (int): Maximum number of items to return (default=0, no limit) + * - `sanitizer` (string): Name of sanitizer to run values through (default='', none) + * - `arrays` (bool): Also find on input varibles that are arrays? (default=false) + * @return array Returns associative array of values `[ name => value ]` if found, or empty array if none found. + * @since 3.0.163 + * + */ + public function find($pattern, array $options = array()) { + + $defaults = array( + 'type' => 'name', // match on 'name' or 'value' (default='name') + 'limit' => 0, // max allowed matches in return value + 'values' => $this, // use these values rather than those from this input class + 'sanitizer' => '', // sanitizer name to apply found values + 'arrays' => false, // also find on input vars that are arrays? + ); + + if(!strlen($pattern)) return array(); + + $options = array_merge($defaults, $options); + $sanitizer = $this->wire('sanitizer'); /** @var Sanitizer $sanitizer */ + $isRE = in_array($pattern[0], array('/', '!', '%', '#', '@')); + $items = array(); + $count = 0; + $type = $options['type']; + $tests = array(); + + if(!strlen($pattern)) return array(); + + if(strpos($pattern, '=')) { + // pattern indicates "value=pattern" or "name=pattern" + list($type, $pattern) = explode('=', $pattern, 2); + } + + if(!$isRE && strpos($pattern, '*') !== false) { + // wildcard, convert to regex + $a = explode('*', $pattern); + foreach($a as $k => $v) { + if(!strlen($v)) continue; + $a[$k] = preg_quote($v); + $tests[] = $v; + } + $isRE = true; + $pattern = '/^' . implode('.*', $a) . '$/'; + } + + if(!count($tests)) $tests = false; + + foreach($options['values'] as $name => $value) { + + if($options['limit'] && $count >= $options['limit']) break; + + $isArray = is_array($value); + + if($isArray && !$options['arrays']) { + continue; + } else if($isArray && $type === 'value') { + $v = $this->find($pattern, array_merge($options, array('values' => $value))); + if(count($v)) list($items[$name], $count) = array($v, $count + 1); + continue; + } else if($type === 'value') { + $match = $value; + } else { + $match = $name; + } + + if($tests) { + // tests to confirm a preg_match is necessary (wildcard mode only) + $passes = true; + foreach($tests as $test) { + $passes = strpos($match, $test) !== false; + if(!$passes) break; + } + if(!$passes) continue; + } + + if($isRE) { + if(!preg_match($pattern, $match)) continue; + } else { + if(strpos($match, $pattern) === false) continue; + } + + if($options['sanitizer']) { + $value = $sanitizer->sanitize($value, $options['sanitizer']); + } + + $items[$name] = $value; + $count++; + } + + return $items; + } + /** * Clean an array of data * @@ -282,12 +420,25 @@ class WireInputData extends Wire implements \ArrayAccess, \IteratorAggregate, \C public function count() { return count($this->data); } - + + /** + * Remove a value from input + * + * @param string $key Name of input variable to remove value for + * @return $this + * + */ public function remove($key) { $this->offsetUnset($key); return $this; } + /** + * Remove all values from input + * + * @return $this + * + */ public function removeAll() { $this->data = array(); $this->lazy = false; @@ -303,16 +454,30 @@ class WireInputData extends Wire implements \ArrayAccess, \IteratorAggregate, \C $this->offsetUnset($key); } - public function queryString($overrides = array()) { - return http_build_query(array_merge($this->getArray(), $overrides)); + /** + * Return a query string of all input values + * + * Please note returned query string contains non-sanitized/non-validated variables, so this method + * should only be used for specific cases where all input is known to be safe/valid. If that is not + * an option then use PHP’s `http_build_query()` function on your own with known safe/valid values. + * + * #pw-internal + * + * @param array $overrides Associative array of [ name => value ] containing values to override/replace + * @param string $separator String to separate values with, i.e. '&' or '&' (default='&') + * @return string + * @since 3.0.163 + * + */ + public function queryString($overrides = array(), $separator = '&') { + return http_build_query(array_merge($this->getArray(), $overrides), '', $separator); } /** * Maps to Sanitizer functions * - * @param $method - * @param $arguments - * + * @param string $method + * @param array $arguments * @return string|int|array|float|null Returns null when input variable does not exist * @throws WireException *