Creating repository for dokuwiki modifications for sudaraka.org
[sudaraka-org:dokuwiki-mods.git] / inc / pageutils.php
1 <?php
2 /**
3  * Utilities for handling pagenames
4  *
5  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6  * @author     Andreas Gohr <andi@splitbrain.org>
7  * @todo       Combine similar functions like {wiki,media,meta}FN()
8  */
9
10 /**
11  * Fetch the an ID from request
12  *
13  * Uses either standard $_REQUEST variable or extracts it from
14  * the full request URI when userewrite is set to 2
15  *
16  * For $param='id' $conf['start'] is returned if no id was found.
17  * If the second parameter is true (default) the ID is cleaned.
18  *
19  * @author Andreas Gohr <andi@splitbrain.org>
20  */
21 function getID($param='id',$clean=true){
22     global $conf;
23
24     $id = isset($_REQUEST[$param]) ? $_REQUEST[$param] : null;
25
26     //construct page id from request URI
27     if(empty($id) && $conf['userewrite'] == 2){
28         $request = $_SERVER['REQUEST_URI'];
29         $script = '';
30
31         //get the script URL
32         if($conf['basedir']){
33             $relpath = '';
34             if($param != 'id') {
35                 $relpath = 'lib/exe/';
36             }
37             $script = $conf['basedir'].$relpath.basename($_SERVER['SCRIPT_FILENAME']);
38
39         }elseif($_SERVER['PATH_INFO']){
40             $request = $_SERVER['PATH_INFO'];
41         }elseif($_SERVER['SCRIPT_NAME']){
42             $script = $_SERVER['SCRIPT_NAME'];
43         }elseif($_SERVER['DOCUMENT_ROOT'] && $_SERVER['SCRIPT_FILENAME']){
44             $script = preg_replace ('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'],'/').'/','',
45                     $_SERVER['SCRIPT_FILENAME']);
46             $script = '/'.$script;
47         }
48
49         //clean script and request (fixes a windows problem)
50         $script  = preg_replace('/\/\/+/','/',$script);
51         $request = preg_replace('/\/\/+/','/',$request);
52
53         //remove script URL and Querystring to gain the id
54         if(preg_match('/^'.preg_quote($script,'/').'(.*)/',$request, $match)){
55             $id = preg_replace ('/\?.*/','',$match[1]);
56         }
57         $id = urldecode($id);
58         //strip leading slashes
59         $id = preg_replace('!^/+!','',$id);
60     }
61
62     // Namespace autolinking from URL
63     if(substr($id,-1) == ':' || ($conf['useslash'] && substr($id,-1) == '/')){
64         if(page_exists($id.$conf['start'])){
65             // start page inside namespace
66             $id = $id.$conf['start'];
67         }elseif(page_exists($id.noNS(cleanID($id)))){
68             // page named like the NS inside the NS
69             $id = $id.noNS(cleanID($id));
70         }elseif(page_exists($id)){
71             // page like namespace exists
72             $id = substr($id,0,-1);
73         }else{
74             // fall back to default
75             $id = $id.$conf['start'];
76         }
77         send_redirect(wl($id,'',true));
78     }
79
80     if($clean) $id = cleanID($id);
81     if(empty($id) && $param=='id') $id = $conf['start'];
82
83     return $id;
84 }
85
86 /**
87  * Remove unwanted chars from ID
88  *
89  * Cleans a given ID to only use allowed characters. Accented characters are
90  * converted to unaccented ones
91  *
92  * @author Andreas Gohr <andi@splitbrain.org>
93  * @param  string  $raw_id    The pageid to clean
94  * @param  boolean $ascii     Force ASCII
95  * @param  boolean $media     DEPRECATED
96  */
97 function cleanID($raw_id,$ascii=false,$media=false){
98     global $conf;
99     static $sepcharpat = null;
100
101     global $cache_cleanid;
102     $cache = & $cache_cleanid;
103
104     // check if it's already in the memory cache
105     if (isset($cache[(string)$raw_id])) {
106         return $cache[(string)$raw_id];
107     }
108
109     $sepchar = $conf['sepchar'];
110     if($sepcharpat == null) // build string only once to save clock cycles
111         $sepcharpat = '#\\'.$sepchar.'+#';
112
113     $id = trim((string)$raw_id);
114     $id = utf8_strtolower($id);
115
116     //alternative namespace seperator
117     $id = strtr($id,';',':');
118     if($conf['useslash']){
119         $id = strtr($id,'/',':');
120     }else{
121         $id = strtr($id,'/',$sepchar);
122     }
123
124     if($conf['deaccent'] == 2 || $ascii) $id = utf8_romanize($id);
125     if($conf['deaccent'] || $ascii) $id = utf8_deaccent($id,-1);
126
127     //remove specials
128     $id = utf8_stripspecials($id,$sepchar,'\*');
129
130     if($ascii) $id = utf8_strip($id);
131
132     //clean up
133     $id = preg_replace($sepcharpat,$sepchar,$id);
134     $id = preg_replace('#:+#',':',$id);
135     $id = trim($id,':._-');
136     $id = preg_replace('#:[:\._\-]+#',':',$id);
137     $id = preg_replace('#[:\._\-]+:#',':',$id);
138
139     $cache[(string)$raw_id] = $id;
140     return($id);
141 }
142
143 /**
144  * Return namespacepart of a wiki ID
145  *
146  * @author Andreas Gohr <andi@splitbrain.org>
147  */
148 function getNS($id){
149     $pos = strrpos((string)$id,':');
150     if($pos!==false){
151         return substr((string)$id,0,$pos);
152     }
153     return false;
154 }
155
156 /**
157  * Returns the ID without the namespace
158  *
159  * @author Andreas Gohr <andi@splitbrain.org>
160  */
161 function noNS($id) {
162     $pos = strrpos($id, ':');
163     if ($pos!==false) {
164         return substr($id, $pos+1);
165     } else {
166         return $id;
167     }
168 }
169
170 /**
171  * Returns the current namespace
172  *
173  * @author Nathan Fritz <fritzn@crown.edu>
174  */
175 function curNS($id) {
176     return noNS(getNS($id));
177 }
178
179 /**
180  * Returns the ID without the namespace or current namespace for 'start' pages
181  *
182  * @author Nathan Fritz <fritzn@crown.edu>
183  */
184 function noNSorNS($id) {
185     global $conf;
186
187     $p = noNS($id);
188     if ($p == $conf['start'] || $p == false) {
189         $p = curNS($id);
190         if ($p == false) {
191             return $conf['start'];
192         }
193     }
194     return $p;
195 }
196
197 /**
198  * Creates a XHTML valid linkid from a given headline title
199  *
200  * @param string  $title   The headline title
201  * @param array   $check   Existing IDs (title => number)
202  * @author Andreas Gohr <andi@splitbrain.org>
203  */
204 function sectionID($title,&$check) {
205     $title = str_replace(array(':','.'),'',cleanID($title));
206     $new = ltrim($title,'0123456789_-');
207     if(empty($new)){
208         $title = 'section'.preg_replace('/[^0-9]+/','',$title); //keep numbers from headline
209     }else{
210         $title = $new;
211     }
212
213     if(is_array($check)){
214         // make sure tiles are unique
215         if (!array_key_exists ($title,$check)) {
216            $check[$title] = 0;
217         } else {
218            $title .= ++ $check[$title];
219         }
220     }
221
222     return $title;
223 }
224
225
226 /**
227  * Wiki page existence check
228  *
229  * parameters as for wikiFN
230  *
231  * @author Chris Smith <chris@jalakai.co.uk>
232  */
233 function page_exists($id,$rev='',$clean=true) {
234     return @file_exists(wikiFN($id,$rev,$clean));
235 }
236
237 /**
238  * returns the full path to the datafile specified by ID and optional revision
239  *
240  * The filename is URL encoded to protect Unicode chars
241  *
242  * @param  $raw_id  string   id of wikipage
243  * @param  $rev     string   page revision, empty string for current
244  * @param  $clean   bool     flag indicating that $raw_id should be cleaned.  Only set to false
245  *                           when $id is guaranteed to have been cleaned already.
246  *
247  * @author Andreas Gohr <andi@splitbrain.org>
248  */
249 function wikiFN($raw_id,$rev='',$clean=true){
250     global $conf;
251
252     global $cache_wikifn;
253     $cache = & $cache_wikifn;
254
255     if (isset($cache[$raw_id]) && isset($cache[$raw_id][$rev])) {
256         return $cache[$raw_id][$rev];
257     }
258
259     $id = $raw_id;
260
261     if ($clean) $id = cleanID($id);
262     $id = str_replace(':','/',$id);
263     if(empty($rev)){
264         $fn = $conf['datadir'].'/'.utf8_encodeFN($id).'.txt';
265     }else{
266         $fn = $conf['olddir'].'/'.utf8_encodeFN($id).'.'.$rev.'.txt';
267         if($conf['compression']){
268             //test for extensions here, we want to read both compressions
269             if (@file_exists($fn . '.gz')){
270                 $fn .= '.gz';
271             }else if(@file_exists($fn . '.bz2')){
272                 $fn .= '.bz2';
273             }else{
274                 //file doesnt exist yet, so we take the configured extension
275                 $fn .= '.' . $conf['compression'];
276             }
277         }
278     }
279
280     if (!isset($cache[$raw_id])) { $cache[$raw_id] = array(); }
281     $cache[$raw_id][$rev] = $fn;
282     return $fn;
283 }
284
285 /**
286  * Returns the full path to the file for locking the page while editing.
287  *
288  * @author Ben Coburn <btcoburn@silicodon.net>
289  */
290 function wikiLockFN($id) {
291     global $conf;
292     return $conf['lockdir'].'/'.md5(cleanID($id)).'.lock';
293 }
294
295
296 /**
297  * returns the full path to the meta file specified by ID and extension
298  *
299  * @author Steven Danz <steven-danz@kc.rr.com>
300  */
301 function metaFN($id,$ext){
302     global $conf;
303     $id = cleanID($id);
304     $id = str_replace(':','/',$id);
305     $fn = $conf['metadir'].'/'.utf8_encodeFN($id).$ext;
306     return $fn;
307 }
308
309 /**
310  * returns the full path to the media's meta file specified by ID and extension
311  *
312  * @author Kate Arzamastseva <pshns@ukr.net>
313  */
314 function mediaMetaFN($id,$ext){
315     global $conf;
316     $id = cleanID($id);
317     $id = str_replace(':','/',$id);
318     $fn = $conf['mediametadir'].'/'.utf8_encodeFN($id).$ext;
319     return $fn;
320 }
321
322 /**
323  * returns an array of full paths to all metafiles of a given ID
324  *
325  * @author Esther Brunner <esther@kaffeehaus.ch>
326  * @author Michael Hamann <michael@content-space.de>
327  */
328 function metaFiles($id){
329     $basename = metaFN($id, '');
330     $files    = glob($basename.'.*', GLOB_MARK);
331     // filter files like foo.bar.meta when $id == 'foo'
332     return    $files ? preg_grep('/^'.preg_quote($basename, '/').'\.[^.\/]*$/u', $files) : array();
333 }
334
335 /**
336  * returns the full path to the mediafile specified by ID
337  *
338  * The filename is URL encoded to protect Unicode chars
339  *
340  * @author Andreas Gohr <andi@splitbrain.org>
341  * @author Kate Arzamastseva <pshns@ukr.net>
342  */
343 function mediaFN($id, $rev=''){
344     global $conf;
345     $id = cleanID($id);
346     $id = str_replace(':','/',$id);
347     if(empty($rev)){
348         $fn = $conf['mediadir'].'/'.utf8_encodeFN($id);
349     }else{
350         $ext = mimetype($id);
351         $name = substr($id,0, -1*strlen($ext[0])-1);
352         $fn = $conf['mediaolddir'].'/'.utf8_encodeFN($name .'.'.( (int) $rev ).'.'.$ext[0]);
353     }
354     return $fn;
355 }
356
357 /**
358  * Returns the full filepath to a localized textfile if local
359  * version isn't found the english one is returned
360  *
361  * @author Andreas Gohr <andi@splitbrain.org>
362  */
363 function localeFN($id){
364     global $conf;
365     $file = DOKU_CONF.'/lang/'.$conf['lang'].'/'.$id.'.txt';
366     if(!@file_exists($file)){
367         $file = DOKU_INC.'inc/lang/'.$conf['lang'].'/'.$id.'.txt';
368         if(!@file_exists($file)){
369             //fall back to english
370             $file = DOKU_INC.'inc/lang/en/'.$id.'.txt';
371         }
372     }
373     return $file;
374 }
375
376 /**
377  * Resolve relative paths in IDs
378  *
379  * Do not call directly use resolve_mediaid or resolve_pageid
380  * instead
381  *
382  * Partyly based on a cleanPath function found at
383  * http://www.php.net/manual/en/function.realpath.php#57016
384  *
385  * @author <bart at mediawave dot nl>
386  */
387 function resolve_id($ns,$id,$clean=true){
388     global $conf;
389
390     // some pre cleaning for useslash:
391     if($conf['useslash']) $id = str_replace('/',':',$id);
392
393     // if the id starts with a dot we need to handle the
394     // relative stuff
395     if($id{0} == '.'){
396         // normalize initial dots without a colon
397         $id = preg_replace('/^(\.+)(?=[^:\.])/','\1:',$id);
398         // prepend the current namespace
399         $id = $ns.':'.$id;
400
401         // cleanup relatives
402         $result = array();
403         $pathA  = explode(':', $id);
404         if (!$pathA[0]) $result[] = '';
405         foreach ($pathA AS $key => $dir) {
406             if ($dir == '..') {
407                 if (end($result) == '..') {
408                     $result[] = '..';
409                 } elseif (!array_pop($result)) {
410                     $result[] = '..';
411                 }
412             } elseif ($dir && $dir != '.') {
413                 $result[] = $dir;
414             }
415         }
416         if (!end($pathA)) $result[] = '';
417         $id = implode(':', $result);
418     }elseif($ns !== false && strpos($id,':') === false){
419         //if link contains no namespace. add current namespace (if any)
420         $id = $ns.':'.$id;
421     }
422
423     if($clean) $id = cleanID($id);
424     return $id;
425 }
426
427 /**
428  * Returns a full media id
429  *
430  * @author Andreas Gohr <andi@splitbrain.org>
431  */
432 function resolve_mediaid($ns,&$page,&$exists){
433     $page   = resolve_id($ns,$page);
434     $file   = mediaFN($page);
435     $exists = @file_exists($file);
436 }
437
438 /**
439  * Returns a full page id
440  *
441  * @author Andreas Gohr <andi@splitbrain.org>
442  */
443 function resolve_pageid($ns,&$page,&$exists){
444     global $conf;
445     global $ID;
446     $exists = false;
447
448     //empty address should point to current page
449     if ($page === "") {
450         $page = $ID;
451     }
452
453     //keep hashlink if exists then clean both parts
454     if (strpos($page,'#')) {
455         list($page,$hash) = explode('#',$page,2);
456     } else {
457         $hash = '';
458     }
459     $hash = cleanID($hash);
460     $page = resolve_id($ns,$page,false); // resolve but don't clean, yet
461
462     // get filename (calls clean itself)
463     $file = wikiFN($page);
464
465     // if ends with colon or slash we have a namespace link
466     if(in_array(substr($page,-1), array(':', ';')) ||
467        ($conf['useslash'] && substr($page,-1) == '/')){
468         if(page_exists($page.$conf['start'])){
469             // start page inside namespace
470             $page = $page.$conf['start'];
471             $exists = true;
472         }elseif(page_exists($page.noNS(cleanID($page)))){
473             // page named like the NS inside the NS
474             $page = $page.noNS(cleanID($page));
475             $exists = true;
476         }elseif(page_exists($page)){
477             // page like namespace exists
478             $page = $page;
479             $exists = true;
480         }else{
481             // fall back to default
482             $page = $page.$conf['start'];
483         }
484     }else{
485         //check alternative plural/nonplural form
486         if(!@file_exists($file)){
487             if( $conf['autoplural'] ){
488                 if(substr($page,-1) == 's'){
489                     $try = substr($page,0,-1);
490                 }else{
491                     $try = $page.'s';
492                 }
493                 if(page_exists($try)){
494                     $page   = $try;
495                     $exists = true;
496                 }
497             }
498         }else{
499             $exists = true;
500         }
501     }
502
503     // now make sure we have a clean page
504     $page = cleanID($page);
505
506     //add hash if any
507     if(!empty($hash)) $page .= '#'.$hash;
508 }
509
510 /**
511  * Returns the name of a cachefile from given data
512  *
513  * The needed directory is created by this function!
514  *
515  * @author Andreas Gohr <andi@splitbrain.org>
516  *
517  * @param string $data  This data is used to create a unique md5 name
518  * @param string $ext   This is appended to the filename if given
519  * @return string       The filename of the cachefile
520  */
521 function getCacheName($data,$ext=''){
522     global $conf;
523     $md5  = md5($data);
524     $file = $conf['cachedir'].'/'.$md5{0}.'/'.$md5.$ext;
525     io_makeFileDir($file);
526     return $file;
527 }
528
529 /**
530  * Checks a pageid against $conf['hidepages']
531  *
532  * @author Andreas Gohr <gohr@cosmocode.de>
533  */
534 function isHiddenPage($id){
535     global $conf;
536     global $ACT;
537     if(empty($conf['hidepages'])) return false;
538     if($ACT == 'admin') return false;
539
540     if(preg_match('/'.$conf['hidepages'].'/ui',':'.$id)){
541         return true;
542     }
543     return false;
544 }
545
546 /**
547  * Reverse of isHiddenPage
548  *
549  * @author Andreas Gohr <gohr@cosmocode.de>
550  */
551 function isVisiblePage($id){
552     return !isHiddenPage($id);
553 }
554
555 /**
556  * Format an id for output to a user
557  *
558  * Namespaces are denoted by a trailing “:*”. The root namespace is
559  * “*”. Output is escaped.
560  *
561  * @author Adrian Lang <lang@cosmocode.de>
562  */
563
564 function prettyprint_id($id) {
565     if (!$id || $id === ':') {
566         return '*';
567     }
568     if ((substr($id, -1, 1) === ':')) {
569         $id .= '*';
570     }
571     return hsc($id);
572 }
573
574 /**
575  * Encode a UTF-8 filename to use on any filesystem
576  *
577  * Uses the 'fnencode' option to determine encoding
578  *
579  * When the second parameter is true the string will
580  * be encoded only if non ASCII characters are detected -
581  * This makes it safe to run it multiple times on the
582  * same string (default is true)
583  *
584  * @author Andreas Gohr <andi@splitbrain.org>
585  * @see    urlencode
586  */
587 function utf8_encodeFN($file,$safe=true){
588     global $conf;
589     if($conf['fnencode'] == 'utf-8') return $file;
590
591     if($safe && preg_match('#^[a-zA-Z0-9/_\-\.%]+$#',$file)){
592         return $file;
593     }
594
595     if($conf['fnencode'] == 'safe'){
596         return SafeFN::encode($file);
597     }
598
599     $file = urlencode($file);
600     $file = str_replace('%2F','/',$file);
601     return $file;
602 }
603
604 /**
605  * Decode a filename back to UTF-8
606  *
607  * Uses the 'fnencode' option to determine encoding
608  *
609  * @author Andreas Gohr <andi@splitbrain.org>
610  * @see    urldecode
611  */
612 function utf8_decodeFN($file){
613     global $conf;
614     if($conf['fnencode'] == 'utf-8') return $file;
615
616     if($conf['fnencode'] == 'safe'){
617         return SafeFN::decode($file);
618     }
619
620     return urldecode($file);
621 }
622