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 9 if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/'); 10 require_once(DOKU_CONF.'dokuwiki.php'); 11 require_once(DOKU_INC.'inc/io.php'); 12 require_once(DOKU_INC.'inc/utf8.php'); 13 require_once(DOKU_INC.'inc/mail.php'); 14 require_once(DOKU_INC.'inc/parserutils.php'); 15 16/** 17 * These constants are used with the recents function 18 */ 19define('RECENTS_SKIP_DELETED',2); 20define('RECENTS_SKIP_MINORS',4); 21define('RECENTS_SKIP_SUBSPACES',8); 22 23/** 24 * Return info about the current document as associative 25 * array. 26 * 27 * @author Andreas Gohr <andi@splitbrain.org> 28 */ 29function pageinfo(){ 30 global $ID; 31 global $REV; 32 global $USERINFO; 33 global $conf; 34 35 if($_SERVER['REMOTE_USER']){ 36 $info['userinfo'] = $USERINFO; 37 $info['perm'] = auth_quickaclcheck($ID); 38 $info['subscribed'] = is_subscribed($ID,$_SERVER['REMOTE_USER']); 39 40 // if some outside auth were used only REMOTE_USER is set 41 if(!$info['userinfo']['name']){ 42 $info['userinfo']['name'] = $_SERVER['REMOTE_USER']; 43 } 44 }else{ 45 $info['perm'] = auth_aclcheck($ID,'',null); 46 $info['subscribed'] = false; 47 } 48 49 $info['namespace'] = getNS($ID); 50 $info['locked'] = checklock($ID); 51 $info['filepath'] = realpath(wikiFN($ID,$REV)); 52 $info['exists'] = @file_exists($info['filepath']); 53 if($REV && !$info['exists']){ 54 //check if current revision was meant 55 $cur = wikiFN($ID); 56 if(@file_exists($cur) && (@filemtime($cur) == $REV)){ 57 $info['filepath'] = realpath($cur); 58 $info['exists'] = true; 59 $REV = ''; 60 } 61 } 62 $info['rev'] = $REV; 63 if($info['exists']){ 64 $info['writable'] = (is_writable($info['filepath']) && 65 ($info['perm'] >= AUTH_EDIT)); 66 }else{ 67 $info['writable'] = ($info['perm'] >= AUTH_CREATE); 68 } 69 $info['editable'] = ($info['writable'] && empty($info['lock'])); 70 $info['lastmod'] = @filemtime($info['filepath']); 71 72 //who's the editor 73 if($REV){ 74 $revinfo = getRevisionInfo($ID,$REV); 75 }else{ 76 $revinfo = getRevisionInfo($ID,$info['lastmod']); 77 } 78 $info['ip'] = $revinfo['ip']; 79 $info['user'] = $revinfo['user']; 80 $info['sum'] = $revinfo['sum']; 81 $info['minor'] = $revinfo['minor']; 82 83 if($revinfo['user']){ 84 $info['editor'] = $revinfo['user']; 85 }else{ 86 $info['editor'] = $revinfo['ip']; 87 } 88 89 return $info; 90} 91 92/** 93 * Build an string of URL parameters 94 * 95 * @author Andreas Gohr 96 */ 97function buildURLparams($params, $sep='&'){ 98 $url = ''; 99 $amp = false; 100 foreach($params as $key => $val){ 101 if($amp) $url .= $sep; 102 103 $url .= $key.'='; 104 $url .= rawurlencode($val); 105 $amp = true; 106 } 107 return $url; 108} 109 110/** 111 * Build an string of html tag attributes 112 * 113 * @author Andreas Gohr 114 */ 115function buildAttributes($params){ 116 $url = ''; 117 foreach($params as $key => $val){ 118 $url .= $key.'="'; 119 $url .= htmlspecialchars ($val); 120 $url .= '" '; 121 } 122 return $url; 123} 124 125 126/** 127 * print a message 128 * 129 * If HTTP headers were not sent yet the message is added 130 * to the global message array else it's printed directly 131 * using html_msgarea() 132 * 133 * 134 * Levels can be: 135 * 136 * -1 error 137 * 0 info 138 * 1 success 139 * 140 * @author Andreas Gohr <andi@splitbrain.org> 141 * @see html_msgarea 142 */ 143function msg($message,$lvl=0,$line='',$file=''){ 144 global $MSG; 145 $errors[-1] = 'error'; 146 $errors[0] = 'info'; 147 $errors[1] = 'success'; 148 149 if($line || $file) $message.=' ['.basename($file).':'.$line.']'; 150 151 if(!headers_sent()){ 152 if(!isset($MSG)) $MSG = array(); 153 $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message); 154 }else{ 155 $MSG = array(); 156 $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message); 157 if(function_exists('html_msgarea')){ 158 html_msgarea(); 159 }else{ 160 print "ERROR($lvl) $message"; 161 } 162 } 163} 164 165/** 166 * This builds the breadcrumb trail and returns it as array 167 * 168 * @author Andreas Gohr <andi@splitbrain.org> 169 */ 170function breadcrumbs(){ 171 // we prepare the breadcrumbs early for quick session closing 172 static $crumbs = null; 173 if($crumbs != null) return $crumbs; 174 175 global $ID; 176 global $ACT; 177 global $conf; 178 $crumbs = $_SESSION[$conf['title']]['bc']; 179 180 //first visit? 181 if (!is_array($crumbs)){ 182 $crumbs = array(); 183 } 184 //we only save on show and existing wiki documents 185 $file = wikiFN($ID); 186 if($ACT != 'show' || !@file_exists($file)){ 187 $_SESSION[$conf['title']]['bc'] = $crumbs; 188 return $crumbs; 189 } 190 191 // page names 192 $name = noNS($ID); 193 if ($conf['useheading']) { 194 // get page title 195 $title = p_get_first_heading($ID); 196 if ($title) { 197 $name = $title; 198 } 199 } 200 201 //remove ID from array 202 if (isset($crumbs[$ID])) { 203 unset($crumbs[$ID]); 204 } 205 206 //add to array 207 $crumbs[$ID] = $name; 208 //reduce size 209 while(count($crumbs) > $conf['breadcrumbs']){ 210 array_shift($crumbs); 211 } 212 //save to session 213 $_SESSION[$conf['title']]['bc'] = $crumbs; 214 return $crumbs; 215} 216 217/** 218 * Filter for page IDs 219 * 220 * This is run on a ID before it is outputted somewhere 221 * currently used to replace the colon with something else 222 * on Windows systems and to have proper URL encoding 223 * 224 * Urlencoding is ommitted when the second parameter is false 225 * 226 * @author Andreas Gohr <andi@splitbrain.org> 227 */ 228function idfilter($id,$ue=true){ 229 global $conf; 230 if ($conf['useslash'] && $conf['userewrite']){ 231 $id = strtr($id,':','/'); 232 }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && 233 $conf['userewrite']) { 234 $id = strtr($id,':',';'); 235 } 236 if($ue){ 237 $id = rawurlencode($id); 238 $id = str_replace('%3A',':',$id); //keep as colon 239 $id = str_replace('%2F','/',$id); //keep as slash 240 } 241 return $id; 242} 243 244/** 245 * This builds a link to a wikipage 246 * 247 * It handles URL rewriting and adds additional parameter if 248 * given in $more 249 * 250 * @author Andreas Gohr <andi@splitbrain.org> 251 */ 252function wl($id='',$more='',$abs=false,$sep='&'){ 253 global $conf; 254 if(is_array($more)){ 255 $more = buildURLparams($more,$sep); 256 }else{ 257 $more = str_replace(',',$sep,$more); 258 } 259 260 $id = idfilter($id); 261 if($abs){ 262 $xlink = DOKU_URL; 263 }else{ 264 $xlink = DOKU_BASE; 265 } 266 267 if($conf['userewrite'] == 2){ 268 $xlink .= DOKU_SCRIPT.'/'.$id; 269 if($more) $xlink .= '?'.$more; 270 }elseif($conf['userewrite']){ 271 $xlink .= $id; 272 if($more) $xlink .= '?'.$more; 273 }else{ 274 $xlink .= DOKU_SCRIPT.'?id='.$id; 275 if($more) $xlink .= $sep.$more; 276 } 277 278 return $xlink; 279} 280 281/** 282 * Build a link to a media file 283 * 284 * Will return a link to the detail page if $direct is false 285 */ 286function ml($id='',$more='',$direct=true,$sep='&'){ 287 global $conf; 288 if(is_array($more)){ 289 $more = buildURLparams($more,$sep); 290 }else{ 291 $more = str_replace(',',$sep,$more); 292 } 293 294 $xlink = DOKU_BASE; 295 296 // external URLs are always direct without rewriting 297 if(preg_match('#^(https?|ftp)://#i',$id)){ 298 $xlink .= 'lib/exe/fetch.php'; 299 if($more){ 300 $xlink .= '?'.$more; 301 $xlink .= $sep.'media='.rawurlencode($id); 302 }else{ 303 $xlink .= '?media='.rawurlencode($id); 304 } 305 return $xlink; 306 } 307 308 $id = idfilter($id); 309 310 // decide on scriptname 311 if($direct){ 312 if($conf['userewrite'] == 1){ 313 $script = '_media'; 314 }else{ 315 $script = 'lib/exe/fetch.php'; 316 } 317 }else{ 318 if($conf['userewrite'] == 1){ 319 $script = '_detail'; 320 }else{ 321 $script = 'lib/exe/detail.php'; 322 } 323 } 324 325 // build URL based on rewrite mode 326 if($conf['userewrite']){ 327 $xlink .= $script.'/'.$id; 328 if($more) $xlink .= '?'.$more; 329 }else{ 330 if($more){ 331 $xlink .= $script.'?'.$more; 332 $xlink .= $sep.'media='.$id; 333 }else{ 334 $xlink .= $script.'?media='.$id; 335 } 336 } 337 338 return $xlink; 339} 340 341 342 343/** 344 * Just builds a link to a script 345 * 346 * @todo maybe obsolete 347 * @author Andreas Gohr <andi@splitbrain.org> 348 */ 349function script($script='doku.php'){ 350# $link = getBaseURL(); 351# $link .= $script; 352# return $link; 353 return DOKU_BASE.DOKU_SCRIPT; 354} 355 356/** 357 * Spamcheck against wordlist 358 * 359 * Checks the wikitext against a list of blocked expressions 360 * returns true if the text contains any bad words 361 * 362 * @author Andreas Gohr <andi@splitbrain.org> 363 */ 364function checkwordblock(){ 365 global $TEXT; 366 global $conf; 367 368 if(!$conf['usewordblock']) return false; 369 370 $wordblocks = getWordblocks(); 371 //how many lines to read at once (to work around some PCRE limits) 372 if(version_compare(phpversion(),'4.3.0','<')){ 373 //old versions of PCRE define a maximum of parenthesises even if no 374 //backreferences are used - the maximum is 99 375 //this is very bad performancewise and may even be too high still 376 $chunksize = 40; 377 }else{ 378 //read file in chunks of 600 - this should work around the 379 //MAX_PATTERN_SIZE in modern PCRE 380 $chunksize = 400; 381 } 382 while($blocks = array_splice($wordblocks,0,$chunksize)){ 383 $re = array(); 384 #build regexp from blocks 385 foreach($blocks as $block){ 386 $block = preg_replace('/#.*$/','',$block); 387 $block = trim($block); 388 if(empty($block)) continue; 389 $re[] = $block; 390 } 391 if(preg_match('#('.join('|',$re).')#si',$TEXT, $match=array())) { 392 return true; 393 } 394 } 395 return false; 396} 397 398/** 399 * Return the IP of the client 400 * 401 * Honours X-Forwarded-For and X-Real-IP Proxy Headers 402 * 403 * It returns a comma separated list of IPs if the above mentioned 404 * headers are set. If the single parameter is set, it tries to return 405 * a routable public address, prefering the ones suplied in the X 406 * headers 407 * 408 * @param boolean $single If set only a single IP is returned 409 * @author Andreas Gohr <andi@splitbrain.org> 410 */ 411function clientIP($single=false){ 412 $ip = array(); 413 $ip[] = $_SERVER['REMOTE_ADDR']; 414 if($_SERVER['HTTP_X_FORWARDED_FOR']) 415 $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_FORWARDED_FOR'])); 416 if($_SERVER['HTTP_X_REAL_IP']) 417 $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_REAL_IP'])); 418 419 // remove any non-IP stuff 420 $cnt = count($ip); 421 for($i=0; $i<$cnt; $i++){ 422 $ip[$i] = preg_replace('/[^0-9\.]+/','',$ip[$i]); 423 if(!preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$ip[$i])) $ip[$i] = ''; 424 if(empty($ip[$i])) unset($ip[$i]); 425 } 426 $ip = array_values(array_unique($ip)); 427 if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP 428 429 if(!$single) return join(',',$ip); 430 431 // decide which IP to use, trying to avoid local addresses 432 $ip = array_reverse($ip); 433 foreach($ip as $i){ 434 if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){ 435 continue; 436 }else{ 437 return $i; 438 } 439 } 440 // still here? just use the first (last) address 441 return $ip[0]; 442} 443 444/** 445 * Checks if a given page is currently locked. 446 * 447 * removes stale lockfiles 448 * 449 * @author Andreas Gohr <andi@splitbrain.org> 450 */ 451function checklock($id){ 452 global $conf; 453 $lock = wikiFN($id).'.lock'; 454 455 //no lockfile 456 if(!@file_exists($lock)) return false; 457 458 //lockfile expired 459 if((time() - filemtime($lock)) > $conf['locktime']){ 460 unlink($lock); 461 return false; 462 } 463 464 //my own lock 465 $ip = io_readFile($lock); 466 if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){ 467 return false; 468 } 469 470 return $ip; 471} 472 473/** 474 * Lock a page for editing 475 * 476 * @author Andreas Gohr <andi@splitbrain.org> 477 */ 478function lock($id){ 479 $lock = wikiFN($id).'.lock'; 480 if($_SERVER['REMOTE_USER']){ 481 io_saveFile($lock,$_SERVER['REMOTE_USER']); 482 }else{ 483 io_saveFile($lock,clientIP()); 484 } 485} 486 487/** 488 * Unlock a page if it was locked by the user 489 * 490 * @author Andreas Gohr <andi@splitbrain.org> 491 * @return bool true if a lock was removed 492 */ 493function unlock($id){ 494 $lock = wikiFN($id).'.lock'; 495 if(@file_exists($lock)){ 496 $ip = io_readFile($lock); 497 if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){ 498 @unlink($lock); 499 return true; 500 } 501 } 502 return false; 503} 504 505/** 506 * convert line ending to unix format 507 * 508 * @see formText() for 2crlf conversion 509 * @author Andreas Gohr <andi@splitbrain.org> 510 */ 511function cleanText($text){ 512 $text = preg_replace("/(\015\012)|(\015)/","\012",$text); 513 return $text; 514} 515 516/** 517 * Prepares text for print in Webforms by encoding special chars. 518 * It also converts line endings to Windows format which is 519 * pseudo standard for webforms. 520 * 521 * @see cleanText() for 2unix conversion 522 * @author Andreas Gohr <andi@splitbrain.org> 523 */ 524function formText($text){ 525 $text = preg_replace("/\012/","\015\012",$text); 526 return htmlspecialchars($text); 527} 528 529/** 530 * Returns the specified local text in raw format 531 * 532 * @author Andreas Gohr <andi@splitbrain.org> 533 */ 534function rawLocale($id){ 535 return io_readFile(localeFN($id)); 536} 537 538/** 539 * Returns the raw WikiText 540 * 541 * @author Andreas Gohr <andi@splitbrain.org> 542 */ 543function rawWiki($id,$rev=''){ 544 return io_readFile(wikiFN($id,$rev)); 545} 546 547/** 548 * Returns the pagetemplate contents for the ID's namespace 549 * 550 * @author Andreas Gohr <andi@splitbrain.org> 551 */ 552function pageTemplate($id){ 553 global $conf; 554 global $INFO; 555 $tpl = io_readFile(dirname(wikiFN($id)).'/_template.txt'); 556 $tpl = str_replace('@ID@',$id,$tpl); 557 $tpl = str_replace('@NS@',getNS($id),$tpl); 558 $tpl = str_replace('@PAGE@',strtr(noNS($id),'_',' '),$tpl); 559 $tpl = str_replace('@USER@',$_SERVER['REMOTE_USER'],$tpl); 560 $tpl = str_replace('@NAME@',$INFO['userinfo']['name'],$tpl); 561 $tpl = str_replace('@MAIL@',$INFO['userinfo']['mail'],$tpl); 562 $tpl = str_replace('@DATE@',date($conf['dformat']),$tpl); 563 return $tpl; 564} 565 566 567/** 568 * Returns the raw Wiki Text in three slices. 569 * 570 * The range parameter needs to have the form "from-to" 571 * and gives the range of the section in bytes - no 572 * UTF-8 awareness is needed. 573 * The returned order is prefix, section and suffix. 574 * 575 * @author Andreas Gohr <andi@splitbrain.org> 576 */ 577function rawWikiSlices($range,$id,$rev=''){ 578 list($from,$to) = split('-',$range,2); 579 $text = io_readFile(wikiFN($id,$rev)); 580 if(!$from) $from = 0; 581 if(!$to) $to = strlen($text)+1; 582 583 $slices[0] = substr($text,0,$from-1); 584 $slices[1] = substr($text,$from-1,$to-$from); 585 $slices[2] = substr($text,$to); 586 587 return $slices; 588} 589 590/** 591 * Joins wiki text slices 592 * 593 * function to join the text slices with correct lineendings again. 594 * When the pretty parameter is set to true it adds additional empty 595 * lines between sections if needed (used on saving). 596 * 597 * @author Andreas Gohr <andi@splitbrain.org> 598 */ 599function con($pre,$text,$suf,$pretty=false){ 600 601 if($pretty){ 602 if($pre && substr($pre,-1) != "\n") $pre .= "\n"; 603 if($suf && substr($text,-1) != "\n") $text .= "\n"; 604 } 605 606 if($pre) $pre .= "\n"; 607 if($suf) $text .= "\n"; 608 return $pre.$text.$suf; 609} 610 611/** 612 * print debug messages 613 * 614 * little function to print the content of a var 615 * 616 * @author Andreas Gohr <andi@splitbrain.org> 617 */ 618function dbg($msg,$hidden=false){ 619 (!$hidden) ? print '<pre class="dbg">' : print "<!--\n"; 620 print_r($msg); 621 (!$hidden) ? print '</pre>' : print "\n-->"; 622} 623 624/** 625 * Add's an entry to the changelog 626 * 627 * @author Andreas Gohr <andi@splitbrain.org> 628 */ 629function addLogEntry($date,$id,$summary='',$minor=false){ 630 global $conf; 631 632 if(!@is_writable($conf['changelog'])){ 633 msg($conf['changelog'].' is not writable!',-1); 634 return; 635 } 636 637 if(!$date) $date = time(); //use current time if none supplied 638 $remote = $_SERVER['REMOTE_ADDR']; 639 $user = $_SERVER['REMOTE_USER']; 640 641 if($conf['useacl'] && $user && $minor){ 642 $summary = '*'.$summary; 643 }else{ 644 $summary = ' '.$summary; 645 } 646 647 $logline = join("\t",array($date,$remote,$id,$user,$summary))."\n"; 648 io_saveFile($conf['changelog'],$logline,true); 649} 650 651/** 652 * Checks an summary entry if it was a minor edit 653 * 654 * The summary is cleaned of the marker char 655 * 656 * @author Andreas Gohr <andi@splitbrain.org> 657 */ 658function isMinor(&$summary){ 659 if(substr($summary,0,1) == '*'){ 660 $summary = substr($summary,1); 661 return true; 662 } 663 $summary = trim($summary); 664 return false; 665} 666 667/** 668 * Internal function used by getRecents 669 * 670 * don't call directly 671 * 672 * @see getRecents() 673 * @author Andreas Gohr <andi@splitbrain.org> 674 */ 675function _handleRecent($line,$ns,$flags){ 676 static $seen = array(); //caches seen pages and skip them 677 if(empty($line)) return false; //skip empty lines 678 679 // split the line into parts 680 list($dt,$ip,$id,$usr,$sum) = explode("\t",$line); 681 682 // skip seen ones 683 if($seen[$id]) return false; 684 $recent = array(); 685 686 // check minors 687 if(isMinor($sum)){ 688 // skip minors 689 if($flags & RECENTS_SKIP_MINORS) return false; 690 $recent['minor'] = true; 691 }else{ 692 $recent['minor'] = false; 693 } 694 695 // remember in seen to skip additional sights 696 $seen[$id] = 1; 697 698 // check if it's a hidden page 699 if(isHiddenPage($id)) return false; 700 701 // filter namespace 702 if (($ns) && (strpos($id,$ns.':') !== 0)) return false; 703 704 // exclude subnamespaces 705 if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($id) != $ns)) return false; 706 707 // check ACL 708 if (auth_quickaclcheck($id) < AUTH_READ) return false; 709 710 // check existance 711 if(!@file_exists(wikiFN($id))){ 712 if($flags & RECENTS_SKIP_DELETED){ 713 return false; 714 }else{ 715 $recent['del'] = true; 716 } 717 }else{ 718 $recent['del'] = false; 719 } 720 721 $recent['id'] = $id; 722 $recent['date'] = $dt; 723 $recent['ip'] = $ip; 724 $recent['user'] = $usr; 725 $recent['sum'] = $sum; 726 727 return $recent; 728} 729 730 731/** 732 * returns an array of recently changed files using the 733 * changelog 734 * 735 * The following constants can be used to control which changes are 736 * included. Add them together as needed. 737 * 738 * RECENTS_SKIP_DELETED - don't include deleted pages 739 * RECENTS_SKIP_MINORS - don't include minor changes 740 * RECENTS_SKIP_SUBSPACES - don't include subspaces 741 * 742 * @param int $first number of first entry returned (for paginating 743 * @param int $num return $num entries 744 * @param string $ns restrict to given namespace 745 * @param bool $flags see above 746 * 747 * @author Andreas Gohr <andi@splitbrain.org> 748 */ 749function getRecents($first,$num,$ns='',$flags=0){ 750 global $conf; 751 $recent = array(); 752 $count = 0; 753 754 if(!$num) 755 return $recent; 756 757 if(!@is_readable($conf['changelog'])){ 758 msg($conf['changelog'].' is not readable',-1); 759 return $recent; 760 } 761 762 $fh = fopen($conf['changelog'],'r'); 763 $buf = ''; 764 $csz = 4096; //chunksize 765 fseek($fh,0,SEEK_END); // jump to the end 766 $pos = ftell($fh); // position pointer 767 768 // now read backwards into buffer 769 while($pos > 0){ 770 $pos -= $csz; // seek to previous chunk... 771 if($pos < 0) { // ...or rest of file 772 $csz += $pos; 773 $pos = 0; 774 } 775 776 fseek($fh,$pos); 777 778 $buf = fread($fh,$csz).$buf; // prepend to buffer 779 780 $lines = explode("\n",$buf); // split buffer into lines 781 782 if($pos > 0){ 783 $buf = array_shift($lines); // first one may be still incomplete 784 } 785 786 $cnt = count($lines); 787 if(!$cnt) continue; // no lines yet 788 789 // handle lines 790 for($i = $cnt-1; $i >= 0; $i--){ 791 $rec = _handleRecent($lines[$i],$ns,$flags); 792 if($rec !== false){ 793 if(--$first >= 0) continue; // skip first entries 794 $recent[] = $rec; 795 $count++; 796 797 // break while when we have enough entries 798 if($count >= $num){ 799 $pos = 0; // will break the while loop 800 break; // will break the for loop 801 } 802 } 803 } 804 }// end of while 805 806 fclose($fh); 807 return $recent; 808} 809 810/** 811 * gets additonal informations for a certain pagerevison 812 * from the changelog 813 * 814 * @author Andreas Gohr <andi@splitbrain.org> 815 */ 816function getRevisionInfo($id,$rev){ 817 global $conf; 818 819 if(!$rev) return(null); 820 821 $info = array(); 822 if(!@is_readable($conf['changelog'])){ 823 msg($conf['changelog'].' is not readable',-1); 824 return $recent; 825 } 826 $loglines = file($conf['changelog']); 827 $loglines = preg_grep("/$rev\t\d+\.\d+\.\d+\.\d+\t$id\t/",$loglines); 828 $loglines = array_reverse($loglines); //reverse sort on timestamp (shouldn't be needed) 829 $line = split("\t",$loglines[0]); 830 $info['date'] = $line[0]; 831 $info['ip'] = $line[1]; 832 $info['user'] = $line[3]; 833 $info['sum'] = $line[4]; 834 $info['minor'] = isMinor($info['sum']); 835 return $info; 836} 837 838/** 839 * Saves a wikitext by calling io_saveFile 840 * 841 * @author Andreas Gohr <andi@splitbrain.org> 842 */ 843function saveWikiText($id,$text,$summary,$minor=false){ 844 global $conf; 845 global $lang; 846 // ignore if no changes were made 847 if($text == rawWiki($id,'')){ 848 return; 849 } 850 851 $file = wikiFN($id); 852 $old = saveOldRevision($id); 853 854 if (empty($text)){ 855 // remove empty file 856 @unlink($file); 857 // remove any meta info 858 $mfiles = metaFiles($id); 859 foreach ($mfiles as $mfile) { 860 if (file_exists($mfile)) @unlink($mfile); 861 } 862 $del = true; 863 // autoset summary on deletion 864 if(empty($summary)) $summary = $lang['deleted']; 865 // unlock early 866 unlock($id); 867 // remove empty namespaces 868 io_sweepNS($id); 869 }else{ 870 // save file (datadir is created in io_saveFile) 871 io_saveFile($file,$text); 872 $del = false; 873 } 874 875 addLogEntry(@filemtime($file),$id,$summary,$minor); 876 // send notify mails 877 notify($id,'admin',$old,$summary,$minor); 878 notify($id,'subscribers',$old,$summary,$minor); 879 880 //purge cache on add by updating the purgefile 881 if($conf['purgeonadd'] && (!$old || $del)){ 882 io_saveFile($conf['cachedir'].'/purgefile',time()); 883 } 884} 885 886/** 887 * moves the current version to the attic and returns its 888 * revision date 889 * 890 * @author Andreas Gohr <andi@splitbrain.org> 891 */ 892function saveOldRevision($id){ 893 global $conf; 894 $oldf = wikiFN($id); 895 if(!@file_exists($oldf)) return ''; 896 $date = filemtime($oldf); 897 $newf = wikiFN($id,$date); 898 if(substr($newf,-3)=='.gz'){ 899 io_saveFile($newf,rawWiki($id)); 900 }else{ 901 io_makeFileDir($newf); 902 copy($oldf, $newf); 903 } 904 return $date; 905} 906 907/** 908 * Sends a notify mail on page change 909 * 910 * @param string $id The changed page 911 * @param string $who Who to notify (admin|subscribers) 912 * @param int $rev Old page revision 913 * @param string $summary What changed 914 * @param boolean $minor Is this a minor edit? 915 * 916 * @author Andreas Gohr <andi@splitbrain.org> 917 */ 918function notify($id,$who,$rev='',$summary='',$minor=false){ 919 global $lang; 920 global $conf; 921 922 // decide if there is something to do 923 if($who == 'admin'){ 924 if(empty($conf['notify'])) return; //notify enabled? 925 $text = rawLocale('mailtext'); 926 $to = $conf['notify']; 927 $bcc = ''; 928 }elseif($who == 'subscribers'){ 929 if(!$conf['subscribers']) return; //subscribers enabled? 930 if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors 931 $bcc = subscriber_addresslist($id); 932 if(empty($bcc)) return; 933 $to = ''; 934 $text = rawLocale('subscribermail'); 935 }else{ 936 return; //just to be safe 937 } 938 939 $text = str_replace('@DATE@',date($conf['dformat']),$text); 940 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 941 $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text); 942 $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$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 if($rev){ 951 $subject = $lang['mail_changed'].' '.$id; 952 $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true),$text); 953 require_once(DOKU_INC.'inc/DifferenceEngine.php'); 954 $df = new Diff(split("\n",rawWiki($id,$rev)), 955 split("\n",rawWiki($id))); 956 $dformat = new UnifiedDiffFormatter(); 957 $diff = $dformat->format($df); 958 }else{ 959 $subject=$lang['mail_newpage'].' '.$id; 960 $text = str_replace('@OLDPAGE@','none',$text); 961 $diff = rawWiki($id); 962 } 963 $text = str_replace('@DIFF@',$diff,$text); 964 $subject = '['.$conf['title'].'] '.$subject; 965 966 mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc); 967} 968 969/** 970 * Return a list of available page revisons 971 * 972 * @author Andreas Gohr <andi@splitbrain.org> 973 */ 974function getRevisions($id){ 975 $revd = dirname(wikiFN($id,'foo')); 976 $revs = array(); 977 $clid = cleanID($id); 978 if(strrpos($clid,':')) $clid = substr($clid,strrpos($clid,':')+1); //remove path 979 $clid = utf8_encodeFN($clid); 980 981 if (is_dir($revd) && $dh = opendir($revd)) { 982 while (($file = readdir($dh)) !== false) { 983 if (is_dir($revd.'/'.$file)) continue; 984 if (preg_match('/^'.$clid.'\.(\d+)\.txt(\.gz)?$/',$file,$match)){ 985 $revs[]=$match[1]; 986 } 987 } 988 closedir($dh); 989 } 990 rsort($revs); 991 return $revs; 992} 993 994/** 995 * extracts the query from a google referer 996 * 997 * @todo should be more generic and support yahoo et al 998 * @author Andreas Gohr <andi@splitbrain.org> 999 */ 1000function getGoogleQuery(){ 1001 $url = parse_url($_SERVER['HTTP_REFERER']); 1002 if(!$url) return ''; 1003 1004 if(!preg_match("#google\.#i",$url['host'])) return ''; 1005 $query = array(); 1006 parse_str($url['query'],$query); 1007 1008 return $query['q']; 1009} 1010 1011/** 1012 * Try to set correct locale 1013 * 1014 * @deprecated No longer used 1015 * @author Andreas Gohr <andi@splitbrain.org> 1016 */ 1017function setCorrectLocale(){ 1018 global $conf; 1019 global $lang; 1020 1021 $enc = strtoupper($lang['encoding']); 1022 foreach ($lang['locales'] as $loc){ 1023 //try locale 1024 if(@setlocale(LC_ALL,$loc)) return; 1025 //try loceale with encoding 1026 if(@setlocale(LC_ALL,"$loc.$enc")) return; 1027 } 1028 //still here? try to set from environment 1029 @setlocale(LC_ALL,""); 1030} 1031 1032/** 1033 * Return the human readable size of a file 1034 * 1035 * @param int $size A file size 1036 * @param int $dec A number of decimal places 1037 * @author Martin Benjamin <b.martin@cybernet.ch> 1038 * @author Aidan Lister <aidan@php.net> 1039 * @version 1.0.0 1040 */ 1041function filesize_h($size, $dec = 1){ 1042 $sizes = array('B', 'KB', 'MB', 'GB'); 1043 $count = count($sizes); 1044 $i = 0; 1045 1046 while ($size >= 1024 && ($i < $count - 1)) { 1047 $size /= 1024; 1048 $i++; 1049 } 1050 1051 return round($size, $dec) . ' ' . $sizes[$i]; 1052} 1053 1054/** 1055 * return an obfuscated email address in line with $conf['mailguard'] setting 1056 * 1057 * @author Harry Fuecks <hfuecks@gmail.com> 1058 * @author Christopher Smith <chris@jalakai.co.uk> 1059 */ 1060function obfuscate($email) { 1061 global $conf; 1062 1063 switch ($conf['mailguard']) { 1064 case 'visible' : 1065 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 1066 return strtr($email, $obfuscate); 1067 1068 case 'hex' : 1069 $encode = ''; 1070 for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';'; 1071 return $encode; 1072 1073 case 'none' : 1074 default : 1075 return $email; 1076 } 1077} 1078 1079/** 1080 * Return DokuWikis version 1081 * 1082 * @author Andreas Gohr <andi@splitbrain.org> 1083 */ 1084function getVersion(){ 1085 //import version string 1086 if(@file_exists('VERSION')){ 1087 //official release 1088 return 'Release '.trim(io_readfile(DOKU_INC.'/VERSION')); 1089 }elseif(is_dir('_darcs')){ 1090 //darcs checkout 1091 $inv = file('_darcs/inventory'); 1092 $inv = preg_grep('#\*\*\d{14}[\]$]#',$inv); 1093 $cur = array_pop($inv); 1094 preg_match('#\*\*(\d{4})(\d{2})(\d{2})#',$cur,$matches); 1095 return 'Darcs '.$matches[1].'-'.$matches[2].'-'.$matches[3]; 1096 }else{ 1097 return 'snapshot?'; 1098 } 1099} 1100 1101/** 1102 * Run a few sanity checks 1103 * 1104 * @author Andreas Gohr <andi@splitbrain.org> 1105 */ 1106function check(){ 1107 global $conf; 1108 global $INFO; 1109 1110 msg('DokuWiki version: '.getVersion(),1); 1111 1112 if(version_compare(phpversion(),'4.3.0','<')){ 1113 msg('Your PHP version is too old ('.phpversion().' vs. 4.3.+ recommended)',-1); 1114 }elseif(version_compare(phpversion(),'4.3.10','<')){ 1115 msg('Consider upgrading PHP to 4.3.10 or higher for security reasons (your version: '.phpversion().')',0); 1116 }else{ 1117 msg('PHP version '.phpversion(),1); 1118 } 1119 1120 if(is_writable($conf['changelog'])){ 1121 msg('Changelog is writable',1); 1122 }else{ 1123 msg('Changelog is not writable',-1); 1124 } 1125 1126 if(is_writable($conf['datadir'])){ 1127 msg('Datadir is writable',1); 1128 }else{ 1129 msg('Datadir is not writable',-1); 1130 } 1131 1132 if(is_writable($conf['olddir'])){ 1133 msg('Attic is writable',1); 1134 }else{ 1135 msg('Attic is not writable',-1); 1136 } 1137 1138 if(is_writable($conf['mediadir'])){ 1139 msg('Mediadir is writable',1); 1140 }else{ 1141 msg('Mediadir is not writable',-1); 1142 } 1143 1144 if(is_writable($conf['cachedir'])){ 1145 msg('Cachedir is writable',1); 1146 }else{ 1147 msg('Cachedir is not writable',-1); 1148 } 1149 1150 if(is_writable(DOKU_CONF.'users.auth.php')){ 1151 msg('conf/users.auth.php is writable',1); 1152 }else{ 1153 msg('conf/users.auth.php is not writable',0); 1154 } 1155 1156 if(function_exists('mb_strpos')){ 1157 if(defined('UTF8_NOMBSTRING')){ 1158 msg('mb_string extension is available but will not be used',0); 1159 }else{ 1160 msg('mb_string extension is available and will be used',1); 1161 } 1162 }else{ 1163 msg('mb_string extension not available - PHP only replacements will be used',0); 1164 } 1165 1166 if($conf['allowdebug']){ 1167 msg('Debugging support is enabled. If you don\'t need it you should set $conf[\'allowdebug\'] = 0',-1); 1168 }else{ 1169 msg('Debugging support is disabled',1); 1170 } 1171 1172 msg('Your current permission for this page is '.$INFO['perm'],0); 1173 1174 if(is_writable($INFO['filepath'])){ 1175 msg('The current page is writable by the webserver',0); 1176 }else{ 1177 msg('The current page is not writable by the webserver',0); 1178 } 1179 1180 if($INFO['writable']){ 1181 msg('The current page is writable by you',0); 1182 }else{ 1183 msg('The current page is not writable you',0); 1184 } 1185} 1186 1187/** 1188 * Let us know if a user is tracking a page 1189 * 1190 * @author Andreas Gohr <andi@splitbrain.org> 1191 */ 1192function is_subscribed($id,$uid){ 1193 $file=metaFN($id,'.mlist'); 1194 if (@file_exists($file)) { 1195 $mlist = file($file); 1196 $pos = array_search($uid."\n",$mlist); 1197 return is_int($pos); 1198 } 1199 1200 return false; 1201} 1202 1203/** 1204 * Return a string with the email addresses of all the 1205 * users subscribed to a page 1206 * 1207 * @author Steven Danz <steven-danz@kc.rr.com> 1208 */ 1209function subscriber_addresslist($id){ 1210 global $conf; 1211 global $auth; 1212 1213 $emails = ''; 1214 1215 if (!$conf['subscribers']) return; 1216 1217 $mlist = array(); 1218 $file=metaFN($id,'.mlist'); 1219 if (file_exists($file)) { 1220 $mlist = file($file); 1221 } 1222 if(count($mlist) > 0) { 1223 foreach ($mlist as $who) { 1224 $who = rtrim($who); 1225 $info = $auth->getUserData($who); 1226 $level = auth_aclcheck($id,$who,$info['grps']); 1227 if ($level >= AUTH_READ) { 1228 if (strcasecmp($info['mail'],$conf['notify']) != 0) { 1229 if (empty($emails)) { 1230 $emails = $info['mail']; 1231 } else { 1232 $emails = "$emails,".$info['mail']; 1233 } 1234 } 1235 } 1236 } 1237 } 1238 1239 return $emails; 1240} 1241 1242/** 1243 * Removes quoting backslashes 1244 * 1245 * @author Andreas Gohr <andi@splitbrain.org> 1246 */ 1247function unslash($string,$char="'"){ 1248 return str_replace('\\'.$char,$char,$string); 1249} 1250 1251//Setup VIM: ex: et ts=2 enc=utf-8 : 1252