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 echo str_repeat(' ', $intend)."$string\n"; 44} 45 46/** 47 * strips control characters (<32) from the given string 48 * 49 * @author Andreas Gohr <andi@splitbrain.org> 50 */ 51function stripctl($string){ 52 return preg_replace('/[\x00-\x1F]+/s','',$string); 53} 54 55/** 56 * Return info about the current document as associative 57 * array. 58 * 59 * @author Andreas Gohr <andi@splitbrain.org> 60 */ 61function pageinfo(){ 62 global $ID; 63 global $REV; 64 global $USERINFO; 65 global $conf; 66 67 // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml 68 // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary 69 $info['id'] = $ID; 70 $info['rev'] = $REV; 71 72 if($_SERVER['REMOTE_USER']){ 73 $info['userinfo'] = $USERINFO; 74 $info['perm'] = auth_quickaclcheck($ID); 75 $info['subscribed'] = is_subscribed($ID,$_SERVER['REMOTE_USER']); 76 $info['client'] = $_SERVER['REMOTE_USER']; 77 78 // set info about manager/admin status 79 $info['isadmin'] = false; 80 $info['ismanager'] = false; 81 if($info['perm'] == AUTH_ADMIN){ 82 $info['isadmin'] = true; 83 $info['ismanager'] = true; 84 }elseif(auth_ismanager()){ 85 $info['ismanager'] = true; 86 } 87 88 // if some outside auth were used only REMOTE_USER is set 89 if(!$info['userinfo']['name']){ 90 $info['userinfo']['name'] = $_SERVER['REMOTE_USER']; 91 } 92 93 }else{ 94 $info['perm'] = auth_aclcheck($ID,'',null); 95 $info['subscribed'] = false; 96 $info['client'] = clientIP(true); 97 } 98 99 $info['namespace'] = getNS($ID); 100 $info['locked'] = checklock($ID); 101 $info['filepath'] = realpath(wikiFN($ID)); 102 $info['exists'] = @file_exists($info['filepath']); 103 if($REV){ 104 //check if current revision was meant 105 if($info['exists'] && (@filemtime($info['filepath'])==$REV)){ 106 $REV = ''; 107 }else{ 108 //really use old revision 109 $info['filepath'] = realpath(wikiFN($ID,$REV)); 110 $info['exists'] = @file_exists($info['filepath']); 111 } 112 } 113 $info['rev'] = $REV; 114 if($info['exists']){ 115 $info['writable'] = (is_writable($info['filepath']) && 116 ($info['perm'] >= AUTH_EDIT)); 117 }else{ 118 $info['writable'] = ($info['perm'] >= AUTH_CREATE); 119 } 120 $info['editable'] = ($info['writable'] && empty($info['lock'])); 121 $info['lastmod'] = @filemtime($info['filepath']); 122 123 //load page meta data 124 $info['meta'] = p_get_metadata($ID); 125 126 //who's the editor 127 if($REV){ 128 $revinfo = getRevisionInfo($ID, $REV, 1024); 129 }else{ 130 if (is_array($info['meta']['last_change'])) { 131 $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']===DOKU_CHANGE_TYPE_MINOR_EDIT 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 = noNSorNS($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 = str_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, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true)); 740 // remove soon to be stale instructions 741 $cache = new cache_instructions($id, $file); 742 $cache->removeCache(); 743 } 744 } 745 746 if ($wasRemoved){ 747 // pre-save deleted revision 748 @touch($file); 749 clearstatcache(); 750 $newRev = saveOldRevision($id); 751 // remove empty file 752 @unlink($file); 753 // remove old meta info... 754 $mfiles = metaFiles($id); 755 $changelog = metaFN($id, '.changes'); 756 foreach ($mfiles as $mfile) { 757 // but keep per-page changelog to preserve page history 758 if (@file_exists($mfile) && $mfile!==$changelog) { @unlink($mfile); } 759 } 760 $del = true; 761 // autoset summary on deletion 762 if(empty($summary)) $summary = $lang['deleted']; 763 // remove empty namespaces 764 io_sweepNS($id, 'datadir'); 765 io_sweepNS($id, 'mediadir'); 766 }else{ 767 // save file (namespace dir is created in io_writeWikiPage) 768 io_writeWikiPage($file, $text, $id); 769 // pre-save the revision, to keep the attic in sync 770 $newRev = saveOldRevision($id); 771 $del = false; 772 } 773 774 // select changelog line type 775 $extra = ''; 776 $type = DOKU_CHANGE_TYPE_EDIT; 777 if ($wasReverted) { 778 $type = DOKU_CHANGE_TYPE_REVERT; 779 $extra = $REV; 780 } 781 else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; } 782 else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; } 783 else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users 784 785 addLogEntry($newRev, $id, $type, $summary, $extra); 786 // send notify mails 787 notify($id,'admin',$old,$summary,$minor); 788 notify($id,'subscribers',$old,$summary,$minor); 789 790 // update the purgefile (timestamp of the last time anything within the wiki was changed) 791 io_saveFile($conf['cachedir'].'/purgefile',time()); 792} 793 794/** 795 * moves the current version to the attic and returns its 796 * revision date 797 * 798 * @author Andreas Gohr <andi@splitbrain.org> 799 */ 800function saveOldRevision($id){ 801 global $conf; 802 $oldf = wikiFN($id); 803 if(!@file_exists($oldf)) return ''; 804 $date = filemtime($oldf); 805 $newf = wikiFN($id,$date); 806 io_writeWikiPage($newf, rawWiki($id), $id, $date); 807 return $date; 808} 809 810/** 811 * Sends a notify mail on page change 812 * 813 * @param string $id The changed page 814 * @param string $who Who to notify (admin|subscribers) 815 * @param int $rev Old page revision 816 * @param string $summary What changed 817 * @param boolean $minor Is this a minor edit? 818 * @param array $replace Additional string substitutions, @KEY@ to be replaced by value 819 * 820 * @author Andreas Gohr <andi@splitbrain.org> 821 */ 822function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){ 823 global $lang; 824 global $conf; 825 global $INFO; 826 827 // decide if there is something to do 828 if($who == 'admin'){ 829 if(empty($conf['notify'])) return; //notify enabled? 830 $text = rawLocale('mailtext'); 831 $to = $conf['notify']; 832 $bcc = ''; 833 }elseif($who == 'subscribers'){ 834 if(!$conf['subscribers']) return; //subscribers enabled? 835 if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors 836 $bcc = subscriber_addresslist($id); 837 if(empty($bcc)) return; 838 $to = ''; 839 $text = rawLocale('subscribermail'); 840 }elseif($who == 'register'){ 841 if(empty($conf['registernotify'])) return; 842 $text = rawLocale('registermail'); 843 $to = $conf['registernotify']; 844 $bcc = ''; 845 }else{ 846 return; //just to be safe 847 } 848 849 $text = str_replace('@DATE@',date($conf['dformat']),$text); 850 $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text); 851 $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text); 852 $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text); 853 $text = str_replace('@NEWPAGE@',wl($id,'',true),$text); 854 $text = str_replace('@PAGE@',$id,$text); 855 $text = str_replace('@TITLE@',$conf['title'],$text); 856 $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text); 857 $text = str_replace('@SUMMARY@',$summary,$text); 858 $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text); 859 860 foreach ($replace as $key => $substitution) { 861 $text = str_replace('@'.strtoupper($key).'@',$substitution, $text); 862 } 863 864 if($who == 'register'){ 865 $subject = $lang['mail_new_user'].' '.$summary; 866 }elseif($rev){ 867 $subject = $lang['mail_changed'].' '.$id; 868 $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true),$text); 869 require_once(DOKU_INC.'inc/DifferenceEngine.php'); 870 $df = new Diff(split("\n",rawWiki($id,$rev)), 871 split("\n",rawWiki($id))); 872 $dformat = new UnifiedDiffFormatter(); 873 $diff = $dformat->format($df); 874 }else{ 875 $subject=$lang['mail_newpage'].' '.$id; 876 $text = str_replace('@OLDPAGE@','none',$text); 877 $diff = rawWiki($id); 878 } 879 $text = str_replace('@DIFF@',$diff,$text); 880 $subject = '['.$conf['title'].'] '.$subject; 881 882 $from = $conf['mailfrom']; 883 $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from); 884 $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from); 885 $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from); 886 887 mail_send($to,$subject,$text,$from,'',$bcc); 888} 889 890/** 891 * extracts the query from a google referer 892 * 893 * @todo should be more generic and support yahoo et al 894 * @author Andreas Gohr <andi@splitbrain.org> 895 */ 896function getGoogleQuery(){ 897 $url = parse_url($_SERVER['HTTP_REFERER']); 898 if(!$url) return ''; 899 900 if(!preg_match("#google\.#i",$url['host'])) return ''; 901 $query = array(); 902 parse_str($url['query'],$query); 903 904 return $query['q']; 905} 906 907/** 908 * Try to set correct locale 909 * 910 * @deprecated No longer used 911 * @author Andreas Gohr <andi@splitbrain.org> 912 */ 913function setCorrectLocale(){ 914 global $conf; 915 global $lang; 916 917 $enc = strtoupper($lang['encoding']); 918 foreach ($lang['locales'] as $loc){ 919 //try locale 920 if(@setlocale(LC_ALL,$loc)) return; 921 //try loceale with encoding 922 if(@setlocale(LC_ALL,"$loc.$enc")) return; 923 } 924 //still here? try to set from environment 925 @setlocale(LC_ALL,""); 926} 927 928/** 929 * Return the human readable size of a file 930 * 931 * @param int $size A file size 932 * @param int $dec A number of decimal places 933 * @author Martin Benjamin <b.martin@cybernet.ch> 934 * @author Aidan Lister <aidan@php.net> 935 * @version 1.0.0 936 */ 937function filesize_h($size, $dec = 1){ 938 $sizes = array('B', 'KB', 'MB', 'GB'); 939 $count = count($sizes); 940 $i = 0; 941 942 while ($size >= 1024 && ($i < $count - 1)) { 943 $size /= 1024; 944 $i++; 945 } 946 947 return round($size, $dec) . ' ' . $sizes[$i]; 948} 949 950/** 951 * return an obfuscated email address in line with $conf['mailguard'] setting 952 * 953 * @author Harry Fuecks <hfuecks@gmail.com> 954 * @author Christopher Smith <chris@jalakai.co.uk> 955 */ 956function obfuscate($email) { 957 global $conf; 958 959 switch ($conf['mailguard']) { 960 case 'visible' : 961 $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); 962 return strtr($email, $obfuscate); 963 964 case 'hex' : 965 $encode = ''; 966 for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';'; 967 return $encode; 968 969 case 'none' : 970 default : 971 return $email; 972 } 973} 974 975/** 976 * Let us know if a user is tracking a page 977 * 978 * @author Andreas Gohr <andi@splitbrain.org> 979 */ 980function is_subscribed($id,$uid){ 981 $file=metaFN($id,'.mlist'); 982 if (@file_exists($file)) { 983 $mlist = file($file); 984 $pos = array_search($uid."\n",$mlist); 985 return is_int($pos); 986 } 987 988 return false; 989} 990 991/** 992 * Return a string with the email addresses of all the 993 * users subscribed to a page 994 * 995 * @author Steven Danz <steven-danz@kc.rr.com> 996 */ 997function subscriber_addresslist($id){ 998 global $conf; 999 global $auth; 1000 1001 $emails = ''; 1002 1003 if (!$conf['subscribers']) return; 1004 1005 $mlist = array(); 1006 $file=metaFN($id,'.mlist'); 1007 if (@file_exists($file)) { 1008 $mlist = file($file); 1009 } 1010 if(count($mlist) > 0) { 1011 foreach ($mlist as $who) { 1012 $who = rtrim($who); 1013 $info = $auth->getUserData($who); 1014 $level = auth_aclcheck($id,$who,$info['grps']); 1015 if ($level >= AUTH_READ) { 1016 if (strcasecmp($info['mail'],$conf['notify']) != 0) { 1017 if (empty($emails)) { 1018 $emails = $info['mail']; 1019 } else { 1020 $emails = "$emails,".$info['mail']; 1021 } 1022 } 1023 } 1024 } 1025 } 1026 1027 return $emails; 1028} 1029 1030/** 1031 * Removes quoting backslashes 1032 * 1033 * @author Andreas Gohr <andi@splitbrain.org> 1034 */ 1035function unslash($string,$char="'"){ 1036 return str_replace('\\'.$char,$char,$string); 1037} 1038 1039//Setup VIM: ex: et ts=2 enc=utf-8 : 1040