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