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