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