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