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