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