1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Esther Brunner <wikidesign@gmail.com> 5 * @author Christopher Smith <chris@jalakai.co.uk> 6 * @author Gina Häußge, Michael Klier <dokuwiki@chimeric.de> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) die(); 11 12if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 13if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 14if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 15 16require_once(DOKU_INC.'inc/search.php'); 17 18class helper_plugin_include extends DokuWiki_Plugin { // DokuWiki_Helper_Plugin 19 20 var $defaults = array(); 21 var $sec_close = true; 22 var $taghelper = null; 23 var $includes = array(); // deprecated - compatibility code for the blog plugin 24 25 /** 26 * Constructor loads default config settings once 27 */ 28 function helper_plugin_include() { 29 $this->defaults['firstsec'] = $this->getConf('firstseconly'); 30 $this->defaults['editbtn'] = $this->getConf('showeditbtn'); 31 $this->defaults['taglogos'] = $this->getConf('showtaglogos'); 32 $this->defaults['footer'] = $this->getConf('showfooter'); 33 $this->defaults['redirect'] = $this->getConf('doredirect'); 34 $this->defaults['date'] = $this->getConf('showdate'); 35 $this->defaults['user'] = $this->getConf('showuser'); 36 $this->defaults['comments'] = $this->getConf('showcomments'); 37 $this->defaults['linkbacks'] = $this->getConf('showlinkbacks'); 38 $this->defaults['tags'] = $this->getConf('showtags'); 39 $this->defaults['link'] = $this->getConf('showlink'); 40 $this->defaults['permalink'] = $this->getConf('showpermalink'); 41 $this->defaults['indent'] = $this->getConf('doindent'); 42 $this->defaults['linkonly'] = $this->getConf('linkonly'); 43 $this->defaults['inline'] = false; 44 } 45 46 /** 47 * Available methods for other plugins 48 */ 49 function getMethods() { 50 $result = array(); 51 $result[] = array( 52 'name' => 'get_flags', 53 'desc' => 'overrides standard values for showfooter and firstseconly settings', 54 'params' => array('flags' => 'array'), 55 ); 56 return $result; 57 } 58 59 /** 60 * Overrides standard values for showfooter and firstseconly settings 61 */ 62 function get_flags($setflags) { 63 // load defaults 64 $flags = array(); 65 $flags = $this->defaults; 66 foreach ($setflags as $flag) { 67 switch ($flag) { 68 case 'footer': 69 $flags['footer'] = 1; 70 break; 71 case 'nofooter': 72 $flags['footer'] = 0; 73 break; 74 case 'firstseconly': 75 case 'firstsectiononly': 76 $flags['firstsec'] = 1; 77 break; 78 case 'fullpage': 79 $flags['firstsec'] = 0; 80 break; 81 case 'noheader': 82 $flags['noheader'] = 1; 83 break; 84 case 'editbtn': 85 case 'editbutton': 86 $flags['editbtn'] = 1; 87 break; 88 case 'noeditbtn': 89 case 'noeditbutton': 90 $flags['editbtn'] = 0; 91 break; 92 case 'permalink': 93 $flags['permalink'] = 1; 94 break; 95 case 'nopermalink': 96 $flags['permalink'] = 0; 97 break; 98 case 'redirect': 99 $flags['redirect'] = 1; 100 break; 101 case 'noredirect': 102 $flags['redirect'] = 0; 103 break; 104 case 'link': 105 $flags['link'] = 1; 106 break; 107 case 'nolink': 108 $flags['link'] = 0; 109 break; 110 case 'user': 111 $flags['user'] = 1; 112 break; 113 case 'nouser': 114 $flags['user'] = 0; 115 break; 116 case 'comments': 117 $flags['comments'] = 1; 118 break; 119 case 'nocomments': 120 $flags['comments'] = 0; 121 break; 122 case 'linkbacks': 123 $flags['linkbacks'] = 1; 124 break; 125 case 'nolinkbacks': 126 $flags['linkbacks'] = 0; 127 break; 128 case 'tags': 129 $flags['tags'] = 1; 130 break; 131 case 'notags': 132 $flags['tags'] = 0; 133 break; 134 case 'date': 135 $flags['date'] = 1; 136 break; 137 case 'nodate': 138 $flags['date'] = 0; 139 break; 140 case 'indent': 141 $flags['indent'] = 1; 142 break; 143 case 'noindent': 144 $flags['indent'] = 0; 145 break; 146 case 'linkonly': 147 $flags['linkonly'] = 1; 148 break; 149 case 'nolinkonly': 150 case 'include_content': 151 $flags['linkonly'] = 0; 152 break; 153 case 'inline': 154 $flags['inline'] = 1; 155 break; 156 } 157 } 158 // the include_content URL parameter overrides flags 159 if (isset($_REQUEST['include_content'])) 160 $flags['linkonly'] = 0; 161 return $flags; 162 } 163 164 /** 165 * Returns the converted instructions of a give page/section 166 * 167 * @author Michael Klier <chi@chimeric.de> 168 * @author Michael Hamann <michael@content-space.de> 169 */ 170 function _get_instructions($page, $sect, $mode, $lvl, $flags, $root_id = null) { 171 $key = ($sect) ? $page . '#' . $sect : $page; 172 $this->includes[$key] = true; // legacy code for keeping compatibility with other plugins 173 174 // keep compatibility with other plugins that don't know the $root_id parameter 175 if (is_null($root_id)) { 176 global $ID; 177 $root_id = $ID; 178 } 179 180 if ($flags['linkonly']) { 181 $ins = array( 182 array('p_open', array()), 183 array('internallink', array(':'.$key)), 184 array('p_close', array()), 185 ); 186 } else { 187 if (page_exists($page)) { 188 $ins = p_cached_instructions(wikiFN($page)); 189 } else { 190 $ins = array(); 191 } 192 193 $this->_convert_instructions($ins, $lvl, $page, $sect, $flags, $root_id); 194 } 195 return $ins; 196 } 197 198 /** 199 * Converts instructions of the included page 200 * 201 * The funcion iterates over the given list of instructions and generates 202 * an index of header and section indicies. It also removes document 203 * start/end instructions, converts links, and removes unwanted 204 * instructions like tags, comments, linkbacks. 205 * 206 * Later all header/section levels are convertet to match the current 207 * inclusion level. 208 * 209 * @author Michael Klier <chi@chimeric.de> 210 */ 211 function _convert_instructions(&$ins, $lvl, $page, $sect, $flags, $root_id) { 212 global $conf; 213 214 // filter instructions if needed 215 if(!empty($sect)) { 216 $this->_get_section($ins, $sect); // section required 217 } 218 219 if($flags['firstsec']) { 220 $this->_get_firstsec($ins, $page); // only first section 221 } 222 223 $ns = getNS($page); 224 $num = count($ins); 225 226 $conv_idx = array(); // conversion index 227 $lvl_max = false; // max level 228 $first_header = -1; 229 $no_header = false; 230 $sect_title = false; 231 232 for($i=0; $i<$num; $i++) { 233 switch($ins[$i][0]) { 234 case 'document_start': 235 case 'document_end': 236 case 'section_edit': 237 unset($ins[$i]); 238 break; 239 case 'header': 240 // get section title of first section 241 if($sect && !$sect_title) { 242 $sect_title = $ins[$i][1][0]; 243 } 244 // check if we need to skip the first header 245 if((!$no_header) && $flags['noheader']) { 246 $no_header = true; 247 } 248 249 $conv_idx[] = $i; 250 // get index of first header 251 if($first_header == -1) $first_header = $i; 252 // get max level of this instructions set 253 if(!$lvl_max || ($ins[$i][1][1] < $lvl_max)) { 254 $lvl_max = $ins[$i][1][1]; 255 } 256 break; 257 case 'section_open': 258 if ($flags['inline']) 259 unset($ins[$i]); 260 else 261 $conv_idx[] = $i; 262 break; 263 case 'section_close': 264 if ($flags['inline']) 265 unset($ins[$i]); 266 case 'internallink': 267 case 'internalmedia': 268 // make sure parameters aren't touched 269 $link_params = ''; 270 $link_id = $ins[$i][1][0]; 271 $link_parts = explode('?', $link_id, 2); 272 if (count($link_parts) === 2) { 273 $link_id = $link_parts[0]; 274 $link_params = $link_parts[1]; 275 } 276 // resolve the id without cleaning it 277 $link_id = resolve_id($ns, $link_id, false); 278 // this id is internal (i.e. absolute) now, add ':' to make resolve_id work again 279 if ($link_id{0} != ':') $link_id = ':'.$link_id; 280 // restore parameters 281 $ins[$i][1][0] = ($link_params != '') ? $link_id.'?'.$link_params : $link_id; 282 break; 283 case 'plugin': 284 // FIXME skip other plugins? 285 switch($ins[$i][1][0]) { 286 case 'tag_tag': // skip tags 287 case 'discussion_comments': // skip comments 288 case 'linkback': // skip linkbacks 289 case 'data_entry': // skip data plugin 290 case 'meta': // skip meta plugin 291 unset($ins[$i]); 292 break; 293 // adapt indentation level of nested includes 294 case 'include_include': 295 if (!$flags['inline'] && $flags['indent']) 296 $ins[$i][1][1][4] += $lvl; 297 break; 298 } 299 break; 300 default: 301 break; 302 } 303 } 304 305 // calculate difference between header/section level and include level 306 $diff = 0; 307 if (!isset($lvl_max)) $lvl_max = 0; // if no level found in target, set to 0 308 $diff = $lvl - $lvl_max + 1; 309 if ($no_header) $diff -= 1; // push up one level if "noheader" 310 311 // convert headers and set footer/permalink 312 $hdr_deleted = false; 313 $has_permalink = false; 314 $footer_lvl = false; 315 $contains_secedit = false; 316 $section_close_at = false; 317 foreach($conv_idx as $idx) { 318 if($ins[$idx][0] == 'header') { 319 if ($section_close_at === false) { 320 // store the index of the first heading (the begin of the first section) 321 $section_close_at = $idx; 322 } 323 324 if($no_header && !$hdr_deleted) { 325 unset ($ins[$idx]); 326 $hdr_deleted = true; 327 continue; 328 } 329 330 if($flags['indent']) { 331 $lvl_new = (($ins[$idx][1][1] + $diff) > 5) ? 5 : ($ins[$idx][1][1] + $diff); 332 $ins[$idx][1][1] = $lvl_new; 333 } 334 335 if($ins[$idx][1][1] <= $conf['maxseclevel']) 336 $contains_secedit = true; 337 338 // set permalink 339 if($flags['link'] && !$has_permalink && ($idx == $first_header)) { 340 $this->_permalink($ins[$idx], $page, $sect, $flags); 341 $has_permalink = true; 342 } 343 344 // set footer level 345 if(!$footer_lvl && ($idx == $first_header) && !$no_header) { 346 if($flags['indent']) { 347 $footer_lvl = $lvl_new; 348 } else { 349 $footer_lvl = $lvl_max; 350 } 351 } 352 } else { 353 // it's a section 354 if($flags['indent']) { 355 $lvl_new = (($ins[$idx][1][0] + $diff) > 5) ? 5 : ($ins[$idx][1][0] + $diff); 356 $ins[$idx][1][0] = $lvl_new; 357 } 358 359 // check if noheader is used and set the footer level to the first section 360 if($no_header && !$footer_lvl) { 361 if($flags['indent']) { 362 $footer_lvl = $lvl_new; 363 } else { 364 $footer_lvl = $lvl_max; 365 } 366 } 367 } 368 } 369 370 // close last open section of the included page if there is any 371 if ($contains_secedit) { 372 array_push($ins, array('plugin', array('include_close_last_secedit', array()))); 373 } 374 375 // add edit button 376 if($flags['editbtn']) { 377 $perm = auth_quickaclcheck($page); 378 $can_edit = page_exists($page) ? $perm >= AUTH_EDIT : $perm >= AUTH_CREATE; 379 if ($can_edit) 380 $this->_editbtn($ins, $page, $sect, $sect_title, ($flags['redirect'] ? $root_id : false)); 381 } 382 383 // add footer 384 if($flags['footer']) { 385 $ins[] = $this->_footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id); 386 } 387 388 // wrap content at the beginning of the include that is not in a section in a section 389 if ($lvl > 0 && $section_close_at !== 0 && $flags['indent'] && !$flags['inline']) { 390 if ($section_close_at === false) { 391 $ins[] = array('section_close', array()); 392 array_unshift($ins, array('section_open', array($lvl))); 393 } else { 394 $section_close_idx = array_search($section_close_at, array_keys($ins)); 395 if ($section_close_idx > 0) { 396 $before_ins = array_slice($ins, 0, $section_close_idx); 397 $after_ins = array_slice($ins, $section_close_idx); 398 $ins = array_merge($before_ins, array(array('section_close', array())), $after_ins); 399 array_unshift($ins, array('section_open', array($lvl))); 400 } 401 } 402 } 403 404 // add instructions entry wrapper 405 array_unshift($ins, array('plugin', array('include_wrap', array('open', $page, $flags['redirect'])))); 406 array_push($ins, array('plugin', array('include_wrap', array('close')))); 407 408 // close previous section if any and re-open after inclusion 409 if($lvl != 0 && $this->sec_close && !$flags['inline']) { 410 array_unshift($ins, array('section_close', array())); 411 $ins[] = array('section_open', array($lvl)); 412 } 413 } 414 415 /** 416 * Appends instruction item for the include plugin footer 417 * 418 * @author Michael Klier <chi@chimeric.de> 419 */ 420 function _footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id) { 421 $footer = array(); 422 $footer[0] = 'plugin'; 423 $footer[1] = array('include_footer', array($page, $sect, $sect_title, $flags, $root_id, $footer_lvl)); 424 return $footer; 425 } 426 427 /** 428 * Appends instruction item for an edit button 429 * 430 * @author Michael Klier <chi@chimeric.de> 431 */ 432 function _editbtn(&$ins, $page, $sect, $sect_title, $root_id) { 433 $editbtn = array(); 434 $editbtn[0] = 'plugin'; 435 $editbtn[1] = array('include_editbtn', array($page, $sect, $sect_title, $root_id)); 436 $ins[] = $editbtn; 437 } 438 439 /** 440 * Convert instruction item for a permalink header 441 * 442 * @author Michael Klier <chi@chimeric.de> 443 */ 444 function _permalink(&$ins, $page, $sect, $flags) { 445 $ins[0] = 'plugin'; 446 $ins[1] = array('include_header', array($ins[1][0], $ins[1][1], $page, $sect, $flags)); 447 } 448 449 /** 450 * Get a section including its subsections 451 * 452 * @author Michael Klier <chi@chimeric.de> 453 */ 454 function _get_section(&$ins, $sect) { 455 $num = count($ins); 456 $offset = false; 457 $lvl = false; 458 $end = false; 459 460 for($i=0; $i<$num; $i++) { 461 if ($ins[$i][0] == 'header') { 462 463 // found the right header 464 if (cleanID($ins[$i][1][0]) == $sect) { 465 $offset = $i; 466 $lvl = $ins[$i][1][1]; 467 } elseif ($offset && $lvl && ($ins[$i][1][1] <= $lvl)) { 468 $end = $i - $offset; 469 break; 470 } 471 } 472 } 473 $offset = $offset ? $offset : 0; 474 $end = $end ? $end : ($num - 1); 475 if(is_array($ins)) { 476 $ins = array_slice($ins, $offset, $end); 477 } 478 } 479 480 /** 481 * Only display the first section of a page and a readmore link 482 * 483 * @author Michael Klier <chi@chimeric.de> 484 */ 485 function _get_firstsec(&$ins, $page) { 486 $num = count($ins); 487 $first_sect = false; 488 for($i=0; $i<$num; $i++) { 489 if($ins[$i][0] == 'section_close') { 490 $first_sect = $i; 491 } 492 if(($first_sect) && ($ins[$i][0] == 'section_open')) { 493 $ins = array_slice($ins, 0, $first_sect); 494 $ins[] = array('p_open', array()); 495 $ins[] = array('internallink', array($page, $this->getLang('readmore'))); 496 $ins[] = array('p_close', array()); 497 $ins[] = array('section_close', array()); 498 return; 499 } 500 } 501 } 502 503 /** 504 * Gives a list of pages for a given include statement 505 * 506 * @author Michael Hamann <michael@content-space.de> 507 */ 508 function _get_included_pages($mode, $page, $sect, $parent_id) { 509 global $conf; 510 $pages = array(); 511 switch($mode) { 512 case 'namespace': 513 $ns = str_replace(':', '/', cleanID($page)); 514 search($pagearrays, $conf['datadir'], 'search_list', '', $ns); 515 if (is_array($pagearrays)) { 516 foreach ($pagearrays as $pagearray) { 517 $pages[] = $pagearray['id']; 518 } 519 } 520 break; 521 case 'tagtopic': 522 if (!$this->taghelper) 523 $this->taghelper =& plugin_load('helper', 'tag'); 524 if(!$this->taghelper) { 525 msg('You have to install the tag plugin to use this functionality!', -1); 526 return array(); 527 } 528 $tag = $page; 529 $sect = ''; 530 $pagearrays = $this->taghelper->getTopic('', null, $tag); 531 foreach ($pagearrays as $pagearray) { 532 $pages[] = $pagearray['id']; 533 } 534 break; 535 default: 536 $page = $this->_apply_macro($page); 537 resolve_pageid(getNS($parent_id), $page, $exists); // resolve shortcuts and clean ID 538 if (auth_quickaclcheck($page) >= AUTH_READ) 539 $pages[] = $page; 540 } 541 542 sort($pages); 543 544 $result = array(); 545 foreach ($pages as $page) { 546 $perm = auth_quickaclcheck($page); 547 $exists = page_exists($page); 548 $result[] = array('id' => $page, 'exists' => $exists, 'can_edit' => ($perm >= AUTH_EDIT), 'parent_id' => $parent_id); 549 } 550 return $result; 551 } 552 553 /** 554 * This function generates the list of all included pages from a list of metadata 555 * instructions. 556 */ 557 function _get_included_pages_from_meta_instructions($instructions) { 558 $pages = array(); 559 foreach ($instructions as $instruction) { 560 extract($instruction); 561 $pages = array_merge($pages, $this->_get_included_pages($mode, $page, $sect, $parent_id)); 562 } 563 return $pages; 564 } 565 566 /** 567 * Makes user or date dependent includes possible 568 */ 569 function _apply_macro($id) { 570 global $INFO; 571 global $auth; 572 573 // if we don't have an auth object, do nothing 574 if (!$auth) return $id; 575 576 $user = $_SERVER['REMOTE_USER']; 577 $group = $INFO['userinfo']['grps'][0]; 578 579 $replace = array( 580 '@USER@' => cleanID($user), 581 '@NAME@' => cleanID($INFO['userinfo']['name']), 582 '@GROUP@' => cleanID($group), 583 '@YEAR@' => date('Y'), 584 '@MONTH@' => date('m'), 585 '@DAY@' => date('d'), 586 ); 587 return str_replace(array_keys($replace), array_values($replace), $id); 588 } 589} 590// vim:ts=4:sw=4:et: 591