_subForms) as $name) { // Zend returns values from subforms grouped by their names, but we want them flat $values = array_merge($values, $values[$name]); unset($values[$name]); } return $values; } /** * Set the configuration to use when populating the form or when saving the user's input * * @param Config $config The configuration to use * * @return $this */ public function setIniConfig(Config $config) { if ($this->config !== null) { throw new Exception("Cannot call both methods setConfigScope() and setIniConfig() on same form"); } $this->config = $config; $this->isConfigResourceIni = true; return $this; } public function getModuleName() { return $this->moduleName; } public function getScopeName() { return $this->scopeName; } /** * Whether the config resource is ini * * @return bool True if resource is ini, false otherwise */ public function isConfigResourceIni() { return $this->isConfigResourceIni; } /** * * @param string $module Scope module name * * @param string $name Scope name * * @return $this */ public function setConfigScope(string $module = 'default', string $name = 'config') { if ($this->config !== null) { throw new Exception("Cannot call both methods setConfigScope() and setIniConfig() on same form"); } $this->scopeName = $name; $this->moduleName = $module; $this->config = self::fromDb(); return $this; } public function createElements(array $formData) { $this->addElement( 'hidden', 'hash', [ 'value' => $formData['hash'] ?? '', 'required' => true ] ); } public function isValid($formData) { $valid = parent::isValid($formData); if ($valid && ConfigFormEventsHook::runIsValid($this) === false) { foreach (ConfigFormEventsHook::getLastErrors() as $msg) { $this->error($msg); } $valid = false; } if (! $this->isConfigResourceIni()) { $query = ConfigScope::on($this->getDb()); $query->filter(Filter::all( Filter::equal('module', $this->getModuleName()), Filter::equal('name', $this->getScopeName()) )); try { $data = $this->getDb()->fetchRow($query->assembleSelect()->for('UPDATE')); } catch (Exception $e) { $this->warning($e->getMessage()); return false; } $oldHash = $formData['hash'] ?? ''; if ($data && $oldHash !== bin2hex($data->hash)) { $newOptions = self::fromDb()->toArray()['config']; unset($newOptions['hash']); $this->warning($this->translate(sprintf('The general configuration has been updated and the ' . 'updated general configuration is %s', json_encode($newOptions, JSON_PRETTY_PRINT)))); $this->getElement('hash')->setValue(bin2hex($data->hash)); return false; } if ($oldHash === '') { $valid = true; } } return $valid; } public function onSuccess() { if ($this->isConfigResourceIni()) { $sections = array(); foreach (static::transformEmptyValuesToNull($this->getValues()) as $sectionAndPropertyName => $value) { list($section, $property) = explode('_', $sectionAndPropertyName, 2); $sections[$section][$property] = $value; } foreach ($sections as $section => $config) { $this->config->setSection($section, $config); } } if ($this->save()) { Notification::success($this->translate('New configuration has successfully been stored')); } else { return false; } if (ConfigFormEventsHook::runOnSuccess($this) === false) { Notification::error($this->translate( 'Configuration successfully stored. Though, one or more module hooks failed to run.' . ' See logs for details' )); } } public function onRequest() { $values = []; foreach ($this->config as $section => $properties) { foreach ($properties as $name => $value) { $sectionPrefix = ''; if ($this->isConfigResourceIni()) { $sectionPrefix = $section . '_'; } $values[$sectionPrefix . $name] = $value; } } $this->populate($values); } /** * Persist the current configuration to disk * * If an error occurs the user is shown a view describing the issue and displaying the raw INI configuration. * * @return bool Whether the configuration could be persisted */ public function save() { try { $this->writeConfig($this->config); } catch (ConfigurationError $e) { $this->addError($e->getMessage()); return false; } catch (Exception $e) { if (! $this->isConfigResourceIni()) { $this->addError($e->getMessage()); return false; } $this->addDecorator('ViewScript', array( 'viewModule' => 'default', 'viewScript' => 'showConfiguration.phtml', 'errorMessage' => $e->getMessage(), 'configString' => $this->config, 'filePath' => $this->config->getConfigFile(), 'placement' => Zend_Form_Decorator_Abstract::PREPEND )); return false; } return true; } /** * Write the configuration to disk * * @param Config|ConfigObject $config * * @throws ConfigurationError */ protected function writeConfig($config) { if ($this->isConfigResourceIni()) { $config->saveIni(); return; } $values = static::transformEmptyValuesToNull($this->getValues()); unset($values['hash']); $valuesAsStr = implode(', ', array_map( function ($v, $k) { return sprintf("%s=%s", $k, $v); }, $values, array_keys($values) )); $newHash = sha1($valuesAsStr, true); $db = $this->getDb(); $data = ConfigScope::on($db)->with(['option']); $data->filter(Filter::all( Filter::equal('module', $this->getModuleName()), Filter::equal('name', $this->getScopeName()) )); $data = $data->first(); $db->beginTransaction(); try { if ($data === null) { $db->insert('icingaweb_config_scope', [ 'module' => $this->getModuleName(), 'name' => $this->getScopeName(), 'hash' => $newHash ]); $id = $db->lastInsertId(); foreach ($values as $k => $v) { if (isset($v)) { $db->insert('icingaweb_config_option', [ 'scope_id' => $id, 'name' => $k, 'value' => $v ]); } } } elseif ($data->hash !== $newHash) { $db->update('icingaweb_config_scope', [ 'module' => $this->getModuleName(), 'name' => $this->getScopeName(), 'hash' => $newHash ], ['id = ?' => $data->id]); $db->delete('icingaweb_config_option', ['scope_id = ?' => $data->id]); foreach ($values as $k => $v) { $db->insert('icingaweb_config_option', [ 'scope_id'=> $data->id, 'name' => $k, 'value' => $v ]); } } $db->commitTransaction(); } catch (Exception $e) { $db->rollBackTransaction(); throw $e; } } /** * Transform all empty values of the given array to null * * @param array $values * * @return array */ public static function transformEmptyValuesToNull(array $values) { array_walk($values, function (&$v) { if ($v === '' || $v === false || $v === array()) { $v = null; } }); return $values; } protected function fromDb() { $data = ConfigScope::on($this->getDb())->with(['option']); $data->filter(Filter::all( Filter::equal('module', $this->getModuleName()), Filter::equal('name', $this->getScopeName()) )); $options = []; foreach ($data as $values) { $options[$values->name][$values->option->name] = $values->option->value; $options[$values->name]['hash'] = bin2hex($values->hash); } return new ConfigObject($options); } }