1<?php 2/* 3 * Configuration Class and generic setting classes 4 * 5 * @author Chris Smith <chris@jalakai.co.uk> 6 */ 7 8if (!class_exists('configuration0')) { 9 10 class configuration0 { 11 12 var $_name = 'conf'; // name of the config variable found in the files (overridden by $config['varname']) 13 var $_format = 'php'; // format of the config file, supported formats - php (overridden by $config['format']) 14 var $_heading = ''; // heading string written at top of config file - don't include comment indicators 15 var $_loaded = false; // set to true after configuration files are loaded 16 var $_metadata = array(); // holds metadata describing the settings 17 var $setting = array(); // array of setting objects 18 var $locked = false; // configuration is considered locked if it can't be updated 19 20 // filenames, these will be eval()'d prior to use so maintain any constants in output 21 var $_default_file = ''; 22 var $_local_file = ''; 23 var $_protected_file = ''; 24 25 /** 26 * constructor 27 */ 28 function configuration0($datafile) { 29 global $conf; 30 31 if (!@file_exists($datafile)) { 32 msg('No configuration metadata found at - '.htmlspecialchars($datafile),-1); 33 return; 34 } 35 include($datafile); 36 37 if (isset($config['varname'])) $this->_name = $config['varname']; 38 if (isset($config['format'])) $this->_format = $config['format']; 39 if (isset($config['heading'])) $this->_heading = $config['heading']; 40 41 if (isset($file['default'])) $this->_default_file = $file['default']; 42 if (isset($file['local'])) $this->_local_file = $file['local']; 43 if (isset($file['protected'])) $this->_protected_file = $file['protected']; 44 45 $this->locked = $this->_is_locked(); 46 47 $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template'])); 48 49 $this->retrieve_settings(); 50 } 51 52 function retrieve_settings() { 53 global $conf; 54 55 if (!$this->_loaded) { 56 $default = array_merge($this->_read_config($this->_default_file), $this->get_plugintpl_default($conf['template'])); 57 $local = $this->_read_config($this->_local_file); 58 $protected = $this->_read_config($this->_protected_file); 59 60 $keys = array_merge(array_keys($this->_metadata),array_keys($default), array_keys($local), array_keys($protected)); 61 $keys = array_unique($keys); 62 63 foreach ($keys as $key) { 64 if (isset($this->_metadata[$key])) { 65 $class = $this->_metadata[$key][0]; 66 $class = ($class && class_exists('setting_'.$class)) ? 'setting_'.$class : 'setting'; 67 68 $param = $this->_metadata[$key]; 69 array_shift($param); 70 } else { 71 $class = 'setting'; 72 $param = NULL; 73 } 74 75 $this->setting[$key] = new $class($key,$param); 76 $this->setting[$key]->initialize($default[$key],$local[$key],$protected[$key]); 77 } 78 79 $this->_loaded = true; 80 } 81 } 82 83 function save_settings($id, $header='', $backup=true) { 84 85 if ($this->locked) return false; 86 87 $file = eval('return '.$this->_local_file.';'); 88 89 // backup current file (remove any existing backup) 90 if (@file_exists($file) && $backup) { 91 if (@file_exists($file.'.bak')) @unlink($file.'.bak'); 92 if (!@rename($file, $file.'.bak')) return false; 93 } 94 95 if (!$fh = @fopen($file, 'wb')) { 96 @rename($file.'.bak', $file); // problem opening, restore the backup 97 return false; 98 } 99 100 if (empty($header)) $header = $this->_heading; 101 102 $out = $this->_out_header($id,$header); 103 104 foreach ($this->setting as $setting) { 105 $out .= $setting->out($this->_name, $this->_format); 106 } 107 108 $out .= $this->_out_footer(); 109 110 @fwrite($fh, $out); 111 fclose($fh); 112 return true; 113 } 114 115 /** 116 * return an array of config settings 117 */ 118 function _read_config($file) { 119 120 if (!$file) return array(); 121 122 $config = array(); 123 $file = eval('return '.$file.';'); 124 125 if ($this->_format == 'php') { 126 127 $contents = @php_strip_whitespace($file); 128 $pattern = '/\$'.$this->_name.'\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);/'; 129 $matches=array(); 130 preg_match_all($pattern,$contents,$matches,PREG_SET_ORDER); 131 132 for ($i=0; $i<count($matches); $i++) { 133 134 // correct issues with the incoming data 135 // FIXME ... for now merge multi-dimensional array indices using ____ 136 $key = preg_replace('/.\]\[./',CM_KEYMARKER,$matches[$i][1]); 137 138 // remove quotes from quoted strings & unescape escaped data 139 $value = preg_replace('/^(\'|")(.*)(?<!\\\\)\1$/','$2',$matches[$i][2]); 140 $value = strtr($value, array('\\\\'=>'\\','\\\''=>'\'','\\"'=>'"')); 141 142 $config[$key] = $value; 143 } 144 } 145 146 return $config; 147 } 148 149 function _out_header($id, $header) { 150 $out = ''; 151 if ($this->_format == 'php') { 152 $out .= '<'.'?php'."\n". 153 "/*\n". 154 " * ".$header." \n". 155 " * Auto-generated by ".$id." plugin \n". 156 " * Run for user: ".$_SERVER['REMOTE_USER']."\n". 157 " * Date: ".date('r')."\n". 158 " */\n\n"; 159 } 160 161 return $out; 162 } 163 164 function _out_footer() { 165 $out = ''; 166 if ($this->_format == 'php') { 167 if ($this->_protected_file) { 168 $out .= "\n@include(".$this->_protected_file.");\n"; 169 } 170 $out .= "\n// end auto-generated content\n"; 171 } 172 173 return $out; 174 } 175 176 // configuration is considered locked if there is no local settings filename 177 // or the directory its in is not writable or the file exists and is not writable 178 function _is_locked() { 179 if (!$this->_local_file) return true; 180 181 $local = eval('return '.$this->_local_file.';'); 182 183 if (!is_writable(dirname($local))) return true; 184 if (file_exists($local) && !is_writable($local)) return true; 185 186 return false; 187 } 188 189 /** 190 * not used ... conf's contents are an array! 191 * reduce any multidimensional settings to one dimension using CM_KEYMARKER 192 */ 193 function _flatten($conf,$prefix='') { 194 195 $out = array(); 196 197 foreach($conf as $key => $value) { 198 if (!is_array($value)) { 199 $out[$prefix.$key] = $value; 200 continue; 201 } 202 203 $tmp = $this->_flatten($value,$prefix.$key.CM_KEYMARKER); 204 $out = array_merge($out,$tmp); 205 } 206 207 return $out; 208 } 209 210 /** 211 * load metadata for plugin and template settings 212 */ 213 function get_plugintpl_metadata($tpl){ 214 $file = '/conf/metadata.php'; 215 $metadata = array(); 216 217 if ($dh = opendir(DOKU_PLUGIN)) { 218 while (false !== ($plugin = readdir($dh))) { 219 if ($plugin == '.' || $plugin == '..' || $plugin == 'tmp' || $plugin == 'config') continue; 220 if (is_file(DOKU_PLUGIN.$plugin)) continue; 221 222 if (@file_exists(DOKU_PLUGIN.$plugin.$file)){ 223 $meta = array(); 224 @include(DOKU_PLUGIN.$plugin.$file); 225 foreach ($meta as $key => $value){ 226 $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value; 227 } 228 } 229 } 230 closedir($dh); 231 } 232 233 // the same for the active template 234 if (@file_exists(DOKU_TPLINC.$file)){ 235 $meta = array(); 236 @include(DOKU_TPLINC.$file); 237 foreach ($meta as $key => $value){ 238 $metadata['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value; 239 } 240 } 241 242 return $metadata; 243 } 244 245 /** 246 * load default settings for plugins and templates 247 */ 248 function get_plugintpl_default($tpl){ 249 $file = '/conf/default.php'; 250 $default = array(); 251 252 if ($dh = opendir(DOKU_PLUGIN)) { 253 while (false !== ($plugin = readdir($dh))) { 254 if (@file_exists(DOKU_PLUGIN.$plugin.$file)){ 255 $conf = array(); 256 @include(DOKU_PLUGIN.$plugin.$file); 257 foreach ($conf as $key => $value){ 258 $default['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value; 259 } 260 } 261 } 262 closedir($dh); 263 } 264 265 // the same for the active template 266 if (@file_exists(DOKU_TPLINC.$file)){ 267 $conf = array(); 268 @include(DOKU_TPLINC.$file); 269 foreach ($conf as $key => $value){ 270 $default['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value; 271 } 272 } 273 274 return $default; 275 } 276 277 } 278} 279 280if (!class_exists('setting0')) { 281 class setting0 { 282 283 var $_key = ''; 284 var $_default = NULL; 285 var $_local = NULL; 286 var $_protected = NULL; 287 288 var $_pattern = ''; 289 var $_error = false; // only used by those classes which error check 290 var $_input = NULL; // only used by those classes which error check 291 292 function setting0($key, $params=NULL) { 293 $this->_key = $key; 294 295 if (is_array($params)) { 296 foreach($params as $property => $value) { 297 $this->$property = $value; 298 } 299 } 300 } 301 302 /** 303 * recieves current values for the setting $key 304 */ 305 function initialize($default, $local, $protected) { 306 if (isset($default)) $this->_default = $default; 307 if (isset($local)) $this->_local = $local; 308 if (isset($protected)) $this->_protected = $protected; 309 } 310 311 /** 312 * update setting with user provided value $input 313 * if value fails error check, save it 314 * 315 * @return true if changed, false otherwise (incl. on error) 316 */ 317 function update($input) { 318 if (is_null($input)) return false; 319 if ($this->is_protected()) return false; 320 321 $value = is_null($this->_local) ? $this->_default : $this->_local; 322 if ($value == $input) return false; 323 324 if ($this->_pattern && !preg_match($this->_pattern,$input)) { 325 $this->_error = true; 326 $this->_input = $input; 327 return false; 328 } 329 330 $this->_local = $input; 331 return true; 332 } 333 334 /** 335 * @return array(string $label_html, string $input_html) 336 */ 337 function html(&$plugin, $echo=false) { 338 $value = ''; 339 $disable = ''; 340 341 if ($this->is_protected()) { 342 $value = $this->_protected; 343 $disable = 'disabled="disabled"'; 344 } else { 345 if ($echo && $this->_error) { 346 $value = $this->_input; 347 } else { 348 $value = is_null($this->_local) ? $this->_default : $this->_local; 349 } 350 } 351 352 $key = htmlspecialchars($this->_key); 353 $value = htmlspecialchars($value); 354 355 $label = '<label for="config__'.$key.'">'.$this->prompt($plugin).'</label>'; 356 $input = '<textarea rows="3" cols="40" id="config__'.$key.'" name="config['.$key.']" class="edit" '.$disable.'>'.$value.'</textarea>'; 357 return array($label,$input); 358 } 359 360 /** 361 * generate string to save setting value to file according to $fmt 362 */ 363 function out($var, $fmt='php') { 364 365 if ($this->is_protected()) return ''; 366 if (is_null($this->_local) || ($this->_default == $this->_local)) return ''; 367 368 $out = ''; 369 370 if ($fmt=='php') { 371 // translation string needs to be improved FIXME 372 $tr = array("\n"=>'\n', "\r"=>'\r', "\t"=>'\t', "\\" => '\\\\', "'" => '\\\''); 373 $tr = array("\\" => '\\\\', "'" => '\\\''); 374 375 $out = '$'.$var."['".$this->_out_key()."'] = '".strtr($this->_local, $tr)."';\n"; 376 } 377 378 return $out; 379 } 380 381 function prompt(&$plugin) { 382 $prompt = $plugin->getLang($this->_key); 383 if (!$prompt) $prompt = htmlspecialchars(str_replace(array('____','_'),' ',$this->_key)); 384 return $prompt; 385 } 386 387 function is_protected() { return !is_null($this->_protected); } 388 function is_default() { return !$this->is_protected() && is_null($this->_local); } 389 function error() { return $this->_error; } 390 391 function _out_key() { return str_replace(CM_KEYMARKER,"']['",$this->_key); } 392 } 393} 394 395 396/** 397 * Provide php_strip_whitespace (php5 function) functionality 398 * 399 * @author Chris Smith <chris@jalakai.co.uk> 400 */ 401if (!function_exists('php_strip_whitespace')) { 402 403 if (function_exists('token_get_all')) { 404 405 if (!defined('T_ML_COMMENT')) { 406 define('T_ML_COMMENT', T_COMMENT); 407 } else { 408 define('T_DOC_COMMENT', T_ML_COMMENT); 409 } 410 411 /** 412 * modified from original 413 * source Google Groups, php.general, by David Otton 414 */ 415 function php_strip_whitespace($file) { 416 if (!@is_readable($file)) return ''; 417 418 $in = join('',@file($file)); 419 $out = ''; 420 421 $tokens = token_get_all($in); 422 423 foreach ($tokens as $token) { 424 if (is_string ($token)) { 425 $out .= $token; 426 } else { 427 list ($id, $text) = $token; 428 switch ($id) { 429 case T_COMMENT : // fall thru 430 case T_ML_COMMENT : // fall thru 431 case T_DOC_COMMENT : // fall thru 432 case T_WHITESPACE : 433 break; 434 default : $out .= $text; break; 435 } 436 } 437 } 438 return ($out); 439 } 440 441 } else { 442 443 function is_whitespace($c) { return (strpos("\t\n\r ",$c) !== false); } 444 function is_quote($c) { return (strpos("\"'",$c) !== false); } 445 function is_escaped($s,$i) { 446 $idx = $i-1; 447 while(($idx>=0) && ($s{$idx} == '\\')) $idx--; 448 return (($i - $idx + 1) % 2); 449 } 450 451 function is_commentopen($str, $i) { 452 if ($str{$i} == '#') return "\n"; 453 if ($str{$i} == '/') { 454 if ($str{$i+1} == '/') return "\n"; 455 if ($str{$i+1} == '*') return "*/"; 456 } 457 458 return false; 459 } 460 461 function php_strip_whitespace($file) { 462 463 if (!@is_readable($file)) return ''; 464 465 $contents = join('',@file($file)); 466 $out = ''; 467 468 $state = 0; 469 for ($i=0; $i<strlen($contents); $i++) { 470 if (!$state && is_whitespace($contents{$i})) continue; 471 472 if (!$state && ($c_close = is_commentopen($contents, $i))) { 473 $c_open_len = ($contents{$i} == '/') ? 2 : 1; 474 $i = strpos($contents, $c_close, $i+$c_open_len)+strlen($c_close)-1; 475 continue; 476 } 477 478 $out .= $contents{$i}; 479 if (is_quote($contents{$i})) { 480 if (($state == $contents{$i}) && !is_escaped($contents, $i)) { $state = 0; continue; } 481 if (!$state) {$state = $contents{$i}; continue; } 482 } 483 } 484 485 return $out; 486 } 487 } 488} 489