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