From 08d7edebfcd24cb8582cbbacf30f059aebd1722e Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 14 Feb 2014 17:28:11 +0100 Subject: [PATCH] Decouple (automatic) preferences storage from user preferences (WIP) DbStore, persisting preferences and tests not yet reworked. --- library/Icinga/Application/Web.php | 26 +- library/Icinga/Authentication/Manager.php | 23 +- library/Icinga/Exception/NotReadableError.php | 11 + library/Icinga/Exception/NotWritableError.php | 11 + library/Icinga/Test/BaseTestCase.php | 1 - library/Icinga/User/Preferences.php | 225 +++++++----------- library/Icinga/User/Preferences/ChangeSet.php | 139 ----------- .../Preferences/FlushObserverInterface.php | 46 ---- library/Icinga/User/Preferences/IniStore.php | 205 ---------------- .../Icinga/User/Preferences/LoadInterface.php | 43 ---- library/Icinga/User/Preferences/NullStore.php | 76 ------ .../User/Preferences/PreferencesStore.php | 173 ++++++++++++++ .../Icinga/User/Preferences/SessionStore.php | 119 --------- .../User/Preferences/{ => Store}/DbStore.php | 10 +- .../User/Preferences/Store/IniStore.php | 146 ++++++++++++ .../Icinga/User/Preferences/StoreFactory.php | 98 -------- library/Icinga/Util/File.php | 14 ++ .../Icinga/User/Preferences/ChangeSetTest.php | 93 -------- .../Icinga/User/Preferences/DbStoreTest.php | 131 ---------- .../Icinga/User/Preferences/IniStoreTest.php | 130 ---------- .../library/Icinga/User/PreferencesTest.php | 93 -------- test/php/library/Icinga/UserTest.php | 1 - 22 files changed, 479 insertions(+), 1335 deletions(-) create mode 100644 library/Icinga/Exception/NotReadableError.php create mode 100644 library/Icinga/Exception/NotWritableError.php delete mode 100644 library/Icinga/User/Preferences/ChangeSet.php delete mode 100644 library/Icinga/User/Preferences/FlushObserverInterface.php delete mode 100644 library/Icinga/User/Preferences/IniStore.php delete mode 100644 library/Icinga/User/Preferences/LoadInterface.php delete mode 100644 library/Icinga/User/Preferences/NullStore.php create mode 100644 library/Icinga/User/Preferences/PreferencesStore.php delete mode 100644 library/Icinga/User/Preferences/SessionStore.php rename library/Icinga/User/Preferences/{ => Store}/DbStore.php (95%) create mode 100644 library/Icinga/User/Preferences/Store/IniStore.php delete mode 100644 library/Icinga/User/Preferences/StoreFactory.php create mode 100644 library/Icinga/Util/File.php delete mode 100644 test/php/library/Icinga/User/Preferences/ChangeSetTest.php delete mode 100644 test/php/library/Icinga/User/Preferences/DbStoreTest.php delete mode 100644 test/php/library/Icinga/User/Preferences/IniStoreTest.php delete mode 100644 test/php/library/Icinga/User/PreferencesTest.php diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index f71389ccc..d3ae8016d 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -44,13 +44,10 @@ use \Zend_Controller_Front; use Icinga\Application\Logger; use Icinga\Authentication\Manager as AuthenticationManager; use Icinga\Exception\ConfigurationError; -use Icinga\User\Preferences; -use Icinga\User\Preferences\LoadInterface; use Icinga\User; use Icinga\Web\Request; use Icinga\Web\View; -use Icinga\User\Preferences\StoreFactory; -use Icinga\User\Preferences\SessionStore; + use Icinga\Util\DateTimeFactory; use Icinga\Session\Session as BaseSession; use Icinga\Web\Session; @@ -322,15 +319,19 @@ class Web extends ApplicationBootstrap */ protected function setupTimezone() { - $userTimeZone = $this->user === null ? null : $this->user->getPreferences()->get('app.timezone'); + if ($this->user !== null && $this->user->getPreferences() !== null) { + $userTimezone = $this->user->getPreferences()->get('app.timezone'); + } else { + $userTimezone = null; + } try { - $tz = new DateTimeZone($userTimeZone); + $tz = new DateTimeZone($userTimezone); } catch (Exception $e) { return parent::setupTimezone(); } - date_default_timezone_set($userTimeZone); + date_default_timezone_set($userTimezone); DateTimeFactory::setConfig(array('timezone' => $tz)); return $this; } @@ -345,19 +346,18 @@ class Web extends ApplicationBootstrap protected function setupInternationalization() { parent::setupInternationalization(); - $userLocale = $this->user === null ? null : $this->user->getPreferences()->get('app.language'); - - if ($userLocale) { + if ($this->user !== null && $this->user->getPreferences() !== null + && ($locale = $this->user->getPreferences()->get('app.language') !== null) + ) { try { - Translator::setupLocale($userLocale); + Translator::setupLocale($locale); } catch (Exception $error) { Logger::info( - 'Cannot set locale "' . $userLocale . '" configured in ' . + 'Cannot set locale "' . $locale . '" configured in ' . 'preferences of user "' . $this->user->getUsername() . '"' ); } } - return $this; } } diff --git a/library/Icinga/Authentication/Manager.php b/library/Icinga/Authentication/Manager.php index 05ae84d70..68901ffb7 100644 --- a/library/Icinga/Authentication/Manager.php +++ b/library/Icinga/Authentication/Manager.php @@ -29,8 +29,8 @@ namespace Icinga\Authentication; -use \Exception; -use \Zend_Config; +use Exception; +use Zend_Config; use Icinga\User; use Icinga\Web\Session; use Icinga\Data\ResourceFactory; @@ -39,6 +39,9 @@ use Icinga\Exception\ConfigurationError; use Icinga\Application\Config as IcingaConfig; use Icinga\Authentication\Backend\DbUserBackend; use Icinga\Authentication\Backend\LdapUserBackend; +use Icinga\User\Preferences; +use Icinga\User\Preferences\PreferencesStore; +use Icinga\Exception\NotReadableError; /** * The authentication manager allows to identify users and @@ -332,6 +335,22 @@ class Manager $admissionLoader->getRestrictions($username, $groups) ); + if (($preferencesConfig = IcingaConfig::app()->preferences) !== null) { + try { + $preferencesStore = PreferencesStore::create( + $preferencesConfig, + $this->user + ); + $preferences = new Preferences($preferencesStore->load()); + } catch (NotReadableError $e) { + Logger::error($e); + $preferences = new Preferences(); + } + } else { + $preferences = new Preferences(); + } + $this->user->setPreferences($preferences); + if ($persist == true) { $this->persistCurrentUser(); } diff --git a/library/Icinga/Exception/NotReadableError.php b/library/Icinga/Exception/NotReadableError.php new file mode 100644 index 000000000..6bc4fc55f --- /dev/null +++ b/library/Icinga/Exception/NotReadableError.php @@ -0,0 +1,11 @@ + + * 'value')); // Start with initial preferences + * + * $preferences->aNewPreference = 'value'; // Set a preference + * + * unset($preferences['aPreference']); // Unset a preference + * + * // Retrieve a preference and return a default value if the preference does not exist + * $anotherPreference = $preferences->get('anotherPreference', 'defaultValue'); */ -class Preferences implements SplSubject, \Countable +class Preferences implements Countable { /** - * Container for all preferences + * Preferences key-value array * * @var array */ private $preferences = array(); /** - * All observers for changes + * Constructor * - * @var SplObserver[] + * @param array $preferences Preferences key-value array */ - private $observer = array(); - - /** - * Current change set - * - * @var ChangeSet - */ - private $changeSet; - - /** - * Flag how commits are handled - * - * @var bool - */ - private $autoCommit = true; - - /** - * Create a new instance - * @param array $initialPreferences - */ - public function __construct(array $initialPreferences) + public function __construct(array $preferences = array()) { - $this->preferences = $initialPreferences; - $this->changeSet = new ChangeSet(); + $this->preferences = $preferences; } /** - * Attach an SplObserver + * Count all preferences * - * @link http://php.net/manual/en/splsubject.attach.php - * @param SplObserver $observer - */ - public function attach(SplObserver $observer) - { - $this->observer[] = $observer; - } - - /** - * Detach an observer - * - * @link http://php.net/manual/en/splsubject.detach.php - * @param SplObserver $observer - */ - public function detach(SplObserver $observer) - { - $key = array_search($observer, $this->observer, true); - if ($key !== false) { - unset($this->observer[$key]); - } - } - - /** - * Notify an observer - * - * @link http://php.net/manual/en/splsubject.notify.php - */ - public function notify() - { - /** @var SplObserver $observer */ - $observer = null; - foreach ($this->observer as $observer) { - $observer->update($this); - } - } - - /** - * Count elements of an object - * - * @link http://php.net/manual/en/countable.count.php - * @return int The custom count as an integer + * @return int The number of preferences */ public function count() { @@ -129,93 +81,84 @@ class Preferences implements SplSubject, \Countable } /** - * Getter for change set - * @return ChangeSet + * Determine whether a preference exists + * + * @param string $name + * + * @return bool */ - public function getChangeSet() + public function has($name) { - return $this->changeSet; + return array_key_exists($name, $this->preferences); } - public function has($key) + /** + * Write data to a preference + * + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) { - return array_key_exists($key, $this->preferences); + $this->preferences[$name] = $value; } - public function set($key, $value) + /** + * Retrieve a preference and return $default if the preference is not set + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function get($name, $default = null) { - if ($this->has($key)) { - $oldValue = $this->get($key); - - // Do not notify useless writes - if ($oldValue !== $value) { - $this->changeSet->appendUpdate($key, $value); - } - } else { - $this->changeSet->appendCreate($key, $value); + if (array_key_exists($name, $this->preferences)) { + return $this->preferences[$name]; } - - $this->processCommit(); - } - - public function get($key, $default = null) - { - if ($this->has($key)) { - return $this->preferences[$key]; - } - return $default; } - public function remove($key) + /** + * Magic method so that $obj->value will work. + * + * @param string $name + * + * @return mixed + */ + public function __get($name) { - if ($this->has($key)) { - $this->changeSet->appendDelete($key); - $this->processCommit(); - return true; - } - - return false; + return $this->get($name); } - public function startTransaction() + /** + * Remove a given preference + * + * @param string $name Preference name + */ + public function remove($name) { - $this->autoCommit = false; + unset($this->preferences[$name]); } - public function commit() + /** + * Determine if a preference is set and is not NULL + * + * @param string $name Preference name + * @return bool + */ + public function __isset($name) { - $changeSet = $this->changeSet; - - if ($this->autoCommit === false) { - $this->autoCommit = true; - } - - if ($changeSet->hasChanges() === true) { - foreach ($changeSet->getCreate() as $key => $value) { - $this->preferences[$key] = $value; - } - - foreach ($changeSet->getUpdate() as $key => $value) { - $this->preferences[$key] = $value; - } - - foreach ($changeSet->getDelete() as $key) { - unset($this->preferences[$key]); - } - - $this->notify(); - - $this->changeSet->clear(); - } else { - throw new ProgrammingError('Nothing to commit'); - } + return isset($this->preferences[$name]); } - private function processCommit() + /** + * Unset a given preference + * + * @param string $name Preference name + */ + public function __unset($name) { - if ($this->autoCommit === true) { - $this->commit(); - } + $this->remove($name); } } diff --git a/library/Icinga/User/Preferences/ChangeSet.php b/library/Icinga/User/Preferences/ChangeSet.php deleted file mode 100644 index 41c867418..000000000 --- a/library/Icinga/User/Preferences/ChangeSet.php +++ /dev/null @@ -1,139 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\User\Preferences; - -/** - * Voyager object to transport changes between consumers - */ -class ChangeSet -{ - /** - * Stack of pending updates - * - * @var array - */ - private $update = array(); - - /** - * Stack of pending delete operations - * - * @var array - */ - private $delete = array(); - - /** - * Stack of pending create operations - * - * @var array - */ - private $create = array(); - - /** - * Push an update to stack - * - * @param string $key - * @param mixed $value - */ - public function appendUpdate($key, $value) - { - $this->update[$key] = $value; - } - - /** - * Getter for pending updates - * - * @return array - */ - public function getUpdate() - { - return $this->update; - } - - /** - * Push delete operation to stack - * - * @param string $key - */ - public function appendDelete($key) - { - $this->delete[] = $key; - } - - /** - * Get pending delete operations - * - * @return array - */ - public function getDelete() - { - return $this->delete; - } - - /** - * Push create operation to stack - * - * @param string $key - * @param mixed $value - */ - public function appendCreate($key, $value) - { - $this->create[$key] = $value; - } - - /** - * Get pending create operations - * - * @return array - */ - public function getCreate() - { - return $this->create; - } - - /** - * Clear all changes - */ - public function clear() - { - $this->update = array(); - $this->delete = array(); - $this->create = array(); - } - - /** - * Test for registered changes - * - * @return bool - */ - public function hasChanges() - { - return (count($this->update) > 0) || (count($this->delete) > 0) || (count($this->create)); - } -} diff --git a/library/Icinga/User/Preferences/FlushObserverInterface.php b/library/Icinga/User/Preferences/FlushObserverInterface.php deleted file mode 100644 index 780dd89e1..000000000 --- a/library/Icinga/User/Preferences/FlushObserverInterface.php +++ /dev/null @@ -1,46 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\User\Preferences; - -use Icinga\User; -use \SplObserver; - -/** - * Handle preference updates - */ -interface FlushObserverInterface extends SplObserver -{ - /** - * Setter for user - * - * @param User $user - */ - public function setUser(User $user); -} diff --git a/library/Icinga/User/Preferences/IniStore.php b/library/Icinga/User/Preferences/IniStore.php deleted file mode 100644 index 66a7525b4..000000000 --- a/library/Icinga/User/Preferences/IniStore.php +++ /dev/null @@ -1,205 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\User\Preferences; - -use Icinga\Application\Logger; -use Icinga\Protocol\Ldap\Exception; -use \SplObserver; -use \SplSubject; -use \Icinga\User; -use \Icinga\User\Preferences; -use \Icinga\Exception\ConfigurationError; -use \Icinga\Exception\ProgrammingError; -use \Zend_Config; -use \Icinga\Application\Config as IcingaConfig; -use \Zend_Config_Writer_Ini; - -/** - * Handle preferences in ini files - * - * Load and write values from user preferences to ini files - */ -class IniStore implements LoadInterface, FlushObserverInterface -{ - /** - * Path to ini configuration - * - * @var string - */ - private $configPath; - - /** - * Specific user file for preferences - * - * @var string - */ - private $preferencesFile; - - /** - * Config container - * - * @var Zend_Config - */ - private $iniConfig; - - /** - * Ini writer - * - * @var Zend_Config_Writer_Ini - */ - private $iniWriter; - - /** - * Current user - * - * @var User - */ - private $user; - - /** - * Create a new object - * - * @param string|null $configPath - */ - public function __construct($configPath = null) - { - if ($configPath !== null) { - $this->setConfigPath($configPath); - } - } - - /** - * Setter for config directory - * - * @param string $configPath - * @throws \Icinga\Exception\ConfigurationError - */ - public function setConfigPath($configPath) - { - $configPath = IcingaConfig::resolvePath($configPath); - if (!is_dir($configPath)) { - throw new ConfigurationError('Config dir dos not exist: '. $configPath); - } - - $this->configPath = $configPath; - } - - /** - * Setter for user - * - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - - $this->preferencesFile = sprintf( - '%s/%s.ini', - $this->configPath, - $this->user->getUsername() - ); - - if (file_exists($this->preferencesFile) === false) { - $this->createDefaultIniFile(); - } - try { - $this->iniConfig = new Zend_Config( - parse_ini_file($this->preferencesFile), - true - ); - - $this->iniWriter = new Zend_Config_Writer_Ini( - array( - 'config' => $this->iniConfig, - 'filename' => $this->preferencesFile - ) - ); - } catch (Exception $e) { - Logger::error('Could not create IniStore backend: %s', $e->getMessage()); - throw new \RuntimeException("Creating user preference backend failed"); - } - } - - /** - * Helper to create blank ini file - */ - private function createDefaultIniFile() - { - // TODO: We should be able to work without preferences. Also we shouldn't store any - // prefs as long as we didn't change some. - if (! is_writable($this->configPath) || touch($this->preferencesFile) === false) { - throw new ConfigurationError( - sprintf('Unable to store "%s"', $this->preferencesFile) - ); - } - chmod($this->preferencesFile, 0664); - } - - /** - * Load preferences from source - * - * @return array - */ - public function load() - { - return $this->iniConfig->toArray(); - } - - /** - * Receive update from subject - * - * @link http://php.net/manual/en/splobserver.update.php - * @param SplSubject $subject - * @throws ProgrammingError - */ - public function update(SplSubject $subject) - { - if (!$subject instanceof Preferences) { - throw new ProgrammingError('Not compatible with '. get_class($subject)); - } - - $changeSet = $subject->getChangeSet(); - - foreach ($changeSet->getCreate() as $key => $value) { - $this->iniConfig->{$key} = $value; - } - - foreach ($changeSet->getUpdate() as $key => $value) { - $this->iniConfig->{$key} = $value; - } - - foreach ($changeSet->getDelete() as $key) { - unset($this->iniConfig->{$key}); - } - - // Persist changes to disk - $this->iniWriter->write(); - } -} diff --git a/library/Icinga/User/Preferences/LoadInterface.php b/library/Icinga/User/Preferences/LoadInterface.php deleted file mode 100644 index 3cf7aae69..000000000 --- a/library/Icinga/User/Preferences/LoadInterface.php +++ /dev/null @@ -1,43 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\User\Preferences; - -/** - * Describe how to load preferences from data sources - */ -interface LoadInterface -{ - /** - * Load preferences from source - * - * @return array - */ - public function load(); -} diff --git a/library/Icinga/User/Preferences/NullStore.php b/library/Icinga/User/Preferences/NullStore.php deleted file mode 100644 index 849be5733..000000000 --- a/library/Icinga/User/Preferences/NullStore.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\User\Preferences; - -use Icinga\User; -use SplSubject; - -/** - * Preference store that simply discards any settings made - * - * Mainly used as a fallback provider if no preferences can be created - */ -class NullStore implements LoadInterface, FlushObserverInterface -{ - - /** - * Setter for user, does nothing - * - * @param User $user - */ - public function setUser(User $user) - { - } - - /** - * Load preferences from source, return an empty array - * - * @return array - */ - public function load() - { - return array(); - } - - /** - * - * Receive update from subject - * - * @link http://php.net/manual/en/splobserver.update.php - * @param SplSubject $subject

- * The SplSubject notifying the observer of an update. - *

- * @return void - */ - public function update(SplSubject $subject) - { - return null; - } -} diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php new file mode 100644 index 000000000..f7c5a74ec --- /dev/null +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -0,0 +1,173 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + * + */ +// {{{ICINGA_LICENSE_HEADER}}} + +namespace Icinga\User\Preferences; + +use Zend_Config; +use Icinga\User; +use Icinga\Exception\ConfigurationError; +use Icinga\User\Preferences; + +/** + * Preferences store factory + * + * Usage example: + * + * 'ini', + * 'configPath' => '/path/to/preferences' + * ), + * $user // Instance of Icinga\User + * ); + * + * $preferences = new Preferences($store->load()); + * $prefereces->aPreference = 'value'; + * $store->save($preferences); + */ +abstract class PreferencesStore +{ + /** + * Store config + * + * @var Zend_Config + */ + private $config; + + /** + * Given user + * + * @var User + */ + private $user; + + /** + * Create a new store + * + * @param Zend_Config $config + * @param User $user + */ + public function __construct(Zend_Config $config, User $user) + { + $this->config = $config; + $this->user = $user; + $this->init(); + } + + /** + * Initialize the sore + */ + public function init() + { + + } + + /** + * Getter for the store config + * + * @return Zend_Config + */ + final public function getStoreConfig() + { + return $this->config; + } + + /** + * Getter for the user + * + * @return User + */ + final public function getUser() + { + return $this->user; + } + + /** + * Load preferences from source + * + * @return array + */ + abstract public function load(); + + /** + * Save the given preferences + * + * @param Preferences $preferences + */ + public function save(Preferences $preferences) + { + $storedPreferences = $this->load(); + $deletedPreferences = array_keys(array_diff_key($storedPreferences, $preferences)); + $newPreferences = array_diff_key($preferences, $storedPreferences); + $updatedPreferences = array_diff_assoc($preferences, $storedPreferences); + $this->cud($newPreferences, $updatedPreferences, $deletedPreferences); + } + + /** + * Create, update and delete the given preferences + * + * @param array $newPreferences Key-value array of preferences to create + * @param array $updatedPreferences Key-value array of preferences to update + * @param array $deletedPreferences An array of preference names to delete + */ + abstract public function cud($newPreferences, $updatedPreferences, $deletedPreferences); + + /** + * Create preferences storage adapter from config + * + * @param Zend_Config $config + * @param User $user + * + * @return self + * @throws ConfigurationError When the configuration defines an invalid storage type + */ + public static function create(Zend_Config $config, User $user) + { + if (($type = $config->type) === null) { + throw new ConfigurationError( + 'Preferences configuration is missing the type directive' + ); + } + $type = ucfirst(strtolower($type)); + $storeClass = 'Icinga\\User\\Preferences\\Store\\' . $type . 'Store'; + if (!class_exists($storeClass)) { + throw new ConfigurationError( + 'Preferences configuration defines an invalid storage type. Storage type ' . $type . ' not found' + ); + } + return new $storeClass($config, $user); + } +} diff --git a/library/Icinga/User/Preferences/SessionStore.php b/library/Icinga/User/Preferences/SessionStore.php deleted file mode 100644 index 5c939c9db..000000000 --- a/library/Icinga/User/Preferences/SessionStore.php +++ /dev/null @@ -1,119 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\User\Preferences; - -use Icinga\Session\Session; -use \SplObserver; -use \SplSubject; -use Icinga\User\Preferences; -use Icinga\Exception\ProgrammingError; - -/** - * Modify preferences into session - */ -class SessionStore implements SplObserver, LoadInterface -{ - /** - * Name of session var for preferences - */ - const DEFAULT_SESSION_NAMESPACE = 'preferences'; - - /** - * Session data - * - * @var Session - */ - private $session; - - /** - * Create a new object - * - * @param Session $session - */ - public function __construct(Session $session) - { - $this->session = $session; - } - - /** - * Receive update from subject - * - * @link http://php.net/manual/en/splobserver.update.php - * @param SplSubject $subject - * @throws ProgrammingError - */ - public function update(SplSubject $subject) - { - if (!$subject instanceof Preferences) { - throw new ProgrammingError('Not compatible with '. get_class($subject)); - } - - $changeSet = $subject->getChangeSet(); - - $data = $this->session->get(self::DEFAULT_SESSION_NAMESPACE, array()); - - foreach ($changeSet->getCreate() as $key => $value) { - $data[$key] = $value; - } - - foreach ($changeSet->getUpdate() as $key => $value) { - $data[$key] = $value; - } - - foreach ($changeSet->getDelete() as $key) { - unset($data[$key]); - } - - $this->session->set(self::DEFAULT_SESSION_NAMESPACE, $data); - - $this->session->write(); - } - - /** - * Public interface to copy all preferences into session - * - * @param array $preferences - */ - public function writeAll(array $preferences) - { - $this->session->set(self::DEFAULT_SESSION_NAMESPACE, $preferences); - $this->session->write(); - } - - /** - * Load preferences from source - * - * @return array - */ - public function load() - { - return $this->session->get(self::DEFAULT_SESSION_NAMESPACE, array()); - } -} diff --git a/library/Icinga/User/Preferences/DbStore.php b/library/Icinga/User/Preferences/Store/DbStore.php similarity index 95% rename from library/Icinga/User/Preferences/DbStore.php rename to library/Icinga/User/Preferences/Store/DbStore.php index 3b5a95f56..4e91fee01 100644 --- a/library/Icinga/User/Preferences/DbStore.php +++ b/library/Icinga/User/Preferences/Store/DbStore.php @@ -31,9 +31,9 @@ namespace Icinga\User\Preferences; use Icinga\User; use SplSubject; -use Zend_Db_Adapter_Abstract; use Icinga\Exception\ProgrammingError; use Icinga\User\Preferences; +use Icinga\Data\ResourceFactory; /** * Store user preferences in database @@ -84,6 +84,9 @@ class DbStore implements LoadInterface, FlushObserverInterface public function setUser(User $user) { $this->user = $user; + ResourceFactory::createResource( + ResourceFactory::getResourceConfig($config->resource) + ); } /** @@ -91,7 +94,7 @@ class DbStore implements LoadInterface, FlushObserverInterface * * @param Zend_Db_Adapter_Abstract $db */ - public function setDbAdapter(Zend_Db_Adapter_Abstract $db) + public function setDbAdapter( $db) { $this->db = $db; @@ -115,8 +118,7 @@ class DbStore implements LoadInterface, FlushObserverInterface public function load() { $res = $this->db->select()->from($this->table) - ->where('username=?', $this->user->getUsername()) - ->query(); + ->where('username=?', $this->user->getUsername()); $out = array(); diff --git a/library/Icinga/User/Preferences/Store/IniStore.php b/library/Icinga/User/Preferences/Store/IniStore.php new file mode 100644 index 000000000..609656ee6 --- /dev/null +++ b/library/Icinga/User/Preferences/Store/IniStore.php @@ -0,0 +1,146 @@ + + * 'ini', + * 'configPath' => '/path/to/preferences' + * ), + * $user // Instance of Icinga\User + * ); + * + * // Create the store directly + * $store = new IniStore( + * new Zend_Config( + * 'configPath' => '/path/to/preferences' + * ), + * $user // Instance of Icinga\User + * ); + * + * $preferences = new Preferences($store->load()); + * $prefereces->aPreference = 'value'; + * $store->save($preferences); + * + */ +class IniStore extends PreferencesStore +{ + /** + * Preferences file of the given user + * + * @var string + */ + private $preferencesFile; + + /** + * Stored preferences + * + * @var Zend_Config_Ini + */ + private $config; + + /** + * Writer which stores the preferences + * + * @var PreservingIniWriter + */ + private $writer; + + /** + * Initialize the store + * + * @throws NotReadableError When the preferences INI file of the given user is not readable + */ + public function init() + { + $this->preferencesFile = sprintf( + '%s/%s.ini', + IcingaConfig::resolvePath($this->getStoreConfig()->configPath), + $this->getUser()->getUsername() + ); + if (file_exists($this->preferencesFile)) { + if (!is_readable($this->preferencesFile)) { + throw new NotReadableError('Preferences INI file ' . $this->preferencesFile . ' for user ' + . $this->getUser()->getUsername() . ' is not readable'); + } else { + $this->config = new Zend_Config_Ini($this->preferencesFile); + } + } + } + + /** + * Load preferences from source + * + * @return array + */ + public function load() + { + return $this->config !== null ? $this->config->toArray() : array(); + } + + /** + * Create, update and delete the given preferences + * + * @param array $newPreferences Key-value array of preferences to create + * @param array $updatedPreferences Key-value array of preferences to update + * @param array $deletedPreferences An array of preference names to delete + * + * @throws NotWritableError When either the path to the preferences INI files is not writable or the + * preferences INI file for the given user is not writable + */ + public function cud($newPreferences, $updatedPreferences, $deletedPreferences) + { + if ($this->config === null) { + // Preferences INI file does not yet exist + if (!is_writable($this->getStoreConfig()->configPath)) { + throw new NotWritableError('Path to the preferences INI files ' . $this->getStoreConfig()->configPath + . ' is not writable'); + } + File::create($this->preferencesFile); + $this->config = new Zend_Config_Ini($this->preferencesFile); + } + foreach ($newPreferences as $name => $value) { + $this->config->{$name} = $value; + } + foreach ($updatedPreferences as $name => $value) { + $this->config->{$name} = $value; + } + foreach ($deletedPreferences as $name) { + unset($this->config->{$name}); + } + if ($this->writer === null) { + $this->writer = new PreservingIniWriter( + array('config' => $this->config) + ); + } + if (!is_writable($this->preferencesFile)) { + throw new NotWritableError('Preferences INI file ' . $this->preferencesFile . ' for user ' + . $this->getUser()->getUsername() . ' is not writable'); + } + $this->writer->write(); + } +} diff --git a/library/Icinga/User/Preferences/StoreFactory.php b/library/Icinga/User/Preferences/StoreFactory.php deleted file mode 100644 index ae5459cdb..000000000 --- a/library/Icinga/User/Preferences/StoreFactory.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\User\Preferences; - -use \Zend_Config; -use \Icinga\User; -use \Icinga\Exception\ProgrammingError; -use \Icinga\Data\ResourceFactory; - -/** - * Create preference stores from zend config - */ -final class StoreFactory -{ - /** - * Prefix for classes containing namespace - */ - const CLASS_PREFIX = 'Icinga\\User\\Preferences\\'; - - /** - * Suffix for class - */ - const CLASS_SUFFIX = 'Store'; - - /** - * Create storage adapter from zend configuration - * - * @param Zend_Config $config - * @param User $user - * - * @return FlushObserverInterface - * @throws ProgrammingError - */ - public static function create(Zend_Config $config, User $user) - { - $class = self::CLASS_PREFIX. ucfirst($config->get('type')). self::CLASS_SUFFIX; - - if (class_exists($class)) { - $store = new $class(); - - if (!$store instanceof FlushObserverInterface) { - throw new ProgrammingError( - 'Preferences StoreFactory could not create provider (class ' . $class . '). ' - . 'Class is not instance of FlushObserverInterface.' - ); - } - - $items = $config->toArray(); - - if ($items['type'] == 'db') { - $items['dbAdapter'] = ResourceFactory::createResource($items['resource']); - } - unset($items['type']); - - foreach ($items as $key => $value) { - $setter = 'set'. ucfirst($key); - if (is_callable(array($store, $setter))) { - $store->$setter($value); - } - } - - $store->setUser($user); - - return $store; - } - - throw new ProgrammingError( - 'Preferences StoreFactory could not create provider (class ' . $class . '). Class not found.' - ); - } -} diff --git a/library/Icinga/Util/File.php b/library/Icinga/Util/File.php new file mode 100644 index 000000000..47b0deb6c --- /dev/null +++ b/library/Icinga/Util/File.php @@ -0,0 +1,14 @@ +appendCreate('test.key1', 'ok1'); - $changeSet->appendCreate('test.key2', 'ok2'); - - $creates = $changeSet->getCreate(); - - $this->assertCount(2, $creates); - $this->assertTrue($changeSet->hasChanges()); - - $this->assertEquals( - array( - 'test.key1' => 'ok1', - 'test.key2' => 'ok2' - ), - $creates - ); - } - - public function testAppendUpdate() - { - $changeSet = new ChangeSet(); - $changeSet->appendUpdate('test.key3', 'ok1'); - $changeSet->appendUpdate('test.key4', 'ok2'); - $changeSet->appendUpdate('test.key5', 'ok3'); - - $updates = $changeSet->getUpdate(); - - $this->assertCount(3, $updates); - $this->assertTrue($changeSet->hasChanges()); - - $this->assertEquals( - array( - 'test.key3' => 'ok1', - 'test.key4' => 'ok2', - 'test.key5' => 'ok3' - ), - $updates - ); - } - - public function testAppendDelete() - { - $changeSet = new ChangeSet(); - $changeSet->appendDelete('test.key6'); - $changeSet->appendDelete('test.key7'); - $changeSet->appendDelete('test.key8'); - $changeSet->appendDelete('test.key9'); - - $deletes = $changeSet->getDelete(); - - $this->assertCount(4, $deletes); - $this->assertTrue($changeSet->hasChanges()); - - $this->assertEquals( - array( - 'test.key6', - 'test.key7', - 'test.key8', - 'test.key9', - ), - $deletes - ); - } - - public function testObjectReset() - { - $changeSet = new ChangeSet(); - $changeSet->appendCreate('test.key1', 'ok'); - $changeSet->appendCreate('test.key2', 'ok'); - $changeSet->appendUpdate('test.key3', 'ok'); - $changeSet->appendUpdate('test.key4', 'ok'); - $changeSet->appendUpdate('test.key5', 'ok'); - $changeSet->appendDelete('test.key6'); - $changeSet->appendDelete('test.key7'); - $changeSet->appendDelete('test.key8'); - - $this->assertTrue($changeSet->hasChanges()); - $changeSet->clear(); - $this->assertFalse($changeSet->hasChanges()); - } -} diff --git a/test/php/library/Icinga/User/Preferences/DbStoreTest.php b/test/php/library/Icinga/User/Preferences/DbStoreTest.php deleted file mode 100644 index 9b271e180..000000000 --- a/test/php/library/Icinga/User/Preferences/DbStoreTest.php +++ /dev/null @@ -1,131 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Tests\Icinga\User\Preferences; - -// @codingStandardsIgnoreStart -require_once realpath(__DIR__. '/../../../../../../library/Icinga/Test/BaseTestCase.php'); -// @codingStandardsIgnoreEnd - -use Icinga\Test\BaseTestCase; - -// @codingStandardsIgnoreStart -require_once 'Zend/Db.php'; -require_once 'Zend/Db/Adapter/Abstract.php'; -require_once BaseTestCase::$libDir . '/Exception/ConfigurationError.php'; -require_once BaseTestCase::$libDir . '/User.php'; -require_once BaseTestCase::$libDir . '/User/Preferences.php'; -require_once BaseTestCase::$libDir . '/User/Preferences/LoadInterface.php'; -require_once BaseTestCase::$libDir . '/User/Preferences/FlushObserverInterface.php'; -require_once BaseTestCase::$libDir . '/User/Preferences/DbStore.php'; -// @codingStandardsIgnoreEnd - -use \Zend_Db_Adapter_PDO_Abstract; -use Icinga\User; -use Icinga\User\Preferences\DbStore; -use Icinga\User\Preferences; - -class DbStoreTest extends BaseTestCase -{ - - private function createDbStore(Zend_Db_Adapter_PDO_Abstract $db) - { - $user = new User('jdoe'); - - $store = new DbStore(); - $store->setDbAdapter($db); - $store->setUser($user); - - return $store; - } - - /** - * @dataProvider mysqlDb - * @param Zend_Db_Adapter_PDO_Abstract $mysqlDb - */ - public function testCreateUpdateDeletePreferenceValuesMySQL($mysqlDb) - { - $this->setupDbProvider($mysqlDb); - - $this->loadSql( - $mysqlDb, - $sqlDumpFile = BaseTestCase::$etcDir . '/schema/preferences.mysql.sql' - ); - - $store = $this->createDbStore($mysqlDb); - - $preferences = new Preferences(array()); - $preferences->attach($store); - - $preferences->set('test.key1', 'OK1'); - $preferences->set('test.key2', 'OK2'); - $preferences->set('test.key3', 'OK2'); - - $preferences->remove('test.key2'); - - $preferences->set('test.key3', 'OKOK333'); - - $preferencesTest = new Preferences($store->load()); - $this->assertEquals('OK1', $preferencesTest->get('test.key1')); - $this->assertNull($preferencesTest->get('test.key2')); - $this->assertEquals('OKOK333', $preferencesTest->get('test.key3')); - } - - /** - * @dataProvider pgsqlDb - * @param Zend_Db_Adapter_PDO_Abstract $pgsqlDb - */ - public function testCreateUpdateDeletePreferenceValuesPgSQL($pgsqlDb) - { - $this->setupDbProvider($pgsqlDb); - - $this->loadSql( - $pgsqlDb, - $sqlDumpFile = BaseTestCase::$etcDir . '/schema/preferences.pgsql.sql' - ); - - $store = $this->createDbStore($pgsqlDb); - - $preferences = new Preferences(array()); - $preferences->attach($store); - - $preferences->set('test.key1', 'OK1'); - $preferences->set('test.key2', 'OK2'); - $preferences->set('test.key3', 'OK2'); - - $preferences->remove('test.key2'); - - $preferences->set('test.key3', 'OKOK333'); - - $preferencesTest = new Preferences($store->load()); - $this->assertEquals('OK1', $preferencesTest->get('test.key1')); - $this->assertNull($preferencesTest->get('test.key2')); - $this->assertEquals('OKOK333', $preferencesTest->get('test.key3')); - } -} diff --git a/test/php/library/Icinga/User/Preferences/IniStoreTest.php b/test/php/library/Icinga/User/Preferences/IniStoreTest.php deleted file mode 100644 index b7ba48fe3..000000000 --- a/test/php/library/Icinga/User/Preferences/IniStoreTest.php +++ /dev/null @@ -1,130 +0,0 @@ -tempDir = tempnam($tempDir, 'ini-store-test'); - if (file_exists($this->tempDir)) { - unlink($this->tempDir); - } - mkdir($this->tempDir); - } - - protected function tearDown() - { - if (is_dir($this->tempDir)) { - system('rm -rf '. $this->tempDir); - } - } - - private function createTestConfig() - { - $user = new User('jdoe'); - $iniStore = new IniStore($this->tempDir); - $iniStore->setUser($user); - - $preferences = new User\Preferences(array()); - $preferences->attach($iniStore); - - $preferences->startTransaction(); - $preferences->set('test.key1', 'ok1'); - $preferences->set('test.key2', 'ok2'); - $preferences->set('test.key3', 'ok3'); - $preferences->set('test.key4', 'ok4'); - $preferences->commit(); - - return $preferences; - } - - public function testWritePreferencesToFile() - { - $user = new User('jdoe'); - $iniStore = new IniStore($this->tempDir); - $iniStore->setUser($user); - - $preferences = new User\Preferences(array()); - $preferences->attach($iniStore); - - $preferences->startTransaction(); - $preferences->set('test.key1', 'ok1'); - $preferences->set('test.key2', 'ok2'); - $preferences->set('test.key3', 'ok3'); - $preferences->commit(); - - $preferences->remove('test.key2'); - - $file = $this->tempDir. '/jdoe.ini'; - $data = (object)parse_ini_file($file); - - $this->assertAttributeEquals('ok1', 'test.key1', $data, 'ini contains test.key1'); - $this->assertAttributeEquals('ok3', 'test.key3', $data, 'ini contains test.key3'); - $this->assertObjectNotHasAttribute('test.key2', $data, 'ini does not contain key test.key2'); - } - - public function testUpdatePreferencesToFile() - { - $this->createTestConfig(); - - $user = new User('jdoe'); - $iniStore = new IniStore($this->tempDir); - $iniStore->setUser($user); - - $preferences = new User\Preferences($iniStore->load()); - $preferences->attach($iniStore); - - $preferences->startTransaction(); - $preferences->remove('test.key1'); - $preferences->remove('test.key2'); - $preferences->remove('test.key3'); - $preferences->set('test.key4', 'ok9898'); - - $this->assertCount(4, $preferences, 'Before commit we need 4 items'); - - $preferences->commit(); - $this->assertCount(1, $preferences, 'After we need 1 item'); - - $this->assertEquals('ok9898', $preferences->get('test.key4'), 'After commit preference key has changed'); - } - - public function testLoadInterface() - { - $this->createTestConfig(); - - $user = new User('jdoe'); - $iniStore = new IniStore($this->tempDir); - $iniStore->setUser($user); - - $preferences = new User\Preferences($iniStore->load()); - $this->assertEquals('ok4', $preferences->get('test.key4'), 'Test for test.key4'); - $this->assertCount(4, $preferences, 'Count 4 items'); - } - - /** - * @expectedException Icinga\Exception\ConfigurationError - * @expectedExceptionMessage Config dir dos not exist: /path/does/not/exist - */ - public function testInitializationFailure() - { - $iniStore = new IniStore('/path/does/not/exist'); - } -} diff --git a/test/php/library/Icinga/User/PreferencesTest.php b/test/php/library/Icinga/User/PreferencesTest.php deleted file mode 100644 index e89600321..000000000 --- a/test/php/library/Icinga/User/PreferencesTest.php +++ /dev/null @@ -1,93 +0,0 @@ - 'ok1', - 'test.key2' => 'ok2' - ) - ); - - $this->assertCount(2, $preferences); - - $this->assertEquals('ok2', $preferences->get('test.key2')); - } - - public function testGetDefaultValues() - { - $preferences = new Preferences(array()); - $preferences->set('test.key223', 'ok223'); - $preferences->set('test.key333', 'ok333'); - - $this->assertCount(2, $preferences); - - $this->assertEquals('ok223', $preferences->get('test.key223')); - - $this->assertNull($preferences->get('does.not.exist')); - - $this->assertEquals(123123, $preferences->get('does.not.exist', 123123)); - } - - public function testTransactionalCommit() - { - $preferences = new Preferences(array()); - $preferences->startTransaction(); - - $preferences->set('test.key1', 'ok1'); - $preferences->set('test.key2', 'ok2'); - - $this->assertCount(0, $preferences); - $this->assertCount(2, $preferences->getChangeSet()->getCreate()); - - $preferences->commit(); - - $this->assertCount(2, $preferences); - - $preferences->startTransaction(); - - $preferences->remove('test.key2'); - $this->assertEquals('ok2', $preferences->get('test.key2')); - $this->assertCount(1, $preferences->getChangeSet()->getDelete()); - - $preferences->commit(); - $this->assertNull($preferences->get('test.key2')); - } - - /** - * @expectedException Icinga\Exception\ProgrammingError - * @expectedExceptionMessage Nothing to commit - */ - public function testNothingToCommitException() - { - $preferences = new Preferences(array()); - $preferences->commit(); - } - - public function testSetCreateOrUpdate() - { - $preferences = new Preferences(array()); - - $preferences->startTransaction(); - $preferences->set('test.key1', 'ok1'); - $this->assertCount(1, $preferences->getChangeSet()->getCreate()); - $this->assertCount(0, $preferences->getChangeSet()->getUpdate()); - $preferences->commit(); - - $preferences->startTransaction(); - $preferences->set('test.key1', 'ok2'); - $this->assertCount(0, $preferences->getChangeSet()->getCreate()); - $this->assertCount(1, $preferences->getChangeSet()->getUpdate()); - $preferences->commit(); - } -} diff --git a/test/php/library/Icinga/UserTest.php b/test/php/library/Icinga/UserTest.php index 714663140..ec27a3381 100644 --- a/test/php/library/Icinga/UserTest.php +++ b/test/php/library/Icinga/UserTest.php @@ -4,7 +4,6 @@ namespace Tests\Icinga; require_once __DIR__ . '/../../../../library/Icinga/User.php'; require_once __DIR__ . '/../../../../library/Icinga/User/Preferences.php'; -require_once __DIR__ . '/../../../../library/Icinga/User/Preferences/ChangeSet.php'; use \DateTimeZone; use Icinga\User as IcingaUser;