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