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