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