* @copyright 2020 MyAAC * @link https://my-aac.org */ class Settings implements ArrayAccess { static private $instance; private $settingsFile = []; private $settingsDatabase = []; private $cache = []; private $valuesAsked = []; private $errors = []; /** * @return Settings */ public static function getInstance(): Settings { if (!self::$instance) { self::$instance = new self(); } return self::$instance; } public function load() { $cache = Cache::getInstance(); if ($cache->enabled()) { $tmp = ''; if ($cache->fetch('settings', $tmp)) { $this->settingsDatabase = unserialize($tmp); return; } } global $db; $settings = $db->query('SELECT * FROM `' . TABLE_PREFIX . 'settings`'); if($settings->rowCount() > 0) { foreach ($settings->fetchAll(PDO::FETCH_ASSOC) as $setting) { $this->settingsDatabase[$setting['name']][$setting['key']] = $setting['value']; } } if ($cache->enabled()) { $cache->set('settings', serialize($this->settingsDatabase), 600); } } public function save($pluginName, $values) { global $db; if (!isset($this->settingsFile[$pluginName])) { throw new RuntimeException('Error on save settings: plugin does not exist'); } $settings = $this->settingsFile[$pluginName]; if (isset($settings['callbacks']['beforeSave'])) { if (!$settings['callbacks']['beforeSave']($settings, $values)) { return false; } } $this->errors = []; $db->query('DELETE FROM `' . TABLE_PREFIX . 'settings` WHERE `name` = ' . $db->quote($pluginName) . ';'); foreach ($values as $key => $value) { $errorMessage = ''; if (isset($settings['settings'][$key]['callbacks']['beforeSave']) && !$settings['settings'][$key]['callbacks']['beforeSave']($key, $value, $errorMessage)) { $this->errors[] = $errorMessage; continue; } try { $db->insert(TABLE_PREFIX . 'settings', ['name' => $pluginName, 'key' => $key, 'value' => $value]); } catch (PDOException $error) { $this->errors[] = 'Error while saving setting (' . $pluginName . ' - ' . $key . '): ' . $error->getMessage(); } } $cache = Cache::getInstance(); if ($cache->enabled()) { $cache->delete('settings'); } return true; } public function updateInDatabase($pluginName, $key, $value) { global $db; $db->update(TABLE_PREFIX . 'settings', ['value' => $value], ['name' => $pluginName, 'key' => $key]); } public function deleteFromDatabase($pluginName, $key = null) { global $db; if (!isset($key)) { $db->delete(TABLE_PREFIX . 'settings', ['name' => $pluginName], -1); } else { $db->delete(TABLE_PREFIX . 'settings', ['name' => $pluginName, 'key' => $key]); } } public static function display($plugin, $settings): array { global $db; $query = 'SELECT `key`, `value` FROM `' . TABLE_PREFIX . 'settings` WHERE `name` = ' . $db->quote($plugin) . ';'; $query = $db->query($query); $settingsDb = []; if($query->rowCount() > 0) { foreach($query->fetchAll(PDO::FETCH_ASSOC) as $value) { $settingsDb[$value['key']] = $value['value']; } } $config = []; require BASE . 'config.local.php'; foreach ($config as $key => $value) { if (is_bool($value)) { $settingsDb[$key] = $value ? 'true' : 'false'; } else { $settingsDb[$key] = (string)$value; } } $javascript = ''; ob_start(); ?>
' . ($type ? 'Yes' : 'No') . ' '; }; $i = 0; $j = 0; foreach($settings as $key => $setting) { if ($setting['type'] === 'category') { if ($j++ !== 0) { // close previous category echo '
'; } ?>
'; } ?>

