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