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 */ 21function 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 //get the script URL 29 if($conf['basedir']){ 30 $relpath = ''; 31 if($param != 'id') { 32 $relpath = 'lib/exe/'; 33 } 34 $script = $conf['basedir'].$relpath.basename($_SERVER['SCRIPT_FILENAME']); 35 }elseif($_SERVER['DOCUMENT_ROOT'] && $_SERVER['SCRIPT_FILENAME']){ 36 $script = preg_replace ('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'],'/').'/','', 37 $_SERVER['SCRIPT_FILENAME']); 38 $script = '/'.$script; 39 }else{ 40 $script = $_SERVER['SCRIPT_NAME']; 41 } 42 43 //clean script and request (fixes a windows problem) 44 $script = preg_replace('/\/\/+/','/',$script); 45 $request = preg_replace('/\/\/+/','/',$_SERVER['REQUEST_URI']); 46 47 //remove script URL and Querystring to gain the id 48 if(preg_match('/^'.preg_quote($script,'/').'(.*)/',$request, $match)){ 49 $id = preg_replace ('/\?.*/','',$match[1]); 50 } 51 $id = urldecode($id); 52 //strip leading slashes 53 $id = preg_replace('!^/+!','',$id); 54 } 55 56 // Namespace autolinking from URL 57 if(substr($id,-1) == ':' || ($conf['useslash'] && substr($id,-1) == '/')){ 58 if(page_exists($id.$conf['start'])){ 59 // start page inside namespace 60 $id = $id.$conf['start']; 61 }elseif(page_exists($id.noNS(cleanID($id)))){ 62 // page named like the NS inside the NS 63 $id = $id.noNS(cleanID($id)); 64 }elseif(page_exists($id)){ 65 // page like namespace exists 66 $id = substr($id,0,-1); 67 }else{ 68 // fall back to default 69 $id = $id.$conf['start']; 70 } 71 header("Location: ".wl($id,'',true)); 72 } 73 74 if($clean) $id = cleanID($id); 75 if(empty($id) && $param=='id') $id = $conf['start']; 76 77 return $id; 78} 79 80/** 81 * Remove unwanted chars from ID 82 * 83 * Cleans a given ID to only use allowed characters. Accented characters are 84 * converted to unaccented ones 85 * 86 * @author Andreas Gohr <andi@splitbrain.org> 87 * @param string $raw_id The pageid to clean 88 * @param boolean $ascii Force ASCII 89 */ 90function cleanID($raw_id,$ascii=false){ 91 global $conf; 92 global $lang; 93 static $sepcharpat = null; 94 95 global $cache_cleanid; 96 $cache = & $cache_cleanid; 97 98 // check if it's already in the memory cache 99 if (isset($cache[$raw_id])) { 100 return $cache[$raw_id]; 101 } 102 103 $sepchar = $conf['sepchar']; 104 if($sepcharpat == null) // build string only once to save clock cycles 105 $sepcharpat = '#\\'.$sepchar.'+#'; 106 107 $id = trim($raw_id); 108 $id = utf8_strtolower($id); 109 110 //alternative namespace seperator 111 $id = strtr($id,';',':'); 112 if($conf['useslash']){ 113 $id = strtr($id,'/',':'); 114 }else{ 115 $id = strtr($id,'/',$sepchar); 116 } 117 118 if($conf['deaccent'] == 2 || $ascii) $id = utf8_romanize($id); 119 if($conf['deaccent'] || $ascii) $id = utf8_deaccent($id,-1); 120 121 //remove specials 122 $id = utf8_stripspecials($id,$sepchar,'\*'); 123 124 if($ascii) $id = utf8_strip($id); 125 126 //clean up 127 $id = preg_replace($sepcharpat,$sepchar,$id); 128 $id = preg_replace('#:+#',':',$id); 129 $id = trim($id,':._-'); 130 $id = preg_replace('#:[:\._\-]+#',':',$id); 131 132 $cache[$raw_id] = $id; 133 return($id); 134} 135 136/** 137 * Return namespacepart of a wiki ID 138 * 139 * @author Andreas Gohr <andi@splitbrain.org> 140 */ 141function getNS($id){ 142 $pos = strrpos($id,':'); 143 if($pos!==false){ 144 return substr($id,0,$pos); 145 } 146 return false; 147} 148 149/** 150 * Returns the ID without the namespace 151 * 152 * @author Andreas Gohr <andi@splitbrain.org> 153 */ 154function noNS($id) { 155 $pos = strrpos($id, ':'); 156 if ($pos!==false) { 157 return substr($id, $pos+1); 158 } else { 159 return $id; 160 } 161} 162 163/** 164* Returns the current namespace 165* 166* @author Nathan Fritz <fritzn@crown.edu> 167*/ 168function curNS($id) { 169 return noNS(getNS($id)); 170} 171 172/** 173* Returns the ID without the namespace or current namespace for 'start' pages 174* 175* @author Nathan Fritz <fritzn@crown.edu> 176*/ 177function noNSorNS($id) { 178 global $conf; 179 180 $p = noNS($id); 181 if ($p == $conf['start']) { 182 $p = curNS($id); 183 if ($p == false) { 184 return noNS($id); 185 } 186 } 187 return $p; 188} 189 190/** 191 * Wiki page existence check 192 * 193 * parameters as for wikiFN 194 * 195 * @author Chris Smith <chris@jalakai.co.uk> 196 */ 197function page_exists($id,$rev='',$clean=true) { 198 return @file_exists(wikiFN($id,$rev,$clean)); 199} 200 201/** 202 * returns the full path to the datafile specified by ID and optional revision 203 * 204 * The filename is URL encoded to protect Unicode chars 205 * 206 * @param $raw_id string id of wikipage 207 * @param $rev string page revision, empty string for current 208 * @param $clean bool flag indicating that $raw_id should be cleaned. Only set to false 209 * when $id is guaranteed to have been cleaned already. 210 * 211 * @author Andreas Gohr <andi@splitbrain.org> 212 */ 213function wikiFN($raw_id,$rev='',$clean=true){ 214 global $conf; 215 216 global $cache_wikifn; 217 $cache = & $cache_wikifn; 218 219 if (isset($cache[$raw_id]) && isset($cache[$raw_id][$rev])) { 220 return $cache[$raw_id][$rev]; 221 } 222 223 $id = $raw_id; 224 225 if ($clean) $id = cleanID($id); 226 $id = str_replace(':','/',$id); 227 if(empty($rev)){ 228 $fn = $conf['datadir'].'/'.utf8_encodeFN($id).'.txt'; 229 }else{ 230 $fn = $conf['olddir'].'/'.utf8_encodeFN($id).'.'.$rev.'.txt'; 231 if($conf['compression']){ 232 //test for extensions here, we want to read both compressions 233 if (@file_exists($fn . '.gz')){ 234 $fn .= '.gz'; 235 }else if(@file_exists($fn . '.bz2')){ 236 $fn .= '.bz2'; 237 }else{ 238 //file doesnt exist yet, so we take the configured extension 239 $fn .= '.' . $conf['compression']; 240 } 241 } 242 } 243 244 if (!isset($cache[$raw_id])) { $cache[$raw_id] = array(); } 245 $cache[$raw_id][$rev] = $fn; 246 return $fn; 247} 248 249/** 250 * Returns the full path to the file for locking the page while editing. 251 * 252 * @author Ben Coburn <btcoburn@silicodon.net> 253 */ 254function wikiLockFN($id) { 255 global $conf; 256 return $conf['lockdir'].'/'.md5(cleanID($id)).'.lock'; 257} 258 259 260/** 261 * returns the full path to the meta file specified by ID and extension 262 * 263 * The filename is URL encoded to protect Unicode chars 264 * 265 * @author Steven Danz <steven-danz@kc.rr.com> 266 */ 267function metaFN($id,$ext){ 268 global $conf; 269 $id = cleanID($id); 270 $id = str_replace(':','/',$id); 271 $fn = $conf['metadir'].'/'.utf8_encodeFN($id).$ext; 272 return $fn; 273} 274 275/** 276 * returns an array of full paths to all metafiles of a given ID 277 * 278 * @author Esther Brunner <esther@kaffeehaus.ch> 279 */ 280function metaFiles($id){ 281 $name = noNS($id); 282 $ns = getNS($id); 283 $dir = ($ns) ? metaFN($ns,'').'/' : metaFN($ns,''); 284 $files = array(); 285 286 $dh = @opendir($dir); 287 if(!$dh) return $files; 288 while(($file = readdir($dh)) !== false){ 289 if(strpos($file,$name.'.') === 0 && !is_dir($dir.$file)) 290 $files[] = $dir.$file; 291 } 292 closedir($dh); 293 294 return $files; 295} 296 297/** 298 * returns the full path to the mediafile specified by ID 299 * 300 * The filename is URL encoded to protect Unicode chars 301 * 302 * @author Andreas Gohr <andi@splitbrain.org> 303 */ 304function mediaFN($id){ 305 global $conf; 306 $id = cleanID($id); 307 $id = str_replace(':','/',$id); 308 $fn = $conf['mediadir'].'/'.utf8_encodeFN($id); 309 return $fn; 310} 311 312/** 313 * Returns the full filepath to a localized textfile if local 314 * version isn't found the english one is returned 315 * 316 * @author Andreas Gohr <andi@splitbrain.org> 317 */ 318function localeFN($id){ 319 global $conf; 320 $file = DOKU_INC.'inc/lang/'.$conf['lang'].'/'.$id.'.txt'; 321 if(!@file_exists($file)){ 322 //fall back to english 323 $file = DOKU_INC.'inc/lang/en/'.$id.'.txt'; 324 } 325 return $file; 326} 327 328/** 329 * Resolve relative paths in IDs 330 * 331 * Do not call directly use resolve_mediaid or resolve_pageid 332 * instead 333 * 334 * Partyly based on a cleanPath function found at 335 * http://www.php.net/manual/en/function.realpath.php#57016 336 * 337 * @author <bart at mediawave dot nl> 338 */ 339function resolve_id($ns,$id,$clean=true){ 340 global $conf; 341 342 // some pre cleaning for useslash: 343 if($conf['useslash']) $id = str_replace('/',':',$id); 344 345 // if the id starts with a dot we need to handle the 346 // relative stuff 347 if($id{0} == '.'){ 348 // normalize initial dots without a colon 349 $id = preg_replace('/^(\.+)(?=[^:\.])/','\1:',$id); 350 // prepend the current namespace 351 $id = $ns.':'.$id; 352 353 // cleanup relatives 354 $result = array(); 355 $pathA = explode(':', $id); 356 if (!$pathA[0]) $result[] = ''; 357 foreach ($pathA AS $key => $dir) { 358 if ($dir == '..') { 359 if (end($result) == '..') { 360 $result[] = '..'; 361 } elseif (!array_pop($result)) { 362 $result[] = '..'; 363 } 364 } elseif ($dir && $dir != '.') { 365 $result[] = $dir; 366 } 367 } 368 if (!end($pathA)) $result[] = ''; 369 $id = implode(':', $result); 370 }elseif($ns !== false && strpos($id,':') === false){ 371 //if link contains no namespace. add current namespace (if any) 372 $id = $ns.':'.$id; 373 } 374 375 if($clean) $id = cleanID($id); 376 return $id; 377} 378 379/** 380 * Returns a full media id 381 * 382 * @author Andreas Gohr <andi@splitbrain.org> 383 */ 384function resolve_mediaid($ns,&$page,&$exists){ 385 $page = resolve_id($ns,$page); 386 $file = mediaFN($page); 387 $exists = @file_exists($file); 388} 389 390/** 391 * Returns a full page id 392 * 393 * @author Andreas Gohr <andi@splitbrain.org> 394 */ 395function resolve_pageid($ns,&$page,&$exists){ 396 global $conf; 397 $exists = false; 398 399 //keep hashlink if exists then clean both parts 400 if (strpos($page,'#')) { 401 list($page,$hash) = split('#',$page,2); 402 } else { 403 $hash = ''; 404 } 405 $hash = cleanID($hash); 406 $page = resolve_id($ns,$page,false); // resolve but don't clean, yet 407 408 // get filename (calls clean itself) 409 $file = wikiFN($page); 410 411 // if ends with colon or slash we have a namespace link 412 if(substr($page,-1) == ':' || ($conf['useslash'] && substr($page,-1) == '/')){ 413 if(page_exists($page.$conf['start'])){ 414 // start page inside namespace 415 $page = $page.$conf['start']; 416 $exists = true; 417 }elseif(page_exists($page.noNS(cleanID($page)))){ 418 // page named like the NS inside the NS 419 $page = $page.noNS(cleanID($page)); 420 $exists = true; 421 }elseif(page_exists($page)){ 422 // page like namespace exists 423 $page = $page; 424 $exists = true; 425 }else{ 426 // fall back to default 427 $page = $page.$conf['start']; 428 } 429 }else{ 430 //check alternative plural/nonplural form 431 if(!@file_exists($file)){ 432 if( $conf['autoplural'] ){ 433 if(substr($page,-1) == 's'){ 434 $try = substr($page,0,-1); 435 }else{ 436 $try = $page.'s'; 437 } 438 if(page_exists($try)){ 439 $page = $try; 440 $exists = true; 441 } 442 } 443 }else{ 444 $exists = true; 445 } 446 } 447 448 // now make sure we have a clean page 449 $page = cleanID($page); 450 451 //add hash if any 452 if(!empty($hash)) $page .= '#'.$hash; 453} 454 455/** 456 * Returns the name of a cachefile from given data 457 * 458 * The needed directory is created by this function! 459 * 460 * @author Andreas Gohr <andi@splitbrain.org> 461 * 462 * @param string $data This data is used to create a unique md5 name 463 * @param string $ext This is appended to the filename if given 464 * @return string The filename of the cachefile 465 */ 466function getCacheName($data,$ext=''){ 467 global $conf; 468 $md5 = md5($data); 469 $file = $conf['cachedir'].'/'.$md5{0}.'/'.$md5.$ext; 470 io_makeFileDir($file); 471 return $file; 472} 473 474/** 475 * Checks a pageid against $conf['hidepages'] 476 * 477 * @author Andreas Gohr <gohr@cosmocode.de> 478 */ 479function isHiddenPage($id){ 480 global $conf; 481 if(empty($conf['hidepages'])) return false; 482 483 if(preg_match('/'.$conf['hidepages'].'/ui',':'.$id)){ 484 return true; 485 } 486 return false; 487} 488 489/** 490 * Reverse of isHiddenPage 491 * 492 * @author Andreas Gohr <gohr@cosmocode.de> 493 */ 494function isVisiblePage($id){ 495 return !isHiddenPage($id); 496} 497 498/** 499 * Checks and sets HTTP headers for conditional HTTP requests 500 * 501 * @author Simon Willison <swillison@gmail.com> 502 * @link http://simon.incutio.com/archive/2003/04/23/conditionalGet 503 * @param timestamp $timestamp lastmodified time of the cache file 504 * @returns void or void with previously header() commands executed 505 */ 506function http_conditionalRequest($timestamp){ 507 // A PHP implementation of conditional get, see 508 // http://fishbowl.pastiche.org/archives/001132.html 509 $last_modified = substr(gmdate('r', $timestamp), 0, -5).'GMT'; 510 $etag = '"'.md5($last_modified).'"'; 511 // Send the headers 512 header("Last-Modified: $last_modified"); 513 header("ETag: $etag"); 514 // See if the client has provided the required headers 515 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])){ 516 $if_modified_since = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']); 517 }else{ 518 $if_modified_since = false; 519 } 520 521 if (isset($_SERVER['HTTP_IF_NONE_MATCH'])){ 522 $if_none_match = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); 523 }else{ 524 $if_none_match = false; 525 } 526 527 if (!$if_modified_since && !$if_none_match){ 528 return; 529 } 530 531 // At least one of the headers is there - check them 532 if ($if_none_match && $if_none_match != $etag) { 533 return; // etag is there but doesn't match 534 } 535 536 if ($if_modified_since && $if_modified_since != $last_modified) { 537 return; // if-modified-since is there but doesn't match 538 } 539 540 // Nothing has changed since their last request - serve a 304 and exit 541 header('HTTP/1.0 304 Not Modified'); 542 543 // don't produce output, even if compression is on 544 ob_end_clean(); 545 exit; 546} 547 548//Setup VIM: ex: et ts=2 enc=utf-8 : 549