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