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 $callfn = 'call' . ucfirst($call); 26 if(method_exists($this, $callfn)) { 27 $this->$callfn(); 28 } else { 29 $evt = new Event('AJAX_CALL_UNKNOWN', $call); 30 if($evt->advise_before()) { 31 print "AJAX call '" . hsc($call) . "' unknown!\n"; 32 } else { 33 $evt->advise_after(); 34 unset($evt); 35 } 36 } 37 } 38 39 /** 40 * Searches for matching pagenames 41 * 42 * @author Andreas Gohr <andi@splitbrain.org> 43 */ 44 protected function callQsearch() { 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 56 $data = ft_pageLookup($query, true, useHeading('navigation')); 57 58 if($data === []) return; 59 60 print '<strong>' . $lang['quickhits'] . '</strong>'; 61 print '<ul>'; 62 $counter = 0; 63 foreach($data as $id => $title) { 64 if(useHeading('navigation')) { 65 $name = $title; 66 } else { 67 $ns = getNS($id); 68 if($ns) { 69 $name = noNS($id) . ' (' . $ns . ')'; 70 } else { 71 $name = $id; 72 } 73 } 74 echo '<li>' . html_wikilink(':' . $id, $name) . '</li>'; 75 76 $counter++; 77 if($counter > $maxnumbersuggestions) { 78 echo '<li>...</li>'; 79 break; 80 } 81 } 82 print '</ul>'; 83 } 84 85 /** 86 * Support OpenSearch suggestions 87 * 88 * @link http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.0 89 * @author Mike Frysinger <vapier@gentoo.org> 90 */ 91 protected function callSuggestions() { 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 = ft_pageLookup($query); 99 if($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 = [ 111 $query, // the original query 112 $data, // some suggestions 113 [], // no description 114 [], // no urls 115 ]; 116 117 header('Content-Type: application/x-suggestions+json'); 118 print json_encode($suggestions, JSON_THROW_ON_ERROR); 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, JSON_THROW_ON_ERROR); 159 } 160 161 /** 162 * Delete a draft 163 * 164 * @author Andreas Gohr <andi@splitbrain.org> 165 */ 166 protected function callDraftdel() { 167 global $INPUT; 168 $id = cleanID($INPUT->str('id')); 169 if(empty($id)) return; 170 171 $client = $INPUT->server->str('REMOTE_USER'); 172 if(!$client) $client = clientIP(true); 173 174 $draft = new Draft($id, $client); 175 if ($draft->isDraftAvailable() && checkSecurityToken()) { 176 $draft->deleteDraft(); 177 } 178 } 179 180 /** 181 * Return subnamespaces for the Mediamanager 182 * 183 * @author Andreas Gohr <andi@splitbrain.org> 184 */ 185 protected function callMedians() { 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 = []; 196 search($data, $conf['mediadir'], 'search_index', ['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 global $INPUT; 250 251 $image = ''; 252 if($INPUT->has('image')) $image = cleanID($INPUT->str('image')); 253 (new MediaDiff($image))->preference('fromAjax', true)->show(); 254 } 255 256 /** 257 * Manages file uploads 258 * 259 * @author Kate Arzamastseva <pshns@ukr.net> 260 */ 261 protected function callMediaupload() { 262 global $NS, $MSG, $INPUT; 263 264 $id = ''; 265 if(isset($_FILES['qqfile']['tmp_name'])) { 266 $id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']); 267 } elseif($INPUT->get->has('qqfile')) { 268 $id = $INPUT->get->str('qqfile'); 269 } 270 271 $id = cleanID($id); 272 273 $NS = $INPUT->str('ns'); 274 $ns = $NS . ':' . getNS($id); 275 276 $AUTH = auth_quickaclcheck("$ns:*"); 277 if($AUTH >= AUTH_UPLOAD) { 278 io_createNamespace("$ns:xxx", 'media'); 279 } 280 281 if(isset($_FILES['qqfile']['error']) && $_FILES['qqfile']['error']) unset($_FILES['qqfile']); 282 283 $res = false; 284 if(isset($_FILES['qqfile']['tmp_name'])) $res = media_upload($NS, $AUTH, $_FILES['qqfile']); 285 if($INPUT->get->has('qqfile')) $res = media_upload_xhr($NS, $AUTH); 286 287 if($res) { 288 $result = [ 289 'success' => true, 290 'link' => media_managerURL(['ns' => $ns, 'image' => $NS . ':' . $id], '&'), 291 'id' => $NS . ':' . $id, 292 'ns' => $NS 293 ]; 294 } else { 295 $error = ''; 296 if(isset($MSG)) { 297 foreach($MSG as $msg) { 298 $error .= $msg['msg']; 299 } 300 } 301 $result = ['error' => $error, 'ns' => $NS]; 302 } 303 304 header('Content-Type: application/json'); 305 echo json_encode($result, JSON_THROW_ON_ERROR); 306 } 307 308 /** 309 * Return sub index for index view 310 * 311 * @author Andreas Gohr <andi@splitbrain.org> 312 */ 313 protected function callIndex() { 314 global $conf; 315 global $INPUT; 316 317 // wanted namespace 318 $ns = cleanID($INPUT->post->str('idx')); 319 $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 320 321 $lvl = count(explode(':', $ns)); 322 323 $data = []; 324 search($data, $conf['datadir'], 'search_index', ['ns' => $ns], $dir); 325 foreach (array_keys($data) as $item) { 326 $data[$item]['level'] = $lvl + 1; 327 } 328 $idx = new Index; 329 echo html_buildlist($data, 'idx', [$idx,'formatListItem'], [$idx,'tagListItem']); 330 } 331 332 /** 333 * List matching namespaces and pages for the link wizard 334 * 335 * @author Andreas Gohr <gohr@cosmocode.de> 336 */ 337 protected function callLinkwiz() { 338 global $conf; 339 global $lang; 340 global $INPUT; 341 342 $q = ltrim(trim($INPUT->post->str('q')), ':'); 343 $id = noNS($q); 344 $ns = getNS($q); 345 346 $ns = cleanID($ns); 347 348 $id = cleanID($id); 349 350 $nsd = utf8_encodeFN(str_replace(':', '/', $ns)); 351 352 $data = []; 353 if($q !== '' && $ns === '') { 354 355 // use index to lookup matching pages 356 $pages = ft_pageLookup($id, true); 357 358 // If 'useheading' option is 'always' or 'content', 359 // search page titles with original query as well. 360 if ($conf['useheading'] == '1' || $conf['useheading'] == 'content') { 361 $pages = array_merge($pages, ft_pageLookup($q, true, true)); 362 asort($pages, SORT_STRING); 363 } 364 365 // result contains matches in pages and namespaces 366 // we now extract the matching namespaces to show 367 // them seperately 368 $dirs = []; 369 370 foreach($pages as $pid => $title) { 371 if(strpos(getNS($pid), $id) !== false) { 372 // match was in the namespace 373 $dirs[getNS($pid)] = 1; // assoc array avoids dupes 374 } else { 375 // it is a matching page, add it to the result 376 $data[] = ['id' => $pid, 'title' => $title, 'type' => 'f']; 377 } 378 unset($pages[$pid]); 379 } 380 foreach(array_keys($dirs) as $dir) { 381 $data[] = ['id' => $dir, 'type' => 'd']; 382 } 383 384 } else { 385 386 $opts = [ 387 'depth' => 1, 388 'listfiles' => true, 389 'listdirs' => true, 390 'pagesonly' => true, 391 'firsthead' => true, 392 'sneakyacl' => $conf['sneaky_index'] 393 ]; 394 if($id) $opts['filematch'] = '^.*\/' . $id; 395 if($id) $opts['dirmatch'] = '^.*\/' . $id; 396 search($data, $conf['datadir'], 'search_universal', $opts, $nsd); 397 398 // add back to upper 399 if($ns) { 400 array_unshift( 401 $data, ['id' => getNS($ns), 'type' => 'u'] 402 ); 403 } 404 } 405 406 // fixme sort results in a useful way ? 407 408 if(!count($data)) { 409 echo $lang['nothingfound']; 410 exit; 411 } 412 413 // output the found data 414 $even = 1; 415 foreach($data as $item) { 416 $even *= -1; //zebra 417 418 if(($item['type'] == 'd' || $item['type'] == 'u') && $item['id'] !== '') $item['id'] .= ':'; 419 $link = wl($item['id']); 420 421 echo '<div class="' . (($even > 0) ? 'even' : 'odd') . ' type_' . $item['type'] . '">'; 422 423 if($item['type'] == 'u') { 424 $name = $lang['upperns']; 425 } else { 426 $name = hsc($item['id']); 427 } 428 429 echo '<a href="' . $link . '" title="' . hsc($item['id']) . '" class="wikilink1">' . $name . '</a>'; 430 431 if(!blank($item['title'])) { 432 echo '<span>' . hsc($item['title']) . '</span>'; 433 } 434 echo '</div>'; 435 } 436 437 } 438 439} 440