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