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