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