1<?php 2 3/***************************************************************** 4 * SearchPattern plugin for dokuwiki / Syntax plugin 5 * 6 * Syntax : 7 * ~~SEARCHPATTERN(:|;|#)\'[pattern or regexp to search]\'~~ 8 * ~~SEARCHPATTERN:\'myString\'~~ -> search "myString" - case sensitive 9 * ~~SEARCHPATTERN;\'myString\'~~ -> search "myString" - case insensitive 10 * ~~SEARCHPATTERN#\'myRegex\'~~ -> search myRegex 11 * ~~SEARCHPATTERN#'/======([^=]+)======/'~~ -> search all headlines and output the headlines 12 * 13 * In combination with dokuwiki todo plugin version (at least v20130408), 14 * it is a lightweight solution for a task management system based on dokuwiki. 15 * use this searchpattern expression for open todos: 16 * ~~SEARCHPATTERN#'/<todo[^#>]*>.*?<\/todo[\W]*?>/'?? _ToDo ??~~ 17 * use this searchpattern expression for completed todos: 18 * ~~SEARCHPATTERN#'/<todo[^#>]*#[^>]*>.*?<\/todo[\W]*?>/'?? _ToDo ??~~ 19 * do not forget the no-cache option 20 * ~~NOCACHE~~ 21 * 22 * Options are passed by adding them between two times '??' after last single quote and before double tilde. 23 * Regex syntax is strictly the same as the one used with PHP "preg_match" function. 24 * !!! In all cases, every single quote inside the string/regex shall be doubled. !!! 25 * 26 * Valid Options (between the double ?? questionmarks): 27 * - restricting option (page or namespace exclude) 28 * + limit option (only page or namespace included) 29 * $ output all regex matches 30 * $<Matches> output given comma separated regex matches e.g. $3,1 will output match 3 and 1 31 * _ call a other dokuwiki syntax plugin to format the matching/output 32 * 33 * Example: 34 * ~~SEARCHPATTERN#'/<todo[^#>]*#[^>]*>.*?<\/todo[\W]*?>/'?? _ToDo ??~~ 35 * this will search all <todo #>Some Todo</todo> tags and call the todo plugin (option _ToDo) for outputting 36 * ~~SEARCHPATTERN#'/([\W]+)[\W]+([\W]+)[\W]+([\W]+)[\W]+/'?? $1,3 ??~~ 37 * this will only output the 1st and the 3rd regex match 38 * ~~SEARCHPATTERN#'/<todo[^@>]*@([^\W]+)[^#>]*(#)?[^>]*>(.*?)<\/todo[\W]*?>/'?? $2,1,3 ??~~ 39 * the matches will displayed in the following order: 2nd match, 1st match, 3rd match 40 * this will output all assigned todos: the # Flag (todo completed), the username and the todo text 41 * as example input: <todo @leo #>Finished task for leo</todo> <todo>Some task</todo> <todo @leo>Uncompleted task for leo</todo> 42 * output: | # | leo | Finished task for leo | 43 * | | leo | Uncompleted task for leo | 44 * ~~SEARCHPATTERN#'/FIXME[ \t]+([^ \t\n\r]+)([ \t]*)?([^ \t\n\r]+)?/i'?? $3,1 ??~~ 45 * will display one or two words after the word FIXME in reverse direction 46 * as example input: FIXME firstWord secondWord 47 * output: | secondWord | firstWord | 48 * 49 * 50 * Compatibility: 51 * Release 2013-03-06 "Weatherwax RC1" 52 * Release 2012-10-13 "Adora Belle" 53 * 54 * @author Matthieu Rioteau <matthieu<dot>rioteau<at>skf<dot>com>; Leo Eibler <dokuwiki@sprossenwanne.at> 55 * 56 */ 57 58/** 59 * ChangeLog: 60 * 61 * [06/16/2013]: by Leo Eibler <dokuwiki@sprossenwanne.at> / http://www.eibler.at 62 * bugfix: implement suggestions from Matthieu and use correct 'badopt' configuration from admin panel 63 * implement new admin option 'dispheadl' to show or hide regex code in result table 64 * [06/16/2013]: by Matthieu Rioteau <matthieu<dot>rioteau<at>skf<dot>com> / http://wiki.splitbrain.org/plugin:searchpattern 65 * bugfix: incorrect XHTML or PHP warnings in log 66 * include a better default.php configuration file 67 * [04/15/2013]: by Leo Eibler <dokuwiki@sprossenwanne.at> / http://www.eibler.at 68 * translation of language file to german 69 * [04/15/2013]: by Matthieu Rioteau <matthieu<dot>rioteau<at>skf<dot>com> / http://wiki.splitbrain.org/plugin:searchpattern 70 * bugfix: incorrect XHTML or PHP warnings in log 71 * bugfix: quickaclcheck only handles pages (not namespaces) 72 * [04/12/2013]: by Leo Eibler <dokuwiki@sprossenwanne.at> / http://www.eibler.at 73 * bugfix: incorrect if statement using $ syntax 74 * example using FIXME and $3,1 75 * bugfix: use parameter call_plugin_handler and fallback to lowercase if plugin not found 76 * [04/11/2013]: by Leo Eibler <dokuwiki@sprossenwanne.at> / http://www.eibler.at 77 * change description / comments and syntax howto about integration with dokuwiki plugin 'todo' 78 * bugfix: encoding html code (security risk <script>alert('hi')</script>) when using regex and match output $ (dollar) option. 79 * bugfix: default behavior (output only count) if no $ (dollar) is used 80 * [04/08/2013]: by Leo Eibler <dokuwiki@sprossenwanne.at> / http://www.eibler.at 81 * add description / comments and syntax howto about integration with dokuwiki plugin 'todo' 82 * check compatibility with dokuwiki release 2012-10-13 "Adora Belle" and 2013-03-06 "Weatherwax RC1" 83 * reformat inline documentation of syntax.php file 84 * remove getInfo() call because it's done by plugin.info.txt (since dokuwiki 2009-12-25 "Lemming") 85 * [04/07/2013]: by Leo Eibler <dokuwiki@sprossenwanne.at> / http://www.eibler.at 86 * add regex match output with new option $ (dollar) 87 * add callback handler method to call other plugin for formatting the output with new option _ (underscore) 88 * [07/05/2010]: by Matthieu Rioteau <matthieu<dot>rioteau<at>skf<dot>com> / http://wiki.splitbrain.org/plugin:searchpattern 89 * Add capability to exclude pages/namespaces from search, or to restrict it to certain pages/namespaces 90 * [01/05/2010]: by Matthieu Rioteau <matthieu<dot>rioteau<at>skf<dot>com> / http://wiki.splitbrain.org/plugin:searchpattern 91 * Initial release 92 * [12/10/2009]: by Matthieu Rioteau <matthieu<dot>rioteau<at>skf<dot>com> / http://wiki.splitbrain.org/plugin:searchpattern 93 * Creation 94 */ 95 96if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/'); 97if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 98 99require_once(DOKU_PLUGIN.'syntax.php'); 100require_once(DOKU_INC.'inc/search.php'); //needed for use of the "search" function to list pages 101require_once(DOKU_INC.'inc/common.php'); //needed for use of "wl' function 102 103/** 104* Convert a string to a regex so it can be used in PHP "preg_match" function 105*/ 106function str2regex($str){ 107 $regex = ''; //init 108 for($i = 0; $i < strlen($str); $i++){ //for each char in the string 109 if(!ctype_alnum($str[$i])){ //if char is not alpha-numeric 110 $regex = $regex.'\\'; //escape it with a backslash 111 } 112 $regex = $regex.$str[$i]; //compose regex 113 } 114 return $regex; //return 115} 116 117/** 118* Callback function for "search" function 119* Look for matches in wiki files and store them 120*/ 121function search_pattern(&$data, $base, $file, $type, $lvl, $opts) { 122 global $INFO; 123 $id = pathID($file); //get current file ID 124 if($type == 'd'){ //if type is directory 125 return true; //recurse but don't process 126 } 127 if(auth_quickaclcheck($id) < AUTH_READ) { //if user don't have enough rights 128 return false; //don't processa and don't recurse 129 } 130 if(!preg_match("/.*\.txt$/", $file)) { //if file is not a true wiki file (.txt) 131 return true; //don't process 132 } 133 if(isset($opts['page_limit']) || isset($opts['nmsp_limit'])){ //if limiting options have been used 134 $process = false; //page is not processed by default : will be true if page shall be processed 135 if(isset($opts['page_limit'])) 136 { 137 foreach($opts['page_limit'] as $page){ //going through limiting page list 138 if($id == $page){ //if the analyzed page is in the list 139 $process = true; //process it 140 break; 141 } 142 } 143 } 144 if(!$process){ //if analyzed page wasn't in the page list 145 if(isset($opts['nmsp_limit'])) 146 { 147 foreach($opts['nmsp_limit'] as $nmsp){ //going through the limiting namespace list 148 if(substr($id,0,strlen($nmsp)) == $nmsp){ //if analyzed page is in the list 149 $process = true; // process it 150 break; 151 } 152 } 153 } 154 } 155 if(!$process){ //if page shall not be processed 156 return true; //recurse but don't process 157 } 158 } 159 if(isset($opts['page_exclude']) || isset($opts['nmsp_exclude'])){ //if restricting option have been used 160 if(isset($opts['page_exclude'])) { 161 foreach($opts['page_exclude'] as $page){ //going through the restricting page list 162 if($id == $page){ //if analyzed page is in the exclusion list 163 return true; //recurse but don't process 164 } 165 } 166 } 167 if(isset($opts['nmsp_exclude'])) { 168 foreach($opts['nmsp_exclude'] as $nmsp){ //going through the restricting namespace list 169 if(substr($id,0,strlen($nmsp)) == $nmsp){ //if analyzed page is in the exclusion list 170 return true; //recurse but don't process 171 } 172 } 173 } 174 } 175 $filebody = file_get_contents($base.$file); //get file content 176 if($INFO['id'] == $id){ //if we are processing the page in which plugin has been called 177 $filebody = str_replace($opts['match'], '', $filebody, $count); //remove the call to avoid counting it 178 $count--; //hold number of extra pattern occurences that should have been unvolunteerly removed 179 } 180 else{ 181 $count = 0; //for correct calculation 182 } 183 $nboccur = preg_match_all($opts['pattern'], $filebody, $matches) + $count; //count how many times appears the pattern 184 if($nboccur != 0){ //if it appears at least once 185 $data[$id] = $nboccur; //store it in data 186 if( count($matches) > 0 ) { 187 // if at least 1 match () is found 188 $data['{'.$id.'}'] = $matches; 189 } 190 } 191 return true; //return and continue processing 192} 193 194/** 195* All DokuWiki plugins to extend the parser/rendering mechanism 196* Need to inherit from this class 197*/ 198class syntax_plugin_searchpattern extends DokuWiki_Syntax_Plugin { 199 /** 200 * Return some info 201 */ 202 /* 203 function getInfo(){ 204 // replaced by plugin.info.txt file 205 return array( 206 'author' => 'Matthieu Rioteau', 207 'email' => 'matthieu<dot>rioteau<at>skf<dot>com', 208 'date' => '2010-07-05', 209 'name' => 'SearchPattern plugin ver. 0.2', 210 'desc' => 'Find a specified pattern inside wiki pages. 211 syntax : ~~SEARCHPATTERN(:|;|#)\'[pattern or regexp to search]\'~~ 212 ~~SEARCHPATTERN:\'myString\'~~ -> search "myString" - case sensitive 213 ~~SEARCHPATTERN;\'myString\'~~ -> search "myString" - case insensitive 214 ~~SEARCHPATTERN#\'myRegex\'~~ -> search myRegex 215 Regex syntax is strictly the same as the one used with PHP "preg_match" function. 216 In all cases, every single quote inside the string/regex shall be doubled. 217 Options can be passed to limit pages/namespaces search. See website.', 218 'url' => 'http://wiki.splitbrain.org/plugin:searchpattern', 219 ); 220 } 221 */ 222 223 /** 224 * Plugin is a substitution one : typo is volunteer 225 */ 226 function getType(){ 227 return 'substition'; 228 } 229 230 /** 231 * Paragraph type is "normal" 232 */ 233 function getPType(){ 234 return 'normal'; 235 } 236 237 /** 238 * Sort order (don't know where it is used) 239 */ 240 function getSort(){ 241 return 250; //randon number, don't know what to put 242 } 243 244 245 /** 246 * Register the ID pattern to the lexer 247 */ 248 function connectTo($mode) { 249 $this->Lexer->addSpecialPattern('~~SEARCHPATTERN[\;\:\#]\'(?:\'\')*[^\r\v\t\r\n]*[^\']\'(?:\'\')*(?:\?\?[^\r\v\t\r\n\?]+\?\?)?~~', $mode, 'plugin_searchpattern'); 250 } 251 252 /** 253 * Handle the match 254 */ 255 function handle($match, $state, $pos, &$handler){ 256 global $INFO; 257 258 $params = array(); //store all parameters needed for the render computing 259 $params['match'] = $match; //store the original matching string 260 $params['reg_err'] = false; //init needed for future comparison 261 $params['regex_output_matches'] = false; // default behavior if no $ (dollar) is used 262 $params['call_plugin_handler'] = false; 263 if(substr($match,-4,2) == '??'){ //if options are passed 264 $options = substr($match, 0, -4); //extract options ... 265 $match = substr($options, 0, strrpos($options,'??')).'~~'; //... 266 $options = substr($options, strrpos($options,'??')+2); //... 267 if($options != ""){ 268 $options_list = explode(' ', $options); //explode options in an array 269 $limit = false; //init : $limit will be true if a limiting option is passed 270 foreach($options_list as $optid => $option){ //going through the options to clean and pretreat 271 if($option == ""){ //remove empty ones... 272 unset($options_list[$optid]); //... 273 } 274 if($option[0] == "+"){ //if a limiting option is used 275 $limit = true; //update flag 276 } 277 } 278 foreach($options_list as $option){ //going through the options to treat them 279 $optact = $option[0]; //action is defined by first character 280 switch($optact){ 281 // @date 20130407 by Leo Eibler <dokuwiki@sprossenwanne.at> extend output of matches with $ 282 case '$': //if it is a regex with displaying option of matches 283 if(substr($option,1) == ""){ //if nothing more, we will output all matches 284 $params['regex_output_matches'] = array(); 285 } else { 286 // take match numbers as comma separated list 287 $params['regex_output_matches'] = explode( ',', substr($option,1) ); 288 } 289 break; 290 case '_': //call the _searchpatternHandler() method from the given plugin 291 if(substr($option,1) == ""){ //no plugin defined 292 // we cannot call a handler if the plugin name is not defined 293 } else { 294 $params['call_plugin_handler'] = substr($option,1); 295 } 296 break; 297 case '+': //if it is a limiting option 298 if(substr($option,1) == ""){ //if nothing more 299 $params['page_limit'][] = $INFO['id']; //add current page to limiting 300 } 301 elseif(substr($option,1) == ":"){ //if ':' 302 $params['nmsp_limit'][] = substr($INFO['id'],0,strrpos($INFO['id'],":")+1); //add current namespace to limiting 303 } 304 elseif($option[strlen($option)-1] == ":"){ //if parameter is a namespace 305 $params['nmsp_limit'][] = substr($option,1); //add to limiting namespace list 306 } 307 else{ 308 $params['page_limit'][] = substr($option,1); //add to limiting page list 309 } 310 break; 311 case '-': //if it is a restricting option 312 if($limit){ //if a limiting option is also used 313 $params['ignored_options'][] = $option; //restricting options will be ignored -> store them 314 } 315 else{ 316 if(substr($option,1) == ""){ // if nothing more 317 $params['page_exclude'][] = $INFO['id']; //add current page to restricting 318 } 319 elseif(substr($option,1) == ":"){ //if ':' 320 $params['nmsp_exclude'][] = substr($INFO['id'],0,strrpos($INFO['id'],":")+1); //add current namespace to restricting 321 } 322 elseif($option[strlen($option)-1] == ":"){ //if parameter is a namespace 323 $params['nmsp_exclude'][] = substr($option,1); //add to restricting namespace list 324 } 325 else{ 326 $params['page_exclude'][] = substr($option,1); //add to page restricting list 327 } 328 } 329 break; 330 default: 331 $params['bad_options'][] = $option; //store unknown options 332 } 333 } 334 } 335 } 336 $pattern = substr($match, 17, -3); //extract the pattern we are searching 337 if(preg_match('/[^\']\'(\'\')*[^\']/', $pattern, $matches)){ //if there is at least one non-doubled single quote in the pattern 338 $params['ndq_err'] = true; //store error 339 } 340 else{ 341 $params['ndq_err'] = false; //or correctness 342 } 343 $pattern=str_replace('\'\'', '\'', $pattern); //remove doubled quote from pattern 344 $params['or_pattern'] = $pattern; //and store it as original one 345 switch($match[15]){ //determine the type of search and subsequent treatment 346 case ':': //this a normal case-sensitive search 347 $params['type'] = 'normal'; //type is normal 348 $params['cs'] = 'cs'; //case-sensitive 349 $pattern = '/'.str2regex($pattern).'/'; //convert pattern to regex 350 break; 351 case ';': //this a normal not case-sensitive search 352 $params['type'] = 'normal'; //type is normal 353 $params['cs'] = 'not_cs'; //not case-sensitive 354 $pattern = '/'.str2regex($pattern).'/i'; //convert pattern to regex 355 break; 356 case '#': //this a regex search 357 $params['type'] = 'regex'; //type is regex 358 $params['cs'] = ''; //no case sensitivity for regex 359 if(!preg_match('/^\/.*\/[msixg]*$/', $pattern)){ //if regex is not correctly syntaxed 360 $params['reg_err'] = true; //store regex error 361 } 362 break; 363 default: //normally not reachable but we never know 364 $params['type'] = 'undef'; 365 } 366 $params['pattern'] = $pattern; //store the final pattern that we use for searching 367 return $params; //return with parameters 368 } 369 370 /** 371 * Create output render 372 */ 373 function render($format, &$renderer, $data) { 374 global $conf; 375 if($format == 'xhtml'){ 376 if(($data['type'] != 'undef') && ($data['ndq_err'] == false || $this->getConf('ndqerr') == 'warning' || $this->getConf('ndqerr') == 'nowarn') && ($data['type'] != 'regex' || $data['reg_err'] == false) && (!isset($data['bad_options']) || $this->getConf('badopt') == 'warning' || $this->getConf('badopt') == 'nowarn') && (!isset($data['ignored_options']) || $this->getConf('ignopt') == 'warning' || $this->getConf('ignopt') == 'nowarn')){ //if all conditions are ok 377 $data['cond_ok'] = true; 378 //definition : function search(&$data,$base,$func,$opts,$dir='',$lvl=1) 379 search($wikidata, $conf['datadir'], search_pattern, $data); //browse wiki pages with callback to search_pattern 380 } 381 else{ 382 $data['cond_ok'] = false; 383 } 384 $this->report($wikidata, $renderer, $data); 385 return true; //return 386 } 387 return false; //return 388 } 389 390 /** 391 * Create the report table 392 */ 393 function report($data, &$renderer, $params){ 394 if( $params['cond_ok'] ) { //if the search has been done 395 // if call_plugin_handler is active 396 $xPlugin = null; 397 if( !empty($params['call_plugin_handler']) ) { 398 // try to load the plugin and check if it is enabled for searchpattern callback handler 399 // therefor a method '_searchpatternHandler' in syntax plugin must exist 400 $badoptWarnErrorCssClass = ''; 401 if( $this->getConf('badopt') == 'error' ) { 402 $badoptWarnErrorCssClass = 'sp_error'; 403 } else 404 if( $this->getConf('badopt') == 'warning' ) { 405 $badoptWarnErrorCssClass = 'sp_warning'; 406 } 407 $callPluginHandlerError = false; 408 if( preg_match( '/[^a-zA-Z0-9]+/i', $params['call_plugin_handler'], $temp ) > 0 ) { 409 //$renderer->doc .= '<div class="sp_main">'; 410 if( !empty($badoptWarnErrorCssClass) ) { 411 $renderer->doc .= '<div class="'.$badoptWarnErrorCssClass.'"><span class="'.$badoptWarnErrorCssClass.'_pat">'.htmlspecialchars($params['call_plugin_handler']).'</span> :: '.$this->getLang('call_handler_invalid').'<br /></div>'; //write error message 412 } 413 $callPluginHandlerError = true; 414 //$renderer->doc .= '</div>'; //close error 415 } else { 416 if( plugin_isdisabled( $params['call_plugin_handler'] ) ) { 417 // @date 20130411 by Leo Eibler <dokuwiki@sprossenwanne.at> bugfix: fallback to lowercase 418 // plugin_isdisabled will return true if plugin not exists - so try with lowercase 419 $params['call_plugin_handler'] = strtolower($params['call_plugin_handler']); 420 } 421 if( !plugin_isdisabled( $params['call_plugin_handler'] ) ) { 422 // @date 20130411 by Leo Eibler <dokuwiki@sprossenwanne.at> bugfix: use the paramter call_plugin_handler 423 $xPlugin =& plugin_load('syntax', $params['call_plugin_handler'] ); 424 if( $xPlugin && method_exists( $xPlugin, '_searchpatternHandler' ) ) { 425 //$renderer->doc .= $xPlugin && $xPlugin->_searchpatternHandler(); 426 } else { 427 //$renderer->doc .= '<div class="sp_main">'; 428 if( !empty($badoptWarnErrorCssClass) ) { 429 $renderer->doc .= '<div class="'.$badoptWarnErrorCssClass.'"><span class="'.$badoptWarnErrorCssClass.'_pat">'.htmlspecialchars($params['call_plugin_handler']).'</span> :: '.$this->getLang('call_handler_notsupported').'<br /></div>'; //write error message 430 } 431 $callPluginHandlerError = true; 432 //$renderer->doc .= '</div>'; //close error 433 } 434 } else { 435 //$renderer->doc .= '<div class="sp_main">'; 436 if( !empty($badoptWarnErrorCssClass) ) { 437 $renderer->doc .= '<div class="'.$badoptWarnErrorCssClass.'"><span class="'.$badoptWarnErrorCssClass.'_pat">'.htmlspecialchars($params['call_plugin_handler']).'</span> :: '.$this->getLang('call_handler_disabled').'<br /></div>'; //write error message 438 } 439 $callPluginHandlerError = true; 440 //$renderer->doc .= '</div>'; //close error 441 } 442 } 443 } 444 if( $callPluginHandlerError && $this->getConf('badopt') == 'nocatch' ) { 445 // there was something wrong to call the plugin handler (maybe not implemented or plugin is disabled). 446 // but the user has configured to ignore this and output the original text 447 $renderer->doc .= htmlspecialchars($params['match']); //in all remaining cases, display the original text 448 return; 449 } 450 if( $callPluginHandlerError && $this->getConf('badopt') == 'error' ) { 451 return; 452 } 453 454 // now process the data 455 // after processing: 456 // data should hold array( $page => count of matches ); regex matches are stripped 457 // matches should hold array( $page => matches from regex ); 458 $matches = array(); 459 if( $data ) { //if there are search results 460 // @date 20130407 by Leo Eibler <dokuwiki@sprossenwanne.at> extend output of regex matches () with command $ 461 // @date 20130411 by Leo Eibler <dokuwiki@sprossenwanne.at> bugfix: default behavior (output only count) if no $ (dollar) is used 462 if( is_array($params['regex_output_matches']) && count($params['regex_output_matches']) == 0 ) { 463 $params['regex_output_matches'] = array(); // as default we will use all matches 464 } 465 foreach( $data as $page => $count ) { //for each result 466 // @date 20130407 by Leo Eibler <dokuwiki@sprossenwanne.at> extend output of regex matches () with command $ 467 if( substr( $page, 0, 1 ) == '{' && substr( $page, -1 ) == '}' ) { 468 // skip this one - it's for the regex matching result 469 } else { 470 if( isset( $data['{'.$page.'}'] ) ) { 471 $matches[$page] = $data['{'.$page.'}']; 472 unset($data['{'.$page.'}']); 473 } 474 } 475 } 476 } 477 // now check if the partner plugin handles all of the output itself 478 if( $xPlugin && $xPlugin->_searchpatternHandler( 'wholeoutput', $renderer, $data, $matches, $params ) ) { 479 return; 480 } 481 // 482 483 $renderer->doc .= '<div class="sp_main">'; 484 $renderer->doc .= '<table class="inline sp_main_table">'; //create table 485 if( $this->getConf('dispheadl') == 'nodisp' ) { 486 } else { 487 $renderer->doc .= '<tr class="sp_title"><th colspan="2" class="sp_title">'.$this->getLang('src_res').' : <span class="sp_src">'.htmlspecialchars($params['or_pattern']).'</span><br /><span class="sp_src_params">'.$this->getLang($params['cs'].$params['type']).'</span></th></tr>'; //write table title 488 } 489 if($this->getConf('option') == 'disp' && (isset($params['page_limit']) || isset($params['nmsp_limit']) || isset($params['page_exclude']) || isset($params['nmsp_exclude']))){ //if limiting/restricting options are used and we shall display them 490 $renderer->doc .= '<tr class="sp_options"><td colspan="2" class="sp_options">'; 491 if(isset($params['page_limit']) || isset($params['nmsp_limit'])){ //if limiting type 492 $renderer->doc .= $this->getLang('restriction').'<ul>'; 493 if(isset($params['page_limit'])) { 494 foreach($params['page_limit'] as $page){ 495 $renderer->doc .= '<li>'.$page.'</li>'; 496 } 497 } 498 if(isset($params['page_limit'])) { 499 foreach($params['nmsp_limit'] as $nmsp){ 500 $renderer->doc .= '<li>'.$nmsp.'</li>'; 501 } 502 } 503 $renderer->doc .= '</ul>'; 504 } 505 if(isset($params['page_exclude']) || $params['nmsp_exclude']){ //if excluding type 506 $renderer->doc .= $this->getLang('exclusion').'<ul>'; 507 if(isset($params['page_exclude'])) 508 { 509 foreach($params['page_exclude'] as $page){ 510 $renderer->doc .= '<li>'.$page.'</li>'; 511 } 512 } 513 if(isset($params['nmsp_exclude'])) 514 { 515 foreach($params['nmsp_exclude'] as $nmsp){ 516 $renderer->doc .= '<li>'.$nmsp.'</li>'; 517 } 518 } 519 $renderer->doc .= '</ul>'; 520 } 521 $renderer->doc .= '</td></tr>'; 522 } 523 if($params['ndq_err'] == true && $this->getConf('ndqerr') == 'warning'){ //if there is a ndq error and we shall warn 524 $renderer->doc .= '<tr class="sp_warning"><td colspan="2" class="sp_warning">'.$this->getLang('ndq_err_warn').'</td></tr>'; //so warn 525 } 526 if(isset($params['bad_options']) && $this->getConf('badopt') == 'warning'){ //if an unknown option is used and we shall warn 527 $renderer->doc .= '<tr class="sp_warning"><td colspan="2" class="sp_warning">'.$this->getLang('badopt_warn').'<br><ul>'; //warn and 528 foreach($params['bad_options'] as $badopt){ 529 $renderer->doc .= '<li>'.$badopt.'</li>'; //display the bad options list 530 } 531 $renderer->doc .= '</td></tr>'; 532 } 533 if(isset($params['ignored_options']) && $this->getConf('ignopt') == 'warning'){ //if an option has been ignored 534 $renderer->doc .= '<tr class="sp_warning"><td colspan="2" class="sp_warning">'.$this->getLang('ignopt_warn').'<br><ul>'; //warn and 535 foreach($params['ignored_options'] as $ignopt){ 536 $renderer->doc .= '<li>'.$ignopt.'</li>'; //display the ignored options list 537 } 538 $renderer->doc .= '</td></tr>'; 539 } 540 if( $data ) { //if there are search results 541 // @date 20130407 by Leo Eibler <dokuwiki@sprossenwanne.at> extend output of regex matches () with command $ 542 // @date 20130411 by Leo Eibler <dokuwiki@sprossenwanne.at> bugfix: default behavior (output only count) if no $ (dollar) is used 543 if( is_array($params['regex_output_matches']) && count($params['regex_output_matches']) == 0 ) { 544 $params['regex_output_matches'] = array(); // as default we will use all matches 545 } 546 $renderer->doc .= '<tr class="sp_col_head"><th class="sp_col_head">'.$this->getLang('page_name').'</th><th class="sp_col_head">'.$this->getLang('num_match').'</th></tr>'; //write column headers 547 foreach($data as $page => $count){ //for each result 548 $renderer->doc .= '<tr class="sp_result"><td class="sp_page"><a href="'.wl($page).'">'.$page.'</a></td><td class="sp_count">'; 549 550 if( $xPlugin && $xPlugin->_searchpatternHandler( 'intable:whole', $renderer, $data, $matches, $params, $page ) ) { 551 // the partner plugin handles all the inner table output itself - skip searchpattern output 552 } else { 553 if( $xPlugin ) { 554 $xPlugin->_searchpatternHandler( 'intable:prefix', $renderer, $data, $matches, $params, $page ); 555 } 556 // @date 20130411 by Leo Eibler <dokuwiki@sprossenwanne.at> bugfix: default behavior (output only count) if no $ (dollar) is used 557 // @date 20130412 by Leo Eibler <dokuwiki@sprossenwanne.at> bugfix: incorrect if statement 558 if( isset( $matches[$page] ) && is_array($params['regex_output_matches']) && count($params['regex_output_matches']) >= 0 ) { 559 $match = $matches[$page]; 560 if( count($match) > 1 ) { 561 $renderer->doc .= '<table>'; 562 $regex_output_matches = $params['regex_output_matches']; 563 if( count($regex_output_matches) == 0 ) { 564 // no match numbers given, use all existing, but strip 0 565 $regex_output_matches = array_keys( $match ); 566 array_shift($regex_output_matches); 567 } 568 for( $i=0; $i<count($match[0]); $i++ ) { 569 if( !isset( $match[0][$i] ) ) { 570 continue; 571 } 572 $renderer->doc .= '<tr class="sp_result">'; 573 foreach( $regex_output_matches as $j ) { 574 $renderer->doc .= '<td class="'.( isset($match[$j][$i] ) ? 'sp_count' : 'sp_nores' ).'">'; 575 // does the partner plugin handle the inner table output itself? 576 if( $xPlugin ) { 577 if( !$xPlugin->_searchpatternHandler( 'intable:match', $renderer, $data, $matches, $params, $page, $match[$j][$i] ) ) { 578 $renderer->doc .= ( isset($match[$j][$i] ) ? htmlspecialchars($match[$j][$i]) : '' ); 579 } 580 } else { 581 $renderer->doc .= ( isset($match[$j][$i] ) ? htmlspecialchars($match[$j][$i]) : '' ); 582 } 583 $renderer->doc .= '</td>'; 584 } 585 $renderer->doc .= '</tr>'; 586 } 587 $renderer->doc .= '</table>'; 588 } else { 589 if( $xPlugin ) { 590 if( !$xPlugin->_searchpatternHandler( 'intable:count', $renderer, $data, $matches, $params, $page, $count ) ) { 591 $renderer->doc .= $count; 592 } 593 } else { 594 $renderer->doc .= $count; 595 } 596 } 597 } else { 598 if( $xPlugin ) { 599 if( !$xPlugin->_searchpatternHandler( 'intable:count', $renderer, $data, $matches, $params, $page, $count ) ) { 600 $renderer->doc .= $count; 601 } 602 } else { 603 $renderer->doc .= $count; 604 } 605 } 606 if( $xPlugin ) { 607 $xPlugin->_searchpatternHandler( 'intable:suffix', $renderer, $data, $matches, $params, $page ); 608 } 609 } // END the partner plugin handles all the inner table output itself - skip searchpattern output 610 $renderer->doc .= '</td></tr>'; // display it 611 } 612 } else { 613 // no result 614 $renderer->doc .= '<tr class="sp_result"><td colspan="2" class="sp_nores">'; 615 if( $xPlugin ) { 616 $xPlugin->_searchpatternHandler( 'intable:prefix', $renderer, $data, $matches, $params, $page ); 617 } 618 if( $xPlugin && $xPlugin->_searchpatternHandler( 'intable:noresult', $renderer, $data, $matches, $params, $page ) ) { 619 $renderer->doc .= $this->getLang('no_res'); 620 } 621 if( $xPlugin ) { 622 $xPlugin->_searchpatternHandler( 'intable:suffix', $renderer, $data, $matches, $params, $page ); 623 } 624 $renderer->doc .= '</td></tr>'; // write there is not any result 625 } 626 $renderer->doc .= '</table></div>'; //close table 627 } else { 628 // error 629 if(($this->getConf('ndqerr') == 'error' && $params['ndq_err'] == true) || ($this->getConf('regerr') == 'error' && $params['reg_err'] == true) || ($this->getConf('badopt') == 'error' && isset($params['bad_options']))){ //if there is an error that shall be displayed 630 $renderer->doc .= '<div class="sp_error"><span class="sp_error_pat">'.htmlspecialchars($params['match']).'</span> :: '.$this->getLang('pat_err').' :<br /><ul class="sp_error">'; //write error message 631 $err_found = false; //true if the error is known 632 if($params['ndq_err'] == true && $this->getConf('ndqerr') == 'error'){ //if the error is ndq type 633 $renderer->doc .= '<li class="sp_error">'.$this->getLang('ndq_err').'</li>'; //display error 634 $err_found = true; //we know the error 635 } 636 if($params['type'] == 'regex' && $params['reg_err'] == true && $this->getConf('regerr') == 'error'){ //if the error is reg type 637 $renderer->doc .= '<li class="sp_error">'.$this->getLang('reg_err').'</li>'; //display error 638 $err_found = true; //we know the error 639 } 640 if(isset($params['bad_options']) && $this->getConf('badopt') == 'error'){ //if the error is a ba doption 641 $renderer->doc .= '<li class="sp_error">'.$this->getLang('badopt_err'); //display error 642 foreach($params['bad_options'] as $badopt){ 643 $renderer->doc .= ' '.$badopt; //display the bad option(s) 644 } 645 $renderer->doc .= '</li>'; 646 $err_found = true; //we know the error 647 } 648 if($err_found == false){ //if the error is unknown type 649 $renderer->doc .= '<li class="sp_error">'.$this->getLang('unkw_err').'</li>'; //display a message 650 } 651 $renderer->doc .= '</ul></div>'; //close error 652 } 653 else 654 { 655 $renderer->doc .= htmlspecialchars($params['match']); //in all remaining cases, display the original text 656 } 657 } 658 } 659 660 661} 662 663?>