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