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