xref: /dokuwiki/inc/common.php (revision f4e5a5701bfc0a178a6d0f350e55f9642120acb4)
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  return io_readFile(dirname(wikiFN($id)).'/_template.txt');
508}
509
510
511/**
512 * Returns the raw Wiki Text in three slices.
513 *
514 * The range parameter needs to have the form "from-to"
515 * and gives the range of the section in bytes - no
516 * UTF-8 awareness is needed.
517 * The returned order is prefix, section and suffix.
518 *
519 * @author Andreas Gohr <andi@splitbrain.org>
520 */
521function rawWikiSlices($range,$id,$rev=''){
522  list($from,$to) = split('-',$range,2);
523  $text = io_readFile(wikiFN($id,$rev));
524  if(!$from) $from = 0;
525  if(!$to)   $to   = strlen($text)+1;
526
527  $slices[0] = substr($text,0,$from-1);
528  $slices[1] = substr($text,$from-1,$to-$from);
529  $slices[2] = substr($text,$to);
530
531  return $slices;
532}
533
534/**
535 * Joins wiki text slices
536 *
537 * function to join the text slices with correct lineendings again.
538 * When the pretty parameter is set to true it adds additional empty
539 * lines between sections if needed (used on saving).
540 *
541 * @author Andreas Gohr <andi@splitbrain.org>
542 */
543function con($pre,$text,$suf,$pretty=false){
544
545  if($pretty){
546    if($pre && substr($pre,-1) != "\n") $pre .= "\n";
547    if($suf && substr($text,-1) != "\n") $text .= "\n";
548  }
549
550  if($pre) $pre .= "\n";
551  if($suf) $text .= "\n";
552  return $pre.$text.$suf;
553}
554
555/**
556 * print debug messages
557 *
558 * little function to print the content of a var
559 *
560 * @author Andreas Gohr <andi@splitbrain.org>
561 */
562function dbg($msg,$hidden=false){
563  (!$hidden) ? print '<pre class="dbg">' : print "<!--\n";
564  print_r($msg);
565  (!$hidden) ? print '</pre>' : print "\n-->";
566}
567
568/**
569 * Add's an entry to the changelog
570 *
571 * @author Andreas Gohr <andi@splitbrain.org>
572 */
573function addLogEntry($date,$id,$summary=""){
574  global $conf;
575  $id     = cleanID($id);//FIXME not needed anymore?
576
577  if(!@is_writable($conf['changelog'])){
578    msg($conf['changelog'].' is not writable!',-1);
579    return;
580  }
581
582  if(!$date) $date = time(); //use current time if none supplied
583  $remote = $_SERVER['REMOTE_ADDR'];
584  $user   = $_SERVER['REMOTE_USER'];
585
586  $logline = join("\t",array($date,$remote,$id,$user,$summary))."\n";
587
588  //FIXME: use adjusted io_saveFile instead
589  $fh = fopen($conf['changelog'],'a');
590  if($fh){
591    fwrite($fh,$logline);
592    fclose($fh);
593  }
594}
595
596/**
597 * returns an array of recently changed files using the
598 * changelog
599 * first   : first entry in array returned
600 * num     : return 'num' entries
601 *
602 * @author Andreas Gohr <andi@splitbrain.org>
603 */
604function getRecents($first,$num,$incdel=false){
605  global $conf;
606  $recent = array();
607  $names  = array();
608
609  if(!$num)
610    return $recent;
611
612  if(!@is_readable($conf['changelog'])){
613    msg($conf['changelog'].' is not readable',-1);
614    return $recent;
615  }
616
617  $loglines = file($conf['changelog']);
618  rsort($loglines); //reverse sort on timestamp
619
620  foreach ($loglines as $line){
621    $line = rtrim($line);        //remove newline
622    if(empty($line)) continue;   //skip empty lines
623    $info = split("\t",$line);   //split into parts
624    //add id if not in yet and file still exists and is allowed to read
625    if(!$names[$info[2]] &&
626       (@file_exists(wikiFN($info[2])) || $incdel) &&
627       (auth_quickaclcheck($info[2]) >= AUTH_READ)
628      ){
629      $names[$info[2]] = 1;
630      if(--$first >= 0) continue;  /* skip "first" entries */
631
632      $recent[$info[2]]['date'] = $info[0];
633      $recent[$info[2]]['ip']   = $info[1];
634      $recent[$info[2]]['user'] = $info[3];
635      $recent[$info[2]]['sum']  = $info[4];
636      $recent[$info[2]]['del']  = !@file_exists(wikiFN($info[2]));
637    }
638    if(count($recent) >= $num){
639      break; //finish if enough items found
640    }
641  }
642  return $recent;
643}
644
645/**
646 * gets additonal informations for a certain pagerevison
647 * from the changelog
648 *
649 * @author Andreas Gohr <andi@splitbrain.org>
650 */
651function getRevisionInfo($id,$rev){
652  global $conf;
653
654  if(!$rev) return(null);
655
656  $info = array();
657  if(!@is_readable($conf['changelog'])){
658    msg($conf['changelog'].' is not readable',-1);
659    return $recent;
660  }
661  $loglines = file($conf['changelog']);
662  $loglines = preg_grep("/$rev\t\d+\.\d+\.\d+\.\d+\t$id\t/",$loglines);
663  $loglines = array_reverse($loglines); //reverse sort on timestamp (shouldn't be needed)
664  $line = split("\t",$loglines[0]);
665  $info['date'] = $line[0];
666  $info['ip']   = $line[1];
667  $info['user'] = $line[3];
668  $info['sum']   = $line[4];
669  return $info;
670}
671
672/**
673 * Saves a wikitext by calling io_saveFile
674 *
675 * @author Andreas Gohr <andi@splitbrain.org>
676 */
677function saveWikiText($id,$text,$summary){
678  global $conf;
679  global $lang;
680  umask($conf['umask']);
681  // ignore if no changes were made
682  if($text == rawWiki($id,'')){
683    return;
684  }
685
686  $file = wikiFN($id);
687  $old  = saveOldRevision($id);
688
689  if (empty($text)){
690    // remove empty file
691    @unlink($file);
692    // remove any meta info
693    $mfiles = metaFiles($id);
694    foreach ($mfiles as $mfile) {
695      if (file_exists($mfile)) @unlink($mfile);
696    }
697    $del = true;
698    //autoset summary on deletion
699    if(empty($summary)) $summary = $lang['deleted'];
700    //remove empty namespaces
701    io_sweepNS($id);
702  }else{
703    // save file (datadir is created in io_saveFile)
704    io_saveFile($file,$text);
705    $del = false;
706  }
707
708  addLogEntry(@filemtime($file),$id,$summary);
709  // send notify mails
710  notify($id,'admin',$old,$summary);
711  notify($id,'subscribers',$old,$summary);
712
713  //purge cache on add by updating the purgefile
714  if($conf['purgeonadd'] && (!$old || $del)){
715    io_saveFile($conf['cachedir'].'/purgefile',time());
716  }
717}
718
719/**
720 * moves the current version to the attic and returns its
721 * revision date
722 *
723 * @author Andreas Gohr <andi@splitbrain.org>
724 */
725function saveOldRevision($id){
726	global $conf;
727  umask($conf['umask']);
728  $oldf = wikiFN($id);
729  if(!@file_exists($oldf)) return '';
730  $date = filemtime($oldf);
731  $newf = wikiFN($id,$date);
732  if(substr($newf,-3)=='.gz'){
733    io_saveFile($newf,rawWiki($id));
734  }else{
735    io_makeFileDir($newf);
736    copy($oldf, $newf);
737  }
738  return $date;
739}
740
741/**
742 * Sends a notify mail on page change
743 *
744 * @param  string $id       The changed page
745 * @param  string $who      Who to notify (admin|subscribers)
746 * @param  int    $rev      Old page revision
747 * @param  string $summary  What changed
748 *
749 * @author Andreas Gohr <andi@splitbrain.org>
750 */
751function notify($id,$who,$rev='',$summary=''){
752  global $lang;
753  global $conf;
754
755  // decide if there is something to do
756  if($who == 'admin'){
757    if(empty($conf['notify'])) return; //notify enabled?
758    $text = rawLocale('mailtext');
759    $to   = $conf['notify'];
760    $bcc  = '';
761  }elseif($who == 'subscribers'){
762    if(!$conf['subscribers']) return; //subscribers enabled?
763    $bcc  = subscriber_addresslist($id);
764    if(empty($bcc)) return;
765    $to   = '';
766    $text = rawLocale('subscribermail');
767  }else{
768    return; //just to be safe
769  }
770
771  $text = str_replace('@DATE@',date($conf['dformat']),$text);
772  $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
773  $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text);
774  $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text);
775  $text = str_replace('@NEWPAGE@',wl($id,'',true),$text);
776  $text = str_replace('@PAGE@',$id,$text);
777  $text = str_replace('@TITLE@',$conf['title'],$text);
778  $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
779  $text = str_replace('@SUMMARY@',$summary,$text);
780  $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
781
782  if($rev){
783    $subject = $lang['mail_changed'].' '.$id;
784    $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true),$text);
785    require_once("inc/DifferenceEngine.php");
786    $df  = new Diff(split("\n",rawWiki($id,$rev)),
787                    split("\n",rawWiki($id)));
788    $dformat = new UnifiedDiffFormatter();
789    $diff    = $dformat->format($df);
790  }else{
791    $subject=$lang['mail_newpage'].' '.$id;
792    $text = str_replace('@OLDPAGE@','none',$text);
793    $diff = rawWiki($id);
794  }
795  $text = str_replace('@DIFF@',$diff,$text);
796  $subject = '['.$conf['title'].'] '.$subject;
797
798  mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc);
799}
800
801/**
802 * Return a list of available page revisons
803 *
804 * @author Andreas Gohr <andi@splitbrain.org>
805 */
806function getRevisions($id){
807  $revd = dirname(wikiFN($id,'foo'));
808  $revs = array();
809  $clid = cleanID($id);
810  if(strrpos($clid,':')) $clid = substr($clid,strrpos($clid,':')+1); //remove path
811  $clid = utf8_encodeFN($clid);
812
813  if (is_dir($revd) && $dh = opendir($revd)) {
814    while (($file = readdir($dh)) !== false) {
815      if (is_dir($revd.'/'.$file)) continue;
816      if (preg_match('/^'.$clid.'\.(\d+)\.txt(\.gz)?$/',$file,$match)){
817        $revs[]=$match[1];
818      }
819    }
820    closedir($dh);
821  }
822  rsort($revs);
823  return $revs;
824}
825
826/**
827 * extracts the query from a google referer
828 *
829 * @todo   should be more generic and support yahoo et al
830 * @author Andreas Gohr <andi@splitbrain.org>
831 */
832function getGoogleQuery(){
833  $url = parse_url($_SERVER['HTTP_REFERER']);
834  if(!$url) return '';
835
836  if(!preg_match("#google\.#i",$url['host'])) return '';
837  $query = array();
838  parse_str($url['query'],$query);
839
840  return $query['q'];
841}
842
843/**
844 * Try to set correct locale
845 *
846 * @deprecated No longer used
847 * @author     Andreas Gohr <andi@splitbrain.org>
848 */
849function setCorrectLocale(){
850  global $conf;
851  global $lang;
852
853  $enc = strtoupper($lang['encoding']);
854  foreach ($lang['locales'] as $loc){
855    //try locale
856    if(@setlocale(LC_ALL,$loc)) return;
857    //try loceale with encoding
858    if(@setlocale(LC_ALL,"$loc.$enc")) return;
859  }
860  //still here? try to set from environment
861  @setlocale(LC_ALL,"");
862}
863
864/**
865 * Return the human readable size of a file
866 *
867 * @param       int    $size   A file size
868 * @param       int    $dec    A number of decimal places
869 * @author      Martin Benjamin <b.martin@cybernet.ch>
870 * @author      Aidan Lister <aidan@php.net>
871 * @version     1.0.0
872 */
873function filesize_h($size, $dec = 1){
874  $sizes = array('B', 'KB', 'MB', 'GB');
875  $count = count($sizes);
876  $i = 0;
877
878  while ($size >= 1024 && ($i < $count - 1)) {
879    $size /= 1024;
880    $i++;
881  }
882
883  return round($size, $dec) . ' ' . $sizes[$i];
884}
885
886/**
887 * Return DokuWikis version
888 *
889 * @author Andreas Gohr <andi@splitbrain.org>
890 */
891function getVersion(){
892  //import version string
893  if(@file_exists('VERSION')){
894    //official release
895    return 'Release '.trim(io_readfile('VERSION'));
896  }elseif(is_dir('_darcs')){
897    //darcs checkout
898    $inv = file('_darcs/inventory');
899    $inv = preg_grep('#andi@splitbrain\.org\*\*\d{14}#',$inv);
900    $cur = array_pop($inv);
901    preg_match('#\*\*(\d{4})(\d{2})(\d{2})#',$cur,$matches);
902    return 'Darcs '.$matches[1].'-'.$matches[2].'-'.$matches[3];
903  }else{
904    return 'snapshot?';
905  }
906}
907
908/**
909 * Run a few sanity checks
910 *
911 * @author Andreas Gohr <andi@splitbrain.org>
912 */
913function check(){
914  global $conf;
915  global $INFO;
916
917  msg('DokuWiki version: '.getVersion(),1);
918
919  if(version_compare(phpversion(),'4.3.0','<')){
920    msg('Your PHP version is too old ('.phpversion().' vs. 4.3.+ recommended)',-1);
921  }elseif(version_compare(phpversion(),'4.3.10','<')){
922    msg('Consider upgrading PHP to 4.3.10 or higher for security reasons (your version: '.phpversion().')',0);
923  }else{
924    msg('PHP version '.phpversion(),1);
925  }
926
927  if(is_writable($conf['changelog'])){
928    msg('Changelog is writable',1);
929  }else{
930    msg('Changelog is not writable',-1);
931  }
932
933  if(is_writable($conf['datadir'])){
934    msg('Datadir is writable',1);
935  }else{
936    msg('Datadir is not writable',-1);
937  }
938
939  if(is_writable($conf['olddir'])){
940    msg('Attic is writable',1);
941  }else{
942    msg('Attic is not writable',-1);
943  }
944
945  if(is_writable($conf['mediadir'])){
946    msg('Mediadir is writable',1);
947  }else{
948    msg('Mediadir is not writable',-1);
949  }
950
951  if(is_writable($conf['cachedir'])){
952    msg('Cachedir is writable',1);
953  }else{
954    msg('Cachedir is not writable',-1);
955  }
956
957  if(is_writable(DOKU_CONF.'users.auth.php')){
958    msg('conf/users.auth.php is writable',1);
959  }else{
960    msg('conf/users.auth.php is not writable',0);
961  }
962
963  if(function_exists('mb_strpos')){
964    if(defined('UTF8_NOMBSTRING')){
965      msg('mb_string extension is available but will not be used',0);
966    }else{
967      msg('mb_string extension is available and will be used',1);
968    }
969  }else{
970    msg('mb_string extension not available - PHP only replacements will be used',0);
971  }
972
973  msg('Your current permission for this page is '.$INFO['perm'],0);
974
975  if(is_writable($INFO['filepath'])){
976    msg('The current page is writable by the webserver',0);
977  }else{
978    msg('The current page is not writable by the webserver',0);
979  }
980
981  if($INFO['writable']){
982    msg('The current page is writable by you',0);
983  }else{
984    msg('The current page is not writable you',0);
985  }
986}
987
988/**
989 * Let us know if a user is tracking a page
990 *
991 * @author Andreas Gohr <andi@splitbrain.org>
992 */
993function is_subscribed($id,$uid){
994  $file=metaFN($id,'.mlist');
995  if (@file_exists($file)) {
996    $mlist = file($file);
997    $pos = array_search($uid."\n",$mlist);
998    return is_int($pos);
999  }
1000
1001  return false;
1002}
1003
1004/**
1005 * Return a string with the email addresses of all the
1006 * users subscribed to a page
1007 *
1008 * @author Steven Danz <steven-danz@kc.rr.com>
1009 */
1010function subscriber_addresslist($id){
1011  global $conf;
1012
1013  $emails = '';
1014
1015  if (!$conf['subscribers']) return;
1016
1017  $mlist = array();
1018  $file=metaFN($id,'.mlist');
1019  if (file_exists($file)) {
1020    $mlist = file($file);
1021  }
1022  if(count($mlist) > 0) {
1023    foreach ($mlist as $who) {
1024      $who = rtrim($who);
1025      $info = auth_getUserData($who);
1026      $level = auth_aclcheck($id,$who,$info['grps']);
1027      if ($level >= AUTH_READ) {
1028        if (strcasecmp($info['mail'],$conf['notify']) != 0) {
1029          if (empty($emails)) {
1030            $emails = $info['mail'];
1031          } else {
1032            $emails = "$emails,".$info['mail'];
1033          }
1034        }
1035      }
1036    }
1037  }
1038
1039  return $emails;
1040}
1041
1042//Setup VIM: ex: et ts=2 enc=utf-8 :
1043