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 = $_REQUEST[$param]; 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 return preg_replace('/.*:/','',$id); 128} 129 130/** 131 * returns the full path to the datafile specified by ID and 132 * optional revision 133 * 134 * The filename is URL encoded to protect Unicode chars 135 * 136 * @author Andreas Gohr <andi@splitbrain.org> 137 */ 138function wikiFN($id,$rev=''){ 139 global $conf; 140 $id = cleanID($id); 141 $id = str_replace(':','/',$id); 142 if(empty($rev)){ 143 $fn = $conf['datadir'].'/'.utf8_encodeFN($id).'.txt'; 144 }else{ 145 $fn = $conf['olddir'].'/'.utf8_encodeFN($id).'.'.$rev.'.txt'; 146 if($conf['usegzip'] && !@file_exists($fn)){ 147 //return gzip if enabled and plaintext doesn't exist 148 $fn .= '.gz'; 149 } 150 } 151 return $fn; 152} 153 154/** 155 * returns the full path to the meta file specified by ID and extension 156 * 157 * The filename is URL encoded to protect Unicode chars 158 * 159 * @author Steven Danz <steven-danz@kc.rr.com> 160 */ 161function metaFN($id,$ext){ 162 global $conf; 163 $id = cleanID($id); 164 $id = str_replace(':','/',$id); 165 $fn = $conf['metadir'].'/'.utf8_encodeFN($id).$ext; 166 return $fn; 167} 168 169/** 170 * returns an array of full paths to all metafiles of a given ID 171 * 172 * @author Esther Brunner <esther@kaffeehaus.ch> 173 */ 174function metaFiles($id){ 175 $name = noNS($id); 176 $dir = metaFN(getNS($id),''); 177 $files = array(); 178 179 $dh = @opendir($dir); 180 if(!$dh) return $files; 181 while(($file = readdir($dh)) !== false){ 182 if(strpos($file,$name.'.') === 0 && !is_dir($dir.$file)) 183 $files[] = $dir.$file; 184 } 185 closedir($dh); 186 187 return $files; 188} 189 190/** 191 * returns the full path to the mediafile specified by ID 192 * 193 * The filename is URL encoded to protect Unicode chars 194 * 195 * @author Andreas Gohr <andi@splitbrain.org> 196 */ 197function mediaFN($id){ 198 global $conf; 199 $id = cleanID($id); 200 $id = str_replace(':','/',$id); 201 $fn = $conf['mediadir'].'/'.utf8_encodeFN($id); 202 return $fn; 203} 204 205/** 206 * Returns the full filepath to a localized textfile if local 207 * version isn't found the english one is returned 208 * 209 * @author Andreas Gohr <andi@splitbrain.org> 210 */ 211function localeFN($id){ 212 global $conf; 213 $file = DOKU_INC.'inc/lang/'.$conf['lang'].'/'.$id.'.txt'; 214 if(!@file_exists($file)){ 215 //fall back to english 216 $file = DOKU_INC.'inc/lang/en/'.$id.'.txt'; 217 } 218 return $file; 219} 220 221/** 222 * Resolve relative paths in IDs 223 * 224 * Do not call directly use resolve_mediaid or resolve_pageid 225 * instead 226 * 227 * Partyly based on a cleanPath function found at 228 * http://www.php.net/manual/en/function.realpath.php#57016 229 * 230 * @author <bart at mediawave dot nl> 231 */ 232function resolve_id($ns,$id,$clean=true){ 233 // if the id starts with a dot we need to handle the 234 // relative stuff 235 if($id{0} == '.'){ 236 // normalize initial dots without a colon 237 $id = preg_replace('/^(\.+)(?=[^:\.])/','\1:',$id); 238 // prepend the current namespace 239 $id = $ns.':'.$id; 240 241 // cleanup relatives 242 $result = array(); 243 $pathA = explode(':', $id); 244 if (!$pathA[0]) $result[] = ''; 245 foreach ($pathA AS $key => $dir) { 246 if ($dir == '..') { 247 if (end($result) == '..') { 248 $result[] = '..'; 249 } elseif (!array_pop($result)) { 250 $result[] = '..'; 251 } 252 } elseif ($dir && $dir != '.') { 253 $result[] = $dir; 254 } 255 } 256 if (!end($pathA)) $result[] = ''; 257 $id = implode(':', $result); 258 }elseif($ns !== false && strpos($id,':') === false){ 259 //if link contains no namespace. add current namespace (if any) 260 $id = $ns.':'.$id; 261 } 262 263 if($clean) $id = cleanID($id); 264 return $id; 265} 266 267/** 268 * Returns a full media id 269 * 270 * @author Andreas Gohr <andi@splitbrain.org> 271 */ 272function resolve_mediaid($ns,&$page,&$exists){ 273 $page = resolve_id($ns,$page); 274 $file = mediaFN($page); 275 $exists = @file_exists($file); 276} 277 278/** 279 * Returns a full page id 280 * 281 * @author Andreas Gohr <andi@splitbrain.org> 282 */ 283function resolve_pageid($ns,&$page,&$exists){ 284 global $conf; 285 $exists = false; 286 287 //keep hashlink if exists then clean both parts 288 list($page,$hash) = split('#',$page,2); 289 $hash = cleanID($hash); 290 $page = resolve_id($ns,$page,false); // resolve but don't clean, yet 291 292 // get filename (calls clean itself) 293 $file = wikiFN($page); 294 295 // if ends with colon we have a namespace link 296 if(substr($page,-1) == ':'){ 297 if(@file_exists(wikiFN($page.$conf['start']))){ 298 // start page inside namespace 299 $page = $page.$conf['start']; 300 $exists = true; 301 }elseif(@file_exists(wikiFN($page.noNS(cleanID($page))))){ 302 // page named like the NS inside the NS 303 $page = $page.noNS(cleanID($page)); 304 $exists = true; 305 }elseif(@file_exists(wikiFN($page))){ 306 // page like namespace exists 307 $page = $page; 308 $exists = true; 309 }else{ 310 // fall back to default 311 $page = $page.$conf['start']; 312 } 313 }else{ 314 //check alternative plural/nonplural form 315 if(!@file_exists($file)){ 316 if( $conf['autoplural'] ){ 317 if(substr($page,-1) == 's'){ 318 $try = substr($page,0,-1); 319 }else{ 320 $try = $page.'s'; 321 } 322 if(@file_exists(wikiFN($try))){ 323 $page = $try; 324 $exists = true; 325 } 326 } 327 }else{ 328 $exists = true; 329 } 330 } 331 332 // now make sure we have a clean page 333 $page = cleanID($page); 334 335 //add hash if any 336 if(!empty($hash)) $page .= '#'.$hash; 337} 338 339/** 340 * Returns the name of a cachefile from given data 341 * 342 * The needed directory is created by this function! 343 * 344 * @author Andreas Gohr <andi@splitbrain.org> 345 * 346 * @param string $data This data is used to create a unique md5 name 347 * @param string $ext This is appended to the filename if given 348 * @return string The filename of the cachefile 349 */ 350function getCacheName($data,$ext=''){ 351 global $conf; 352 $md5 = md5($data); 353 $file = $conf['cachedir'].'/'.$md5{0}.'/'.$md5.$ext; 354 io_makeFileDir($file); 355 return $file; 356} 357 358/** 359 * Checks a pageid against $conf['hidepages'] 360 * 361 * @author Andreas Gohr <gohr@cosmocode.de> 362 */ 363function isHiddenPage($id){ 364 global $conf; 365 if(empty($conf['hidepages'])) return false; 366 367 if(preg_match('/'.$conf['hidepages'].'/ui',':'.$id)){ 368 return true; 369 } 370 return false; 371} 372 373/** 374 * Reverse of isHiddenPage 375 * 376 * @author Andreas Gohr <gohr@cosmocode.de> 377 */ 378function isVisiblePage($id){ 379 return !isHiddenPage($id); 380} 381 382/** 383 * Checks and sets HTTP headers for conditional HTTP requests 384 * 385 * @author Simon Willison <swillison@gmail.com> 386 * @link http://simon.incutio.com/archive/2003/04/23/conditionalGet 387 */ 388function http_conditionalRequest($timestamp){ 389 // A PHP implementation of conditional get, see 390 // http://fishbowl.pastiche.org/archives/001132.html 391 $last_modified = substr(date('r', $timestamp), 0, -5).'GMT'; 392 $etag = '"'.md5($last_modified).'"'; 393 // Send the headers 394 header("Last-Modified: $last_modified"); 395 header("ETag: $etag"); 396 // See if the client has provided the required headers 397 $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? 398 stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : 399 false; 400 $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? 401 stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : 402 false; 403 if (!$if_modified_since && !$if_none_match) { 404 return; 405 } 406 // At least one of the headers is there - check them 407 if ($if_none_match && $if_none_match != $etag) { 408 return; // etag is there but doesn't match 409 } 410 if ($if_modified_since && $if_modified_since != $last_modified) { 411 return; // if-modified-since is there but doesn't match 412 } 413 // Nothing has changed since their last request - serve a 304 and exit 414 header('HTTP/1.0 304 Not Modified'); 415 exit; 416} 417 418//Setup VIM: ex: et ts=2 enc=utf-8 : 419