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