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