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')) define('DOKU_INC',fullpath(dirname(__FILE__).'/../').'/'); 10require_once(DOKU_CONF.'dokuwiki.php'); 11require_once(DOKU_INC.'inc/io.php'); 12require_once(DOKU_INC.'inc/changelog.php'); 13require_once(DOKU_INC.'inc/utf8.php'); 14require_once(DOKU_INC.'inc/mail.php'); 15require_once(DOKU_INC.'inc/parserutils.php'); 16require_once(DOKU_INC.'inc/infoutils.php'); 17 18/** 19 * These constants are used with the recents function 20 */ 21define('RECENTS_SKIP_DELETED',2); 22define('RECENTS_SKIP_MINORS',4); 23define('RECENTS_SKIP_SUBSPACES',8); 24 25/** 26 * Wrapper around htmlspecialchars() 27 * 28 * @author Andreas Gohr <andi@splitbrain.org> 29 * @see htmlspecialchars() 30 */ 31function hsc($string){ 32 return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); 33} 34 35/** 36 * print a newline terminated string 37 * 38 * You can give an indention as optional parameter 39 * 40 * @author Andreas Gohr <andi@splitbrain.org> 41 */ 42function ptln($string,$indent=0){ 43 echo str_repeat(' ', $indent)."$string\n"; 44} 45 46/** 47 * strips control characters (<32) from the given string 48 * 49 * @author Andreas Gohr <andi@splitbrain.org> 50 */ 51function stripctl($string){ 52 return preg_replace('/[\x00-\x1F]+/s','',$string); 53} 54 55/** 56 * Return a secret token to be used for CSRF attack prevention 57 * 58 * @author Andreas Gohr <andi@splitbrain.org> 59 * @link http://en.wikipedia.org/wiki/Cross-site_request_forgery 60 * @link http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html 61 * @return string 62 */ 63function getSecurityToken(){ 64 return md5(auth_cookiesalt().session_id()); 65} 66 67/** 68 * Check the secret CSRF token 69 */ 70function checkSecurityToken($token=null){ 71 if(is_null($token)) $token = $_REQUEST['sectok']; 72 if(getSecurityToken() != $token){ 73 msg('Security Token did not match. Possible CSRF attack.',-1); 74 return false; 75 } 76 return true; 77} 78 79/** 80 * Print a hidden form field with a secret CSRF token 81 * 82 * @author Andreas Gohr <andi@splitbrain.org> 83 */ 84function formSecurityToken($print=true){ 85 $ret = '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />'."\n"; 86 if($print){ 87 echo $ret; 88 }else{ 89 return $ret; 90 } 91} 92 93/** 94 * Return info about the current document as associative 95 * array. 96 * 97 * @author Andreas Gohr <andi@splitbrain.org> 98 */ 99function pageinfo(){ 100 global $ID; 101 global $REV; 102 global $USERINFO; 103 global $conf; 104 105 // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml 106 // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary 107 $info['id'] = $ID; 108 $info['rev'] = $REV; 109 110 if($_SERVER['REMOTE_USER']){ 111 $info['userinfo'] = $USERINFO; 112 $info['perm'] = auth_quickaclcheck($ID); 113 $info['subscribed'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],false); 114 $info['subscribedns'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],true); 115 $info['client'] = $_SERVER['REMOTE_USER']; 116 117 // set info about manager/admin status 118 $info['isadmin'] = false; 119 $info['ismanager'] = false; 120 if($info['perm'] == AUTH_ADMIN){ 121 $info['isadmin'] = true; 122 $info['ismanager'] = true; 123 }elseif(auth_ismanager()){ 124 $info['ismanager'] = true; 125 } 126 127 // if some outside auth were used only REMOTE_USER is set 128 if(!$info['userinfo']['name']){ 129 $info['userinfo']['name'] = $_SERVER['REMOTE_USER']; 130 } 131 132 }else{ 133 $info['perm'] = auth_aclcheck($ID,'',null); 134 $info['subscribed'] = false; 135 $info['client'] = clientIP(true); 136 } 137 138 $info['namespace'] = getNS($ID); 139 $info['locked'] = checklock($ID); 140 $info['filepath'] = fullpath(wikiFN($ID)); 141 $info['exists'] = @file_exists($info['filepath']); 142 if($REV){ 143 //check if current revision was meant 144 if($info['exists'] && (@filemtime($info['filepath'])==$REV)){ 145 $REV = ''; 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['lock'])); 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 return $info; 212} 213 214/** 215 * Build an string of URL parameters 216 * 217 * @author Andreas Gohr 218 */ 219function buildURLparams($params, $sep='&'){ 220 $url = ''; 221 $amp = false; 222 foreach($params as $key => $val){ 223 if($amp) $url .= $sep; 224 225 $url .= $key.'='; 226 $url .= rawurlencode($val); 227 $amp = true; 228 } 229 return $url; 230} 231 232/** 233 * Build an string of html tag attributes 234 * 235 * Skips keys starting with '_', values get HTML encoded 236 * 237 * @author Andreas Gohr 238 */ 239function buildAttributes($params,$skipempty=false){ 240 $url = ''; 241 foreach($params as $key => $val){ 242 if($key{0} == '_') continue; 243 if($val === '' && $skipempty) continue; 244 245 $url .= $key.'="'; 246 $url .= htmlspecialchars ($val); 247 $url .= '" '; 248 } 249 return $url; 250} 251 252 253/** 254 * This builds the breadcrumb trail and returns it as array 255 * 256 * @author Andreas Gohr <andi@splitbrain.org> 257 */ 258function breadcrumbs(){ 259 // we prepare the breadcrumbs early for quick session closing 260 static $crumbs = null; 261 if($crumbs != null) return $crumbs; 262 263 global $ID; 264 global $ACT; 265 global $conf; 266 $crumbs = $_SESSION[DOKU_COOKIE]['bc']; 267 268 //first visit? 269 if (!is_array($crumbs)){ 270 $crumbs = array(); 271 } 272 //we only save on show and existing wiki documents 273 $file = wikiFN($ID); 274 if($ACT != 'show' || !@file_exists($file)){ 275 $_SESSION[DOKU_COOKIE]['bc'] = $crumbs; 276 return $crumbs; 277 } 278 279 // page names 280 $name = noNSorNS($ID); 281 if ($conf['useheading']) { 282 // get page title 283 $title = p_get_first_heading($ID,true); 284 if ($title) { 285 $name = $title; 286 } 287 } 288 289 //remove ID from array 290 if (isset($crumbs[$ID])) { 291 unset($crumbs[$ID]); 292 } 293 294 //add to array 295 $crumbs[$ID] = $name; 296 //reduce size 297 while(count($crumbs) > $conf['breadcrumbs']){ 298 array_shift($crumbs); 299 } 300 //save to session 301 $_SESSION[DOKU_COOKIE]['bc'] = $crumbs; 302 return $crumbs; 303} 304 305/** 306 * Filter for page IDs 307 * 308 * This is run on a ID before it is outputted somewhere 309 * currently used to replace the colon with something else 310 * on Windows systems and to have proper URL encoding 311 * 312 * Urlencoding is ommitted when the second parameter is false 313 * 314 * @author Andreas Gohr <andi@splitbrain.org> 315 */ 316function idfilter($id,$ue=true){ 317 global $conf; 318 if ($conf['useslash'] && $conf['userewrite']){ 319 $id = strtr($id,':','/'); 320 }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && 321 $conf['userewrite']) { 322 $id = strtr($id,':',';'); 323 } 324 if($ue){ 325 $id = rawurlencode($id); 326 $id = str_replace('%3A',':',$id); //keep as colon 327 $id = str_replace('%2F','/',$id); //keep as slash 328 } 329 return $id; 330} 331 332/** 333 * This builds a link to a wikipage 334 * 335 * It handles URL rewriting and adds additional parameter if 336 * given in $more 337 * 338 * @author Andreas Gohr <andi@splitbrain.org> 339 */ 340function wl($id='',$more='',$abs=false,$sep='&'){ 341 global $conf; 342 if(is_array($more)){ 343 $more = buildURLparams($more,$sep); 344 }else{ 345 $more = str_replace(',',$sep,$more); 346 } 347 348 $id = idfilter($id); 349 if($abs){ 350 $xlink = DOKU_URL; 351 }else{ 352 $xlink = DOKU_BASE; 353 } 354 355 if($conf['userewrite'] == 2){ 356 $xlink .= DOKU_SCRIPT.'/'.$id; 357 if($more) $xlink .= '?'.$more; 358 }elseif($conf['userewrite']){ 359 $xlink .= $id; 360 if($more) $xlink .= '?'.$more; 361 }elseif($id){ 362 $xlink .= DOKU_SCRIPT.'?id='.$id; 363 if($more) $xlink .= $sep.$more; 364 }else{ 365 $xlink .= DOKU_SCRIPT; 366 if($more) $xlink .= '?'.$more; 367 } 368 369 return $xlink; 370} 371 372/** 373 * This builds a link to an alternate page format 374 * 375 * Handles URL rewriting if enabled. Follows the style of wl(). 376 * 377 * @author Ben Coburn <btcoburn@silicodon.net> 378 */ 379function exportlink($id='',$format='raw',$more='',$abs=false,$sep='&'){ 380 global $conf; 381 if(is_array($more)){ 382 $more = buildURLparams($more,$sep); 383 }else{ 384 $more = str_replace(',',$sep,$more); 385 } 386 387 $format = rawurlencode($format); 388 $id = idfilter($id); 389 if($abs){ 390 $xlink = DOKU_URL; 391 }else{ 392 $xlink = DOKU_BASE; 393 } 394 395 if($conf['userewrite'] == 2){ 396 $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format; 397 if($more) $xlink .= $sep.$more; 398 }elseif($conf['userewrite'] == 1){ 399 $xlink .= '_export/'.$format.'/'.$id; 400 if($more) $xlink .= '?'.$more; 401 }else{ 402 $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id; 403 if($more) $xlink .= $sep.$more; 404 } 405 406 return $xlink; 407} 408 409/** 410 * Build a link to a media file 411 * 412 * Will return a link to the detail page if $direct is false 413 */ 414function ml($id='',$more='',$direct=true,$sep='&',$abs=false){ 415 global $conf; 416 if(is_array($more)){ 417 $more = buildURLparams($more,$sep); 418 }else{ 419 $more = str_replace(',',$sep,$more); 420 } 421 422 if($abs){ 423 $xlink = DOKU_URL; 424 }else{ 425 $xlink = DOKU_BASE; 426 } 427 428 // external URLs are always direct without rewriting 429 if(preg_match('#^(https?|ftp)://#i',$id)){ 430 $xlink .= 'lib/exe/fetch.php'; 431 if($more){ 432 $xlink .= '?'.$more; 433 $xlink .= $sep.'media='.rawurlencode($id); 434 }else{ 435 $xlink .= '?media='.rawurlencode($id); 436 } 437 return $xlink; 438 } 439 440 $id = idfilter($id); 441 442 // decide on scriptname 443 if($direct){ 444 if($conf['userewrite'] == 1){ 445 $script = '_media'; 446 }else{ 447 $script = 'lib/exe/fetch.php'; 448 } 449 }else{ 450 if($conf['userewrite'] == 1){ 451 $script = '_detail'; 452 }else{ 453 $script = 'lib/exe/detail.php'; 454 } 455 } 456 457 // build URL based on rewrite mode 458 if($conf['userewrite']){ 459 $xlink .= $script.'/'.$id; 460 if($more) $xlink .= '?'.$more; 461 }else{ 462 if($more){ 463 $xlink .= $script.'?'.$more; 464 $xlink .= $sep.'media='.$id; 465 }else{ 466 $xlink .= $script.'?media='.$id; 467 } 468 } 469 470 return $xlink; 471} 472 473 474 475/** 476 * Just builds a link to a script 477 * 478 * @todo maybe obsolete 479 * @author Andreas Gohr <andi@splitbrain.org> 480 */ 481function script($script='doku.php'){ 482# $link = getBaseURL(); 483# $link .= $script; 484# return $link; 485 return DOKU_BASE.DOKU_SCRIPT; 486} 487 488/** 489 * Spamcheck against wordlist 490 * 491 * Checks the wikitext against a list of blocked expressions 492 * returns true if the text contains any bad words 493 * 494 * @author Andreas Gohr <andi@splitbrain.org> 495 */ 496function checkwordblock(){ 497 global $TEXT; 498 global $conf; 499 500 if(!$conf['usewordblock']) return false; 501 502 // we prepare the text a tiny bit to prevent spammers circumventing URL checks 503 $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$TEXT); 504 505 $wordblocks = getWordblocks(); 506 //how many lines to read at once (to work around some PCRE limits) 507 if(version_compare(phpversion(),'4.3.0','<')){ 508 //old versions of PCRE define a maximum of parenthesises even if no 509 //backreferences are used - the maximum is 99 510 //this is very bad performancewise and may even be too high still 511 $chunksize = 40; 512 }else{ 513 //read file in chunks of 200 - this should work around the 514 //MAX_PATTERN_SIZE in modern PCRE 515 $chunksize = 200; 516 } 517 while($blocks = array_splice($wordblocks,0,$chunksize)){ 518 $re = array(); 519 #build regexp from blocks 520 foreach($blocks as $block){ 521 $block = preg_replace('/#.*$/','',$block); 522 $block = trim($block); 523 if(empty($block)) continue; 524 $re[] = $block; 525 } 526 if(count($re) && preg_match('#('.join('|',$re).')#si',$text)) { 527 return true; 528 } 529 } 530 return false; 531} 532 533/** 534 * Return the IP of the client 535 * 536 * Honours X-Forwarded-For and X-Real-IP Proxy Headers 537 * 538 * It returns a comma separated list of IPs if the above mentioned 539 * headers are set. If the single parameter is set, it tries to return 540 * a routable public address, prefering the ones suplied in the X 541 * headers 542 * 543 * @param boolean $single If set only a single IP is returned 544 * @author Andreas Gohr <andi@splitbrain.org> 545 */ 546function clientIP($single=false){ 547 $ip = array(); 548 $ip[] = $_SERVER['REMOTE_ADDR']; 549 if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) 550 $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_FORWARDED_FOR'])); 551 if(!empty($_SERVER['HTTP_X_REAL_IP'])) 552 $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_REAL_IP'])); 553 554 // remove any non-IP stuff 555 $cnt = count($ip); 556 $match = array(); 557 for($i=0; $i<$cnt; $i++){ 558 if(preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$ip[$i],$match)) { 559 $ip[$i] = $match[0]; 560 } else { 561 $ip[$i] = ''; 562 } 563 if(empty($ip[$i])) unset($ip[$i]); 564 } 565 $ip = array_values(array_unique($ip)); 566 if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP 567 568 if(!$single) return join(',',$ip); 569 570 // decide which IP to use, trying to avoid local addresses 571 $ip = array_reverse($ip); 572 foreach($ip as $i){ 573 if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){ 574 continue; 575 }else{ 576 return $i; 577 } 578 } 579 // still here? just use the first (last) address 580 return $ip[0]; 581} 582 583/** 584 * Convert one or more comma separated IPs to hostnames 585 * 586 * @author Glen Harris <astfgl@iamnota.org> 587 * @returns a comma separated list of hostnames 588 */ 589function gethostsbyaddrs($ips){ 590 $hosts = array(); 591 $ips = explode(',',$ips); 592 593 if(is_array($ips)) { 594 foreach($ips as $ip){ 595 $hosts[] = gethostbyaddr(trim($ip)); 596 } 597 return join(',',$hosts); 598 } else { 599 return gethostbyaddr(trim($ips)); 600 } 601} 602 603/** 604 * Checks if a given page is currently locked. 605 * 606 * removes stale lockfiles 607 * 608 * @author Andreas Gohr <andi@splitbrain.org> 609 */ 610function checklock($id){ 611 global $conf; 612 $lock = wikiLockFN($id); 613 614 //no lockfile 615 if(!@file_exists($lock)) return false; 616 617 //lockfile expired 618 if((time() - filemtime($lock)) > $conf['locktime']){ 619 @unlink($lock); 620 return false; 621 } 622 623 //my own lock 624 $ip = io_readFile($lock); 625 if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){ 626 return false; 627 } 628 629 return $ip; 630} 631 632/** 633 * Lock a page for editing 634 * 635 * @author Andreas Gohr <andi@splitbrain.org> 636 */ 637function lock($id){ 638 $lock = wikiLockFN($id); 639 if($_SERVER['REMOTE_USER']){ 640 io_saveFile($lock,$_SERVER['REMOTE_USER']); 641 }else{ 642 io_saveFile($lock,clientIP()); 643 } 644} 645 646/** 647 * Unlock a page if it was locked by the user 648 * 649 * @author Andreas Gohr <andi@splitbrain.org> 650 * @return bool true if a lock was removed 651 */ 652function unlock($id){ 653 $lock = wikiLockFN($id); 654 if(@file_exists($lock)){ 655 $ip = io_readFile($lock); 656 if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){ 657 @unlink($lock); 658 return true; 659 } 660 } 661 return false; 662} 663 664/** 665 * convert line ending to unix format 666 * 667 * @see formText() for 2crlf conversion 668 * @author Andreas Gohr <andi@splitbrain.org> 669 */ 670function cleanText($text){ 671 $text = preg_replace("/(\015\012)|(\015)/","\012",$text); 672 return $text; 673} 674 675/** 676 * Prepares text for print in Webforms by encoding special chars. 677 * It also converts line endings to Windows format which is 678 * pseudo standard for webforms. 679 * 680 * @see cleanText() for 2unix conversion 681 * @author Andreas Gohr <andi@splitbrain.org> 682 */ 683function formText($text){ 684 $text = str_replace("\012","\015\012",$text); 685 return htmlspecialchars($text); 686} 687 688/** 689 * Returns the specified local text in raw format 690 * 691 * @author Andreas Gohr <andi@splitbrain.org> 692 */ 693function rawLocale($id){ 694 return io_readFile(localeFN($id)); 695} 696 697/** 698 * Returns the raw WikiText 699 * 700 * @author Andreas Gohr <andi@splitbrain.org> 701 */ 702function rawWiki($id,$rev=''){ 703 return io_readWikiPage(wikiFN($id, $rev), $id, $rev); 704} 705 706/** 707 * Returns the pagetemplate contents for the ID's namespace 708 * 709 * @author Andreas Gohr <andi@splitbrain.org> 710 */ 711function pageTemplate($data){ 712 $id = $data[0]; 713 global $conf; 714 global $INFO; 715 716 $path = dirname(wikiFN($id)); 717 718 if(@file_exists($path.'/_template.txt')){ 719 $tpl = io_readFile($path.'/_template.txt'); 720 }else{ 721 // search upper namespaces for templates 722 $len = strlen(rtrim($conf['datadir'],'/')); 723 while (strlen($path) >= $len){ 724 if(@file_exists($path.'/__template.txt')){ 725 $tpl = io_readFile($path.'/__template.txt'); 726 break; 727 } 728 $path = substr($path, 0, strrpos($path, '/')); 729 } 730 } 731 if(!$tpl) return ''; 732 733 // replace placeholders 734 $tpl = str_replace('@ID@',$id,$tpl); 735 $tpl = str_replace('@NS@',getNS($id),$tpl); 736 $tpl = str_replace('@PAGE@',strtr(noNS($id),'_',' '),$tpl); 737 $tpl = str_replace('@USER@',$_SERVER['REMOTE_USER'],$tpl); 738 $tpl = str_replace('@NAME@',$INFO['userinfo']['name'],$tpl); 739 $tpl = str_replace('@MAIL@',$INFO['userinfo']['mail'],$tpl); 740 $tpl = str_replace('@DATE@',$conf['dformat'],$tpl); 741 $tpl = strftime($tpl); 742 return $tpl; 743} 744 745 746/** 747 * Returns the raw Wiki Text in three slices. 748 * 749 * The range parameter needs to have the form "from-to" 750 * and gives the range of the section in bytes - no 751 * UTF-8 awareness is needed. 752 * The returned order is prefix, section and suffix. 753 * 754 * @author Andreas Gohr <andi@splitbrain.org> 755 */ 756function rawWikiSlices($range,$id,$rev=''){ 757 list($from,$to) = split('-',$range,2); 758 $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev); 759 if(!$from) $from = 0; 760 if(!$to) $to = strlen($text)+1; 761 762 $slices[0] = substr($text,0,$from-1); 763 $slices[1] = substr($text,$from-1,$to-$from); 764 $slices[2] = substr($text,$to); 765 766 return $slices; 767} 768 769/** 770 * Joins wiki text slices 771 * 772 * function to join the text slices with correct lineendings again. 773 * When the pretty parameter is set to true it adds additional empty 774 * lines between sections if needed (used on saving). 775 * 776 * @author Andreas Gohr <andi@splitbrain.org> 777 */ 778function con($pre,$text,$suf,$pretty=false){ 779 780 if($pretty){ 781 if($pre && substr($pre,-1) != "\n") $pre .= "\n"; 782 if($suf && substr($text,-1) != "\n") $text .= "\n"; 783 } 784 785 if($pre) $pre .= "\n"; 786 if($suf) $text .= "\n"; 787 return $pre.$text.$suf; 788} 789 790/** 791 * Saves a wikitext by calling io_writeWikiPage. 792 * Also directs changelog and attic updates. 793 * 794 * @author Andreas Gohr <andi@splitbrain.org> 795 * @author Ben Coburn <btcoburn@silicodon.net> 796 */ 797function saveWikiText($id,$text,$summary,$minor=false){ 798 /* Note to developers: 799 This code is subtle and delicate. Test the behavior of 800 the attic and changelog with dokuwiki and external edits 801 after any changes. External edits change the wiki page 802 directly without using php or dokuwiki. 803 */ 804 global $conf; 805 global $lang; 806 global $REV; 807 // ignore if no changes were made 808 if($text == rawWiki($id,'')){ 809 return; 810 } 811 812 $file = wikiFN($id); 813 $old = @filemtime($file); // from page 814 $wasRemoved = empty($text); 815 $wasCreated = !@file_exists($file); 816 $wasReverted = ($REV==true); 817 $newRev = false; 818 $oldRev = getRevisions($id, -1, 1, 1024); // from changelog 819 $oldRev = (int)(empty($oldRev)?0:$oldRev[0]); 820 if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) { 821 // add old revision to the attic if missing 822 saveOldRevision($id); 823 // add a changelog entry if this edit came from outside dokuwiki 824 if ($old>$oldRev) { 825 addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true)); 826 // remove soon to be stale instructions 827 $cache = new cache_instructions($id, $file); 828 $cache->removeCache(); 829 } 830 } 831 832 if ($wasRemoved){ 833 // Send "update" event with empty data, so plugins can react to page deletion 834 $data = array(array($file, '', false), getNS($id), noNS($id), false); 835 trigger_event('IO_WIKIPAGE_WRITE', $data); 836 // pre-save deleted revision 837 @touch($file); 838 clearstatcache(); 839 $newRev = saveOldRevision($id); 840 // remove empty file 841 @unlink($file); 842 // remove old meta info... 843 $mfiles = metaFiles($id); 844 $changelog = metaFN($id, '.changes'); 845 foreach ($mfiles as $mfile) { 846 // but keep per-page changelog to preserve page history 847 if (@file_exists($mfile) && $mfile!==$changelog) { @unlink($mfile); } 848 } 849 $del = true; 850 // autoset summary on deletion 851 if(empty($summary)) $summary = $lang['deleted']; 852 // remove empty namespaces 853 io_sweepNS($id, 'datadir'); 854 io_sweepNS($id, 'mediadir'); 855 }else{ 856 // save file (namespace dir is created in io_writeWikiPage) 857 io_writeWikiPage($file, $text, $id); 858 // pre-save the revision, to keep the attic in sync 859 $newRev = saveOldRevision($id); 860 $del = false; 861 } 862 863 // select changelog line type 864 $extra = ''; 865 $type = DOKU_CHANGE_TYPE_EDIT; 866 if ($wasReverted) { 867 $type = DOKU_CHANGE_TYPE_REVERT; 868 $extra = $REV; 869 } 870 else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; } 871 else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; } 872 else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users 873 874 addLogEntry($newRev, $id, $type, $summary, $extra); 875 // send notify mails 876 notify($id,'admin',$old,$summary,$minor); 877 notify($id,'subscribers',$old,$summary,$minor); 878 879 // update the purgefile (timestamp of the last time anything within the wiki was changed) 880 io_saveFile($conf['cachedir'].'/purgefile',time()); 881} 882 883/** 884 * moves the current version to the attic and returns its 885 * revision date 886 * 887 * @author Andreas Gohr <andi@splitbrain.org> 888 */ 889function saveOldRevision($id){ 890 global $conf; 891 $oldf = wikiFN($id); 892 if(!@file_exists($oldf)) return ''; 893 $date = filemtime($oldf); 894 $newf = wikiFN($id,$date); 895 io_writeWikiPage($newf, rawWiki($id), $id, $date); 896 return $date; 897} 898 899/** 900 * Sends a notify mail on page change 901 * 902 * @param string $id The changed page 903 * @param string $who Who to notify (admin|subscribers) 904 * @param int $rev Old page revision 905 * @param string $summary What changed 906 * @param boolean $minor Is this a minor edit? 907 * @param array $replace Additional string substitutions, @KEY@ to be replaced by value 908 * 909 * @author Andreas Gohr <andi@splitbrain.org> 910 */ 911function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){ 912 global $lang; 913 global $conf; 914 global $INFO; 915 916 // decide if there is something to do 917 if($who == 'admin'){ 918 if(empty($conf['notify'])) return; //notify enabled? 919 $text = rawLocale('mailtext'); 920 $to = $conf['notify']; 921 $bcc = ''; 922 }elseif($who == 'subscribers'){ 923 if(!$conf['subscribers']) return; //subscribers enabled? 924 if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors 925 $bcc = subscriber_addresslist($id); 926 if(empty($bcc)) return; 927 $to = ''; 928 $text = rawLocale('subscribermail'); 929 }elseif($who == 'register'){ 930 if(empty($conf['registernotify'])) return; 931 $text = rawLocale('registermail'); 932 $to = $conf['registernotify']; 933 $bcc = ''; 934 }else{ 935 return; //just to be safe 936 } 937 938 $ip = clientIP(); 939 $text = str_replace('@DATE@',strftime($conf['dformat']),$text); 940 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 941 $text = str_replace('@IPADDRESS@',$ip,$text); 942 $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text); 943 $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text); 944 $text = str_replace('@PAGE@',$id,$text); 945 $text = str_replace('@TITLE@',$conf['title'],$text); 946 $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text); 947 $text = str_replace('@SUMMARY@',$summary,$text); 948 $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text); 949 950 foreach ($replace as $key => $substitution) { 951 $text = str_replace('@'.strtoupper($key).'@',$substitution, $text); 952 } 953 954 if($who == 'register'){ 955 $subject = $lang['mail_new_user'].' '.$summary; 956 }elseif($rev){ 957 $subject = $lang['mail_changed'].' '.$id; 958 $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text); 959 require_once(DOKU_INC.'inc/DifferenceEngine.php'); 960 $df = new Diff(split("\n",rawWiki($id,$rev)), 961 split("\n",rawWiki($id))); 962 $dformat = new UnifiedDiffFormatter(); 963 $diff = $dformat->format($df); 964 }else{ 965 $subject=$lang['mail_newpage'].' '.$id; 966 $text = str_replace('@OLDPAGE@','none',$text); 967 $diff = rawWiki($id); 968 } 969 $text = str_replace('@DIFF@',$diff,$text); 970 $subject = '['.$conf['title'].'] '.$subject; 971 972 $from = $conf['mailfrom']; 973 $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from); 974 $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from); 975 $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from); 976 977 mail_send($to,$subject,$text,$from,'',$bcc); 978} 979 980/** 981 * extracts the query from a search engine referrer 982 * 983 * @author Andreas Gohr <andi@splitbrain.org> 984 * @author Todd Augsburger <todd@rollerorgans.com> 985 */ 986function getGoogleQuery(){ 987 $url = parse_url($_SERVER['HTTP_REFERER']); 988 if(!$url) return ''; 989 990 $query = array(); 991 parse_str($url['query'],$query); 992 if(isset($query['q'])) 993 $q = $query['q']; // google, live/msn, aol, ask, altavista, alltheweb, gigablast 994 elseif(isset($query['p'])) 995 $q = $query['p']; // yahoo 996 elseif(isset($query['query'])) 997 $q = $query['query']; // lycos, netscape, clusty, hotbot 998 elseif(preg_match("#a9\.com#i",$url['host'])) // a9 999 $q = urldecode(ltrim($url['path'],'/')); 1000 1001 if(!$q) return ''; 1002 $q = join('|',preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/',$q,-1,PREG_SPLIT_NO_EMPTY)); 1003 return $q; 1004} 1005 1006/** 1007 * Try to set correct locale 1008 * 1009 * @deprecated No longer used 1010 * @author Andreas Gohr <andi@splitbrain.org> 1011 */ 1012function setCorrectLocale(){ 1013 global $conf; 1014 global $lang; 1015 1016 $enc = strtoupper($lang['encoding']); 1017 foreach ($lang['locales'] as $loc){ 1018 //try locale 1019 if(@setlocale(LC_ALL,$loc)) return; 1020 //try loceale with encoding 1021 if(@setlocale(LC_ALL,"$loc.$enc")) return; 1022 } 1023 //still here? try to set from environment 1024 @setlocale(LC_ALL,""); 1025} 1026 1027/** 1028 * Return the human readable size of a file 1029 * 1030 * @param int $size A file size 1031 * @param int $dec A number of decimal places 1032 * @author Martin Benjamin <b.martin@cybernet.ch> 1033 * @author Aidan Lister <aidan@php.net> 1034 * @version 1.0.0 1035 */ 1036function filesize_h($size, $dec = 1){ 1037 $sizes = array('B', 'KB', 'MB', 'GB'); 1038 $count = count($sizes); 1039 $i = 0; 1040 1041 while ($size >= 1024 && ($i < $count - 1)) { 1042 $size /= 1024; 1043 $i++; 1044 } 1045 1046 return round($size, $dec) . ' ' . $sizes[$i]; 1047} 1048 1049/** 1050 * return an obfuscated email address in line with $conf['mailguard'] setting 1051 * 1052 * @author Harry Fuecks <hfuecks@gmail.com> 1053 * @author Christopher Smith <chris@jalakai.co.uk> 1054 */ 1055function obfuscate($email) { 1056 global $conf; 1057 1058 switch ($conf['mailguard']) { 1059 case 'visible' : 1060 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 1061 return strtr($email, $obfuscate); 1062 1063 case 'hex' : 1064 $encode = ''; 1065 for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';'; 1066 return $encode; 1067 1068 case 'none' : 1069 default : 1070 return $email; 1071 } 1072} 1073 1074/** 1075 * Let us know if a user is tracking a page or a namespace 1076 * 1077 * @author Andreas Gohr <andi@splitbrain.org> 1078 */ 1079function is_subscribed($id,$uid,$ns=false){ 1080 if(!$ns) { 1081 $file=metaFN($id,'.mlist'); 1082 } else { 1083 if(!getNS($id)) { 1084 $file = metaFN(getNS($id),'.mlist'); 1085 } else { 1086 $file = metaFN(getNS($id),'/.mlist'); 1087 } 1088 } 1089 if (@file_exists($file)) { 1090 $mlist = file($file); 1091 $pos = array_search($uid."\n",$mlist); 1092 return is_int($pos); 1093 } 1094 1095 return false; 1096} 1097 1098/** 1099 * Return a string with the email addresses of all the 1100 * users subscribed to a page 1101 * 1102 * @author Steven Danz <steven-danz@kc.rr.com> 1103 */ 1104function subscriber_addresslist($id){ 1105 global $conf; 1106 global $auth; 1107 1108 $emails = ''; 1109 1110 if (!$conf['subscribers']) return; 1111 1112 // load the page mlist file content 1113 $mlist = array(); 1114 $file=metaFN($id,'.mlist'); 1115 if (@file_exists($file)) { 1116 $mlist = file($file); 1117 } 1118 if(count($mlist) > 0) { 1119 foreach ($mlist as $who) { 1120 $who = rtrim($who); 1121 $info = $auth->getUserData($who); 1122 if($info === false) continue; 1123 $level = auth_aclcheck($id,$who,$info['grps']); 1124 if ($level >= AUTH_READ) { 1125 if (strcasecmp($info['mail'],$conf['notify']) != 0) { 1126 if (empty($emails)) { 1127 $emails = $info['mail']; 1128 } else { 1129 $emails = "$emails,".$info['mail']; 1130 } 1131 } 1132 } 1133 } 1134 } 1135 1136 // load also the namespace mlist file content 1137 if(!getNS($id)) { 1138 $nsfile = metaFN(getNS($id),'.mlist'); 1139 } else { 1140 $nsfile = metaFN(getNS($id),'/.mlist'); 1141 } 1142 if (@file_exists($nsfile)) { 1143 $mlist = file($nsfile); 1144 } 1145 if(count($mlist) > 0) { 1146 foreach ($mlist as $who) { 1147 $who = rtrim($who); 1148 $info = $auth->getUserData($who); 1149 if($info === false) continue; 1150 $level = auth_aclcheck($id,$who,$info['grps']); 1151 if ($level >= AUTH_READ) { 1152 if (strcasecmp($info['mail'],$conf['notify']) != 0) { 1153 if (empty($emails)) { 1154 $emails = $info['mail']; 1155 } else { 1156 $emails = "$emails,".$info['mail']; 1157 } 1158 } 1159 } 1160 } 1161 } 1162 1163 return $emails; 1164} 1165 1166/** 1167 * Removes quoting backslashes 1168 * 1169 * @author Andreas Gohr <andi@splitbrain.org> 1170 */ 1171function unslash($string,$char="'"){ 1172 return str_replace('\\'.$char,$char,$string); 1173} 1174 1175/** 1176 * Convert php.ini shorthands to byte 1177 * 1178 * @author <gilthans dot NO dot SPAM at gmail dot com> 1179 * @link http://de3.php.net/manual/en/ini.core.php#79564 1180 */ 1181function php_to_byte($v){ 1182 $l = substr($v, -1); 1183 $ret = substr($v, 0, -1); 1184 switch(strtoupper($l)){ 1185 case 'P': 1186 $ret *= 1024; 1187 case 'T': 1188 $ret *= 1024; 1189 case 'G': 1190 $ret *= 1024; 1191 case 'M': 1192 $ret *= 1024; 1193 case 'K': 1194 $ret *= 1024; 1195 break; 1196 } 1197 return $ret; 1198} 1199 1200 1201 1202//Setup VIM: ex: et ts=2 enc=utf-8 : 1203