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