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 PassHash::hmac('md5', session_id().$_SERVER['REMOTE_USER'], auth_cookiesalt()); 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 * Return info about the current document as associative 90 * array. 91 * 92 * @author Andreas Gohr <andi@splitbrain.org> 93 */ 94function pageinfo() { 95 global $ID; 96 global $REV; 97 global $RANGE; 98 global $USERINFO; 99 global $lang; 100 101 // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml 102 // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary 103 $info['id'] = $ID; 104 $info['rev'] = $REV; 105 106 // set info about manager/admin status. 107 $info['isadmin'] = false; 108 $info['ismanager'] = false; 109 if(isset($_SERVER['REMOTE_USER'])) { 110 $sub = new Subscription(); 111 112 $info['userinfo'] = $USERINFO; 113 $info['perm'] = auth_quickaclcheck($ID); 114 $info['subscribed'] = $sub->user_subscription(); 115 $info['client'] = $_SERVER['REMOTE_USER']; 116 117 if($info['perm'] == AUTH_ADMIN) { 118 $info['isadmin'] = true; 119 $info['ismanager'] = true; 120 } elseif(auth_ismanager()) { 121 $info['ismanager'] = true; 122 } 123 124 // if some outside auth were used only REMOTE_USER is set 125 if(!$info['userinfo']['name']) { 126 $info['userinfo']['name'] = $_SERVER['REMOTE_USER']; 127 } 128 129 } else { 130 $info['perm'] = auth_aclcheck($ID, '', null); 131 $info['subscribed'] = false; 132 $info['client'] = clientIP(true); 133 } 134 135 $info['namespace'] = getNS($ID); 136 $info['locked'] = checklock($ID); 137 $info['filepath'] = fullpath(wikiFN($ID)); 138 $info['exists'] = @file_exists($info['filepath']); 139 if($REV) { 140 //check if current revision was meant 141 if($info['exists'] && (@filemtime($info['filepath']) == $REV)) { 142 $REV = ''; 143 } elseif($RANGE) { 144 //section editing does not work with old revisions! 145 $REV = ''; 146 $RANGE = ''; 147 msg($lang['nosecedit'], 0); 148 } else { 149 //really use old revision 150 $info['filepath'] = fullpath(wikiFN($ID, $REV)); 151 $info['exists'] = @file_exists($info['filepath']); 152 } 153 } 154 $info['rev'] = $REV; 155 if($info['exists']) { 156 $info['writable'] = (is_writable($info['filepath']) && 157 ($info['perm'] >= AUTH_EDIT)); 158 } else { 159 $info['writable'] = ($info['perm'] >= AUTH_CREATE); 160 } 161 $info['editable'] = ($info['writable'] && empty($info['locked'])); 162 $info['lastmod'] = @filemtime($info['filepath']); 163 164 //load page meta data 165 $info['meta'] = p_get_metadata($ID); 166 167 //who's the editor 168 if($REV) { 169 $revinfo = getRevisionInfo($ID, $REV, 1024); 170 } else { 171 if(is_array($info['meta']['last_change'])) { 172 $revinfo = $info['meta']['last_change']; 173 } else { 174 $revinfo = getRevisionInfo($ID, $info['lastmod'], 1024); 175 // cache most recent changelog line in metadata if missing and still valid 176 if($revinfo !== false) { 177 $info['meta']['last_change'] = $revinfo; 178 p_set_metadata($ID, array('last_change' => $revinfo)); 179 } 180 } 181 } 182 //and check for an external edit 183 if($revinfo !== false && $revinfo['date'] != $info['lastmod']) { 184 // cached changelog line no longer valid 185 $revinfo = false; 186 $info['meta']['last_change'] = $revinfo; 187 p_set_metadata($ID, array('last_change' => $revinfo)); 188 } 189 190 $info['ip'] = $revinfo['ip']; 191 $info['user'] = $revinfo['user']; 192 $info['sum'] = $revinfo['sum']; 193 // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID. 194 // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor']. 195 196 if($revinfo['user']) { 197 $info['editor'] = $revinfo['user']; 198 } else { 199 $info['editor'] = $revinfo['ip']; 200 } 201 202 // draft 203 $draft = getCacheName($info['client'].$ID, '.draft'); 204 if(@file_exists($draft)) { 205 if(@filemtime($draft) < @filemtime(wikiFN($ID))) { 206 // remove stale draft 207 @unlink($draft); 208 } else { 209 $info['draft'] = $draft; 210 } 211 } 212 213 // mobile detection 214 $info['ismobile'] = clientismobile(); 215 216 return $info; 217} 218 219/** 220 * Build an string of URL parameters 221 * 222 * @author Andreas Gohr 223 */ 224function buildURLparams($params, $sep = '&') { 225 $url = ''; 226 $amp = false; 227 foreach($params as $key => $val) { 228 if($amp) $url .= $sep; 229 230 $url .= rawurlencode($key).'='; 231 $url .= rawurlencode((string) $val); 232 $amp = true; 233 } 234 return $url; 235} 236 237/** 238 * Build an string of html tag attributes 239 * 240 * Skips keys starting with '_', values get HTML encoded 241 * 242 * @author Andreas Gohr 243 */ 244function buildAttributes($params, $skipempty = false) { 245 $url = ''; 246 $white = false; 247 foreach($params as $key => $val) { 248 if($key{0} == '_') continue; 249 if($val === '' && $skipempty) continue; 250 if($white) $url .= ' '; 251 252 $url .= $key.'="'; 253 $url .= htmlspecialchars($val); 254 $url .= '"'; 255 $white = true; 256 } 257 return $url; 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 (non-IIS) systems and to have proper URL encoding 315 * 316 * See discussions at https://github.com/splitbrain/dokuwiki/pull/84 and 317 * https://github.com/splitbrain/dokuwiki/pull/173 why we use a whitelist of 318 * unaffected servers instead of blacklisting affected servers here. 319 * 320 * Urlencoding is ommitted when the second parameter is false 321 * 322 * @author Andreas Gohr <andi@splitbrain.org> 323 */ 324function idfilter($id, $ue = true) { 325 global $conf; 326 if($conf['useslash'] && $conf['userewrite']) { 327 $id = strtr($id, ':', '/'); 328 } elseif(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && 329 $conf['userewrite'] && 330 strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false 331 ) { 332 $id = strtr($id, ':', ';'); 333 } 334 if($ue) { 335 $id = rawurlencode($id); 336 $id = str_replace('%3A', ':', $id); //keep as colon 337 $id = str_replace('%2F', '/', $id); //keep as slash 338 } 339 return $id; 340} 341 342/** 343 * This builds a link to a wikipage 344 * 345 * It handles URL rewriting and adds additional parameter if 346 * given in $more 347 * 348 * @author Andreas Gohr <andi@splitbrain.org> 349 */ 350function wl($id = '', $urlParameters = '', $absolute = false, $separator = '&') { 351 global $conf; 352 if(is_array($urlParameters)) { 353 $urlParameters = buildURLparams($urlParameters, $separator); 354 } else { 355 $urlParameters = str_replace(',', $separator, $urlParameters); 356 } 357 if($id === '') { 358 $id = $conf['start']; 359 } 360 $id = idfilter($id); 361 if($absolute) { 362 $xlink = DOKU_URL; 363 } else { 364 $xlink = DOKU_BASE; 365 } 366 367 if($conf['userewrite'] == 2) { 368 $xlink .= DOKU_SCRIPT.'/'.$id; 369 if($urlParameters) $xlink .= '?'.$urlParameters; 370 } elseif($conf['userewrite']) { 371 $xlink .= $id; 372 if($urlParameters) $xlink .= '?'.$urlParameters; 373 } elseif($id) { 374 $xlink .= DOKU_SCRIPT.'?id='.$id; 375 if($urlParameters) $xlink .= $separator.$urlParameters; 376 } else { 377 $xlink .= DOKU_SCRIPT; 378 if($urlParameters) $xlink .= '?'.$urlParameters; 379 } 380 381 return $xlink; 382} 383 384/** 385 * This builds a link to an alternate page format 386 * 387 * Handles URL rewriting if enabled. Follows the style of wl(). 388 * 389 * @author Ben Coburn <btcoburn@silicodon.net> 390 */ 391function exportlink($id = '', $format = 'raw', $more = '', $abs = false, $sep = '&') { 392 global $conf; 393 if(is_array($more)) { 394 $more = buildURLparams($more, $sep); 395 } else { 396 $more = str_replace(',', $sep, $more); 397 } 398 399 $format = rawurlencode($format); 400 $id = idfilter($id); 401 if($abs) { 402 $xlink = DOKU_URL; 403 } else { 404 $xlink = DOKU_BASE; 405 } 406 407 if($conf['userewrite'] == 2) { 408 $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format; 409 if($more) $xlink .= $sep.$more; 410 } elseif($conf['userewrite'] == 1) { 411 $xlink .= '_export/'.$format.'/'.$id; 412 if($more) $xlink .= '?'.$more; 413 } else { 414 $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id; 415 if($more) $xlink .= $sep.$more; 416 } 417 418 return $xlink; 419} 420 421/** 422 * Build a link to a media file 423 * 424 * Will return a link to the detail page if $direct is false 425 * 426 * The $more parameter should always be given as array, the function then 427 * will strip default parameters to produce even cleaner URLs 428 * 429 * @param string $id the media file id or URL 430 * @param mixed $more string or array with additional parameters 431 * @param bool $direct link to detail page if false 432 * @param string $sep URL parameter separator 433 * @param bool $abs Create an absolute URL 434 * @return string 435 */ 436function ml($id = '', $more = '', $direct = true, $sep = '&', $abs = false) { 437 global $conf; 438 $isexternalimage = preg_match('#^(https?|ftp)://#i', $id); 439 if(!$isexternalimage) { 440 $id = cleanID($id); 441 } 442 443 if(is_array($more)) { 444 // add token for resized images 445 if($more['w'] || $more['h']){ 446 $more['tok'] = media_get_token($id,$more['w'],$more['h']); 447 } 448 // strip defaults for shorter URLs 449 if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']); 450 if(!$more['w']) unset($more['w']); 451 if(!$more['h']) unset($more['h']); 452 if(isset($more['id']) && $direct) unset($more['id']); 453 $more = buildURLparams($more, $sep); 454 } else { 455 $matches = array(); 456 if (preg_match_all('/\b(w|h)=(\d*)\b/',$more,$matches,PREG_SET_ORDER)){ 457 $resize = array('w'=>0, 'h'=>0); 458 foreach ($matches as $match){ 459 $resize[$match[1]] = $match[2]; 460 } 461 $more .= $sep.'tok='.media_get_token($id,$resize['w'],$resize['h']); 462 } 463 $more = str_replace('cache=cache', '', $more); //skip default 464 $more = str_replace(',,', ',', $more); 465 $more = str_replace(',', $sep, $more); 466 } 467 468 if($abs) { 469 $xlink = DOKU_URL; 470 } else { 471 $xlink = DOKU_BASE; 472 } 473 474 // external URLs are always direct without rewriting 475 if($isexternalimage) { 476 $xlink .= 'lib/exe/fetch.php'; 477 // add hash: 478 $xlink .= '?hash='.substr(PassHash::hmac('md5', $id, auth_cookiesalt()), 0, 6); 479 if($more) { 480 $xlink .= $sep.$more; 481 $xlink .= $sep.'media='.rawurlencode($id); 482 } else { 483 $xlink .= $sep.'media='.rawurlencode($id); 484 } 485 return $xlink; 486 } 487 488 $id = idfilter($id); 489 490 // decide on scriptname 491 if($direct) { 492 if($conf['userewrite'] == 1) { 493 $script = '_media'; 494 } else { 495 $script = 'lib/exe/fetch.php'; 496 } 497 } else { 498 if($conf['userewrite'] == 1) { 499 $script = '_detail'; 500 } else { 501 $script = 'lib/exe/detail.php'; 502 } 503 } 504 505 // build URL based on rewrite mode 506 if($conf['userewrite']) { 507 $xlink .= $script.'/'.$id; 508 if($more) $xlink .= '?'.$more; 509 } else { 510 if($more) { 511 $xlink .= $script.'?'.$more; 512 $xlink .= $sep.'media='.$id; 513 } else { 514 $xlink .= $script.'?media='.$id; 515 } 516 } 517 518 return $xlink; 519} 520 521/** 522 * Returns the URL to the DokuWiki base script 523 * 524 * Consider using wl() instead, unless you absoutely need the doku.php endpoint 525 * 526 * @author Andreas Gohr <andi@splitbrain.org> 527 */ 528function script() { 529 return DOKU_BASE.DOKU_SCRIPT; 530} 531 532/** 533 * Spamcheck against wordlist 534 * 535 * Checks the wikitext against a list of blocked expressions 536 * returns true if the text contains any bad words 537 * 538 * Triggers COMMON_WORDBLOCK_BLOCKED 539 * 540 * Action Plugins can use this event to inspect the blocked data 541 * and gain information about the user who was blocked. 542 * 543 * Event data: 544 * data['matches'] - array of matches 545 * data['userinfo'] - information about the blocked user 546 * [ip] - ip address 547 * [user] - username (if logged in) 548 * [mail] - mail address (if logged in) 549 * [name] - real name (if logged in) 550 * 551 * @author Andreas Gohr <andi@splitbrain.org> 552 * @author Michael Klier <chi@chimeric.de> 553 * @param string $text - optional text to check, if not given the globals are used 554 * @return bool - true if a spam word was found 555 */ 556function checkwordblock($text = '') { 557 global $TEXT; 558 global $PRE; 559 global $SUF; 560 global $SUM; 561 global $conf; 562 global $INFO; 563 564 if(!$conf['usewordblock']) return false; 565 566 if(!$text) $text = "$PRE $TEXT $SUF $SUM"; 567 568 // we prepare the text a tiny bit to prevent spammers circumventing URL checks 569 $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i', '\1http://\2 \2\3', $text); 570 571 $wordblocks = getWordblocks(); 572 // how many lines to read at once (to work around some PCRE limits) 573 if(version_compare(phpversion(), '4.3.0', '<')) { 574 // old versions of PCRE define a maximum of parenthesises even if no 575 // backreferences are used - the maximum is 99 576 // this is very bad performancewise and may even be too high still 577 $chunksize = 40; 578 } else { 579 // read file in chunks of 200 - this should work around the 580 // MAX_PATTERN_SIZE in modern PCRE 581 $chunksize = 200; 582 } 583 while($blocks = array_splice($wordblocks, 0, $chunksize)) { 584 $re = array(); 585 // build regexp from blocks 586 foreach($blocks as $block) { 587 $block = preg_replace('/#.*$/', '', $block); 588 $block = trim($block); 589 if(empty($block)) continue; 590 $re[] = $block; 591 } 592 if(count($re) && preg_match('#('.join('|', $re).')#si', $text, $matches)) { 593 // prepare event data 594 $data['matches'] = $matches; 595 $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR']; 596 if($_SERVER['REMOTE_USER']) { 597 $data['userinfo']['user'] = $_SERVER['REMOTE_USER']; 598 $data['userinfo']['name'] = $INFO['userinfo']['name']; 599 $data['userinfo']['mail'] = $INFO['userinfo']['mail']; 600 } 601 $callback = create_function('', 'return true;'); 602 return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true); 603 } 604 } 605 return false; 606} 607 608/** 609 * Return the IP of the client 610 * 611 * Honours X-Forwarded-For and X-Real-IP Proxy Headers 612 * 613 * It returns a comma separated list of IPs if the above mentioned 614 * headers are set. If the single parameter is set, it tries to return 615 * a routable public address, prefering the ones suplied in the X 616 * headers 617 * 618 * @author Andreas Gohr <andi@splitbrain.org> 619 * @param boolean $single If set only a single IP is returned 620 * @return string 621 */ 622function clientIP($single = false) { 623 $ip = array(); 624 $ip[] = $_SERVER['REMOTE_ADDR']; 625 if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) 626 $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['HTTP_X_FORWARDED_FOR']))); 627 if(!empty($_SERVER['HTTP_X_REAL_IP'])) 628 $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['HTTP_X_REAL_IP']))); 629 630 // some IPv4/v6 regexps borrowed from Feyd 631 // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479 632 $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])'; 633 $hex_digit = '[A-Fa-f0-9]'; 634 $h16 = "{$hex_digit}{1,4}"; 635 $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet"; 636 $ls32 = "(?:$h16:$h16|$IPv4Address)"; 637 $IPv6Address = 638 "(?:(?:{$IPv4Address})|(?:". 639 "(?:$h16:){6}$ls32". 640 "|::(?:$h16:){5}$ls32". 641 "|(?:$h16)?::(?:$h16:){4}$ls32". 642 "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32". 643 "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32". 644 "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32". 645 "|(?:(?:$h16:){0,4}$h16)?::$ls32". 646 "|(?:(?:$h16:){0,5}$h16)?::$h16". 647 "|(?:(?:$h16:){0,6}$h16)?::". 648 ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)"; 649 650 // remove any non-IP stuff 651 $cnt = count($ip); 652 $match = array(); 653 for($i = 0; $i < $cnt; $i++) { 654 if(preg_match("/^$IPv4Address$/", $ip[$i], $match) || preg_match("/^$IPv6Address$/", $ip[$i], $match)) { 655 $ip[$i] = $match[0]; 656 } else { 657 $ip[$i] = ''; 658 } 659 if(empty($ip[$i])) unset($ip[$i]); 660 } 661 $ip = array_values(array_unique($ip)); 662 if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP 663 664 if(!$single) return join(',', $ip); 665 666 // decide which IP to use, trying to avoid local addresses 667 $ip = array_reverse($ip); 668 foreach($ip as $i) { 669 if(preg_match('/^(::1|[fF][eE]80:|127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/', $i)) { 670 continue; 671 } else { 672 return $i; 673 } 674 } 675 // still here? just use the first (last) address 676 return $ip[0]; 677} 678 679/** 680 * Check if the browser is on a mobile device 681 * 682 * Adapted from the example code at url below 683 * 684 * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code 685 */ 686function clientismobile() { 687 688 if(isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true; 689 690 if(preg_match('/wap\.|\.wap/i', $_SERVER['HTTP_ACCEPT'])) return true; 691 692 if(!isset($_SERVER['HTTP_USER_AGENT'])) return false; 693 694 $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'; 695 696 if(preg_match("/$uamatches/i", $_SERVER['HTTP_USER_AGENT'])) return true; 697 698 return false; 699} 700 701/** 702 * Convert one or more comma separated IPs to hostnames 703 * 704 * If $conf['dnslookups'] is disabled it simply returns the input string 705 * 706 * @author Glen Harris <astfgl@iamnota.org> 707 * @param string $ips comma separated list of IP addresses 708 * @return string a comma separated list of hostnames 709 */ 710function gethostsbyaddrs($ips) { 711 global $conf; 712 if(!$conf['dnslookups']) return $ips; 713 714 $hosts = array(); 715 $ips = explode(',', $ips); 716 717 if(is_array($ips)) { 718 foreach($ips as $ip) { 719 $hosts[] = gethostbyaddr(trim($ip)); 720 } 721 return join(',', $hosts); 722 } else { 723 return gethostbyaddr(trim($ips)); 724 } 725} 726 727/** 728 * Checks if a given page is currently locked. 729 * 730 * removes stale lockfiles 731 * 732 * @author Andreas Gohr <andi@splitbrain.org> 733 */ 734function checklock($id) { 735 global $conf; 736 $lock = wikiLockFN($id); 737 738 //no lockfile 739 if(!@file_exists($lock)) return false; 740 741 //lockfile expired 742 if((time() - filemtime($lock)) > $conf['locktime']) { 743 @unlink($lock); 744 return false; 745 } 746 747 //my own lock 748 list($ip, $session) = explode("\n", io_readFile($lock)); 749 if($ip == $_SERVER['REMOTE_USER'] || $ip == clientIP() || $session == session_id()) { 750 return false; 751 } 752 753 return $ip; 754} 755 756/** 757 * Lock a page for editing 758 * 759 * @author Andreas Gohr <andi@splitbrain.org> 760 */ 761function lock($id) { 762 global $conf; 763 764 if($conf['locktime'] == 0) { 765 return; 766 } 767 768 $lock = wikiLockFN($id); 769 if($_SERVER['REMOTE_USER']) { 770 io_saveFile($lock, $_SERVER['REMOTE_USER']); 771 } else { 772 io_saveFile($lock, clientIP()."\n".session_id()); 773 } 774} 775 776/** 777 * Unlock a page if it was locked by the user 778 * 779 * @author Andreas Gohr <andi@splitbrain.org> 780 * @param string $id page id to unlock 781 * @return bool true if a lock was removed 782 */ 783function unlock($id) { 784 $lock = wikiLockFN($id); 785 if(@file_exists($lock)) { 786 list($ip, $session) = explode("\n", io_readFile($lock)); 787 if($ip == $_SERVER['REMOTE_USER'] || $ip == clientIP() || $session == session_id()) { 788 @unlink($lock); 789 return true; 790 } 791 } 792 return false; 793} 794 795/** 796 * convert line ending to unix format 797 * 798 * also makes sure the given text is valid UTF-8 799 * 800 * @see formText() for 2crlf conversion 801 * @author Andreas Gohr <andi@splitbrain.org> 802 */ 803function cleanText($text) { 804 $text = preg_replace("/(\015\012)|(\015)/", "\012", $text); 805 806 // if the text is not valid UTF-8 we simply assume latin1 807 // this won't break any worse than it breaks with the wrong encoding 808 // but might actually fix the problem in many cases 809 if(!utf8_check($text)) $text = utf8_encode($text); 810 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 * Return the human readable size of a file 1205 * 1206 * @param int $size A file size 1207 * @param int $dec A number of decimal places 1208 * @author Martin Benjamin <b.martin@cybernet.ch> 1209 * @author Aidan Lister <aidan@php.net> 1210 * @version 1.0.0 1211 */ 1212function filesize_h($size, $dec = 1) { 1213 $sizes = array('B', 'KB', 'MB', 'GB'); 1214 $count = count($sizes); 1215 $i = 0; 1216 1217 while($size >= 1024 && ($i < $count - 1)) { 1218 $size /= 1024; 1219 $i++; 1220 } 1221 1222 return round($size, $dec).' '.$sizes[$i]; 1223} 1224 1225/** 1226 * Return the given timestamp as human readable, fuzzy age 1227 * 1228 * @author Andreas Gohr <gohr@cosmocode.de> 1229 */ 1230function datetime_h($dt) { 1231 global $lang; 1232 1233 $ago = time() - $dt; 1234 if($ago > 24 * 60 * 60 * 30 * 12 * 2) { 1235 return sprintf($lang['years'], round($ago / (24 * 60 * 60 * 30 * 12))); 1236 } 1237 if($ago > 24 * 60 * 60 * 30 * 2) { 1238 return sprintf($lang['months'], round($ago / (24 * 60 * 60 * 30))); 1239 } 1240 if($ago > 24 * 60 * 60 * 7 * 2) { 1241 return sprintf($lang['weeks'], round($ago / (24 * 60 * 60 * 7))); 1242 } 1243 if($ago > 24 * 60 * 60 * 2) { 1244 return sprintf($lang['days'], round($ago / (24 * 60 * 60))); 1245 } 1246 if($ago > 60 * 60 * 2) { 1247 return sprintf($lang['hours'], round($ago / (60 * 60))); 1248 } 1249 if($ago > 60 * 2) { 1250 return sprintf($lang['minutes'], round($ago / (60))); 1251 } 1252 return sprintf($lang['seconds'], $ago); 1253} 1254 1255/** 1256 * Wraps around strftime but provides support for fuzzy dates 1257 * 1258 * The format default to $conf['dformat']. It is passed to 1259 * strftime - %f can be used to get the value from datetime_h() 1260 * 1261 * @see datetime_h 1262 * @author Andreas Gohr <gohr@cosmocode.de> 1263 */ 1264function dformat($dt = null, $format = '') { 1265 global $conf; 1266 1267 if(is_null($dt)) $dt = time(); 1268 $dt = (int) $dt; 1269 if(!$format) $format = $conf['dformat']; 1270 1271 $format = str_replace('%f', datetime_h($dt), $format); 1272 return strftime($format, $dt); 1273} 1274 1275/** 1276 * Formats a timestamp as ISO 8601 date 1277 * 1278 * @author <ungu at terong dot com> 1279 * @link http://www.php.net/manual/en/function.date.php#54072 1280 * @param int $int_date: current date in UNIX timestamp 1281 * @return string 1282 */ 1283function date_iso8601($int_date) { 1284 $date_mod = date('Y-m-d\TH:i:s', $int_date); 1285 $pre_timezone = date('O', $int_date); 1286 $time_zone = substr($pre_timezone, 0, 3).":".substr($pre_timezone, 3, 2); 1287 $date_mod .= $time_zone; 1288 return $date_mod; 1289} 1290 1291/** 1292 * return an obfuscated email address in line with $conf['mailguard'] setting 1293 * 1294 * @author Harry Fuecks <hfuecks@gmail.com> 1295 * @author Christopher Smith <chris@jalakai.co.uk> 1296 */ 1297function obfuscate($email) { 1298 global $conf; 1299 1300 switch($conf['mailguard']) { 1301 case 'visible' : 1302 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 1303 return strtr($email, $obfuscate); 1304 1305 case 'hex' : 1306 $encode = ''; 1307 $len = strlen($email); 1308 for($x = 0; $x < $len; $x++) { 1309 $encode .= '&#x'.bin2hex($email{$x}).';'; 1310 } 1311 return $encode; 1312 1313 case 'none' : 1314 default : 1315 return $email; 1316 } 1317} 1318 1319/** 1320 * Removes quoting backslashes 1321 * 1322 * @author Andreas Gohr <andi@splitbrain.org> 1323 */ 1324function unslash($string, $char = "'") { 1325 return str_replace('\\'.$char, $char, $string); 1326} 1327 1328/** 1329 * Convert php.ini shorthands to byte 1330 * 1331 * @author <gilthans dot NO dot SPAM at gmail dot com> 1332 * @link http://de3.php.net/manual/en/ini.core.php#79564 1333 */ 1334function php_to_byte($v) { 1335 $l = substr($v, -1); 1336 $ret = substr($v, 0, -1); 1337 switch(strtoupper($l)) { 1338 case 'P': 1339 $ret *= 1024; 1340 case 'T': 1341 $ret *= 1024; 1342 case 'G': 1343 $ret *= 1024; 1344 case 'M': 1345 $ret *= 1024; 1346 case 'K': 1347 $ret *= 1024; 1348 break; 1349 default; 1350 $ret *= 10; 1351 break; 1352 } 1353 return $ret; 1354} 1355 1356/** 1357 * Wrapper around preg_quote adding the default delimiter 1358 */ 1359function preg_quote_cb($string) { 1360 return preg_quote($string, '/'); 1361} 1362 1363/** 1364 * Shorten a given string by removing data from the middle 1365 * 1366 * You can give the string in two parts, the first part $keep 1367 * will never be shortened. The second part $short will be cut 1368 * in the middle to shorten but only if at least $min chars are 1369 * left to display it. Otherwise it will be left off. 1370 * 1371 * @param string $keep the part to keep 1372 * @param string $short the part to shorten 1373 * @param int $max maximum chars you want for the whole string 1374 * @param int $min minimum number of chars to have left for middle shortening 1375 * @param string $char the shortening character to use 1376 * @return string 1377 */ 1378function shorten($keep, $short, $max, $min = 9, $char = '…') { 1379 $max = $max - utf8_strlen($keep); 1380 if($max < $min) return $keep; 1381 $len = utf8_strlen($short); 1382 if($len <= $max) return $keep.$short; 1383 $half = floor($max / 2); 1384 return $keep.utf8_substr($short, 0, $half - 1).$char.utf8_substr($short, $len - $half); 1385} 1386 1387/** 1388 * Return the users realname or e-mail address for use 1389 * in page footer and recent changes pages 1390 * 1391 * @author Andy Webber <dokuwiki AT andywebber DOT com> 1392 */ 1393function editorinfo($username) { 1394 global $conf; 1395 global $auth; 1396 1397 switch($conf['showuseras']) { 1398 case 'username': 1399 case 'email': 1400 case 'email_link': 1401 if($auth) $info = $auth->getUserData($username); 1402 break; 1403 default: 1404 return hsc($username); 1405 } 1406 1407 if(isset($info) && $info) { 1408 switch($conf['showuseras']) { 1409 case 'username': 1410 return hsc($info['name']); 1411 case 'email': 1412 return obfuscate($info['mail']); 1413 case 'email_link': 1414 $mail = obfuscate($info['mail']); 1415 return '<a href="mailto:'.$mail.'">'.$mail.'</a>'; 1416 default: 1417 return hsc($username); 1418 } 1419 } else { 1420 return hsc($username); 1421 } 1422} 1423 1424/** 1425 * Returns the path to a image file for the currently chosen license. 1426 * When no image exists, returns an empty string 1427 * 1428 * @author Andreas Gohr <andi@splitbrain.org> 1429 * @param string $type - type of image 'badge' or 'button' 1430 * @return string 1431 */ 1432function license_img($type) { 1433 global $license; 1434 global $conf; 1435 if(!$conf['license']) return ''; 1436 if(!is_array($license[$conf['license']])) return ''; 1437 $lic = $license[$conf['license']]; 1438 $try = array(); 1439 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png'; 1440 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif'; 1441 if(substr($conf['license'], 0, 3) == 'cc-') { 1442 $try[] = 'lib/images/license/'.$type.'/cc.png'; 1443 } 1444 foreach($try as $src) { 1445 if(@file_exists(DOKU_INC.$src)) return $src; 1446 } 1447 return ''; 1448} 1449 1450/** 1451 * Checks if the given amount of memory is available 1452 * 1453 * If the memory_get_usage() function is not available the 1454 * function just assumes $bytes of already allocated memory 1455 * 1456 * @author Filip Oscadal <webmaster@illusionsoftworks.cz> 1457 * @author Andreas Gohr <andi@splitbrain.org> 1458 * 1459 * @param int $mem Size of memory you want to allocate in bytes 1460 * @param int $bytes 1461 * @internal param int $used already allocated memory (see above) 1462 * @return bool 1463 */ 1464function is_mem_available($mem, $bytes = 1048576) { 1465 $limit = trim(ini_get('memory_limit')); 1466 if(empty($limit)) return true; // no limit set! 1467 1468 // parse limit to bytes 1469 $limit = php_to_byte($limit); 1470 1471 // get used memory if possible 1472 if(function_exists('memory_get_usage')) { 1473 $used = memory_get_usage(); 1474 } else { 1475 $used = $bytes; 1476 } 1477 1478 if($used + $mem > $limit) { 1479 return false; 1480 } 1481 1482 return true; 1483} 1484 1485/** 1486 * Send a HTTP redirect to the browser 1487 * 1488 * Works arround Microsoft IIS cookie sending bug. Exits the script. 1489 * 1490 * @link http://support.microsoft.com/kb/q176113/ 1491 * @author Andreas Gohr <andi@splitbrain.org> 1492 */ 1493function send_redirect($url) { 1494 //are there any undisplayed messages? keep them in session for display 1495 global $MSG; 1496 if(isset($MSG) && count($MSG) && !defined('NOSESSION')) { 1497 //reopen session, store data and close session again 1498 @session_start(); 1499 $_SESSION[DOKU_COOKIE]['msg'] = $MSG; 1500 } 1501 1502 // always close the session 1503 session_write_close(); 1504 1505 // work around IE bug 1506 // http://www.ianhoar.com/2008/11/16/internet-explorer-6-and-redirected-anchor-links/ 1507 list($url, $hash) = explode('#', $url); 1508 if($hash) { 1509 if(strpos($url, '?')) { 1510 $url = $url.'&#'.$hash; 1511 } else { 1512 $url = $url.'?&#'.$hash; 1513 } 1514 } 1515 1516 // check if running on IIS < 6 with CGI-PHP 1517 if(isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) && 1518 (strpos($_SERVER['GATEWAY_INTERFACE'], 'CGI') !== false) && 1519 (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) && 1520 $matches[1] < 6 1521 ) { 1522 header('Refresh: 0;url='.$url); 1523 } else { 1524 header('Location: '.$url); 1525 } 1526 exit; 1527} 1528 1529/** 1530 * Validate a value using a set of valid values 1531 * 1532 * This function checks whether a specified value is set and in the array 1533 * $valid_values. If not, the function returns a default value or, if no 1534 * default is specified, throws an exception. 1535 * 1536 * @param string $param The name of the parameter 1537 * @param array $valid_values A set of valid values; Optionally a default may 1538 * be marked by the key “default”. 1539 * @param array $array The array containing the value (typically $_POST 1540 * or $_GET) 1541 * @param string $exc The text of the raised exception 1542 * 1543 * @throws Exception 1544 * @return mixed 1545 * @author Adrian Lang <lang@cosmocode.de> 1546 */ 1547function valid_input_set($param, $valid_values, $array, $exc = '') { 1548 if(isset($array[$param]) && in_array($array[$param], $valid_values)) { 1549 return $array[$param]; 1550 } elseif(isset($valid_values['default'])) { 1551 return $valid_values['default']; 1552 } else { 1553 throw new Exception($exc); 1554 } 1555} 1556 1557/** 1558 * Read a preference from the DokuWiki cookie 1559 * (remembering both keys & values are urlencoded) 1560 */ 1561function get_doku_pref($pref, $default) { 1562 $enc_pref = urlencode($pref); 1563 if(strpos($_COOKIE['DOKU_PREFS'], $enc_pref) !== false) { 1564 $parts = explode('#', $_COOKIE['DOKU_PREFS']); 1565 $cnt = count($parts); 1566 for($i = 0; $i < $cnt; $i += 2) { 1567 if($parts[$i] == $enc_pref) { 1568 return urldecode($parts[$i + 1]); 1569 } 1570 } 1571 } 1572 return $default; 1573} 1574 1575/** 1576 * Add a preference to the DokuWiki cookie 1577 * (remembering $_COOKIE['DOKU_PREFS'] is urlencoded) 1578 */ 1579function set_doku_pref($pref, $val) { 1580 global $conf; 1581 $orig = get_doku_pref($pref, false); 1582 $cookieVal = ''; 1583 1584 if($orig && ($orig != $val)) { 1585 $parts = explode('#', $_COOKIE['DOKU_PREFS']); 1586 $cnt = count($parts); 1587 // urlencode $pref for the comparison 1588 $enc_pref = rawurlencode($pref); 1589 for($i = 0; $i < $cnt; $i += 2) { 1590 if($parts[$i] == $enc_pref) { 1591 $parts[$i + 1] = rawurlencode($val); 1592 break; 1593 } 1594 } 1595 $cookieVal = implode('#', $parts); 1596 } else if (!$orig) { 1597 $cookieVal = ($_COOKIE['DOKU_PREFS'] ? $_COOKIE['DOKU_PREFS'].'#' : '').rawurlencode($pref).'#'.rawurlencode($val); 1598 } 1599 1600 if (!empty($cookieVal)) { 1601 setcookie('DOKU_PREFS', $cookieVal, time()+365*24*3600, DOKU_BASE, '', ($conf['securecookie'] && is_ssl())); 1602 } 1603} 1604 1605//Setup VIM: ex: et ts=2 : 1606