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