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