1<?php
2/**
3 * Simulates a full DokuWiki HTTP Request and allows
4 * runtime inspection.
5 */
6
7use dokuwiki\Input\Input;
8
9/**
10 * Helper class to execute a fake request
11 */
12class TestRequest {
13
14    protected $valid_scripts = array('/doku.php', '/lib/exe/fetch.php', '/lib/exe/detail.php', '/lib/exe/ajax.php');
15    protected $script;
16
17    protected $server = array();
18    protected $session = array();
19    protected $get = array();
20    protected $post = array();
21    protected $data = array();
22
23    /** @var string stores the output buffer, even when it's flushed */
24    protected $output_buffer = '';
25
26    /** @var null|TestRequest the currently running request */
27    static protected $running = null;
28
29    /**
30     * Get a $_SERVER var
31     *
32     * @param string $key
33     * @return mixed
34     */
35    public function getServer($key) {
36        return $this->server[$key];
37    }
38
39    /**
40     * Get a $_SESSION var
41     *
42     * @param string $key
43     * @return mixed
44     */
45    public function getSession($key) {
46        return $this->session[$key];
47    }
48
49    /**
50     * Get a $_GET var
51     *
52     * @param string $key
53     * @return mixed
54     */
55    public function getGet($key) {
56        return $this->get[$key];
57    }
58
59    /**
60     * Get a $_POST var
61     *
62     * @param string $key
63     * @return mixed
64     */
65    public function getPost($key) {
66        return $this->post[$key];
67    }
68
69    /**
70     * Get the script that will execute the request
71     *
72     * @return string
73     */
74    public function getScript() {
75        return $this->script;
76    }
77
78    /**
79     * Set a $_SERVER var
80     *
81     * @param string $key
82     * @param mixed $value
83     */
84    public function setServer($key, $value) {
85        $this->server[$key] = $value;
86    }
87
88    /**
89     * Set a $_SESSION var
90     *
91     * @param string $key
92     * @param mixed $value
93     */
94    public function setSession($key, $value) {
95        $this->session[$key] = $value;
96    }
97
98    /**
99     * Set a $_GET var
100     *
101     * @param string $key
102     * @param mixed $value
103     */
104    public function setGet($key, $value) {
105        $this->get[$key] = $value;
106    }
107
108    /**
109     * Set a $_POST var
110     *
111     * @param string $key
112     * @param mixed $value
113     */
114    public function setPost($key, $value) {
115        $this->post[$key] = $value;
116    }
117
118    /**
119     * Executes the request
120     *
121     * @param string $uri end URL to simulate, needs to be one of the testable scripts
122     * @return TestResponse the resulting output of the request
123     */
124    public function execute($uri = '/doku.php') {
125        global $INPUT;
126
127        // save old environment
128        $server = $_SERVER;
129        $session = $_SESSION;
130        $get = $_GET;
131        $post = $_POST;
132        $request = $_REQUEST;
133        $input = $INPUT;
134
135        // prepare the right URI
136        $this->setUri($uri);
137
138        // import all defined globals into the function scope
139        foreach(array_keys($GLOBALS) as $glb) {
140            global $$glb;
141        }
142
143        // fake environment
144        global $default_server_vars;
145        $_SERVER = array_merge($default_server_vars, $this->server);
146        $_SESSION = $this->session;
147        $_GET = $this->get;
148        $_POST = $this->post;
149        $_REQUEST = array_merge($_GET, $_POST);
150
151        // reset output buffer
152        $this->output_buffer = '';
153
154        // now execute dokuwiki and grep the output
155        self::$running = $this;
156        header_remove();
157        ob_start(array($this, 'ob_start_callback'));
158        $INPUT = new Input();
159        include(DOKU_INC . $this->script);
160        ob_end_flush();
161        self::$running = null;
162
163        // create the response object
164        $response = new TestResponse(
165            $this->output_buffer,
166            // cli sapi doesn't do headers, prefer xdebug_get_headers() which works under cli
167            (function_exists('xdebug_get_headers') ? xdebug_get_headers() : headers_list()),
168            $this->data
169        );
170
171        // reset environment
172        $_SERVER = $server;
173        $_SESSION = $session;
174        $_GET = $get;
175        $_POST = $post;
176        $_REQUEST = $request;
177        $INPUT = $input;
178
179        return $response;
180    }
181
182    /**
183     * Set the virtual URI the request works against
184     *
185     * This parses the given URI and sets any contained GET variables
186     * but will not overwrite any previously set ones (eg. set via setGet()).
187     *
188     * It initializes the $_SERVER['REQUEST_URI'] and $_SERVER['QUERY_STRING']
189     * with all set GET variables.
190     *
191     * @param string $uri end URL to simulate
192     * @throws Exception when an invalid script is passed
193     */
194    protected function setUri($uri) {
195        if(!preg_match('#^(' . join('|', $this->valid_scripts) . ')#', $uri)) {
196            throw new Exception("$uri \n--- only " . join(', ', $this->valid_scripts) . " are supported currently");
197        }
198
199        $params = array();
200        list($uri, $query) = sexplode('?', $uri, 2);
201        if($query) parse_str($query, $params);
202
203        $this->script = substr($uri, 1);
204        $this->get = array_merge($params, $this->get);
205        if(count($this->get)) {
206            $query = '?' . http_build_query($this->get, '', '&');
207            $query = str_replace(
208                array('%3A', '%5B', '%5D'),
209                array(':', '[', ']'),
210                $query
211            );
212            $uri = $uri . $query;
213        }
214
215        $this->setServer('QUERY_STRING', $query);
216        $this->setServer('REQUEST_URI', $uri);
217    }
218
219    /**
220     * Simulate a POST request with the given variables
221     *
222     * @param array $post all the POST parameters to use
223     * @param string $uri end URL to simulate
224     * @return TestResponse
225     */
226    public function post($post = array(), $uri = '/doku.php') {
227        $this->post = array_merge($this->post, $post);
228        $this->setServer('REQUEST_METHOD', 'POST');
229        return $this->execute($uri);
230    }
231
232    /**
233     * Simulate a GET request with the given variables
234     *
235     * @param array $get all the GET parameters to use
236     * @param string $uri end URL to simulate
237     * @return TestResponse
238     */
239    public function get($get = array(), $uri = '/doku.php') {
240        $this->get = array_merge($this->get, $get);
241        $this->setServer('REQUEST_METHOD', 'GET');
242        return $this->execute($uri);
243    }
244
245    /**
246     * Callback for ob_start
247     *
248     * This continues to fill our own buffer, even when some part
249     * of the code askes for flushing the buffers
250     *
251     * @param string $buffer
252     */
253    public function ob_start_callback($buffer) {
254        $this->output_buffer .= $buffer;
255    }
256
257    /**
258     * Access the TestRequest from the executed code
259     *
260     * This allows certain functions to access the TestRequest that is accessing them
261     * to add additional info.
262     *
263     * @return null|TestRequest the currently executed request if any
264     */
265    public static function getRunning() {
266        return self::$running;
267    }
268
269    /**
270     * Store data to be read in the response later
271     *
272     * When called multiple times with the same key, the data is appended to this
273     * key's array
274     *
275     * @param string $key the identifier for this information
276     * @param mixed $value arbitrary data to store
277     */
278    public function addData($key, $value) {
279        if(!isset($this->data[$key])) $this->data[$key] = array();
280        $this->data[$key][] = $value;
281    }
282}
283