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