You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

448 lines
12 KiB

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