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,METADATA_RENDER_USING_SIMPLE_CACHE); 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('/^(::1|[fF][eE]80:|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_PAGETPL_LOAD 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 // prepare initial event data 816 $data = array( 817 'id' => $id, // the id of the page to be created 818 'tpl' => '', // the text used as template 819 'tplfile' => '', // the file above text was/should be loaded from 820 'doreplace' => true // should wildcard replacements be done on the text? 821 ); 822 823 $evt = new Doku_Event('COMMON_PAGETPL_LOAD',$data); 824 if($evt->advise_before(true)){ 825 // the before event might have loaded the content already 826 if(empty($data['tpl'])){ 827 // if the before event did not set a template file, try to find one 828 if(empty($data['tplfile'])){ 829 $path = dirname(wikiFN($id)); 830 $tpl = ''; 831 if(@file_exists($path.'/_template.txt')){ 832 $data['tplfile'] = $path.'/_template.txt'; 833 }else{ 834 // search upper namespaces for templates 835 $len = strlen(rtrim($conf['datadir'],'/')); 836 while (strlen($path) >= $len){ 837 if(@file_exists($path.'/__template.txt')){ 838 $data['tplfile'] = $path.'/__template.txt'; 839 break; 840 } 841 $path = substr($path, 0, strrpos($path, '/')); 842 } 843 } 844 } 845 // load the content 846 $data['tpl'] = io_readFile($data['tplfile']); 847 } 848 if($data['doreplace']) parsePageTemplate($data); 849 } 850 $evt->advise_after(); 851 unset($evt); 852 853 return $data['tpl']; 854} 855 856/** 857 * Performs common page template replacements 858 * This works on data from COMMON_PAGETPL_LOAD 859 * 860 * @author Andreas Gohr <andi@splitbrain.org> 861 */ 862function parsePageTemplate(&$data) { 863 extract($data); 864 865 global $USERINFO; 866 global $conf; 867 868 // replace placeholders 869 $file = noNS($id); 870 $page = strtr($file, $conf['sepchar'], ' '); 871 872 $tpl = str_replace(array( 873 '@ID@', 874 '@NS@', 875 '@FILE@', 876 '@!FILE@', 877 '@!FILE!@', 878 '@PAGE@', 879 '@!PAGE@', 880 '@!!PAGE@', 881 '@!PAGE!@', 882 '@USER@', 883 '@NAME@', 884 '@MAIL@', 885 '@DATE@', 886 ), 887 array( 888 $id, 889 getNS($id), 890 $file, 891 utf8_ucfirst($file), 892 utf8_strtoupper($file), 893 $page, 894 utf8_ucfirst($page), 895 utf8_ucwords($page), 896 utf8_strtoupper($page), 897 $_SERVER['REMOTE_USER'], 898 $USERINFO['name'], 899 $USERINFO['mail'], 900 $conf['dformat'], 901 ), $tpl); 902 903 // we need the callback to work around strftime's char limit 904 $tpl = preg_replace_callback('/%./',create_function('$m','return strftime($m[0]);'),$tpl); 905 $data['tpl'] = $tpl; 906 return $tpl; 907} 908 909/** 910 * Returns the raw Wiki Text in three slices. 911 * 912 * The range parameter needs to have the form "from-to" 913 * and gives the range of the section in bytes - no 914 * UTF-8 awareness is needed. 915 * The returned order is prefix, section and suffix. 916 * 917 * @author Andreas Gohr <andi@splitbrain.org> 918 */ 919function rawWikiSlices($range,$id,$rev=''){ 920 $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev); 921 922 // Parse range 923 list($from,$to) = explode('-',$range,2); 924 // Make range zero-based, use defaults if marker is missing 925 $from = !$from ? 0 : ($from - 1); 926 $to = !$to ? strlen($text) : ($to - 1); 927 928 $slices[0] = substr($text, 0, $from); 929 $slices[1] = substr($text, $from, $to-$from); 930 $slices[2] = substr($text, $to); 931 return $slices; 932} 933 934/** 935 * Joins wiki text slices 936 * 937 * function to join the text slices. 938 * When the pretty parameter is set to true it adds additional empty 939 * lines between sections if needed (used on saving). 940 * 941 * @author Andreas Gohr <andi@splitbrain.org> 942 */ 943function con($pre,$text,$suf,$pretty=false){ 944 if($pretty){ 945 if ($pre !== '' && substr($pre, -1) !== "\n" && 946 substr($text, 0, 1) !== "\n") { 947 $pre .= "\n"; 948 } 949 if ($suf !== '' && substr($text, -1) !== "\n" && 950 substr($suf, 0, 1) !== "\n") { 951 $text .= "\n"; 952 } 953 } 954 955 return $pre.$text.$suf; 956} 957 958/** 959 * Saves a wikitext by calling io_writeWikiPage. 960 * Also directs changelog and attic updates. 961 * 962 * @author Andreas Gohr <andi@splitbrain.org> 963 * @author Ben Coburn <btcoburn@silicodon.net> 964 */ 965function saveWikiText($id,$text,$summary,$minor=false){ 966 /* Note to developers: 967 This code is subtle and delicate. Test the behavior of 968 the attic and changelog with dokuwiki and external edits 969 after any changes. External edits change the wiki page 970 directly without using php or dokuwiki. 971 */ 972 global $conf; 973 global $lang; 974 global $REV; 975 // ignore if no changes were made 976 if($text == rawWiki($id,'')){ 977 return; 978 } 979 980 $file = wikiFN($id); 981 $old = @filemtime($file); // from page 982 $wasRemoved = empty($text); 983 $wasCreated = !@file_exists($file); 984 $wasReverted = ($REV==true); 985 $newRev = false; 986 $oldRev = getRevisions($id, -1, 1, 1024); // from changelog 987 $oldRev = (int)(empty($oldRev)?0:$oldRev[0]); 988 if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) { 989 // add old revision to the attic if missing 990 saveOldRevision($id); 991 // add a changelog entry if this edit came from outside dokuwiki 992 if ($old>$oldRev) { 993 addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true)); 994 // remove soon to be stale instructions 995 $cache = new cache_instructions($id, $file); 996 $cache->removeCache(); 997 } 998 } 999 1000 if ($wasRemoved){ 1001 // Send "update" event with empty data, so plugins can react to page deletion 1002 $data = array(array($file, '', false), getNS($id), noNS($id), false); 1003 trigger_event('IO_WIKIPAGE_WRITE', $data); 1004 // pre-save deleted revision 1005 @touch($file); 1006 clearstatcache(); 1007 $newRev = saveOldRevision($id); 1008 // remove empty file 1009 @unlink($file); 1010 // don't remove old meta info as it should be saved, plugins can use IO_WIKIPAGE_WRITE for removing their metadata... 1011 // purge non-persistant meta data 1012 p_purge_metadata($id); 1013 $del = true; 1014 // autoset summary on deletion 1015 if(empty($summary)) $summary = $lang['deleted']; 1016 // remove empty namespaces 1017 io_sweepNS($id, 'datadir'); 1018 io_sweepNS($id, 'mediadir'); 1019 }else{ 1020 // save file (namespace dir is created in io_writeWikiPage) 1021 io_writeWikiPage($file, $text, $id); 1022 // pre-save the revision, to keep the attic in sync 1023 $newRev = saveOldRevision($id); 1024 $del = false; 1025 } 1026 1027 // select changelog line type 1028 $extra = ''; 1029 $type = DOKU_CHANGE_TYPE_EDIT; 1030 if ($wasReverted) { 1031 $type = DOKU_CHANGE_TYPE_REVERT; 1032 $extra = $REV; 1033 } 1034 else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; } 1035 else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; } 1036 else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users 1037 1038 addLogEntry($newRev, $id, $type, $summary, $extra); 1039 // send notify mails 1040 notify($id,'admin',$old,$summary,$minor); 1041 notify($id,'subscribers',$old,$summary,$minor); 1042 1043 // update the purgefile (timestamp of the last time anything within the wiki was changed) 1044 io_saveFile($conf['cachedir'].'/purgefile',time()); 1045 1046 // if useheading is enabled, purge the cache of all linking pages 1047 if(useHeading('content')){ 1048 $pages = ft_backlinks($id); 1049 foreach ($pages as $page) { 1050 $cache = new cache_renderer($page, wikiFN($page), 'xhtml'); 1051 $cache->removeCache(); 1052 } 1053 } 1054} 1055 1056/** 1057 * moves the current version to the attic and returns its 1058 * revision date 1059 * 1060 * @author Andreas Gohr <andi@splitbrain.org> 1061 */ 1062function saveOldRevision($id){ 1063 global $conf; 1064 $oldf = wikiFN($id); 1065 if(!@file_exists($oldf)) return ''; 1066 $date = filemtime($oldf); 1067 $newf = wikiFN($id,$date); 1068 io_writeWikiPage($newf, rawWiki($id), $id, $date); 1069 return $date; 1070} 1071 1072/** 1073 * Sends a notify mail on page change or registration 1074 * 1075 * @param string $id The changed page 1076 * @param string $who Who to notify (admin|subscribers|register) 1077 * @param int $rev Old page revision 1078 * @param string $summary What changed 1079 * @param boolean $minor Is this a minor edit? 1080 * @param array $replace Additional string substitutions, @KEY@ to be replaced by value 1081 * 1082 * @author Andreas Gohr <andi@splitbrain.org> 1083 */ 1084function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){ 1085 global $lang; 1086 global $conf; 1087 global $INFO; 1088 1089 // decide if there is something to do 1090 if($who == 'admin'){ 1091 if(empty($conf['notify'])) return; //notify enabled? 1092 $text = rawLocale('mailtext'); 1093 $to = $conf['notify']; 1094 $bcc = ''; 1095 }elseif($who == 'subscribers'){ 1096 if(!$conf['subscribers']) return; //subscribers enabled? 1097 if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors 1098 $data = array('id' => $id, 'addresslist' => '', 'self' => false); 1099 trigger_event('COMMON_NOTIFY_ADDRESSLIST', $data, 1100 'subscription_addresslist'); 1101 $bcc = $data['addresslist']; 1102 if(empty($bcc)) return; 1103 $to = ''; 1104 $text = rawLocale('subscr_single'); 1105 }elseif($who == 'register'){ 1106 if(empty($conf['registernotify'])) return; 1107 $text = rawLocale('registermail'); 1108 $to = $conf['registernotify']; 1109 $bcc = ''; 1110 }else{ 1111 return; //just to be safe 1112 } 1113 1114 $ip = clientIP(); 1115 $text = str_replace('@DATE@',dformat(),$text); 1116 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 1117 $text = str_replace('@IPADDRESS@',$ip,$text); 1118 $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text); 1119 $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text); 1120 $text = str_replace('@PAGE@',$id,$text); 1121 $text = str_replace('@TITLE@',$conf['title'],$text); 1122 $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text); 1123 $text = str_replace('@SUMMARY@',$summary,$text); 1124 $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text); 1125 $text = str_replace('@NAME@',$INFO['userinfo']['name'],$text); 1126 $text = str_replace('@MAIL@',$INFO['userinfo']['mail'],$text); 1127 1128 foreach ($replace as $key => $substitution) { 1129 $text = str_replace('@'.strtoupper($key).'@',$substitution, $text); 1130 } 1131 1132 if($who == 'register'){ 1133 $subject = $lang['mail_new_user'].' '.$summary; 1134 }elseif($rev){ 1135 $subject = $lang['mail_changed'].' '.$id; 1136 $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text); 1137 $df = new Diff(explode("\n",rawWiki($id,$rev)), 1138 explode("\n",rawWiki($id))); 1139 $dformat = new UnifiedDiffFormatter(); 1140 $diff = $dformat->format($df); 1141 }else{ 1142 $subject=$lang['mail_newpage'].' '.$id; 1143 $text = str_replace('@OLDPAGE@','none',$text); 1144 $diff = rawWiki($id); 1145 } 1146 $text = str_replace('@DIFF@',$diff,$text); 1147 if(empty($conf['mailprefix'])) { 1148 if(utf8_strlen($conf['title']) < 20) { 1149 $subject = '['.$conf['title'].'] '.$subject; 1150 }else{ 1151 $subject = '['.utf8_substr($conf['title'], 0, 20).'...] '.$subject; 1152 } 1153 }else{ 1154 $subject = '['.$conf['mailprefix'].'] '.$subject; 1155 } 1156 mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc); 1157} 1158 1159/** 1160 * extracts the query from a search engine referrer 1161 * 1162 * @author Andreas Gohr <andi@splitbrain.org> 1163 * @author Todd Augsburger <todd@rollerorgans.com> 1164 */ 1165function getGoogleQuery(){ 1166 if (!isset($_SERVER['HTTP_REFERER'])) { 1167 return ''; 1168 } 1169 $url = parse_url($_SERVER['HTTP_REFERER']); 1170 1171 $query = array(); 1172 1173 // temporary workaround against PHP bug #49733 1174 // see http://bugs.php.net/bug.php?id=49733 1175 if(UTF8_MBSTRING) $enc = mb_internal_encoding(); 1176 parse_str($url['query'],$query); 1177 if(UTF8_MBSTRING) mb_internal_encoding($enc); 1178 1179 $q = ''; 1180 if(isset($query['q'])) 1181 $q = $query['q']; // google, live/msn, aol, ask, altavista, alltheweb, gigablast 1182 elseif(isset($query['p'])) 1183 $q = $query['p']; // yahoo 1184 elseif(isset($query['query'])) 1185 $q = $query['query']; // lycos, netscape, clusty, hotbot 1186 elseif(preg_match("#a9\.com#i",$url['host'])) // a9 1187 $q = urldecode(ltrim($url['path'],'/')); 1188 1189 if($q === '') return ''; 1190 $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/',$q,-1,PREG_SPLIT_NO_EMPTY); 1191 return $q; 1192} 1193 1194/** 1195 * Try to set correct locale 1196 * 1197 * @deprecated No longer used 1198 * @author Andreas Gohr <andi@splitbrain.org> 1199 */ 1200function setCorrectLocale(){ 1201 global $conf; 1202 global $lang; 1203 1204 $enc = strtoupper($lang['encoding']); 1205 foreach ($lang['locales'] as $loc){ 1206 //try locale 1207 if(@setlocale(LC_ALL,$loc)) return; 1208 //try loceale with encoding 1209 if(@setlocale(LC_ALL,"$loc.$enc")) return; 1210 } 1211 //still here? try to set from environment 1212 @setlocale(LC_ALL,""); 1213} 1214 1215/** 1216 * Return the human readable size of a file 1217 * 1218 * @param int $size A file size 1219 * @param int $dec A number of decimal places 1220 * @author Martin Benjamin <b.martin@cybernet.ch> 1221 * @author Aidan Lister <aidan@php.net> 1222 * @version 1.0.0 1223 */ 1224function filesize_h($size, $dec = 1){ 1225 $sizes = array('B', 'KB', 'MB', 'GB'); 1226 $count = count($sizes); 1227 $i = 0; 1228 1229 while ($size >= 1024 && ($i < $count - 1)) { 1230 $size /= 1024; 1231 $i++; 1232 } 1233 1234 return round($size, $dec) . ' ' . $sizes[$i]; 1235} 1236 1237/** 1238 * Return the given timestamp as human readable, fuzzy age 1239 * 1240 * @author Andreas Gohr <gohr@cosmocode.de> 1241 */ 1242function datetime_h($dt){ 1243 global $lang; 1244 1245 $ago = time() - $dt; 1246 if($ago > 24*60*60*30*12*2){ 1247 return sprintf($lang['years'], round($ago/(24*60*60*30*12))); 1248 } 1249 if($ago > 24*60*60*30*2){ 1250 return sprintf($lang['months'], round($ago/(24*60*60*30))); 1251 } 1252 if($ago > 24*60*60*7*2){ 1253 return sprintf($lang['weeks'], round($ago/(24*60*60*7))); 1254 } 1255 if($ago > 24*60*60*2){ 1256 return sprintf($lang['days'], round($ago/(24*60*60))); 1257 } 1258 if($ago > 60*60*2){ 1259 return sprintf($lang['hours'], round($ago/(60*60))); 1260 } 1261 if($ago > 60*2){ 1262 return sprintf($lang['minutes'], round($ago/(60))); 1263 } 1264 return sprintf($lang['seconds'], $ago); 1265} 1266 1267/** 1268 * Wraps around strftime but provides support for fuzzy dates 1269 * 1270 * The format default to $conf['dformat']. It is passed to 1271 * strftime - %f can be used to get the value from datetime_h() 1272 * 1273 * @see datetime_h 1274 * @author Andreas Gohr <gohr@cosmocode.de> 1275 */ 1276function dformat($dt=null,$format=''){ 1277 global $conf; 1278 1279 if(is_null($dt)) $dt = time(); 1280 $dt = (int) $dt; 1281 if(!$format) $format = $conf['dformat']; 1282 1283 $format = str_replace('%f',datetime_h($dt),$format); 1284 return strftime($format,$dt); 1285} 1286 1287/** 1288 * Formats a timestamp as ISO 8601 date 1289 * 1290 * @author <ungu at terong dot com> 1291 * @link http://www.php.net/manual/en/function.date.php#54072 1292 */ 1293function date_iso8601($int_date) { 1294 //$int_date: current date in UNIX timestamp 1295 $date_mod = date('Y-m-d\TH:i:s', $int_date); 1296 $pre_timezone = date('O', $int_date); 1297 $time_zone = substr($pre_timezone, 0, 3).":".substr($pre_timezone, 3, 2); 1298 $date_mod .= $time_zone; 1299 return $date_mod; 1300} 1301 1302/** 1303 * return an obfuscated email address in line with $conf['mailguard'] setting 1304 * 1305 * @author Harry Fuecks <hfuecks@gmail.com> 1306 * @author Christopher Smith <chris@jalakai.co.uk> 1307 */ 1308function obfuscate($email) { 1309 global $conf; 1310 1311 switch ($conf['mailguard']) { 1312 case 'visible' : 1313 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 1314 return strtr($email, $obfuscate); 1315 1316 case 'hex' : 1317 $encode = ''; 1318 $len = strlen($email); 1319 for ($x=0; $x < $len; $x++){ 1320 $encode .= '&#x' . bin2hex($email{$x}).';'; 1321 } 1322 return $encode; 1323 1324 case 'none' : 1325 default : 1326 return $email; 1327 } 1328} 1329 1330/** 1331 * Removes quoting backslashes 1332 * 1333 * @author Andreas Gohr <andi@splitbrain.org> 1334 */ 1335function unslash($string,$char="'"){ 1336 return str_replace('\\'.$char,$char,$string); 1337} 1338 1339/** 1340 * Convert php.ini shorthands to byte 1341 * 1342 * @author <gilthans dot NO dot SPAM at gmail dot com> 1343 * @link http://de3.php.net/manual/en/ini.core.php#79564 1344 */ 1345function php_to_byte($v){ 1346 $l = substr($v, -1); 1347 $ret = substr($v, 0, -1); 1348 switch(strtoupper($l)){ 1349 case 'P': 1350 $ret *= 1024; 1351 case 'T': 1352 $ret *= 1024; 1353 case 'G': 1354 $ret *= 1024; 1355 case 'M': 1356 $ret *= 1024; 1357 case 'K': 1358 $ret *= 1024; 1359 break; 1360 default; 1361 $ret *= 10; 1362 break; 1363 } 1364 return $ret; 1365} 1366 1367/** 1368 * Wrapper around preg_quote adding the default delimiter 1369 */ 1370function preg_quote_cb($string){ 1371 return preg_quote($string,'/'); 1372} 1373 1374/** 1375 * Shorten a given string by removing data from the middle 1376 * 1377 * You can give the string in two parts, the first part $keep 1378 * will never be shortened. The second part $short will be cut 1379 * in the middle to shorten but only if at least $min chars are 1380 * left to display it. Otherwise it will be left off. 1381 * 1382 * @param string $keep the part to keep 1383 * @param string $short the part to shorten 1384 * @param int $max maximum chars you want for the whole string 1385 * @param int $min minimum number of chars to have left for middle shortening 1386 * @param string $char the shortening character to use 1387 */ 1388function shorten($keep,$short,$max,$min=9,$char='…'){ 1389 $max = $max - utf8_strlen($keep); 1390 if($max < $min) return $keep; 1391 $len = utf8_strlen($short); 1392 if($len <= $max) return $keep.$short; 1393 $half = floor($max/2); 1394 return $keep.utf8_substr($short,0,$half-1).$char.utf8_substr($short,$len-$half); 1395} 1396 1397/** 1398 * Return the users realname or e-mail address for use 1399 * in page footer and recent changes pages 1400 * 1401 * @author Andy Webber <dokuwiki AT andywebber DOT com> 1402 */ 1403function editorinfo($username){ 1404 global $conf; 1405 global $auth; 1406 1407 switch($conf['showuseras']){ 1408 case 'username': 1409 case 'email': 1410 case 'email_link': 1411 if($auth) $info = $auth->getUserData($username); 1412 break; 1413 default: 1414 return hsc($username); 1415 } 1416 1417 if(isset($info) && $info) { 1418 switch($conf['showuseras']){ 1419 case 'username': 1420 return hsc($info['name']); 1421 case 'email': 1422 return obfuscate($info['mail']); 1423 case 'email_link': 1424 $mail=obfuscate($info['mail']); 1425 return '<a href="mailto:'.$mail.'">'.$mail.'</a>'; 1426 default: 1427 return hsc($username); 1428 } 1429 } else { 1430 return hsc($username); 1431 } 1432} 1433 1434/** 1435 * Returns the path to a image file for the currently chosen license. 1436 * When no image exists, returns an empty string 1437 * 1438 * @author Andreas Gohr <andi@splitbrain.org> 1439 * @param string $type - type of image 'badge' or 'button' 1440 */ 1441function license_img($type){ 1442 global $license; 1443 global $conf; 1444 if(!$conf['license']) return ''; 1445 if(!is_array($license[$conf['license']])) return ''; 1446 $lic = $license[$conf['license']]; 1447 $try = array(); 1448 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png'; 1449 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif'; 1450 if(substr($conf['license'],0,3) == 'cc-'){ 1451 $try[] = 'lib/images/license/'.$type.'/cc.png'; 1452 } 1453 foreach($try as $src){ 1454 if(@file_exists(DOKU_INC.$src)) return $src; 1455 } 1456 return ''; 1457} 1458 1459/** 1460 * Checks if the given amount of memory is available 1461 * 1462 * If the memory_get_usage() function is not available the 1463 * function just assumes $bytes of already allocated memory 1464 * 1465 * @param int $mem Size of memory you want to allocate in bytes 1466 * @param int $used already allocated memory (see above) 1467 * @author Filip Oscadal <webmaster@illusionsoftworks.cz> 1468 * @author Andreas Gohr <andi@splitbrain.org> 1469 */ 1470function is_mem_available($mem,$bytes=1048576){ 1471 $limit = trim(ini_get('memory_limit')); 1472 if(empty($limit)) return true; // no limit set! 1473 1474 // parse limit to bytes 1475 $limit = php_to_byte($limit); 1476 1477 // get used memory if possible 1478 if(function_exists('memory_get_usage')){ 1479 $used = memory_get_usage(); 1480 }else{ 1481 $used = $bytes; 1482 } 1483 1484 if($used+$mem > $limit){ 1485 return false; 1486 } 1487 1488 return true; 1489} 1490 1491/** 1492 * Send a HTTP redirect to the browser 1493 * 1494 * Works arround Microsoft IIS cookie sending bug. Exits the script. 1495 * 1496 * @link http://support.microsoft.com/kb/q176113/ 1497 * @author Andreas Gohr <andi@splitbrain.org> 1498 */ 1499function send_redirect($url){ 1500 //are there any undisplayed messages? keep them in session for display 1501 global $MSG; 1502 if (isset($MSG) && count($MSG) && !defined('NOSESSION')){ 1503 //reopen session, store data and close session again 1504 @session_start(); 1505 $_SESSION[DOKU_COOKIE]['msg'] = $MSG; 1506 } 1507 1508 // always close the session 1509 session_write_close(); 1510 1511 // work around IE bug 1512 // http://www.ianhoar.com/2008/11/16/internet-explorer-6-and-redirected-anchor-links/ 1513 list($url,$hash) = explode('#',$url); 1514 if($hash){ 1515 if(strpos($url,'?')){ 1516 $url = $url.'&#'.$hash; 1517 }else{ 1518 $url = $url.'?&#'.$hash; 1519 } 1520 } 1521 1522 // check if running on IIS < 6 with CGI-PHP 1523 if( isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) && 1524 (strpos($_SERVER['GATEWAY_INTERFACE'],'CGI') !== false) && 1525 (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) && 1526 $matches[1] < 6 ){ 1527 header('Refresh: 0;url='.$url); 1528 }else{ 1529 header('Location: '.$url); 1530 } 1531 exit; 1532} 1533 1534/** 1535 * Validate a value using a set of valid values 1536 * 1537 * This function checks whether a specified value is set and in the array 1538 * $valid_values. If not, the function returns a default value or, if no 1539 * default is specified, throws an exception. 1540 * 1541 * @param string $param The name of the parameter 1542 * @param array $valid_values A set of valid values; Optionally a default may 1543 * be marked by the key “default”. 1544 * @param array $array The array containing the value (typically $_POST 1545 * or $_GET) 1546 * @param string $exc The text of the raised exception 1547 * 1548 * @author Adrian Lang <lang@cosmocode.de> 1549 */ 1550function valid_input_set($param, $valid_values, $array, $exc = '') { 1551 if (isset($array[$param]) && in_array($array[$param], $valid_values)) { 1552 return $array[$param]; 1553 } elseif (isset($valid_values['default'])) { 1554 return $valid_values['default']; 1555 } else { 1556 throw new Exception($exc); 1557 } 1558} 1559 1560//Setup VIM: ex: et ts=2 : 1561