1<?php 2/** 3 * Dir Plugin: Shows pages in one or namespaces in a table or list 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Jacobus Geluk <Jacobus.Geluk@gmail.com> 7 * @based_on "pageindex" plugin by Kite <Kite@puzzlers.org> 8 * @based_on "externallink" plugin by Otto Vainio <plugins@valjakko.net> 9 * @based_on "pagelist" plugin by Esther Brunner <wikidesign@gmail.com> 10 * 11 * Contributions by: 12 * 13 * - Jean-Philippe Prade 14 * - Gunther Hartmann 15 * - Sebastian Menge 16 * - Matthias Schulte 17 * - Geert Janssens 18 * - Gerry Weißbach 19 */ 20 21if(!defined('DOKU_INC')) { 22 define ('DOKU_INC', realpath(dirname(__FILE__).'/../../').'/'); 23} 24if(!defined('DOKU_PLUGIN')) { 25 define ('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 26} 27 28require_once (DOKU_PLUGIN.'syntax.php'); 29require_once (DOKU_INC.'inc/search.php'); 30require_once (DOKU_INC.'inc/pageutils.php'); 31 32define ("DIR_PLUGIN_PATTERN", "DIR"); 33 34/** 35 * The main DIR plugin class... 36 */ 37class syntax_plugin_dir extends DokuWiki_Syntax_Plugin { 38 var $debug = false; 39 var $plugins = Array(); 40 var $opts = Array(); 41 var $cols = Array(); 42 var $hdrs = Array(); 43 var $pages = Array(); 44 var $includeTags = Array(); 45 var $excludeTags = Array(); 46 var $hasTags = false; 47 var $style = "default"; 48 var $rdr = NULL; 49 var $rdrMode = NULL; 50 var $start = "start"; 51 var $dformat = NULL; 52 var $sortKeys = Array(); 53 var $nbrOfSortKeys = 0; 54 var $useDefaultTitle = true; 55 var $modeIsXHTML = false; 56 var $modeIsLatex = false; 57 var $processedLatex = false; 58 var $rowNumber = 0; 59 var $ucnames = false; 60 61 /** 62 * Constructor 63 */ 64 function syntax_plugin_dir() { 65 global $conf; 66 67 // 68 // In the config you can set allowdebug, but you can also 69 // specify the debug attribute in the ~~DIR~~ line... 70 // 71 if($conf ["allowdebug"] == 1) 72 $this->debug = true; 73 74 $this->start = $conf ["start"]; 75 $this->dformat = $conf ["dformat"]; 76 $this->style = $this->getConf("style"); 77 } 78 79 /** 80 * return some info 81 */ 82 function getInfo() { 83 return array( 84 'author' => 'Jacobus Geluk', 85 'email' => 'Jacobus.Geluk@gmail.com', 86 'date' => '2008-06-28', 87 'name' => 'Dir Plugin', 88 'desc' => 'Shows pages in one or namespaces in a table or list', 89 'url' => 'http://www.dokuwiki.org/plugin:dir', 90 ); 91 } 92 93 /** 94 * What kind of syntax are we? 95 */ 96 function getType() { 97 return "substition"; 98 } 99 100 /** 101 * Just before build in links 102 */ 103 function getSort() { 104 return 299; 105 } 106 107 /** 108 * What about paragraphs? 109 */ 110 function getPType() { 111 return "block"; 112 } 113 114 /** 115 * Register the ~~DIR~~ verb... 116 * Supported signatures: 117 * 118 * 1. ~~DIR~~ 119 * 2. ~~DIR:...~~ 120 * 3. ~~DIR?...~~ 121 */ 122 function connectTo($mode) { 123 $this->Lexer->addSpecialPattern('~~'.DIR_PLUGIN_PATTERN.'~~', $mode, 'plugin_dir'); 124 $this->Lexer->addSpecialPattern('~~'.DIR_PLUGIN_PATTERN.'[:?][^~]*~~', $mode, 'plugin_dir'); 125 } 126 127 /** 128 * Handle the match 129 */ 130 function handle($match, $state, $pos, Doku_Handler $handler) { 131 return preg_replace("%~~".DIR_PLUGIN_PATTERN.":(=(.*))?~~%", "\\2", $match); 132 } 133 134 /** 135 * Initialize the current object for each rendering pass 136 */ 137 function _initRender($mode, &$renderer) { 138 $rc = FALSE; 139 $this->rowNumber = 0; 140 $this->opts = Array(); 141 $this->cols = Array(); 142 $this->hdrs = Array(); 143 $this->pages = Array(); 144 $this->hasTags = false; 145 $this->excludeTags = Array(); 146 $this->includeTags = Array(); 147 $this->rdr =& $renderer; 148 $this->rdrMode = $mode; 149 150 switch($mode) { 151 case 'latex': 152 $this->modeIsXHTML = false; 153 $this->modeIsLatex = true; 154 $rc = TRUE; 155 break; 156 case 'xhtml': 157 $this->modeIsXHTML = true; 158 $this->modeIsLatex = false; 159 $rc = TRUE; 160 break; 161 default: 162 $this->modeIsXHTML = false; 163 $this->modeIsLatex = false; 164 } 165 166 return $rc; 167 } 168 169 /** 170 * Create output 171 */ 172 function render($mode, Doku_Renderer $renderer, $data) { 173 if(!$this->_initRender($mode, $renderer)) return false; 174 175 $rc = $this->_dir($data); 176 177 if($this->modeIsLatex) $this->processedLatex = true; 178 179 $this->_showDebugMsg("Leaving syntax_plugin_dir.render()"); 180 181 return $rc; 182 } 183 184 /** 185 * Put a debug message on screen... 186 */ 187 function _showDebugMsg($msg) { 188 if(!$this->debug) return; 189 190 if(is_array($msg)) { 191 foreach($msg as $index => $m) { 192 $this->_showDebugMsg("Array [$index]: ".$m); 193 } 194 return; 195 } 196 197 $this->_putNewLine(); 198 199 switch($this->rdrMode) { 200 case 'xhtml': 201 $this->_put(DOKU_LF."<span style=\"color:red;\">~~"); 202 $this->_put(DIR_PLUGIN_PATTERN."~~: ".hsc($msg)."</span>"); 203 break; 204 case 'latex': 205 $this->_put(DOKU_LF."~~"); 206 $this->_put(DIR_PLUGIN_PATTERN."~~: ".$msg); 207 break; 208 } 209 } 210 211 /** 212 * Load the specified plugin (like the tag or discussion plugin) 213 */ 214 function _loadPlugin($plugin) { 215 if(plugin_isdisabled($plugin)) 216 return false; 217 218 $plug = plugin_load('helper', $plugin); 219 220 if(!$plug) { 221 $this->_showDebugMsg("Plugin \"$plugin\" NOT loaded!"); 222 return false; 223 } 224 225 $this->plugins [$plugin] = $plug; 226 $this->_showDebugMsg("Plugin \"$plugin\" loaded!"); 227 228 return true; 229 } 230 231 /** 232 * Let another plugin generate the content... 233 */ 234 function _pluginCell($plugin, $id) { 235 $plug = $this->plugins [$plugin]; 236 237 if(!$plug) 238 return 'Plugin '.$plugin.' not loaded!'; 239 240 $html = $plug->td(cleanID($id)); 241 242 return $html; 243 } 244 245 /** 246 * Shows parsed options (in debug mode) 247 */ 248 function _parseOptionsShow($data, $dir, $ns) { 249 if(!$this->debug) return; 250 251 $this->_put(DOKU_LF."<xmp style=\"font-family: Courier; color: red;\">"); 252 $this->_put(DOKU_LF." data = $data"); 253 $this->_put(DOKU_LF." dir = $dir"); 254 $this->_put(DOKU_LF." ns = $ns"); 255 256 foreach($this->opts as $key => $opt) { 257 if(is_array($opt)) { 258 foreach($opt as $optkey => $optval) { 259 $this->_put(DOKU_LF." opts[$key][$optkey] = $optval"); 260 } 261 } else if(is_bool($opt)) { 262 $this->_put(DOKU_LF." opts[$key] = ".($opt ? "true" : "false")); 263 } else { 264 $this->_put(DOKU_LF." opts[$key] = $opt"); 265 } 266 } 267 $this->_put(DOKU_LF.DOKU_LF."date: ".date("D M j G:i:s T Y")); 268 $this->_put(DOKU_LF."</xmp>".DOKU_LF); 269 } 270 271 /** 272 * Get the namespace of the parent directory 273 * (always prefixed and postfixed with a colon, root is ':') 274 */ 275 function _getParentNS($id) { 276 // global $ID ; 277 $curNS = getNS($id); 278 279 if($curNS == '') return ':'; 280 281 if(substr($curNS, 0, 1) != ':') { 282 $curNS = ':'.$curNS; 283 } 284 285 return $curNS.':'; 286 } 287 288 /** 289 * Create a fully qualified namespace from the specified one. 290 * The second parameter must be true when the given namespace 291 * is never a page id. In that case, the returned namespace 292 * always ends with a colon. 293 */ 294 function _parseNS($ns, $mustBeNSnoPage) { 295 global $ID; 296 297 if(substr($ns, 0, 2) == '.:') { 298 $ns = ':'.getNS($ID).substr($ns, 1); 299 } elseif(substr($ns, 0, 3) == '..:') { 300 $ns = $this->_getParentNS($ID).substr($ns, 3); 301 } elseif($ns == '..') { 302 $ns = $this->_getParentNS($ID); 303 } elseif(substr($ns, 0, 1) == ':') { 304 } elseif($ns == '.' || $ns == '*') { 305 $ns = ':'.getNS($ID); 306 } else { 307 $ns = ':'.getNS($ID).':'.$ns; 308 } 309 310 if($mustBeNSnoPage && substr($ns, -1) <> ':') $ns .= ':'; 311 312 return $ns; 313 } 314 315 /** 316 * Convert namespace to its path 317 */ 318 function _ns2path($ns) { 319 global $conf; 320 321 if($ns == ':' || $ns == '') return $conf ['datadir']; 322 323 $ns = trim($ns, ':'); 324 325 $path = $conf ['datadir'].'/'.utf8_encodeFN(str_replace(':', '/', $ns)); 326 327 return $path; 328 } 329 330 /** 331 * Initialize the opts array... 332 */ 333 function _initOpts($flags) { 334 $this->opts = array(); 335 $this->opts ["noheader"] = false; 336 $this->opts ["collapse"] = false; 337 $this->opts ["ego"] = false; 338 $this->opts ["namespacename"] = false; 339 340 $flags = explode('&', $flags); 341 342 foreach($flags as $index => $par) { 343 $tmp = explode("=", $par); 344 $key = $tmp [0]; 345 $val = $tmp [1]; 346 347 switch($key) { 348 case "skip": 349 case "cols": 350 case "hdrs": 351 case "sort": 352 case "tag": 353 $val = explode(';', trim($val, ';')); 354 $this->_loadPlugin("tag"); 355 break; 356 case "noheader": 357 case "nohead": 358 case "nohdr": 359 $key = "noheader"; 360 $val = true; 361 break; 362 case "showheader": 363 case "header": 364 $key = "noheader"; 365 $val = false; 366 break; 367 case "collapse": 368 $key = "collapse"; 369 $val = true; 370 break; 371 case "ego": 372 $key = "ego"; 373 $val = true; 374 break; 375 case "nodefaulttitle": 376 case "ndt": 377 $key = "nodefaulttitle"; 378 $val = true; 379 $this->useDefaultTitle = false; 380 break; 381 case "widedesc": 382 $val = true; 383 break; 384 case "table": 385 $this->style = "table"; 386 break; 387 case "list": 388 $this->style = "list"; 389 break; 390 case "namespacename": 391 $key = "namespacename"; 392 $val = true; 393 break; 394 case "ucnames": 395 $this->ucnames = true; 396 break; 397 398 case "debug": 399 $this->debug = true; 400 break; 401 case "last": 402 $key = 'maxrows'; 403 $val = intval($val); 404 break; 405 } 406 $this->opts [$key] = $val; 407 } 408 } 409 410 /** 411 * Check the supplied column names 412 */ 413 function _parseColumnNames() { 414 if(is_array($this->opts ["cols"])) { 415 $this->cols = $this->opts ["cols"]; 416 } else { 417 $this->cols = Array("page"); 418 } 419 420 if(count($this->cols) == 0) { 421 $cols [] = "page"; 422 $cols [] = "desc"; 423 } 424 425 $newCols = Array(); 426 427 foreach($this->cols as $index => $col) { 428 switch($col) { 429 case "page": 430 case "desc": 431 case "user": 432 case "userid": 433 case "mdate": 434 case "cdate": 435 case "rowno": 436 break; 437 case "comments": 438 $this->_loadPlugin("discussion"); 439 break; 440 case "tags": 441 $this->_loadPlugin("tag"); 442 break; 443 case "date": 444 $col = "mdate"; 445 break; 446 case "description": 447 $col = "desc"; 448 break; 449 default: 450 $this->_showDebugMsg("Unrecognized column name: \"$col\""); 451 $col = ''; 452 } 453 if($col != '') { 454 $this->_showDebugMsg("Recognized column name: $col"); 455 $newCols [] = $col; 456 } 457 } 458 $this->cols = $newCols; 459 if(count($this->opts ["hdrs"]) != count($this->cols)) { 460 $this->_showDebugMsg( 461 "The number of specified headers (".count($this->opts ["hdrs"]). 462 ") is not equal to the number of specified columns (". 463 count($this->cols).")!" 464 ); 465 } 466 } 467 468 /** 469 * Check the supplied tags 470 */ 471 function _parseTags() { 472 $this->hasTags = false; 473 474 if(is_array($this->opts ["tag"])) { 475 foreach($this->opts ["tag"] as $tag) { 476 if($tag == NULL || $tag == '') 477 continue; 478 $tag = mb_convert_case($tag, MB_CASE_LOWER, "UTF-8"); 479 if(substr($tag, 0, 1) == '!') { 480 $this->excludeTags [] = substr($tag, 1); 481 } else { 482 $this->includeTags [] = $tag; 483 } 484 $this->hasTags = true; 485 } 486 foreach($this->excludeTags as $tag) { 487 $this->_showDebugMsg("Specified exclude tag: $tag"); 488 } 489 foreach($this->includeTags as $tag) { 490 $this->_showDebugMsg("Specified include tag: $tag"); 491 } 492 } 493 } 494 495 /** 496 * Check the supplied sort keys 497 */ 498 function _parseSortKeys() { 499 if(is_array($this->opts ["sort"])) { 500 $this->sortKeys = $this->opts ["sort"]; 501 } 502 503 $sortKeys = Array(); 504 505 foreach($this->sortKeys as $index => $sortKey) { 506 507 $array = explode('-', strtolower($sortKey)); 508 if(count($array) == 1) { 509 $array = Array($sortKey, "a"); 510 } 511 512 switch($array [1]) { 513 case NULL: 514 case "a": 515 case "asc": 516 case "ascending": 517 $array [1] = false; 518 break; 519 case "d": 520 case "desc": 521 case "descending": 522 $array [1] = true; 523 break; 524 default: 525 $this->_showDebugMsg( 526 "Unrecognized sort column name modifier: ". 527 $array [1] 528 ); 529 $array [1] = false; 530 } 531 532 switch($array [0]) { 533 case "page": 534 case "desc": 535 case "user": 536 case "userid": 537 case "mdate": 538 case "cdate": 539 case "rowno": 540 break; 541 case "comments": 542 $this->_loadPlugin("discussion"); 543 break; 544 case "tags": 545 $this->_loadPlugin("tag"); 546 break; 547 case "date": 548 $array [0] = "mdate"; 549 break; 550 case "description": 551 $array [0] = "desc"; 552 break; 553 default: 554 $this->_showDebugMsg( 555 "Unrecognized sort column name: ".$array [0] 556 ); 557 $array [0] = NULL; 558 } 559 if($array [0]) { 560 $this->_showDebugMsg( 561 "Sort column ".$array [0]." ". 562 ($array [1] ? "descending" : "ascending") 563 ); 564 $sortKeys [] = $array; 565 } 566 } 567 568 $this->sortKeys = $sortKeys; 569 $this->nbrOfSortKeys = count($this->sortKeys); 570 } 571 572 /** 573 * Add a page to the collection of $pages. Check first if it should 574 * not be skipped... 575 */ 576 function _addFoundPage(&$data, $ns, $id, $type, $level) { 577 global $ID; 578 $fqid = $ns.$id; // Fully qualified id... 579 580 // 581 // If this file or directory should be skipped, do so 582 // 583 switch($type) { 584 case "f": 585 if(($fqid == ':'.$ID) && !$this->opts ["ego"]) // If we found ourself, skip it 586 return false; 587 $pageName = noNS($id); 588 if($pageName == $this->start) 589 return false; 590 foreach($this->opts ["skipfqid"] as $index => $skipitem) { 591 if($skipitem) { 592 if($skipitem == $fqid) { 593 // 594 // Remove the skip rule, it has no use any more... 595 // 596 $this->opts ["skipfqid"] [$index] = NULL; 597 $this->_showDebugMsg("Skipping $fqid due to skip rule $skipitem"); 598 return false; 599 } 600 } 601 } 602 if($this->opts ["collapse"]) { 603 // With collapse, only show: 604 // - pages within the same namespace as the current page 605 if($this->_getParentNS($fqid) != $this->_getParentNS($ID)) { 606 return false; 607 } 608 } 609 $linkid = $fqid; 610 break; 611 case "d": 612 $fqid .= ':'; 613 foreach($this->opts ["skipns"] as $skipitem) { 614 if($skipitem == $fqid) { 615 $this->_showDebugMsg("Skipping $fqid due to skip rule $skipitem"); 616 return false; 617 } 618 } 619 620 // Don't add startpages the user isn't authorized to read 621 if(auth_quickaclcheck(substr($linkid, 1)) < AUTH_READ) 622 return false; 623 624 if($this->opts ["collapse"]) { 625 // With collapse, only show: 626 // - sibling namespaces of the current namespace and it's ancestors 627 $curPathSplit = explode(":", trim(getNS($ID), ":")); 628 $fqidPathSplit = explode(":", trim(getNS($fqid), ":")); 629 630 // Find the last parent namespace that matches 631 // If there is only one more child namespace in the namespace under evaluation, 632 // Then this is a sibling of one of the parent namespaces of the current page. 633 // Siblings are ok, grandchild namespaces and below should be skipped (for collapse). 634 $clevel = 0; 635 if(count($curPathSplit) > 0) { 636 while(($clevel < count($fqidPathSplit) - 1) && ($clevel < count($curPathSplit))) { 637 if($curPathSplit[$clevel] == $fqidPathSplit[$clevel]) { 638 $clevel++; 639 } else { 640 break; 641 } 642 } 643 } 644 if(count($fqidPathSplit) > $clevel + 1) { 645 return false; 646 } 647 } 648 649 $linkid = $fqid.$this->start; 650 break; 651 } 652 653 // $this->_showDebugMsg ("$level $type $ns$id:"); 654 655 if($this->ucnames) { 656 $fqid = str_replace('_'," ",$fqid); 657 // $fqid = ltrim($fqid , ':'); 658 $fqid = preg_replace_callback( 659 '|:\w|', 660 function ($matches) { 661 return strtoupper($matches[0]); 662 }, 663 $fqid 664 ); 665 $fqid = ucwords($fqid); 666 } 667 $data [] = array( 668 'id' => $fqid, 669 'type' => $type, 670 'level' => $level, 671 'linkid' => $linkid, 672 'timestamp' => NULL 673 ); 674 675 return true; 676 } 677 678 /** 679 * Callback method for the search function in _parseOptions 680 */ 681 function _searchDir(&$data, $base, $file, $type, $level, $opts) { 682 global $ID; 683 $ns = $opts ["ns"]; 684 685 switch($type) { 686 case "d": 687 return $this->_addFoundPage($data, $ns, pathID($file), $type, $level); 688 case "f": 689 if(!preg_match('#\.txt$#', $file)) 690 return false; 691 //check ACL 692 $id = pathID($file); 693 if(auth_quickaclcheck($id) < AUTH_READ) 694 return false; 695 $this->_addFoundPage($data, $ns, $id, $type, $level); 696 } 697 698 return false; 699 } 700 701 /** 702 * Parse the options after the ~~DIR: string and 703 * return true if the table can be generated... 704 * 705 * A namespace specifaction should start with a colon and the flags 706 * should start with a question mark, like this: 707 * 708 * ~~DIR[[:<namespace>][?<flags>]]~~ 709 * 710 * To not break pages created with an older version of this plugin, this 711 * syntax is also supported: 712 * 713 * ~~DIR:<flags>~~ 714 * 715 * This assumes that no other colon is put in <flags> 716 */ 717 function _parseOptions($data) { 718 global $conf; 719 global $ID; 720 $ns = '.'; 721 $flags = trim($data, '~'); 722 $flags = substr($flags, strlen(DIR_PLUGIN_PATTERN)); 723 $flags = trim($flags); 724 725 $this->_showDebugMsg("specified arguments=".$flags); 726 727 if( 728 substr($flags, 0, 1) == ':' && 729 strpos(substr($flags, 1), '?') === FALSE && 730 strpos(substr($flags, 1), ':') === FALSE 731 ) { 732 // 733 // This is the "old" syntax where flags do not start with a question mark 734 // 735 $this->_showDebugMsg("parseOptions A"); 736 $flags = substr($flags, 1); 737 } else if( 738 substr($flags, 0, 1) == ':' && 739 strpos(substr($flags, 1), '?') === FALSE 740 ) { 741 // 742 // There is no questionmark so it's all namespace specification 743 // 744 $this->_showDebugMsg("parseOptions B"); 745 $ns = substr($flags, 1); 746 $flags = ''; 747 } else if(substr($flags, 0, 1) == '?') { 748 $this->_showDebugMsg("parseOptions C"); 749 $flags = substr($flags, 1); 750 } else if(strlen($flags) == 0) { 751 $this->_showDebugMsg("parseOptions D"); 752 } else if( 753 strpos(substr($flags, 1), '?') !== FALSE 754 ) { 755 $this->_showDebugMsg("parseOptions E"); 756 $tmp = explode('?', $flags); 757 758 if(count($tmp) == 2) { 759 $ns = substr($tmp [0], 1); 760 $flags = $tmp [1]; 761 } else { 762 $this->_showDebugMsg("ERROR: Multiple questionmarks are not supported"); 763 $flags = ''; 764 } 765 } else { 766 $this->_showDebugMsg("parseOptions E"); 767 $ns = $flags; 768 $flags = ''; 769 } 770 771 $this->_showDebugMsg("specified namespace=$ns"); 772 $this->_showDebugMsg("specified flags=$flags"); 773 774 $ns = $this->_parseNS($ns, true); 775 776 $path = $this->_ns2path($ns); 777 $this->_showDebugMsg("path=$path"); 778 779 $this->_initOpts($flags); 780 $this->_parseColumnNames(); 781 $this->_parseSortKeys(); 782 $this->_parseTags(); 783 784 // 785 // Check the column headers 786 // 787 $this->hdrs = $this->cols; 788 if(is_array($this->opts ["hdrs"])) { 789 foreach($this->opts ["hdrs"] as $index => $hdr) { 790 $this->hdrs [$index] = $hdr; 791 } 792 } 793 794 // 795 // Check the skip items 796 // 797 $this->opts ["skipfqid"] = Array(); 798 $this->opts ["skipns"] = Array(); 799 if(is_array($this->opts ["skip"])) { 800 foreach($this->opts ["skip"] as $skipitem) { 801 $item = $this->_parseNS($skipitem, false); 802 if(substr($item, -1) == ":") { 803 $this->opts ["skipns"] [] = $item; 804 } else { 805 $this->opts ["skipfqid"] [] = $item; 806 } 807 } 808 } 809 810 $this->_parseOptionsShow($data, $path, $ns); 811 812 // 813 // Search the directory $dir, only if the pages array 814 // is empty, since we can pass here several times (xhtml, latex). 815 // 816 $this->_showDebugMsg("Search directory $path"); 817 $this->_showDebugMsg("for namespace $ns"); 818 819 if(count($this->pages) == 0) { 820 search( 821 $this->pages, // results 822 $path, // folder root 823 array($this, '_searchDir'), // handler 824 array('ns' => $ns) // namespace 825 ); 826 } 827 $count = count($this->pages); 828 829 $this->_showDebugMsg("Found ".$count." pages!"); 830 831 if($count == 0) { 832 $this->_put(DOKU_LF."\t<p>There are no documents to show.</p>".DOKU_LF); 833 return false; 834 } 835 836 $this->_sortResult(); 837 if ( !empty($this->opts['maxrows']) && $this->opts['maxrows'] > 0 ) { 838 $this->pages = array_slice($this->pages, 0, $this->opts['maxrows']); 839 } 840 841 return true; 842 } 843 844 /** 845 * Sort the found pages according to the settings 846 */ 847 function _sortResult() { 848 849 if($this->nbrOfSortKeys == 0) 850 return; 851 852 usort($this->pages, array($this, "_sortPage")); 853 } 854 855 /** 856 * Compare function for usort 857 */ 858 function _sortPage($a, $b) { 859 return $this->_sortPageByKey($a, $b, 0); 860 } 861 862 function _sortPageByKey(&$a, &$b, $index) { 863 if($index >= $this->nbrOfSortKeys) 864 return 0; 865 866 $keyType = $this->sortKeys [$index]; 867 $sortKeyA = $this->_getSortKey($a, $keyType [0]); 868 $sortKeyB = $this->_getSortKey($b, $keyType [0]); 869 870 if($sortKeyA == $sortKeyB) 871 return $this->_sortPageByKey($a, $b, $index + 1); 872 873 if($keyType [1]) { 874 $tmp = $sortKeyA; 875 $sortKeyA = $sortKeyB; 876 $sortKeyB = $tmp; 877 } 878 879 return ($sortKeyA < $sortKeyB) ? -1 : 1; 880 } 881 882 /** 883 * Produces a sortable key 884 */ 885 function _getSortKey(&$page, $keyType) { 886 switch($keyType) { 887 case "page": 888 return html_wikilink($page ["id"]); 889 case "desc": 890 case "widedesc": 891 return $this->_getMeta($page, "description", "abstract"); 892 case "mdate": 893 return $this->_getMeta($page, "date", "modified"); 894 case "cdate": 895 return $this->_getMeta($page, "date", "created"); 896 case "user": 897 $users = $this->_getMeta($page, "contributor"); 898 if(is_array($users)) { 899 $index = 0; 900 foreach($users as $userid => $user) { 901 if($user && $user <> "") { 902 return $user; 903 } 904 } 905 } 906 return $users; 907 case "userid": 908 $users = $this->_getMeta($page, "contributor"); 909 if(is_array($users)) { 910 $index = 0; 911 foreach($users as $userid => $user) { 912 if($userid && $userid <> "") { 913 return $userid; 914 } 915 } 916 } 917 return $users; 918 case "comments": 919 return $this->_pluginCell("discussion", $page ["linkid"]); 920 case "tags": 921 return $this->_pluginCell("tag", $page ["linkid"]); 922 case "rowno": 923 return '0'; 924 } 925 926 return NULL; 927 } 928 929 /** 930 * Generate the content for the cell with the page link... 931 */ 932 function _tableCellContentID(&$page) { 933 $fqid = $page ["id"]; 934 $tmplvl = $page ["level"] - 1; 935 $spacerWidth = $tmplvl * 20; 936 $pageid = $fqid; 937 $name = NULL; 938 939 if($page ["type"] == 'd') { 940 if($this->opts ["namespacename"]) { 941 $pieces = explode(':', trim($pageid, ':')); 942 $name = array_pop($pieces); 943 } 944 $pageid .= ':'.$this->start; 945 } 946 947 if(!$this->useDefaultTitle) { 948 $name = explode(':', $fqid); 949 $name = ucfirst($name [count($name) - 1]); 950 } 951 952 switch($this->rdrMode) { 953 case 'latex': 954 $this->rdr->internallink($pageid, $name); 955 break; 956 case 'xhtml': 957 if($spacerWidth > 0) { 958 $this->_put('<div style="margin-left: '.$spacerWidth.'px;">'); 959 } 960 961 if($page ["type"] == 'd' && $this->ucnames) { 962 $dirlnk = html_wikilink($pageid, $name); 963 $dirlnk = str_replace('wikilink2', 'wikilink',$dirlnk); 964 $this->_put($dirlnk); 965 } 966 else $this->_put(html_wikilink($pageid, $name)); 967 if($spacerWidth > 0) { 968 $this->_put('</div>'); 969 } 970 break; 971 } 972 } 973 974 /** 975 * Get default value for an unset element 976 */ 977 function _getMeta(&$page, $key1, $key2 = NULL) { 978 if(!isset ($page ["meta"])) 979 $page ["meta"] = p_get_metadata($page ["linkid"], false, true); 980 981 // 982 // Use "created" instead of "modified" if null 983 // 984 if( 985 $key1 == "date" && 986 $key2 == "modified" && 987 !isset ($page ["meta"]["date"]["modified"]) 988 ) { 989 $key2 = "created"; 990 } 991 // 992 // Return "creator" if "contributor" is null 993 // 994 if($key1 == "contributor" && !isset ($page ["meta"]["contributor"])) { 995 $key1 = "creator"; 996 } 997 998 if(is_string($key2)) return $page ["meta"] [$key1] [$key2]; 999 1000 return $page ["meta"] [$key1]; 1001 } 1002 1003 /** 1004 * Generate the table cell content... 1005 */ 1006 function _tableCellContent(&$page, $col) { 1007 switch($col) { 1008 case "page": 1009 $this->_tableCellContentID($page); 1010 break; 1011 case "desc": 1012 case "widedesc": 1013 $this->_put($this->_getMeta($page, "description", "abstract")); 1014 break; 1015 case "mdate": 1016 $this->_putDate($this->_getMeta($page, "date", "modified")); 1017 break; 1018 case "cdate": 1019 $this->_putDate($this->_getMeta($page, "date", "created")); 1020 break; 1021 case "user": 1022 $users = $this->_getMeta($page, "contributor"); 1023 if(is_array($users)) { 1024 $index = 0; 1025 foreach($users as $userid => $user) { 1026 if($user && $user <> '') { 1027 if($index++ > 0) { 1028 $this->_putNewLine(); 1029 } 1030 $this->_put($user); 1031 } 1032 } 1033 } 1034 break; 1035 case "userid": 1036 $users = $this->_getMeta($page, "contributor"); 1037 if(is_array($users)) { 1038 $index = 0; 1039 foreach($users as $userid => $user) { 1040 if($userid && $userid <> '') { 1041 if($index++ > 0) { 1042 $this->_putNewLine(); 1043 } 1044 $this->_put($userid); 1045 } 1046 } 1047 } 1048 break; 1049 case "comments": 1050 if(!$this->modeIsLatex) 1051 $this->_put($this->_pluginCell("discussion", $page ["linkid"])); 1052 break; 1053 case "tags": 1054 if(!$this->modeIsLatex) 1055 $this->_put($this->_pluginCell("tag", $page ["linkid"])); 1056 break; 1057 case "rowno": 1058 $this->_put($this->rowNumber); 1059 break; 1060 default: 1061 $this->_put($col); 1062 } 1063 } 1064 1065 /** 1066 * Rewrite of renderer->table_open () because of class 1067 */ 1068 function _tableOpen() { 1069 1070 if($this->modeIsLatex) { 1071 $rdr = $this->rdr; 1072 $rdr->_counter['row_counter'] = 0; 1073 $rdr->_current_tab_cols = 0; 1074 if($rdr->info ['usetablefigure'] == "on") { 1075 $this->_putCmdNl("begin{figure}[h]"); 1076 } else { 1077 $this->_putCmdNl("vspace{0.8em}"); 1078 } 1079 $rdr->putcmd("begin{tabular}"); 1080 $rdr->put("{"); 1081 foreach($this->hdrs as $index => $hdr) { 1082 $rdr->put("l"); 1083 if($index + 1 < sizeof($this->hdrs)) 1084 $rdr->put('|'); 1085 } 1086 $rdr->putnl("}"); 1087 return; 1088 } 1089 1090 switch($this->style) { 1091 case "table": 1092 $class = "inline"; 1093 break; 1094 case "list": 1095 $class = "ul"; 1096 break; 1097 default: 1098 $class = "pagelist"; 1099 } 1100 $this->_showDebugMsg("Style=".$this->style." table class=$class"); 1101 $this->rdr->table_open (null, null, null, $class) ; 1102 } 1103 1104 /** 1105 * Rewrite of renderer->table_close () 1106 */ 1107 function _tableClose() { 1108 if($this->modeIsLatex) { 1109 $this->rdr->tabular_close(); 1110 return; 1111 } 1112 1113 $this->rdr->table_close () ; 1114 } 1115 1116 /** 1117 * Rewrite of renderer->tableheader_open () because of class 1118 */ 1119 function _tableHeaderCellOpen($class) { 1120 if($this->modeIsLatex) 1121 return; 1122 1123 $this->_put(DOKU_LF.DOKU_TAB.DOKU_TAB.'<th class="'.$class.'">'); 1124 } 1125 1126 /** 1127 * Rewrite of renderer->tableheader_close () 1128 */ 1129 function _tableHeaderCellClose($index) { 1130 if($this->modeIsLatex) { 1131 if(($index + 1) == sizeof($this->hdrs)) { 1132 $this->rdr->putnl('\\\\'); 1133 } else { 1134 $this->rdr->put('&'); 1135 } 1136 return; 1137 } 1138 return $this->rdr->tableheader_close(); 1139 } 1140 1141 /** 1142 * Rewrite of renderer->tablecell_open () because of class 1143 */ 1144 function _tableCellOpen($colspan, $class) { 1145 if($this->modeIsLatex) return; 1146 1147 $this->_put(DOKU_LF.DOKU_TAB.DOKU_TAB.'<td class="'.$class.'"'); 1148 1149 if($colspan > 1) 1150 $this->_put(' colspan="'.$colspan.'"'); 1151 1152 $this->_put(">"); 1153 } 1154 1155 /** 1156 * Rewrite of renderer->tablecell_close () because of class 1157 */ 1158 function _tableCellClose($index) { 1159 if($this->modeIsLatex) { 1160 if(($index + 1) == sizeof($this->hdrs)) { 1161 $this->rdr->putnl('\\\\'); 1162 } else { 1163 $this->rdr->put('&'); 1164 } 1165 return; 1166 } 1167 1168 return $this->rdr->tablecell_close(); 1169 } 1170 1171 /** 1172 * Return the class name to be used for the <td> showing $col. 1173 */ 1174 function _getCellClassForCol($col) { 1175 switch($col) { 1176 case "page": 1177 return "dpage"; 1178 case "date": 1179 case "user": 1180 case "desc": 1181 case "comments": 1182 case "tags": 1183 case "rowno": 1184 return $col; 1185 case "mdate": 1186 case "cdate": 1187 return "date"; 1188 case "userid": 1189 return "user"; 1190 case "": 1191 return ""; 1192 } 1193 $this->_showDebugMsg("Unknown style class for col $col"); 1194 return "desc"; 1195 } 1196 1197 function _tableHeaderRowOpen() { 1198 if($this->modeIsLatex) { 1199 $this->_putCmdNl('hline'); 1200 return; 1201 } 1202 1203 $this->rdr->tablerow_open(); 1204 } 1205 1206 function _tableHeaderRowClose() { 1207 if($this->modeIsLatex) { 1208 $this->_putCmdNl('hline'); 1209 return; 1210 } 1211 1212 $this->rdr->tablerow_close(); 1213 } 1214 1215 function _tableRowOpen() { 1216 if($this->modeIsLatex) 1217 return; 1218 1219 $this->rdr->tablerow_open(); 1220 } 1221 1222 function _tableRowClose() { 1223 if($this->modeIsLatex) 1224 return; 1225 1226 $this->rdr->tablerow_close(); 1227 } 1228 1229 /** 1230 * Return true if the tag plugin is not loaded, 1231 * if the ~~DIR~~ line has no tag attribute or 1232 * if the given page has one of the specified tags. 1233 */ 1234 function _hasTag($page) { 1235 if(!$this->hasTags) return true; 1236 1237 $plug = $this->plugins ['tag']; 1238 if(!$plug) return true; 1239 1240 // Get the tags of the current page 1241 $tmp = $this->_getMeta($page, "subject"); 1242 1243 if(!is_array($tmp)) return false; 1244 1245 $tags = Array(); 1246 1247 // Convert them to lowercase 1248 foreach($tmp as $tag) { 1249 $tags [] = mb_convert_case($tag, MB_CASE_LOWER, "UTF-8"); 1250 } 1251 1252 # 1253 # If there is an intersection with the exclude tags then we can not show 1254 # the current document 1255 # 1256 if(count($this->excludeTags) > 0) { 1257 if(count(array_intersect($tags, $this->excludeTags)) > 0) { 1258 $this->_showDebugMsg('skip'); 1259 return false; 1260 } 1261 } 1262 1263 # If the intersection with the include tags is not equal (in size) to the 1264 # array of include tags, we must skip the current document. 1265 if(count($this->includeTags) > 0) { 1266 $intersection = array_intersect($tags, $this->includeTags); 1267 if(count($intersection) != count($this->includeTags)) { 1268 return false; 1269 } 1270 } 1271 1272 return true; 1273 } 1274 1275 /** 1276 * Generate the actual table content... 1277 */ 1278 function _tableContent() { 1279 $doWideDesc = $this->opts ["widedesc"]; 1280 1281 if(!$this->opts ["noheader"]) { 1282 $this->_tableHeaderRowOpen(); 1283 foreach($this->hdrs as $index => $hdr) { 1284 $this->_tableHeaderCellOpen( 1285 $this->_getCellClassForCol($this->cols [$index]) 1286 ); 1287 $this->_put($hdr); 1288 $this->_tableHeaderCellClose($index); 1289 } 1290 $this->_tableHeaderRowClose(); 1291 } 1292 1293 foreach($this->pages as $page) { 1294 1295 if(!$this->_hasTag($page)) continue; 1296 1297 $this->rowNumber += 1; 1298 1299 $this->_tableRowOpen(); 1300 foreach($this->cols as $index => $col) { 1301 $this->_tableCellOpen(1, $this->_getCellClassForCol($col)); 1302 $this->_tableCellContent($page, $col); 1303 $this->_tableCellClose($index); 1304 } 1305 $this->_tableRowClose(); 1306 if($doWideDesc) { 1307 $this->_tableRowOpen(); 1308 $this->_tableCellOpen(count($this->cols), "desc"); 1309 $this->_tableCellContent($page, "widedesc"); 1310 $this->_tableCellClose(0); 1311 $this->_tableRowClose(); 1312 } 1313 } 1314 } 1315 1316 /** 1317 * Write data to the output stream 1318 */ 1319 function _put($data) { 1320 if($data == NULL || $data == '') 1321 return; 1322 1323 switch($this->rdrMode) { 1324 case 'xhtml': 1325 $this->rdr->doc .= $data; 1326 break; 1327 case 'latex': 1328 $this->rdr->put($data); 1329 break; 1330 } 1331 } 1332 1333 /** 1334 * Write a date to the output stream 1335 */ 1336 function _putDate($date) { 1337 $this->_put(strftime($this->dformat, $date)); 1338 } 1339 1340 function _putCmdNl($cmd) { 1341 $this->rdr->putcmdnl($cmd); 1342 } 1343 1344 function _putNewLine() { 1345 if($this->modeIsLatex) { 1346 $this->_putCmdNl('newline'); 1347 } else { 1348 $this->_put('<br />'); 1349 } 1350 } 1351 1352 /** 1353 * Do the real work 1354 */ 1355 function _dir($data) { 1356 if(!$this->_parseOptions($data)) 1357 return false; 1358 1359 // 1360 // If we already did the latex pass, skip the xhtml pass 1361 // 1362 if($this->processedLatex && $this->rdrMode == 'xhtml') { 1363 //return false ; 1364 } 1365 1366 // 1367 // Generate the actual table... 1368 // 1369 $this->_tableOpen(); 1370 $this->_tableContent(); 1371 $this->_tableClose(); 1372 1373 return true; 1374 } 1375 1376} // syntax_plugin_dir 1377 1378//Setup VIM: ex: et ts=2 enc=utf-8 : 1379