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