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