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