xref: /dokuwiki/inc/common.php (revision d4ff305907ac195652cfacfc12a1c52cb8a3f9e0)
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    if($more){
460      $xlink .= '?'.$more;
461      $xlink .= $sep.'media='.rawurlencode($id);
462    }else{
463      $xlink .= '?media='.rawurlencode($id);
464    }
465    return $xlink;
466  }
467
468  $id = idfilter($id);
469
470  // decide on scriptname
471  if($direct){
472    if($conf['userewrite'] == 1){
473      $script = '_media';
474    }else{
475      $script = 'lib/exe/fetch.php';
476    }
477  }else{
478    if($conf['userewrite'] == 1){
479      $script = '_detail';
480    }else{
481      $script = 'lib/exe/detail.php';
482    }
483  }
484
485  // build URL based on rewrite mode
486   if($conf['userewrite']){
487     $xlink .= $script.'/'.$id;
488     if($more) $xlink .= '?'.$more;
489   }else{
490     if($more){
491       $xlink .= $script.'?'.$more;
492       $xlink .= $sep.'media='.$id;
493     }else{
494       $xlink .= $script.'?media='.$id;
495     }
496   }
497
498  return $xlink;
499}
500
501
502
503/**
504 * Just builds a link to a script
505 *
506 * @todo   maybe obsolete
507 * @author Andreas Gohr <andi@splitbrain.org>
508 */
509function script($script='doku.php'){
510#  $link = getBaseURL();
511#  $link .= $script;
512#  return $link;
513  return DOKU_BASE.DOKU_SCRIPT;
514}
515
516/**
517 * Spamcheck against wordlist
518 *
519 * Checks the wikitext against a list of blocked expressions
520 * returns true if the text contains any bad words
521 *
522 * Triggers COMMON_WORDBLOCK_BLOCKED
523 *
524 *  Action Plugins can use this event to inspect the blocked data
525 *  and gain information about the user who was blocked.
526 *
527 *  Event data:
528 *    data['matches']  - array of matches
529 *    data['userinfo'] - information about the blocked user
530 *      [ip]           - ip address
531 *      [user]         - username (if logged in)
532 *      [mail]         - mail address (if logged in)
533 *      [name]         - real name (if logged in)
534 *
535 * @author Andreas Gohr <andi@splitbrain.org>
536 * @author Michael Klier <chi@chimeric.de>
537 * @param  string $text - optional text to check, if not given the globals are used
538 * @return bool         - true if a spam word was found
539 */
540function checkwordblock($text=''){
541  global $TEXT;
542  global $PRE;
543  global $SUF;
544  global $conf;
545  global $INFO;
546
547  if(!$conf['usewordblock']) return false;
548
549  if(!$text) $text = "$PRE $TEXT $SUF";
550
551  // we prepare the text a tiny bit to prevent spammers circumventing URL checks
552  $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$text);
553
554  $wordblocks = getWordblocks();
555  //how many lines to read at once (to work around some PCRE limits)
556  if(version_compare(phpversion(),'4.3.0','<')){
557    //old versions of PCRE define a maximum of parenthesises even if no
558    //backreferences are used - the maximum is 99
559    //this is very bad performancewise and may even be too high still
560    $chunksize = 40;
561  }else{
562    //read file in chunks of 200 - this should work around the
563    //MAX_PATTERN_SIZE in modern PCRE
564    $chunksize = 200;
565  }
566  while($blocks = array_splice($wordblocks,0,$chunksize)){
567    $re = array();
568    #build regexp from blocks
569    foreach($blocks as $block){
570      $block = preg_replace('/#.*$/','',$block);
571      $block = trim($block);
572      if(empty($block)) continue;
573      $re[]  = $block;
574    }
575    if(count($re) && preg_match('#('.join('|',$re).')#si',$text,$matches)) {
576      //prepare event data
577      $data['matches'] = $matches;
578      $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR'];
579      if($_SERVER['REMOTE_USER']) {
580          $data['userinfo']['user'] = $_SERVER['REMOTE_USER'];
581          $data['userinfo']['name'] = $INFO['userinfo']['name'];
582          $data['userinfo']['mail'] = $INFO['userinfo']['mail'];
583      }
584      $callback = create_function('', 'return true;');
585      return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true);
586    }
587  }
588  return false;
589}
590
591/**
592 * Return the IP of the client
593 *
594 * Honours X-Forwarded-For and X-Real-IP Proxy Headers
595 *
596 * It returns a comma separated list of IPs if the above mentioned
597 * headers are set. If the single parameter is set, it tries to return
598 * a routable public address, prefering the ones suplied in the X
599 * headers
600 *
601 * @param  boolean $single If set only a single IP is returned
602 * @author Andreas Gohr <andi@splitbrain.org>
603 */
604function clientIP($single=false){
605  $ip = array();
606  $ip[] = $_SERVER['REMOTE_ADDR'];
607  if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
608    $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']));
609  if(!empty($_SERVER['HTTP_X_REAL_IP']))
610    $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_REAL_IP']));
611
612  // some IPv4/v6 regexps borrowed from Feyd
613  // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479
614  $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])';
615  $hex_digit = '[A-Fa-f0-9]';
616  $h16 = "{$hex_digit}{1,4}";
617  $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet";
618  $ls32 = "(?:$h16:$h16|$IPv4Address)";
619  $IPv6Address =
620    "(?:(?:{$IPv4Address})|(?:".
621    "(?:$h16:){6}$ls32" .
622    "|::(?:$h16:){5}$ls32" .
623    "|(?:$h16)?::(?:$h16:){4}$ls32" .
624    "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32" .
625    "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32" .
626    "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32" .
627    "|(?:(?:$h16:){0,4}$h16)?::$ls32" .
628    "|(?:(?:$h16:){0,5}$h16)?::$h16" .
629    "|(?:(?:$h16:){0,6}$h16)?::" .
630    ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)";
631
632  // remove any non-IP stuff
633  $cnt = count($ip);
634  $match = array();
635  for($i=0; $i<$cnt; $i++){
636    if(preg_match("/^$IPv4Address$/",$ip[$i],$match) || preg_match("/^$IPv6Address$/",$ip[$i],$match)) {
637      $ip[$i] = $match[0];
638    } else {
639      $ip[$i] = '';
640    }
641    if(empty($ip[$i])) unset($ip[$i]);
642  }
643  $ip = array_values(array_unique($ip));
644  if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP
645
646  if(!$single) return join(',',$ip);
647
648  // decide which IP to use, trying to avoid local addresses
649  $ip = array_reverse($ip);
650  foreach($ip as $i){
651    if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){
652      continue;
653    }else{
654      return $i;
655    }
656  }
657  // still here? just use the first (last) address
658  return $ip[0];
659}
660
661/**
662 * Check if the browser is on a mobile device
663 *
664 * Adapted from the example code at url below
665 *
666 * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code
667 */
668function clientismobile(){
669
670    if(isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true;
671
672    if(preg_match('/wap\.|\.wap/i',$_SERVER['HTTP_ACCEPT'])) return true;
673
674    if(!isset($_SERVER['HTTP_USER_AGENT'])) return false;
675
676    $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';
677
678    if(preg_match("/$uamatches/i",$_SERVER['HTTP_USER_AGENT'])) return true;
679
680    return false;
681}
682
683
684/**
685 * Convert one or more comma separated IPs to hostnames
686 *
687 * @author Glen Harris <astfgl@iamnota.org>
688 * @returns a comma separated list of hostnames
689 */
690function gethostsbyaddrs($ips){
691  $hosts = array();
692  $ips = explode(',',$ips);
693
694  if(is_array($ips)) {
695    foreach($ips as $ip){
696      $hosts[] = gethostbyaddr(trim($ip));
697    }
698    return join(',',$hosts);
699  } else {
700    return gethostbyaddr(trim($ips));
701  }
702}
703
704/**
705 * Checks if a given page is currently locked.
706 *
707 * removes stale lockfiles
708 *
709 * @author Andreas Gohr <andi@splitbrain.org>
710 */
711function checklock($id){
712  global $conf;
713  $lock = wikiLockFN($id);
714
715  //no lockfile
716  if(!@file_exists($lock)) return false;
717
718  //lockfile expired
719  if((time() - filemtime($lock)) > $conf['locktime']){
720    @unlink($lock);
721    return false;
722  }
723
724  //my own lock
725  $ip = io_readFile($lock);
726  if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
727    return false;
728  }
729
730  return $ip;
731}
732
733/**
734 * Lock a page for editing
735 *
736 * @author Andreas Gohr <andi@splitbrain.org>
737 */
738function lock($id){
739  $lock = wikiLockFN($id);
740  if($_SERVER['REMOTE_USER']){
741    io_saveFile($lock,$_SERVER['REMOTE_USER']);
742  }else{
743    io_saveFile($lock,clientIP());
744  }
745}
746
747/**
748 * Unlock a page if it was locked by the user
749 *
750 * @author Andreas Gohr <andi@splitbrain.org>
751 * @return bool true if a lock was removed
752 */
753function unlock($id){
754  $lock = wikiLockFN($id);
755  if(@file_exists($lock)){
756    $ip = io_readFile($lock);
757    if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
758      @unlink($lock);
759      return true;
760    }
761  }
762  return false;
763}
764
765/**
766 * convert line ending to unix format
767 *
768 * @see    formText() for 2crlf conversion
769 * @author Andreas Gohr <andi@splitbrain.org>
770 */
771function cleanText($text){
772  $text = preg_replace("/(\015\012)|(\015)/","\012",$text);
773  return $text;
774}
775
776/**
777 * Prepares text for print in Webforms by encoding special chars.
778 * It also converts line endings to Windows format which is
779 * pseudo standard for webforms.
780 *
781 * @see    cleanText() for 2unix conversion
782 * @author Andreas Gohr <andi@splitbrain.org>
783 */
784function formText($text){
785  $text = str_replace("\012","\015\012",$text);
786  return htmlspecialchars($text);
787}
788
789/**
790 * Returns the specified local text in raw format
791 *
792 * @author Andreas Gohr <andi@splitbrain.org>
793 */
794function rawLocale($id){
795  return io_readFile(localeFN($id));
796}
797
798/**
799 * Returns the raw WikiText
800 *
801 * @author Andreas Gohr <andi@splitbrain.org>
802 */
803function rawWiki($id,$rev=''){
804  return io_readWikiPage(wikiFN($id, $rev), $id, $rev);
805}
806
807/**
808 * Returns the pagetemplate contents for the ID's namespace
809 *
810 * @author Andreas Gohr <andi@splitbrain.org>
811 */
812function pageTemplate($data){
813  $id = $data[0];
814  global $conf;
815  global $INFO;
816
817  $path = dirname(wikiFN($id));
818
819  if(@file_exists($path.'/_template.txt')){
820    $tpl = io_readFile($path.'/_template.txt');
821  }else{
822    // search upper namespaces for templates
823    $len = strlen(rtrim($conf['datadir'],'/'));
824    while (strlen($path) >= $len){
825      if(@file_exists($path.'/__template.txt')){
826        $tpl = io_readFile($path.'/__template.txt');
827        break;
828      }
829      $path = substr($path, 0, strrpos($path, '/'));
830    }
831  }
832  if(!$tpl) return '';
833
834  // replace placeholders
835  $file = noNS($id);
836  $page = strtr($file,'_',' ');
837
838  $tpl = str_replace(array(
839                        '@ID@',
840                        '@NS@',
841                        '@FILE@',
842                        '@!FILE@',
843                        '@!FILE!@',
844                        '@PAGE@',
845                        '@!PAGE@',
846                        '@!!PAGE@',
847                        '@!PAGE!@',
848                        '@USER@',
849                        '@NAME@',
850                        '@MAIL@',
851                        '@DATE@',
852                     ),
853                     array(
854                        $id,
855                        getNS($id),
856                        $file,
857                        utf8_ucfirst($file),
858                        utf8_strtoupper($file),
859                        $page,
860                        utf8_ucfirst($page),
861                        utf8_ucwords($page),
862                        utf8_strtoupper($page),
863                        $_SERVER['REMOTE_USER'],
864                        $INFO['userinfo']['name'],
865                        $INFO['userinfo']['mail'],
866                        $conf['dformat'],
867                     ), $tpl);
868
869  // we need the callback to work around strftime's char limit
870  $tpl = preg_replace_callback('/%./',create_function('$m','return strftime($m[0]);'),$tpl);
871
872  return $tpl;
873}
874
875
876/**
877 * Returns the raw Wiki Text in three slices.
878 *
879 * The range parameter needs to have the form "from-to"
880 * and gives the range of the section in bytes - no
881 * UTF-8 awareness is needed.
882 * The returned order is prefix, section and suffix.
883 *
884 * @author Andreas Gohr <andi@splitbrain.org>
885 */
886function rawWikiSlices($range,$id,$rev=''){
887  list($from,$to) = explode('-',$range,2);
888  $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev);
889  if(!$from) $from = 0;
890  if(!$to)   $to   = strlen($text)+1;
891
892  $slices[0] = substr($text,0,$from-1);
893  $slices[1] = substr($text,$from-1,$to-$from);
894  $slices[2] = substr($text,$to);
895
896  return $slices;
897}
898
899/**
900 * Joins wiki text slices
901 *
902 * function to join the text slices with correct lineendings again.
903 * When the pretty parameter is set to true it adds additional empty
904 * lines between sections if needed (used on saving).
905 *
906 * @author Andreas Gohr <andi@splitbrain.org>
907 */
908function con($pre,$text,$suf,$pretty=false){
909
910  if($pretty){
911    if($pre && substr($pre,-1) != "\n") $pre .= "\n";
912    if($suf && substr($text,-1) != "\n") $text .= "\n";
913  }
914
915  // Avoid double newline above section when saving section edit
916  //if($pre) $pre .= "\n";
917  if($suf) $text .= "\n";
918  return $pre.$text.$suf;
919}
920
921/**
922 * Saves a wikitext by calling io_writeWikiPage.
923 * Also directs changelog and attic updates.
924 *
925 * @author Andreas Gohr <andi@splitbrain.org>
926 * @author Ben Coburn <btcoburn@silicodon.net>
927 */
928function saveWikiText($id,$text,$summary,$minor=false){
929  /* Note to developers:
930     This code is subtle and delicate. Test the behavior of
931     the attic and changelog with dokuwiki and external edits
932     after any changes. External edits change the wiki page
933     directly without using php or dokuwiki.
934  */
935  global $conf;
936  global $lang;
937  global $REV;
938  // ignore if no changes were made
939  if($text == rawWiki($id,'')){
940    return;
941  }
942
943  $file = wikiFN($id);
944  $old = @filemtime($file); // from page
945  $wasRemoved = empty($text);
946  $wasCreated = !@file_exists($file);
947  $wasReverted = ($REV==true);
948  $newRev = false;
949  $oldRev = getRevisions($id, -1, 1, 1024); // from changelog
950  $oldRev = (int)(empty($oldRev)?0:$oldRev[0]);
951  if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) {
952    // add old revision to the attic if missing
953    saveOldRevision($id);
954    // add a changelog entry if this edit came from outside dokuwiki
955    if ($old>$oldRev) {
956      addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true));
957      // remove soon to be stale instructions
958      $cache = new cache_instructions($id, $file);
959      $cache->removeCache();
960    }
961  }
962
963  if ($wasRemoved){
964    // Send "update" event with empty data, so plugins can react to page deletion
965    $data = array(array($file, '', false), getNS($id), noNS($id), false);
966    trigger_event('IO_WIKIPAGE_WRITE', $data);
967    // pre-save deleted revision
968    @touch($file);
969    clearstatcache();
970    $newRev = saveOldRevision($id);
971    // remove empty file
972    @unlink($file);
973    // remove old meta info...
974    $mfiles = metaFiles($id);
975    $changelog = metaFN($id, '.changes');
976    $metadata  = metaFN($id, '.meta');
977    foreach ($mfiles as $mfile) {
978      // but keep per-page changelog to preserve page history and keep meta data
979      if (@file_exists($mfile) && $mfile!==$changelog && $mfile!==$metadata) { @unlink($mfile); }
980    }
981    // purge meta data
982    p_purge_metadata($id);
983    $del = true;
984    // autoset summary on deletion
985    if(empty($summary)) $summary = $lang['deleted'];
986    // remove empty namespaces
987    io_sweepNS($id, 'datadir');
988    io_sweepNS($id, 'mediadir');
989  }else{
990    // save file (namespace dir is created in io_writeWikiPage)
991    io_writeWikiPage($file, $text, $id);
992    // pre-save the revision, to keep the attic in sync
993    $newRev = saveOldRevision($id);
994    $del = false;
995  }
996
997  // select changelog line type
998  $extra = '';
999  $type = DOKU_CHANGE_TYPE_EDIT;
1000  if ($wasReverted) {
1001    $type = DOKU_CHANGE_TYPE_REVERT;
1002    $extra = $REV;
1003  }
1004  else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; }
1005  else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; }
1006  else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users
1007
1008  addLogEntry($newRev, $id, $type, $summary, $extra);
1009  // send notify mails
1010  notify($id,'admin',$old,$summary,$minor);
1011  notify($id,'subscribers',$old,$summary,$minor);
1012
1013  // update the purgefile (timestamp of the last time anything within the wiki was changed)
1014  io_saveFile($conf['cachedir'].'/purgefile',time());
1015
1016  // if useheading is enabled, purge the cache of all linking pages
1017  if(useHeading('content')){
1018    require_once(DOKU_INC.'inc/fulltext.php');
1019    $pages = ft_backlinks($id);
1020    foreach ($pages as $page) {
1021      $cache = new cache_renderer($page, wikiFN($page), 'xhtml');
1022      $cache->removeCache();
1023    }
1024  }
1025}
1026
1027/**
1028 * moves the current version to the attic and returns its
1029 * revision date
1030 *
1031 * @author Andreas Gohr <andi@splitbrain.org>
1032 */
1033function saveOldRevision($id){
1034  global $conf;
1035  $oldf = wikiFN($id);
1036  if(!@file_exists($oldf)) return '';
1037  $date = filemtime($oldf);
1038  $newf = wikiFN($id,$date);
1039  io_writeWikiPage($newf, rawWiki($id), $id, $date);
1040  return $date;
1041}
1042
1043/**
1044 * Sends a notify mail on page change
1045 *
1046 * @param  string  $id       The changed page
1047 * @param  string  $who      Who to notify (admin|subscribers)
1048 * @param  int     $rev      Old page revision
1049 * @param  string  $summary  What changed
1050 * @param  boolean $minor    Is this a minor edit?
1051 * @param  array   $replace  Additional string substitutions, @KEY@ to be replaced by value
1052 *
1053 * @author Andreas Gohr <andi@splitbrain.org>
1054 */
1055function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
1056  global $lang;
1057  global $conf;
1058  global $INFO;
1059
1060  // decide if there is something to do
1061  if($who == 'admin'){
1062    if(empty($conf['notify'])) return; //notify enabled?
1063    $text = rawLocale('mailtext');
1064    $to   = $conf['notify'];
1065    $bcc  = '';
1066  }elseif($who == 'subscribers'){
1067    if(!$conf['subscribers']) return; //subscribers enabled?
1068    if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors
1069    $bcc  = subscriber_addresslist($id,false);
1070    if(empty($bcc)) return;
1071    $to   = '';
1072    $text = rawLocale('subscribermail');
1073  }elseif($who == 'register'){
1074    if(empty($conf['registernotify'])) return;
1075    $text = rawLocale('registermail');
1076    $to   = $conf['registernotify'];
1077    $bcc  = '';
1078  }else{
1079    return; //just to be safe
1080  }
1081
1082  $ip   = clientIP();
1083  $text = str_replace('@DATE@',strftime($conf['dformat']),$text);
1084  $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
1085  $text = str_replace('@IPADDRESS@',$ip,$text);
1086  $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text);
1087  $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text);
1088  $text = str_replace('@PAGE@',$id,$text);
1089  $text = str_replace('@TITLE@',$conf['title'],$text);
1090  $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
1091  $text = str_replace('@SUMMARY@',$summary,$text);
1092  $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
1093
1094  foreach ($replace as $key => $substitution) {
1095    $text = str_replace('@'.strtoupper($key).'@',$substitution, $text);
1096  }
1097
1098  if($who == 'register'){
1099    $subject = $lang['mail_new_user'].' '.$summary;
1100  }elseif($rev){
1101    $subject = $lang['mail_changed'].' '.$id;
1102    $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text);
1103    require_once(DOKU_INC.'inc/DifferenceEngine.php');
1104    $df  = new Diff(explode("\n",rawWiki($id,$rev)),
1105                    explode("\n",rawWiki($id)));
1106    $dformat = new UnifiedDiffFormatter();
1107    $diff    = $dformat->format($df);
1108  }else{
1109    $subject=$lang['mail_newpage'].' '.$id;
1110    $text = str_replace('@OLDPAGE@','none',$text);
1111    $diff = rawWiki($id);
1112  }
1113  $text = str_replace('@DIFF@',$diff,$text);
1114  $subject = '['.$conf['title'].'] '.$subject;
1115
1116  $from = $conf['mailfrom'];
1117  $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from);
1118  $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from);
1119  $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from);
1120
1121  mail_send($to,$subject,$text,$from,'',$bcc);
1122}
1123
1124/**
1125 * extracts the query from a search engine referrer
1126 *
1127 * @author Andreas Gohr <andi@splitbrain.org>
1128 * @author Todd Augsburger <todd@rollerorgans.com>
1129 */
1130function getGoogleQuery(){
1131  $url = parse_url($_SERVER['HTTP_REFERER']);
1132  if(!$url) return '';
1133
1134  $query = array();
1135  parse_str($url['query'],$query);
1136  if(isset($query['q']))
1137    $q = $query['q'];        // google, live/msn, aol, ask, altavista, alltheweb, gigablast
1138  elseif(isset($query['p']))
1139    $q = $query['p'];        // yahoo
1140  elseif(isset($query['query']))
1141    $q = $query['query'];    // lycos, netscape, clusty, hotbot
1142  elseif(preg_match("#a9\.com#i",$url['host'])) // a9
1143    $q = urldecode(ltrim($url['path'],'/'));
1144
1145  if(!$q) return '';
1146  $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/',$q,-1,PREG_SPLIT_NO_EMPTY);
1147  return $q;
1148}
1149
1150/**
1151 * Try to set correct locale
1152 *
1153 * @deprecated No longer used
1154 * @author     Andreas Gohr <andi@splitbrain.org>
1155 */
1156function setCorrectLocale(){
1157  global $conf;
1158  global $lang;
1159
1160  $enc = strtoupper($lang['encoding']);
1161  foreach ($lang['locales'] as $loc){
1162    //try locale
1163    if(@setlocale(LC_ALL,$loc)) return;
1164    //try loceale with encoding
1165    if(@setlocale(LC_ALL,"$loc.$enc")) return;
1166  }
1167  //still here? try to set from environment
1168  @setlocale(LC_ALL,"");
1169}
1170
1171/**
1172 * Return the human readable size of a file
1173 *
1174 * @param       int    $size   A file size
1175 * @param       int    $dec    A number of decimal places
1176 * @author      Martin Benjamin <b.martin@cybernet.ch>
1177 * @author      Aidan Lister <aidan@php.net>
1178 * @version     1.0.0
1179 */
1180function filesize_h($size, $dec = 1){
1181  $sizes = array('B', 'KB', 'MB', 'GB');
1182  $count = count($sizes);
1183  $i = 0;
1184
1185  while ($size >= 1024 && ($i < $count - 1)) {
1186    $size /= 1024;
1187    $i++;
1188  }
1189
1190  return round($size, $dec) . ' ' . $sizes[$i];
1191}
1192
1193/**
1194 * return an obfuscated email address in line with $conf['mailguard'] setting
1195 *
1196 * @author Harry Fuecks <hfuecks@gmail.com>
1197 * @author Christopher Smith <chris@jalakai.co.uk>
1198 */
1199function obfuscate($email) {
1200  global $conf;
1201
1202  switch ($conf['mailguard']) {
1203    case 'visible' :
1204      $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
1205      return strtr($email, $obfuscate);
1206
1207    case 'hex' :
1208      $encode = '';
1209      for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';';
1210      return $encode;
1211
1212    case 'none' :
1213    default :
1214      return $email;
1215  }
1216}
1217
1218/**
1219 * Let us know if a user is tracking a page or a namespace
1220 *
1221 * @author Andreas Gohr <andi@splitbrain.org>
1222 */
1223function is_subscribed($id,$uid,$ns=false){
1224  if(!$ns) {
1225    $file=metaFN($id,'.mlist');
1226  } else {
1227    if(!getNS($id)) {
1228      $file = metaFN(getNS($id),'.mlist');
1229    } else {
1230      $file = metaFN(getNS($id),'/.mlist');
1231    }
1232  }
1233  if (@file_exists($file)) {
1234    $mlist = file($file);
1235    $pos = array_search($uid."\n",$mlist);
1236    return is_int($pos);
1237  }
1238
1239  return false;
1240}
1241
1242/**
1243 * Return a string with the email addresses of all the
1244 * users subscribed to a page
1245 *
1246 * @author Steven Danz <steven-danz@kc.rr.com>
1247 */
1248function subscriber_addresslist($id,$self=true){
1249  global $conf;
1250  global $auth;
1251
1252  if (!$conf['subscribers']) return '';
1253
1254  $users = array();
1255  $emails = array();
1256
1257  // load the page mlist file content
1258  $mlist = array();
1259  $file=metaFN($id,'.mlist');
1260  if (@file_exists($file)) {
1261    $mlist = file($file);
1262    foreach ($mlist as $who) {
1263      $who = rtrim($who);
1264      if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
1265      $users[$who] = true;
1266    }
1267  }
1268
1269  // load also the namespace mlist file content
1270  $ns = getNS($id);
1271  while ($ns) {
1272    $nsfile = metaFN($ns,'/.mlist');
1273    if (@file_exists($nsfile)) {
1274      $mlist = file($nsfile);
1275      foreach ($mlist as $who) {
1276        $who = rtrim($who);
1277        if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
1278        $users[$who] = true;
1279      }
1280    }
1281    $ns = getNS($ns);
1282  }
1283  // root namespace
1284  $nsfile = metaFN('','.mlist');
1285  if (@file_exists($nsfile)) {
1286    $mlist = file($nsfile);
1287    foreach ($mlist as $who) {
1288      $who = rtrim($who);
1289      if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
1290      $users[$who] = true;
1291    }
1292  }
1293  if(!empty($users)) {
1294    foreach (array_keys($users) as $who) {
1295      $info = $auth->getUserData($who);
1296      if($info === false) continue;
1297      $level = auth_aclcheck($id,$who,$info['grps']);
1298      if ($level >= AUTH_READ) {
1299        if (strcasecmp($info['mail'],$conf['notify']) != 0) {
1300          $emails[] = $info['mail'];
1301        }
1302      }
1303    }
1304  }
1305
1306  return implode(',',$emails);
1307}
1308
1309/**
1310 * Removes quoting backslashes
1311 *
1312 * @author Andreas Gohr <andi@splitbrain.org>
1313 */
1314function unslash($string,$char="'"){
1315  return str_replace('\\'.$char,$char,$string);
1316}
1317
1318/**
1319 * Convert php.ini shorthands to byte
1320 *
1321 * @author <gilthans dot NO dot SPAM at gmail dot com>
1322 * @link   http://de3.php.net/manual/en/ini.core.php#79564
1323 */
1324function php_to_byte($v){
1325    $l = substr($v, -1);
1326    $ret = substr($v, 0, -1);
1327    switch(strtoupper($l)){
1328        case 'P':
1329            $ret *= 1024;
1330        case 'T':
1331            $ret *= 1024;
1332        case 'G':
1333            $ret *= 1024;
1334        case 'M':
1335            $ret *= 1024;
1336        case 'K':
1337            $ret *= 1024;
1338        break;
1339    }
1340    return $ret;
1341}
1342
1343/**
1344 * Wrapper around preg_quote adding the default delimiter
1345 */
1346function preg_quote_cb($string){
1347    return preg_quote($string,'/');
1348}
1349
1350/**
1351 * Shorten a given string by removing data from the middle
1352 *
1353 * You can give the string in two parts, teh first part $keep
1354 * will never be shortened. The second part $short will be cut
1355 * in the middle to shorten but only if at least $min chars are
1356 * left to display it. Otherwise it will be left off.
1357 *
1358 * @param string $keep   the part to keep
1359 * @param string $short  the part to shorten
1360 * @param int    $max    maximum chars you want for the whole string
1361 * @param int    $min    minimum number of chars to have left for middle shortening
1362 * @param string $char   the shortening character to use
1363 */
1364function shorten($keep,$short,$max,$min=9,$char='…'){
1365    $max = $max - utf8_strlen($keep);
1366   if($max < $min) return $keep;
1367    $len = utf8_strlen($short);
1368    if($len <= $max) return $keep.$short;
1369    $half = floor($max/2);
1370    return $keep.utf8_substr($short,0,$half-1).$char.utf8_substr($short,$len-$half);
1371}
1372
1373/**
1374 * Return the users realname or e-mail address for use
1375 * in page footer and recent changes pages
1376 *
1377 * @author Andy Webber <dokuwiki AT andywebber DOT com>
1378 */
1379function editorinfo($username){
1380    global $conf;
1381    global $auth;
1382
1383    switch($conf['showuseras']){
1384      case 'username':
1385      case 'email':
1386      case 'email_link':
1387        if($auth) $info = $auth->getUserData($username);
1388        break;
1389      default:
1390        return hsc($username);
1391    }
1392
1393    if(isset($info) && $info) {
1394        switch($conf['showuseras']){
1395          case 'username':
1396            return hsc($info['name']);
1397          case 'email':
1398            return obfuscate($info['mail']);
1399          case 'email_link':
1400            $mail=obfuscate($info['mail']);
1401            return '<a href="mailto:'.$mail.'">'.$mail.'</a>';
1402          default:
1403            return hsc($username);
1404        }
1405    } else {
1406        return hsc($username);
1407    }
1408}
1409
1410/**
1411 * Returns the path to a image file for the currently chosen license.
1412 * When no image exists, returns an empty string
1413 *
1414 * @author Andreas Gohr <andi@splitbrain.org>
1415 * @param  string $type - type of image 'badge' or 'button'
1416 */
1417function license_img($type){
1418    global $license;
1419    global $conf;
1420    if(!$conf['license']) return '';
1421    if(!is_array($license[$conf['license']])) return '';
1422    $lic = $license[$conf['license']];
1423    $try = array();
1424    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png';
1425    $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif';
1426    if(substr($conf['license'],0,3) == 'cc-'){
1427        $try[] = 'lib/images/license/'.$type.'/cc.png';
1428    }
1429    foreach($try as $src){
1430        if(@file_exists(DOKU_INC.$src)) return $src;
1431    }
1432    return '';
1433}
1434
1435/**
1436 * Checks if the given amount of memory is available
1437 *
1438 * If the memory_get_usage() function is not available the
1439 * function just assumes $bytes of already allocated memory
1440 *
1441 * @param  int $mem  Size of memory you want to allocate in bytes
1442 * @param  int $used already allocated memory (see above)
1443 * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
1444 * @author Andreas Gohr <andi@splitbrain.org>
1445 */
1446function is_mem_available($mem,$bytes=1048576){
1447  $limit = trim(ini_get('memory_limit'));
1448  if(empty($limit)) return true; // no limit set!
1449
1450  // parse limit to bytes
1451  $limit = php_to_byte($limit);
1452
1453  // get used memory if possible
1454  if(function_exists('memory_get_usage')){
1455    $used = memory_get_usage();
1456  }
1457
1458  if($used+$mem > $limit){
1459    return false;
1460  }
1461
1462  return true;
1463}
1464
1465/**
1466 * Send a HTTP redirect to the browser
1467 *
1468 * Works arround Microsoft IIS cookie sending bug. Exits the script.
1469 *
1470 * @link   http://support.microsoft.com/kb/q176113/
1471 * @author Andreas Gohr <andi@splitbrain.org>
1472 */
1473function send_redirect($url){
1474    // always close the session
1475    session_write_close();
1476
1477    // check if running on IIS < 6 with CGI-PHP
1478    if( isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) &&
1479        (strpos($_SERVER['GATEWAY_INTERFACE'],'CGI') !== false) &&
1480        (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) &&
1481        $matches[1] < 6 ){
1482        header('Refresh: 0;url='.$url);
1483    }else{
1484        header('Location: '.$url);
1485    }
1486    exit;
1487}
1488
1489//Setup VIM: ex: et ts=2 enc=utf-8 :
1490