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 * 681 * @author Andreas Gohr <andi@splitbrain.org> 682 * @author Ben Coburn <btcoburn@silicodon.net> 683 */ 684function saveWikiText($id,$text,$summary,$minor=false){ 685 global $conf; 686 global $lang; 687 global $REV; 688 // ignore if no changes were made 689 if($text == rawWiki($id,'')){ 690 return; 691 } 692 693 $file = wikiFN($id); 694 $old = @filemtime($file); 695 $wasRemoved = empty($text); 696 $wasCreated = !@file_exists($file); 697 $wasReverted = ($REV==true); 698 $newRev = false; 699 if(!@file_exists(wikiFN($id, $old)) && @file_exists($file)) { 700 // add old revision to the attic if missing 701 saveOldRevision($id); 702 // add a changelog entry if this edit came from outside dokuwiki 703 $oldRev = getRevisions($id, -1, 1, 1024); 704 $oldRev = (int)(empty($oldRev)?0:$oldRev[0]); 705 if ($oldRev!==$old) { 706 addLogEntry($old, $id); 707 // send notify mails 708 notify($id,'admin',$oldRev,'',false); 709 notify($id,'subscribers',$oldRev,'',false); 710 // remove soon to be stale instructions 711 $cache = new cache_instructions($id, $file); 712 $cache->removeCache(); 713 } 714 } 715 716 if ($wasRemoved){ 717 // pre-save deleted revision 718 @touch($file); 719 clearstatcache(); 720 $newRev = saveOldRevision($id); 721 // remove empty file 722 @unlink($file); 723 // remove old meta info... 724 $mfiles = metaFiles($id); 725 $changelog = metaFN($id, '.changes'); 726 foreach ($mfiles as $mfile) { 727 // but keep per-page changelog to preserve page history 728 if (@file_exists($mfile) && $mfile!==$changelog) { @unlink($mfile); } 729 } 730 $del = true; 731 // autoset summary on deletion 732 if(empty($summary)) $summary = $lang['deleted']; 733 // remove empty namespaces 734 io_sweepNS($id, 'datadir'); 735 io_sweepNS($id, 'mediadir'); 736 }else{ 737 // save file (namespace dir is created in io_writeWikiPage) 738 io_writeWikiPage($file, $text, $id); 739 // pre-save the revision, to keep the attic in sync 740 $newRev = saveOldRevision($id); 741 $del = false; 742 } 743 744 // select changelog line type 745 $extra = ''; 746 $type = 'E'; 747 if ($wasReverted) { 748 $type = 'R'; 749 $extra = $REV; 750 } 751 else if ($wasCreated) { $type = 'C'; } 752 else if ($wasRemoved) { $type = 'D'; } 753 else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = 'e'; } //minor edits only for logged in users 754 755 addLogEntry($newRev, $id, $type, $summary, $extra); 756 // send notify mails 757 notify($id,'admin',$old,$summary,$minor); 758 notify($id,'subscribers',$old,$summary,$minor); 759 760 // update the purgefile (timestamp of the last time anything within the wiki was changed) 761 io_saveFile($conf['cachedir'].'/purgefile',time()); 762} 763 764/** 765 * moves the current version to the attic and returns its 766 * revision date 767 * 768 * @author Andreas Gohr <andi@splitbrain.org> 769 */ 770function saveOldRevision($id){ 771 global $conf; 772 $oldf = wikiFN($id); 773 if(!@file_exists($oldf)) return ''; 774 $date = filemtime($oldf); 775 $newf = wikiFN($id,$date); 776 io_writeWikiPage($newf, rawWiki($id), $id, $date); 777 return $date; 778} 779 780/** 781 * Sends a notify mail on page change 782 * 783 * @param string $id The changed page 784 * @param string $who Who to notify (admin|subscribers) 785 * @param int $rev Old page revision 786 * @param string $summary What changed 787 * @param boolean $minor Is this a minor edit? 788 * @param array $replace Additional string substitutions, @KEY@ to be replaced by value 789 * 790 * @author Andreas Gohr <andi@splitbrain.org> 791 */ 792function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){ 793 global $lang; 794 global $conf; 795 796 // decide if there is something to do 797 if($who == 'admin'){ 798 if(empty($conf['notify'])) return; //notify enabled? 799 $text = rawLocale('mailtext'); 800 $to = $conf['notify']; 801 $bcc = ''; 802 }elseif($who == 'subscribers'){ 803 if(!$conf['subscribers']) return; //subscribers enabled? 804 if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors 805 $bcc = subscriber_addresslist($id); 806 if(empty($bcc)) return; 807 $to = ''; 808 $text = rawLocale('subscribermail'); 809 }elseif($who == 'register'){ 810 if(empty($conf['registernotify'])) return; 811 $text = rawLocale('registermail'); 812 $to = $conf['registernotify']; 813 $bcc = ''; 814 }else{ 815 return; //just to be safe 816 } 817 818 $text = str_replace('@DATE@',date($conf['dformat']),$text); 819 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 820 $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text); 821 $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text); 822 $text = str_replace('@NEWPAGE@',wl($id,'',true),$text); 823 $text = str_replace('@PAGE@',$id,$text); 824 $text = str_replace('@TITLE@',$conf['title'],$text); 825 $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text); 826 $text = str_replace('@SUMMARY@',$summary,$text); 827 $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text); 828 829 foreach ($replace as $key => $substitution) { 830 $text = str_replace('@'.strtoupper($key).'@',$substitution, $text); 831 } 832 833 if($who == 'register'){ 834 $subject = $lang['mail_new_user'].' '.$summary; 835 }elseif($rev){ 836 $subject = $lang['mail_changed'].' '.$id; 837 $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true),$text); 838 require_once(DOKU_INC.'inc/DifferenceEngine.php'); 839 $df = new Diff(split("\n",rawWiki($id,$rev)), 840 split("\n",rawWiki($id))); 841 $dformat = new UnifiedDiffFormatter(); 842 $diff = $dformat->format($df); 843 }else{ 844 $subject=$lang['mail_newpage'].' '.$id; 845 $text = str_replace('@OLDPAGE@','none',$text); 846 $diff = rawWiki($id); 847 } 848 $text = str_replace('@DIFF@',$diff,$text); 849 $subject = '['.$conf['title'].'] '.$subject; 850 851 mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc); 852} 853 854/** 855 * extracts the query from a google referer 856 * 857 * @todo should be more generic and support yahoo et al 858 * @author Andreas Gohr <andi@splitbrain.org> 859 */ 860function getGoogleQuery(){ 861 $url = parse_url($_SERVER['HTTP_REFERER']); 862 if(!$url) return ''; 863 864 if(!preg_match("#google\.#i",$url['host'])) return ''; 865 $query = array(); 866 parse_str($url['query'],$query); 867 868 return $query['q']; 869} 870 871/** 872 * Try to set correct locale 873 * 874 * @deprecated No longer used 875 * @author Andreas Gohr <andi@splitbrain.org> 876 */ 877function setCorrectLocale(){ 878 global $conf; 879 global $lang; 880 881 $enc = strtoupper($lang['encoding']); 882 foreach ($lang['locales'] as $loc){ 883 //try locale 884 if(@setlocale(LC_ALL,$loc)) return; 885 //try loceale with encoding 886 if(@setlocale(LC_ALL,"$loc.$enc")) return; 887 } 888 //still here? try to set from environment 889 @setlocale(LC_ALL,""); 890} 891 892/** 893 * Return the human readable size of a file 894 * 895 * @param int $size A file size 896 * @param int $dec A number of decimal places 897 * @author Martin Benjamin <b.martin@cybernet.ch> 898 * @author Aidan Lister <aidan@php.net> 899 * @version 1.0.0 900 */ 901function filesize_h($size, $dec = 1){ 902 $sizes = array('B', 'KB', 'MB', 'GB'); 903 $count = count($sizes); 904 $i = 0; 905 906 while ($size >= 1024 && ($i < $count - 1)) { 907 $size /= 1024; 908 $i++; 909 } 910 911 return round($size, $dec) . ' ' . $sizes[$i]; 912} 913 914/** 915 * return an obfuscated email address in line with $conf['mailguard'] setting 916 * 917 * @author Harry Fuecks <hfuecks@gmail.com> 918 * @author Christopher Smith <chris@jalakai.co.uk> 919 */ 920function obfuscate($email) { 921 global $conf; 922 923 switch ($conf['mailguard']) { 924 case 'visible' : 925 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 926 return strtr($email, $obfuscate); 927 928 case 'hex' : 929 $encode = ''; 930 for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';'; 931 return $encode; 932 933 case 'none' : 934 default : 935 return $email; 936 } 937} 938 939/** 940 * Let us know if a user is tracking a page 941 * 942 * @author Andreas Gohr <andi@splitbrain.org> 943 */ 944function is_subscribed($id,$uid){ 945 $file=metaFN($id,'.mlist'); 946 if (@file_exists($file)) { 947 $mlist = file($file); 948 $pos = array_search($uid."\n",$mlist); 949 return is_int($pos); 950 } 951 952 return false; 953} 954 955/** 956 * Return a string with the email addresses of all the 957 * users subscribed to a page 958 * 959 * @author Steven Danz <steven-danz@kc.rr.com> 960 */ 961function subscriber_addresslist($id){ 962 global $conf; 963 global $auth; 964 965 $emails = ''; 966 967 if (!$conf['subscribers']) return; 968 969 $mlist = array(); 970 $file=metaFN($id,'.mlist'); 971 if (@file_exists($file)) { 972 $mlist = file($file); 973 } 974 if(count($mlist) > 0) { 975 foreach ($mlist as $who) { 976 $who = rtrim($who); 977 $info = $auth->getUserData($who); 978 $level = auth_aclcheck($id,$who,$info['grps']); 979 if ($level >= AUTH_READ) { 980 if (strcasecmp($info['mail'],$conf['notify']) != 0) { 981 if (empty($emails)) { 982 $emails = $info['mail']; 983 } else { 984 $emails = "$emails,".$info['mail']; 985 } 986 } 987 } 988 } 989 } 990 991 return $emails; 992} 993 994/** 995 * Removes quoting backslashes 996 * 997 * @author Andreas Gohr <andi@splitbrain.org> 998 */ 999function unslash($string,$char="'"){ 1000 return str_replace('\\'.$char,$char,$string); 1001} 1002 1003//Setup VIM: ex: et ts=2 enc=utf-8 : 1004