1<?php
2
3/**
4 * Encapsulates access to the $_REQUEST array, making sure used parameters are initialized and
5 * have the correct type.
6 *
7 * All function access the $_REQUEST array by default, if you want to access $_POST or $_GET
8 * explicitly use the $post and $get members.
9 *
10 * @author Andreas Gohr <andi@splitbrain.org>
11 */
12class Input {
13
14    /** @var PostInput Access $_POST parameters */
15    public $post;
16    /** @var GetInput Access $_GET parameters */
17    public $get;
18    /** @var ServerInput Access $_SERVER parameters */
19    public $server;
20
21    protected $access;
22
23    /**
24     * @var Callable
25     */
26    protected $filter;
27
28    /**
29     * Intilizes the Input class and it subcomponents
30     */
31    function __construct() {
32        $this->access = &$_REQUEST;
33        $this->post   = new PostInput();
34        $this->get    = new GetInput();
35        $this->server = new ServerInput();
36    }
37
38    /**
39     * Apply the set filter to the given value
40     *
41     * @param string $data
42     * @return string
43     */
44    protected function applyfilter($data){
45        if(!$this->filter) return $data;
46        return call_user_func($this->filter, $data);
47    }
48
49    /**
50     * Return a filtered copy of the input object
51     *
52     * Expects a callable that accepts one string parameter and returns a filtered string
53     *
54     * @param Callable|string $filter
55     * @return Input
56     */
57    public function filter($filter='stripctl'){
58        $this->filter = $filter;
59        $clone = clone $this;
60        $this->filter = '';
61        return $clone;
62    }
63
64    /**
65     * Check if a parameter was set
66     *
67     * Basically a wrapper around isset. When called on the $post and $get subclasses,
68     * the parameter is set to $_POST or $_GET and to $_REQUEST
69     *
70     * @see isset
71     * @param string $name Parameter name
72     * @return bool
73     */
74    public function has($name) {
75        return isset($this->access[$name]);
76    }
77
78    /**
79     * Remove a parameter from the superglobals
80     *
81     * Basically a wrapper around unset. When NOT called on the $post and $get subclasses,
82     * the parameter will also be removed from $_POST or $_GET
83     *
84     * @see isset
85     * @param string $name Parameter name
86     */
87    public function remove($name) {
88        if(isset($this->access[$name])) {
89            unset($this->access[$name]);
90        }
91        // also remove from sub classes
92        if(isset($this->post) && isset($_POST[$name])) {
93            unset($_POST[$name]);
94        }
95        if(isset($this->get) && isset($_GET[$name])) {
96            unset($_GET[$name]);
97        }
98    }
99
100    /**
101     * Access a request parameter without any type conversion
102     *
103     * @param string    $name     Parameter name
104     * @param mixed     $default  Default to return if parameter isn't set
105     * @param bool      $nonempty Return $default if parameter is set but empty()
106     * @return mixed
107     */
108    public function param($name, $default = null, $nonempty = false) {
109        if(!isset($this->access[$name])) return $default;
110        $value = $this->applyfilter($this->access[$name]);
111        if($nonempty && empty($value)) return $default;
112        return $value;
113    }
114
115    /**
116     * Sets a parameter
117     *
118     * @param string $name Parameter name
119     * @param mixed  $value Value to set
120     */
121    public function set($name, $value) {
122        $this->access[$name] = $value;
123    }
124
125    /**
126     * Get a reference to a request parameter
127     *
128     * This avoids copying data in memory, when the parameter is not set it will be created
129     * and intialized with the given $default value before a reference is returned
130     *
131     * @param string    $name Parameter name
132     * @param mixed     $default If parameter is not set, initialize with this value
133     * @param bool      $nonempty Init with $default if parameter is set but empty()
134     * @return mixed (reference)
135     */
136    public function &ref($name, $default = '', $nonempty = false) {
137        if(!isset($this->access[$name]) || ($nonempty && empty($this->access[$name]))) {
138            $this->set($name, $default);
139        }
140
141        return $this->access[$name];
142    }
143
144    /**
145     * Access a request parameter as int
146     *
147     * @param string    $name     Parameter name
148     * @param int       $default  Default to return if parameter isn't set or is an array
149     * @param bool      $nonempty Return $default if parameter is set but empty()
150     * @return int
151     */
152    public function int($name, $default = 0, $nonempty = false) {
153        if(!isset($this->access[$name])) return $default;
154        if(is_array($this->access[$name])) return $default;
155        $value = $this->applyfilter($this->access[$name]);
156        if($value === '') return $default;
157        if($nonempty && empty($value)) return $default;
158
159        return (int) $value;
160    }
161
162    /**
163     * Access a request parameter as string
164     *
165     * @param string    $name     Parameter name
166     * @param string    $default  Default to return if parameter isn't set or is an array
167     * @param bool      $nonempty Return $default if parameter is set but empty()
168     * @return string
169     */
170    public function str($name, $default = '', $nonempty = false) {
171        if(!isset($this->access[$name])) return $default;
172        if(is_array($this->access[$name])) return $default;
173        $value = $this->applyfilter($this->access[$name]);
174        if($nonempty && empty($value)) return $default;
175
176        return (string) $value;
177    }
178
179    /**
180     * Access a request parameter and make sure it is has a valid value
181     *
182     * Please note that comparisons to the valid values are not done typesafe (request vars
183     * are always strings) however the function will return the correct type from the $valids
184     * array when an match was found.
185     *
186     * @param string $name    Parameter name
187     * @param array  $valids  Array of valid values
188     * @param mixed  $default Default to return if parameter isn't set or not valid
189     * @return null|mixed
190     */
191    public function valid($name, $valids, $default = null) {
192        if(!isset($this->access[$name])) return $default;
193        if(is_array($this->access[$name])) return $default; // we don't allow arrays
194        $value = $this->applyfilter($this->access[$name]);
195        $found = array_search($value, $valids);
196        if($found !== false) return $valids[$found]; // return the valid value for type safety
197        return $default;
198    }
199
200    /**
201     * Access a request parameter as bool
202     *
203     * Note: $nonempty is here for interface consistency and makes not much sense for booleans
204     *
205     * @param string    $name     Parameter name
206     * @param mixed     $default  Default to return if parameter isn't set
207     * @param bool      $nonempty Return $default if parameter is set but empty()
208     * @return bool
209     */
210    public function bool($name, $default = false, $nonempty = false) {
211        if(!isset($this->access[$name])) return $default;
212        if(is_array($this->access[$name])) return $default;
213        $value = $this->applyfilter($this->access[$name]);
214        if($value === '') return $default;
215        if($nonempty && empty($value)) return $default;
216
217        return (bool) $value;
218    }
219
220    /**
221     * Access a request parameter as array
222     *
223     * @param string    $name     Parameter name
224     * @param mixed     $default  Default to return if parameter isn't set
225     * @param bool      $nonempty Return $default if parameter is set but empty()
226     * @return array
227     */
228    public function arr($name, $default = array(), $nonempty = false) {
229        if(!isset($this->access[$name])) return $default;
230        if(!is_array($this->access[$name])) return $default;
231        if($nonempty && empty($this->access[$name])) return $default;
232
233        return (array) $this->access[$name];
234    }
235
236    /**
237     * Create a simple key from an array key
238     *
239     * This is useful to access keys where the information is given as an array key or as a single array value.
240     * For example when the information was submitted as the name of a submit button.
241     *
242     * This function directly changes the access array.
243     *
244     * Eg. $_REQUEST['do']['save']='Speichern' becomes $_REQUEST['do'] = 'save'
245     *
246     * This function returns the $INPUT object itself for easy chaining
247     *
248     * @param string $name
249     * @return Input
250     */
251    public function extract($name){
252        if(!isset($this->access[$name])) return $this;
253        if(!is_array($this->access[$name])) return $this;
254        $keys = array_keys($this->access[$name]);
255        if(!$keys){
256            // this was an empty array
257            $this->remove($name);
258            return $this;
259        }
260        // get the first key
261        $value = array_shift($keys);
262        if($value === 0){
263            // we had a numeric array, assume the value is not in the key
264            $value = array_shift($this->access[$name]);
265        }
266
267        $this->set($name, $value);
268        return $this;
269    }
270}
271
272/**
273 * Internal class used for $_POST access in Input class
274 */
275class PostInput extends Input {
276    protected $access;
277
278    /**
279     * Initialize the $access array, remove subclass members
280     */
281    function __construct() {
282        $this->access = &$_POST;
283    }
284
285    /**
286     * Sets a parameter in $_POST and $_REQUEST
287     *
288     * @param string $name Parameter name
289     * @param mixed  $value Value to set
290     */
291    public function set($name, $value) {
292        parent::set($name, $value);
293        $_REQUEST[$name] = $value;
294    }
295}
296
297/**
298 * Internal class used for $_GET access in Input class
299 */
300class GetInput extends Input {
301    protected $access;
302
303    /**
304     * Initialize the $access array, remove subclass members
305     */
306    function __construct() {
307        $this->access = &$_GET;
308    }
309
310    /**
311     * Sets a parameter in $_GET and $_REQUEST
312     *
313     * @param string $name Parameter name
314     * @param mixed  $value Value to set
315     */
316    public function set($name, $value) {
317        parent::set($name, $value);
318        $_REQUEST[$name] = $value;
319    }
320}
321
322/**
323 * Internal class used for $_SERVER access in Input class
324 */
325class ServerInput extends Input {
326    protected $access;
327
328    /**
329     * Initialize the $access array, remove subclass members
330     */
331    function __construct() {
332        $this->access = &$_SERVER;
333    }
334
335}
336