1<?php 2/** 3 * Configuration Class 4 * 5 * @author Chris Smith <chris@jalakai.co.uk> 6 * @author Ben Coburn <btcoburn@silicodon.net> 7 */ 8 9namespace dokuwiki\plugin\config\core; 10 11/** 12 * Class configuration 13 */ 14class Configuration { 15 16 const KEYMARKER = '____'; 17 18 protected $_name = 'conf'; // name of the config variable found in the files (overridden by $config['varname']) 19 protected $_format = 'php'; // format of the config file, supported formats - php (overridden by $config['format']) 20 protected $_heading = ''; // heading string written at top of config file - don't include comment indicators 21 protected $_loaded = false; // set to true after configuration files are loaded 22 protected $_metadata = array();// holds metadata describing the settings 23 /** @var Setting[] */ 24 public $setting = array(); // array of setting objects 25 public $locked = false; // configuration is considered locked if it can't be updated 26 public $show_disabled_plugins = false; 27 28 // configuration filenames 29 protected $_default_files = array(); 30 protected $_local_files = array(); // updated configuration is written to the first file 31 protected $_protected_files = array(); 32 33 protected $_plugin_list = null; 34 35 /** 36 * constructor 37 * 38 * @param string $datafile path to config metadata file 39 */ 40 public function __construct($datafile) { 41 global $conf, $config_cascade; 42 43 if(!file_exists($datafile)) { 44 msg('No configuration metadata found at - ' . htmlspecialchars($datafile), -1); 45 return; 46 } 47 $meta = array(); 48 /** @var array $config gets loaded via include here */ 49 include($datafile); 50 51 if(isset($config['varname'])) $this->_name = $config['varname']; 52 if(isset($config['format'])) $this->_format = $config['format']; 53 if(isset($config['heading'])) $this->_heading = $config['heading']; 54 55 $this->_default_files = $config_cascade['main']['default']; 56 $this->_local_files = $config_cascade['main']['local']; 57 $this->_protected_files = $config_cascade['main']['protected']; 58 59 $this->locked = $this->_is_locked(); 60 $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template'])); 61 $this->retrieve_settings(); 62 } 63 64 /** 65 * Retrieve and stores settings in setting[] attribute 66 */ 67 public function retrieve_settings() { 68 global $conf; 69 $no_default_check = array('setting_fieldset', 'setting_undefined', 'setting_no_class'); 70 71 if(!$this->_loaded) { 72 $default = array_merge( 73 $this->get_plugintpl_default($conf['template']), 74 $this->_read_config_group($this->_default_files) 75 ); 76 $local = $this->_read_config_group($this->_local_files); 77 $protected = $this->_read_config_group($this->_protected_files); 78 79 $keys = array_merge( 80 array_keys($this->_metadata), 81 array_keys($default), 82 array_keys($local), 83 array_keys($protected) 84 ); 85 $keys = array_unique($keys); 86 87 $param = null; 88 foreach($keys as $key) { 89 if(isset($this->_metadata[$key])) { 90 $class = $this->_metadata[$key][0]; 91 92 if($class && class_exists('setting_' . $class)) { 93 $class = 'setting_' . $class; 94 } else { 95 if($class != '') { 96 $this->setting[] = new SettingNoClass($key, $param); 97 } 98 $class = 'setting'; 99 } 100 101 $param = $this->_metadata[$key]; 102 array_shift($param); 103 } else { 104 $class = 'setting_undefined'; 105 $param = null; 106 } 107 108 if(!in_array($class, $no_default_check) && !isset($default[$key])) { 109 $this->setting[] = new SettingNoDefault($key, $param); 110 } 111 112 $this->setting[$key] = new $class($key, $param); 113 114 $d = array_key_exists($key, $default) ? $default[$key] : null; 115 $l = array_key_exists($key, $local) ? $local[$key] : null; 116 $p = array_key_exists($key, $protected) ? $protected[$key] : null; 117 118 $this->setting[$key]->initialize($d, $l, $p); 119 } 120 121 $this->_loaded = true; 122 } 123 } 124 125 /** 126 * Stores setting[] array to file 127 * 128 * @param string $id Name of plugin, which saves the settings 129 * @param string $header Text at the top of the rewritten settings file 130 * @param bool $backup backup current file? (remove any existing backup) 131 * @return bool succesful? 132 */ 133 public function save_settings($id, $header = '', $backup = true) { 134 global $conf; 135 136 if($this->locked) return false; 137 138 // write back to the last file in the local config cascade 139 $file = end($this->_local_files); 140 141 // backup current file (remove any existing backup) 142 if(file_exists($file) && $backup) { 143 if(file_exists($file . '.bak')) @unlink($file . '.bak'); 144 if(!io_rename($file, $file . '.bak')) return false; 145 } 146 147 if(!$fh = @fopen($file, 'wb')) { 148 io_rename($file . '.bak', $file); // problem opening, restore the backup 149 return false; 150 } 151 152 if(empty($header)) $header = $this->_heading; 153 154 $out = $this->_out_header($id, $header); 155 156 foreach($this->setting as $setting) { 157 $out .= $setting->out($this->_name, $this->_format); 158 } 159 160 $out .= $this->_out_footer(); 161 162 @fwrite($fh, $out); 163 fclose($fh); 164 if($conf['fperm']) chmod($file, $conf['fperm']); 165 return true; 166 } 167 168 /** 169 * Update last modified time stamp of the config file 170 * 171 * @return bool 172 */ 173 public function touch_settings() { 174 if($this->locked) return false; 175 $file = end($this->_local_files); 176 return @touch($file); 177 } 178 179 /** 180 * Read and merge given config files 181 * 182 * @param array $files file paths 183 * @return array config settings 184 */ 185 protected function _read_config_group($files) { 186 $config = array(); 187 foreach($files as $file) { 188 $config = array_merge($config, $this->_read_config($file)); 189 } 190 191 return $config; 192 } 193 194 /** 195 * Return an array of config settings 196 * 197 * @param string $file file path 198 * @return array config settings 199 */ 200 protected function _read_config($file) { 201 202 if(!$file) return array(); 203 204 $config = array(); 205 206 if($this->_format == 'php') { 207 208 if(file_exists($file)) { 209 $contents = @php_strip_whitespace($file); 210 } else { 211 $contents = ''; 212 } 213 $pattern = '/\$' . $this->_name . '\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$' . $this->_name . '|$))/s'; 214 $matches = array(); 215 preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER); 216 217 for($i = 0; $i < count($matches); $i++) { 218 $value = $matches[$i][2]; 219 220 // correct issues with the incoming data 221 // FIXME ... for now merge multi-dimensional array indices using ____ 222 $key = preg_replace('/.\]\[./', Configuration::KEYMARKER, $matches[$i][1]); 223 224 // handle arrays 225 if(preg_match('/^array ?\((.*)\)/', $value, $match)) { 226 $arr = explode(',', $match[1]); 227 228 // remove quotes from quoted strings & unescape escaped data 229 $len = count($arr); 230 for($j = 0; $j < $len; $j++) { 231 $arr[$j] = trim($arr[$j]); 232 $arr[$j] = $this->_readValue($arr[$j]); 233 } 234 235 $value = $arr; 236 } else { 237 $value = $this->_readValue($value); 238 } 239 240 $config[$key] = $value; 241 } 242 } 243 244 return $config; 245 } 246 247 /** 248 * Convert php string into value 249 * 250 * @param string $value 251 * @return bool|string 252 */ 253 protected function _readValue($value) { 254 $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s'; 255 $unescape_pairs = array( 256 '\\\\' => '\\', 257 '\\\'' => '\'', 258 '\\"' => '"' 259 ); 260 261 if($value == 'true') { 262 $value = true; 263 } elseif($value == 'false') { 264 $value = false; 265 } else { 266 // remove quotes from quoted strings & unescape escaped data 267 $value = preg_replace($removequotes_pattern, '$2', $value); 268 $value = strtr($value, $unescape_pairs); 269 } 270 return $value; 271 } 272 273 /** 274 * Returns header of rewritten settings file 275 * 276 * @param string $id plugin name of which generated this output 277 * @param string $header additional text for at top of the file 278 * @return string text of header 279 */ 280 protected function _out_header($id, $header) { 281 $out = ''; 282 if($this->_format == 'php') { 283 $out .= '<' . '?php' . "\n" . 284 "/*\n" . 285 " * " . $header . "\n" . 286 " * Auto-generated by " . $id . " plugin\n" . 287 " * Run for user: " . $_SERVER['REMOTE_USER'] . "\n" . 288 " * Date: " . date('r') . "\n" . 289 " */\n\n"; 290 } 291 292 return $out; 293 } 294 295 /** 296 * Returns footer of rewritten settings file 297 * 298 * @return string text of footer 299 */ 300 protected function _out_footer() { 301 $out = ''; 302 if($this->_format == 'php') { 303 $out .= "\n// end auto-generated content\n"; 304 } 305 306 return $out; 307 } 308 309 /** 310 * Configuration is considered locked if there is no local settings filename 311 * or the directory its in is not writable or the file exists and is not writable 312 * 313 * @return bool true: locked, false: writable 314 */ 315 protected function _is_locked() { 316 if(!$this->_local_files) return true; 317 318 $local = $this->_local_files[0]; 319 320 if(!is_writable(dirname($local))) return true; 321 if(file_exists($local) && !is_writable($local)) return true; 322 323 return false; 324 } 325 326 /** 327 * not used ... conf's contents are an array! 328 * reduce any multidimensional settings to one dimension using Configuration::KEYMARKER 329 * 330 * @param $conf 331 * @param string $prefix 332 * @return array 333 */ 334 protected function _flatten($conf, $prefix = '') { 335 336 $out = array(); 337 338 foreach($conf as $key => $value) { 339 if(!is_array($value)) { 340 $out[$prefix . $key] = $value; 341 continue; 342 } 343 344 $tmp = $this->_flatten($value, $prefix . $key . Configuration::KEYMARKER); 345 $out = array_merge($out, $tmp); 346 } 347 348 return $out; 349 } 350 351 /** 352 * Returns array of plugin names 353 * 354 * @return array plugin names 355 * @triggers PLUGIN_CONFIG_PLUGINLIST event 356 */ 357 protected function get_plugin_list() { 358 if(is_null($this->_plugin_list)) { 359 $list = plugin_list('', $this->show_disabled_plugins); 360 361 // remove this plugin from the list 362 $idx = array_search('config', $list); 363 unset($list[$idx]); 364 365 trigger_event('PLUGIN_CONFIG_PLUGINLIST', $list); 366 $this->_plugin_list = $list; 367 } 368 369 return $this->_plugin_list; 370 } 371 372 /** 373 * load metadata for plugin and template settings 374 * 375 * @param string $tpl name of active template 376 * @return array metadata of settings 377 */ 378 protected function get_plugintpl_metadata($tpl) { 379 $file = '/conf/metadata.php'; 380 $class = '/conf/settings.class.php'; 381 $metadata = array(); 382 383 foreach($this->get_plugin_list() as $plugin) { 384 $plugin_dir = plugin_directory($plugin); 385 if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) { 386 $meta = array(); 387 @include(DOKU_PLUGIN . $plugin_dir . $file); 388 @include(DOKU_PLUGIN . $plugin_dir . $class); 389 if(!empty($meta)) { 390 $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . 'plugin_settings_name'] = ['fieldset']; 391 } 392 foreach($meta as $key => $value) { 393 if($value[0] == 'fieldset') { 394 continue; 395 } //plugins only get one fieldset 396 $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value; 397 } 398 } 399 } 400 401 // the same for the active template 402 if(file_exists(tpl_incdir() . $file)) { 403 $meta = array(); 404 @include(tpl_incdir() . $file); 405 @include(tpl_incdir() . $class); 406 if(!empty($meta)) { 407 $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . 'template_settings_name'] = array('fieldset'); 408 } 409 foreach($meta as $key => $value) { 410 if($value[0] == 'fieldset') { 411 continue; 412 } //template only gets one fieldset 413 $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value; 414 } 415 } 416 417 return $metadata; 418 } 419 420 /** 421 * Load default settings for plugins and templates 422 * 423 * @param string $tpl name of active template 424 * @return array default settings 425 */ 426 protected function get_plugintpl_default($tpl) { 427 $file = '/conf/default.php'; 428 $default = array(); 429 430 foreach($this->get_plugin_list() as $plugin) { 431 $plugin_dir = plugin_directory($plugin); 432 if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) { 433 $conf = $this->_read_config(DOKU_PLUGIN . $plugin_dir . $file); 434 foreach($conf as $key => $value) { 435 $default['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value; 436 } 437 } 438 } 439 440 // the same for the active template 441 if(file_exists(tpl_incdir() . $file)) { 442 $conf = $this->_read_config(tpl_incdir() . $file); 443 foreach($conf as $key => $value) { 444 $default['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value; 445 } 446 } 447 448 return $default; 449 } 450 451} 452 453