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); 830 notify($id,'subscribers',$old,$summary); 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 * 868 * @author Andreas Gohr <andi@splitbrain.org> 869 */ 870function notify($id,$who,$rev='',$summary=''){ 871 global $lang; 872 global $conf; 873 874 // decide if there is something to do 875 if($who == 'admin'){ 876 if(empty($conf['notify'])) return; //notify enabled? 877 $text = rawLocale('mailtext'); 878 $to = $conf['notify']; 879 $bcc = ''; 880 }elseif($who == 'subscribers'){ 881 if(!$conf['subscribers']) return; //subscribers enabled? 882 $bcc = subscriber_addresslist($id); 883 if(empty($bcc)) return; 884 $to = ''; 885 $text = rawLocale('subscribermail'); 886 }else{ 887 return; //just to be safe 888 } 889 890 $text = str_replace('@DATE@',date($conf['dformat']),$text); 891 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 892 $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text); 893 $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text); 894 $text = str_replace('@NEWPAGE@',wl($id,'',true),$text); 895 $text = str_replace('@PAGE@',$id,$text); 896 $text = str_replace('@TITLE@',$conf['title'],$text); 897 $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text); 898 $text = str_replace('@SUMMARY@',$summary,$text); 899 $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text); 900 901 if($rev){ 902 $subject = $lang['mail_changed'].' '.$id; 903 $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true),$text); 904 require_once("inc/DifferenceEngine.php"); 905 $df = new Diff(split("\n",rawWiki($id,$rev)), 906 split("\n",rawWiki($id))); 907 $dformat = new UnifiedDiffFormatter(); 908 $diff = $dformat->format($df); 909 }else{ 910 $subject=$lang['mail_newpage'].' '.$id; 911 $text = str_replace('@OLDPAGE@','none',$text); 912 $diff = rawWiki($id); 913 } 914 $text = str_replace('@DIFF@',$diff,$text); 915 $subject = '['.$conf['title'].'] '.$subject; 916 917 mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc); 918} 919 920/** 921 * Return a list of available page revisons 922 * 923 * @author Andreas Gohr <andi@splitbrain.org> 924 */ 925function getRevisions($id){ 926 $revd = dirname(wikiFN($id,'foo')); 927 $revs = array(); 928 $clid = cleanID($id); 929 if(strrpos($clid,':')) $clid = substr($clid,strrpos($clid,':')+1); //remove path 930 $clid = utf8_encodeFN($clid); 931 932 if (is_dir($revd) && $dh = opendir($revd)) { 933 while (($file = readdir($dh)) !== false) { 934 if (is_dir($revd.'/'.$file)) continue; 935 if (preg_match('/^'.$clid.'\.(\d+)\.txt(\.gz)?$/',$file,$match)){ 936 $revs[]=$match[1]; 937 } 938 } 939 closedir($dh); 940 } 941 rsort($revs); 942 return $revs; 943} 944 945/** 946 * extracts the query from a google referer 947 * 948 * @todo should be more generic and support yahoo et al 949 * @author Andreas Gohr <andi@splitbrain.org> 950 */ 951function getGoogleQuery(){ 952 $url = parse_url($_SERVER['HTTP_REFERER']); 953 if(!$url) return ''; 954 955 if(!preg_match("#google\.#i",$url['host'])) return ''; 956 $query = array(); 957 parse_str($url['query'],$query); 958 959 return $query['q']; 960} 961 962/** 963 * Try to set correct locale 964 * 965 * @deprecated No longer used 966 * @author Andreas Gohr <andi@splitbrain.org> 967 */ 968function setCorrectLocale(){ 969 global $conf; 970 global $lang; 971 972 $enc = strtoupper($lang['encoding']); 973 foreach ($lang['locales'] as $loc){ 974 //try locale 975 if(@setlocale(LC_ALL,$loc)) return; 976 //try loceale with encoding 977 if(@setlocale(LC_ALL,"$loc.$enc")) return; 978 } 979 //still here? try to set from environment 980 @setlocale(LC_ALL,""); 981} 982 983/** 984 * Return the human readable size of a file 985 * 986 * @param int $size A file size 987 * @param int $dec A number of decimal places 988 * @author Martin Benjamin <b.martin@cybernet.ch> 989 * @author Aidan Lister <aidan@php.net> 990 * @version 1.0.0 991 */ 992function filesize_h($size, $dec = 1){ 993 $sizes = array('B', 'KB', 'MB', 'GB'); 994 $count = count($sizes); 995 $i = 0; 996 997 while ($size >= 1024 && ($i < $count - 1)) { 998 $size /= 1024; 999 $i++; 1000 } 1001 1002 return round($size, $dec) . ' ' . $sizes[$i]; 1003} 1004 1005/** 1006 * return an obfuscated email address in line with $conf['mailguard'] setting 1007 * 1008 * @author Harry Fuecks <hfuecks@gmail.com> 1009 * @author Christopher Smith <chris@jalakai.co.uk> 1010 */ 1011function obfuscate($email) { 1012 global $conf; 1013 1014 switch ($conf['mailguard']) { 1015 case 'visible' : 1016 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 1017 return strtr($email, $obfuscate); 1018 1019 case 'hex' : 1020 $encode = ''; 1021 for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';'; 1022 return $encode; 1023 1024 case 'none' : 1025 default : 1026 return $email; 1027 } 1028} 1029 1030/** 1031 * Return DokuWikis version 1032 * 1033 * @author Andreas Gohr <andi@splitbrain.org> 1034 */ 1035function getVersion(){ 1036 //import version string 1037 if(@file_exists('VERSION')){ 1038 //official release 1039 return 'Release '.trim(io_readfile(DOKU_INC.'/VERSION')); 1040 }elseif(is_dir('_darcs')){ 1041 //darcs checkout 1042 $inv = file('_darcs/inventory'); 1043 $inv = preg_grep('#andi@splitbrain\.org\*\*\d{14}#',$inv); 1044 $cur = array_pop($inv); 1045 preg_match('#\*\*(\d{4})(\d{2})(\d{2})#',$cur,$matches); 1046 return 'Darcs '.$matches[1].'-'.$matches[2].'-'.$matches[3]; 1047 }else{ 1048 return 'snapshot?'; 1049 } 1050} 1051 1052/** 1053 * Run a few sanity checks 1054 * 1055 * @author Andreas Gohr <andi@splitbrain.org> 1056 */ 1057function check(){ 1058 global $conf; 1059 global $INFO; 1060 1061 msg('DokuWiki version: '.getVersion(),1); 1062 1063 if(version_compare(phpversion(),'4.3.0','<')){ 1064 msg('Your PHP version is too old ('.phpversion().' vs. 4.3.+ recommended)',-1); 1065 }elseif(version_compare(phpversion(),'4.3.10','<')){ 1066 msg('Consider upgrading PHP to 4.3.10 or higher for security reasons (your version: '.phpversion().')',0); 1067 }else{ 1068 msg('PHP version '.phpversion(),1); 1069 } 1070 1071 if(is_writable($conf['changelog'])){ 1072 msg('Changelog is writable',1); 1073 }else{ 1074 msg('Changelog is not writable',-1); 1075 } 1076 1077 if(is_writable($conf['datadir'])){ 1078 msg('Datadir is writable',1); 1079 }else{ 1080 msg('Datadir is not writable',-1); 1081 } 1082 1083 if(is_writable($conf['olddir'])){ 1084 msg('Attic is writable',1); 1085 }else{ 1086 msg('Attic is not writable',-1); 1087 } 1088 1089 if(is_writable($conf['mediadir'])){ 1090 msg('Mediadir is writable',1); 1091 }else{ 1092 msg('Mediadir is not writable',-1); 1093 } 1094 1095 if(is_writable($conf['cachedir'])){ 1096 msg('Cachedir is writable',1); 1097 }else{ 1098 msg('Cachedir is not writable',-1); 1099 } 1100 1101 if(is_writable(DOKU_CONF.'users.auth.php')){ 1102 msg('conf/users.auth.php is writable',1); 1103 }else{ 1104 msg('conf/users.auth.php is not writable',0); 1105 } 1106 1107 if(function_exists('mb_strpos')){ 1108 if(defined('UTF8_NOMBSTRING')){ 1109 msg('mb_string extension is available but will not be used',0); 1110 }else{ 1111 msg('mb_string extension is available and will be used',1); 1112 } 1113 }else{ 1114 msg('mb_string extension not available - PHP only replacements will be used',0); 1115 } 1116 1117 msg('Your current permission for this page is '.$INFO['perm'],0); 1118 1119 if(is_writable($INFO['filepath'])){ 1120 msg('The current page is writable by the webserver',0); 1121 }else{ 1122 msg('The current page is not writable by the webserver',0); 1123 } 1124 1125 if($INFO['writable']){ 1126 msg('The current page is writable by you',0); 1127 }else{ 1128 msg('The current page is not writable you',0); 1129 } 1130} 1131 1132/** 1133 * Let us know if a user is tracking a page 1134 * 1135 * @author Andreas Gohr <andi@splitbrain.org> 1136 */ 1137function is_subscribed($id,$uid){ 1138 $file=metaFN($id,'.mlist'); 1139 if (@file_exists($file)) { 1140 $mlist = file($file); 1141 $pos = array_search($uid."\n",$mlist); 1142 return is_int($pos); 1143 } 1144 1145 return false; 1146} 1147 1148/** 1149 * Return a string with the email addresses of all the 1150 * users subscribed to a page 1151 * 1152 * @author Steven Danz <steven-danz@kc.rr.com> 1153 */ 1154function subscriber_addresslist($id){ 1155 global $conf; 1156 1157 $emails = ''; 1158 1159 if (!$conf['subscribers']) return; 1160 1161 $mlist = array(); 1162 $file=metaFN($id,'.mlist'); 1163 if (file_exists($file)) { 1164 $mlist = file($file); 1165 } 1166 if(count($mlist) > 0) { 1167 foreach ($mlist as $who) { 1168 $who = rtrim($who); 1169 $info = auth_getUserData($who); 1170 $level = auth_aclcheck($id,$who,$info['grps']); 1171 if ($level >= AUTH_READ) { 1172 if (strcasecmp($info['mail'],$conf['notify']) != 0) { 1173 if (empty($emails)) { 1174 $emails = $info['mail']; 1175 } else { 1176 $emails = "$emails,".$info['mail']; 1177 } 1178 } 1179 } 1180 } 1181 } 1182 1183 return $emails; 1184} 1185 1186/** 1187 * Removes quoting backslashes 1188 * 1189 * @author Andreas Gohr <andi@splitbrain.org> 1190 */ 1191function unslash($string,$char="'"){ 1192 return str_replace('\\'.$char,$char,$string); 1193} 1194 1195//Setup VIM: ex: et ts=2 enc=utf-8 : 1196