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