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