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 global $ID; 189 $backupID = $ID; 190 $ID = $page; // Change the global $ID as otherwise plugins like the discussion plugin will save data for the wrong page 191 $ins = p_cached_instructions(wikiFN($page)); 192 $ID = $backupID; 193 } else { 194 $ins = array(); 195 } 196 197 $this->_convert_instructions($ins, $lvl, $page, $sect, $flags, $root_id); 198 } 199 return $ins; 200 } 201 202 /** 203 * Converts instructions of the included page 204 * 205 * The funcion iterates over the given list of instructions and generates 206 * an index of header and section indicies. It also removes document 207 * start/end instructions, converts links, and removes unwanted 208 * instructions like tags, comments, linkbacks. 209 * 210 * Later all header/section levels are convertet to match the current 211 * inclusion level. 212 * 213 * @author Michael Klier <chi@chimeric.de> 214 */ 215 function _convert_instructions(&$ins, $lvl, $page, $sect, $flags, $root_id) { 216 global $conf; 217 218 // filter instructions if needed 219 if(!empty($sect)) { 220 $this->_get_section($ins, $sect); // section required 221 } 222 223 if($flags['firstsec']) { 224 $this->_get_firstsec($ins, $page); // only first section 225 } 226 227 $ns = getNS($page); 228 $num = count($ins); 229 230 $conv_idx = array(); // conversion index 231 $lvl_max = false; // max level 232 $first_header = -1; 233 $no_header = false; 234 $sect_title = false; 235 236 for($i=0; $i<$num; $i++) { 237 switch($ins[$i][0]) { 238 case 'document_start': 239 case 'document_end': 240 case 'section_edit': 241 unset($ins[$i]); 242 break; 243 case 'header': 244 // get section title of first section 245 if($sect && !$sect_title) { 246 $sect_title = $ins[$i][1][0]; 247 } 248 // check if we need to skip the first header 249 if((!$no_header) && $flags['noheader']) { 250 $no_header = true; 251 } 252 253 $conv_idx[] = $i; 254 // get index of first header 255 if($first_header == -1) $first_header = $i; 256 // get max level of this instructions set 257 if(!$lvl_max || ($ins[$i][1][1] < $lvl_max)) { 258 $lvl_max = $ins[$i][1][1]; 259 } 260 break; 261 case 'section_open': 262 if ($flags['inline']) 263 unset($ins[$i]); 264 else 265 $conv_idx[] = $i; 266 break; 267 case 'section_close': 268 if ($flags['inline']) 269 unset($ins[$i]); 270 case 'internallink': 271 case 'internalmedia': 272 // make sure parameters aren't touched 273 $link_params = ''; 274 $link_id = $ins[$i][1][0]; 275 $link_parts = explode('?', $link_id, 2); 276 if (count($link_parts) === 2) { 277 $link_id = $link_parts[0]; 278 $link_params = $link_parts[1]; 279 } 280 // resolve the id without cleaning it 281 $link_id = resolve_id($ns, $link_id, false); 282 // this id is internal (i.e. absolute) now, add ':' to make resolve_id work again 283 if ($link_id{0} != ':') $link_id = ':'.$link_id; 284 // restore parameters 285 $ins[$i][1][0] = ($link_params != '') ? $link_id.'?'.$link_params : $link_id; 286 break; 287 case 'plugin': 288 // FIXME skip other plugins? 289 switch($ins[$i][1][0]) { 290 case 'tag_tag': // skip tags 291 case 'discussion_comments': // skip comments 292 case 'linkback': // skip linkbacks 293 case 'data_entry': // skip data plugin 294 case 'meta': // skip meta plugin 295 unset($ins[$i]); 296 break; 297 // adapt indentation level of nested includes 298 case 'include_include': 299 if (!$flags['inline'] && $flags['indent']) 300 $ins[$i][1][1][4] += $lvl; 301 break; 302 } 303 break; 304 default: 305 break; 306 } 307 } 308 309 // calculate difference between header/section level and include level 310 $diff = 0; 311 if (!isset($lvl_max)) $lvl_max = 0; // if no level found in target, set to 0 312 $diff = $lvl - $lvl_max + 1; 313 if ($no_header) $diff -= 1; // push up one level if "noheader" 314 315 // convert headers and set footer/permalink 316 $hdr_deleted = false; 317 $has_permalink = false; 318 $footer_lvl = false; 319 $contains_secedit = false; 320 $section_close_at = false; 321 foreach($conv_idx as $idx) { 322 if($ins[$idx][0] == 'header') { 323 if ($section_close_at === false) { 324 // store the index of the first heading (the begin of the first section) 325 $section_close_at = $idx; 326 } 327 328 if($no_header && !$hdr_deleted) { 329 unset ($ins[$idx]); 330 $hdr_deleted = true; 331 continue; 332 } 333 334 if($flags['indent']) { 335 $lvl_new = (($ins[$idx][1][1] + $diff) > 5) ? 5 : ($ins[$idx][1][1] + $diff); 336 $ins[$idx][1][1] = $lvl_new; 337 } 338 339 if($ins[$idx][1][1] <= $conf['maxseclevel']) 340 $contains_secedit = true; 341 342 // set permalink 343 if($flags['link'] && !$has_permalink && ($idx == $first_header)) { 344 $this->_permalink($ins[$idx], $page, $sect, $flags); 345 $has_permalink = true; 346 } 347 348 // set footer level 349 if(!$footer_lvl && ($idx == $first_header) && !$no_header) { 350 if($flags['indent']) { 351 $footer_lvl = $lvl_new; 352 } else { 353 $footer_lvl = $lvl_max; 354 } 355 } 356 } else { 357 // it's a section 358 if($flags['indent']) { 359 $lvl_new = (($ins[$idx][1][0] + $diff) > 5) ? 5 : ($ins[$idx][1][0] + $diff); 360 $ins[$idx][1][0] = $lvl_new; 361 } 362 363 // check if noheader is used and set the footer level to the first section 364 if($no_header && !$footer_lvl) { 365 if($flags['indent']) { 366 $footer_lvl = $lvl_new; 367 } else { 368 $footer_lvl = $lvl_max; 369 } 370 } 371 } 372 } 373 374 // close last open section of the included page if there is any 375 if ($contains_secedit) { 376 array_push($ins, array('plugin', array('include_closelastsecedit', array()))); 377 } 378 379 // add edit button 380 if($flags['editbtn']) { 381 $perm = auth_quickaclcheck($page); 382 $can_edit = page_exists($page) ? $perm >= AUTH_EDIT : $perm >= AUTH_CREATE; 383 if ($can_edit) 384 $this->_editbtn($ins, $page, $sect, $sect_title, ($flags['redirect'] ? $root_id : false)); 385 } 386 387 // add footer 388 if($flags['footer']) { 389 $ins[] = $this->_footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id); 390 } 391 392 // wrap content at the beginning of the include that is not in a section in a section 393 if ($lvl > 0 && $section_close_at !== 0 && $flags['indent'] && !$flags['inline']) { 394 if ($section_close_at === false) { 395 $ins[] = array('section_close', array()); 396 array_unshift($ins, array('section_open', array($lvl))); 397 } else { 398 $section_close_idx = array_search($section_close_at, array_keys($ins)); 399 if ($section_close_idx > 0) { 400 $before_ins = array_slice($ins, 0, $section_close_idx); 401 $after_ins = array_slice($ins, $section_close_idx); 402 $ins = array_merge($before_ins, array(array('section_close', array())), $after_ins); 403 array_unshift($ins, array('section_open', array($lvl))); 404 } 405 } 406 } 407 408 // add instructions entry wrapper 409 array_unshift($ins, array('plugin', array('include_wrap', array('open', $page, $flags['redirect'])))); 410 array_push($ins, array('plugin', array('include_wrap', array('close')))); 411 412 // close previous section if any and re-open after inclusion 413 if($lvl != 0 && $this->sec_close && !$flags['inline']) { 414 array_unshift($ins, array('section_close', array())); 415 $ins[] = array('section_open', array($lvl)); 416 } 417 } 418 419 /** 420 * Appends instruction item for the include plugin footer 421 * 422 * @author Michael Klier <chi@chimeric.de> 423 */ 424 function _footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id) { 425 $footer = array(); 426 $footer[0] = 'plugin'; 427 $footer[1] = array('include_footer', array($page, $sect, $sect_title, $flags, $root_id, $footer_lvl)); 428 return $footer; 429 } 430 431 /** 432 * Appends instruction item for an edit button 433 * 434 * @author Michael Klier <chi@chimeric.de> 435 */ 436 function _editbtn(&$ins, $page, $sect, $sect_title, $root_id) { 437 $editbtn = array(); 438 $editbtn[0] = 'plugin'; 439 $editbtn[1] = array('include_editbtn', array($page, $sect, $sect_title, $root_id)); 440 $ins[] = $editbtn; 441 } 442 443 /** 444 * Convert instruction item for a permalink header 445 * 446 * @author Michael Klier <chi@chimeric.de> 447 */ 448 function _permalink(&$ins, $page, $sect, $flags) { 449 $ins[0] = 'plugin'; 450 $ins[1] = array('include_header', array($ins[1][0], $ins[1][1], $page, $sect, $flags)); 451 } 452 453 /** 454 * Get a section including its subsections 455 * 456 * @author Michael Klier <chi@chimeric.de> 457 */ 458 function _get_section(&$ins, $sect) { 459 $num = count($ins); 460 $offset = false; 461 $lvl = false; 462 $end = false; 463 464 $check = array(); // used for sectionID() in order to get the same ids as the xhtml renderer 465 466 for($i=0; $i<$num; $i++) { 467 if ($ins[$i][0] == 'header') { 468 469 // found the right header 470 if (sectionID($ins[$i][1][0], $check) == $sect) { 471 $offset = $i; 472 $lvl = $ins[$i][1][1]; 473 } elseif ($offset && $lvl && ($ins[$i][1][1] <= $lvl)) { 474 $end = $i - $offset; 475 break; 476 } 477 } 478 } 479 $offset = $offset ? $offset : 0; 480 $end = $end ? $end : ($num - 1); 481 if(is_array($ins)) { 482 $ins = array_slice($ins, $offset, $end); 483 } 484 } 485 486 /** 487 * Only display the first section of a page and a readmore link 488 * 489 * @author Michael Klier <chi@chimeric.de> 490 */ 491 function _get_firstsec(&$ins, $page) { 492 $num = count($ins); 493 $first_sect = false; 494 for($i=0; $i<$num; $i++) { 495 if($ins[$i][0] == 'section_close') { 496 $first_sect = $i; 497 } 498 if(($first_sect) && ($ins[$i][0] == 'section_open')) { 499 $ins = array_slice($ins, 0, $first_sect); 500 $ins[] = array('p_open', array()); 501 $ins[] = array('internallink', array($page, $this->getLang('readmore'))); 502 $ins[] = array('p_close', array()); 503 $ins[] = array('section_close', array()); 504 return; 505 } 506 } 507 } 508 509 /** 510 * Gives a list of pages for a given include statement 511 * 512 * @author Michael Hamann <michael@content-space.de> 513 */ 514 function _get_included_pages($mode, $page, $sect, $parent_id) { 515 global $conf; 516 $pages = array(); 517 switch($mode) { 518 case 'namespace': 519 $ns = str_replace(':', '/', cleanID($page)); 520 search($pagearrays, $conf['datadir'], 'search_list', '', $ns); 521 if (is_array($pagearrays)) { 522 foreach ($pagearrays as $pagearray) { 523 $pages[] = $pagearray['id']; 524 } 525 } 526 break; 527 case 'tagtopic': 528 if (!$this->taghelper) 529 $this->taghelper =& plugin_load('helper', 'tag'); 530 if(!$this->taghelper) { 531 msg('You have to install the tag plugin to use this functionality!', -1); 532 return array(); 533 } 534 $tag = $page; 535 $sect = ''; 536 $pagearrays = $this->taghelper->getTopic('', null, $tag); 537 foreach ($pagearrays as $pagearray) { 538 $pages[] = $pagearray['id']; 539 } 540 break; 541 default: 542 $page = $this->_apply_macro($page); 543 resolve_pageid(getNS($parent_id), $page, $exists); // resolve shortcuts and clean ID 544 if (auth_quickaclcheck($page) >= AUTH_READ) 545 $pages[] = $page; 546 } 547 548 sort($pages); 549 550 $result = array(); 551 foreach ($pages as $page) { 552 $perm = auth_quickaclcheck($page); 553 $exists = page_exists($page); 554 $result[] = array('id' => $page, 'exists' => $exists, 'can_edit' => ($perm >= AUTH_EDIT), 'parent_id' => $parent_id); 555 } 556 return $result; 557 } 558 559 /** 560 * This function generates the list of all included pages from a list of metadata 561 * instructions. 562 */ 563 function _get_included_pages_from_meta_instructions($instructions) { 564 $pages = array(); 565 foreach ($instructions as $instruction) { 566 extract($instruction); 567 $pages = array_merge($pages, $this->_get_included_pages($mode, $page, $sect, $parent_id)); 568 } 569 return $pages; 570 } 571 572 /** 573 * Makes user or date dependent includes possible 574 */ 575 function _apply_macro($id) { 576 global $INFO; 577 global $auth; 578 579 // if we don't have an auth object, do nothing 580 if (!$auth) return $id; 581 582 $user = $_SERVER['REMOTE_USER']; 583 $group = $INFO['userinfo']['grps'][0]; 584 585 $replace = array( 586 '@USER@' => cleanID($user), 587 '@NAME@' => cleanID($INFO['userinfo']['name']), 588 '@GROUP@' => cleanID($group), 589 '@YEAR@' => date('Y'), 590 '@MONTH@' => date('m'), 591 '@DAY@' => date('d'), 592 ); 593 return str_replace(array_keys($replace), array_values($replace), $id); 594 } 595} 596// vim:ts=4:sw=4:et: 597