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.
 
 
 
 
 

465 lines
16 KiB

  1. <?php
  2. use dokuwiki\File\PageResolver;
  3. /**
  4. * Simple template replacement action for the bureaucracy plugin
  5. *
  6. * @author Michael Klier <chi@chimeric.de>
  7. */
  8. class helper_plugin_bureaucracy_actiontemplate extends helper_plugin_bureaucracy_action {
  9. var $targetpages;
  10. var $pagename;
  11. /**
  12. * Performs template action
  13. *
  14. * @param helper_plugin_bureaucracy_field[] $fields array with form fields
  15. * @param string $thanks thanks message
  16. * @param array $argv array with entries: template, pagename, separator
  17. * @return array|mixed
  18. *
  19. * @throws Exception
  20. */
  21. public function run($fields, $thanks, $argv) {
  22. global $conf;
  23. [$tpl, $this->pagename] = $argv;
  24. $sep = $argv[2] ?? $conf['sepchar'];
  25. $this->patterns = array();
  26. $this->values = array();
  27. $this->targetpages = array();
  28. $this->prepareNamespacetemplateReplacements();
  29. $this->prepareDateTimereplacements();
  30. $this->prepareLanguagePlaceholder();
  31. $this->prepareNoincludeReplacement();
  32. $this->prepareFieldReplacements($fields);
  33. $evdata = array(
  34. 'patterns' => &$this->patterns,
  35. 'values' => &$this->values,
  36. 'fields' => $fields,
  37. 'action' => $this
  38. );
  39. $event = new Doku_Event('PLUGIN_BUREAUCRACY_PAGENAME', $evdata);
  40. if ($event->advise_before()) {
  41. $this->buildTargetPagename($fields, $sep);
  42. }
  43. $event->advise_after();
  44. //target&template(s) from addpage fields
  45. $this->getAdditionalTargetpages($fields);
  46. //target&template(s) from action field
  47. $tpl = $this->getActionTargetpages($tpl);
  48. if(empty($this->targetpages)) {
  49. throw new Exception(sprintf($this->getLang('e_template'), $tpl));
  50. }
  51. $this->checkTargetPageNames();
  52. $this->processUploads($fields);
  53. $this->replaceAndSavePages($fields);
  54. $ret = $this->buildThankYouPage($thanks);
  55. return $ret;
  56. }
  57. /**
  58. * Prepare and resolve target page
  59. *
  60. * @param helper_plugin_bureaucracy_field[] $fields List of field objects
  61. * @param string $sep Separator between fields for page id
  62. * @throws Exception missing pagename
  63. */
  64. protected function buildTargetPagename($fields, $sep) {
  65. global $ID;
  66. foreach ($fields as $field) {
  67. $pname = $field->getParam('pagename');
  68. if (!is_null($pname)) {
  69. if (is_array($pname)) $pname = implode($sep, $pname);
  70. $this->pagename .= $sep . $pname;
  71. }
  72. }
  73. $resolver = new PageResolver(getNS($ID));
  74. $this->pagename = $resolver->resolveId($this->replace($this->pagename));
  75. if ($this->pagename === '') {
  76. throw new Exception($this->getLang('e_pagename'));
  77. }
  78. }
  79. /**
  80. * Handle templates from addpage field
  81. *
  82. * @param helper_plugin_bureaucracy_field[] $fields List of field objects
  83. * @return array
  84. */
  85. function getAdditionalTargetpages($fields) {
  86. global $ID;
  87. $ns = getNS($ID);
  88. foreach ($fields as $field) {
  89. if (!is_null($field->getParam('page_tpl')) && !is_null($field->getParam('page_tgt')) ) {
  90. $resolver = new PageResolver($ns);
  91. //template
  92. $templatepage = $this->replace($field->getParam('page_tpl'));
  93. $templatepage = $resolver->resolveId($templatepage);
  94. //target
  95. $relativetargetpage = $resolver->resolveId($field->getParam('page_tgt'));
  96. $targetpage = "$this->pagename:$relativetargetpage";
  97. $auth = $this->aclcheck($templatepage); // runas
  98. if ($auth >= AUTH_READ ) {
  99. $this->addParsedTargetpage($targetpage, $templatepage);
  100. }
  101. }
  102. }
  103. }
  104. /**
  105. * Returns raw pagetemplate contents for the ID's namespace
  106. *
  107. * @param string $id the id of the page to be created
  108. * @return string raw pagetemplate content
  109. */
  110. protected function rawPageTemplate($id) {
  111. global $conf;
  112. $path = dirname(wikiFN($id));
  113. if(file_exists($path.'/_template.txt')) {
  114. $tplfile = $path.'/_template.txt';
  115. } else {
  116. // search upper namespaces for templates
  117. $len = strlen(rtrim($conf['datadir'], '/'));
  118. while(strlen($path) >= $len) {
  119. if(file_exists($path.'/__template.txt')) {
  120. $tplfile = $path.'/__template.txt';
  121. break;
  122. }
  123. $path = substr($path, 0, strrpos($path, '/'));
  124. }
  125. }
  126. $tpl = io_readFile($tplfile);
  127. return $tpl;
  128. }
  129. /**
  130. * Load template(s) for targetpage as given via action field
  131. *
  132. * @param string $tpl template name as given in form
  133. * @return string parsed templatename
  134. */
  135. protected function getActionTargetpages($tpl) {
  136. global $USERINFO;
  137. global $conf;
  138. global $ID;
  139. $runas = $this->getConf('runas');
  140. if ($tpl == '_') {
  141. // use namespace template
  142. if (!isset($this->targetpages[$this->pagename])) {
  143. $raw = $this->rawPageTemplate($this->pagename);
  144. $this->noreplace_save($raw);
  145. $this->targetpages[$this->pagename] = pageTemplate(array($this->pagename));
  146. }
  147. } elseif ($tpl !== '!') {
  148. $tpl = $this->replace($tpl);
  149. // resolve templates, but keep references to whole namespaces intact (ending in a colon)
  150. $resolver = new PageResolver(getNS($ID));
  151. if(substr($tpl, -1) == ':') {
  152. $tpl = $tpl.'xxx'; // append a fake page name
  153. $tpl = $resolver->resolveId($tpl);
  154. $tpl = substr($tpl, 0, -3); // cut off fake page name again
  155. } else {
  156. $tpl = $resolver->resolveId($tpl);
  157. }
  158. $backup = array();
  159. if ($runas) {
  160. // Hack user credentials.
  161. $backup = array($_SERVER['REMOTE_USER'], $USERINFO['grps']);
  162. $_SERVER['REMOTE_USER'] = $runas;
  163. $USERINFO['grps'] = array();
  164. }
  165. $template_pages = array();
  166. //search checks acl (as runas)
  167. $opts = array(
  168. 'depth' => 0,
  169. 'listfiles' => true,
  170. 'showhidden' => true
  171. );
  172. search($template_pages, $conf['datadir'], 'search_universal', $opts, str_replace(':', '/', getNS($tpl)));
  173. foreach ($template_pages as $template_page) {
  174. $templatepageid = cleanID($template_page['id']);
  175. // try to replace $tpl path with $this->pagename path in the founded $templatepageid
  176. // - a single-page template will only match on itself and will be replaced,
  177. // other newtargets are pages in same namespace, so aren't changed
  178. // - a namespace as template will match at the namespaces-part of the path of pages in this namespace
  179. // so these newtargets are changed
  180. // if there exist a single-page and a namespace with name $tpl, both are selected
  181. $newTargetpageid = preg_replace('/^' . preg_quote_cb(cleanID($tpl)) . '($|:)/', $this->pagename . '$1', $templatepageid);
  182. if ($newTargetpageid === $templatepageid) {
  183. // only a single-page template or page in the namespace template
  184. // which matches the $tpl path are changed
  185. continue;
  186. }
  187. if (!isset($this->targetpages[$newTargetpageid])) {
  188. $this->addParsedTargetpage($newTargetpageid, $templatepageid);
  189. }
  190. }
  191. if ($runas) {
  192. /* Restore user credentials. */
  193. list($_SERVER['REMOTE_USER'], $USERINFO['grps']) = $backup;
  194. }
  195. }
  196. return $tpl;
  197. }
  198. /**
  199. * Checks for existance and access of target pages
  200. *
  201. * @return mixed
  202. * @throws Exception
  203. */
  204. protected function checkTargetPageNames() {
  205. foreach (array_keys($this->targetpages) as $pname) {
  206. // prevent overriding already existing pages
  207. if (page_exists($pname)) {
  208. throw new Exception(sprintf($this->getLang('e_pageexists'), html_wikilink($pname)));
  209. }
  210. $auth = $this->aclcheck($pname);
  211. if ($auth < AUTH_CREATE) {
  212. throw new Exception($this->getLang('e_denied'));
  213. }
  214. }
  215. }
  216. /**
  217. * Perform replacements on the collected templates, and save the pages.
  218. *
  219. * Note: wrt runas, for changelog are used:
  220. * - $INFO['userinfo']['name']
  221. * - $INPUT->server->str('REMOTE_USER')
  222. */
  223. protected function replaceAndSavePages($fields) {
  224. global $ID;
  225. foreach ($this->targetpages as $pageName => $template) {
  226. // set NSBASE var to make certain dataplugin constructs easier
  227. $this->patterns['__nsbase__'] = '/@NSBASE@/';
  228. $this->values['__nsbase__'] = noNS(getNS($pageName));
  229. $evdata = array(
  230. 'patterns' => &$this->patterns,
  231. 'values' => &$this->values,
  232. 'id' => $pageName,
  233. 'template' => $template,
  234. 'form' => $ID,
  235. 'fields' => $fields
  236. );
  237. $event = new Doku_Event('PLUGIN_BUREAUCRACY_TEMPLATE_SAVE', $evdata);
  238. if($event->advise_before()) {
  239. // save page
  240. saveWikiText(
  241. $evdata['id'],
  242. cleanText($this->replace($evdata['template'], false)),
  243. sprintf($this->getLang('summary'), $ID)
  244. );
  245. }
  246. $event->advise_after();
  247. }
  248. }
  249. /**
  250. * (Callback) Sorts first by namespace depth, next by page ids
  251. *
  252. * @param string $a
  253. * @param string $b
  254. * @return int positive if $b is in deeper namespace than $a, negative higher.
  255. * further sorted by pageids
  256. *
  257. * return an integer less than, equal to, or
  258. * greater than zero if the first argument is considered to be
  259. * respectively less than, equal to, or greater than the second.
  260. */
  261. public function _sorttargetpages($a, $b) {
  262. $ns_diff = substr_count($a, ':') - substr_count($b, ':');
  263. return ($ns_diff === 0) ? strcmp($a, $b) : ($ns_diff > 0 ? -1 : 1);
  264. }
  265. /**
  266. * (Callback) Build content of item
  267. *
  268. * @param array $item
  269. * @return string
  270. */
  271. public function html_list_index($item){
  272. $ret = '';
  273. if($item['type']=='f'){
  274. $ret .= html_wikilink(':'.$item['id']);
  275. } else {
  276. $ret .= '<strong>' . trim(substr($item['id'], strrpos($item['id'], ':', -2)), ':') . '</strong>';
  277. }
  278. return $ret;
  279. }
  280. /**
  281. * Build thanks message, trigger indexing and rendering of new pages.
  282. *
  283. * @param string $thanks
  284. * @return string html of thanks message or when redirect the first page id of created pages
  285. */
  286. protected function buildThankYouPage($thanks) {
  287. global $ID;
  288. $backupID = $ID;
  289. $html = "<p>$thanks</p>";
  290. // Build result tree
  291. $pages = array_keys($this->targetpages);
  292. usort($pages, array($this, '_sorttargetpages'));
  293. $data = array();
  294. $last_folder = array();
  295. foreach ($pages as $ID) {
  296. $lvl = substr_count($ID, ':');
  297. for ($n = 0; $n < $lvl; ++$n) {
  298. if (!isset($last_folder[$n]) || strpos($ID, $last_folder[$n]['id']) !== 0) {
  299. $last_folder[$n] = array(
  300. 'id' => substr($ID, 0, strpos($ID, ':', ($n > 0 ? strlen($last_folder[$n - 1]['id']) : 0) + 1) + 1),
  301. 'level' => $n + 1,
  302. 'open' => 1,
  303. 'type' => null,
  304. );
  305. $data[] = $last_folder[$n];
  306. }
  307. }
  308. $data[] = array('id' => $ID, 'level' => 1 + substr_count($ID, ':'), 'type' => 'f');
  309. }
  310. $index = new dokuwiki\Ui\Index();
  311. $html .= html_buildlist($data, 'idx', array($this, 'html_list_index'), array($index, 'tagListItem'));
  312. // Add indexer bugs for every just-created page
  313. $html .= '<div class="no">';
  314. ob_start();
  315. foreach ($pages as $ID) {
  316. // indexerWebBug uses ID and INFO[exists], but the bureaucracy form
  317. // page always exists, as does the just-saved page, so INFO[exists]
  318. // is correct in any case
  319. tpl_indexerWebBug();
  320. // the iframe will trigger real rendering of the pages to make sure
  321. // any used plugins are initialized (eg. the do plugin)
  322. echo '<iframe src="' . wl($ID, array('do' => 'export_html')) . '" width="1" height="1" style="visibility:hidden"></iframe>';
  323. }
  324. $html .= ob_get_contents();
  325. ob_end_clean();
  326. $html .= '</div>';
  327. $ID = $backupID;
  328. return $html;
  329. }
  330. /**
  331. * move the uploaded files to <pagename>:FILENAME
  332. *
  333. *
  334. * @param helper_plugin_bureaucracy_field[] $fields
  335. * @throws Exception
  336. */
  337. protected function processUploads($fields) {
  338. foreach($fields as $field) {
  339. if($field->getFieldType() !== 'file') continue;
  340. $label = $field->getParam('label');
  341. $file = $field->getParam('file');
  342. $ns = $field->getParam('namespace');
  343. //skip empty files
  344. if(!$file['size']) {
  345. $this->values[$label] = '';
  346. continue;
  347. }
  348. $id = $ns.':'.$file['name'];
  349. resolve_mediaid($this->pagename, $id, $ignored); // resolve relatives
  350. $auth = $this->aclcheck($id); // runas
  351. $move = 'copy_uploaded_file';
  352. //prevent from is_uploaded_file() check
  353. if(defined('DOKU_UNITTEST')) {
  354. $move = 'copy';
  355. }
  356. $res = media_save(
  357. array('name' => $file['tmp_name']),
  358. $id,
  359. false,
  360. $auth,
  361. $move);
  362. if(is_array($res)) throw new Exception($res[0]);
  363. $this->values[$label] = $res;
  364. }
  365. }
  366. /**
  367. * Load page data and do default pattern replacements like namespace templates do
  368. * and add it to list of targetpages
  369. *
  370. * Note: for runas the values of the real user are used for the placeholders
  371. * @NAME@ => $USERINFO['name']
  372. * @MAIL@ => $USERINFO['mail']
  373. * and the replaced value:
  374. * @USER@ => $INPUT->server->str('REMOTE_USER')
  375. *
  376. * @param string $targetpageid pageid of destination
  377. * @param string $templatepageid pageid of template for this targetpage
  378. */
  379. protected function addParsedTargetpage($targetpageid, $templatepageid) {
  380. $tpl = rawWiki($templatepageid);
  381. $this->noreplace_save($tpl);
  382. $data = array(
  383. 'id' => $targetpageid,
  384. 'tpl' => $tpl,
  385. 'doreplace' => true,
  386. );
  387. parsePageTemplate($data);
  388. //collect and apply some other replacements
  389. $patterns = array();
  390. $values = array();
  391. $keys = array('__lang__', '__trans__', '__year__', '__month__', '__day__', '__time__');
  392. foreach($keys as $key) {
  393. $patterns[$key] = $this->patterns[$key];
  394. $values[$key] = $this->values[$key];
  395. }
  396. $this->targetpages[$targetpageid] = preg_replace($patterns, $values, $data['tpl']);
  397. }
  398. }
  399. // vim:ts=4:sw=4:et:enc=utf-8: