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