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