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