xref: /dokuwiki/inc/common.php (revision 69d17d94c50857e95e30b3becdb2c068f9c764b9)
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
9if(!defined('DOKU_INC')) die('meh.');
10require_once(DOKU_INC.'inc/io.php');
11require_once(DOKU_INC.'inc/changelog.php');
12require_once(DOKU_INC.'inc/utf8.php');
13require_once(DOKU_INC.'inc/mail.php');
14require_once(DOKU_INC.'inc/parserutils.php');
15require_once(DOKU_INC.'inc/infoutils.php');
16
17/**
18 * These constants are used with the recents function
19 */
20define('RECENTS_SKIP_DELETED',2);
21define('RECENTS_SKIP_MINORS',4);
22define('RECENTS_SKIP_SUBSPACES',8);
23define('RECENTS_MEDIA_CHANGES',16);
24
25/**
26 * Wrapper around htmlspecialchars()
27 *
28 * @author Andreas Gohr <andi@splitbrain.org>
29 * @see    htmlspecialchars()
30 */
31function hsc($string){
32  return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
33}
34
35/**
36 * print a newline terminated string
37 *
38 * You can give an indention as optional parameter
39 *
40 * @author Andreas Gohr <andi@splitbrain.org>
41 */
42function ptln($string,$indent=0){
43  echo str_repeat(' ', $indent)."$string\n";
44}
45
46/**
47 * strips control characters (<32) from the given string
48 *
49 * @author Andreas Gohr <andi@splitbrain.org>
50 */
51function stripctl($string){
52  return preg_replace('/[\x00-\x1F]+/s','',$string);
53}
54
55/**
56 * Return a secret token to be used for CSRF attack prevention
57 *
58 * @author  Andreas Gohr <andi@splitbrain.org>
59 * @link    http://en.wikipedia.org/wiki/Cross-site_request_forgery
60 * @link    http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html
61 * @return  string
62 */
63function getSecurityToken(){
64  return md5(auth_cookiesalt().session_id());
65}
66
67/**
68 * Check the secret CSRF token
69 */
70function checkSecurityToken($token=null){
71  if(!$_SERVER['REMOTE_USER']) return true; // no logged in user, no need for a check
72
73  if(is_null($token)) $token = $_REQUEST['sectok'];
74  if(getSecurityToken() != $token){
75    msg('Security Token did not match. Possible CSRF attack.',-1);
76    return false;
77  }
78  return true;
79}
80
81/**
82 * Print a hidden form field with a secret CSRF token
83 *
84 * @author  Andreas Gohr <andi@splitbrain.org>
85 */
86function formSecurityToken($print=true){
87  $ret = '<div class="no"><input type="hidden" name="sectok" value="'.getSecurityToken().'" /></div>'."\n";
88  if($print){
89    echo $ret;
90  }else{
91    return $ret;
92  }
93}
94
95/**
96 * Return info about the current document as associative
97 * array.
98 *
99 * @author Andreas Gohr <andi@splitbrain.org>
100 */
101function pageinfo(){
102  global $ID;
103  global $REV;
104  global $RANGE;
105  global $USERINFO;
106  global $conf;
107  global $lang;
108
109  // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml
110  // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary
111  $info['id'] = $ID;
112  $info['rev'] = $REV;
113
114  if($_SERVER['REMOTE_USER']){
115    $info['userinfo']     = $USERINFO;
116    $info['perm']         = auth_quickaclcheck($ID);
117    $info['subscribed']   = is_subscribed($ID,$_SERVER['REMOTE_USER'],false);
118    $info['subscribedns'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],true);
119    $info['client']       = $_SERVER['REMOTE_USER'];
120
121    // set info about manager/admin status
122    $info['isadmin']   = false;
123    $info['ismanager'] = false;
124    if($info['perm'] == AUTH_ADMIN){
125      $info['isadmin']   = true;
126      $info['ismanager'] = true;
127    }elseif(auth_ismanager()){
128      $info['ismanager'] = true;
129    }
130
131    // if some outside auth were used only REMOTE_USER is set
132    if(!$info['userinfo']['name']){
133      $info['userinfo']['name'] = $_SERVER['REMOTE_USER'];
134    }
135
136  }else{
137    $info['perm']       = auth_aclcheck($ID,'',null);
138    $info['subscribed'] = false;
139    $info['client']     = clientIP(true);
140  }
141
142  $info['namespace'] = getNS($ID);
143  $info['locked']    = checklock($ID);
144  $info['filepath']  = fullpath(wikiFN($ID));
145  $info['exists']    = @file_exists($info['filepath']);
146  if($REV){
147    //check if current revision was meant
148    if($info['exists'] && (@filemtime($info['filepath'])==$REV)){
149      $REV = '';
150    }elseif($RANGE){
151      //section editing does not work with old revisions!
152      $REV   = '';
153      $RANGE = '';
154      msg($lang['nosecedit'],0);
155    }else{
156      //really use old revision
157      $info['filepath'] = fullpath(wikiFN($ID,$REV));
158      $info['exists']   = @file_exists($info['filepath']);
159    }
160  }
161  $info['rev'] = $REV;
162  if($info['exists']){
163    $info['writable'] = (is_writable($info['filepath']) &&
164                         ($info['perm'] >= AUTH_EDIT));
165  }else{
166    $info['writable'] = ($info['perm'] >= AUTH_CREATE);
167  }
168  $info['editable']  = ($info['writable'] && empty($info['lock']));
169  $info['lastmod']   = @filemtime($info['filepath']);
170
171  //load page meta data
172  $info['meta'] = p_get_metadata($ID);
173
174  //who's the editor
175  if($REV){
176    $revinfo = getRevisionInfo($ID, $REV, 1024);
177  }else{
178    if (is_array($info['meta']['last_change'])) {
179       $revinfo = $info['meta']['last_change'];
180    } else {
181      $revinfo = getRevisionInfo($ID, $info['lastmod'], 1024);
182      // cache most recent changelog line in metadata if missing and still valid
183      if ($revinfo!==false) {
184        $info['meta']['last_change'] = $revinfo;
185        p_set_metadata($ID, array('last_change' => $revinfo));
186      }
187    }
188  }
189  //and check for an external edit
190  if($revinfo!==false && $revinfo['date']!=$info['lastmod']){
191    // cached changelog line no longer valid
192    $revinfo = false;
193    $info['meta']['last_change'] = $revinfo;
194    p_set_metadata($ID, array('last_change' => $revinfo));
195  }
196
197  $info['ip']     = $revinfo['ip'];
198  $info['user']   = $revinfo['user'];
199  $info['sum']    = $revinfo['sum'];
200  // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID.
201  // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor'].
202
203  if($revinfo['user']){
204    $info['editor'] = $revinfo['user'];
205  }else{
206    $info['editor'] = $revinfo['ip'];
207  }
208
209  // draft
210  $draft = getCacheName($info['client'].$ID,'.draft');
211  if(@file_exists($draft)){
212    if(@filemtime($draft) < @filemtime(wikiFN($ID))){
213      // remove stale draft
214      @unlink($draft);
215    }else{
216      $info['draft'] = $draft;
217    }
218  }
219
220  // mobile detection
221  $info['ismobile'] = clientismobile();
222
223  return $info;
224}
225
226/**
227 * Build an string of URL parameters
228 *
229 * @author Andreas Gohr
230 */
231function buildURLparams($params, $sep='&amp;'){
232  $url = '';
233  $amp = false;
234  foreach($params as $key => $val){
235    if($amp) $url .= $sep;
236
237    $url .= $key.'=';
238    $url .= rawurlencode((string)$val);
239    $amp = true;
240  }
241  return $url;
242}
243
244/**
245 * Build an string of html tag attributes
246 *
247 * Skips keys starting with '_', values get HTML encoded
248 *
249 * @author Andreas Gohr
250 */
251function buildAttributes($params,$skipempty=false){
252  $url = '';
253  foreach($params as $key => $val){
254    if($key{0} == '_') continue;
255    if($val === '' && $skipempty) continue;
256
257    $url .= $key.'="';
258    $url .= htmlspecialchars ($val);
259    $url .= '" ';
260  }
261  return $url;
262}
263
264
265/**
266 * This builds the breadcrumb trail and returns it as array
267 *
268 * @author Andreas Gohr <andi@splitbrain.org>
269 */
270function breadcrumbs(){
271  // we prepare the breadcrumbs early for quick session closing
272  static $crumbs = null;
273  if($crumbs != null) return $crumbs;
274
275  global $ID;
276  global $ACT;
277  global $conf;
278  $crumbs = $_SESSION[DOKU_COOKIE]['bc'];
279
280  //first visit?
281  if (!is_array($crumbs)){
282    $crumbs = array();
283  }
284  //we only save on show and existing wiki documents
285  $file = wikiFN($ID);
286  if($ACT != 'show' || !@file_exists($file)){
287    $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
288    return $crumbs;
289  }
290
291  // page names
292  $name = noNSorNS($ID);
293  if (useHeading('navigation')) {
294    // get page title
295    $title = p_get_first_heading($ID,true);
296    if ($title) {
297      $name = $title;
298    }
299  }
300
301  //remove ID from array
302  if (isset($crumbs[$ID])) {
303    unset($crumbs[$ID]);
304  }
305
306  //add to array
307  $crumbs[$ID] = $name;
308  //reduce size
309  while(count($crumbs) > $conf['breadcrumbs']){
310    array_shift($crumbs);
311  }
312  //save to session
313  $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
314  return $crumbs;
315}
316
317/**
318 * Filter for page IDs
319 *
320 * This is run on a ID before it is outputted somewhere
321 * currently used to replace the colon with something else
322 * on Windows systems and to have proper URL encoding
323 *
324 * Urlencoding is ommitted when the second parameter is false
325 *
326 * @author Andreas Gohr <andi@splitbrain.org>
327 */
328function idfilter($id,$ue=true){
329  global $conf;
330  if ($conf['useslash'] && $conf['userewrite']){
331    $id = strtr($id,':','/');
332  }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
333      $conf['userewrite']) {
334    $id = strtr($id,':',';');
335  }
336  if($ue){
337    $id = rawurlencode($id);
338    $id = str_replace('%3A',':',$id); //keep as colon
339    $id = str_replace('%2F','/',$id); //keep as slash
340  }
341  return $id;
342}
343
344/**
345 * This builds a link to a wikipage
346 *
347 * It handles URL rewriting and adds additional parameter if
348 * given in $more
349 *
350 * @author Andreas Gohr <andi@splitbrain.org>
351 */
352function wl($id='',$more='',$abs=false,$sep='&amp;'){
353  global $conf;
354  if(is_array($more)){
355    $more = buildURLparams($more,$sep);
356  }else{
357    $more = str_replace(',',$sep,$more);
358  }
359
360  $id    = idfilter($id);
361  if($abs){
362    $xlink = DOKU_URL;
363  }else{
364    $xlink = DOKU_BASE;
365  }
366
367  if($conf['userewrite'] == 2){
368    $xlink .= DOKU_SCRIPT.'/'.$id;
369    if($more) $xlink .= '?'.$more;
370  }elseif($conf['userewrite']){
371    $xlink .= $id;
372    if($more) $xlink .= '?'.$more;
373  }elseif($id){
374    $xlink .= DOKU_SCRIPT.'?id='.$id;
375    if($more) $xlink .= $sep.$more;
376  }else{
377    $xlink .= DOKU_SCRIPT;
378    if($more) $xlink .= '?'.$more;
379  }
380
381  return $xlink;
382}
383
384/**
385 * This builds a link to an alternate page format
386 *
387 * Handles URL rewriting if enabled. Follows the style of wl().
388 *
389 * @author Ben Coburn <btcoburn@silicodon.net>
390 */
391function exportlink($id='',$format='raw',$more='',$abs=false,$sep='&amp;'){
392  global $conf;
393  if(is_array($more)){
394    $more = buildURLparams($more,$sep);
395  }else{
396    $more = str_replace(',',$sep,$more);
397  }
398
399  $format = rawurlencode($format);
400  $id = idfilter($id);
401  if($abs){
402    $xlink = DOKU_URL;
403  }else{
404    $xlink = DOKU_BASE;
405  }
406
407  if($conf['userewrite'] == 2){
408    $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format;
409    if($more) $xlink .= $sep.$more;
410  }elseif($conf['userewrite'] == 1){
411    $xlink .= '_export/'.$format.'/'.$id;
412    if($more) $xlink .= '?'.$more;
413  }else{
414    $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id;
415    if($more) $xlink .= $sep.$more;
416  }
417
418  return $xlink;
419}
420
421/**
422 * Build a link to a media file
423 *
424 * Will return a link to the detail page if $direct is false
425 *
426 * The $more parameter should always be given as array, the function then
427 * will strip default parameters to produce even cleaner URLs
428 *
429 * @param string  $id     - the media file id or URL
430 * @param mixed   $more   - string or array with additional parameters
431 * @param boolean $direct - link to detail page if false
432 * @param string  $sep    - URL parameter separator
433 * @param boolean $abs    - Create an absolute URL
434 */
435function ml($id='',$more='',$direct=true,$sep='&amp;',$abs=false){
436  global $conf;
437  if(is_array($more)){
438    // strip defaults for shorter URLs
439    if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']);
440    if(!$more['w']) unset($more['w']);
441    if(!$more['h']) unset($more['h']);
442    if(isset($more['id']) && $direct) unset($more['id']);
443    $more = buildURLparams($more,$sep);
444  }else{
445    $more = str_replace('cache=cache','',$more); //skip default
446    $more = str_replace(',,',',',$more);
447    $more = str_replace(',',$sep,$more);
448  }
449
450  if($abs){
451    $xlink = DOKU_URL;
452  }else{
453    $xlink = DOKU_BASE;
454  }
455
456  // external URLs are always direct without rewriting
457  if(preg_match('#^(https?|ftp)://#i',$id)){
458    $xlink .= 'lib/exe/fetch.php';
459    // add hash:
460    $xlink .= '?hash='.substr(md5(auth_cookiesalt().$id),0,6);
461    if($more){
462      $xlink .= $sep.$more;
463      $xlink .= $sep.'media='.rawurlencode($id);
464    }else{
465      $xlink .= $sep.'media='.rawurlencode($id);
466    }
467    return $xlink;
468  }
469
470  $id = idfilter($id);
471
472  // decide on scriptname
473  if($direct){
474    if($conf['userewrite'] == 1){
475      $script = '_media';
476    }else{
477      $script = 'lib/exe/fetch.php';
478    }
479  }else{
480    if($conf['userewrite'] == 1){
481      $script = '_detail';
482    }else{
483      $script = 'lib/exe/detail.php';
484    }
485  }
486
487  // build URL based on rewrite mode
488   if($conf['userewrite']){
489     $xlink .= $script.'/'.$id;
490     if($more) $xlink .= '?'.$more;
491   }else{
492     if($more){
493       $xlink .= $script.'?'.$more;
494       $xlink .= $sep.'media='.$id;
495     }else{
496       $xlink .= $script.'?media='.$id;
497     }
498   }
499
500  return $xlink;
501}
502
503
504
505/**
506 * Just builds a link to a script
507 *
508 * @todo   maybe obsolete
509 * @author Andreas Gohr <andi@splitbrain.org>
510 */
511function script($script='doku.php'){
512#  $link = getBaseURL();
513#  $link .= $script;
514#  return $link;
515  return DOKU_BASE.DOKU_SCRIPT;
516}
517
518/**
519 * Spamcheck against wordlist
520 *
521 * Checks the wikitext against a list of blocked expressions
522 * returns true if the text contains any bad words
523 *
524 * Triggers COMMON_WORDBLOCK_BLOCKED
525 *
526 *  Action Plugins can use this event to inspect the blocked data
527 *  and gain information about the user who was blocked.
528 *
529 *  Event data:
530 *    data['matches']  - array of matches
531 *    data['userinfo'] - information about the blocked user
532 *      [ip]           - ip address
533 *      [user]         - username (if logged in)
534 *      [mail]         - mail address (if logged in)
535 *      [name]         - real name (if logged in)
536 *
537 * @author Andreas Gohr <andi@splitbrain.org>
538 * @author Michael Klier <chi@chimeric.de>
539 * @param  string $text - optional text to check, if not given the globals are used
540 * @return bool         - true if a spam word was found
541 */
542function checkwordblock($text=''){
543  global $TEXT;
544  global $PRE;
545  global $SUF;
546  global $conf;
547  global $INFO;
548
549  if(!$conf['usewordblock']) return false;
550
551  if(!$text) $text = "$PRE $TEXT $SUF";
552
553  // we prepare the text a tiny bit to prevent spammers circumventing URL checks
554  $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$text);
555
556  $wordblocks = getWordblocks();
557  //how many lines to read at once (to work around some PCRE limits)
558  if(version_compare(phpversion(),'4.3.0','<')){
559    //old versions of PCRE define a maximum of parenthesises even if no
560    //backreferences are used - the maximum is 99
561    //this is very bad performancewise and may even be too high still
562    $chunksize = 40;
563  }else{
564    //read file in chunks of 200 - this should work around the
565    //MAX_PATTERN_SIZE in modern PCRE
566    $chunksize = 200;
567  }
568  while($blocks = array_splice($wordblocks,0,$chunksize)){
569    $re = array();
570    #build regexp from blocks
571    foreach($blocks as $block){
572      $block = preg_replace('/#.*$/','',$block);
573      $block = trim($block);
574      if(empty($block)) continue;
575      $re[]  = $block;
576    }
577    if(count($re) && preg_match('#('.join('|',$re).')#si',$text,$matches)) {
578      //prepare event data
579      $data['matches'] = $matches;
580      $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR'];
581      if($_SERVER['REMOTE_USER']) {
582          $data['userinfo']['user'] = $_SERVER['REMOTE_USER'];
583          $data['userinfo']['name'] = $INFO['userinfo']['name'];
584          $data['userinfo']['mail'] = $INFO['userinfo']['mail'];
585      }
586      $callback = create_function('', 'return true;');
587      return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true);
588    }
589  }
590  return false;
591}
592
593/**
594 * Return the IP of the client
595 *
596 * Honours X-Forwarded-For and X-Real-IP Proxy Headers
597 *
598 * It returns a comma separated list of IPs if the above mentioned
599 * headers are set. If the single parameter is set, it tries to return
600 * a routable public address, prefering the ones suplied in the X
601 * headers
602 *
603 * @param  boolean $single If set only a single IP is returned
604 * @author Andreas Gohr <andi@splitbrain.org>
605 */
606function clientIP($single=false){
607  $ip = array();
608  $ip[] = $_SERVER['REMOTE_ADDR'];
609  if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
610    $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']));
611  if(!empty($_SERVER['HTTP_X_REAL_IP']))
612    $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_REAL_IP']));
613
614  // some IPv4/v6 regexps borrowed from Feyd
615  // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479
616  $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])';
617  $hex_digit = '[A-Fa-f0-9]';
618  $h16 = "{$hex_digit}{1,4}";
619  $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet";
620  $ls32 = "(?:$h16:$h16|$IPv4Address)";
621  $IPv6Address =
622    "(?:(?:{$IPv4Address})|(?:".
623    "(?:$h16:){6}$ls32" .
624    "|::(?:$h16:){5}$ls32" .
625    "|(?:$h16)?::(?:$h16:){4}$ls32" .
626    "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32" .
627    "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32" .
628    "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32" .
629    "|(?:(?:$h16:){0,4}$h16)?::$ls32" .
630    "|(?:(?:$h16:){0,5}$h16)?::$h16" .
631    "|(?:(?:$h16:){0,6}$h16)?::" .
632    ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)";
633
634  // remove any non-IP stuff
635  $cnt = count($ip);
636  $match = array();
637  for($i=0; $i<$cnt; $i++){
638    if(preg_match("/^$IPv4Address$/",$ip[$i],$match) || preg_match("/^$IPv6Address$/",$ip[$i],$match)) {
639      $ip[$i] = $match[0];
640    } else {
641      $ip[$i] = '';
642    }
643    if(empty($ip[$i])) unset($ip[$i]);
644  }
645  $ip = array_values(array_unique($ip));
646  if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP
647
648  if(!$single) return join(',',$ip);
649
650  // decide which IP to use, trying to avoid local addresses
651  $ip = array_reverse($ip);
652  foreach($ip as $i){
653    if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){
654      continue;
655    }else{
656      return $i;
657    }
658  }
659  // still here? just use the first (last) address
660  return $ip[0];
661}
662
663/**
664 * Check if the browser is on a mobile device
665 *
666 * Adapted from the example code at url below
667 *
668 * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code
669 */
670function clientismobile(){
671
672    if(isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true;
673
674    if(preg_match('/wap\.|\.wap/i',$_SERVER['HTTP_ACCEPT'])) return true;
675
676    if(!isset($_SERVER['HTTP_USER_AGENT'])) return false;
677
678    $uamatches = 'midp|j2me|avantg|docomo|novarra|palmos|palmsource|240x320|opwv|chtml|pda|windows ce|mmp\/|blackberry|mib\/|symbian|wireless|nokia|hand|mobi|phone|cdm|up\.b|audio|SIE\-|SEC\-|samsung|HTC|mot\-|mitsu|sagem|sony|alcatel|lg|erics|vx|NEC|philips|mmm|xx|panasonic|sharp|wap|sch|rover|pocket|benq|java|pt|pg|vox|amoi|bird|compal|kg|voda|sany|kdd|dbt|sendo|sgh|gradi|jb|\d\d\di|moto';
679
680    if(preg_match("/$uamatches/i",$_SERVER['HTTP_USER_AGENT'])) return true;
681
682    return false;
683}
684
685
686/**
687 * Convert one or more comma separated IPs to hostnames
688 *
689 * @author Glen Harris <astfgl@iamnota.org>
690 * @returns a comma separated list of hostnames
691 */
692function gethostsbyaddrs($ips){
693  $hosts = array();
694  $ips = explode(',',$ips);
695
696  if(is_array($ips)) {
697    foreach($ips as $ip){
698      $hosts[] = gethostbyaddr(trim($ip));
699    }
700    return join(',',$hosts);
701  } else {
702    return gethostbyaddr(trim($ips));
703  }
704}
705
706/**
707 * Checks if a given page is currently locked.
708 *
709 * removes stale lockfiles
710 *
711 * @author Andreas Gohr <andi@splitbrain.org>
712 */
713function checklock($id){
714  global $conf;
715  $lock = wikiLockFN($id);
716
717  //no lockfile
718  if(!@file_exists($lock)) return false;
719
720  //lockfile expired
721  if((time() - filemtime($lock)) > $conf['locktime']){
722    @unlink($lock);
723    return false;
724  }
725
726  //my own lock
727  $ip = io_readFile($lock);
728  if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
729    return false;
730  }
731
732  return $ip;
733}
734
735/**
736 * Lock a page for editing
737 *
738 * @author Andreas Gohr <andi@splitbrain.org>
739 */
740function lock($id){
741  $lock = wikiLockFN($id);
742  if($_SERVER['REMOTE_USER']){
743    io_saveFile($lock,$_SERVER['REMOTE_USER']);
744  }else{
745    io_saveFile($lock,clientIP());
746  }
747}
748
749/**
750 * Unlock a page if it was locked by the user
751 *
752 * @author Andreas Gohr <andi@splitbrain.org>
753 * @return bool true if a lock was removed
754 */
755function unlock($id){
756  $lock = wikiLockFN($id);
757  if(@file_exists($lock)){
758    $ip = io_readFile($lock);
759    if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
760      @unlink($lock);
761      return true;
762    }
763  }
764  return false;
765}
766
767/**
768 * convert line ending to unix format
769 *
770 * @see    formText() for 2crlf conversion
771 * @author Andreas Gohr <andi@splitbrain.org>
772 */
773function cleanText($text){
774  $text = preg_replace("/(\015\012)|(\015)/","\012",$text);
775  return $text;
776}
777
778/**
779 * Prepares text for print in Webforms by encoding special chars.
780 * It also converts line endings to Windows format which is
781 * pseudo standard for webforms.
782 *
783 * @see    cleanText() for 2unix conversion
784 * @author Andreas Gohr <andi@splitbrain.org>
785 */
786function formText($text){
787  $text = str_replace("\012","\015\012",$text);
788  return htmlspecialchars($text);
789}
790
791/**
792 * Returns the specified local text in raw format
793 *
794 * @author Andreas Gohr <andi@splitbrain.org>
795 */
796function rawLocale($id){
797  return io_readFile(localeFN($id));
798}
799
800/**
801 * Returns the raw WikiText
802 *
803 * @author Andreas Gohr <andi@splitbrain.org>
804 */
805function rawWiki($id,$rev=''){
806  return io_readWikiPage(wikiFN($id, $rev), $id, $rev);
807}
808
809/**
810 * Returns the pagetemplate contents for the ID's namespace
811 *
812 * @author Andreas Gohr <andi@splitbrain.org>
813 */
814function pageTemplate($data){
815  $id = $data[0];
816  global $conf;
817  global $INFO;
818
819  $path = dirname(wikiFN($id));
820
821  if(@file_exists($path.'/_template.txt')){
822    $tpl = io_readFile($path.'/_template.txt');
823  }else{
824    // search upper namespaces for templates
825    $len = strlen(rtrim($conf['datadir'],'/'));
826    while (strlen($path) >= $len){
827      if(@file_exists($path.'/__template.txt')){
828        $tpl = io_readFile($path.'/__template.txt');
829        break;
830      }
831      $path = substr($path, 0, strrpos($path, '/'));
832    }
833  }
834  if(!$tpl) return '';
835
836  // replace placeholders
837  $file = noNS($id);
838  $page = strtr($file,'_',' ');
839
840  $tpl = str_replace(array(
841                        '@ID@',
842                        '@NS@',
843                        '@FILE@',
844                        '@!FILE@',
845                        '@!FILE!@',
846                        '@PAGE@',
847                        '@!PAGE@',
848                        '@!!PAGE@',
849                        '@!PAGE!@',
850                        '@USER@',
851                        '@NAME@',
852                        '@MAIL@',
853                        '@DATE@',
854                     ),
855                     array(
856                        $id,
857                        getNS($id),
858                        $file,
859                        utf8_ucfirst($file),
860                        utf8_strtoupper($file),
861                        $page,
862                        utf8_ucfirst($page),
863                        utf8_ucwords($page),
864                        utf8_strtoupper($page),
865                        $_SERVER['REMOTE_USER'],
866                        $INFO['userinfo']['name'],
867                        $INFO['userinfo']['mail'],
868                        $conf['dformat'],
869                     ), $tpl);
870
871  // we need the callback to work around strftime's char limit
872  $tpl = preg_replace_callback('/%./',create_function('$m','return strftime($m[0]);'),$tpl);
873
874  return $tpl;
875}
876
877
878/**
879 * Returns the raw Wiki Text in three slices.
880 *
881 * The range parameter needs to have the form "from-to"
882 * and gives the range of the section in bytes - no
883 * UTF-8 awareness is needed.
884 * The returned order is prefix, section and suffix.
885 *
886 * @author Andreas Gohr <andi@splitbrain.org>
887 */
888function rawWikiSlices($range,$id,$rev=''){
889  list($from,$to) = explode('-',$range,2);
890  $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev);
891  if(!$from) $from = 0;
892  if(!$to)   $to   = strlen($text)+1;
893
894  $slices[0] = substr($text,0,$from-1);
895  $slices[1] = substr($text,$from-1,$to-$from);
896  $slices[2] = substr($text,$to);
897
898  return $slices;
899}
900
901/**
902 * Joins wiki text slices
903 *
904 * function to join the text slices with correct lineendings again.
905 * When the pretty parameter is set to true it adds additional empty
906 * lines between sections if needed (used on saving).
907 *
908 * @author Andreas Gohr <andi@splitbrain.org>
909 */
910function con($pre,$text,$suf,$pretty=false){
911
912  if($pretty){
913    if($pre && substr($pre,-1) != "\n") $pre .= "\n";
914    if($suf && substr($text,-1) != "\n") $text .= "\n";
915  }
916
917  // Avoid double newline above section when saving section edit
918  //if($pre) $pre .= "\n";
919  if($suf) $text .= "\n";
920  return $pre.$text.$suf;
921}
922
923/**
924 * Saves a wikitext by calling io_writeWikiPage.
925 * Also directs changelog and attic updates.
926 *
927 * @author Andreas Gohr <andi@splitbrain.org>
928 * @author Ben Coburn <btcoburn@silicodon.net>
929 */
930function saveWikiText($id,$text,$summary,$minor=false){
931  /* Note to developers:
932     This code is subtle and delicate. Test the behavior of
933     the attic and changelog with dokuwiki and external edits
934     after any changes. External edits change the wiki page
935     directly without using php or dokuwiki.
936  */
937  global $conf;
938  global $lang;
939  global $REV;
940  // ignore if no changes were made
941  if($text == rawWiki($id,'')){
942    return;
943  }
944
945  $file = wikiFN($id);
946  $old = @filemtime($file); // from page
947  $wasRemoved = empty($text);
948  $wasCreated = !@file_exists($file);
949  $wasReverted = ($REV==true);
950  $newRev = false;
951  $oldRev = getRevisions($id, -1, 1, 1024); // from changelog
952  $oldRev = (int)(empty($oldRev)?0:$oldRev[0]);
953  if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) {
954    // add old revision to the attic if missing
955    saveOldRevision($id);
956    // add a changelog entry if this edit came from outside dokuwiki
957    if ($old>$oldRev) {
958      addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true));
959      // remove soon to be stale instructions
960      $cache = new cache_instructions($id, $file);
961      $cache->removeCache();
962    }
963  }
964
965  if ($wasRemoved){
966    // Send "update" event with empty data, so plugins can react to page deletion
967    $data = array(array($file, '', false), getNS($id), noNS($id), false);
968    trigger_event('IO_WIKIPAGE_WRITE', $data);
969    // pre-save deleted revision
970    @touch($file);
971    clearstatcache();
972    $newRev = saveOldRevision($id);
973    // remove empty file
974    @unlink($file);
975    // remove old meta info...
976    $mfiles = metaFiles($id);
977    $changelog = metaFN($id, '.changes');
978    $metadata  = metaFN($id, '.meta');
979    foreach ($mfiles as $mfile) {
980      // but keep per-page changelog to preserve page history and keep meta data
981      if (@file_exists($mfile) && $mfile!==$changelog && $mfile!==$metadata) { @unlink($mfile); }
982    }
983    // purge meta data
984    p_purge_metadata($id);
985    $del = true;
986    // autoset summary on deletion
987    if(empty($summary)) $summary = $lang['deleted'];
988    // remove empty namespaces
989    io_sweepNS($id, 'datadir');
990    io_sweepNS($id, 'mediadir');
991  }else{
992    // save file (namespace dir is created in io_writeWikiPage)
993    io_writeWikiPage($file, $text, $id);
994    // pre-save the revision, to keep the attic in sync
995    $newRev = saveOldRevision($id);
996    $del = false;
997  }
998
999  // select changelog line type
1000  $extra = '';
1001  $type = DOKU_CHANGE_TYPE_EDIT;
1002  if ($wasReverted) {
1003    $type = DOKU_CHANGE_TYPE_REVERT;
1004    $extra = $REV;
1005  }
1006  else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; }
1007  else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; }
1008  else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users
1009
1010  addLogEntry($newRev, $id, $type, $summary, $extra);
1011  // send notify mails
1012  notify($id,'admin',$old,$summary,$minor);
1013  notify($id,'subscribers',$old,$summary,$minor);
1014
1015  // update the purgefile (timestamp of the last time anything within the wiki was changed)
1016  io_saveFile($conf['cachedir'].'/purgefile',time());
1017
1018  // if useheading is enabled, purge the cache of all linking pages
1019  if(useHeading('content')){
1020    require_once(DOKU_INC.'inc/fulltext.php');
1021    $pages = ft_backlinks($id);
1022    foreach ($pages as $page) {
1023      $cache = new cache_renderer($page, wikiFN($page), 'xhtml');
1024      $cache->removeCache();
1025    }
1026  }
1027}
1028
1029/**
1030 * moves the current version to the attic and returns its
1031 * revision date
1032 *
1033 * @author Andreas Gohr <andi@splitbrain.org>
1034 */
1035function saveOldRevision($id){
1036  global $conf;
1037  $oldf = wikiFN($id);
1038  if(!@file_exists($oldf)) return '';
1039  $date = filemtime($oldf);
1040  $newf = wikiFN($id,$date);
1041  io_writeWikiPage($newf, rawWiki($id), $id, $date);
1042  return $date;
1043}
1044
1045/**
1046 * Sends a notify mail on page change
1047 *
1048 * @param  string  $id       The changed page
1049 * @param  string  $who      Who to notify (admin|subscribers)
1050 * @param  int     $rev      Old page revision
1051 * @param  string  $summary  What changed
1052 * @param  boolean $minor    Is this a minor edit?
1053 * @param  array   $replace  Additional string substitutions, @KEY@ to be replaced by value
1054 *
1055 * @author Andreas Gohr <andi@splitbrain.org>
1056 */
1057function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
1058  global $lang;
1059  global $conf;
1060  global $INFO;
1061
1062  // decide if there is something to do
1063  if($who == 'admin'){
1064    if(empty($conf['notify'])) return; //notify enabled?
1065    $text = rawLocale('mailtext');
1066    $to   = $conf['notify'];
1067    $bcc  = '';
1068  }elseif($who == 'subscribers'){
1069    if(!$conf['subscribers']) return; //subscribers enabled?
1070    if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors
1071    $bcc  = subscriber_addresslist($id,false);
1072    if(empty($bcc)) return;
1073    $to   = '';
1074    $text = rawLocale('subscribermail');
1075  }elseif($who == 'register'){
1076    if(empty($conf['registernotify'])) return;
1077    $text = rawLocale('registermail');
1078    $to   = $conf['registernotify'];
1079    $bcc  = '';
1080  }else{
1081    return; //just to be safe
1082  }
1083
1084  $ip   = clientIP();
1085  $text = str_replace('@DATE@',strftime($conf['dformat']),$text);
1086  $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
1087  $text = str_replace('@IPADDRESS@',$ip,$text);
1088  $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text);
1089  $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text);
1090  $text = str_replace('@PAGE@',$id,$text);
1091  $text = str_replace('@TITLE@',$conf['title'],$text);
1092  $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
1093  $text = str_replace('@SUMMARY@',$summary,$text);
1094  $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
1095
1096  foreach ($replace as $key => $substitution) {
1097    $text = str_replace('@'.strtoupper($key).'@',$substitution, $text);
1098  }
1099
1100  if($who == 'register'){
1101    $subject = $lang['mail_new_user'].' '.$summary;
1102  }elseif($rev){
1103    $subject = $lang['mail_changed'].' '.$id;
1104    $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text);
1105    require_once(DOKU_INC.'inc/DifferenceEngine.php');
1106    $df  = new Diff(explode("\n",rawWiki($id,$rev)),
1107                    explode("\n",rawWiki($id)));
1108    $dformat = new UnifiedDiffFormatter();
1109    $diff    = $dformat->format($df);
1110  }else{
1111    $subject=$lang['mail_newpage'].' '.$id;
1112    $text = str_replace('@OLDPAGE@','none',$text);
1113    $diff = rawWiki($id);
1114  }
1115  $text = str_replace('@DIFF@',$diff,$text);
1116  $subject = '['.$conf['title'].'] '.$subject;
1117
1118  $from = $conf['mailfrom'];
1119  $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from);
1120  $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from);
1121  $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from);
1122
1123  mail_send($to,$subject,$text,$from,'',$bcc);
1124}
1125
1126/**
1127 * extracts the query from a search engine referrer
1128 *
1129 * @author Andreas Gohr <andi@splitbrain.org>
1130 * @author Todd Augsburger <todd@rollerorgans.com>
1131 */
1132function getGoogleQuery(){
1133  $url = parse_url($_SERVER['HTTP_REFERER']);
1134  if(!$url) return '';
1135
1136  $query = array();
1137  parse_str($url['query'],$query);
1138  if(isset($query['q']))
1139    $q = $query['q'];        // google, live/msn, aol, ask, altavista, alltheweb, gigablast
1140  elseif(isset($query['p']))
1141    $q = $query['p'];        // yahoo
1142  elseif(isset($query['query']))
1143    $q = $query['query'];    // lycos, netscape, clusty, hotbot
1144  elseif(preg_match("#a9\.com#i",$url['host'])) // a9
1145    $q = urldecode(ltrim($url['path'],'/'));
1146
1147  if(!$q) return '';
1148  $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/',$q,-1,PREG_SPLIT_NO_EMPTY);
1149  return $q;
1150}
1151
1152/**
1153 * Try to set correct locale
1154 *
1155 * @deprecated No longer used
1156 * @author     Andreas Gohr <andi@splitbrain.org>
1157 */
1158function setCorrectLocale(){
1159  global $conf;
1160  global $lang;
1161
1162  $enc = strtoupper($lang['encoding']);
1163  foreach ($lang['locales'] as $loc){
1164    //try locale
1165    if(@setlocale(LC_ALL,$loc)) return;
1166    //try loceale with encoding
1167    if(@setlocale(LC_ALL,"$loc.$enc")) return;
1168  }
1169  //still here? try to set from environment
1170  @setlocale(LC_ALL,"");
1171}
1172
1173/**
1174 * Return the human readable size of a file
1175 *
1176 * @param       int    $size   A file size
1177 * @param       int    $dec    A number of decimal places
1178 * @author      Martin Benjamin <b.martin@cybernet.ch>
1179 * @author      Aidan Lister <aidan@php.net>
1180 * @version     1.0.0
1181 */
1182function filesize_h($size, $dec = 1){
1183  $sizes = array('B', 'KB', 'MB', 'GB');
1184  $count = count($sizes);
1185  $i = 0;
1186
1187  while ($size >= 1024 && ($i < $count - 1)) {
1188    $size /= 1024;
1189    $i++;
1190  }
1191
1192  return round($size, $dec) . ' ' . $sizes[$i];
1193}
1194
1195/**
1196 * return an obfuscated email address in line with $conf['mailguard'] setting
1197 *
1198 * @author Harry Fuecks <hfuecks@gmail.com>
1199 * @author Christopher Smith <chris@jalakai.co.uk>
1200 */
1201function obfuscate($email) {
1202  global $conf;
1203
1204  switch ($conf['mailguard']) {
1205    case 'visible' :
1206      $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
1207      return strtr($email, $obfuscate);
1208
1209    case 'hex' :
1210      $encode = '';
1211      for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';';
1212      return $encode;
1213
1214    case 'none' :
1215    default :
1216      return $email;
1217  }
1218}
1219
1220/**
1221 * Let us know if a user is tracking a page or a namespace
1222 *
1223 * @author Andreas Gohr <andi@splitbrain.org>
1224 */
1225function is_subscribed($id,$uid,$ns=false){
1226  if(!$ns) {
1227    $file=metaFN($id,'.mlist');
1228  } else {
1229    if(!getNS($id)) {
1230      $file = metaFN(getNS($id),'.mlist');
1231    } else {
1232      $file = metaFN(getNS($id),'/.mlist');
1233    }
1234  }
1235  if (@file_exists($file)) {
1236    $mlist = file($file);
1237    $pos = array_search($uid."\n",$mlist);
1238    return is_int($pos);
1239  }
1240
1241  return false;
1242}
1243
1244/**
1245 * Return a string with the email addresses of all the
1246 * users subscribed to a page
1247 *
1248 * @author Steven Danz <steven-danz@kc.rr.com>
1249 */
1250function subscriber_addresslist($id,$self=true){
1251  global $conf;
1252  global $auth;
1253
1254  if (!$conf['subscribers']) return '';
1255
1256  $users = array();
1257  $emails = array();
1258
1259  // load the page mlist file content
1260  $mlist = array();
1261  $file=metaFN($id,'.mlist');
1262  if (@file_exists($file)) {
1263    $mlist = file($file);
1264    foreach ($mlist as $who) {
1265      $who = rtrim($who);
1266      if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
1267      $users[$who] = true;
1268    }
1269  }
1270
1271  // load also the namespace mlist file content
1272  $ns = getNS($id);
1273  while ($ns) {
1274    $nsfile = metaFN($ns,'/.mlist');
1275    if (@file_exists($nsfile)) {
1276      $mlist = file($nsfile);
1277      foreach ($mlist as $who) {
1278        $who = rtrim($who);
1279        if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
1280        $users[$who] = true;
1281      }
1282    }
1283    $ns = getNS($ns);
1284  }
1285  // root namespace
1286  $nsfile = metaFN('','.mlist');
1287  if (@file_exists($nsfile)) {
1288    $mlist = file($nsfile);
1289    foreach ($mlist as $who) {
1290      $who = rtrim($who);
1291      if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
1292      $users[$who] = true;
1293    }
1294  }
1295  if(!empty($users)) {
1296    foreach (array_keys($users) as $who) {
1297      $info = $auth->getUserData($who);
1298      if($info === false) continue;
1299      $level = auth_aclcheck($id,$who,$info['grps']);
1300      if ($level >= AUTH_READ) {
1301        if (strcasecmp($info['mail'],$conf['notify']) != 0) {
1302          $emails[] = $info['mail'];
1303        }
1304      }
1305    }
1306  }
1307
1308  return implode(',',$emails);
1309}
1310
1311/**
1312 * Removes quoting backslashes
1313 *
1314 * @author Andreas Gohr <andi@splitbrain.org>
1315 */
1316function unslash($string,$char="'"){
1317  return str_replace('\\'.$char,$char,$string);
1318}
1319
1320/**
1321 * Convert php.ini shorthands to byte
1322 *
1323 * @author <gilthans dot NO dot SPAM at gmail dot com>
1324 * @link   http://de3.php.net/manual/en/ini.core.php#79564
1325 */
1326function php_to_byte($v){
1327    $l = substr($v, -1);
1328    $ret = substr($v, 0, -1);
1329    switch(strtoupper($l)){
1330        case 'P':
1331            $ret *= 1024;
1332        case 'T':
1333            $ret *= 1024;
1334        case 'G':
1335            $ret *= 1024;
1336        case 'M':
1337            $ret *= 1024;
1338        case 'K':
1339            $ret *= 1024;
1340        break;
1341    }
1342    return $ret;
1343}
1344
1345/**
1346 * Wrapper around preg_quote adding the default delimiter
1347 */
1348function preg_quote_cb($string){
1349    return preg_quote($string,'/');
1350}
1351
1352/**
1353 * Shorten a given string by removing data from the middle
1354 *
1355 * You can give the string in two parts, teh first part $keep
1356 * will never be shortened. The second part $short will be cut
1357 * in the middle to shorten but only if at least $min chars are
1358 * left to display it. Otherwise it will be left off.
1359 *
1360 * @param string $keep   the part to keep
1361 * @param string $short  the part to shorten
1362 * @param int    $max    maximum chars you want for the whole string
1363 * @param int    $min    minimum number of chars to have left for middle shortening
1364 * @param string $char   the shortening character to use
1365 */
1366function shorten($keep,$short,$max,$min=9,$char='…'){
1367    $max = $max - utf8_strlen($keep);
1368   if($max < $min) return $keep;
1369    $len = utf8_strlen($short);
1370    if($len <= $max) return $keep.$short;
1371    $half = floor($max/2);
1372    return $keep.utf8_substr($short,0,$half-1).$char.utf8_substr($short,$len-$half);
1373}
1374
1375/**
1376 * Return the users realname or e-mail address for use
1377 * in page footer and recent changes pages
1378 *
1379 * @author Andy Webber <dokuwiki AT andywebber DOT com>
1380 */
1381function editorinfo($username){
1382    global $conf;
1383    global $auth;
1384
1385    switch($conf['showuseras']){
1386      case 'username':
1387      case 'email':
1388      case 'email_link':
1389        if($auth) $info = $auth->getUserData($username);
1390        break;
1391      default:
1392        return hsc($username);
1393    }
1394
1395    if(isset($info) && $info) {
1396        switch($conf['showuseras']){
1397          case 'username':
1398            return hsc($info['name']);
1399          case 'email':
1400            return obfuscate($info['mail']);
1401          case 'email_link':
1402            $mail=obfuscate($info['mail']);
1403            return '<a href="mailto:'.$mail.'">'.$mail.'</a>';
1404          default:
1405            return hsc($username);
1406        }
1407    } else {
1408        return hsc($username);
1409    }
1410}
1411
1412/**
1413 * Returns the path to a image file for the currently chosen license.
1414 * When no image exists, returns an empty string
1415 *
1416 * @author Andreas Gohr <andi@splitbrain.org>
1417 * @param  string $type - type of image 'badge' or 'button'
1418 */
1419function license_img($type){
1420    global $license;
1421    global $conf;
1422    if(!$conf['license']) return '';
1423    if(!is_array($license[$conf['license']])) return '';
1424    $lic = $license[$conf['license']];
1425    $try = array();
1426    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png';
1427    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif';
1428    if(substr($conf['license'],0,3) == 'cc-'){
1429        $try[] = 'lib/images/license/'.$type.'/cc.png';
1430    }
1431    foreach($try as $src){
1432        if(@file_exists(DOKU_INC.$src)) return $src;
1433    }
1434    return '';
1435}
1436
1437/**
1438 * Checks if the given amount of memory is available
1439 *
1440 * If the memory_get_usage() function is not available the
1441 * function just assumes $bytes of already allocated memory
1442 *
1443 * @param  int $mem  Size of memory you want to allocate in bytes
1444 * @param  int $used already allocated memory (see above)
1445 * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
1446 * @author Andreas Gohr <andi@splitbrain.org>
1447 */
1448function is_mem_available($mem,$bytes=1048576){
1449  $limit = trim(ini_get('memory_limit'));
1450  if(empty($limit)) return true; // no limit set!
1451
1452  // parse limit to bytes
1453  $limit = php_to_byte($limit);
1454
1455  // get used memory if possible
1456  if(function_exists('memory_get_usage')){
1457    $used = memory_get_usage();
1458  }
1459
1460  if($used+$mem > $limit){
1461    return false;
1462  }
1463
1464  return true;
1465}
1466
1467/**
1468 * Send a HTTP redirect to the browser
1469 *
1470 * Works arround Microsoft IIS cookie sending bug. Exits the script.
1471 *
1472 * @link   http://support.microsoft.com/kb/q176113/
1473 * @author Andreas Gohr <andi@splitbrain.org>
1474 */
1475function send_redirect($url){
1476    // always close the session
1477    session_write_close();
1478
1479    // check if running on IIS < 6 with CGI-PHP
1480    if( isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) &&
1481        (strpos($_SERVER['GATEWAY_INTERFACE'],'CGI') !== false) &&
1482        (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) &&
1483        $matches[1] < 6 ){
1484        header('Refresh: 0;url='.$url);
1485    }else{
1486        header('Location: '.$url);
1487    }
1488    exit;
1489}
1490
1491//Setup VIM: ex: et ts=2 enc=utf-8 :
1492