diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php index c6d207b95..b5ea62cc3 100644 --- a/library/Icinga/User/Preferences/PreferencesStore.php +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -1,38 +1,16 @@ - * @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 \Zend_Config; use Icinga\User; -use Icinga\Exception\ConfigurationError; use Icinga\User\Preferences; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\ConfigurationError; +use Icinga\Data\Db\Connection as DbConnection; +use Icinga\Application\Config as IcingaConfig; /** * Preferences store factory @@ -46,7 +24,7 @@ use Icinga\User\Preferences; * use Icinga\User\Preferences\PreferencesStore; * * // Create a INI store - * $store = new PreferencesStore( + * $store = PreferencesStore::create( * new Zend_Config( * 'type' => 'ini', * 'configPath' => '/path/to/preferences' @@ -57,6 +35,7 @@ use Icinga\User\Preferences; * $preferences = new Preferences($store->load()); * $preferences->aPreference = 'value'; * $store->save($preferences); + * */ abstract class PreferencesStore { @@ -65,20 +44,20 @@ abstract class PreferencesStore * * @var Zend_Config */ - private $config; + protected $config; /** * Given user * * @var User */ - private $user; + protected $user; /** * Create a new store * - * @param Zend_Config $config - * @param User $user + * @param Zend_Config $config The config for this adapter + * @param User $user The user to which these preferences belong */ public function __construct(Zend_Config $config, User $user) { @@ -87,20 +66,12 @@ abstract class PreferencesStore $this->init(); } - /** - * Initialize the sore - */ - public function init() - { - - } - /** * Getter for the store config * - * @return Zend_Config + * @return Zend_Config */ - final public function getStoreConfig() + public function getStoreConfig() { return $this->config; } @@ -108,54 +79,41 @@ abstract class PreferencesStore /** * Getter for the user * - * @return User + * @return User */ - final public function getUser() + public function getUser() { return $this->user; } + /** + * Initialize the store + */ + abstract protected function init(); + /** * Load preferences from source * - * @return array + * @return array */ abstract public function load(); /** * Save the given preferences * - * @param Preferences $preferences + * @param Preferences $preferences The preferences to save */ - public function save(Preferences $preferences) - { - $storedPreferences = $this->load(); - $preferences = $preferences->toArray(); - $newPreferences = array_diff_key($preferences, $storedPreferences); - $updatedPreferences = array_diff_assoc($preferences, $storedPreferences); - $deletedPreferences = array_keys(array_diff_key($storedPreferences, $preferences)); - if (count($newPreferences) || count($updatedPreferences) || count($deletedPreferences)) { - $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); + abstract public function save(Preferences $preferences); /** * Create preferences storage adapter from config * - * @param Zend_Config $config - * @param User $user + * @param Zend_Config $config The config for the adapter + * @param User $user The user to which these preferences belong * - * @return self - * @throws ConfigurationError When the configuration defines an invalid storage type + * @return self + * + * @throws ConfigurationError When the configuration defines an invalid storage type */ public static function create(Zend_Config $config, User $user) { @@ -164,6 +122,7 @@ abstract class PreferencesStore 'Preferences configuration is missing the type directive' ); } + $type = ucfirst(strtolower($type)); $storeClass = 'Icinga\\User\\Preferences\\Store\\' . $type . 'Store'; if (!class_exists($storeClass)) { @@ -171,6 +130,13 @@ abstract class PreferencesStore 'Preferences configuration defines an invalid storage type. Storage type ' . $type . ' not found' ); } + + if ($type === 'Ini') { + $config->location = IcingaConfig::resolvePath($config->configPath); + } elseif ($type === 'Db') { + $config->connection = new DbConnection(ResourceFactory::getResourceConfig($config->resource)); + } + return new $storeClass($config, $user); } } diff --git a/library/Icinga/User/Preferences/Store/DbStore.php b/library/Icinga/User/Preferences/Store/DbStore.php index 4e91fee01..367552b7f 100644 --- a/library/Icinga/User/Preferences/Store/DbStore.php +++ b/library/Icinga/User/Preferences/Store/DbStore.php @@ -1,44 +1,20 @@ - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ // {{{ICINGA_LICENSE_HEADER}}} -namespace Icinga\User\Preferences; +namespace Icinga\User\Preferences\Store; -use Icinga\User; -use SplSubject; -use Icinga\Exception\ProgrammingError; +use \Exception; +use \Zend_Db_Select; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; use Icinga\User\Preferences; -use Icinga\Data\ResourceFactory; +use Icinga\User\Preferences\PreferencesStore; /** - * Store user preferences in database + * Load and save user preferences by using a database */ -class DbStore implements LoadInterface, FlushObserverInterface +class DbStore extends PreferencesStore { /** * Column name for username @@ -55,55 +31,24 @@ class DbStore implements LoadInterface, FlushObserverInterface */ const COLUMN_VALUE = 'value'; - /** - * User object - * - * @var User - */ - private $user; - - /** - * Zend database adapter - * - * @var Zend_Db_Adapter_Abstract - */ - private $db; - /** * Table name * * @var string */ - private $table = 'preference'; + protected $table = 'preference'; /** - * Setter for user + * Stored preferences * - * @param User $user + * @var array */ - public function setUser(User $user) - { - $this->user = $user; - ResourceFactory::createResource( - ResourceFactory::getResourceConfig($config->resource) - ); - } + protected $preferences = array(); /** - * Setter for db adapter + * Set the table to use * - * @param Zend_Db_Adapter_Abstract $db - */ - public function setDbAdapter( $db) - { - $this->db = $db; - - } - - /** - * Setter for table - * - * @param string $table + * @param string $table The table name */ public function setTable($table) { @@ -111,133 +56,157 @@ class DbStore implements LoadInterface, FlushObserverInterface } /** - * Load preferences from source + * Initialize the store + */ + protected function init() + { + + } + + /** + * Load preferences from the database * - * @return array + * @return array + * + * @throws NotReadableError In case the database operation failed */ public function load() { - $res = $this->db->select()->from($this->table) - ->where('username=?', $this->user->getUsername()); - - $out = array(); - - foreach ($res->fetchAll() as $row) { - $out[$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE}; + try { + $select = new Zend_Db_Select($this->getStoreConfig()->connection->getConnection()); + $result = $select->from($this->table, array(self::COLUMN_PREFERENCE, self::COLUMN_VALUE)) + ->where(self::COLUMN_USERNAME . ' = ?', $this->getUser()->getUsername()) + ->query()->fetchAll(); + } catch (Exception $e) { + throw new NotReadableError( + 'Cannot fetch preferences for user ' . $this->getUser()->getUsername() . ' from database', 0, $e + ); } - return $out; - } - - /** - * Helper to create zend db suitable where condition - * - * @param string $preference - * @return array - */ - private function createWhereCondition($preference) - { - return array( - $this->db->quoteIdentifier(self::COLUMN_USERNAME) . '=?' => $this->user->getUsername(), - $this->db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $preference - ); - } - - /** - * Create operation - * - * @param string $preference - * @param mixed $value - * @return int - */ - private function doCreate($preference, $value) - { - return $this->db->insert( - $this->table, - array( - $this->db->quoteIdentifier(self::COLUMN_USERNAME) => $this->user->getUsername(), - $this->db->quoteIdentifier(self::COLUMN_PREFERENCE) => $preference, - $this->db->quoteIdentifier(self::COLUMN_VALUE) => $value - ) - ); - } - - /** - * Update operation - * - * @param string $preference - * @param mixed $value - * @return int - */ - private function doUpdate($preference, $value) - { - return $this->db->update( - $this->table, - array( - self::COLUMN_VALUE => $value - ), - $this->createWhereCondition($preference) - ); - } - - /** - * Delete preference operation - * - * @param string $preference - * @return int - */ - private function doDelete($preference) - { - return $this->db->delete( - $this->table, - $this->createWhereCondition($preference) - ); - } - - /** - * 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) { - $retVal = $this->doCreate($key, $value); - - if (!$retVal) { - throw new ProgrammingError('Could not create preference value in db: '. $key. '='. $value); + if ($result !== false) { + $values = array(); + foreach ($result as $row) { + $values[$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE}; } + $this->preferences = $values; } - foreach ($changeSet->getUpdate() as $key => $value) { - $retVal = $this->doUpdate($key, $value); + return $this->preferences; + } - /* - * Fallback if we switch storage type while user logged in - */ - if (!$retVal) { - $retVal = $this->doCreate($key, $value); + /** + * Save the given preferences in the database + * + * @param Preferences $preferences The preferences to save + */ + public function save(Preferences $preferences) + { + $preferences = $preferences->toArray(); - if (!$retVal) { - throw new ProgrammingError('Could not create preference value in db: '. $key. '='. $value); - } - } + $toBeInserted = array_diff_key($preferences, $this->preferences); + if (!empty($toBeInserted)) { + $this->insert($toBeInserted); } - foreach ($changeSet->getDelete() as $key) { - $retVal = $this->doDelete($key); + $current = $this->preferences; + $toBeUpdated = array(); + foreach (array_filter( + array_keys(array_intersect_key($preferences, $this->preferences)), + function ($k) use ($current, $preferences) { return $current[$k] == $preferences[$k] ? false : true; } + ) as $key) { + $toBeUpdated[$key] = $preferences[$key]; + } + if (!empty($toBeUpdated)) { + $this->update($toBeUpdated); + } - if (!$retVal) { - throw new ProgrammingError('Could not delete preference value in db: '. $key); + $toBeDeleted = array_keys(array_diff_key($this->preferences, $preferences)); + if (!empty($toBeDeleted)) { + $this->delete($toBeDeleted); + } + } + + /** + * Insert the given preferences into the database + * + * @param array $preferences The preferences to insert + * + * @throws NotWritableError In case the database operation failed + */ + protected function insert(array $preferences) + { + $db = $this->getStoreConfig()->connection->getConnection(); + + try { + foreach ($preferences as $key => $value) { + $db->insert( + $this->table, + array( + self::COLUMN_USERNAME => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key, + self::COLUMN_VALUE => $value + ) + ); } + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot insert preferences for user ' . $this->getUser()->getUsername() . ' into database', 0, $e + ); + } + } + + /** + * Update the given preferences in the database + * + * @param array $preferences The preferences to update + * + * @throws NotWritableError In case the database operation failed + */ + protected function update(array $preferences) + { + $db = $this->getStoreConfig()->connection->getConnection(); + + try { + foreach ($preferences as $key => $value) { + $db->update( + $this->table, + array(self::COLUMN_VALUE => $value), + array( + self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key + ) + ); + } + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot update preferences for user ' . $this->getUser()->getUsername() . ' in database', 0, $e + ); + } + } + + /** + * Delete the given preference names from the database + * + * @param array $preferenceKeys The preference names to delete + * + * @throws NotWritableError In case the database operation failed + */ + protected function delete(array $preferenceKeys) + { + $db = $this->getStoreConfig()->connection->getConnection(); + + try { + $db->delete( + $this->table, + array( + self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys + ) + ); + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot delete preferences for user ' . $this->getUser()->getUsername() . ' from database', 0, $e + ); } } } diff --git a/library/Icinga/User/Preferences/Store/IniStore.php b/library/Icinga/User/Preferences/Store/IniStore.php index 57854296d..c3521b422 100644 --- a/library/Icinga/User/Preferences/Store/IniStore.php +++ b/library/Icinga/User/Preferences/Store/IniStore.php @@ -4,48 +4,16 @@ namespace Icinga\User\Preferences\Store; -use Zend_Config; -use Icinga\Application\Config as IcingaConfig; +use \Zend_Config; +use Icinga\Util\File; use Icinga\Config\PreservingIniWriter; use Icinga\Exception\NotReadableError; use Icinga\Exception\NotWritableError; -use Icinga\User; +use Icinga\User\Preferences; use Icinga\User\Preferences\PreferencesStore; -use Icinga\Util\File; /** * Load and save user preferences from and to INI files - * - * Usage example: - * - * '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()); - * $preferences->aPreference = 'value'; - * $store->save($preferences); - * */ class IniStore extends PreferencesStore { @@ -54,93 +22,135 @@ class IniStore extends PreferencesStore * * @var string */ - private $preferencesFile; + protected $preferencesFile; /** * Stored preferences * * @var array */ - private $preferences; + protected $preferences = array(); /** * Writer which stores the preferences * * @var PreservingIniWriter */ - private $writer; + protected $writer; /** * Initialize the store - * - * @throws NotReadableError When the preferences INI file of the given user is not readable */ - public function init() + protected function init() { $this->preferencesFile = sprintf( '%s/%s.ini', - IcingaConfig::resolvePath($this->getStoreConfig()->configPath), + $this->getStoreConfig()->location, $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->preferences = parse_ini_file($this->preferencesFile); - } - } } /** * Load preferences from source * - * @return array + * @return array + * + * @throws NotReadableError When the INI file of the user exists and is not readable */ public function load() { - return $this->preferences !== null ? $this->preferences : array(); + 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->preferences = parse_ini_file($this->preferencesFile); + } + } + + return $this->preferences; } /** - * Create, update and delete the given preferences + * Save 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 + * @param Preferences $preferences The preferences to save */ - public function cud($newPreferences, $updatedPreferences, $deletedPreferences) + public function save(Preferences $preferences) + { + $preferences = $preferences->toArray(); + $this->update( + array_merge( + array_diff_key($preferences, $this->preferences), + array_intersect_key($preferences, $this->preferences) + ) + ); + $this->delete(array_keys(array_diff_key($this->preferences, $preferences))); + $this->write(); + } + + /** + * Write the preferences + * + * @throws NotWritableError In case the INI file cannot be written + */ + public function write() { - if ($this->preferences === 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->preferences = array(); - } - if (!is_writable($this->preferencesFile)) { - throw new NotWritableError('Preferences INI file ' . $this->preferencesFile . ' for user ' - . $this->getUser()->getUsername() . ' is not writable'); - } - foreach ($newPreferences as $name => $value) { - $this->preferences[$name] = $value; - } - foreach ($updatedPreferences as $name => $value) { - $this->preferences[$name] = $value; - } - foreach ($deletedPreferences as $name) { - unset($this->preferences[$name]); - } if ($this->writer === null) { + if (!file_exists($this->preferencesFile)) { + if (!is_writable($this->getStoreConfig()->location)) { + throw new NotWritableError( + sprintf( + 'Path to the preferences INI files %s is not writable', + $this->getStoreConfig()->location + ) + ); + } + + File::create($this->preferencesFile); + } + + if (!is_writable($this->preferencesFile)) { + throw new NotWritableError( + 'Preferences INI file ' . $this->preferencesFile . ' for user ' + . $this->getUser()->getUsername() . ' is not writable' + ); + } + $this->writer = new PreservingIniWriter( - array('config' => new Zend_Config($this->preferences), 'filename' => $this->preferencesFile) + array( + 'config' => new Zend_Config($this->preferences), + 'filename' => $this->preferencesFile + ) ); } + $this->writer->write(); } + + /** + * Add or update the given preferences + * + * @param array $preferences The preferences to set + */ + protected function update(array $preferences) + { + foreach ($preferences as $key => $value) { + $this->preferences[$key] = $value; + } + } + + /** + * Delete the given preferences by name + * + * @param array $preferenceKeys The preference names to delete + */ + protected function delete(array $preferenceKeys) + { + foreach ($preferenceKeys as $key) { + unset($this->preferences[$key]); + } + } } diff --git a/library/Icinga/Web/Controller/BasePreferenceController.php b/library/Icinga/Web/Controller/BasePreferenceController.php index 411f26e48..e1c330413 100644 --- a/library/Icinga/Web/Controller/BasePreferenceController.php +++ b/library/Icinga/Web/Controller/BasePreferenceController.php @@ -32,7 +32,6 @@ namespace Icinga\Web\Controller; use Icinga\Application\Config as IcingaConfig; use Icinga\Exception\ConfigurationError; use Icinga\Web\Session; -use Icinga\User\Preferences; use Icinga\User\Preferences\PreferencesStore; /** @@ -71,21 +70,24 @@ class BasePreferenceController extends ActionController protected function savePreferences(array $preferences) { - $currentPreferences = $this->_request->getUser()->getPreferences(); + $session = Session::getSession(); + $currentPreferences = $session->user->getPreferences(); foreach ($preferences as $key => $value) { if ($value === null) { - unset($currentPreferences->{$key}); + $currentPreferences->remove($key); } else { $currentPreferences->{$key} = $value; } } - Session::getSession()->write(); + $session->write(); + if (($preferencesConfig = IcingaConfig::app()->preferences) === null) { throw new ConfigurationError( 'Cannot save preferences changes since you\'ve not configured a preferences backend' ); } - $store = PreferencesStore::create($preferencesConfig, $this->_request->getUser()); + $store = PreferencesStore::create($preferencesConfig, $session->user); + $store->load(); // Necessary for patching existing preferences $store->save($currentPreferences); } }