Name Value Description
'; } else if($setting['type'] === 'textarea') { $value = ($settingsDb[$key] ?? ($setting['default'] ?? '')); $valueWithSpaces = array_map('trim', preg_split('/\r\n|\r|\n/', trim($value))); $rows = count($valueWithSpaces); if ($rows < 2) { $rows = 2; // always min 2 rows for textarea } echo ''; } else if ($setting['type'] === 'options') { if ($setting['options'] === '$templates') { $templates = []; foreach (get_templates() as $value) { $templates[$value] = $value; } $setting['options'] = $templates; } else if($setting['options'] === '$clients') { $clients = []; foreach((array)config('clients') as $client) { $client_version = (string)($client / 100); if(strpos($client_version, '.') === false) $client_version .= '.0'; $clients[$client] = $client_version; } $setting['options'] = $clients; } else if ($setting['options'] == '$timezones') { $timezones = []; foreach (DateTimeZone::listIdentifiers() as $value) { $timezones[$value] = $value; } $setting['options'] = $timezones; } else { if (is_string($setting['options'])) { $setting['options'] = explode(',', $setting['options']); foreach ($setting['options'] as &$option) { $option = trim($option); } } } echo ''; } if (!isset($setting['hidden']) || !$setting['hidden']) { ?>
'; echo 'Default: '; if ($setting['type'] === 'boolean') { echo ($setting['default'] ? 'Yes' : 'No'); } else if (in_array($setting['type'], ['text', 'number', 'email', 'password', 'textarea'])) { echo $setting['default']; } else if ($setting['type'] === 'options') { if (!empty($setting['default'])) { echo $setting['options'][$setting['default']]; } } ?>
ob_get_clean(), 'script' => $javascript]; } #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if (is_null($offset)) { throw new \RuntimeException("Settings: You cannot set empty offset with value: $value!"); } $this->loadPlugin($offset); $pluginKeyName = $this->valuesAsked['pluginKeyName']; $key = $this->valuesAsked['key']; // remove whole plugin settings if (!isset($value)) { $this->offsetUnset($offset); $this->deleteFromDatabase($pluginKeyName, $key); return; } $this->settingsDatabase[$pluginKeyName][$key] = $value; $this->updateInDatabase($pluginKeyName, $key, $value); } #[\ReturnTypeWillChange] public function offsetExists($offset): bool { $this->loadPlugin($offset); $pluginKeyName = $this->valuesAsked['pluginKeyName']; $key = $this->valuesAsked['key']; // remove specified plugin settings (all) if(is_null($key)) { return isset($this->settingsDatabase[$offset]); } return isset($this->settingsDatabase[$pluginKeyName][$key]); } #[\ReturnTypeWillChange] public function offsetUnset($offset) { $this->loadPlugin($offset); $pluginKeyName = $this->valuesAsked['pluginKeyName']; $key = $this->valuesAsked['key']; if (isset($this->cache[$offset])) { unset($this->cache[$offset]); } // remove specified plugin settings (all) if(!isset($key)) { unset($this->settingsFile[$pluginKeyName]); unset($this->settingsDatabase[$pluginKeyName]); $this->deleteFromDatabase($pluginKeyName); return; } unset($this->settingsFile[$pluginKeyName]['settings'][$key]); unset($this->settingsDatabase[$pluginKeyName][$key]); $this->deleteFromDatabase($pluginKeyName, $key); } /** * Get settings * Usage: $setting['plugin_name.key'] * Example: $settings['shop_system.paypal_email'] * * @param mixed $offset * @return array|mixed */ #[\ReturnTypeWillChange] public function offsetGet($offset) { // try cache hit if(isset($this->cache[$offset])) { return $this->cache[$offset]; } $this->loadPlugin($offset); $pluginKeyName = $this->valuesAsked['pluginKeyName']; $key = $this->valuesAsked['key']; // return specified plugin settings (all) if(!isset($key)) { if (!isset($this->settingsFile[$pluginKeyName]['settings'])) { throw new RuntimeException('Unknown plugin settings: ' . $pluginKeyName); } return $this->settingsFile[$pluginKeyName]['settings']; } $ret = []; if(isset($this->settingsFile[$pluginKeyName]['settings'][$key])) { $ret = $this->settingsFile[$pluginKeyName]['settings'][$key]; } if(isset($this->settingsDatabase[$pluginKeyName][$key])) { $value = $this->settingsDatabase[$pluginKeyName][$key]; $ret['value'] = $value; } else { $ret['value'] = $this->settingsFile[$pluginKeyName]['settings'][$key]['default']; } if(isset($ret['type'])) { switch($ret['type']) { case 'boolean': $ret['value'] = getBoolean($ret['value']); break; case 'number': if (!isset($ret['step']) || (int)$ret['step'] == 1) { $ret['value'] = (int)$ret['value']; } break; default: break; } } if (isset($ret['callbacks']['get'])) { $ret['value'] = $ret['callbacks']['get']($ret['value']); } $this->cache[$offset] = $ret; return $ret; } private function updateValuesAsked($offset) { $pluginKeyName = $offset; if (strpos($offset, '.')) { $explode = explode('.', $offset, 2); $pluginKeyName = $explode[0]; $key = $explode[1]; $this->valuesAsked = ['pluginKeyName' => $pluginKeyName, 'key' => $key]; } else { $this->valuesAsked = ['pluginKeyName' => $pluginKeyName, 'key' => null]; } } private function loadPlugin($offset) { $this->updateValuesAsked($offset); $pluginKeyName = $this->valuesAsked['pluginKeyName']; $key = $this->valuesAsked['key']; if (!isset($this->settingsFile[$pluginKeyName])) { if ($pluginKeyName === 'core') { $settingsFilePath = SYSTEM . 'settings.php'; } else { //$pluginSettings = Plugins::getPluginSettings($pluginKeyName); $settings = Plugins::getAllPluginsSettings(); if (!isset($settings[$pluginKeyName])) { warning("Setting $pluginKeyName does not exist or does not have settings defined."); return; } $settingsFilePath = BASE . $settings[$pluginKeyName]['settingsFilename']; } if (!file_exists($settingsFilePath)) { throw new \RuntimeException('Failed to load settings file for plugin: ' . $pluginKeyName); } $this->settingsFile[$pluginKeyName] = require $settingsFilePath; } } public static function saveConfig($config, $filename, &$content = '') { $content = " $value) { $content .= "\$config['$key'] = "; $content .= var_export($value, true); $content .= ';' . PHP_EOL; } $success = file_put_contents($filename, $content); // we saved new config.php, need to revalidate cache (only if opcache is enabled) if (function_exists('opcache_invalidate')) { opcache_invalidate($filename); } return $success; } public static function testDatabaseConnection($config): bool { $user = null; $password = null; $dns = []; if( isset($config['database_name']) ) { $dns[] = 'dbname=' . $config['database_name']; } if( isset($config['database_user']) ) { $user = $config['database_user']; } if( isset($config['database_password']) ) { $password = $config['database_password']; } if( isset($config['database_host']) ) { $dns[] = 'host=' . $config['database_host']; } if( isset($config['database_port']) ) { $dns[] = 'port=' . $config['database_port']; } try { $connectionTest = new PDO('mysql:' . implode(';', $dns), $user, $password); $connectionTest->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $error) { error('MySQL connection failed. Settings has been reverted.'); error($error->getMessage()); return false; } return true; } public function getErrors() { return $this->errors; } }