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