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