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