xref: /dokuwiki/inc/Ajax.php (revision 0489c64b7de1b71fdd124114dd18525156f26327)
1<?php
2
3namespace dokuwiki;
4
5use dokuwiki\Utf8\Sort;
6
7/**
8 * Manage all builtin AJAX calls
9 *
10 * @todo The calls should be refactored out to their own proper classes
11 * @package dokuwiki
12 */
13class Ajax {
14
15    /**
16     * Execute the given call
17     *
18     * @param string $call name of the ajax call
19     */
20    public function __construct($call) {
21        $callfn = 'call' . ucfirst($call);
22        if(method_exists($this, $callfn)) {
23            $this->$callfn();
24        } else {
25            $evt = new Extension\Event('AJAX_CALL_UNKNOWN', $call);
26            if($evt->advise_before()) {
27                print "AJAX call '" . hsc($call) . "' unknown!\n";
28            } else {
29                $evt->advise_after();
30                unset($evt);
31            }
32        }
33    }
34
35    /**
36     * Searches for matching pagenames
37     *
38     * @author Andreas Gohr <andi@splitbrain.org>
39     */
40    protected function callQsearch() {
41        global $lang;
42        global $INPUT;
43
44        $maxnumbersuggestions = 50;
45
46        $query = $INPUT->post->str('q');
47        if(empty($query)) $query = $INPUT->get->str('q');
48        if(empty($query)) return;
49
50        $query = urldecode($query);
51
52        $data = ft_pageLookup($query, true, useHeading('navigation'));
53
54        if(!count($data)) return;
55
56        print '<strong>' . $lang['quickhits'] . '</strong>';
57        print '<ul>';
58        $counter = 0;
59        foreach($data as $id => $title) {
60            if(useHeading('navigation')) {
61                $name = $title;
62            } else {
63                $ns = getNS($id);
64                if($ns) {
65                    $name = noNS($id) . ' (' . $ns . ')';
66                } else {
67                    $name = $id;
68                }
69            }
70            echo '<li>' . html_wikilink(':' . $id, $name) . '</li>';
71
72            $counter++;
73            if($counter > $maxnumbersuggestions) {
74                echo '<li>...</li>';
75                break;
76            }
77        }
78        print '</ul>';
79    }
80
81    /**
82     * Support OpenSearch suggestions
83     *
84     * @link   http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.0
85     * @author Mike Frysinger <vapier@gentoo.org>
86     */
87    protected function callSuggestions() {
88        global $INPUT;
89
90        $query = cleanID($INPUT->post->str('q'));
91        if(empty($query)) $query = cleanID($INPUT->get->str('q'));
92        if(empty($query)) return;
93
94        $data = ft_pageLookup($query);
95        if(!count($data)) return;
96        $data = array_keys($data);
97
98        // limit results to 15 hits
99        $data = array_slice($data, 0, 15);
100        $data = array_map('trim', $data);
101        $data = array_map('noNS', $data);
102        $data = array_unique($data);
103        Sort::sort($data);
104
105        /* now construct a json */
106        $suggestions = array(
107            $query,  // the original query
108            $data,   // some suggestions
109            array(), // no description
110            array()  // no urls
111        );
112
113        header('Content-Type: application/x-suggestions+json');
114        print json_encode($suggestions);
115    }
116
117    /**
118     * Refresh a page lock and save draft
119     *
120     * Andreas Gohr <andi@splitbrain.org>
121     */
122    protected function callLock() {
123        global $ID;
124        global $INFO;
125        global $INPUT;
126
127        $ID = cleanID($INPUT->post->str('id'));
128        if(empty($ID)) return;
129
130        $INFO = pageinfo();
131
132        $response = [
133            'errors' => [],
134            'lock' => '0',
135            'draft' => '',
136        ];
137        if(!$INFO['writable']) {
138            $response['errors'][] = 'Permission to write this page has been denied.';
139            echo json_encode($response);
140            return;
141        }
142
143        if(!checklock($ID)) {
144            lock($ID);
145            $response['lock'] = '1';
146        }
147
148        $draft = new Draft($ID, $INFO['client']);
149        if ($draft->saveDraft()) {
150            $response['draft'] = $draft->getDraftMessage();
151        } else {
152            $response['errors'] = array_merge($response['errors'], $draft->getErrors());
153        }
154        echo json_encode($response);
155    }
156
157    /**
158     * Delete a draft
159     *
160     * @author Andreas Gohr <andi@splitbrain.org>
161     */
162    protected function callDraftdel() {
163        global $INPUT;
164        $id = cleanID($INPUT->str('id'));
165        if(empty($id)) return;
166
167        $client = $_SERVER['REMOTE_USER'];
168        if(!$client) $client = clientIP(true);
169
170        $cname = getCacheName($client . $id, '.draft');
171        @unlink($cname);
172    }
173
174    /**
175     * Return subnamespaces for the Mediamanager
176     *
177     * @author Andreas Gohr <andi@splitbrain.org>
178     */
179    protected function callMedians() {
180        global $conf;
181        global $INPUT;
182
183        // wanted namespace
184        $ns = cleanID($INPUT->post->str('ns'));
185        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
186
187        $lvl = count(explode(':', $ns));
188
189        $data = array();
190        search($data, $conf['mediadir'], 'search_index', array('nofiles' => true), $dir);
191        foreach(array_keys($data) as $item) {
192            $data[$item]['level'] = $lvl + 1;
193        }
194        echo html_buildlist($data, 'idx', 'media_nstree_item', 'media_nstree_li');
195    }
196
197    /**
198     * Return list of files for the Mediamanager
199     *
200     * @author Andreas Gohr <andi@splitbrain.org>
201     */
202    protected function callMedialist() {
203        global $NS;
204        global $INPUT;
205
206        $NS = cleanID($INPUT->post->str('ns'));
207        $sort = $INPUT->post->bool('recent') ? 'date' : 'natural';
208        if($INPUT->post->str('do') == 'media') {
209            tpl_mediaFileList();
210        } else {
211            tpl_mediaContent(true, $sort);
212        }
213    }
214
215    /**
216     * Return the content of the right column
217     * (image details) for the Mediamanager
218     *
219     * @author Kate Arzamastseva <pshns@ukr.net>
220     */
221    protected function callMediadetails() {
222        global $IMG, $JUMPTO, $REV, $fullscreen, $INPUT;
223        $fullscreen = true;
224        require_once(DOKU_INC . 'lib/exe/mediamanager.php');
225
226        $image = '';
227        if($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
228        if(isset($IMG)) $image = $IMG;
229        if(isset($JUMPTO)) $image = $JUMPTO;
230        $rev = false;
231        if(isset($REV) && !$JUMPTO) $rev = $REV;
232
233        html_msgarea();
234        tpl_mediaFileDetails($image, $rev);
235    }
236
237    /**
238     * Returns image diff representation for mediamanager
239     *
240     * @author Kate Arzamastseva <pshns@ukr.net>
241     */
242    protected function callMediadiff() {
243        global $NS;
244        global $INPUT;
245
246        $image = '';
247        if($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
248        $NS = getNS($image);
249        $auth = auth_quickaclcheck("$NS:*");
250        media_diff($image, $NS, $auth, true);
251    }
252
253    /**
254     * Manages file uploads
255     *
256     * @author Kate Arzamastseva <pshns@ukr.net>
257     */
258    protected function callMediaupload() {
259        global $NS, $MSG, $INPUT;
260
261        $id = '';
262        if(isset($_FILES['qqfile']['tmp_name'])) {
263            $id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']);
264        } elseif($INPUT->get->has('qqfile')) {
265            $id = $INPUT->get->str('qqfile');
266        }
267
268        $id = cleanID($id);
269
270        $NS = $INPUT->str('ns');
271        $ns = $NS . ':' . getNS($id);
272
273        $AUTH = auth_quickaclcheck("$ns:*");
274        if($AUTH >= AUTH_UPLOAD) {
275            io_createNamespace("$ns:xxx", 'media');
276        }
277
278        if(isset($_FILES['qqfile']['error']) && $_FILES['qqfile']['error']) unset($_FILES['qqfile']);
279
280        $res = false;
281        if(isset($_FILES['qqfile']['tmp_name'])) $res = media_upload($NS, $AUTH, $_FILES['qqfile']);
282        if($INPUT->get->has('qqfile')) $res = media_upload_xhr($NS, $AUTH);
283
284        if($res) {
285            $result = array(
286                'success' => true,
287                'link' => media_managerURL(array('ns' => $ns, 'image' => $NS . ':' . $id), '&'),
288                'id' => $NS . ':' . $id,
289                'ns' => $NS
290            );
291        } else {
292            $error = '';
293            if(isset($MSG)) {
294                foreach($MSG as $msg) {
295                    $error .= $msg['msg'];
296                }
297            }
298            $result = array(
299                'error' => $error,
300                'ns' => $NS
301            );
302        }
303
304        header('Content-Type: application/json');
305        echo json_encode($result);
306    }
307
308    /**
309     * Return sub index for index view
310     *
311     * @author Andreas Gohr <andi@splitbrain.org>
312     */
313    protected function callIndex() {
314        global $conf;
315        global $INPUT;
316
317        // wanted namespace
318        $ns = cleanID($INPUT->post->str('idx'));
319        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
320
321        $lvl = count(explode(':', $ns));
322
323        $data = array();
324        search($data, $conf['datadir'], 'search_index', array('ns' => $ns), $dir);
325        foreach(array_keys($data) as $item) {
326            $data[$item]['level'] = $lvl + 1;
327        }
328        echo html_buildlist($data, 'idx', 'html_list_index', 'html_li_index');
329    }
330
331    /**
332     * List matching namespaces and pages for the link wizard
333     *
334     * @author Andreas Gohr <gohr@cosmocode.de>
335     */
336    protected function callLinkwiz() {
337        global $conf;
338        global $lang;
339        global $INPUT;
340
341        $q = ltrim(trim($INPUT->post->str('q')), ':');
342        $id = noNS($q);
343        $ns = getNS($q);
344
345        $ns = cleanID($ns);
346        $id = cleanID($id);
347
348        $nsd = utf8_encodeFN(str_replace(':', '/', $ns));
349
350        $data = array();
351        if($q && !$ns) {
352
353            // use index to lookup matching pages
354            $pages = ft_pageLookup($id, true);
355
356            // result contains matches in pages and namespaces
357            // we now extract the matching namespaces to show
358            // them seperately
359            $dirs = array();
360
361            foreach($pages as $pid => $title) {
362                if(strpos(noNS($pid), $id) === false) {
363                    // match was in the namespace
364                    $dirs[getNS($pid)] = 1; // assoc array avoids dupes
365                } else {
366                    // it is a matching page, add it to the result
367                    $data[] = array(
368                        'id' => $pid,
369                        'title' => $title,
370                        'type' => 'f',
371                    );
372                }
373                unset($pages[$pid]);
374            }
375            foreach($dirs as $dir => $junk) {
376                $data[] = array(
377                    'id' => $dir,
378                    'type' => 'd',
379                );
380            }
381
382        } else {
383
384            $opts = array(
385                'depth' => 1,
386                'listfiles' => true,
387                'listdirs' => true,
388                'pagesonly' => true,
389                'firsthead' => true,
390                'sneakyacl' => $conf['sneaky_index'],
391            );
392            if($id) $opts['filematch'] = '^.*\/' . $id;
393            if($id) $opts['dirmatch'] = '^.*\/' . $id;
394            search($data, $conf['datadir'], 'search_universal', $opts, $nsd);
395
396            // add back to upper
397            if($ns) {
398                array_unshift(
399                    $data, array(
400                             'id' => getNS($ns),
401                             'type' => 'u',
402                         )
403                );
404            }
405        }
406
407        // fixme sort results in a useful way ?
408
409        if(!count($data)) {
410            echo $lang['nothingfound'];
411            exit;
412        }
413
414        // output the found data
415        $even = 1;
416        foreach($data as $item) {
417            $even *= -1; //zebra
418
419            if(($item['type'] == 'd' || $item['type'] == 'u') && $item['id'] !== '') $item['id'] .= ':';
420            $link = wl($item['id']);
421
422            echo '<div class="' . (($even > 0) ? 'even' : 'odd') . ' type_' . $item['type'] . '">';
423
424            if($item['type'] == 'u') {
425                $name = $lang['upperns'];
426            } else {
427                $name = hsc($item['id']);
428            }
429
430            echo '<a href="' . $link . '" title="' . hsc($item['id']) . '" class="wikilink1">' . $name . '</a>';
431
432            if(!blank($item['title'])) {
433                echo '<span>' . hsc($item['title']) . '</span>';
434            }
435            echo '</div>';
436        }
437
438    }
439
440}
441