1<?php 2/** 3 * DokuWiki Plugin wikistats (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Chris4x4 <4x4.chris@gmail.com> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) die(); 11 12if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 13require_once(DOKU_PLUGIN.'syntax.php'); 14 15/** 16 * All DokuWiki plugins to extend the parser/rendering mechanism 17 * need to inherit from this class 18 */ 19class syntax_plugin_wikistats extends DokuWiki_Syntax_Plugin { 20 /** 21 * Get an associative array with plugin info. 22 * 23 * <p> 24 * The returned array holds the following fields: 25 * <dl> 26 * <dt>author</dt><dd>Author of the plugin</dd> 27 * <dt>email</dt><dd>Email address to contact the author</dd> 28 * <dt>date</dt><dd>Last modified date of the plugin in 29 * <tt>YYYY-MM-DD</tt> format</dd> 30 * <dt>name</dt><dd>Name of the plugin</dd> 31 * <dt>desc</dt><dd>Short description of the plugin (Text only)</dd> 32 * <dt>url</dt><dd>Website with more information on the plugin 33 * (eg. syntax description)</dd> 34 * </dl> 35 * @param none 36 * @return Array Information about this plugin class. 37 * @public 38 * @static 39 */ 40 function getInfo(){ 41 return confToHash(dirname(__FILE__).'/plugin.info.txt'); 42 } 43 44 /** 45 * Get the type of syntax this plugin defines. 46 * 47 * The type of this plugin is "substition". 48 * 49 * @param none 50 * @return String <tt>'substition'</tt>. 51 * @public 52 * @static 53 */ 54 public function getType() { 55 return 'substition'; 56 } 57 58 /** 59 * Define how this plugin is handled regarding paragraphs. 60 * 61 * <p> 62 * This method is important for correct XHTML nesting. It returns 63 * one of the following values: 64 * </p> 65 * <dl> 66 * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd> 67 * <dt>block</dt><dd>Open paragraphs need to be closed before 68 * plugin output.</dd> 69 * <dt>stack</dt><dd>Special case: Plugin wraps other paragraphs.</dd> 70 * </dl> 71 * @param none 72 * @return String <tt>'normal'</tt>. 73 * @public 74 * @static 75 */ 76 public function getPType() { 77 return 'normal'; 78 } 79 80 /** 81 * Where to sort in? 82 * 83 * Sort the plugin in just behind the formating tokens 84 * Low numbers go before high numbers 85 * 86 * @param none 87 * @return Integer <tt>128</tt>. 88 * @public 89 * @static 90 */ 91 public function getSort() { 92 return 128; 93 } 94 95 /** 96 * Connect lookup pattern to lexer. 97 * 98 * @param $aMode String The desired rendermode. 99 * @return none 100 * @public 101 * @see render() 102 */ 103 public function connectTo($mode) { 104 $this->Lexer->addSpecialPattern('\{\{wikistats>[^}]*\}\}',$mode,'plugin_wikistats'); 105 } 106 107 /** 108 * Handle matches of the wikistats syntax 109 * 110 * @param string $match The match of the syntax 111 * @param int $state The state of the handler 112 * @param int $pos The position in the document 113 * @param Doku_Handler $handler The handler 114 * @return array Data for the renderer 115 */ 116 public function handle($match, $state, $pos, Doku_Handler &$handler){ 117 $match = substr($match, 12, -2); 118 119 $data = array( 120 'ns' => array(), 121 'type' => array() 122 ); 123 124 $match = explode('&', $match); 125 foreach($match as $m) { 126 if (preg_match('/(\w+)\s*=(.+)/', $m, $temp) == 1){ 127 $this->handleNamedParameter($temp[1], trim($temp[2]), $data); 128 } else { 129 $this->addNamespace($data, trim($m)); 130 } 131 } 132 133 return $data; 134 } 135 136 /** 137 * Handle parameters that are specified using <name>=<value> syntax 138 */ 139 function handleNamedParameter($name, $value, &$data) { 140 static $types = array('pages', 'medias', 'stats'); 141 142 switch($name) { 143 case 'ns': 144 foreach(preg_split('/\s*,\s*/', $value) as $value) { 145 $this->addNamespace($data, $value); 146 } 147 break; 148 case 'type': 149 if (preg_match('/(\w+)/', $value, $match) == 1) { 150 if (in_array($match[1], $types)) { 151 $data[$name] = $match[1]; 152 } 153 } 154 break; 155 } 156 } 157 158 /** 159 * Clean-up the namespace name and add it (if valid) into the $data array 160 */ 161 function addNamespace(&$data, $namespace) { 162 $action = ($namespace{0} == '-') ? 'exclude' : 'include'; 163 $namespace = cleanID(preg_replace('/^[+-]/', '', $namespace)); 164 if (!empty($namespace)) { 165 $data['ns'][$action][] = $namespace; 166 } 167 } 168 169 /** 170 * Render xhtml output or metadata 171 * 172 * @param string $mode Renderer mode (supported modes: xhtml) 173 * @param Doku_Renderer $renderer The renderer 174 * @param array $data The data from the handler() function 175 * @return bool If rendering was successful. 176 */ 177 public function render($mode, Doku_Renderer &$renderer, $data) { 178 global $conf; 179 global $ID; 180 global $TOC; 181 182 if ($mode != 'xhtml') return false; 183 184 // prevent caching to ensure the included pages are always fresh 185 $renderer->info['cache'] = false; 186 187 $list = array(); 188 $content = ''; 189 190 switch ($data['type']) { 191 case "pages": 192 search($list, $conf['datadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns']), ''); 193 $content = $list['pages_count']; 194 break; 195 case "medias": 196 search($list, $conf['mediadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns'])); 197 $content = $list['medias_count']; 198 break; 199 case "stats": 200 search($list, $conf['datadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns']), ''); 201 search($list, $conf['mediadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns'])); 202 203 if ($this->getConf('display_toc')) { 204 $TOC = p_get_metadata($ID,'description tableofcontents'); 205 } else { 206 $TOC = NULL; 207 } 208 $content .= $this->displayResourceStats($list); 209 $content .= $this->displayTagStats($list); 210 $content .= $this->displayNamespaceStats($list['dir_label']['ns']); 211 212 break; 213 } 214 $renderer->doc .= $content; 215 216 return true; 217 } 218 219 function _search_count(&$data, $base, $file, $type, $lvl, $opts){ 220 if ($type == 'd') { 221 if ($data['dir_nest'] < $lvl) $data['dir_nest'] = $lvl; 222 223 $data['dir_count']++; 224 225 return true; 226 } 227 228 $file = str_replace("/", ":", $file); 229 230 // filter included namespaces 231 if (isset($opts['ns']['include'])) { 232 if (!$this->isInNamespace($opts['ns']['include'], $file)) return false; 233 } 234 235 // filter excluded namespaces 236 if (isset($opts['ns']['exclude'])) { 237 if ($this->isInNamespace($opts['ns']['exclude'], $file)) return false; 238 } 239 240 switch ($opts['type']) { 241 case "pages": 242 $data['pages_count']++; 243 break; 244 case "medias": 245 $data['medias_count']++; 246 break; 247 case "stats": 248 $path = $file; 249 $file = substr($file, 1); 250 $file = substr($file, 0, strrpos($file, ':')); 251 //if (pathinfo($path, PATHINFO_EXTENSION) != 'txt') $file = substr($file, 0, strrpos($file, ':')); 252 253 switch (pathinfo($path, PATHINFO_EXTENSION)) { 254 case 'txt': 255 $data['pages_count']++; 256 $data['dir_label']['ns'][$file]['pages']++; 257 break; 258 case 'swf': 259 $data['flash_count']++; 260 $data['dir_label']['ns'][$file]['medias']++; 261 break; 262 case 'jpg': 263 case 'jpeg': 264 case 'png': 265 case 'gif': 266 case 'svg': 267 $data['images_count']++; 268 $data['dir_label']['ns'][$file]['medias']++; 269 break; 270 case 'mov': 271 case 'avi': 272 case 'flv': 273 case 'mp4': 274 case 'webm': 275 case 'ogv': 276 $data['movies_count']++; 277 $data['dir_label']['ns'][$file]['medias']++; 278 break; 279 case 'wav': 280 case 'mp3': 281 case 'ogg': 282 $data['audio_count']++; 283 $data['dir_label']['ns'][$file]['medias']++; 284 break; 285 case 'xls': 286 case 'xlsm': 287 case 'xlsx': 288 case 'ods': 289 $data['sheets_count']++; 290 $data['dir_label']['ns'][$file]['medias']++; 291 break; 292 case 'doc': 293 case 'docx': 294 case 'odt': 295 $data['writer_count']++; 296 $data['dir_label']['ns'][$file]['medias']++; 297 break; 298 case 'ppt': 299 case 'pptx': 300 case 'odp': 301 $data['presentation_count']++; 302 $data['dir_label']['ns'][$file]['medias']++; 303 break; 304 case 'djvu': 305 case 'epub': 306 case 'mobi': 307 case 'pdf': 308 $data['documents_count']++; 309 $data['dir_label']['ns'][$file]['medias']++; 310 break; 311 case 'tar': 312 case 'arj': 313 case 'zip': 314 case 'bzip': 315 case 'rar': 316 case 'tgz': 317 case 'gz': 318 case '7z': 319 case 'bz2': 320 $data['archives_count']++; 321 $data['dir_label']['ns'][$file]['medias']++; 322 break; 323 case 'exe': 324 case 'com': 325 $data['binaries_count']++; 326 $data['dir_label']['ns'][$file]['medias']++; 327 break; 328 default: 329 //var_export($file); 330 $data['others_count']++; 331 $data['dir_label']['ns'][$file]['medias']++; 332 break; 333 } 334 break; 335 } 336 337 return false; 338 } 339 340 /** 341 * Check if page belongs to one of namespaces in the list 342 */ 343 function isInNamespace($namespaces, $id) { 344 foreach($namespaces as $ns) { 345 if ((strpos($id, ':' . $ns . ':') === 0)) { 346 return true; 347 } 348 } 349 return false; 350 } 351 352 /** 353 * Display pages and medias stats 354 */ 355 private function displayResourceStats($list) { 356 global $TOC; 357 358 $content = ''; 359 360 if ($this->getConf('display_ressources_stats')) { 361 if ($this->getConf('display_toc')) { 362 $TOC[] = Array('hid' => 'resources', 'title' => $this->getLang('resources_title'), 'type' => 'ul', 'level' => '2'); 363 } 364 365 $class = $this->getConf('display_type'); 366 $col = "page"; 367 368 $content .= '<!-- Resources Stats -->'.DOKU_LF; 369 $content .= '<h2 class="sectionedit2" id="resources">'.$this->getLang('resources_title').'</h2>'.DOKU_LF; 370 $content .= '<div class="level2">'.DOKU_LF; 371 $content .= '<table class="'.$class.'">'.DOKU_LF; 372 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 373 $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">'.$this->getLang('resources').'</th>'.DOKU_LF; 374 $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">#</th>'.DOKU_LF; 375 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 376 377 if(empty($list)) { 378 // Skip output 379 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 380 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'" colspan="2">'.$this->getLang('empty_output').'</td>'.DOKU_LF; 381 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 382 } else { 383 foreach($list as $resname => $count) { 384 if ($count <= 0) continue; // don't display resources with zero occurrences 385 if ($resname === "dir_nest") continue; 386 if ($resname === "dir_count") continue; 387 if ($resname === "dir_label") continue; 388 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 389 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$this->getLang($resname).'</td>'.DOKU_LF; 390 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$count.'</td>'.DOKU_LF; 391 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 392 } 393 } 394 395 $content .= '</table>'.DOKU_LF; 396 $content .= '</div>'.DOKU_LF.DOKU_LF; 397 } 398 return $content; 399 } 400 401 /** 402 * Display tags related stats 403 */ 404 private function displayTagStats($list) { 405 global $TOC; 406 407 $content = ''; 408 409 if ($this->getConf('display_tags_stats')) { 410 if (plugin_isdisabled('tag') || (!$tag = plugin_load('helper', 'tag'))) { 411 msg('The Tag Plugin must be installed to display tag related stats.', -1); 412 } else { 413 if ($this->getConf('display_toc')) { 414 $TOC[] = Array('hid' => 'tags', 'title' => $this->getLang('tags_title'), 'type' => 'ul', 'level' => '2'); 415 } 416 417 $occurrences = $tag->tagOccurrences(NULL, NULL, true, NULL); 418 ksort($occurrences); 419 420 $class = $this->getConf('display_type'); 421 $col = "page"; 422 423 $content .= '<!-- Tags Stats -->'.DOKU_LF; 424 $content .= '<h2 class="sectionedit2" id="tags">'.$this->getLang('tags_title').'</h2>'.DOKU_LF; 425 $content .= '<div class="level2">'.DOKU_LF; 426 $content .= '<table class="'.$class.'">'.DOKU_LF; 427 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 428 $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">'.$this->getLang('tags').' ('.count($occurrences).')</th>'.DOKU_LF; 429 $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">#</th>'.DOKU_LF; 430 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 431 432 if (empty($occurrences)) { 433 // Skip output 434 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 435 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'" colspan="2">'.$this->getLang('empty_output').'</td>'.DOKU_LF; 436 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 437 } else { 438 foreach($occurrences as $tagname => $count) { 439 if ($count <= 0) continue; // don't display tags with zero occurrences 440 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 441 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$tagname.'</td>'.DOKU_LF; 442 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$count.'</td>'.DOKU_LF; 443 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 444 } 445 } 446 447 $content .= '</table>'.DOKU_LF; 448 $content .= '</div>'.DOKU_LF.DOKU_LF; 449 } 450 } 451 return $content; 452 } 453 454 /** 455 * Display namespaces related stats 456 */ 457 private function displayNamespaceStats($list) { 458 global $TOC; 459 460 $content = ''; 461 462 if ($this->getConf('display_namespaces_stats')) { 463 if ($this->getConf('display_toc')) { 464 $TOC[] = Array('hid' => 'namespaces', 'title' => $this->getLang('namespaces_title'), 'type' => 'ul', 'level' => '2'); 465 } 466 467 $class = $this->getConf('display_type'); 468 $col = "page"; 469 470 $content .= '<!-- Namespaces Stats -->'.DOKU_LF; 471 $content .= '<h2 class="sectionedit2" id="namespaces">'.$this->getLang('namespaces_title').'</h2>'.DOKU_LF; 472 $content .= '<div class="level2">'.DOKU_LF; 473 $content .= '<table class="'.$class.'">'.DOKU_LF; 474 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 475 $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">'.$this->getLang('namespaces').' ('.count($list).')</th>'.DOKU_LF; 476 $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'"># '.$this->getLang('pages_count').'</th>'.DOKU_LF; 477 $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'"># '.$this->getLang('medias_count').'</th>'.DOKU_LF; 478 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 479 480 if(empty($list)) { 481 // Skip output 482 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 483 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'" colspan="3">'.$this->getLang('empty_output').'</td>'.DOKU_LF; 484 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 485 } else { 486 ksort($list); 487 488 foreach($list as $namespace => $arr) { 489 if ($namespace == '') $namespace = '[root]'; 490 $content .= DOKU_TAB.'<tr>'.DOKU_LF; 491 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$namespace.'</td>'.DOKU_LF; 492 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.(isset($arr['pages']) ? $arr['pages'] : '0') .'</td>'.DOKU_LF; 493 $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.(isset($arr['medias']) ? $arr['medias'] : '0') .'</td>'.DOKU_LF; 494 $content .= DOKU_TAB.'</tr>'.DOKU_LF; 495 } 496 } 497 498 $content .= '</table>'.DOKU_LF; 499 $content .= '</div>'.DOKU_LF.DOKU_LF; 500 } 501 return $content; 502 } 503 504} 505 506// vim:ts=4:sw=4:et: 507