xref: /dokuwiki/inc/common.php (revision 0647ce3bb66a75de20cf6d9bfaacdc483ab21e5d)
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='',$subNS=true){
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      // exclude subnamespaces
636      if ((!$subNS) && (getNS($info[2]) != $ns)) continue;
637
638      $names[$info[2]] = 1;
639      if(--$first >= 0) continue;  /* skip "first" entries */
640
641      $recent[$info[2]]['date'] = $info[0];
642      $recent[$info[2]]['ip']   = $info[1];
643      $recent[$info[2]]['user'] = $info[3];
644      $recent[$info[2]]['sum']  = $info[4];
645      $recent[$info[2]]['del']  = !@file_exists(wikiFN($info[2]));
646    }
647    if(count($recent) >= $num){
648      break; //finish if enough items found
649    }
650  }
651  return $recent;
652}
653
654/**
655 * gets additonal informations for a certain pagerevison
656 * from the changelog
657 *
658 * @author Andreas Gohr <andi@splitbrain.org>
659 */
660function getRevisionInfo($id,$rev){
661  global $conf;
662
663  if(!$rev) return(null);
664
665  $info = array();
666  if(!@is_readable($conf['changelog'])){
667    msg($conf['changelog'].' is not readable',-1);
668    return $recent;
669  }
670  $loglines = file($conf['changelog']);
671  $loglines = preg_grep("/$rev\t\d+\.\d+\.\d+\.\d+\t$id\t/",$loglines);
672  $loglines = array_reverse($loglines); //reverse sort on timestamp (shouldn't be needed)
673  $line = split("\t",$loglines[0]);
674  $info['date'] = $line[0];
675  $info['ip']   = $line[1];
676  $info['user'] = $line[3];
677  $info['sum']   = $line[4];
678  return $info;
679}
680
681/**
682 * Saves a wikitext by calling io_saveFile
683 *
684 * @author Andreas Gohr <andi@splitbrain.org>
685 */
686function saveWikiText($id,$text,$summary){
687  global $conf;
688  global $lang;
689  umask($conf['umask']);
690  // ignore if no changes were made
691  if($text == rawWiki($id,'')){
692    return;
693  }
694
695  $file = wikiFN($id);
696  $old  = saveOldRevision($id);
697
698  if (empty($text)){
699    // remove empty file
700    @unlink($file);
701    // remove any meta info
702    $mfiles = metaFiles($id);
703    foreach ($mfiles as $mfile) {
704      if (file_exists($mfile)) @unlink($mfile);
705    }
706    $del = true;
707    //autoset summary on deletion
708    if(empty($summary)) $summary = $lang['deleted'];
709    //remove empty namespaces
710    io_sweepNS($id);
711  }else{
712    // save file (datadir is created in io_saveFile)
713    io_saveFile($file,$text);
714    $del = false;
715  }
716
717  addLogEntry(@filemtime($file),$id,$summary);
718  // send notify mails
719  notify($id,'admin',$old,$summary);
720  notify($id,'subscribers',$old,$summary);
721
722  //purge cache on add by updating the purgefile
723  if($conf['purgeonadd'] && (!$old || $del)){
724    io_saveFile($conf['cachedir'].'/purgefile',time());
725  }
726}
727
728/**
729 * moves the current version to the attic and returns its
730 * revision date
731 *
732 * @author Andreas Gohr <andi@splitbrain.org>
733 */
734function saveOldRevision($id){
735	global $conf;
736  umask($conf['umask']);
737  $oldf = wikiFN($id);
738  if(!@file_exists($oldf)) return '';
739  $date = filemtime($oldf);
740  $newf = wikiFN($id,$date);
741  if(substr($newf,-3)=='.gz'){
742    io_saveFile($newf,rawWiki($id));
743  }else{
744    io_makeFileDir($newf);
745    copy($oldf, $newf);
746  }
747  return $date;
748}
749
750/**
751 * Sends a notify mail on page change
752 *
753 * @param  string $id       The changed page
754 * @param  string $who      Who to notify (admin|subscribers)
755 * @param  int    $rev      Old page revision
756 * @param  string $summary  What changed
757 *
758 * @author Andreas Gohr <andi@splitbrain.org>
759 */
760function notify($id,$who,$rev='',$summary=''){
761  global $lang;
762  global $conf;
763
764  // decide if there is something to do
765  if($who == 'admin'){
766    if(empty($conf['notify'])) return; //notify enabled?
767    $text = rawLocale('mailtext');
768    $to   = $conf['notify'];
769    $bcc  = '';
770  }elseif($who == 'subscribers'){
771    if(!$conf['subscribers']) return; //subscribers enabled?
772    $bcc  = subscriber_addresslist($id);
773    if(empty($bcc)) return;
774    $to   = '';
775    $text = rawLocale('subscribermail');
776  }else{
777    return; //just to be safe
778  }
779
780  $text = str_replace('@DATE@',date($conf['dformat']),$text);
781  $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
782  $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text);
783  $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text);
784  $text = str_replace('@NEWPAGE@',wl($id,'',true),$text);
785  $text = str_replace('@PAGE@',$id,$text);
786  $text = str_replace('@TITLE@',$conf['title'],$text);
787  $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
788  $text = str_replace('@SUMMARY@',$summary,$text);
789  $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
790
791  if($rev){
792    $subject = $lang['mail_changed'].' '.$id;
793    $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true),$text);
794    require_once("inc/DifferenceEngine.php");
795    $df  = new Diff(split("\n",rawWiki($id,$rev)),
796                    split("\n",rawWiki($id)));
797    $dformat = new UnifiedDiffFormatter();
798    $diff    = $dformat->format($df);
799  }else{
800    $subject=$lang['mail_newpage'].' '.$id;
801    $text = str_replace('@OLDPAGE@','none',$text);
802    $diff = rawWiki($id);
803  }
804  $text = str_replace('@DIFF@',$diff,$text);
805  $subject = '['.$conf['title'].'] '.$subject;
806
807  mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc);
808}
809
810/**
811 * Return a list of available page revisons
812 *
813 * @author Andreas Gohr <andi@splitbrain.org>
814 */
815function getRevisions($id){
816  $revd = dirname(wikiFN($id,'foo'));
817  $revs = array();
818  $clid = cleanID($id);
819  if(strrpos($clid,':')) $clid = substr($clid,strrpos($clid,':')+1); //remove path
820  $clid = utf8_encodeFN($clid);
821
822  if (is_dir($revd) && $dh = opendir($revd)) {
823    while (($file = readdir($dh)) !== false) {
824      if (is_dir($revd.'/'.$file)) continue;
825      if (preg_match('/^'.$clid.'\.(\d+)\.txt(\.gz)?$/',$file,$match)){
826        $revs[]=$match[1];
827      }
828    }
829    closedir($dh);
830  }
831  rsort($revs);
832  return $revs;
833}
834
835/**
836 * extracts the query from a google referer
837 *
838 * @todo   should be more generic and support yahoo et al
839 * @author Andreas Gohr <andi@splitbrain.org>
840 */
841function getGoogleQuery(){
842  $url = parse_url($_SERVER['HTTP_REFERER']);
843  if(!$url) return '';
844
845  if(!preg_match("#google\.#i",$url['host'])) return '';
846  $query = array();
847  parse_str($url['query'],$query);
848
849  return $query['q'];
850}
851
852/**
853 * Try to set correct locale
854 *
855 * @deprecated No longer used
856 * @author     Andreas Gohr <andi@splitbrain.org>
857 */
858function setCorrectLocale(){
859  global $conf;
860  global $lang;
861
862  $enc = strtoupper($lang['encoding']);
863  foreach ($lang['locales'] as $loc){
864    //try locale
865    if(@setlocale(LC_ALL,$loc)) return;
866    //try loceale with encoding
867    if(@setlocale(LC_ALL,"$loc.$enc")) return;
868  }
869  //still here? try to set from environment
870  @setlocale(LC_ALL,"");
871}
872
873/**
874 * Return the human readable size of a file
875 *
876 * @param       int    $size   A file size
877 * @param       int    $dec    A number of decimal places
878 * @author      Martin Benjamin <b.martin@cybernet.ch>
879 * @author      Aidan Lister <aidan@php.net>
880 * @version     1.0.0
881 */
882function filesize_h($size, $dec = 1){
883  $sizes = array('B', 'KB', 'MB', 'GB');
884  $count = count($sizes);
885  $i = 0;
886
887  while ($size >= 1024 && ($i < $count - 1)) {
888    $size /= 1024;
889    $i++;
890  }
891
892  return round($size, $dec) . ' ' . $sizes[$i];
893}
894
895/**
896 * return an obfuscated email address in line with $conf['mailguard'] setting
897 *
898 * @author Harry Fuecks <hfuecks@gmail.com>
899 * @author Christopher Smith <chris@jalakai.co.uk>
900 */
901function obfuscate($email) {
902  global $conf;
903
904  switch ($conf['mailguard']) {
905    case 'visible' :
906      $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
907      return strtr($email, $obfuscate);
908
909    case 'hex' :
910      $encode = '';
911      for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';';
912      return $encode;
913
914    case 'none' :
915    default :
916      return $email;
917  }
918}
919
920/**
921 * Return DokuWikis version
922 *
923 * @author Andreas Gohr <andi@splitbrain.org>
924 */
925function getVersion(){
926  //import version string
927  if(@file_exists('VERSION')){
928    //official release
929    return 'Release '.trim(io_readfile(DOKU_INC.'/VERSION'));
930  }elseif(is_dir('_darcs')){
931    //darcs checkout
932    $inv = file('_darcs/inventory');
933    $inv = preg_grep('#andi@splitbrain\.org\*\*\d{14}#',$inv);
934    $cur = array_pop($inv);
935    preg_match('#\*\*(\d{4})(\d{2})(\d{2})#',$cur,$matches);
936    return 'Darcs '.$matches[1].'-'.$matches[2].'-'.$matches[3];
937  }else{
938    return 'snapshot?';
939  }
940}
941
942/**
943 * Run a few sanity checks
944 *
945 * @author Andreas Gohr <andi@splitbrain.org>
946 */
947function check(){
948  global $conf;
949  global $INFO;
950
951  msg('DokuWiki version: '.getVersion(),1);
952
953  if(version_compare(phpversion(),'4.3.0','<')){
954    msg('Your PHP version is too old ('.phpversion().' vs. 4.3.+ recommended)',-1);
955  }elseif(version_compare(phpversion(),'4.3.10','<')){
956    msg('Consider upgrading PHP to 4.3.10 or higher for security reasons (your version: '.phpversion().')',0);
957  }else{
958    msg('PHP version '.phpversion(),1);
959  }
960
961  if(is_writable($conf['changelog'])){
962    msg('Changelog is writable',1);
963  }else{
964    msg('Changelog is not writable',-1);
965  }
966
967  if(is_writable($conf['datadir'])){
968    msg('Datadir is writable',1);
969  }else{
970    msg('Datadir is not writable',-1);
971  }
972
973  if(is_writable($conf['olddir'])){
974    msg('Attic is writable',1);
975  }else{
976    msg('Attic is not writable',-1);
977  }
978
979  if(is_writable($conf['mediadir'])){
980    msg('Mediadir is writable',1);
981  }else{
982    msg('Mediadir is not writable',-1);
983  }
984
985  if(is_writable($conf['cachedir'])){
986    msg('Cachedir is writable',1);
987  }else{
988    msg('Cachedir is not writable',-1);
989  }
990
991  if(is_writable(DOKU_CONF.'users.auth.php')){
992    msg('conf/users.auth.php is writable',1);
993  }else{
994    msg('conf/users.auth.php is not writable',0);
995  }
996
997  if(function_exists('mb_strpos')){
998    if(defined('UTF8_NOMBSTRING')){
999      msg('mb_string extension is available but will not be used',0);
1000    }else{
1001      msg('mb_string extension is available and will be used',1);
1002    }
1003  }else{
1004    msg('mb_string extension not available - PHP only replacements will be used',0);
1005  }
1006
1007  msg('Your current permission for this page is '.$INFO['perm'],0);
1008
1009  if(is_writable($INFO['filepath'])){
1010    msg('The current page is writable by the webserver',0);
1011  }else{
1012    msg('The current page is not writable by the webserver',0);
1013  }
1014
1015  if($INFO['writable']){
1016    msg('The current page is writable by you',0);
1017  }else{
1018    msg('The current page is not writable you',0);
1019  }
1020}
1021
1022/**
1023 * Let us know if a user is tracking a page
1024 *
1025 * @author Andreas Gohr <andi@splitbrain.org>
1026 */
1027function is_subscribed($id,$uid){
1028  $file=metaFN($id,'.mlist');
1029  if (@file_exists($file)) {
1030    $mlist = file($file);
1031    $pos = array_search($uid."\n",$mlist);
1032    return is_int($pos);
1033  }
1034
1035  return false;
1036}
1037
1038/**
1039 * Return a string with the email addresses of all the
1040 * users subscribed to a page
1041 *
1042 * @author Steven Danz <steven-danz@kc.rr.com>
1043 */
1044function subscriber_addresslist($id){
1045  global $conf;
1046
1047  $emails = '';
1048
1049  if (!$conf['subscribers']) return;
1050
1051  $mlist = array();
1052  $file=metaFN($id,'.mlist');
1053  if (file_exists($file)) {
1054    $mlist = file($file);
1055  }
1056  if(count($mlist) > 0) {
1057    foreach ($mlist as $who) {
1058      $who = rtrim($who);
1059      $info = auth_getUserData($who);
1060      $level = auth_aclcheck($id,$who,$info['grps']);
1061      if ($level >= AUTH_READ) {
1062        if (strcasecmp($info['mail'],$conf['notify']) != 0) {
1063          if (empty($emails)) {
1064            $emails = $info['mail'];
1065          } else {
1066            $emails = "$emails,".$info['mail'];
1067          }
1068        }
1069      }
1070    }
1071  }
1072
1073  return $emails;
1074}
1075
1076/**
1077 * Removes quoting backslashes
1078 *
1079 * @author Andreas Gohr <andi@splitbrain.org>
1080 */
1081function unslash($string,$char="'"){
1082  return str_replace('\\'.$char,$char,$string);
1083}
1084
1085//Setup VIM: ex: et ts=2 enc=utf-8 :
1086