1<?php 2 3namespace dokuwiki; 4 5use dokuwiki\Search\MetadataSearch; 6use dokuwiki\Ui; 7use dokuwiki\Utf8\Sort; 8 9/** 10 * Manage all builtin AJAX calls 11 * 12 * @todo The calls should be refactored out to their own proper classes 13 * @package dokuwiki 14 */ 15class Ajax 16{ 17 /** 18 * Execute the given call 19 * 20 * @param string $call name of the ajax call 21 */ 22 public function __construct($call) 23 { 24 $callfn = 'call' . ucfirst($call); 25 if (method_exists($this, $callfn)) { 26 $this->$callfn(); 27 } else { 28 $evt = new Extension\Event('AJAX_CALL_UNKNOWN', $call); 29 if ($evt->advise_before()) { 30 print "AJAX call '" . hsc($call) . "' unknown!\n"; 31 } else { 32 $evt->advise_after(); 33 unset($evt); 34 } 35 } 36 } 37 38 /** 39 * Searches for matching pagenames 40 * 41 * @author Andreas Gohr <andi@splitbrain.org> 42 */ 43 protected function callQsearch() 44 { 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 $data = (new 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 $data = (new MetadataSearch)->pageLookup($query); 99 if (!count($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 = array( 111 $query, // the original query 112 $data, // some suggestions 113 array(), // no description 114 array() // no urls 115 ); 116 117 header('Content-Type: application/x-suggestions+json'); 118 print json_encode($suggestions); 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); 159 } 160 161 /** 162 * Delete a draft 163 * 164 * @author Andreas Gohr <andi@splitbrain.org> 165 */ 166 protected function callDraftdel() 167 { 168 global $INPUT; 169 $id = cleanID($INPUT->str('id')); 170 if (empty($id)) return; 171 172 $client = $_SERVER['REMOTE_USER']; 173 if (!$client) $client = clientIP(true); 174 175 $cname = getCacheName($client . $id, '.draft'); 176 @unlink($cname); 177 } 178 179 /** 180 * Return subnamespaces for the Mediamanager 181 * 182 * @author Andreas Gohr <andi@splitbrain.org> 183 */ 184 protected function callMedians() 185 { 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 = array(); 196 search($data, $conf['mediadir'], 'search_index', array('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 { 250 global $NS; 251 global $INPUT; 252 253 $image = ''; 254 if ($INPUT->has('image')) $image = cleanID($INPUT->str('image')); 255 $NS = getNS($image); 256 $auth = auth_quickaclcheck("$NS:*"); 257 media_diff($image, $NS, $auth, true); 258 } 259 260 /** 261 * Manages file uploads 262 * 263 * @author Kate Arzamastseva <pshns@ukr.net> 264 */ 265 protected function callMediaupload() { 266 global $NS, $MSG, $INPUT; 267 268 $id = ''; 269 if (isset($_FILES['qqfile']['tmp_name'])) { 270 $id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']); 271 } elseif ($INPUT->get->has('qqfile')) { 272 $id = $INPUT->get->str('qqfile'); 273 } 274 275 $id = cleanID($id); 276 277 $NS = $INPUT->str('ns'); 278 $ns = $NS . ':' . getNS($id); 279 280 $AUTH = auth_quickaclcheck("$ns:*"); 281 if ($AUTH >= AUTH_UPLOAD) { 282 io_createNamespace("$ns:xxx", 'media'); 283 } 284 285 if (isset($_FILES['qqfile']['error']) && $_FILES['qqfile']['error']) unset($_FILES['qqfile']); 286 287 $res = false; 288 if (isset($_FILES['qqfile']['tmp_name'])) $res = media_upload($NS, $AUTH, $_FILES['qqfile']); 289 if ($INPUT->get->has('qqfile')) $res = media_upload_xhr($NS, $AUTH); 290 291 if ($res) { 292 $result = array( 293 'success' => true, 294 'link' => media_managerURL(array('ns' => $ns, 'image' => $NS . ':' . $id), '&'), 295 'id' => $NS . ':' . $id, 296 'ns' => $NS 297 ); 298 } else { 299 $error = ''; 300 if (isset($MSG)) { 301 foreach ($MSG as $msg) { 302 $error .= $msg['msg']; 303 } 304 } 305 $result = array( 306 'error' => $error, 307 'ns' => $NS 308 ); 309 } 310 311 header('Content-Type: application/json'); 312 echo json_encode($result); 313 } 314 315 /** 316 * Return sub index for index view 317 * 318 * @author Andreas Gohr <andi@splitbrain.org> 319 */ 320 protected function callIndex() 321 { 322 global $conf; 323 global $INPUT; 324 325 // wanted namespace 326 $ns = cleanID($INPUT->post->str('idx')); 327 $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 328 329 $lvl = count(explode(':', $ns)); 330 331 $data = array(); 332 search($data, $conf['datadir'], 'search_index', array('ns' => $ns), $dir); 333 foreach (array_keys($data) as $item) { 334 $data[$item]['level'] = $lvl + 1; 335 } 336 $idx = new Ui\Index; 337 echo html_buildlist($data, 'idx', [$idx,'formatListItem'], [$idx,'tagListItem']); 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 $pages = (new MetadataSearch)->pageLookup($id, true); 365 366 // result contains matches in pages and namespaces 367 // we now extract the matching namespaces to show 368 // them seperately 369 $dirs = array(); 370 371 foreach ($pages as $pid => $title) { 372 if (strpos(noNS($pid), $id) === false) { 373 // match was in the namespace 374 $dirs[getNS($pid)] = 1; // assoc array avoids dupes 375 } else { 376 // it is a matching page, add it to the result 377 $data[] = array( 378 'id' => $pid, 379 'title' => $title, 380 'type' => 'f', 381 ); 382 } 383 unset($pages[$pid]); 384 } 385 foreach ($dirs as $dir => $junk) { 386 $data[] = array( 387 'id' => $dir, 388 'type' => 'd', 389 ); 390 } 391 392 } else { 393 394 $opts = array( 395 'depth' => 1, 396 'listfiles' => true, 397 'listdirs' => true, 398 'pagesonly' => true, 399 'firsthead' => true, 400 'sneakyacl' => $conf['sneaky_index'], 401 ); 402 if ($id) $opts['filematch'] = '^.*\/' . $id; 403 if ($id) $opts['dirmatch'] = '^.*\/' . $id; 404 search($data, $conf['datadir'], 'search_universal', $opts, $nsd); 405 406 // add back to upper 407 if ($ns) { 408 array_unshift( 409 $data, array( 410 'id' => getNS($ns), 411 'type' => 'u', 412 ) 413 ); 414 } 415 } 416 417 // fixme sort results in a useful way ? 418 419 if (!count($data)) { 420 echo $lang['nothingfound']; 421 exit; 422 } 423 424 // output the found data 425 $even = 1; 426 foreach ($data as $item) { 427 $even *= -1; //zebra 428 429 if (($item['type'] == 'd' || $item['type'] == 'u') && $item['id'] !== '') $item['id'] .= ':'; 430 $link = wl($item['id']); 431 432 echo '<div class="' . (($even > 0) ? 'even' : 'odd') . ' type_' . $item['type'] . '">'; 433 434 if ($item['type'] == 'u') { 435 $name = $lang['upperns']; 436 } else { 437 $name = hsc($item['id']); 438 } 439 440 echo '<a href="' . $link . '" title="' . hsc($item['id']) . '" class="wikilink1">' . $name . '</a>'; 441 442 if (!blank($item['title'])) { 443 echo '<span>' . hsc($item['title']) . '</span>'; 444 } 445 echo '</div>'; 446 } 447 448 } 449 450} 451