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