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 9if(!defined('DOKU_INC')) die('meh.'); 10 11/** 12 * These constants are used with the recents function 13 */ 14define('RECENTS_SKIP_DELETED',2); 15define('RECENTS_SKIP_MINORS',4); 16define('RECENTS_SKIP_SUBSPACES',8); 17define('RECENTS_MEDIA_CHANGES',16); 18 19/** 20 * Wrapper around htmlspecialchars() 21 * 22 * @author Andreas Gohr <andi@splitbrain.org> 23 * @see htmlspecialchars() 24 */ 25function hsc($string){ 26 return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); 27} 28 29/** 30 * print a newline terminated string 31 * 32 * You can give an indention as optional parameter 33 * 34 * @author Andreas Gohr <andi@splitbrain.org> 35 */ 36function ptln($string,$indent=0){ 37 echo str_repeat(' ', $indent)."$string\n"; 38} 39 40/** 41 * strips control characters (<32) from the given string 42 * 43 * @author Andreas Gohr <andi@splitbrain.org> 44 */ 45function stripctl($string){ 46 return preg_replace('/[\x00-\x1F]+/s','',$string); 47} 48 49/** 50 * Return a secret token to be used for CSRF attack prevention 51 * 52 * @author Andreas Gohr <andi@splitbrain.org> 53 * @link http://en.wikipedia.org/wiki/Cross-site_request_forgery 54 * @link http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html 55 * @return string 56 */ 57function getSecurityToken(){ 58 return md5(auth_cookiesalt().session_id()); 59} 60 61/** 62 * Check the secret CSRF token 63 */ 64function checkSecurityToken($token=null){ 65 if(!$_SERVER['REMOTE_USER']) return true; // no logged in user, no need for a check 66 67 if(is_null($token)) $token = $_REQUEST['sectok']; 68 if(getSecurityToken() != $token){ 69 msg('Security Token did not match. Possible CSRF attack.',-1); 70 return false; 71 } 72 return true; 73} 74 75/** 76 * Print a hidden form field with a secret CSRF token 77 * 78 * @author Andreas Gohr <andi@splitbrain.org> 79 */ 80function formSecurityToken($print=true){ 81 $ret = '<div class="no"><input type="hidden" name="sectok" value="'.getSecurityToken().'" /></div>'."\n"; 82 if($print){ 83 echo $ret; 84 }else{ 85 return $ret; 86 } 87} 88 89/** 90 * Return info about the current document as associative 91 * array. 92 * 93 * @author Andreas Gohr <andi@splitbrain.org> 94 */ 95function pageinfo(){ 96 global $ID; 97 global $REV; 98 global $RANGE; 99 global $USERINFO; 100 global $lang; 101 102 // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml 103 // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary 104 $info['id'] = $ID; 105 $info['rev'] = $REV; 106 107 // set info about manager/admin status. 108 $info['isadmin'] = false; 109 $info['ismanager'] = false; 110 if(isset($_SERVER['REMOTE_USER'])){ 111 $info['userinfo'] = $USERINFO; 112 $info['perm'] = auth_quickaclcheck($ID); 113 $info['subscribed'] = get_info_subscribed(); 114 $info['client'] = $_SERVER['REMOTE_USER']; 115 116 if($info['perm'] == AUTH_ADMIN){ 117 $info['isadmin'] = true; 118 $info['ismanager'] = true; 119 }elseif(auth_ismanager()){ 120 $info['ismanager'] = true; 121 } 122 123 // if some outside auth were used only REMOTE_USER is set 124 if(!$info['userinfo']['name']){ 125 $info['userinfo']['name'] = $_SERVER['REMOTE_USER']; 126 } 127 128 }else{ 129 $info['perm'] = auth_aclcheck($ID,'',null); 130 $info['subscribed'] = false; 131 $info['client'] = clientIP(true); 132 } 133 134 $info['namespace'] = getNS($ID); 135 $info['locked'] = checklock($ID); 136 $info['filepath'] = fullpath(wikiFN($ID)); 137 $info['exists'] = @file_exists($info['filepath']); 138 if($REV){ 139 //check if current revision was meant 140 if($info['exists'] && (@filemtime($info['filepath'])==$REV)){ 141 $REV = ''; 142 }elseif($RANGE){ 143 //section editing does not work with old revisions! 144 $REV = ''; 145 $RANGE = ''; 146 msg($lang['nosecedit'],0); 147 }else{ 148 //really use old revision 149 $info['filepath'] = fullpath(wikiFN($ID,$REV)); 150 $info['exists'] = @file_exists($info['filepath']); 151 } 152 } 153 $info['rev'] = $REV; 154 if($info['exists']){ 155 $info['writable'] = (is_writable($info['filepath']) && 156 ($info['perm'] >= AUTH_EDIT)); 157 }else{ 158 $info['writable'] = ($info['perm'] >= AUTH_CREATE); 159 } 160 $info['editable'] = ($info['writable'] && empty($info['locked'])); 161 $info['lastmod'] = @filemtime($info['filepath']); 162 163 //load page meta data 164 $info['meta'] = p_get_metadata($ID); 165 166 //who's the editor 167 if($REV){ 168 $revinfo = getRevisionInfo($ID, $REV, 1024); 169 }else{ 170 if (is_array($info['meta']['last_change'])) { 171 $revinfo = $info['meta']['last_change']; 172 } else { 173 $revinfo = getRevisionInfo($ID, $info['lastmod'], 1024); 174 // cache most recent changelog line in metadata if missing and still valid 175 if ($revinfo!==false) { 176 $info['meta']['last_change'] = $revinfo; 177 p_set_metadata($ID, array('last_change' => $revinfo)); 178 } 179 } 180 } 181 //and check for an external edit 182 if($revinfo!==false && $revinfo['date']!=$info['lastmod']){ 183 // cached changelog line no longer valid 184 $revinfo = false; 185 $info['meta']['last_change'] = $revinfo; 186 p_set_metadata($ID, array('last_change' => $revinfo)); 187 } 188 189 $info['ip'] = $revinfo['ip']; 190 $info['user'] = $revinfo['user']; 191 $info['sum'] = $revinfo['sum']; 192 // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID. 193 // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor']. 194 195 if($revinfo['user']){ 196 $info['editor'] = $revinfo['user']; 197 }else{ 198 $info['editor'] = $revinfo['ip']; 199 } 200 201 // draft 202 $draft = getCacheName($info['client'].$ID,'.draft'); 203 if(@file_exists($draft)){ 204 if(@filemtime($draft) < @filemtime(wikiFN($ID))){ 205 // remove stale draft 206 @unlink($draft); 207 }else{ 208 $info['draft'] = $draft; 209 } 210 } 211 212 // mobile detection 213 $info['ismobile'] = clientismobile(); 214 215 return $info; 216} 217 218/** 219 * Build an string of URL parameters 220 * 221 * @author Andreas Gohr 222 */ 223function buildURLparams($params, $sep='&'){ 224 $url = ''; 225 $amp = false; 226 foreach($params as $key => $val){ 227 if($amp) $url .= $sep; 228 229 $url .= rawurlencode($key).'='; 230 $url .= rawurlencode((string)$val); 231 $amp = true; 232 } 233 return $url; 234} 235 236/** 237 * Build an string of html tag attributes 238 * 239 * Skips keys starting with '_', values get HTML encoded 240 * 241 * @author Andreas Gohr 242 */ 243function buildAttributes($params,$skipempty=false){ 244 $url = ''; 245 $white = false; 246 foreach($params as $key => $val){ 247 if($key{0} == '_') continue; 248 if($val === '' && $skipempty) continue; 249 if($white) $url .= ' '; 250 251 $url .= $key.'="'; 252 $url .= htmlspecialchars ($val); 253 $url .= '"'; 254 $white = true; 255 } 256 return $url; 257} 258 259 260/** 261 * This builds the breadcrumb trail and returns it as array 262 * 263 * @author Andreas Gohr <andi@splitbrain.org> 264 */ 265function breadcrumbs(){ 266 // we prepare the breadcrumbs early for quick session closing 267 static $crumbs = null; 268 if($crumbs != null) return $crumbs; 269 270 global $ID; 271 global $ACT; 272 global $conf; 273 274 //first visit? 275 $crumbs = isset($_SESSION[DOKU_COOKIE]['bc']) ? $_SESSION[DOKU_COOKIE]['bc'] : array(); 276 //we only save on show and existing wiki documents 277 $file = wikiFN($ID); 278 if($ACT != 'show' || !@file_exists($file)){ 279 $_SESSION[DOKU_COOKIE]['bc'] = $crumbs; 280 return $crumbs; 281 } 282 283 // page names 284 $name = noNSorNS($ID); 285 if (useHeading('navigation')) { 286 // get page title 287 $title = p_get_first_heading($ID,true); 288 if ($title) { 289 $name = $title; 290 } 291 } 292 293 //remove ID from array 294 if (isset($crumbs[$ID])) { 295 unset($crumbs[$ID]); 296 } 297 298 //add to array 299 $crumbs[$ID] = $name; 300 //reduce size 301 while(count($crumbs) > $conf['breadcrumbs']){ 302 array_shift($crumbs); 303 } 304 //save to session 305 $_SESSION[DOKU_COOKIE]['bc'] = $crumbs; 306 return $crumbs; 307} 308 309/** 310 * Filter for page IDs 311 * 312 * This is run on a ID before it is outputted somewhere 313 * currently used to replace the colon with something else 314 * on Windows systems and to have proper URL encoding 315 * 316 * Urlencoding is ommitted when the second parameter is false 317 * 318 * @author Andreas Gohr <andi@splitbrain.org> 319 */ 320function idfilter($id,$ue=true){ 321 global $conf; 322 if ($conf['useslash'] && $conf['userewrite']){ 323 $id = strtr($id,':','/'); 324 }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && 325 $conf['userewrite']) { 326 $id = strtr($id,':',';'); 327 } 328 if($ue){ 329 $id = rawurlencode($id); 330 $id = str_replace('%3A',':',$id); //keep as colon 331 $id = str_replace('%2F','/',$id); //keep as slash 332 } 333 return $id; 334} 335 336/** 337 * This builds a link to a wikipage 338 * 339 * It handles URL rewriting and adds additional parameter if 340 * given in $more 341 * 342 * @author Andreas Gohr <andi@splitbrain.org> 343 */ 344function wl($id='',$more='',$abs=false,$sep='&'){ 345 global $conf; 346 if(is_array($more)){ 347 $more = buildURLparams($more,$sep); 348 }else{ 349 $more = str_replace(',',$sep,$more); 350 } 351 352 $id = idfilter($id); 353 if($abs){ 354 $xlink = DOKU_URL; 355 }else{ 356 $xlink = DOKU_BASE; 357 } 358 359 if($conf['userewrite'] == 2){ 360 $xlink .= DOKU_SCRIPT.'/'.$id; 361 if($more) $xlink .= '?'.$more; 362 }elseif($conf['userewrite']){ 363 $xlink .= $id; 364 if($more) $xlink .= '?'.$more; 365 }elseif($id){ 366 $xlink .= DOKU_SCRIPT.'?id='.$id; 367 if($more) $xlink .= $sep.$more; 368 }else{ 369 $xlink .= DOKU_SCRIPT; 370 if($more) $xlink .= '?'.$more; 371 } 372 373 return $xlink; 374} 375 376/** 377 * This builds a link to an alternate page format 378 * 379 * Handles URL rewriting if enabled. Follows the style of wl(). 380 * 381 * @author Ben Coburn <btcoburn@silicodon.net> 382 */ 383function exportlink($id='',$format='raw',$more='',$abs=false,$sep='&'){ 384 global $conf; 385 if(is_array($more)){ 386 $more = buildURLparams($more,$sep); 387 }else{ 388 $more = str_replace(',',$sep,$more); 389 } 390 391 $format = rawurlencode($format); 392 $id = idfilter($id); 393 if($abs){ 394 $xlink = DOKU_URL; 395 }else{ 396 $xlink = DOKU_BASE; 397 } 398 399 if($conf['userewrite'] == 2){ 400 $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format; 401 if($more) $xlink .= $sep.$more; 402 }elseif($conf['userewrite'] == 1){ 403 $xlink .= '_export/'.$format.'/'.$id; 404 if($more) $xlink .= '?'.$more; 405 }else{ 406 $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id; 407 if($more) $xlink .= $sep.$more; 408 } 409 410 return $xlink; 411} 412 413/** 414 * Build a link to a media file 415 * 416 * Will return a link to the detail page if $direct is false 417 * 418 * The $more parameter should always be given as array, the function then 419 * will strip default parameters to produce even cleaner URLs 420 * 421 * @param string $id - the media file id or URL 422 * @param mixed $more - string or array with additional parameters 423 * @param boolean $direct - link to detail page if false 424 * @param string $sep - URL parameter separator 425 * @param boolean $abs - Create an absolute URL 426 */ 427function ml($id='',$more='',$direct=true,$sep='&',$abs=false){ 428 global $conf; 429 if(is_array($more)){ 430 // strip defaults for shorter URLs 431 if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']); 432 if(!$more['w']) unset($more['w']); 433 if(!$more['h']) unset($more['h']); 434 if(isset($more['id']) && $direct) unset($more['id']); 435 $more = buildURLparams($more,$sep); 436 }else{ 437 $more = str_replace('cache=cache','',$more); //skip default 438 $more = str_replace(',,',',',$more); 439 $more = str_replace(',',$sep,$more); 440 } 441 442 if($abs){ 443 $xlink = DOKU_URL; 444 }else{ 445 $xlink = DOKU_BASE; 446 } 447 448 // external URLs are always direct without rewriting 449 if(preg_match('#^(https?|ftp)://#i',$id)){ 450 $xlink .= 'lib/exe/fetch.php'; 451 // add hash: 452 $xlink .= '?hash='.substr(md5(auth_cookiesalt().$id),0,6); 453 if($more){ 454 $xlink .= $sep.$more; 455 $xlink .= $sep.'media='.rawurlencode($id); 456 }else{ 457 $xlink .= $sep.'media='.rawurlencode($id); 458 } 459 return $xlink; 460 } 461 462 $id = idfilter($id); 463 464 // decide on scriptname 465 if($direct){ 466 if($conf['userewrite'] == 1){ 467 $script = '_media'; 468 }else{ 469 $script = 'lib/exe/fetch.php'; 470 } 471 }else{ 472 if($conf['userewrite'] == 1){ 473 $script = '_detail'; 474 }else{ 475 $script = 'lib/exe/detail.php'; 476 } 477 } 478 479 // build URL based on rewrite mode 480 if($conf['userewrite']){ 481 $xlink .= $script.'/'.$id; 482 if($more) $xlink .= '?'.$more; 483 }else{ 484 if($more){ 485 $xlink .= $script.'?'.$more; 486 $xlink .= $sep.'media='.$id; 487 }else{ 488 $xlink .= $script.'?media='.$id; 489 } 490 } 491 492 return $xlink; 493} 494 495 496 497/** 498 * Just builds a link to a script 499 * 500 * @todo maybe obsolete 501 * @author Andreas Gohr <andi@splitbrain.org> 502 */ 503function script($script='doku.php'){ 504 return DOKU_BASE.DOKU_SCRIPT; 505} 506 507/** 508 * Spamcheck against wordlist 509 * 510 * Checks the wikitext against a list of blocked expressions 511 * returns true if the text contains any bad words 512 * 513 * Triggers COMMON_WORDBLOCK_BLOCKED 514 * 515 * Action Plugins can use this event to inspect the blocked data 516 * and gain information about the user who was blocked. 517 * 518 * Event data: 519 * data['matches'] - array of matches 520 * data['userinfo'] - information about the blocked user 521 * [ip] - ip address 522 * [user] - username (if logged in) 523 * [mail] - mail address (if logged in) 524 * [name] - real name (if logged in) 525 * 526 * @author Andreas Gohr <andi@splitbrain.org> 527 * @author Michael Klier <chi@chimeric.de> 528 * @param string $text - optional text to check, if not given the globals are used 529 * @return bool - true if a spam word was found 530 */ 531function checkwordblock($text=''){ 532 global $TEXT; 533 global $PRE; 534 global $SUF; 535 global $conf; 536 global $INFO; 537 538 if(!$conf['usewordblock']) return false; 539 540 if(!$text) $text = "$PRE $TEXT $SUF"; 541 542 // we prepare the text a tiny bit to prevent spammers circumventing URL checks 543 $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$text); 544 545 $wordblocks = getWordblocks(); 546 // how many lines to read at once (to work around some PCRE limits) 547 if(version_compare(phpversion(),'4.3.0','<')){ 548 // old versions of PCRE define a maximum of parenthesises even if no 549 // backreferences are used - the maximum is 99 550 // this is very bad performancewise and may even be too high still 551 $chunksize = 40; 552 }else{ 553 // read file in chunks of 200 - this should work around the 554 // MAX_PATTERN_SIZE in modern PCRE 555 $chunksize = 200; 556 } 557 while($blocks = array_splice($wordblocks,0,$chunksize)){ 558 $re = array(); 559 // build regexp from blocks 560 foreach($blocks as $block){ 561 $block = preg_replace('/#.*$/','',$block); 562 $block = trim($block); 563 if(empty($block)) continue; 564 $re[] = $block; 565 } 566 if(count($re) && preg_match('#('.join('|',$re).')#si',$text,$matches)) { 567 // prepare event data 568 $data['matches'] = $matches; 569 $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR']; 570 if($_SERVER['REMOTE_USER']) { 571 $data['userinfo']['user'] = $_SERVER['REMOTE_USER']; 572 $data['userinfo']['name'] = $INFO['userinfo']['name']; 573 $data['userinfo']['mail'] = $INFO['userinfo']['mail']; 574 } 575 $callback = create_function('', 'return true;'); 576 return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true); 577 } 578 } 579 return false; 580} 581 582/** 583 * Return the IP of the client 584 * 585 * Honours X-Forwarded-For and X-Real-IP Proxy Headers 586 * 587 * It returns a comma separated list of IPs if the above mentioned 588 * headers are set. If the single parameter is set, it tries to return 589 * a routable public address, prefering the ones suplied in the X 590 * headers 591 * 592 * @param boolean $single If set only a single IP is returned 593 * @author Andreas Gohr <andi@splitbrain.org> 594 */ 595function clientIP($single=false){ 596 $ip = array(); 597 $ip[] = $_SERVER['REMOTE_ADDR']; 598 if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) 599 $ip = array_merge($ip,explode(',',str_replace(' ','',$_SERVER['HTTP_X_FORWARDED_FOR']))); 600 if(!empty($_SERVER['HTTP_X_REAL_IP'])) 601 $ip = array_merge($ip,explode(',',str_replace(' ','',$_SERVER['HTTP_X_REAL_IP']))); 602 603 // some IPv4/v6 regexps borrowed from Feyd 604 // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479 605 $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])'; 606 $hex_digit = '[A-Fa-f0-9]'; 607 $h16 = "{$hex_digit}{1,4}"; 608 $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet"; 609 $ls32 = "(?:$h16:$h16|$IPv4Address)"; 610 $IPv6Address = 611 "(?:(?:{$IPv4Address})|(?:". 612 "(?:$h16:){6}$ls32" . 613 "|::(?:$h16:){5}$ls32" . 614 "|(?:$h16)?::(?:$h16:){4}$ls32" . 615 "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32" . 616 "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32" . 617 "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32" . 618 "|(?:(?:$h16:){0,4}$h16)?::$ls32" . 619 "|(?:(?:$h16:){0,5}$h16)?::$h16" . 620 "|(?:(?:$h16:){0,6}$h16)?::" . 621 ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)"; 622 623 // remove any non-IP stuff 624 $cnt = count($ip); 625 $match = array(); 626 for($i=0; $i<$cnt; $i++){ 627 if(preg_match("/^$IPv4Address$/",$ip[$i],$match) || preg_match("/^$IPv6Address$/",$ip[$i],$match)) { 628 $ip[$i] = $match[0]; 629 } else { 630 $ip[$i] = ''; 631 } 632 if(empty($ip[$i])) unset($ip[$i]); 633 } 634 $ip = array_values(array_unique($ip)); 635 if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP 636 637 if(!$single) return join(',',$ip); 638 639 // decide which IP to use, trying to avoid local addresses 640 $ip = array_reverse($ip); 641 foreach($ip as $i){ 642 if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){ 643 continue; 644 }else{ 645 return $i; 646 } 647 } 648 // still here? just use the first (last) address 649 return $ip[0]; 650} 651 652/** 653 * Check if the browser is on a mobile device 654 * 655 * Adapted from the example code at url below 656 * 657 * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code 658 */ 659function clientismobile(){ 660 661 if(isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true; 662 663 if(preg_match('/wap\.|\.wap/i',$_SERVER['HTTP_ACCEPT'])) return true; 664 665 if(!isset($_SERVER['HTTP_USER_AGENT'])) return false; 666 667 $uamatches = 'midp|j2me|avantg|docomo|novarra|palmos|palmsource|240x320|opwv|chtml|pda|windows ce|mmp\/|blackberry|mib\/|symbian|wireless|nokia|hand|mobi|phone|cdm|up\.b|audio|SIE\-|SEC\-|samsung|HTC|mot\-|mitsu|sagem|sony|alcatel|lg|erics|vx|NEC|philips|mmm|xx|panasonic|sharp|wap|sch|rover|pocket|benq|java|pt|pg|vox|amoi|bird|compal|kg|voda|sany|kdd|dbt|sendo|sgh|gradi|jb|\d\d\di|moto'; 668 669 if(preg_match("/$uamatches/i",$_SERVER['HTTP_USER_AGENT'])) return true; 670 671 return false; 672} 673 674 675/** 676 * Convert one or more comma separated IPs to hostnames 677 * 678 * @author Glen Harris <astfgl@iamnota.org> 679 * @returns a comma separated list of hostnames 680 */ 681function gethostsbyaddrs($ips){ 682 $hosts = array(); 683 $ips = explode(',',$ips); 684 685 if(is_array($ips)) { 686 foreach($ips as $ip){ 687 $hosts[] = gethostbyaddr(trim($ip)); 688 } 689 return join(',',$hosts); 690 } else { 691 return gethostbyaddr(trim($ips)); 692 } 693} 694 695/** 696 * Checks if a given page is currently locked. 697 * 698 * removes stale lockfiles 699 * 700 * @author Andreas Gohr <andi@splitbrain.org> 701 */ 702function checklock($id){ 703 global $conf; 704 $lock = wikiLockFN($id); 705 706 //no lockfile 707 if(!@file_exists($lock)) return false; 708 709 //lockfile expired 710 if((time() - filemtime($lock)) > $conf['locktime']){ 711 @unlink($lock); 712 return false; 713 } 714 715 //my own lock 716 $ip = io_readFile($lock); 717 if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){ 718 return false; 719 } 720 721 return $ip; 722} 723 724/** 725 * Lock a page for editing 726 * 727 * @author Andreas Gohr <andi@splitbrain.org> 728 */ 729function lock($id){ 730 global $conf; 731 732 if($conf['locktime'] == 0){ 733 return; 734 } 735 736 $lock = wikiLockFN($id); 737 if($_SERVER['REMOTE_USER']){ 738 io_saveFile($lock,$_SERVER['REMOTE_USER']); 739 }else{ 740 io_saveFile($lock,clientIP()); 741 } 742} 743 744/** 745 * Unlock a page if it was locked by the user 746 * 747 * @author Andreas Gohr <andi@splitbrain.org> 748 * @return bool true if a lock was removed 749 */ 750function unlock($id){ 751 $lock = wikiLockFN($id); 752 if(@file_exists($lock)){ 753 $ip = io_readFile($lock); 754 if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){ 755 @unlink($lock); 756 return true; 757 } 758 } 759 return false; 760} 761 762/** 763 * convert line ending to unix format 764 * 765 * @see formText() for 2crlf conversion 766 * @author Andreas Gohr <andi@splitbrain.org> 767 */ 768function cleanText($text){ 769 $text = preg_replace("/(\015\012)|(\015)/","\012",$text); 770 return $text; 771} 772 773/** 774 * Prepares text for print in Webforms by encoding special chars. 775 * It also converts line endings to Windows format which is 776 * pseudo standard for webforms. 777 * 778 * @see cleanText() for 2unix conversion 779 * @author Andreas Gohr <andi@splitbrain.org> 780 */ 781function formText($text){ 782 $text = str_replace("\012","\015\012",$text); 783 return htmlspecialchars($text); 784} 785 786/** 787 * Returns the specified local text in raw format 788 * 789 * @author Andreas Gohr <andi@splitbrain.org> 790 */ 791function rawLocale($id){ 792 return io_readFile(localeFN($id)); 793} 794 795/** 796 * Returns the raw WikiText 797 * 798 * @author Andreas Gohr <andi@splitbrain.org> 799 */ 800function rawWiki($id,$rev=''){ 801 return io_readWikiPage(wikiFN($id, $rev), $id, $rev); 802} 803 804/** 805 * Returns the pagetemplate contents for the ID's namespace 806 * 807 * @triggers COMMON_PAGE_FROMTEMPLATE 808 * @author Andreas Gohr <andi@splitbrain.org> 809 */ 810function pageTemplate($id){ 811 global $conf; 812 813 if (is_array($id)) $id = $id[0]; 814 815 $path = dirname(wikiFN($id)); 816 $tpl = ''; 817 if(@file_exists($path.'/_template.txt')){ 818 $tpl = io_readFile($path.'/_template.txt'); 819 }else{ 820 // search upper namespaces for templates 821 $len = strlen(rtrim($conf['datadir'],'/')); 822 while (strlen($path) >= $len){ 823 if(@file_exists($path.'/__template.txt')){ 824 $tpl = io_readFile($path.'/__template.txt'); 825 break; 826 } 827 $path = substr($path, 0, strrpos($path, '/')); 828 } 829 } 830 $data = compact('tpl', 'id'); 831 trigger_event('COMMON_PAGE_FROMTEMPLATE', $data, 'parsePageTemplate', true); 832 return $data['tpl']; 833} 834 835/** 836 * Performs common page template replacements 837 * This is the default action for COMMON_PAGE_FROMTEMPLATE 838 * 839 * @author Andreas Gohr <andi@splitbrain.org> 840 */ 841function parsePageTemplate(&$data) { 842 extract($data); 843 844 global $USERINFO; 845 global $conf; 846 847 // replace placeholders 848 $file = noNS($id); 849 $page = strtr($file, $conf['sepchar'], ' '); 850 851 $tpl = str_replace(array( 852 '@ID@', 853 '@NS@', 854 '@FILE@', 855 '@!FILE@', 856 '@!FILE!@', 857 '@PAGE@', 858 '@!PAGE@', 859 '@!!PAGE@', 860 '@!PAGE!@', 861 '@USER@', 862 '@NAME@', 863 '@MAIL@', 864 '@DATE@', 865 ), 866 array( 867 $id, 868 getNS($id), 869 $file, 870 utf8_ucfirst($file), 871 utf8_strtoupper($file), 872 $page, 873 utf8_ucfirst($page), 874 utf8_ucwords($page), 875 utf8_strtoupper($page), 876 $_SERVER['REMOTE_USER'], 877 $USERINFO['name'], 878 $USERINFO['mail'], 879 $conf['dformat'], 880 ), $tpl); 881 882 // we need the callback to work around strftime's char limit 883 $tpl = preg_replace_callback('/%./',create_function('$m','return strftime($m[0]);'),$tpl); 884 $data['tpl'] = $tpl; 885 return $tpl; 886} 887 888/** 889 * Returns the raw Wiki Text in three slices. 890 * 891 * The range parameter needs to have the form "from-to" 892 * and gives the range of the section in bytes - no 893 * UTF-8 awareness is needed. 894 * The returned order is prefix, section and suffix. 895 * 896 * @author Andreas Gohr <andi@splitbrain.org> 897 */ 898function rawWikiSlices($range,$id,$rev=''){ 899 $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev); 900 901 // Parse range 902 list($from,$to) = explode('-',$range,2); 903 // Make range zero-based, use defaults if marker is missing 904 $from = !$from ? 0 : ($from - 1); 905 $to = !$to ? strlen($text) : ($to - 1); 906 907 $slices[0] = substr($text, 0, $from); 908 $slices[1] = substr($text, $from, $to-$from); 909 $slices[2] = substr($text, $to); 910 return $slices; 911} 912 913/** 914 * Joins wiki text slices 915 * 916 * function to join the text slices. 917 * When the pretty parameter is set to true it adds additional empty 918 * lines between sections if needed (used on saving). 919 * 920 * @author Andreas Gohr <andi@splitbrain.org> 921 */ 922function con($pre,$text,$suf,$pretty=false){ 923 if($pretty){ 924 if ($pre !== '' && substr($pre, -1) !== "\n" && 925 substr($text, 0, 1) !== "\n") { 926 $pre .= "\n"; 927 } 928 if ($suf !== '' && substr($text, -1) !== "\n" && 929 substr($suf, 0, 1) !== "\n") { 930 $text .= "\n"; 931 } 932 } 933 934 return $pre.$text.$suf; 935} 936 937/** 938 * Saves a wikitext by calling io_writeWikiPage. 939 * Also directs changelog and attic updates. 940 * 941 * @author Andreas Gohr <andi@splitbrain.org> 942 * @author Ben Coburn <btcoburn@silicodon.net> 943 */ 944function saveWikiText($id,$text,$summary,$minor=false){ 945 /* Note to developers: 946 This code is subtle and delicate. Test the behavior of 947 the attic and changelog with dokuwiki and external edits 948 after any changes. External edits change the wiki page 949 directly without using php or dokuwiki. 950 */ 951 global $conf; 952 global $lang; 953 global $REV; 954 // ignore if no changes were made 955 if($text == rawWiki($id,'')){ 956 return; 957 } 958 959 $file = wikiFN($id); 960 $old = @filemtime($file); // from page 961 $wasRemoved = empty($text); 962 $wasCreated = !@file_exists($file); 963 $wasReverted = ($REV==true); 964 $newRev = false; 965 $oldRev = getRevisions($id, -1, 1, 1024); // from changelog 966 $oldRev = (int)(empty($oldRev)?0:$oldRev[0]); 967 if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) { 968 // add old revision to the attic if missing 969 saveOldRevision($id); 970 // add a changelog entry if this edit came from outside dokuwiki 971 if ($old>$oldRev) { 972 addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true)); 973 // remove soon to be stale instructions 974 $cache = new cache_instructions($id, $file); 975 $cache->removeCache(); 976 } 977 } 978 979 if ($wasRemoved){ 980 // Send "update" event with empty data, so plugins can react to page deletion 981 $data = array(array($file, '', false), getNS($id), noNS($id), false); 982 trigger_event('IO_WIKIPAGE_WRITE', $data); 983 // pre-save deleted revision 984 @touch($file); 985 clearstatcache(); 986 $newRev = saveOldRevision($id); 987 // remove empty file 988 @unlink($file); 989 // remove old meta info... 990 $mfiles = metaFiles($id); 991 $changelog = metaFN($id, '.changes'); 992 $metadata = metaFN($id, '.meta'); 993 $subscribers = metaFN($id, '.mlist'); 994 foreach ($mfiles as $mfile) { 995 // but keep per-page changelog to preserve page history, keep subscriber list and keep meta data 996 if (@file_exists($mfile) && $mfile!==$changelog && $mfile!==$metadata && $mfile!==$subscribers) { @unlink($mfile); } 997 } 998 // purge meta data 999 p_purge_metadata($id); 1000 $del = true; 1001 // autoset summary on deletion 1002 if(empty($summary)) $summary = $lang['deleted']; 1003 // remove empty namespaces 1004 io_sweepNS($id, 'datadir'); 1005 io_sweepNS($id, 'mediadir'); 1006 }else{ 1007 // save file (namespace dir is created in io_writeWikiPage) 1008 io_writeWikiPage($file, $text, $id); 1009 // pre-save the revision, to keep the attic in sync 1010 $newRev = saveOldRevision($id); 1011 $del = false; 1012 } 1013 1014 // select changelog line type 1015 $extra = ''; 1016 $type = DOKU_CHANGE_TYPE_EDIT; 1017 if ($wasReverted) { 1018 $type = DOKU_CHANGE_TYPE_REVERT; 1019 $extra = $REV; 1020 } 1021 else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; } 1022 else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; } 1023 else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users 1024 1025 addLogEntry($newRev, $id, $type, $summary, $extra); 1026 // send notify mails 1027 notify($id,'admin',$old,$summary,$minor); 1028 notify($id,'subscribers',$old,$summary,$minor); 1029 1030 // update the purgefile (timestamp of the last time anything within the wiki was changed) 1031 io_saveFile($conf['cachedir'].'/purgefile',time()); 1032 1033 // if useheading is enabled, purge the cache of all linking pages 1034 if(useHeading('content')){ 1035 $pages = ft_backlinks($id); 1036 foreach ($pages as $page) { 1037 $cache = new cache_renderer($page, wikiFN($page), 'xhtml'); 1038 $cache->removeCache(); 1039 } 1040 } 1041} 1042 1043/** 1044 * moves the current version to the attic and returns its 1045 * revision date 1046 * 1047 * @author Andreas Gohr <andi@splitbrain.org> 1048 */ 1049function saveOldRevision($id){ 1050 global $conf; 1051 $oldf = wikiFN($id); 1052 if(!@file_exists($oldf)) return ''; 1053 $date = filemtime($oldf); 1054 $newf = wikiFN($id,$date); 1055 io_writeWikiPage($newf, rawWiki($id), $id, $date); 1056 return $date; 1057} 1058 1059/** 1060 * Sends a notify mail on page change or registration 1061 * 1062 * @param string $id The changed page 1063 * @param string $who Who to notify (admin|subscribers|register) 1064 * @param int $rev Old page revision 1065 * @param string $summary What changed 1066 * @param boolean $minor Is this a minor edit? 1067 * @param array $replace Additional string substitutions, @KEY@ to be replaced by value 1068 * 1069 * @author Andreas Gohr <andi@splitbrain.org> 1070 */ 1071function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){ 1072 global $lang; 1073 global $conf; 1074 global $INFO; 1075 1076 // decide if there is something to do 1077 if($who == 'admin'){ 1078 if(empty($conf['notify'])) return; //notify enabled? 1079 $text = rawLocale('mailtext'); 1080 $to = $conf['notify']; 1081 $bcc = ''; 1082 }elseif($who == 'subscribers'){ 1083 if(!$conf['subscribers']) return; //subscribers enabled? 1084 if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors 1085 $data = array('id' => $id, 'addresslist' => '', 'self' => false); 1086 trigger_event('COMMON_NOTIFY_ADDRESSLIST', $data, 1087 'subscription_addresslist'); 1088 $bcc = $data['addresslist']; 1089 if(empty($bcc)) return; 1090 $to = ''; 1091 $text = rawLocale('subscr_single'); 1092 }elseif($who == 'register'){ 1093 if(empty($conf['registernotify'])) return; 1094 $text = rawLocale('registermail'); 1095 $to = $conf['registernotify']; 1096 $bcc = ''; 1097 }else{ 1098 return; //just to be safe 1099 } 1100 1101 $ip = clientIP(); 1102 $text = str_replace('@DATE@',dformat(),$text); 1103 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 1104 $text = str_replace('@IPADDRESS@',$ip,$text); 1105 $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text); 1106 $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text); 1107 $text = str_replace('@PAGE@',$id,$text); 1108 $text = str_replace('@TITLE@',$conf['title'],$text); 1109 $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text); 1110 $text = str_replace('@SUMMARY@',$summary,$text); 1111 $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text); 1112 $text = str_replace('@NAME@',$INFO['userinfo']['name'],$text); 1113 $text = str_replace('@MAIL@',$INFO['userinfo']['mail'],$text); 1114 1115 foreach ($replace as $key => $substitution) { 1116 $text = str_replace('@'.strtoupper($key).'@',$substitution, $text); 1117 } 1118 1119 if($who == 'register'){ 1120 $subject = $lang['mail_new_user'].' '.$summary; 1121 }elseif($rev){ 1122 $subject = $lang['mail_changed'].' '.$id; 1123 $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text); 1124 $df = new Diff(explode("\n",rawWiki($id,$rev)), 1125 explode("\n",rawWiki($id))); 1126 $dformat = new UnifiedDiffFormatter(); 1127 $diff = $dformat->format($df); 1128 }else{ 1129 $subject=$lang['mail_newpage'].' '.$id; 1130 $text = str_replace('@OLDPAGE@','none',$text); 1131 $diff = rawWiki($id); 1132 } 1133 $text = str_replace('@DIFF@',$diff,$text); 1134 if(empty($conf['mailprefix'])) { 1135 if(utf8_strlen($conf['title']) < 20) { 1136 $subject = '['.$conf['title'].'] '.$subject; 1137 }else{ 1138 $subject = '['.utf8_substr($conf['title'], 0, 20).'...] '.$subject; 1139 } 1140 }else{ 1141 $subject = '['.$conf['mailprefix'].'] '.$subject; 1142 } 1143 mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc); 1144} 1145 1146/** 1147 * extracts the query from a search engine referrer 1148 * 1149 * @author Andreas Gohr <andi@splitbrain.org> 1150 * @author Todd Augsburger <todd@rollerorgans.com> 1151 */ 1152function getGoogleQuery(){ 1153 if (!isset($_SERVER['HTTP_REFERER'])) { 1154 return ''; 1155 } 1156 $url = parse_url($_SERVER['HTTP_REFERER']); 1157 1158 $query = array(); 1159 1160 // temporary workaround against PHP bug #49733 1161 // see http://bugs.php.net/bug.php?id=49733 1162 if(UTF8_MBSTRING) $enc = mb_internal_encoding(); 1163 parse_str($url['query'],$query); 1164 if(UTF8_MBSTRING) mb_internal_encoding($enc); 1165 1166 $q = ''; 1167 if(isset($query['q'])) 1168 $q = $query['q']; // google, live/msn, aol, ask, altavista, alltheweb, gigablast 1169 elseif(isset($query['p'])) 1170 $q = $query['p']; // yahoo 1171 elseif(isset($query['query'])) 1172 $q = $query['query']; // lycos, netscape, clusty, hotbot 1173 elseif(preg_match("#a9\.com#i",$url['host'])) // a9 1174 $q = urldecode(ltrim($url['path'],'/')); 1175 1176 if($q === '') return ''; 1177 $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/',$q,-1,PREG_SPLIT_NO_EMPTY); 1178 return $q; 1179} 1180 1181/** 1182 * Try to set correct locale 1183 * 1184 * @deprecated No longer used 1185 * @author Andreas Gohr <andi@splitbrain.org> 1186 */ 1187function setCorrectLocale(){ 1188 global $conf; 1189 global $lang; 1190 1191 $enc = strtoupper($lang['encoding']); 1192 foreach ($lang['locales'] as $loc){ 1193 //try locale 1194 if(@setlocale(LC_ALL,$loc)) return; 1195 //try loceale with encoding 1196 if(@setlocale(LC_ALL,"$loc.$enc")) return; 1197 } 1198 //still here? try to set from environment 1199 @setlocale(LC_ALL,""); 1200} 1201 1202/** 1203 * Return the human readable size of a file 1204 * 1205 * @param int $size A file size 1206 * @param int $dec A number of decimal places 1207 * @author Martin Benjamin <b.martin@cybernet.ch> 1208 * @author Aidan Lister <aidan@php.net> 1209 * @version 1.0.0 1210 */ 1211function filesize_h($size, $dec = 1){ 1212 $sizes = array('B', 'KB', 'MB', 'GB'); 1213 $count = count($sizes); 1214 $i = 0; 1215 1216 while ($size >= 1024 && ($i < $count - 1)) { 1217 $size /= 1024; 1218 $i++; 1219 } 1220 1221 return round($size, $dec) . ' ' . $sizes[$i]; 1222} 1223 1224/** 1225 * Return the given timestamp as human readable, fuzzy age 1226 * 1227 * @author Andreas Gohr <gohr@cosmocode.de> 1228 */ 1229function datetime_h($dt){ 1230 global $lang; 1231 1232 $ago = time() - $dt; 1233 if($ago > 24*60*60*30*12*2){ 1234 return sprintf($lang['years'], round($ago/(24*60*60*30*12))); 1235 } 1236 if($ago > 24*60*60*30*2){ 1237 return sprintf($lang['months'], round($ago/(24*60*60*30))); 1238 } 1239 if($ago > 24*60*60*7*2){ 1240 return sprintf($lang['weeks'], round($ago/(24*60*60*7))); 1241 } 1242 if($ago > 24*60*60*2){ 1243 return sprintf($lang['days'], round($ago/(24*60*60))); 1244 } 1245 if($ago > 60*60*2){ 1246 return sprintf($lang['hours'], round($ago/(60*60))); 1247 } 1248 if($ago > 60*2){ 1249 return sprintf($lang['minutes'], round($ago/(60))); 1250 } 1251 return sprintf($lang['seconds'], $ago); 1252} 1253 1254/** 1255 * Wraps around strftime but provides support for fuzzy dates 1256 * 1257 * The format default to $conf['dformat']. It is passed to 1258 * strftime - %f can be used to get the value from datetime_h() 1259 * 1260 * @see datetime_h 1261 * @author Andreas Gohr <gohr@cosmocode.de> 1262 */ 1263function dformat($dt=null,$format=''){ 1264 global $conf; 1265 1266 if(is_null($dt)) $dt = time(); 1267 $dt = (int) $dt; 1268 if(!$format) $format = $conf['dformat']; 1269 1270 $format = str_replace('%f',datetime_h($dt),$format); 1271 return strftime($format,$dt); 1272} 1273 1274/** 1275 * Formats a timestamp as ISO 8601 date 1276 * 1277 * @author <ungu at terong dot com> 1278 * @link http://www.php.net/manual/en/function.date.php#54072 1279 */ 1280function date_iso8601($int_date) { 1281 //$int_date: current date in UNIX timestamp 1282 $date_mod = date('Y-m-d\TH:i:s', $int_date); 1283 $pre_timezone = date('O', $int_date); 1284 $time_zone = substr($pre_timezone, 0, 3).":".substr($pre_timezone, 3, 2); 1285 $date_mod .= $time_zone; 1286 return $date_mod; 1287} 1288 1289/** 1290 * return an obfuscated email address in line with $conf['mailguard'] setting 1291 * 1292 * @author Harry Fuecks <hfuecks@gmail.com> 1293 * @author Christopher Smith <chris@jalakai.co.uk> 1294 */ 1295function obfuscate($email) { 1296 global $conf; 1297 1298 switch ($conf['mailguard']) { 1299 case 'visible' : 1300 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 1301 return strtr($email, $obfuscate); 1302 1303 case 'hex' : 1304 $encode = ''; 1305 $len = strlen($email); 1306 for ($x=0; $x < $len; $x++){ 1307 $encode .= '&#x' . bin2hex($email{$x}).';'; 1308 } 1309 return $encode; 1310 1311 case 'none' : 1312 default : 1313 return $email; 1314 } 1315} 1316 1317/** 1318 * Removes quoting backslashes 1319 * 1320 * @author Andreas Gohr <andi@splitbrain.org> 1321 */ 1322function unslash($string,$char="'"){ 1323 return str_replace('\\'.$char,$char,$string); 1324} 1325 1326/** 1327 * Convert php.ini shorthands to byte 1328 * 1329 * @author <gilthans dot NO dot SPAM at gmail dot com> 1330 * @link http://de3.php.net/manual/en/ini.core.php#79564 1331 */ 1332function php_to_byte($v){ 1333 $l = substr($v, -1); 1334 $ret = substr($v, 0, -1); 1335 switch(strtoupper($l)){ 1336 case 'P': 1337 $ret *= 1024; 1338 case 'T': 1339 $ret *= 1024; 1340 case 'G': 1341 $ret *= 1024; 1342 case 'M': 1343 $ret *= 1024; 1344 case 'K': 1345 $ret *= 1024; 1346 break; 1347 default; 1348 $ret *= 10; 1349 break; 1350 } 1351 return $ret; 1352} 1353 1354/** 1355 * Wrapper around preg_quote adding the default delimiter 1356 */ 1357function preg_quote_cb($string){ 1358 return preg_quote($string,'/'); 1359} 1360 1361/** 1362 * Shorten a given string by removing data from the middle 1363 * 1364 * You can give the string in two parts, the first part $keep 1365 * will never be shortened. The second part $short will be cut 1366 * in the middle to shorten but only if at least $min chars are 1367 * left to display it. Otherwise it will be left off. 1368 * 1369 * @param string $keep the part to keep 1370 * @param string $short the part to shorten 1371 * @param int $max maximum chars you want for the whole string 1372 * @param int $min minimum number of chars to have left for middle shortening 1373 * @param string $char the shortening character to use 1374 */ 1375function shorten($keep,$short,$max,$min=9,$char='…'){ 1376 $max = $max - utf8_strlen($keep); 1377 if($max < $min) return $keep; 1378 $len = utf8_strlen($short); 1379 if($len <= $max) return $keep.$short; 1380 $half = floor($max/2); 1381 return $keep.utf8_substr($short,0,$half-1).$char.utf8_substr($short,$len-$half); 1382} 1383 1384/** 1385 * Return the users realname or e-mail address for use 1386 * in page footer and recent changes pages 1387 * 1388 * @author Andy Webber <dokuwiki AT andywebber DOT com> 1389 */ 1390function editorinfo($username){ 1391 global $conf; 1392 global $auth; 1393 1394 switch($conf['showuseras']){ 1395 case 'username': 1396 case 'email': 1397 case 'email_link': 1398 if($auth) $info = $auth->getUserData($username); 1399 break; 1400 default: 1401 return hsc($username); 1402 } 1403 1404 if(isset($info) && $info) { 1405 switch($conf['showuseras']){ 1406 case 'username': 1407 return hsc($info['name']); 1408 case 'email': 1409 return obfuscate($info['mail']); 1410 case 'email_link': 1411 $mail=obfuscate($info['mail']); 1412 return '<a href="mailto:'.$mail.'">'.$mail.'</a>'; 1413 default: 1414 return hsc($username); 1415 } 1416 } else { 1417 return hsc($username); 1418 } 1419} 1420 1421/** 1422 * Returns the path to a image file for the currently chosen license. 1423 * When no image exists, returns an empty string 1424 * 1425 * @author Andreas Gohr <andi@splitbrain.org> 1426 * @param string $type - type of image 'badge' or 'button' 1427 */ 1428function license_img($type){ 1429 global $license; 1430 global $conf; 1431 if(!$conf['license']) return ''; 1432 if(!is_array($license[$conf['license']])) return ''; 1433 $lic = $license[$conf['license']]; 1434 $try = array(); 1435 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png'; 1436 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif'; 1437 if(substr($conf['license'],0,3) == 'cc-'){ 1438 $try[] = 'lib/images/license/'.$type.'/cc.png'; 1439 } 1440 foreach($try as $src){ 1441 if(@file_exists(DOKU_INC.$src)) return $src; 1442 } 1443 return ''; 1444} 1445 1446/** 1447 * Checks if the given amount of memory is available 1448 * 1449 * If the memory_get_usage() function is not available the 1450 * function just assumes $bytes of already allocated memory 1451 * 1452 * @param int $mem Size of memory you want to allocate in bytes 1453 * @param int $used already allocated memory (see above) 1454 * @author Filip Oscadal <webmaster@illusionsoftworks.cz> 1455 * @author Andreas Gohr <andi@splitbrain.org> 1456 */ 1457function is_mem_available($mem,$bytes=1048576){ 1458 $limit = trim(ini_get('memory_limit')); 1459 if(empty($limit)) return true; // no limit set! 1460 1461 // parse limit to bytes 1462 $limit = php_to_byte($limit); 1463 1464 // get used memory if possible 1465 if(function_exists('memory_get_usage')){ 1466 $used = memory_get_usage(); 1467 }else{ 1468 $used = $bytes; 1469 } 1470 1471 if($used+$mem > $limit){ 1472 return false; 1473 } 1474 1475 return true; 1476} 1477 1478/** 1479 * Send a HTTP redirect to the browser 1480 * 1481 * Works arround Microsoft IIS cookie sending bug. Exits the script. 1482 * 1483 * @link http://support.microsoft.com/kb/q176113/ 1484 * @author Andreas Gohr <andi@splitbrain.org> 1485 */ 1486function send_redirect($url){ 1487 //are there any undisplayed messages? keep them in session for display 1488 global $MSG; 1489 if (isset($MSG) && count($MSG) && !defined('NOSESSION')){ 1490 //reopen session, store data and close session again 1491 @session_start(); 1492 $_SESSION[DOKU_COOKIE]['msg'] = $MSG; 1493 } 1494 1495 // always close the session 1496 session_write_close(); 1497 1498 // work around IE bug 1499 // http://www.ianhoar.com/2008/11/16/internet-explorer-6-and-redirected-anchor-links/ 1500 list($url,$hash) = explode('#',$url); 1501 if($hash){ 1502 if(strpos($url,'?')){ 1503 $url = $url.'&#'.$hash; 1504 }else{ 1505 $url = $url.'?&#'.$hash; 1506 } 1507 } 1508 1509 // check if running on IIS < 6 with CGI-PHP 1510 if( isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) && 1511 (strpos($_SERVER['GATEWAY_INTERFACE'],'CGI') !== false) && 1512 (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) && 1513 $matches[1] < 6 ){ 1514 header('Refresh: 0;url='.$url); 1515 }else{ 1516 header('Location: '.$url); 1517 } 1518 exit; 1519} 1520 1521/** 1522 * Validate a value using a set of valid values 1523 * 1524 * This function checks whether a specified value is set and in the array 1525 * $valid_values. If not, the function returns a default value or, if no 1526 * default is specified, throws an exception. 1527 * 1528 * @param string $param The name of the parameter 1529 * @param array $valid_values A set of valid values; Optionally a default may 1530 * be marked by the key “default”. 1531 * @param array $array The array containing the value (typically $_POST 1532 * or $_GET) 1533 * @param string $exc The text of the raised exception 1534 * 1535 * @author Adrian Lang <lang@cosmocode.de> 1536 */ 1537function valid_input_set($param, $valid_values, $array, $exc = '') { 1538 if (isset($array[$param]) && in_array($array[$param], $valid_values)) { 1539 return $array[$param]; 1540 } elseif (isset($valid_values['default'])) { 1541 return $valid_values['default']; 1542 } else { 1543 throw new Exception($exc); 1544 } 1545} 1546 1547//Setup VIM: ex: et ts=2 : 1548