| [ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Common DokuWiki functions 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9 if(!defined('DOKU_INC')) define('DOKU_INC',fullpath(dirname(__FILE__).'/../').'/'); 10 require_once(DOKU_CONF.'dokuwiki.php'); 11 require_once (DOKU_INC.'inc/io.php'); 12 require_once (DOKU_INC.'inc/changelog.php'); 13 require_once (DOKU_INC.'inc/utf8.php'); 14 require_once (DOKU_INC.'inc/mail.php'); 15 require_once (DOKU_INC.'inc/parserutils.php'); 16 require_once (DOKU_INC.'inc/infoutils.php'); 17 18 /** 19 * These constants are used with the recents function 20 */ 21 define('RECENTS_SKIP_DELETED',2); 22 define('RECENTS_SKIP_MINORS',4); 23 define('RECENTS_SKIP_SUBSPACES',8); 24 25 /** 26 * Wrapper around htmlspecialchars() 27 * 28 * @author Andreas Gohr <andi@splitbrain.org> 29 * @see htmlspecialchars() 30 */ 31 function hsc($string){ 32 return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); 33 } 34 35 /** 36 * print a newline terminated string 37 * 38 * You can give an indention as optional parameter 39 * 40 * @author Andreas Gohr <andi@splitbrain.org> 41 */ 42 function ptln($string,$indent=0){ 43 echo str_repeat(' ', $indent)."$string\n"; 44 } 45 46 /** 47 * strips control characters (<32) from the given string 48 * 49 * @author Andreas Gohr <andi@splitbrain.org> 50 */ 51 function stripctl($string){ 52 return preg_replace('/[\x00-\x1F]+/s','',$string); 53 } 54 55 /** 56 * Return a secret token to be used for CSRF attack prevention 57 * 58 * @author Andreas Gohr <andi@splitbrain.org> 59 * @link http://en.wikipedia.org/wiki/Cross-site_request_forgery 60 * @link http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html 61 * @return string 62 */ 63 function getSecurityToken(){ 64 return md5(auth_cookiesalt().session_id()); 65 } 66 67 /** 68 * Check the secret CSRF token 69 */ 70 function checkSecurityToken($token=null){ 71 if(is_null($token)) $token = $_REQUEST['sectok']; 72 if(getSecurityToken() != $token){ 73 msg('Security Token did not match. Possible CSRF attack.',-1); 74 return false; 75 } 76 return true; 77 } 78 79 /** 80 * Print a hidden form field with a secret CSRF token 81 * 82 * @author Andreas Gohr <andi@splitbrain.org> 83 */ 84 function formSecurityToken($print=true){ 85 $ret = '<div class="no"><input type="hidden" name="sectok" value="'.getSecurityToken().'" /></div>'."\n"; 86 if($print){ 87 echo $ret; 88 }else{ 89 return $ret; 90 } 91 } 92 93 /** 94 * Return info about the current document as associative 95 * array. 96 * 97 * @author Andreas Gohr <andi@splitbrain.org> 98 */ 99 function pageinfo(){ 100 global $ID; 101 global $REV; 102 global $RANGE; 103 global $USERINFO; 104 global $conf; 105 global $lang; 106 107 // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml 108 // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary 109 $info['id'] = $ID; 110 $info['rev'] = $REV; 111 112 if($_SERVER['REMOTE_USER']){ 113 $info['userinfo'] = $USERINFO; 114 $info['perm'] = auth_quickaclcheck($ID); 115 $info['subscribed'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],false); 116 $info['subscribedns'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],true); 117 $info['client'] = $_SERVER['REMOTE_USER']; 118 119 // set info about manager/admin status 120 $info['isadmin'] = false; 121 $info['ismanager'] = false; 122 if($info['perm'] == AUTH_ADMIN){ 123 $info['isadmin'] = true; 124 $info['ismanager'] = true; 125 }elseif(auth_ismanager()){ 126 $info['ismanager'] = true; 127 } 128 129 // if some outside auth were used only REMOTE_USER is set 130 if(!$info['userinfo']['name']){ 131 $info['userinfo']['name'] = $_SERVER['REMOTE_USER']; 132 } 133 134 }else{ 135 $info['perm'] = auth_aclcheck($ID,'',null); 136 $info['subscribed'] = false; 137 $info['client'] = clientIP(true); 138 } 139 140 $info['namespace'] = getNS($ID); 141 $info['locked'] = checklock($ID); 142 $info['filepath'] = fullpath(wikiFN($ID)); 143 $info['exists'] = @file_exists($info['filepath']); 144 if($REV){ 145 //check if current revision was meant 146 if($info['exists'] && (@filemtime($info['filepath'])==$REV)){ 147 $REV = ''; 148 }elseif($RANGE){ 149 //section editing does not work with old revisions! 150 $REV = ''; 151 $RANGE = ''; 152 msg($lang['nosecedit'],0); 153 }else{ 154 //really use old revision 155 $info['filepath'] = fullpath(wikiFN($ID,$REV)); 156 $info['exists'] = @file_exists($info['filepath']); 157 } 158 } 159 $info['rev'] = $REV; 160 if($info['exists']){ 161 $info['writable'] = (is_writable($info['filepath']) && 162 ($info['perm'] >= AUTH_EDIT)); 163 }else{ 164 $info['writable'] = ($info['perm'] >= AUTH_CREATE); 165 } 166 $info['editable'] = ($info['writable'] && empty($info['lock'])); 167 $info['lastmod'] = @filemtime($info['filepath']); 168 169 //load page meta data 170 $info['meta'] = p_get_metadata($ID); 171 172 //who's the editor 173 if($REV){ 174 $revinfo = getRevisionInfo($ID, $REV, 1024); 175 }else{ 176 if (is_array($info['meta']['last_change'])) { 177 $revinfo = $info['meta']['last_change']; 178 } else { 179 $revinfo = getRevisionInfo($ID, $info['lastmod'], 1024); 180 // cache most recent changelog line in metadata if missing and still valid 181 if ($revinfo!==false) { 182 $info['meta']['last_change'] = $revinfo; 183 p_set_metadata($ID, array('last_change' => $revinfo)); 184 } 185 } 186 } 187 //and check for an external edit 188 if($revinfo!==false && $revinfo['date']!=$info['lastmod']){ 189 // cached changelog line no longer valid 190 $revinfo = false; 191 $info['meta']['last_change'] = $revinfo; 192 p_set_metadata($ID, array('last_change' => $revinfo)); 193 } 194 195 $info['ip'] = $revinfo['ip']; 196 $info['user'] = $revinfo['user']; 197 $info['sum'] = $revinfo['sum']; 198 // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID. 199 // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor']. 200 201 if($revinfo['user']){ 202 $info['editor'] = $revinfo['user']; 203 }else{ 204 $info['editor'] = $revinfo['ip']; 205 } 206 207 // draft 208 $draft = getCacheName($info['client'].$ID,'.draft'); 209 if(@file_exists($draft)){ 210 if(@filemtime($draft) < @filemtime(wikiFN($ID))){ 211 // remove stale draft 212 @unlink($draft); 213 }else{ 214 $info['draft'] = $draft; 215 } 216 } 217 218 // mobile detection 219 $info['ismobile'] = clientismobile(); 220 221 return $info; 222 } 223 224 /** 225 * Build an string of URL parameters 226 * 227 * @author Andreas Gohr 228 */ 229 function buildURLparams($params, $sep='&'){ 230 $url = ''; 231 $amp = false; 232 foreach($params as $key => $val){ 233 if($amp) $url .= $sep; 234 235 $url .= $key.'='; 236 $url .= rawurlencode((string)$val); 237 $amp = true; 238 } 239 return $url; 240 } 241 242 /** 243 * Build an string of html tag attributes 244 * 245 * Skips keys starting with '_', values get HTML encoded 246 * 247 * @author Andreas Gohr 248 */ 249 function buildAttributes($params,$skipempty=false){ 250 $url = ''; 251 foreach($params as $key => $val){ 252 if($key{0} == '_') continue; 253 if($val === '' && $skipempty) continue; 254 255 $url .= $key.'="'; 256 $url .= htmlspecialchars ($val); 257 $url .= '" '; 258 } 259 return $url; 260 } 261 262 263 /** 264 * This builds the breadcrumb trail and returns it as array 265 * 266 * @author Andreas Gohr <andi@splitbrain.org> 267 */ 268 function breadcrumbs(){ 269 // we prepare the breadcrumbs early for quick session closing 270 static $crumbs = null; 271 if($crumbs != null) return $crumbs; 272 273 global $ID; 274 global $ACT; 275 global $conf; 276 $crumbs = $_SESSION[DOKU_COOKIE]['bc']; 277 278 //first visit? 279 if (!is_array($crumbs)){ 280 $crumbs = array(); 281 } 282 //we only save on show and existing wiki documents 283 $file = wikiFN($ID); 284 if($ACT != 'show' || !@file_exists($file)){ 285 $_SESSION[DOKU_COOKIE]['bc'] = $crumbs; 286 return $crumbs; 287 } 288 289 // page names 290 $name = noNSorNS($ID); 291 if ($conf['useheading']) { 292 // get page title 293 $title = p_get_first_heading($ID,true); 294 if ($title) { 295 $name = $title; 296 } 297 } 298 299 //remove ID from array 300 if (isset($crumbs[$ID])) { 301 unset($crumbs[$ID]); 302 } 303 304 //add to array 305 $crumbs[$ID] = $name; 306 //reduce size 307 while(count($crumbs) > $conf['breadcrumbs']){ 308 array_shift($crumbs); 309 } 310 //save to session 311 $_SESSION[DOKU_COOKIE]['bc'] = $crumbs; 312 return $crumbs; 313 } 314 315 /** 316 * Filter for page IDs 317 * 318 * This is run on a ID before it is outputted somewhere 319 * currently used to replace the colon with something else 320 * on Windows systems and to have proper URL encoding 321 * 322 * Urlencoding is ommitted when the second parameter is false 323 * 324 * @author Andreas Gohr <andi@splitbrain.org> 325 */ 326 function idfilter($id,$ue=true){ 327 global $conf; 328 if ($conf['useslash'] && $conf['userewrite']){ 329 $id = strtr($id,':','/'); 330 }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && 331 $conf['userewrite']) { 332 $id = strtr($id,':',';'); 333 } 334 if($ue){ 335 $id = rawurlencode($id); 336 $id = str_replace('%3A',':',$id); //keep as colon 337 $id = str_replace('%2F','/',$id); //keep as slash 338 } 339 return $id; 340 } 341 342 /** 343 * This builds a link to a wikipage 344 * 345 * It handles URL rewriting and adds additional parameter if 346 * given in $more 347 * 348 * @author Andreas Gohr <andi@splitbrain.org> 349 */ 350 function wl($id='',$more='',$abs=false,$sep='&'){ 351 global $conf; 352 if(is_array($more)){ 353 $more = buildURLparams($more,$sep); 354 }else{ 355 $more = str_replace(',',$sep,$more); 356 } 357 358 $id = idfilter($id); 359 if($abs){ 360 $xlink = DOKU_URL; 361 }else{ 362 $xlink = DOKU_BASE; 363 } 364 365 if($conf['userewrite'] == 2){ 366 $xlink .= DOKU_SCRIPT.'/'.$id; 367 if($more) $xlink .= '?'.$more; 368 }elseif($conf['userewrite']){ 369 $xlink .= $id; 370 if($more) $xlink .= '?'.$more; 371 }elseif($id){ 372 $xlink .= DOKU_SCRIPT.'?id='.$id; 373 if($more) $xlink .= $sep.$more; 374 }else{ 375 $xlink .= DOKU_SCRIPT; 376 if($more) $xlink .= '?'.$more; 377 } 378 379 return $xlink; 380 } 381 382 /** 383 * This builds a link to an alternate page format 384 * 385 * Handles URL rewriting if enabled. Follows the style of wl(). 386 * 387 * @author Ben Coburn <btcoburn@silicodon.net> 388 */ 389 function exportlink($id='',$format='raw',$more='',$abs=false,$sep='&'){ 390 global $conf; 391 if(is_array($more)){ 392 $more = buildURLparams($more,$sep); 393 }else{ 394 $more = str_replace(',',$sep,$more); 395 } 396 397 $format = rawurlencode($format); 398 $id = idfilter($id); 399 if($abs){ 400 $xlink = DOKU_URL; 401 }else{ 402 $xlink = DOKU_BASE; 403 } 404 405 if($conf['userewrite'] == 2){ 406 $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format; 407 if($more) $xlink .= $sep.$more; 408 }elseif($conf['userewrite'] == 1){ 409 $xlink .= '_export/'.$format.'/'.$id; 410 if($more) $xlink .= '?'.$more; 411 }else{ 412 $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id; 413 if($more) $xlink .= $sep.$more; 414 } 415 416 return $xlink; 417 } 418 419 /** 420 * Build a link to a media file 421 * 422 * Will return a link to the detail page if $direct is false 423 * 424 * The $more parameter should always be given as array, the function then 425 * will strip default parameters to produce even cleaner URLs 426 * 427 * @param string $id - the media file id or URL 428 * @param mixed $more - string or array with additional parameters 429 * @param boolean $direct - link to detail page if false 430 * @param string $sep - URL parameter separator 431 * @param boolean $abs - Create an absolute URL 432 */ 433 function ml($id='',$more='',$direct=true,$sep='&',$abs=false){ 434 global $conf; 435 if(is_array($more)){ 436 // strip defaults for shorter URLs 437 if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']); 438 if(!$more['w']) unset($more['w']); 439 if(!$more['h']) unset($more['h']); 440 if(isset($more['id']) && $direct) unset($more['id']); 441 $more = buildURLparams($more,$sep); 442 }else{ 443 $more = str_replace('cache=cache','',$more); //skip default 444 $more = str_replace(',,',',',$more); 445 $more = str_replace(',',$sep,$more); 446 } 447 448 if($abs){ 449 $xlink = DOKU_URL; 450 }else{ 451 $xlink = DOKU_BASE; 452 } 453 454 // external URLs are always direct without rewriting 455 if(preg_match('#^(https?|ftp)://#i',$id)){ 456 $xlink .= 'lib/exe/fetch.php'; 457 if($more){ 458 $xlink .= '?'.$more; 459 $xlink .= $sep.'media='.rawurlencode($id); 460 }else{ 461 $xlink .= '?media='.rawurlencode($id); 462 } 463 return $xlink; 464 } 465 466 $id = idfilter($id); 467 468 // decide on scriptname 469 if($direct){ 470 if($conf['userewrite'] == 1){ 471 $script = '_media'; 472 }else{ 473 $script = 'lib/exe/fetch.php'; 474 } 475 }else{ 476 if($conf['userewrite'] == 1){ 477 $script = '_detail'; 478 }else{ 479 $script = 'lib/exe/detail.php'; 480 } 481 } 482 483 // build URL based on rewrite mode 484 if($conf['userewrite']){ 485 $xlink .= $script.'/'.$id; 486 if($more) $xlink .= '?'.$more; 487 }else{ 488 if($more){ 489 $xlink .= $script.'?'.$more; 490 $xlink .= $sep.'media='.$id; 491 }else{ 492 $xlink .= $script.'?media='.$id; 493 } 494 } 495 496 return $xlink; 497 } 498 499 500 501 /** 502 * Just builds a link to a script 503 * 504 * @todo maybe obsolete 505 * @author Andreas Gohr <andi@splitbrain.org> 506 */ 507 function script($script='doku.php'){ 508 # $link = getBaseURL(); 509 # $link .= $script; 510 # return $link; 511 return DOKU_BASE.DOKU_SCRIPT; 512 } 513 514 /** 515 * Spamcheck against wordlist 516 * 517 * Checks the wikitext against a list of blocked expressions 518 * returns true if the text contains any bad words 519 * 520 * Triggers COMMON_WORDBLOCK_BLOCKED 521 * 522 * Action Plugins can use this event to inspect the blocked data 523 * and gain information about the user who was blocked. 524 * 525 * Event data: 526 * data['matches'] - array of matches 527 * data['userinfo'] - information about the blocked user 528 * [ip] - ip address 529 * [user] - username (if logged in) 530 * [mail] - mail address (if logged in) 531 * [name] - real name (if logged in) 532 * 533 * @author Andreas Gohr <andi@splitbrain.org> 534 * Michael Klier <chi@chimeric.de> 535 */ 536 function checkwordblock(){ 537 global $TEXT; 538 global $conf; 539 global $INFO; 540 541 if(!$conf['usewordblock']) return false; 542 543 // we prepare the text a tiny bit to prevent spammers circumventing URL checks 544 $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$TEXT); 545 546 $wordblocks = getWordblocks(); 547 //how many lines to read at once (to work around some PCRE limits) 548 if(version_compare(phpversion(),'4.3.0','<')){ 549 //old versions of PCRE define a maximum of parenthesises even if no 550 //backreferences are used - the maximum is 99 551 //this is very bad performancewise and may even be too high still 552 $chunksize = 40; 553 }else{ 554 //read file in chunks of 200 - this should work around the 555 //MAX_PATTERN_SIZE in modern PCRE 556 $chunksize = 200; 557 } 558 while($blocks = array_splice($wordblocks,0,$chunksize)){ 559 $re = array(); 560 #build regexp from blocks 561 foreach($blocks as $block){ 562 $block = preg_replace('/#.*$/','',$block); 563 $block = trim($block); 564 if(empty($block)) continue; 565 $re[] = $block; 566 } 567 if(count($re) && preg_match('#('.join('|',$re).')#si',$text,$matches)) { 568 //prepare event data 569 $data['matches'] = $matches; 570 $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR']; 571 if($_SERVER['REMOTE_USER']) { 572 $data<