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