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