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 $conf; 561 global $INFO; 562 563 if(!$conf['usewordblock']) return false; 564 565 if(!$text) $text = "$PRE $TEXT $SUF"; 566 567 // we prepare the text a tiny bit to prevent spammers circumventing URL checks 568 $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i', '\1http://\2 \2\3', $text); 569 570 $wordblocks = getWordblocks(); 571 // how many lines to read at once (to work around some PCRE limits) 572 if(version_compare(phpversion(), '4.3.0', '<')) { 573 // old versions of PCRE define a maximum of parenthesises even if no 574 // backreferences are used - the maximum is 99 575 // this is very bad performancewise and may even be too high still 576 $chunksize = 40; 577 } else { 578 // read file in chunks of 200 - this should work around the 579 // MAX_PATTERN_SIZE in modern PCRE 580 $chunksize = 200; 581 } 582 while($blocks = array_splice($wordblocks, 0, $chunksize)) { 583 $re = array(); 584 // build regexp from blocks 585 foreach($blocks as $block) { 586 $block = preg_replace('/#.*$/', '', $block); 587 $block = trim($block); 588 if(empty($block)) continue; 589 $re[] = $block; 590 } 591 if(count($re) && preg_match('#('.join('|', $re).')#si', $text, $matches)) { 592 // prepare event data 593 $data['matches'] = $matches; 594 $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR']; 595 if($_SERVER['REMOTE_USER']) { 596 $data['userinfo']['user'] = $_SERVER['REMOTE_USER']; 597 $data['userinfo']['name'] = $INFO['userinfo']['name']; 598 $data['userinfo']['mail'] = $INFO['userinfo']['mail']; 599 } 600 $callback = create_function('', 'return true;'); 601 return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true); 602 } 603 } 604 return false; 605} 606 607/** 608 * Return the IP of the client 609 * 610 * Honours X-Forwarded-For and X-Real-IP Proxy Headers 611 * 612 * It returns a comma separated list of IPs if the above mentioned 613 * headers are set. If the single parameter is set, it tries to return 614 * a routable public address, prefering the ones suplied in the X 615 * headers 616 * 617 * @author Andreas Gohr <andi@splitbrain.org> 618 * @param boolean $single If set only a single IP is returned 619 * @return string 620 */ 621function clientIP($single = false) { 622 $ip = array(); 623 $ip[] = $_SERVER['REMOTE_ADDR']; 624 if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) 625 $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['HTTP_X_FORWARDED_FOR']))); 626 if(!empty($_SERVER['HTTP_X_REAL_IP'])) 627 $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['HTTP_X_REAL_IP']))); 628 629 // some IPv4/v6 regexps borrowed from Feyd 630 // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479 631 $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])'; 632 $hex_digit = '[A-Fa-f0-9]'; 633 $h16 = "{$hex_digit}{1,4}"; 634 $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet"; 635 $ls32 = "(?:$h16:$h16|$IPv4Address)"; 636 $IPv6Address = 637 "(?:(?:{$IPv4Address})|(?:". 638 "(?:$h16:){6}$ls32". 639 "|::(?:$h16:){5}$ls32". 640 "|(?:$h16)?::(?:$h16:){4}$ls32". 641 "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32". 642 "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32". 643 "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32". 644 "|(?:(?:$h16:){0,4}$h16)?::$ls32". 645 "|(?:(?:$h16:){0,5}$h16)?::$h16". 646 "|(?:(?:$h16:){0,6}$h16)?::". 647 ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)"; 648 649 // remove any non-IP stuff 650 $cnt = count($ip); 651 $match = array(); 652 for($i = 0; $i < $cnt; $i++) { 653 if(preg_match("/^$IPv4Address$/", $ip[$i], $match) || preg_match("/^$IPv6Address$/", $ip[$i], $match)) { 654 $ip[$i] = $match[0]; 655 } else { 656 $ip[$i] = ''; 657 } 658 if(empty($ip[$i])) unset($ip[$i]); 659 } 660 $ip = array_values(array_unique($ip)); 661 if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP 662 663 if(!$single) return join(',', $ip); 664 665 // decide which IP to use, trying to avoid local addresses 666 $ip = array_reverse($ip); 667 foreach($ip as $i) { 668 if(preg_match('/^(::1|[fF][eE]80:|127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/', $i)) { 669 continue; 670 } else { 671 return $i; 672 } 673 } 674 // still here? just use the first (last) address 675 return $ip[0]; 676} 677 678/** 679 * Check if the browser is on a mobile device 680 * 681 * Adapted from the example code at url below 682 * 683 * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code 684 */ 685function clientismobile() { 686 687 if(isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true; 688 689 if(preg_match('/wap\.|\.wap/i', $_SERVER['HTTP_ACCEPT'])) return true; 690 691 if(!isset($_SERVER['HTTP_USER_AGENT'])) return false; 692 693 $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'; 694 695 if(preg_match("/$uamatches/i", $_SERVER['HTTP_USER_AGENT'])) return true; 696 697 return false; 698} 699 700/** 701 * Convert one or more comma separated IPs to hostnames 702 * 703 * If $conf['dnslookups'] is disabled it simply returns the input string 704 * 705 * @author Glen Harris <astfgl@iamnota.org> 706 * @param string $ips comma separated list of IP addresses 707 * @return string a comma separated list of hostnames 708 */ 709function gethostsbyaddrs($ips) { 710 global $conf; 711 if(!$conf['dnslookups']) return $ips; 712 713 $hosts = array(); 714 $ips = explode(',', $ips); 715 716 if(is_array($ips)) { 717 foreach($ips as $ip) { 718 $hosts[] = gethostbyaddr(trim($ip)); 719 } 720 return join(',', $hosts); 721 } else { 722 return gethostbyaddr(trim($ips)); 723 } 724} 725 726/** 727 * Checks if a given page is currently locked. 728 * 729 * removes stale lockfiles 730 * 731 * @author Andreas Gohr <andi@splitbrain.org> 732 */ 733function checklock($id) { 734 global $conf; 735 $lock = wikiLockFN($id); 736 737 //no lockfile 738 if(!@file_exists($lock)) return false; 739 740 //lockfile expired 741 if((time() - filemtime($lock)) > $conf['locktime']) { 742 @unlink($lock); 743 return false; 744 } 745 746 //my own lock 747 list($ip, $session) = explode("\n", io_readFile($lock)); 748 if($ip == $_SERVER['REMOTE_USER'] || $ip == clientIP() || $session == session_id()) { 749 return false; 750 } 751 752 return $ip; 753} 754 755/** 756 * Lock a page for editing 757 * 758 * @author Andreas Gohr <andi@splitbrain.org> 759 */ 760function lock($id) { 761 global $conf; 762 763 if($conf['locktime'] == 0) { 764 return; 765 } 766 767 $lock = wikiLockFN($id); 768 if($_SERVER['REMOTE_USER']) { 769 io_saveFile($lock, $_SERVER['REMOTE_USER']); 770 } else { 771 io_saveFile($lock, clientIP()."\n".session_id()); 772 } 773} 774 775/** 776 * Unlock a page if it was locked by the user 777 * 778 * @author Andreas Gohr <andi@splitbrain.org> 779 * @param string $id page id to unlock 780 * @return bool true if a lock was removed 781 */ 782function unlock($id) { 783 $lock = wikiLockFN($id); 784 if(@file_exists($lock)) { 785 list($ip, $session) = explode("\n", io_readFile($lock)); 786 if($ip == $_SERVER['REMOTE_USER'] || $ip == clientIP() || $session == session_id()) { 787 @unlink($lock); 788 return true; 789 } 790 } 791 return false; 792} 793 794/** 795 * convert line ending to unix format 796 * 797 * also makes sure the given text is valid UTF-8 798 * 799 * @see formText() for 2crlf conversion 800 * @author Andreas Gohr <andi@splitbrain.org> 801 */ 802function cleanText($text) { 803 $text = preg_replace("/(\015\012)|(\015)/", "\012", $text); 804 805 // if the text is not valid UTF-8 we simply assume latin1 806 // this won't break any worse than it breaks with the wrong encoding 807 // but might actually fix the problem in many cases 808 if(!utf8_check($text)) $text = utf8_encode($text); 809 810 return $text; 811} 812 813/** 814 * Prepares text for print in Webforms by encoding special chars. 815 * It also converts line endings to Windows format which is 816 * pseudo standard for webforms. 817 * 818 * @see cleanText() for 2unix conversion 819 * @author Andreas Gohr <andi@splitbrain.org> 820 */ 821function formText($text) { 822 $text = str_replace("\012", "\015\012", $text); 823 return htmlspecialchars($text); 824} 825 826/** 827 * Returns the specified local text in raw format 828 * 829 * @author Andreas Gohr <andi@splitbrain.org> 830 */ 831function rawLocale($id, $ext = 'txt') { 832 return io_readFile(localeFN($id, $ext)); 833} 834 835/** 836 * Returns the raw WikiText 837 * 838 * @author Andreas Gohr <andi@splitbrain.org> 839 */ 840function rawWiki($id, $rev = '') { 841 return io_readWikiPage(wikiFN($id, $rev), $id, $rev); 842} 843 844/** 845 * Returns the pagetemplate contents for the ID's namespace 846 * 847 * @triggers COMMON_PAGETPL_LOAD 848 * @author Andreas Gohr <andi@splitbrain.org> 849 */ 850function pageTemplate($id) { 851 global $conf; 852 853 if(is_array($id)) $id = $id[0]; 854 855 // prepare initial event data 856 $data = array( 857 'id' => $id, // the id of the page to be created 858 'tpl' => '', // the text used as template 859 'tplfile' => '', // the file above text was/should be loaded from 860 'doreplace' => true // should wildcard replacements be done on the text? 861 ); 862 863 $evt = new Doku_Event('COMMON_PAGETPL_LOAD', $data); 864 if($evt->advise_before(true)) { 865 // the before event might have loaded the content already 866 if(empty($data['tpl'])) { 867 // if the before event did not set a template file, try to find one 868 if(empty($data['tplfile'])) { 869 $path = dirname(wikiFN($id)); 870 if(@file_exists($path.'/_template.txt')) { 871 $data['tplfile'] = $path.'/_template.txt'; 872 } else { 873 // search upper namespaces for templates 874 $len = strlen(rtrim($conf['datadir'], '/')); 875 while(strlen($path) >= $len) { 876 if(@file_exists($path.'/__template.txt')) { 877 $data['tplfile'] = $path.'/__template.txt'; 878 break; 879 } 880 $path = substr($path, 0, strrpos($path, '/')); 881 } 882 } 883 } 884 // load the content 885 $data['tpl'] = io_readFile($data['tplfile']); 886 } 887 if($data['doreplace']) parsePageTemplate($data); 888 } 889 $evt->advise_after(); 890 unset($evt); 891 892 return $data['tpl']; 893} 894 895/** 896 * Performs common page template replacements 897 * This works on data from COMMON_PAGETPL_LOAD 898 * 899 * @author Andreas Gohr <andi@splitbrain.org> 900 */ 901function parsePageTemplate(&$data) { 902 /** 903 * @var string $id the id of the page to be created 904 * @var string $tpl the text used as template 905 * @var string $tplfile the file above text was/should be loaded from 906 * @var bool $doreplace should wildcard replacements be done on the text? 907 */ 908 extract($data); 909 910 global $USERINFO; 911 global $conf; 912 913 // replace placeholders 914 $file = noNS($id); 915 $page = strtr($file, $conf['sepchar'], ' '); 916 917 $tpl = str_replace( 918 array( 919 '@ID@', 920 '@NS@', 921 '@FILE@', 922 '@!FILE@', 923 '@!FILE!@', 924 '@PAGE@', 925 '@!PAGE@', 926 '@!!PAGE@', 927 '@!PAGE!@', 928 '@USER@', 929 '@NAME@', 930 '@MAIL@', 931 '@DATE@', 932 ), 933 array( 934 $id, 935 getNS($id), 936 $file, 937 utf8_ucfirst($file), 938 utf8_strtoupper($file), 939 $page, 940 utf8_ucfirst($page), 941 utf8_ucwords($page), 942 utf8_strtoupper($page), 943 $_SERVER['REMOTE_USER'], 944 $USERINFO['name'], 945 $USERINFO['mail'], 946 $conf['dformat'], 947 ), $tpl 948 ); 949 950 // we need the callback to work around strftime's char limit 951 $tpl = preg_replace_callback('/%./', create_function('$m', 'return strftime($m[0]);'), $tpl); 952 $data['tpl'] = $tpl; 953 return $tpl; 954} 955 956/** 957 * Returns the raw Wiki Text in three slices. 958 * 959 * The range parameter needs to have the form "from-to" 960 * and gives the range of the section in bytes - no 961 * UTF-8 awareness is needed. 962 * The returned order is prefix, section and suffix. 963 * 964 * @author Andreas Gohr <andi@splitbrain.org> 965 */ 966function rawWikiSlices($range, $id, $rev = '') { 967 $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev); 968 969 // Parse range 970 list($from, $to) = explode('-', $range, 2); 971 // Make range zero-based, use defaults if marker is missing 972 $from = !$from ? 0 : ($from - 1); 973 $to = !$to ? strlen($text) : ($to - 1); 974 975 $slices[0] = substr($text, 0, $from); 976 $slices[1] = substr($text, $from, $to - $from); 977 $slices[2] = substr($text, $to); 978 return $slices; 979} 980 981/** 982 * Joins wiki text slices 983 * 984 * function to join the text slices. 985 * When the pretty parameter is set to true it adds additional empty 986 * lines between sections if needed (used on saving). 987 * 988 * @author Andreas Gohr <andi@splitbrain.org> 989 */ 990function con($pre, $text, $suf, $pretty = false) { 991 if($pretty) { 992 if($pre !== '' && substr($pre, -1) !== "\n" && 993 substr($text, 0, 1) !== "\n" 994 ) { 995 $pre .= "\n"; 996 } 997 if($suf !== '' && substr($text, -1) !== "\n" && 998 substr($suf, 0, 1) !== "\n" 999 ) { 1000 $text .= "\n"; 1001 } 1002 } 1003 1004 return $pre.$text.$suf; 1005} 1006 1007/** 1008 * Saves a wikitext by calling io_writeWikiPage. 1009 * Also directs changelog and attic updates. 1010 * 1011 * @author Andreas Gohr <andi@splitbrain.org> 1012 * @author Ben Coburn <btcoburn@silicodon.net> 1013 */ 1014function saveWikiText($id, $text, $summary, $minor = false) { 1015 /* Note to developers: 1016 This code is subtle and delicate. Test the behavior of 1017 the attic and changelog with dokuwiki and external edits 1018 after any changes. External edits change the wiki page 1019 directly without using php or dokuwiki. 1020 */ 1021 global $conf; 1022 global $lang; 1023 global $REV; 1024 // ignore if no changes were made 1025 if($text == rawWiki($id, '')) { 1026 return; 1027 } 1028 1029 $file = wikiFN($id); 1030 $old = @filemtime($file); // from page 1031 $wasRemoved = (trim($text) == ''); // check for empty or whitespace only 1032 $wasCreated = !@file_exists($file); 1033 $wasReverted = ($REV == true); 1034 $newRev = false; 1035 $oldRev = getRevisions($id, -1, 1, 1024); // from changelog 1036 $oldRev = (int) (empty($oldRev) ? 0 : $oldRev[0]); 1037 if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old >= $oldRev) { 1038 // add old revision to the attic if missing 1039 saveOldRevision($id); 1040 // add a changelog entry if this edit came from outside dokuwiki 1041 if($old > $oldRev) { 1042 addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=> true)); 1043 // remove soon to be stale instructions 1044 $cache = new cache_instructions($id, $file); 1045 $cache->removeCache(); 1046 } 1047 } 1048 1049 if($wasRemoved) { 1050 // Send "update" event with empty data, so plugins can react to page deletion 1051 $data = array(array($file, '', false), getNS($id), noNS($id), false); 1052 trigger_event('IO_WIKIPAGE_WRITE', $data); 1053 // pre-save deleted revision 1054 @touch($file); 1055 clearstatcache(); 1056 $newRev = saveOldRevision($id); 1057 // remove empty file 1058 @unlink($file); 1059 // don't remove old meta info as it should be saved, plugins can use IO_WIKIPAGE_WRITE for removing their metadata... 1060 // purge non-persistant meta data 1061 p_purge_metadata($id); 1062 $del = true; 1063 // autoset summary on deletion 1064 if(empty($summary)) $summary = $lang['deleted']; 1065 // remove empty namespaces 1066 io_sweepNS($id, 'datadir'); 1067 io_sweepNS($id, 'mediadir'); 1068 } else { 1069 // save file (namespace dir is created in io_writeWikiPage) 1070 io_writeWikiPage($file, $text, $id); 1071 // pre-save the revision, to keep the attic in sync 1072 $newRev = saveOldRevision($id); 1073 $del = false; 1074 } 1075 1076 // select changelog line type 1077 $extra = ''; 1078 $type = DOKU_CHANGE_TYPE_EDIT; 1079 if($wasReverted) { 1080 $type = DOKU_CHANGE_TYPE_REVERT; 1081 $extra = $REV; 1082 } else if($wasCreated) { 1083 $type = DOKU_CHANGE_TYPE_CREATE; 1084 } else if($wasRemoved) { 1085 $type = DOKU_CHANGE_TYPE_DELETE; 1086 } else if($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { 1087 $type = DOKU_CHANGE_TYPE_MINOR_EDIT; 1088 } //minor edits only for logged in users 1089 1090 addLogEntry($newRev, $id, $type, $summary, $extra); 1091 // send notify mails 1092 notify($id, 'admin', $old, $summary, $minor); 1093 notify($id, 'subscribers', $old, $summary, $minor); 1094 1095 // update the purgefile (timestamp of the last time anything within the wiki was changed) 1096 io_saveFile($conf['cachedir'].'/purgefile', time()); 1097 1098 // if useheading is enabled, purge the cache of all linking pages 1099 if(useHeading('content')) { 1100 $pages = ft_backlinks($id); 1101 foreach($pages as $page) { 1102 $cache = new cache_renderer($page, wikiFN($page), 'xhtml'); 1103 $cache->removeCache(); 1104 } 1105 } 1106} 1107 1108/** 1109 * moves the current version to the attic and returns its 1110 * revision date 1111 * 1112 * @author Andreas Gohr <andi@splitbrain.org> 1113 */ 1114function saveOldRevision($id) { 1115 global $conf; 1116 $oldf = wikiFN($id); 1117 if(!@file_exists($oldf)) return ''; 1118 $date = filemtime($oldf); 1119 $newf = wikiFN($id, $date); 1120 io_writeWikiPage($newf, rawWiki($id), $id, $date); 1121 return $date; 1122} 1123 1124/** 1125 * Sends a notify mail on page change or registration 1126 * 1127 * @param string $id The changed page 1128 * @param string $who Who to notify (admin|subscribers|register) 1129 * @param int|string $rev Old page revision 1130 * @param string $summary What changed 1131 * @param boolean $minor Is this a minor edit? 1132 * @param array $replace Additional string substitutions, @KEY@ to be replaced by value 1133 * 1134 * @return bool 1135 * @author Andreas Gohr <andi@splitbrain.org> 1136 */ 1137function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = array()) { 1138 global $conf; 1139 1140 // decide if there is something to do, eg. whom to mail 1141 if($who == 'admin') { 1142 if(empty($conf['notify'])) return false; //notify enabled? 1143 $tpl = 'mailtext'; 1144 $to = $conf['notify']; 1145 } elseif($who == 'subscribers') { 1146 if(!actionOK('subscribe')) return false; //subscribers enabled? 1147 if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return false; //skip minors 1148 $data = array('id' => $id, 'addresslist' => '', 'self' => false); 1149 trigger_event( 1150 'COMMON_NOTIFY_ADDRESSLIST', $data, 1151 array(new Subscription(), 'notifyaddresses') 1152 ); 1153 $to = $data['addresslist']; 1154 if(empty($to)) return false; 1155 $tpl = 'subscr_single'; 1156 } else { 1157 return false; //just to be safe 1158 } 1159 1160 // prepare content 1161 $subscription = new Subscription(); 1162 return $subscription->send_diff($to, $tpl, $id, $rev, $summary); 1163} 1164 1165/** 1166 * extracts the query from a search engine referrer 1167 * 1168 * @author Andreas Gohr <andi@splitbrain.org> 1169 * @author Todd Augsburger <todd@rollerorgans.com> 1170 */ 1171function getGoogleQuery() { 1172 if(!isset($_SERVER['HTTP_REFERER'])) { 1173 return ''; 1174 } 1175 $url = parse_url($_SERVER['HTTP_REFERER']); 1176 1177 // only handle common SEs 1178 if(!preg_match('/(google|bing|yahoo|ask|duckduckgo|babylon|aol|yandex)/',$url['host'])) return ''; 1179 1180 $query = array(); 1181 // temporary workaround against PHP bug #49733 1182 // see http://bugs.php.net/bug.php?id=49733 1183 if(UTF8_MBSTRING) $enc = mb_internal_encoding(); 1184 parse_str($url['query'], $query); 1185 if(UTF8_MBSTRING) mb_internal_encoding($enc); 1186 1187 $q = ''; 1188 if(isset($query['q'])){ 1189 $q = $query['q']; 1190 }elseif(isset($query['p'])){ 1191 $q = $query['p']; 1192 }elseif(isset($query['query'])){ 1193 $q = $query['query']; 1194 } 1195 $q = trim($q); 1196 1197 if(!$q) return ''; 1198 $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/', $q, -1, PREG_SPLIT_NO_EMPTY); 1199 return $q; 1200} 1201 1202/** 1203 * Return the human readable size of a file 1204 * 1205 * @param int $size A file size 1206 * @param int $dec A number of decimal places 1207 * @author Martin Benjamin <b.martin@cybernet.ch> 1208 * @author Aidan Lister <aidan@php.net> 1209 * @version 1.0.0 1210 */ 1211function filesize_h($size, $dec = 1) { 1212 $sizes = array('B', 'KB', 'MB', 'GB'); 1213 $count = count($sizes); 1214 $i = 0; 1215 1216 while($size >= 1024 && ($i < $count - 1)) { 1217 $size /= 1024; 1218 $i++; 1219 } 1220 1221 return round($size, $dec).' '.$sizes[$i]; 1222} 1223 1224/** 1225 * Return the given timestamp as human readable, fuzzy age 1226 * 1227 * @author Andreas Gohr <gohr@cosmocode.de> 1228 */ 1229function datetime_h($dt) { 1230 global $lang; 1231 1232 $ago = time() - $dt; 1233 if($ago > 24 * 60 * 60 * 30 * 12 * 2) { 1234 return sprintf($lang['years'], round($ago / (24 * 60 * 60 * 30 * 12))); 1235 } 1236 if($ago > 24 * 60 * 60 * 30 * 2) { 1237 return sprintf($lang['months'], round($ago / (24 * 60 * 60 * 30))); 1238 } 1239 if($ago > 24 * 60 * 60 * 7 * 2) { 1240 return sprintf($lang['weeks'], round($ago / (24 * 60 * 60 * 7))); 1241 } 1242 if($ago > 24 * 60 * 60 * 2) { 1243 return sprintf($lang['days'], round($ago / (24 * 60 * 60))); 1244 } 1245 if($ago > 60 * 60 * 2) { 1246 return sprintf($lang['hours'], round($ago / (60 * 60))); 1247 } 1248 if($ago > 60 * 2) { 1249 return sprintf($lang['minutes'], round($ago / (60))); 1250 } 1251 return sprintf($lang['seconds'], $ago); 1252} 1253 1254/** 1255 * Wraps around strftime but provides support for fuzzy dates 1256 * 1257 * The format default to $conf['dformat']. It is passed to 1258 * strftime - %f can be used to get the value from datetime_h() 1259 * 1260 * @see datetime_h 1261 * @author Andreas Gohr <gohr@cosmocode.de> 1262 */ 1263function dformat($dt = null, $format = '') { 1264 global $conf; 1265 1266 if(is_null($dt)) $dt = time(); 1267 $dt = (int) $dt; 1268 if(!$format) $format = $conf['dformat']; 1269 1270 $format = str_replace('%f', datetime_h($dt), $format); 1271 return strftime($format, $dt); 1272} 1273 1274/** 1275 * Formats a timestamp as ISO 8601 date 1276 * 1277 * @author <ungu at terong dot com> 1278 * @link http://www.php.net/manual/en/function.date.php#54072 1279 * @param int $int_date: current date in UNIX timestamp 1280 * @return string 1281 */ 1282function date_iso8601($int_date) { 1283 $date_mod = date('Y-m-d\TH:i:s', $int_date); 1284 $pre_timezone = date('O', $int_date); 1285 $time_zone = substr($pre_timezone, 0, 3).":".substr($pre_timezone, 3, 2); 1286 $date_mod .= $time_zone; 1287 return $date_mod; 1288} 1289 1290/** 1291 * return an obfuscated email address in line with $conf['mailguard'] setting 1292 * 1293 * @author Harry Fuecks <hfuecks@gmail.com> 1294 * @author Christopher Smith <chris@jalakai.co.uk> 1295 */ 1296function obfuscate($email) { 1297 global $conf; 1298 1299 switch($conf['mailguard']) { 1300 case 'visible' : 1301 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 1302 return strtr($email, $obfuscate); 1303 1304 case 'hex' : 1305 $encode = ''; 1306 $len = strlen($email); 1307 for($x = 0; $x < $len; $x++) { 1308 $encode .= '&#x'.bin2hex($email{$x}).';'; 1309 } 1310 return $encode; 1311 1312 case 'none' : 1313 default : 1314 return $email; 1315 } 1316} 1317 1318/** 1319 * Removes quoting backslashes 1320 * 1321 * @author Andreas Gohr <andi@splitbrain.org> 1322 */ 1323function unslash($string, $char = "'") { 1324 return str_replace('\\'.$char, $char, $string); 1325} 1326 1327/** 1328 * Convert php.ini shorthands to byte 1329 * 1330 * @author <gilthans dot NO dot SPAM at gmail dot com> 1331 * @link http://de3.php.net/manual/en/ini.core.php#79564 1332 */ 1333function php_to_byte($v) { 1334 $l = substr($v, -1); 1335 $ret = substr($v, 0, -1); 1336 switch(strtoupper($l)) { 1337 case 'P': 1338 $ret *= 1024; 1339 case 'T': 1340 $ret *= 1024; 1341 case 'G': 1342 $ret *= 1024; 1343 case 'M': 1344 $ret *= 1024; 1345 case 'K': 1346 $ret *= 1024; 1347 break; 1348 default; 1349 $ret *= 10; 1350 break; 1351 } 1352 return $ret; 1353} 1354 1355/** 1356 * Wrapper around preg_quote adding the default delimiter 1357 */ 1358function preg_quote_cb($string) { 1359 return preg_quote($string, '/'); 1360} 1361 1362/** 1363 * Shorten a given string by removing data from the middle 1364 * 1365 * You can give the string in two parts, the first part $keep 1366 * will never be shortened. The second part $short will be cut 1367 * in the middle to shorten but only if at least $min chars are 1368 * left to display it. Otherwise it will be left off. 1369 * 1370 * @param string $keep the part to keep 1371 * @param string $short the part to shorten 1372 * @param int $max maximum chars you want for the whole string 1373 * @param int $min minimum number of chars to have left for middle shortening 1374 * @param string $char the shortening character to use 1375 * @return string 1376 */ 1377function shorten($keep, $short, $max, $min = 9, $char = '…') { 1378 $max = $max - utf8_strlen($keep); 1379 if($max < $min) return $keep; 1380 $len = utf8_strlen($short); 1381 if($len <= $max) return $keep.$short; 1382 $half = floor($max / 2); 1383 return $keep.utf8_substr($short, 0, $half - 1).$char.utf8_substr($short, $len - $half); 1384} 1385 1386/** 1387 * Return the users realname or e-mail address for use 1388 * in page footer and recent changes pages 1389 * 1390 * @author Andy Webber <dokuwiki AT andywebber DOT com> 1391 */ 1392function editorinfo($username) { 1393 global $conf; 1394 global $auth; 1395 1396 switch($conf['showuseras']) { 1397 case 'username': 1398 case 'email': 1399 case 'email_link': 1400 if($auth) $info = $auth->getUserData($username); 1401 break; 1402 default: 1403 return hsc($username); 1404 } 1405 1406 if(isset($info) && $info) { 1407 switch($conf['showuseras']) { 1408 case 'username': 1409 return hsc($info['name']); 1410 case 'email': 1411 return obfuscate($info['mail']); 1412 case 'email_link': 1413 $mail = obfuscate($info['mail']); 1414 return '<a href="mailto:'.$mail.'">'.$mail.'</a>'; 1415 default: 1416 return hsc($username); 1417 } 1418 } else { 1419 return hsc($username); 1420 } 1421} 1422 1423/** 1424 * Returns the path to a image file for the currently chosen license. 1425 * When no image exists, returns an empty string 1426 * 1427 * @author Andreas Gohr <andi@splitbrain.org> 1428 * @param string $type - type of image 'badge' or 'button' 1429 * @return string 1430 */ 1431function license_img($type) { 1432 global $license; 1433 global $conf; 1434 if(!$conf['license']) return ''; 1435 if(!is_array($license[$conf['license']])) return ''; 1436 $lic = $license[$conf['license']]; 1437 $try = array(); 1438 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png'; 1439 $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif'; 1440 if(substr($conf['license'], 0, 3) == 'cc-') { 1441 $try[] = 'lib/images/license/'.$type.'/cc.png'; 1442 } 1443 foreach($try as $src) { 1444 if(@file_exists(DOKU_INC.$src)) return $src; 1445 } 1446 return ''; 1447} 1448 1449/** 1450 * Checks if the given amount of memory is available 1451 * 1452 * If the memory_get_usage() function is not available the 1453 * function just assumes $bytes of already allocated memory 1454 * 1455 * @author Filip Oscadal <webmaster@illusionsoftworks.cz> 1456 * @author Andreas Gohr <andi@splitbrain.org> 1457 * 1458 * @param int $mem Size of memory you want to allocate in bytes 1459 * @param int $bytes 1460 * @internal param int $used already allocated memory (see above) 1461 * @return bool 1462 */ 1463function is_mem_available($mem, $bytes = 1048576) { 1464 $limit = trim(ini_get('memory_limit')); 1465 if(empty($limit)) return true; // no limit set! 1466 1467 // parse limit to bytes 1468 $limit = php_to_byte($limit); 1469 1470 // get used memory if possible 1471 if(function_exists('memory_get_usage')) { 1472 $used = memory_get_usage(); 1473 } else { 1474 $used = $bytes; 1475 } 1476 1477 if($used + $mem > $limit) { 1478 return false; 1479 } 1480 1481 return true; 1482} 1483 1484/** 1485 * Send a HTTP redirect to the browser 1486 * 1487 * Works arround Microsoft IIS cookie sending bug. Exits the script. 1488 * 1489 * @link http://support.microsoft.com/kb/q176113/ 1490 * @author Andreas Gohr <andi@splitbrain.org> 1491 */ 1492function send_redirect($url) { 1493 //are there any undisplayed messages? keep them in session for display 1494 global $MSG; 1495 if(isset($MSG) && count($MSG) && !defined('NOSESSION')) { 1496 //reopen session, store data and close session again 1497 @session_start(); 1498 $_SESSION[DOKU_COOKIE]['msg'] = $MSG; 1499 } 1500 1501 // always close the session 1502 session_write_close(); 1503 1504 // work around IE bug 1505 // http://www.ianhoar.com/2008/11/16/internet-explorer-6-and-redirected-anchor-links/ 1506 list($url, $hash) = explode('#', $url); 1507 if($hash) { 1508 if(strpos($url, '?')) { 1509 $url = $url.'&#'.$hash; 1510 } else { 1511 $url = $url.'?&#'.$hash; 1512 } 1513 } 1514 1515 // check if running on IIS < 6 with CGI-PHP 1516 if(isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) && 1517 (strpos($_SERVER['GATEWAY_INTERFACE'], 'CGI') !== false) && 1518 (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) && 1519 $matches[1] < 6 1520 ) { 1521 header('Refresh: 0;url='.$url); 1522 } else { 1523 header('Location: '.$url); 1524 } 1525 exit; 1526} 1527 1528/** 1529 * Validate a value using a set of valid values 1530 * 1531 * This function checks whether a specified value is set and in the array 1532 * $valid_values. If not, the function returns a default value or, if no 1533 * default is specified, throws an exception. 1534 * 1535 * @param string $param The name of the parameter 1536 * @param array $valid_values A set of valid values; Optionally a default may 1537 * be marked by the key “default”. 1538 * @param array $array The array containing the value (typically $_POST 1539 * or $_GET) 1540 * @param string $exc The text of the raised exception 1541 * 1542 * @throws Exception 1543 * @return mixed 1544 * @author Adrian Lang <lang@cosmocode.de> 1545 */ 1546function valid_input_set($param, $valid_values, $array, $exc = '') { 1547 if(isset($array[$param]) && in_array($array[$param], $valid_values)) { 1548 return $array[$param]; 1549 } elseif(isset($valid_values['default'])) { 1550 return $valid_values['default']; 1551 } else { 1552 throw new Exception($exc); 1553 } 1554} 1555 1556/** 1557 * Read a preference from the DokuWiki cookie 1558 * (remembering both keys & values are urlencoded) 1559 */ 1560function get_doku_pref($pref, $default) { 1561 $enc_pref = urlencode($pref); 1562 if(strpos($_COOKIE['DOKU_PREFS'], $enc_pref) !== false) { 1563 $parts = explode('#', $_COOKIE['DOKU_PREFS']); 1564 $cnt = count($parts); 1565 for($i = 0; $i < $cnt; $i += 2) { 1566 if($parts[$i] == $enc_pref) { 1567 return urldecode($parts[$i + 1]); 1568 } 1569 } 1570 } 1571 return $default; 1572} 1573 1574/** 1575 * Add a preference to the DokuWiki cookie 1576 * (remembering $_COOKIE['DOKU_PREFS'] is urlencoded) 1577 */ 1578function set_doku_pref($pref, $val) { 1579 global $conf; 1580 $orig = get_doku_pref($pref, false); 1581 $cookieVal = ''; 1582 1583 if($orig && ($orig != $val)) { 1584 $parts = explode('#', $_COOKIE['DOKU_PREFS']); 1585 $cnt = count($parts); 1586 // urlencode $pref for the comparison 1587 $enc_pref = rawurlencode($pref); 1588 for($i = 0; $i < $cnt; $i += 2) { 1589 if($parts[$i] == $enc_pref) { 1590 $parts[$i + 1] = rawurlencode($val); 1591 break; 1592 } 1593 } 1594 $cookieVal = implode('#', $parts); 1595 } else if (!$orig) { 1596 $cookieVal = ($_COOKIE['DOKU_PREFS'] ? $_COOKIE['DOKU_PREFS'].'#' : '').rawurlencode($pref).'#'.rawurlencode($val); 1597 } 1598 1599 if (!empty($cookieVal)) { 1600 setcookie('DOKU_PREFS', $cookieVal, time()+365*24*3600, DOKU_BASE, '', ($conf['securecookie'] && is_ssl())); 1601 } 1602} 1603 1604//Setup VIM: ex: et ts=2 : 1605