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 * @author Michael Hamann <michael@content-space.de> 8 */ 9 10// must be run within Dokuwiki 11if (!defined('DOKU_INC')) die(); 12 13if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 14if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 15if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 16 17/** 18 * Helper functions for the include plugin and other plugins that want to include pages. 19 */ 20class helper_plugin_include extends DokuWiki_Plugin { // DokuWiki_Helper_Plugin 21 22 var $defaults = array(); 23 var $sec_close = true; 24 /** @var helper_plugin_tag $taghelper */ 25 var $taghelper = null; 26 var $includes = array(); // deprecated - compatibility code for the blog plugin 27 28 /** 29 * Constructor loads default config settings once 30 */ 31 function __construct() { 32 $this->defaults['noheader'] = $this->getConf('noheader'); 33 $this->defaults['firstsec'] = $this->getConf('firstseconly'); 34 $this->defaults['editbtn'] = $this->getConf('showeditbtn'); 35 $this->defaults['taglogos'] = $this->getConf('showtaglogos'); 36 $this->defaults['footer'] = $this->getConf('showfooter'); 37 $this->defaults['redirect'] = $this->getConf('doredirect'); 38 $this->defaults['date'] = $this->getConf('showdate'); 39 $this->defaults['mdate'] = $this->getConf('showmdate'); 40 $this->defaults['user'] = $this->getConf('showuser'); 41 $this->defaults['comments'] = $this->getConf('showcomments'); 42 $this->defaults['linkbacks'] = $this->getConf('showlinkbacks'); 43 $this->defaults['tags'] = $this->getConf('showtags'); 44 $this->defaults['link'] = $this->getConf('showlink'); 45 $this->defaults['permalink'] = $this->getConf('showpermalink'); 46 $this->defaults['indent'] = $this->getConf('doindent'); 47 $this->defaults['linkonly'] = $this->getConf('linkonly'); 48 $this->defaults['title'] = $this->getConf('title'); 49 $this->defaults['pageexists'] = $this->getConf('pageexists'); 50 $this->defaults['parlink'] = $this->getConf('parlink'); 51 $this->defaults['inline'] = false; 52 $this->defaults['order'] = $this->getConf('order'); 53 $this->defaults['rsort'] = $this->getConf('rsort'); 54 $this->defaults['depth'] = $this->getConf('depth'); 55 $this->defaults['readmore'] = $this->getConf('readmore'); 56 } 57 58 /** 59 * Available methods for other plugins 60 */ 61 function getMethods() { 62 $result = array(); 63 $result[] = array( 64 'name' => 'get_flags', 65 'desc' => 'overrides standard values for showfooter and firstseconly settings', 66 'params' => array('flags' => 'array'), 67 ); 68 return $result; 69 } 70 71 /** 72 * Overrides standard values for showfooter and firstseconly settings 73 */ 74 function get_flags($setflags) { 75 // load defaults 76 $flags = $this->defaults; 77 foreach ($setflags as $flag) { 78 $value = ''; 79 if (strpos($flag, '=') !== false) { 80 list($flag, $value) = explode('=', $flag, 2); 81 } 82 switch ($flag) { 83 case 'footer': 84 $flags['footer'] = 1; 85 break; 86 case 'nofooter': 87 $flags['footer'] = 0; 88 break; 89 case 'firstseconly': 90 case 'firstsectiononly': 91 $flags['firstsec'] = 1; 92 break; 93 case 'fullpage': 94 $flags['firstsec'] = 0; 95 break; 96 case 'showheader': 97 case 'header': 98 $flags['noheader'] = 0; 99 break; 100 case 'noheader': 101 $flags['noheader'] = 1; 102 break; 103 case 'editbtn': 104 case 'editbutton': 105 $flags['editbtn'] = 1; 106 break; 107 case 'noeditbtn': 108 case 'noeditbutton': 109 $flags['editbtn'] = 0; 110 break; 111 case 'permalink': 112 $flags['permalink'] = 1; 113 break; 114 case 'nopermalink': 115 $flags['permalink'] = 0; 116 break; 117 case 'redirect': 118 $flags['redirect'] = 1; 119 break; 120 case 'noredirect': 121 $flags['redirect'] = 0; 122 break; 123 case 'link': 124 $flags['link'] = 1; 125 break; 126 case 'nolink': 127 $flags['link'] = 0; 128 break; 129 case 'user': 130 $flags['user'] = 1; 131 break; 132 case 'nouser': 133 $flags['user'] = 0; 134 break; 135 case 'comments': 136 $flags['comments'] = 1; 137 break; 138 case 'nocomments': 139 $flags['comments'] = 0; 140 break; 141 case 'linkbacks': 142 $flags['linkbacks'] = 1; 143 break; 144 case 'nolinkbacks': 145 $flags['linkbacks'] = 0; 146 break; 147 case 'tags': 148 $flags['tags'] = 1; 149 break; 150 case 'notags': 151 $flags['tags'] = 0; 152 break; 153 case 'date': 154 $flags['date'] = 1; 155 break; 156 case 'nodate': 157 $flags['date'] = 0; 158 break; 159 case 'mdate': 160 $flags['mdate'] = 1; 161 break; 162 case 'nomdate': 163 $flags['mdate'] = 0; 164 break; 165 case 'indent': 166 $flags['indent'] = 1; 167 break; 168 case 'noindent': 169 $flags['indent'] = 0; 170 break; 171 case 'linkonly': 172 $flags['linkonly'] = 1; 173 break; 174 case 'nolinkonly': 175 case 'include_content': 176 $flags['linkonly'] = 0; 177 break; 178 case 'inline': 179 $flags['inline'] = 1; 180 break; 181 case 'title': 182 $flags['title'] = 1; 183 break; 184 case 'notitle': 185 $flags['title'] = 0; 186 break; 187 case 'pageexists': 188 $flags['pageexists'] = 1; 189 break; 190 case 'nopageexists': 191 $flags['pageexists'] = 0; 192 break; 193 case 'existlink': 194 $flags['pageexists'] = 1; 195 $flags['linkonly'] = 1; 196 break; 197 case 'parlink': 198 $flags['parlink'] = 1; 199 break; 200 case 'noparlink': 201 $flags['parlink'] = 0; 202 break; 203 case 'order': 204 $flags['order'] = $value; 205 break; 206 case 'sort': 207 $flags['rsort'] = 0; 208 break; 209 case 'rsort': 210 $flags['rsort'] = 1; 211 break; 212 case 'depth': 213 $flags['depth'] = max(intval($value), 0); 214 break; 215 case 'beforeeach': 216 $flags['beforeeach'] = $value; 217 break; 218 case 'aftereach': 219 $flags['aftereach'] = $value; 220 break; 221 case 'readmore': 222 $flags['readmore'] = 1; 223 break; 224 case 'noreadmore': 225 $flags['readmore'] = 0; 226 break; 227 case 'exclude': 228 $flags['exclude'] = $value; 229 break; 230 } 231 } 232 // the include_content URL parameter overrides flags 233 if (isset($_REQUEST['include_content'])) 234 $flags['linkonly'] = 0; 235 return $flags; 236 } 237 238 /** 239 * Returns the converted instructions of a give page/section 240 * 241 * @author Michael Klier <chi@chimeric.de> 242 * @author Michael Hamann <michael@content-space.de> 243 */ 244 function _get_instructions($page, $sect, $mode, $lvl, $flags, $root_id = null, $included_pages = array()) { 245 $key = ($sect) ? $page . '#' . $sect : $page; 246 $this->includes[$key] = true; // legacy code for keeping compatibility with other plugins 247 248 // keep compatibility with other plugins that don't know the $root_id parameter 249 if (is_null($root_id)) { 250 global $ID; 251 $root_id = $ID; 252 } 253 254 if ($flags['linkonly']) { 255 if (page_exists($page) || $flags['pageexists'] == 0) { 256 $title = ''; 257 if ($flags['title']) 258 $title = p_get_first_heading($page); 259 if($flags['parlink']) { 260 $ins = array( 261 array('p_open', array()), 262 array('internallink', array(':'.$key, $title)), 263 array('p_close', array()), 264 ); 265 } else { 266 $ins = array(array('internallink', array(':'.$key,$title))); 267 } 268 }else { 269 $ins = array(); 270 } 271 } else { 272 if (page_exists($page)) { 273 global $ID; 274 $backupID = $ID; 275 $ID = $page; // Change the global $ID as otherwise plugins like the discussion plugin will save data for the wrong page 276 $ins = p_cached_instructions(wikiFN($page), false, $page); 277 $ID = $backupID; 278 } else { 279 $ins = array(); 280 } 281 282 $this->_convert_instructions($ins, $lvl, $page, $sect, $flags, $root_id, $included_pages); 283 } 284 return $ins; 285 } 286 287 /** 288 * Converts instructions of the included page 289 * 290 * The funcion iterates over the given list of instructions and generates 291 * an index of header and section indicies. It also removes document 292 * start/end instructions, converts links, and removes unwanted 293 * instructions like tags, comments, linkbacks. 294 * 295 * Later all header/section levels are convertet to match the current 296 * inclusion level. 297 * 298 * @author Michael Klier <chi@chimeric.de> 299 */ 300 function _convert_instructions(&$ins, $lvl, $page, $sect, $flags, $root_id, $included_pages = array()) { 301 global $conf; 302 303 // filter instructions if needed 304 if(!empty($sect)) { 305 $this->_get_section($ins, $sect); // section required 306 } 307 308 if($flags['firstsec']) { 309 $this->_get_firstsec($ins, $page, $flags); // only first section 310 } 311 312 $ns = getNS($page); 313 $num = count($ins); 314 315 $conv_idx = array(); // conversion index 316 $lvl_max = false; // max level 317 $first_header = -1; 318 $no_header = false; 319 $sect_title = false; 320 $endpos = null; // end position of the raw wiki text 321 322 $this->adapt_links($ins, $page, $included_pages); 323 324 for($i=0; $i<$num; $i++) { 325 switch($ins[$i][0]) { 326 case 'document_start': 327 case 'document_end': 328 case 'section_edit': 329 unset($ins[$i]); 330 break; 331 case 'header': 332 // get section title of first section 333 if($sect && !$sect_title) { 334 $sect_title = $ins[$i][1][0]; 335 } 336 // check if we need to skip the first header 337 if((!$no_header) && $flags['noheader']) { 338 $no_header = true; 339 } 340 341 $conv_idx[] = $i; 342 // get index of first header 343 if($first_header == -1) $first_header = $i; 344 // get max level of this instructions set 345 if(!$lvl_max || ($ins[$i][1][1] < $lvl_max)) { 346 $lvl_max = $ins[$i][1][1]; 347 } 348 break; 349 case 'section_open': 350 if ($flags['inline']) 351 unset($ins[$i]); 352 else 353 $conv_idx[] = $i; 354 break; 355 case 'section_close': 356 if ($flags['inline']) 357 unset($ins[$i]); 358 break; 359 case 'nest': 360 $this->adapt_links($ins[$i][1][0], $page, $included_pages); 361 break; 362 case 'plugin': 363 // FIXME skip other plugins? 364 switch($ins[$i][1][0]) { 365 case 'tag_tag': // skip tags 366 case 'discussion_comments': // skip comments 367 case 'linkback': // skip linkbacks 368 case 'data_entry': // skip data plugin 369 case 'meta': // skip meta plugin 370 case 'indexmenu_tag': // skip indexmenu sort tag 371 case 'include_sorttag': // skip include plugin sort tag 372 unset($ins[$i]); 373 break; 374 // adapt indentation level of nested includes 375 case 'include_include': 376 if (!$flags['inline'] && $flags['indent']) 377 $ins[$i][1][1][4] += $lvl; 378 break; 379 /* 380 * if there is already a closelastsecedit instruction (was added by one of the section 381 * functions), store its position but delete it as it can't be determined yet if it is needed, 382 * i.e. if there is a header which generates a section edit (depends on the levels, level 383 * adjustments, $no_header, ...) 384 */ 385 case 'include_closelastsecedit': 386 $endpos = $ins[$i][1][1][0]; 387 unset($ins[$i]); 388 break; 389 } 390 break; 391 default: 392 break; 393 } 394 } 395 396 // calculate difference between header/section level and include level 397 $diff = 0; 398 if (!isset($lvl_max)) $lvl_max = 0; // if no level found in target, set to 0 399 $diff = $lvl - $lvl_max + 1; 400 if ($no_header) $diff -= 1; // push up one level if "noheader" 401 402 // convert headers and set footer/permalink 403 $hdr_deleted = false; 404 $has_permalink = false; 405 $footer_lvl = false; 406 $contains_secedit = false; 407 $section_close_at = false; 408 foreach($conv_idx as $idx) { 409 if($ins[$idx][0] == 'header') { 410 if ($section_close_at === false && isset($ins[$idx+1]) && $ins[$idx+1][0] == 'section_open') { 411 // store the index of the first heading that is followed by a new section 412 // the wrap plugin creates sections without section_open so the section shouldn't be closed before them 413 $section_close_at = $idx; 414 } 415 416 if($no_header && !$hdr_deleted) { 417 unset ($ins[$idx]); 418 $hdr_deleted = true; 419 continue; 420 } 421 422 if($flags['indent']) { 423 $lvl_new = (($ins[$idx][1][1] + $diff) > 5) ? 5 : ($ins[$idx][1][1] + $diff); 424 $ins[$idx][1][1] = $lvl_new; 425 } 426 427 if($ins[$idx][1][1] <= $conf['maxseclevel']) 428 $contains_secedit = true; 429 430 // set permalink 431 if($flags['link'] && !$has_permalink && ($idx == $first_header)) { 432 $this->_permalink($ins[$idx], $page, $sect, $flags); 433 $has_permalink = true; 434 } 435 436 // set footer level 437 if(!$footer_lvl && ($idx == $first_header) && !$no_header) { 438 if($flags['indent'] && isset($lvl_new)) { 439 $footer_lvl = $lvl_new; 440 } else { 441 $footer_lvl = $lvl_max; 442 } 443 } 444 } else { 445 // it's a section 446 if($flags['indent']) { 447 $lvl_new = (($ins[$idx][1][0] + $diff) > 5) ? 5 : ($ins[$idx][1][0] + $diff); 448 $ins[$idx][1][0] = $lvl_new; 449 } 450 451 // check if noheader is used and set the footer level to the first section 452 if($no_header && !$footer_lvl) { 453 if($flags['indent'] && isset($lvl_new)) { 454 $footer_lvl = $lvl_new; 455 } else { 456 $footer_lvl = $lvl_max; 457 } 458 } 459 } 460 } 461 462 // close last open section of the included page if there is any 463 if ($contains_secedit) { 464 array_push($ins, array('plugin', array('include_closelastsecedit', array($endpos)))); 465 } 466 467 // add edit button 468 if($flags['editbtn']) { 469 $this->_editbtn($ins, $page, $sect, $sect_title, ($flags['redirect'] ? $root_id : false)); 470 } 471 472 // add footer 473 if($flags['footer']) { 474 $ins[] = $this->_footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id); 475 } 476 477 // wrap content at the beginning of the include that is not in a section in a section 478 if ($lvl > 0 && $section_close_at !== 0 && $flags['indent'] && !$flags['inline']) { 479 if ($section_close_at === false) { 480 $ins[] = array('section_close', array()); 481 array_unshift($ins, array('section_open', array($lvl))); 482 } else { 483 $section_close_idx = array_search($section_close_at, array_keys($ins)); 484 if ($section_close_idx > 0) { 485 $before_ins = array_slice($ins, 0, $section_close_idx); 486 $after_ins = array_slice($ins, $section_close_idx); 487 $ins = array_merge($before_ins, array(array('section_close', array())), $after_ins); 488 array_unshift($ins, array('section_open', array($lvl))); 489 } 490 } 491 } 492 493 // add instructions entry wrapper 494 $include_secid = (isset($flags['include_secid']) ? $flags['include_secid'] : NULL); 495 array_unshift($ins, array('plugin', array('include_wrap', array('open', $page, $flags['redirect'], $include_secid)))); 496 if (isset($flags['beforeeach'])) 497 array_unshift($ins, array('entity', array($flags['beforeeach']))); 498 array_push($ins, array('plugin', array('include_wrap', array('close')))); 499 if (isset($flags['aftereach'])) 500 array_push($ins, array('entity', array($flags['aftereach']))); 501 502 // close previous section if any and re-open after inclusion 503 if($lvl != 0 && $this->sec_close && !$flags['inline']) { 504 array_unshift($ins, array('section_close', array())); 505 $ins[] = array('section_open', array($lvl)); 506 } 507 } 508 509 /** 510 * Appends instruction item for the include plugin footer 511 * 512 * @author Michael Klier <chi@chimeric.de> 513 */ 514 function _footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id) { 515 $footer = array(); 516 $footer[0] = 'plugin'; 517 $footer[1] = array('include_footer', array($page, $sect, $sect_title, $flags, $root_id, $footer_lvl)); 518 return $footer; 519 } 520 521 /** 522 * Appends instruction item for an edit button 523 * 524 * @author Michael Klier <chi@chimeric.de> 525 */ 526 function _editbtn(&$ins, $page, $sect, $sect_title, $root_id) { 527 $title = ($sect) ? $sect_title : $page; 528 $editbtn = array(); 529 $editbtn[0] = 'plugin'; 530 $editbtn[1] = array('include_editbtn', array($title)); 531 $ins[] = $editbtn; 532 } 533 534 /** 535 * Convert instruction item for a permalink header 536 * 537 * @author Michael Klier <chi@chimeric.de> 538 */ 539 function _permalink(&$ins, $page, $sect, $flags) { 540 $ins[0] = 'plugin'; 541 $ins[1] = array('include_header', array($ins[1][0], $ins[1][1], $ins[1][2], $page, $sect, $flags)); 542 } 543 544 /** 545 * Convert internal and local links depending on the included pages 546 * 547 * @param array $ins The instructions that shall be adapted 548 * @param string $page The included page 549 * @param array $included_pages The array of pages that are included 550 */ 551 private function adapt_links(&$ins, $page, $included_pages = null) { 552 $num = count($ins); 553 $ns = getNS($page); 554 555 for($i=0; $i<$num; $i++) { 556 // adjust links with image titles 557 if (strpos($ins[$i][0], 'link') !== false && isset($ins[$i][1][1]) && is_array($ins[$i][1][1]) && $ins[$i][1][1]['type'] == 'internalmedia') { 558 // resolve relative ids, but without cleaning in order to preserve the name 559 $media_id = resolve_id($ns, $ins[$i][1][1]['src']); 560 // make sure that after resolving the link again it will be the same link 561 if ($media_id{0} != ':') $media_id = ':'.$media_id; 562 $ins[$i][1][1]['src'] = $media_id; 563 } 564 switch($ins[$i][0]) { 565 case 'internallink': 566 case 'internalmedia': 567 // make sure parameters aren't touched 568 $link_params = ''; 569 $link_id = $ins[$i][1][0]; 570 $link_parts = explode('?', $link_id, 2); 571 if (count($link_parts) === 2) { 572 $link_id = $link_parts[0]; 573 $link_params = $link_parts[1]; 574 } 575 // resolve the id without cleaning it 576 $link_id = resolve_id($ns, $link_id, false); 577 // this id is internal (i.e. absolute) now, add ':' to make resolve_id work again 578 if ($link_id{0} != ':') $link_id = ':'.$link_id; 579 // restore parameters 580 $ins[$i][1][0] = ($link_params != '') ? $link_id.'?'.$link_params : $link_id; 581 582 if ($ins[$i][0] == 'internallink' && !empty($included_pages)) { 583 // change links to included pages into local links 584 // only adapt links without parameters 585 $link_id = $ins[$i][1][0]; 586 $link_parts = explode('?', $link_id, 2); 587 if (count($link_parts) === 1) { 588 $exists = false; 589 resolve_pageid($ns, $link_id, $exists); 590 591 $link_parts = explode('#', $link_id, 2); 592 $hash = ''; 593 if (count($link_parts) === 2) { 594 list($link_id, $hash) = $link_parts; 595 } 596 if (array_key_exists($link_id, $included_pages)) { 597 if ($hash) { 598 // hopefully the hash is also unique in the including page (otherwise this might be the wrong link target) 599 $ins[$i][0] = 'locallink'; 600 $ins[$i][1][0] = $hash; 601 } else { 602 // the include section ids are different from normal section ids (so they won't conflict) but this 603 // also means that the normal locallink function can't be used 604 $ins[$i][0] = 'plugin'; 605 $ins[$i][1] = array('include_locallink', array($included_pages[$link_id]['hid'], $ins[$i][1][1], $ins[$i][1][0])); 606 } 607 } 608 } 609 } 610 break; 611 case 'locallink': 612 /* Convert local links to internal links if the page hasn't been fully included */ 613 if ($included_pages == null || !array_key_exists($page, $included_pages)) { 614 $ins[$i][0] = 'internallink'; 615 $ins[$i][1][0] = ':'.$page.'#'.$ins[$i][1][0]; 616 } 617 break; 618 } 619 } 620 } 621 622 /** 623 * Get a section including its subsections 624 * 625 * @author Michael Klier <chi@chimeric.de> 626 */ 627 function _get_section(&$ins, $sect) { 628 $num = count($ins); 629 $offset = false; 630 $lvl = false; 631 $end = false; 632 $endpos = null; // end position in the input text, needed for section edit buttons 633 634 $check = array(); // used for sectionID() in order to get the same ids as the xhtml renderer 635 636 for($i=0; $i<$num; $i++) { 637 if ($ins[$i][0] == 'header') { 638 639 // found the right header 640 if (sectionID($ins[$i][1][0], $check) == $sect) { 641 $offset = $i; 642 $lvl = $ins[$i][1][1]; 643 } elseif ($offset && $lvl && ($ins[$i][1][1] <= $lvl)) { 644 $end = $i - $offset; 645 $endpos = $ins[$i][1][2]; // the position directly after the found section, needed for the section edit button 646 break; 647 } 648 } 649 } 650 $offset = $offset ? $offset : 0; 651 $end = $end ? $end : ($num - 1); 652 if(is_array($ins)) { 653 $ins = array_slice($ins, $offset, $end); 654 // store the end position in the include_closelastsecedit instruction so it can generate a matching button 655 $ins[] = array('plugin', array('include_closelastsecedit', array($endpos))); 656 } 657 } 658 659 /** 660 * Only display the first section of a page and a readmore link 661 * 662 * @author Michael Klier <chi@chimeric.de> 663 */ 664 function _get_firstsec(&$ins, $page, $flags) { 665 $num = count($ins); 666 $first_sect = false; 667 $endpos = null; // end position in the input text 668 for($i=0; $i<$num; $i++) { 669 if($ins[$i][0] == 'section_close') { 670 $first_sect = $i; 671 } 672 if ($ins[$i][0] == 'header') { 673 /* 674 * Store the position of the last header that is encountered. As section_close/open-instruction are 675 * always (unless some plugin modifies this) around a header instruction this means that the last 676 * position that is stored here is exactly the position of the section_close/open at which the content 677 * is truncated. 678 */ 679 $endpos = $ins[$i][1][2]; 680 } 681 // only truncate the content and add the read more link when there is really 682 // more than that first section 683 if(($first_sect) && ($ins[$i][0] == 'section_open')) { 684 $ins = array_slice($ins, 0, $first_sect); 685 if ($flags['readmore']) { 686 $ins[] = array('plugin', array('include_readmore', array($page))); 687 } 688 $ins[] = array('section_close', array()); 689 // store the end position in the include_closelastsecedit instruction so it can generate a matching button 690 $ins[] = array('plugin', array('include_closelastsecedit', array($endpos))); 691 return; 692 } 693 } 694 } 695 696 /** 697 * Gives a list of pages for a given include statement 698 * 699 * @author Michael Hamann <michael@content-space.de> 700 */ 701 function _get_included_pages($mode, $page, $sect, $parent_id, $flags) { 702 global $conf; 703 $pages = array(); 704 switch($mode) { 705 case 'namespace': 706 $page = cleanID($page); 707 $ns = utf8_encodeFN(str_replace(':', '/', $page)); 708 // depth is absolute depth, not relative depth, but 0 has a special meaning. 709 $depth = $flags['depth'] ? $flags['depth'] + substr_count($page, ':') + ($page ? 1 : 0) : 0; 710 search($pagearrays, $conf['datadir'], 'search_allpages', array('depth' => $depth, 'skipacl' => false), $ns); 711 if (is_array($pagearrays)) { 712 foreach ($pagearrays as $pagearray) { 713 if (!isHiddenPage($pagearray['id'])) // skip hidden pages 714 $pages[] = $pagearray['id']; 715 } 716 } 717 break; 718 case 'tagtopic': 719 if (!$this->taghelper) 720 $this->taghelper =& plugin_load('helper', 'tag'); 721 if(!$this->taghelper) { 722 msg('You have to install the tag plugin to use this functionality!', -1); 723 return array(); 724 } 725 $tag = $page; 726 $sect = ''; 727 $pagearrays = $this->taghelper->getTopic('', null, $tag); 728 foreach ($pagearrays as $pagearray) { 729 $pages[] = $pagearray['id']; 730 } 731 break; 732 default: 733 $page = $this->_apply_macro($page, $parent_id); 734 resolve_pageid(getNS($parent_id), $page, $exists); // resolve shortcuts and clean ID 735 if (auth_quickaclcheck($page) >= AUTH_READ) 736 $pages[] = $page; 737 } 738 739 if (isset($flags['exclude'])) 740 $pages = array_filter($pages, function ($page) use ($flags) { 741 if (@preg_match($flags['exclude'], $page)) 742 return FALSE; 743 return TRUE; 744 }); 745 746 if (count($pages) > 1) { 747 if ($flags['order'] === 'id') { 748 if ($flags['rsort']) { 749 usort($pages, array($this, '_r_strnatcasecmp')); 750 } else { 751 natcasesort($pages); 752 } 753 } else { 754 $ordered_pages = array(); 755 foreach ($pages as $page) { 756 $key = ''; 757 switch ($flags['order']) { 758 case 'title': 759 $key = p_get_first_heading($page); 760 break; 761 case 'created': 762 $key = p_get_metadata($page, 'date created', METADATA_DONT_RENDER); 763 break; 764 case 'modified': 765 $key = p_get_metadata($page, 'date modified', METADATA_DONT_RENDER); 766 break; 767 case 'indexmenu': 768 $key = p_get_metadata($page, 'indexmenu_n', METADATA_RENDER_USING_SIMPLE_CACHE); 769 if ($key === null) 770 $key = ''; 771 break; 772 case 'custom': 773 $key = p_get_metadata($page, 'include_n', METADATA_RENDER_USING_SIMPLE_CACHE); 774 if ($key === null) 775 $key = ''; 776 break; 777 } 778 $key .= '_'.$page; 779 $ordered_pages[$key] = $page; 780 } 781 if ($flags['rsort']) { 782 uksort($ordered_pages, array($this, '_r_strnatcasecmp')); 783 } else { 784 uksort($ordered_pages, 'strnatcasecmp'); 785 } 786 $pages = $ordered_pages; 787 } 788 } 789 790 $result = array(); 791 foreach ($pages as $page) { 792 $exists = page_exists($page); 793 $result[] = array('id' => $page, 'exists' => $exists, 'parent_id' => $parent_id); 794 } 795 return $result; 796 } 797 798 /** 799 * String comparisons using a "natural order" algorithm in reverse order 800 * 801 * @link http://php.net/manual/en/function.strnatcmp.php 802 * @param string $a First string 803 * @param string $b Second string 804 * @return int Similar to other string comparison functions, this one returns < 0 if 805 * str1 is greater than str2; > 806 * 0 if str1 is lesser than 807 * str2, and 0 if they are equal. 808 */ 809 function _r_strnatcasecmp($a, $b) { 810 return strnatcasecmp($b, $a); 811 } 812 813 /** 814 * This function generates the list of all included pages from a list of metadata 815 * instructions. 816 */ 817 function _get_included_pages_from_meta_instructions($instructions) { 818 $pages = array(); 819 foreach ($instructions as $instruction) { 820 $mode = $instruction['mode']; 821 $page = $instruction['page']; 822 $sect = $instruction['sect']; 823 $parent_id = $instruction['parent_id']; 824 $flags = $instruction['flags']; 825 $pages = array_merge($pages, $this->_get_included_pages($mode, $page, $sect, $parent_id, $flags)); 826 } 827 return $pages; 828 } 829 830 /** 831 * Get wiki language from "HTTP_ACCEPT_LANGUAGE" 832 * We allow the pattern e.g. "ja,en-US;q=0.7,en;q=0.3" 833 */ 834 function _get_language_of_wiki($id, $parent_id) { 835 global $conf; 836 $result = $conf['lang']; 837 if(strpos($id, '@BROWSER_LANG@') !== false){ 838 $brlangp = "/([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})*|\*)(;q=(0(.[0-9]{0,3})?|1(.0{0,3})?))?/"; 839 if(preg_match_all( 840 $brlangp, $_SERVER["HTTP_ACCEPT_LANGUAGE"], 841 $matches, PREG_SET_ORDER 842 )){ 843 $langs = array(); 844 foreach($matches as $match){ 845 $langname = $match[1] == '*' ? $conf['lang'] : $match[1]; 846 $qvalue = $match[4] == '' ? 1.0 : $match[4]; 847 $langs[$langname] = $qvalue; 848 } 849 arsort($langs); 850 foreach($langs as $lang => $langq){ 851 $testpage = $this->_apply_macro(str_replace('@BROWSER_LANG@', $lang, $id), $parent_id); 852 resolve_pageid(getNS($parent_id), $testpage, $exists); 853 if($exists){ 854 $result = $lang; 855 break; 856 } 857 } 858 } 859 } 860 return cleanID($result); 861 } 862 863 /** 864 * Makes user or date dependent includes possible 865 */ 866 function _apply_macro($id, $parent_id) { 867 global $INFO; 868 global $auth; 869 870 // if we don't have an auth object, do nothing 871 if (!$auth) return $id; 872 873 $user = $_SERVER['REMOTE_USER']; 874 $group = $INFO['userinfo']['grps'][0]; 875 876 // set group for unregistered users 877 if (!isset($group)) { 878 $group = 'ALL'; 879 } 880 881 $time_stamp = time(); 882 if(preg_match('/@DATE(\w+)@/',$id,$matches)) { 883 switch($matches[1]) { 884 case 'PMONTH': 885 $time_stamp = strtotime("-1 month"); 886 break; 887 case 'NMONTH': 888 $time_stamp = strtotime("+1 month"); 889 break; 890 case 'NWEEK': 891 $time_stamp = strtotime("+1 week"); 892 break; 893 case 'PWEEK': 894 $time_stamp = strtotime("-1 week"); 895 break; 896 case 'TOMORROW': 897 $time_stamp = strtotime("+1 day"); 898 break; 899 case 'YESTERDAY': 900 $time_stamp = strtotime("-1 day"); 901 break; 902 case 'NYEAR': 903 $time_stamp = strtotime("+1 year"); 904 break; 905 case 'PYEAR': 906 $time_stamp = strtotime("-1 year"); 907 break; 908 } 909 $id = preg_replace('/@DATE(\w+)@/','', $id); 910 } 911 912 $replace = array( 913 '@USER@' => cleanID($user), 914 '@NAME@' => cleanID($INFO['userinfo']['name']), 915 '@GROUP@' => cleanID($group), 916 '@BROWSER_LANG@' => $this->_get_language_of_wiki($id, $parent_id), 917 '@YEAR@' => date('Y',$time_stamp), 918 '@MONTH@' => date('m',$time_stamp), 919 '@WEEK@' => date('W',$time_stamp), 920 '@DAY@' => date('d',$time_stamp), 921 '@YEARPMONTH@' => date('Ym',strtotime("-1 month")), 922 '@PMONTH@' => date('m',strtotime("-1 month")), 923 '@NMONTH@' => date('m',strtotime("+1 month")), 924 '@YEARNMONTH@' => date('Ym',strtotime("+1 month")), 925 '@YEARPWEEK@' => date('YW',strtotime("-1 week")), 926 '@PWEEK@' => date('W',strtotime("-1 week")), 927 '@NWEEK@' => date('W',strtotime("+1 week")), 928 '@YEARNWEEK@' => date('YW',strtotime("+1 week")), 929 ); 930 return str_replace(array_keys($replace), array_values($replace), $id); 931 } 932} 933// vim:ts=4:sw=4:et: 934