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