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 22 /** @var ConfigParser */ 23 protected $parser; 24 25 26 protected $_loaded = false; // set to true after configuration files are loaded 27 protected $_metadata = array();// holds metadata describing the settings 28 /** @var Setting[] */ 29 public $setting = array(); // array of setting objects 30 public $locked = false; // configuration is considered locked if it can't be updated 31 public $show_disabled_plugins = false; 32 33 // configuration filenames 34 protected $_default_files = array(); 35 protected $_local_files = array(); // updated configuration is written to the first file 36 protected $_protected_files = array(); 37 38 protected $_plugin_list = null; 39 40 /** 41 * constructor 42 * 43 * @param string $datafile path to config metadata file 44 */ 45 public function __construct($datafile) { 46 global $conf, $config_cascade; 47 48 if(!file_exists($datafile)) { 49 msg('No configuration metadata found at - ' . htmlspecialchars($datafile), -1); 50 return; 51 } 52 $meta = array(); 53 /** @var array $config gets loaded via include here */ 54 include($datafile); 55 56 if(isset($config['varname'])) $this->_name = $config['varname']; 57 if(isset($config['format'])) $this->_format = $config['format']; 58 if(isset($config['heading'])) $this->_heading = $config['heading']; 59 60 $this->_default_files = $config_cascade['main']['default']; 61 $this->_local_files = $config_cascade['main']['local']; 62 $this->_protected_files = $config_cascade['main']['protected']; 63 64 $this->parser = new ConfigParser($this->_name, Configuration::KEYMARKER); 65 66 $this->locked = $this->_is_locked(); 67 $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template'])); 68 $this->retrieve_settings(); 69 } 70 71 /** 72 * Retrieve and stores settings in setting[] attribute 73 */ 74 public function retrieve_settings() { 75 global $conf; 76 $no_default_check = array('setting_fieldset', 'setting_undefined', 'setting_no_class'); 77 78 if(!$this->_loaded) { 79 $default = array_merge( 80 $this->get_plugintpl_default($conf['template']), 81 $this->_read_config_group($this->_default_files) 82 ); 83 $local = $this->_read_config_group($this->_local_files); 84 $protected = $this->_read_config_group($this->_protected_files); 85 86 $keys = array_merge( 87 array_keys($this->_metadata), 88 array_keys($default), 89 array_keys($local), 90 array_keys($protected) 91 ); 92 $keys = array_unique($keys); 93 94 $param = null; 95 foreach($keys as $key) { 96 if(isset($this->_metadata[$key])) { 97 $class = $this->_metadata[$key][0]; 98 99 if($class && class_exists('setting_' . $class)) { 100 $class = 'setting_' . $class; 101 } else { 102 if($class != '') { 103 $this->setting[] = new SettingNoClass($key, $param); 104 } 105 $class = 'setting'; 106 } 107 108 $param = $this->_metadata[$key]; 109 array_shift($param); 110 } else { 111 $class = 'setting_undefined'; 112 $param = null; 113 } 114 115 if(!in_array($class, $no_default_check) && !isset($default[$key])) { 116 $this->setting[] = new SettingNoDefault($key, $param); 117 } 118 119 $this->setting[$key] = new $class($key, $param); 120 121 $d = array_key_exists($key, $default) ? $default[$key] : null; 122 $l = array_key_exists($key, $local) ? $local[$key] : null; 123 $p = array_key_exists($key, $protected) ? $protected[$key] : null; 124 125 $this->setting[$key]->initialize($d, $l, $p); 126 } 127 128 $this->_loaded = true; 129 } 130 } 131 132 /** 133 * Stores setting[] array to file 134 * 135 * @param string $id Name of plugin, which saves the settings 136 * @param string $header Text at the top of the rewritten settings file 137 * @param bool $backup backup current file? (remove any existing backup) 138 * @return bool succesful? 139 */ 140 public function save_settings($id, $header = '', $backup = true) { 141 global $conf; 142 143 if($this->locked) return false; 144 145 // write back to the last file in the local config cascade 146 $file = end($this->_local_files); 147 148 // backup current file (remove any existing backup) 149 if(file_exists($file) && $backup) { 150 if(file_exists($file . '.bak')) @unlink($file . '.bak'); 151 if(!io_rename($file, $file . '.bak')) return false; 152 } 153 154 if(!$fh = @fopen($file, 'wb')) { 155 io_rename($file . '.bak', $file); // problem opening, restore the backup 156 return false; 157 } 158 159 if(empty($header)) $header = $this->_heading; 160 161 $out = $this->_out_header($id, $header); 162 163 foreach($this->setting as $setting) { 164 $out .= $setting->out($this->_name, $this->_format); 165 } 166 167 $out .= $this->_out_footer(); 168 169 @fwrite($fh, $out); 170 fclose($fh); 171 if($conf['fperm']) chmod($file, $conf['fperm']); 172 return true; 173 } 174 175 /** 176 * Update last modified time stamp of the config file 177 * 178 * @return bool 179 */ 180 public function touch_settings() { 181 if($this->locked) return false; 182 $file = end($this->_local_files); 183 return @touch($file); 184 } 185 186 /** 187 * Read and merge given config files 188 * 189 * @param array $files file paths 190 * @return array config settings 191 */ 192 protected function _read_config_group($files) { 193 $config = array(); 194 foreach($files as $file) { 195 $config = array_merge($config, $this->parser->parse($file)); 196 } 197 198 return $config; 199 } 200 201 202 /** 203 * Returns header of rewritten settings file 204 * 205 * @param string $id plugin name of which generated this output 206 * @param string $header additional text for at top of the file 207 * @return string text of header 208 */ 209 protected function _out_header($id, $header) { 210 $out = ''; 211 if($this->_format == 'php') { 212 $out .= '<' . '?php' . "\n" . 213 "/*\n" . 214 " * " . $header . "\n" . 215 " * Auto-generated by " . $id . " plugin\n" . 216 " * Run for user: " . $_SERVER['REMOTE_USER'] . "\n" . 217 " * Date: " . date('r') . "\n" . 218 " */\n\n"; 219 } 220 221 return $out; 222 } 223 224 /** 225 * Returns footer of rewritten settings file 226 * 227 * @return string text of footer 228 */ 229 protected function _out_footer() { 230 $out = ''; 231 if($this->_format == 'php') { 232 $out .= "\n// end auto-generated content\n"; 233 } 234 235 return $out; 236 } 237 238 /** 239 * Configuration is considered locked if there is no local settings filename 240 * or the directory its in is not writable or the file exists and is not writable 241 * 242 * @return bool true: locked, false: writable 243 */ 244 protected function _is_locked() { 245 if(!$this->_local_files) return true; 246 247 $local = $this->_local_files[0]; 248 249 if(!is_writable(dirname($local))) return true; 250 if(file_exists($local) && !is_writable($local)) return true; 251 252 return false; 253 } 254 255 /** 256 * not used ... conf's contents are an array! 257 * reduce any multidimensional settings to one dimension using Configuration::KEYMARKER 258 * 259 * @param $conf 260 * @param string $prefix 261 * @return array 262 */ 263 protected function _flatten($conf, $prefix = '') { 264 265 $out = array(); 266 267 foreach($conf as $key => $value) { 268 if(!is_array($value)) { 269 $out[$prefix . $key] = $value; 270 continue; 271 } 272 273 $tmp = $this->_flatten($value, $prefix . $key . Configuration::KEYMARKER); 274 $out = array_merge($out, $tmp); 275 } 276 277 return $out; 278 } 279 280 /** 281 * Returns array of plugin names 282 * 283 * @return array plugin names 284 * @triggers PLUGIN_CONFIG_PLUGINLIST event 285 */ 286 protected function get_plugin_list() { 287 if(is_null($this->_plugin_list)) { 288 $list = plugin_list('', $this->show_disabled_plugins); 289 290 // remove this plugin from the list 291 $idx = array_search('config', $list); 292 unset($list[$idx]); 293 294 trigger_event('PLUGIN_CONFIG_PLUGINLIST', $list); 295 $this->_plugin_list = $list; 296 } 297 298 return $this->_plugin_list; 299 } 300 301 /** 302 * load metadata for plugin and template settings 303 * 304 * @param string $tpl name of active template 305 * @return array metadata of settings 306 */ 307 protected function get_plugintpl_metadata($tpl) { 308 $file = '/conf/metadata.php'; 309 $class = '/conf/settings.class.php'; 310 $metadata = array(); 311 312 foreach($this->get_plugin_list() as $plugin) { 313 $plugin_dir = plugin_directory($plugin); 314 if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) { 315 $meta = array(); 316 @include(DOKU_PLUGIN . $plugin_dir . $file); 317 @include(DOKU_PLUGIN . $plugin_dir . $class); 318 if(!empty($meta)) { 319 $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . 'plugin_settings_name'] = ['fieldset']; 320 } 321 foreach($meta as $key => $value) { 322 if($value[0] == 'fieldset') { 323 continue; 324 } //plugins only get one fieldset 325 $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value; 326 } 327 } 328 } 329 330 // the same for the active template 331 if(file_exists(tpl_incdir() . $file)) { 332 $meta = array(); 333 @include(tpl_incdir() . $file); 334 @include(tpl_incdir() . $class); 335 if(!empty($meta)) { 336 $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . 'template_settings_name'] = array('fieldset'); 337 } 338 foreach($meta as $key => $value) { 339 if($value[0] == 'fieldset') { 340 continue; 341 } //template only gets one fieldset 342 $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value; 343 } 344 } 345 346 return $metadata; 347 } 348 349 /** 350 * Load default settings for plugins and templates 351 * 352 * @param string $tpl name of active template 353 * @return array default settings 354 */ 355 protected function get_plugintpl_default($tpl) { 356 $file = '/conf/default.php'; 357 $default = array(); 358 359 foreach($this->get_plugin_list() as $plugin) { 360 $plugin_dir = plugin_directory($plugin); 361 if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) { 362 $conf = $this->parser->parse(DOKU_PLUGIN . $plugin_dir . $file); 363 foreach($conf as $key => $value) { 364 $default['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value; 365 } 366 } 367 } 368 369 // the same for the active template 370 if(file_exists(tpl_incdir() . $file)) { 371 $conf = $this->parser->parse(tpl_incdir() . $file); 372 foreach($conf as $key => $value) { 373 $default['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value; 374 } 375 } 376 377 return $default; 378 } 379 380} 381 382