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