xref: /dokuwiki/inc/common.php (revision 93a9e83512a37d720cb675cbb2017e6419ea8216)
1<?
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  require_once("conf/dokuwiki.php");
10  require_once("inc/io.php");
11  require_once('inc/utf8.php');
12
13  //set up error reporting to sane values
14  error_reporting(E_ALL ^ E_NOTICE);
15
16  //make session rewrites XHTML compliant
17  ini_set('arg_separator.output', '&amp;');
18
19  //init session
20  session_name("DokuWiki");
21  session_start();
22
23  //kill magic quotes
24  if (get_magic_quotes_gpc()) {
25    if (!empty($_GET))    remove_magic_quotes($_GET);
26    if (!empty($_POST))   remove_magic_quotes($_POST);
27    if (!empty($_COOKIE)) remove_magic_quotes($_COOKIE);
28    if (!empty($_REQUEST)) remove_magic_quotes($_REQUEST);
29    if (!empty($_SESSION)) remove_magic_quotes($_SESSION);
30    ini_set('magic_quotes_gpc', 0);
31  }
32  set_magic_quotes_runtime(0);
33  ini_set('magic_quotes_sybase',0);
34
35  //disable gzip if not available
36  if($conf['usegzip'] && !function_exists('gzopen')){
37    $conf['usegzip'] = 0;
38  }
39
40/**
41 * remove magic quotes recursivly
42 *
43 * @author Andreas Gohr <andi@splitbrain.org>
44 */
45function remove_magic_quotes(&$array) {
46  foreach (array_keys($array) as $key) {
47    if (is_array($array[$key])) {
48      remove_magic_quotes($array[$key]);
49    }else {
50      $array[$key] = stripslashes($array[$key]);
51    }
52  }
53}
54
55/**
56 * Returns the full absolute URL to the directory where
57 * DokuWiki is installed in (includes a trailing slash)
58 *
59 * @author Andreas Gohr <andi@splitbrain.org>
60 */
61function getBaseURL($abs=false){
62  global $conf;
63  //if canonical url enabled always return absolute
64  if($conf['canonical']) $abs = true;
65
66  //relative URLs are easy
67  if(!$abs){
68    $dir = dirname($_SERVER['PHP_SELF']).'/';
69    $dir = preg_replace('#//#','/',$dir);
70    $dir = preg_replace('#\/$#','/',$dir); #bugfix for weird WIN behaviour
71    return $dir;
72  }
73
74  $port = ':'.$_SERVER['SERVER_PORT'];
75  //remove port from hostheader as sent by IE
76  $host = preg_replace('/:.*$/','',$_SERVER['HTTP_HOST']);
77
78  // see if HTTPS is enabled - apache leaves this empty when not available,
79  // IIS sets it to 'off', 'false' and 'disabled' are just guessing
80  if (preg_match('/^(|off|false|disabled)$/i',$_SERVER['HTTPS'])){
81    $proto = 'http://';
82    if ($_SERVER['SERVER_PORT'] == '80') {
83      $port='';
84    }
85  }else{
86    $proto = 'https://';
87    if ($_SERVER['SERVER_PORT'] == '443') {
88      $port='';
89    }
90  }
91  $dir = (dirname($_SERVER['PHP_SELF'])).'/';
92  $dir = preg_replace('#//#','/',$dir);
93  $dir = preg_replace('#\/$#','/',$dir); #bugfix for weird WIN behaviour
94
95  return $proto.$host.$port.$dir;
96}
97
98/**
99 * Return info about the current document as associative
100 * array.
101 *
102 * @author Andreas Gohr <andi@splitbrain.org>
103 */
104function pageinfo(){
105  global $ID;
106  global $REV;
107  global $USERINFO;
108  global $conf;
109
110  if($_SERVER['REMOTE_USER']){
111    $info['user']     = $_SERVER['REMOTE_USER'];
112    $info['userinfo'] = $USERINFO;
113    $info['perm']     = auth_quickaclcheck($ID);
114  }else{
115    $info['user']     = '';
116    $info['perm']     = auth_aclcheck($ID,'',null);
117  }
118
119  $info['namespace'] = getNS($ID);
120  $info['locked']    = checklock($ID);
121  $info['filepath']  = realpath(wikiFN($ID,$REV));
122  $info['exists']    = @file_exists($info['filepath']);
123  if($REV && !$info['exists']){
124    //check if current revision was meant
125    $cur = wikiFN($ID);
126    if(@file_exists($cur) && (@filemtime($cur) == $REV)){
127      $info['filepath'] = realpath($cur);
128      $info['exists']   = true;
129      $REV = '';
130    }
131  }
132  if($info['exists']){
133    $info['writable'] = (is_writable($info['filepath']) &&
134                         ($info['perm'] >= AUTH_EDIT));
135  }else{
136    $info['writable'] = ($info['perm'] >= AUTH_CREATE);
137  }
138  $info['editable']  = ($info['writable'] && empty($info['lock']));
139  $info['lastmod']   = @filemtime($info['filepath']);
140
141  return $info;
142}
143
144/**
145 * adds a message to the global message array
146 *
147 * Levels can be:
148 *
149 * -1 error
150 *  0 info
151 *  1 success
152 *
153 * @author Andreas Gohr <andi@splitbrain.org>
154 */
155function msg($message,$lvl=0){
156  global $MSG;
157  $errors[-1] = 'error';
158  $errors[0]  = 'info';
159  $errors[1]  = 'success';
160
161  if(!isset($MSG)) $MSG = array();
162  $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message);
163}
164
165/**
166 * This builds the breadcrumb trail and returns it as array
167 *
168 * @author Andreas Gohr <andi@splitbrain.org>
169 */
170function breadcrumbs(){
171  global $ID;
172  global $ACT;
173  global $conf;
174  $crumbs = $_SESSION[$conf['title']]['bc'];
175
176  //first visit?
177  if (!is_array($crumbs)){
178    $crumbs = array();
179  }
180  //we only save on show and existing wiki documents
181  if($ACT != 'show' || !@file_exists(wikiFN($ID))){
182    $_SESSION[$conf['title']]['bc'] = $crumbs;
183    return $crumbs;
184  }
185  //remove ID from array
186  $pos = array_search($ID,$crumbs);
187  if($pos !== false && $pos !== null){
188    array_splice($crumbs,$pos,1);
189  }
190
191  //add to array
192  $crumbs[] =$ID;
193  //reduce size
194  while(count($crumbs) > $conf['breadcrumbs']){
195    array_shift($crumbs);
196  }
197  //save to session
198  $_SESSION[$conf['title']]['bc'] = $crumbs;
199  return $crumbs;
200}
201
202/**
203 * Filter for page IDs
204 *
205 * This is run on a ID before it is outputted somewhere
206 * currently used to replace the colon with something else
207 * on Windows systems and to have proper URL encoding
208 *
209 * Urlencoding is ommitted when the second parameter is false
210 *
211 * @author Andreas Gohr <andi@splitbrain.org>
212 */
213function idfilter($id,$ue=true){
214  global $conf;
215  if ($conf['useslash'] && $conf['userewrite']){
216    $id = strtr($id,':','/');
217  }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
218      $conf['userewrite']) {
219    $id = strtr($id,':',';');
220  }
221  if($ue){
222    $id = urlencode($id);
223    $id = str_replace('%3A',':',$id); //keep as colon
224    $id = str_replace('%2F','/',$id); //keep as slash
225  }
226  return $id;
227}
228
229/**
230 * This builds a link to a wikipage (using getBaseURL)
231 *
232 * @author Andreas Gohr <andi@splitbrain.org>
233 */
234function wl($id='',$more='',$script='doku.php',$canonical=false){
235  global $conf;
236  $more = str_replace(',','&amp;',$more);
237
238  $id    = idfilter($id);
239  $xlink = getBaseURL($canonical);
240
241  if(!$conf['userewrite']){
242    $xlink .= $script;
243    $xlink .= '?id='.$id;
244    if($more) $xlink .= '&amp;'.$more;
245  }else{
246    $xlink .= $id;
247    if($more) $xlink .= '?'.$more;
248  }
249
250  return $xlink;
251}
252
253/**
254 * Just builds a link to a script
255 *
256 * @author Andreas Gohr <andi@splitbrain.org>
257 */
258function script($script='doku.php'){
259  $link = getBaseURL();
260  $link .= $script;
261  return $link;
262}
263
264/**
265 * Return namespacepart of a wiki ID
266 *
267 * @author Andreas Gohr <andi@splitbrain.org>
268 */
269function getNS($id){
270 if(strpos($id,':')!==false){
271   return substr($id,0,strrpos($id,':'));
272 }
273 return false;
274}
275
276/**
277 * Returns the ID without the namespace
278 *
279 * @author Andreas Gohr <andi@splitbrain.org>
280 */
281function noNS($id){
282  return preg_replace('/.*:/','',$id);
283}
284
285/**
286 * Spamcheck against wordlist
287 *
288 * Checks the wikitext against a list of blocked expressions
289 * returns true if the text contains any bad words
290 *
291 * @author Andreas Gohr <andi@splitbrain.org>
292 */
293function checkwordblock(){
294  global $TEXT;
295  global $conf;
296
297  if(!$conf['usewordblock']) return false;
298
299  $blocks = file('conf/wordblock.conf');
300  $re = array();
301  #build regexp from blocks
302  foreach($blocks as $block){
303    $block = preg_replace('/#.*$/','',$block);
304    $block = trim($block);
305    if(empty($block)) continue;
306    $re[]  = $block;
307  }
308  if(preg_match('#('.join('|',$re).')#si',$TEXT)) return true;
309  return false;
310}
311
312/**
313 * Return the IP of the client
314 *
315 * Honours X-Forwarded-For Proxy Headers
316 *
317 * @author Andreas Gohr <andi@splitbrain.org>
318 */
319function clientIP(){
320  $my = $_SERVER['REMOTE_ADDR'];
321  if($_SERVER['HTTP_X_FORWARDED_FOR']){
322    $my .= ' ('.$_SERVER['HTTP_X_FORWARDED_FOR'].')';
323  }
324  return $my;
325}
326
327/**
328 * Checks if a given page is currently locked.
329 *
330 * removes stale lockfiles
331 *
332 * @author Andreas Gohr <andi@splitbrain.org>
333 */
334function checklock($id){
335  global $conf;
336  $lock = wikiFN($id).'.lock';
337
338  //no lockfile
339  if(!@file_exists($lock)) return false;
340
341  //lockfile expired
342  if((time() - filemtime($lock)) > $conf['locktime']){
343    unlink($lock);
344    return false;
345  }
346
347  //my own lock
348  $ip = io_readFile($lock);
349  if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
350    return false;
351  }
352
353  return $ip;
354}
355
356/**
357 * Lock a page for editing
358 *
359 * @author Andreas Gohr <andi@splitbrain.org>
360 */
361function lock($id){
362  $lock = wikiFN($id).'.lock';
363  if($_SERVER['REMOTE_USER']){
364    io_saveFile($lock,$_SERVER['REMOTE_USER']);
365  }else{
366    io_saveFile($lock,clientIP());
367  }
368}
369
370/**
371 * Unlock a page if it was locked by the user
372 *
373 * @author Andreas Gohr <andi@splitbrain.org>
374 * @return bool true if a lock was removed
375 */
376function unlock($id){
377  $lock = wikiFN($id).'.lock';
378  if(@file_exists($lock)){
379    $ip = io_readFile($lock);
380    if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
381      @unlink($lock);
382      return true;
383    }
384  }
385  return false;
386}
387
388/**
389 * Remove unwanted chars from ID
390 *
391 * Cleans a given ID to only use allowed characters. Accented characters are
392 * converted to unaccented ones
393 *
394 * @author Andreas Gohr <andi@splitbrain.org>
395 */
396function cleanID($id){
397  global $conf;
398  global $lang;
399  $id = trim($id);
400  $id = utf8_strtolower($id);
401
402  //alternative namespace seperator
403  $id = strtr($id,';',':');
404  if($conf['useslash']) $id = strtr($id,'/',':');
405
406  //FIXME use config to ask for deaccenting
407  $id = utf8_deaccent($id,-1);
408
409  //remove specials (only ascii specials are removed)
410  $id = preg_replace('#[ !"§$%&()\[\]{}\\?`\'\#~*+=,<>\|^°@µ¹²³¼½¬]#u','_',$id);
411
412/* DELETEME legacy code
413  if(!$conf['localnames']){
414    if($lang['encoding'] == 'iso-8859-15'){
415      // replace accented chars with unaccented ones
416      // this may look strange on your terminal - just don't touch
417      $id = strtr(
418      strtr($id,
419       'ŠŽšžŸÀÁÂÃÅÇÈÉÊËÌÍÎÏÑÒÓÔÕØÙÚÛÝàáâãåçèéêëìíîïñòóôõøùúûýÿ',
420       'szszyaaaaaceeeeiiiinooooouuuyaaaaaceeeeiiiinooooouuuyy'),
421       array('Þ' => 'th', 'þ' => 'th', 'Ð' => 'dh', 'ð' => 'dh', 'ß' => 'ss',
422             'Œ' => 'oe', 'œ' => 'oe', 'Æ' => 'ae', 'æ' => 'ae', 'µ' => 'u',
423             'ü' => 'ue', 'ö' => 'oe', 'ä' => 'ae', 'Ü' => 'ue', 'Ö' => 'ö',
424             'Ä' => 'ae'));
425    }
426    $WORD = 'a-z';
427  }else{
428    $WORD = '\w';
429  }
430  //special chars left will be converted to _
431  $id = preg_replace('#[^'.$WORD.'0-9:\-\.]#','_',$id);
432*/
433
434  //clean up
435  $id = preg_replace('#__#','_',$id);
436  $id = preg_replace('#:+#',':',$id);
437  $id = trim($id,':._-');
438  $id = preg_replace('#:[:\._\-]+#',':',$id);
439
440  return($id);
441}
442
443/**
444 * returns the full path to the datafile specified by ID and
445 * optional revision
446 *
447 * The filename is URL encoded to protect Unicode chars
448 *
449 * @author Andreas Gohr <andi@splitbrain.org>
450 */
451function wikiFN($id,$rev=''){
452  global $conf;
453  $id = cleanID($id);
454  $id = str_replace(':','/',$id);
455  if(empty($rev)){
456    $fn = $conf['datadir'].'/'.$id.'.txt';
457  }else{
458    $fn = $conf['olddir'].'/'.$id.'.'.$rev.'.txt';
459    if($conf['usegzip'] && !@file_exists($fn)){
460      //return gzip if enabled and plaintext doesn't exist
461      $fn .= '.gz';
462    }
463  }
464  $fn = utf8_encodeFN($fn);
465  return $fn;
466}
467
468/**
469 * Returns the full filepath to a localized textfile if local
470 * version isn't found the english one is returned
471 *
472 * @author Andreas Gohr <andi@splitbrain.org>
473 */
474function localeFN($id){
475  global $conf;
476  $file = './lang/'.$conf['lang'].'/'.$id.'.txt';
477  if(!@file_exists($file)){
478    //fall back to english
479    $file = './lang/en/'.$id.'.txt';
480  }
481  return cleanText($file);
482}
483
484/**
485 * convert line ending to unix format
486 *
487 * @see    formText() for 2crlf conversion
488 * @author Andreas Gohr <andi@splitbrain.org>
489 */
490function cleanText($text){
491  $text = preg_replace("/(\015\012)|(\015)/","\012",$text);
492  return $text;
493}
494
495/**
496 * Prepares text for print in Webforms by encoding special chars.
497 * It also converts line endings to Windows format which is
498 * pseudo standard for webforms.
499 *
500 * @see    cleanText() for 2unix conversion
501 * @author Andreas Gohr <andi@splitbrain.org>
502 */
503function formText($text){
504  $text = preg_replace("/\012/","\015\012",$text);
505  return htmlspecialchars($text);
506}
507
508/**
509 * Returns the specified local text in parsed format
510 *
511 * @author Andreas Gohr <andi@splitbrain.org>
512 */
513function parsedLocale($id){
514  //disable section editing
515  global $parser;
516  $se = $parser['secedit'];
517  $parser['secedit'] = false;
518  //fetch parsed locale
519  $html = io_cacheParse(localeFN($id));
520  //reset section editing
521  $parser['secedit'] = $se;
522  return $html;
523}
524
525/**
526 * Returns the specified local text in raw format
527 *
528 * @author Andreas Gohr <andi@splitbrain.org>
529 */
530function rawLocale($id){
531  return io_readFile(localeFN($id));
532}
533
534
535/**
536 * Returns the parsed Wikitext for the given id and revision.
537 *
538 * If $excuse is true an explanation is returned if the file
539 * wasn't found
540 *
541 * @author Andreas Gohr <andi@splitbrain.org>
542 */
543function parsedWiki($id,$rev='',$excuse=true){
544  $file = wikiFN($id,$rev);
545  $ret  = '';
546
547  //ensure $id is in global $ID (needed for parsing)
548  global $ID;
549  $ID = $id;
550
551  if($rev){
552    if(@file_exists($file)){
553      $ret = parse(io_readFile($file));
554    }elseif($excuse){
555      $ret = parsedLocale('norev');
556    }
557  }else{
558    if(@file_exists($file)){
559      $ret = io_cacheParse($file);
560    }elseif($excuse){
561      $ret = parsedLocale('newpage');
562    }
563  }
564  return $ret;
565}
566
567/**
568 * Returns the raw WikiText
569 *
570 * @author Andreas Gohr <andi@splitbrain.org>
571 */
572function rawWiki($id,$rev=''){
573  return io_readFile(wikiFN($id,$rev));
574}
575
576/**
577 * Returns the raw Wiki Text in three slices.
578 *
579 * The range parameter needs to have the form "from-to"
580 * and gives the range of the section.
581 * The returned order is prefix, section and suffix.
582 *
583 * @author Andreas Gohr <andi@splitbrain.org>
584 */
585function rawWikiSlices($range,$id,$rev=''){
586  list($from,$to) = split('-',$range,2);
587  $text = io_readFile(wikiFN($id,$rev));
588  $text = split("\n",$text);
589  if(!$from) $from = 0;
590  if(!$to)   $to   = count($text);
591
592  $slices[0] = join("\n",array_slice($text,0,$from));
593  $slices[1] = join("\n",array_slice($text,$from,$to + 1  - $from));
594  $slices[2] = join("\n",array_slice($text,$to+1));
595
596  return $slices;
597}
598
599/**
600 * Joins wiki text slices
601 *
602 * function to join the text slices with correct lineendings again.
603 * When the pretty parameter is set to true it adds additional empty
604 * lines between sections if needed (used on saving).
605 *
606 * @author Andreas Gohr <andi@splitbrain.org>
607 */
608function con($pre,$text,$suf,$pretty=false){
609
610  if($pretty){
611    if($pre && substr($pre,-1) != "\n") $pre .= "\n";
612    if($suf && substr($text,-1) != "\n") $text .= "\n";
613  }
614
615  if($pre) $pre .= "\n";
616  if($suf) $text .= "\n";
617  return $pre.$text.$suf;
618}
619
620/**
621 * print debug messages
622 *
623 * little function to print the content of a var
624 *
625 * @author Andreas Gohr <andi@splitbrain.org>
626 */
627function dbg($msg,$hidden=false){
628  (!$hidden) ? print '<pre class="dbg">' : print "<!--\n";
629  print_r($msg);
630  (!$hidden) ? print '</pre>' : print "\n-->";
631}
632
633/**
634 * Add's an entry to the changelog
635 *
636 * @author Andreas Gohr <andi@splitbrain.org>
637 */
638function addLogEntry($id,$summary=""){
639  global $conf;
640  $id     = cleanID($id);
641  $date   = time();
642  $remote = $_SERVER['REMOTE_ADDR'];
643  $user   = $_SERVER['REMOTE_USER'];
644
645  $logline = join("\t",array($date,$remote,$id,$user,$summary))."\n";
646
647  $fh = fopen($conf['changelog'],'a');
648  if($fh){
649    fwrite($fh,$logline);
650    fclose($fh);
651  }
652}
653
654/**
655 * returns an array of recently changed files using the
656 * changelog
657 *
658 * @author Andreas Gohr <andi@splitbrain.org>
659 */
660function getRecents($num=0,$incdel=false){
661  global $conf;
662  $recent = array();
663  if(!$num) $num = $conf['recent'];
664
665  $loglines = file($conf['changelog']);
666  rsort($loglines); //reverse sort on timestamp
667
668  foreach ($loglines as $line){
669    $line = rtrim($line);        //remove newline
670    if(empty($line)) continue;   //skip empty lines
671    $info = split("\t",$line);   //split into parts
672    //add id if not in yet and file still exists and is allowed to read
673    if(!$recent[$info[2]] &&
674       (@file_exists(wikiFN($info[2])) || $incdel) &&
675       (auth_quickaclcheck($info[2]) >= AUTH_READ)
676      ){
677      $recent[$info[2]]['date'] = $info[0];
678      $recent[$info[2]]['ip']   = $info[1];
679      $recent[$info[2]]['user'] = $info[3];
680      $recent[$info[2]]['sum']  = $info[4];
681      $recent[$info[2]]['del']  = !@file_exists(wikiFN($info[2]));
682    }
683    if(count($recent) >= $num){
684      break; //finish if enough items found
685    }
686  }
687  return $recent;
688}
689
690/**
691 * Saves a wikitext by calling io_saveFile
692 *
693 * @author Andreas Gohr <andi@splitbrain.org>
694 */
695function saveWikiText($id,$text,$summary){
696  global $conf;
697  global $lang;
698  umask($conf['umask']);
699  // ignore if no changes were made
700  if($text == rawWiki($id,'')){
701    return;
702  }
703
704  $file = wikiFN($id);
705  $old  = saveOldRevision($id);
706
707  if (empty($text)){
708    // remove empty files
709    @unlink($file);
710    $del = true;
711    $summary = $lang['deleted']; //autoset summary on deletion
712  }else{
713    // save file (datadir is created in io_saveFile)
714    io_saveFile($file,$text);
715    $del = false;
716  }
717
718  addLogEntry($id,$summary);
719  notify($id,$old,$summary);
720
721  //purge cache on add by updating the purgefile
722  if($conf['purgeonadd'] && (!$old || $del)){
723    io_saveFile($conf['datadir'].'/.cache/purgefile',time());
724  }
725}
726
727/**
728 * moves the current version to the attic and returns its
729 * revision date
730 *
731 * @author Andreas Gohr <andi@splitbrain.org>
732 */
733function saveOldRevision($id){
734	global $conf;
735  umask($conf['umask']);
736  $oldf = wikiFN($id);
737  if(!@file_exists($oldf)) return '';
738  $date = filemtime($oldf);
739  $newf = wikiFN($id,$date);
740  if(substr($newf,-3)=='.gz'){
741    io_saveFile($newf,rawWiki($id));
742  }else{
743    io_makeFileDir($newf);
744    copy($oldf, $newf);
745  }
746  return $date;
747}
748
749/**
750 * Sends a notify mail to the wikiadmin when a page was
751 * changed
752 *
753 * @author Andreas Gohr <andi@splitbrain.org>
754 */
755function notify($id,$rev="",$summary=""){
756  global $lang;
757  global $conf;
758  $hdrs ='';
759  if(empty($conf['notify'])) return; //notify enabled?
760
761  $text = rawLocale('mailtext');
762  $text = str_replace('@DATE@',date($conf['dformat']),$text);
763  $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
764  $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text);
765  $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text);
766  $text = str_replace('@NEWPAGE@',wl($id,'','',true),$text);
767  $text = str_replace('@DOKUWIKIURL@',getBaseURL(true),$text);
768  $text = str_replace('@SUMMARY@',$summary,$text);
769
770  if($rev){
771    $subject = $lang['mail_changed'].' '.$id;
772    $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",'',true),$text);
773    require_once("inc/DifferenceEngine.php");
774    $df  = new Diff(split("\n",rawWiki($id,$rev)),
775                    split("\n",rawWiki($id)));
776    $dformat = new UnifiedDiffFormatter();
777    $diff    = $dformat->format($df);
778  }else{
779    $subject=$lang['mail_newpage'].' '.$id;
780    $text = str_replace('@OLDPAGE@','none',$text);
781    $diff = rawWiki($id);
782  }
783  $text = str_replace('@DIFF@',$diff,$text);
784
785  if (!empty($conf['mailfrom'])) {
786    $hdrs = 'From: '.$conf['mailfrom']."\n";
787  }
788  @mail($conf['notify'],$subject,$text,$hdrs);
789}
790
791/**
792 * Return a list of available page revisons
793 *
794 * @author Andreas Gohr <andi@splitbrain.org>
795 */
796function getRevisions($id){
797  $revd = dirname(wikiFN($id,'foo'));
798  $revs = array();
799  $clid = cleanID($id);
800  if(strrpos($clid,':')) $clid = substr($clid,strrpos($clid,':')+1); //remove path
801
802  if (is_dir($revd) && $dh = opendir($revd)) {
803    while (($file = readdir($dh)) !== false) {
804      if (is_dir($revd.'/'.$file)) continue;
805      if (preg_match('/^'.$clid.'\.(\d+)\.txt(\.gz)?$/',$file,$match)){
806        $revs[]=$match[1];
807      }
808    }
809    closedir($dh);
810  }
811  rsort($revs);
812  return $revs;
813}
814
815/**
816 * downloads a file from the net and saves it to the given location
817 *
818 * @author Andreas Gohr <andi@splitbrain.org>
819 */
820function download($url,$file){
821  $fp = @fopen($url,"rb");
822  if(!$fp) return false;
823
824  while(!feof($fp)){
825    $cont.= fread($fp,1024);
826  }
827  fclose($fp);
828
829  $fp2 = @fopen($file,"w");
830  if(!$fp2) return false;
831  fwrite($fp2,$cont);
832  fclose($fp2);
833  return true;
834}
835
836/**
837 * extracts the query from a google referer
838 *
839 * @author Andreas Gohr <andi@splitbrain.org>
840 */
841function getGoogleQuery(){
842  $url = parse_url($_SERVER['HTTP_REFERER']);
843
844  if(!preg_match("#google\.#i",$url['host'])) return '';
845  $query = array();
846  parse_str($url['query'],$query);
847
848  return $query['q'];
849}
850
851/**
852 * Try to set correct locale
853 *
854 * @author Andreas Gohr <andi@splitbrain.org>
855 */
856function setCorrectLocale(){
857  global $conf;
858  global $lang;
859
860  $enc = strtoupper($lang['encoding']);
861  foreach ($lang['locales'] as $loc){
862    //try locale
863    if(@setlocale(LC_ALL,$loc)) return;
864    //try loceale with encoding
865    if(@setlocale(LC_ALL,"$loc.$enc")) return;
866  }
867  //still here? try to set from environment
868  @setlocale(LC_ALL,"");
869}
870
871/**
872 * Return the human readable size of a file
873 *
874 * @param       int    $size   A file size
875 * @param       int    $dec    A number of decimal places
876 * @author      Martin Benjamin <b.martin@cybernet.ch>
877 * @author      Aidan Lister <aidan@php.net>
878 * @version     1.0.0
879 */
880function filesize_h($size, $dec = 1)
881{
882  $sizes = array('B', 'KB', 'MB', 'GB');
883  $count = count($sizes);
884  $i = 0;
885
886  while ($size >= 1024 && ($i < $count - 1)) {
887    $size /= 1024;
888    $i++;
889  }
890
891  return round($size, $dec) . ' ' . $sizes[$i];
892}
893
894/**
895 * Run a few sanity checks
896 *
897 * @author Andreas Gohr <andi@splitbrain.org>
898 */
899function check(){
900  global $conf;
901  global $INFO;
902
903  if(version_compare(phpversion(),'4.3.0','<')){
904    msg('Your PHP version is too old ('.phpversion().' vs. 4.3.+ recommended)',-1);
905  }elseif(version_compare(phpversion(),'4.3.10','<')){
906    msg('Consider upgrading PHP to 4.3.10 or higher for security reasons (your version: '.phpversion().')',0);
907  }else{
908    msg('PHP version '.phpversion(),1);
909  }
910
911  if(is_writable($conf['changelog'])){
912    msg('Changelog is writable',1);
913  }else{
914    msg('Changelog is not writable',-1);
915  }
916
917  if(is_writable($conf['datadir'])){
918    msg('Datadir is writable',1);
919  }else{
920    msg('Datadir is not writable',-1);
921  }
922
923  if(is_writable($conf['olddir'])){
924    msg('Attic is writable',1);
925  }else{
926    msg('Attic is not writable',-1);
927  }
928
929  if(is_writable($conf['mediadir'])){
930    msg('Mediadir is writable',1);
931  }else{
932    msg('Mediadir is not writable',-1);
933  }
934
935  if(is_writable('conf/users.auth')){
936    msg('conf/users.auth is writable',1);
937  }else{
938    msg('conf/users.auth is not writable',0);
939  }
940
941  if(function_exists('mb_strpos')){
942    if(defined('UTF8_NOMBSTRING')){
943      msg('mb_string extension is available but will not be used',0);
944    }else{
945      msg('mb_string extension is available and will be used',1);
946    }
947  }else{
948    msg('mb_string extension not available - PHP only replacements will be used',0);
949  }
950
951  msg('Your current permission for this page is '.$INFO['perm'],0);
952
953  if(is_writable($INFO['filepath'])){
954    msg('The current page is writable by the webserver',0);
955  }else{
956    msg('The current page is not writable by the webserver',0);
957  }
958
959  if($INFO['writable']){
960    msg('The current page is writable by you',0);
961  }else{
962    msg('The current page is not writable you',0);
963  }
964}
965?>
966