mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-28 08:14:03 +02:00
Merge pull request #4765 from Icinga/drop-ini-backend-support
Drop ini backend support
This commit is contained in:
commit
db51fd79ab
@ -69,9 +69,8 @@ class AccountController extends Controller
|
||||
|
||||
$form = new PreferenceForm();
|
||||
$form->setPreferences($user->getPreferences());
|
||||
if ($config->get('config_backend', 'db') !== 'none' && isset($config->config_resource)) {
|
||||
if (isset($config->config_resource)) {
|
||||
$form->setStore(PreferencesStore::create(new ConfigObject(array(
|
||||
'store' => $config->get('config_backend', 'db'),
|
||||
'resource' => $config->config_resource
|
||||
)), $user));
|
||||
}
|
||||
|
@ -70,41 +70,8 @@ class ApplicationConfigForm extends Form
|
||||
)
|
||||
);
|
||||
|
||||
// we do not need this form for setup because we set the database there as default.
|
||||
// this form is only displayed in configuration -> application if preferences backend type of ini is recognized
|
||||
if (isset($formData['global_config_backend']) && $formData['global_config_backend'] === 'ini') {
|
||||
$this->addElement(
|
||||
'select',
|
||||
'global_config_backend',
|
||||
[
|
||||
'required' => true,
|
||||
'autosubmit' => true,
|
||||
'label' => $this->translate('User Preference Storage Type'),
|
||||
'multiOptions' => [
|
||||
'ini' => $this->translate('File System (INI Files)'),
|
||||
'db' => $this->translate('Database')
|
||||
]
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->addElement(
|
||||
'hidden',
|
||||
'global_config_backend',
|
||||
[
|
||||
'required' => true,
|
||||
'value' => 'db',
|
||||
'disabled' => true
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (! isset($formData['global_config_backend']) || $formData['global_config_backend'] === 'db') {
|
||||
$backends = array();
|
||||
foreach (ResourceFactory::getResourceConfigs()->toArray() as $name => $resource) {
|
||||
if ($resource['type'] === 'db') {
|
||||
$backends[$name] = $name;
|
||||
}
|
||||
}
|
||||
$backends = array_keys(ResourceFactory::getResourceConfigs()->toArray());
|
||||
$backends = array_combine($backends, $backends);
|
||||
|
||||
$this->addElement(
|
||||
'select',
|
||||
@ -120,7 +87,6 @@ class ApplicationConfigForm extends Form
|
||||
'label' => $this->translate('Configuration Database')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -37,13 +37,4 @@ class GeneralConfigForm extends ConfigForm
|
||||
$this->addSubForm($themingConfigForm->create($formData));
|
||||
$this->addSubForm($domainConfigForm->create($formData));
|
||||
}
|
||||
|
||||
public function onRequest()
|
||||
{
|
||||
parent::onRequest();
|
||||
|
||||
if ($this->config->get('global', 'config_backend') === 'ini') {
|
||||
$this->warning('The preferences backend of type INI is deprecated and will be removed with version 2.11');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,7 @@ Option | Description
|
||||
-------------------------|-----------------------------------------------
|
||||
show\_stacktraces | **Optional.** Whether to show debug stacktraces. Defaults to `0`.
|
||||
module\_path | **Optional.** Specifies the directories where modules can be installed. Multiple directories must be separated with colons.
|
||||
config\_backend | **Optional.** Select the user preference storage. Can be set to `ini` (default), `db` or `none`. If `db` is selected, this requires the `config_resource` attribute.
|
||||
config\_resource | **Optional.** Specify a defined [resource](04-Resources.md#resources-configuration-database) name. Can only be used if `config_backend` is set to `db`.
|
||||
config\_resource | **Required.** Specify a defined [resource](04-Resources.md#resources-configuration-database) name.
|
||||
|
||||
|
||||
Example for storing the user preferences in the database resource `icingaweb_db`:
|
||||
@ -37,7 +36,6 @@ Example for storing the user preferences in the database resource `icingaweb_db`
|
||||
```
|
||||
[global]
|
||||
show_stacktraces = "0"
|
||||
config_backend = "db"
|
||||
config_resource = "icingaweb_db"
|
||||
module_path = "/usr/share/icingaweb2/modules"
|
||||
```
|
||||
|
@ -3,35 +3,13 @@
|
||||
Preferences are settings a user can set for their account only,
|
||||
for example the language and time zone.
|
||||
|
||||
Preferences can be stored either in INI files or in a MySQL or in a PostgreSQL database. By default, Icinga Web 2 stores
|
||||
preferences in INI files beneath Icinga Web 2's configuration directory.
|
||||
|
||||
```
|
||||
/etc/icingaweb2/<username>/config.ini
|
||||
```
|
||||
Preferences can be stored either in a MySQL or in a PostgreSQL database. The database must be configured.
|
||||
|
||||
## Configuration <a id="preferences-configuration"></a>
|
||||
|
||||
The preference configuration backend is defined in the global [config.ini](03-Configuration.md#configuration-general-global) file.
|
||||
|
||||
### Store Preferences in INI Files <a id="preferences-configuration-ini"></a>
|
||||
|
||||
If preferences are stored in INI Files, Icinga Web 2 automatically creates one file per user using the username as
|
||||
file name for storing preferences. A INI file is created once a user saves changed preferences the first time.
|
||||
The files are located beneath the `preferences` directory beneath Icinga Web 2's configuration directory.
|
||||
|
||||
You need to add the following section to the global [config.ini](03-Configuration.md#configuration-general-global) file
|
||||
in order to store preferences in a file.
|
||||
|
||||
```
|
||||
[global]
|
||||
config_backend = "ini"
|
||||
```
|
||||
|
||||
### Store Preferences in a Database <a id="preferences-configuration-db"></a>
|
||||
|
||||
In order to be more flexible in distributed setups you can store preferences in a MySQL or in a PostgreSQL database.
|
||||
For storing preferences in a database, you have to define a [database resource](04-Resources.md#resources-configuration-database)
|
||||
You have to define a [database resource](04-Resources.md#resources-configuration-database)
|
||||
which will be referenced as resource for the preferences storage.
|
||||
|
||||
You need to add the following section to the global [config.ini](03-Configuration.md#configuration-general-global) file
|
||||
@ -39,6 +17,5 @@ in order to store preferences in a database.
|
||||
|
||||
```
|
||||
[global]
|
||||
config_backend = "db"
|
||||
config_resource = "icingaweb_db"
|
||||
```
|
||||
|
@ -6,6 +6,8 @@ v2.6 to v2.8 requires to follow the instructions for v2.7 too.
|
||||
## Upgrading to Icinga Web 2 2.11.x
|
||||
|
||||
* The Vagrant file and all its assets have been removed.
|
||||
* The `IniStore` class has been removed due to the deprecation of the Preferences ini backend.
|
||||
* The `DbStore` class has been removed and its methods have been added to `PreferencesStore` class.
|
||||
|
||||
**Database Schema**
|
||||
|
||||
|
@ -376,9 +376,7 @@ class Auth
|
||||
$config = new Config();
|
||||
}
|
||||
|
||||
if ($config->get('global', 'config_backend', 'db') !== 'none') {
|
||||
$preferencesConfig = new ConfigObject([
|
||||
'store' => $config->get('global', 'config_backend', 'db'),
|
||||
'resource' => $config->get('global', 'config_resource')
|
||||
]);
|
||||
|
||||
@ -395,9 +393,6 @@ class Auth
|
||||
);
|
||||
$preferences = new Preferences();
|
||||
}
|
||||
} else {
|
||||
$preferences = new Preferences();
|
||||
}
|
||||
|
||||
$user->setPreferences($preferences);
|
||||
|
||||
|
@ -3,18 +3,21 @@
|
||||
|
||||
namespace Icinga\User\Preferences;
|
||||
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger;
|
||||
use Exception;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Exception\NotWritableError;
|
||||
use Icinga\User;
|
||||
use Icinga\User\Preferences;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Data\Db\DbConnection;
|
||||
use Zend_Db_Expr;
|
||||
|
||||
/**
|
||||
* Preferences store factory
|
||||
*
|
||||
* Load and save user preferences by using a database
|
||||
*
|
||||
* Usage example:
|
||||
* <code>
|
||||
* <?php
|
||||
@ -23,11 +26,10 @@ use Icinga\Data\Db\DbConnection;
|
||||
* use Icinga\User\Preferences;
|
||||
* use Icinga\User\Preferences\PreferencesStore;
|
||||
*
|
||||
* // Create a INI store
|
||||
* // Create a db store
|
||||
* $store = PreferencesStore::create(
|
||||
* new ConfigObject(
|
||||
* 'store' => 'ini',
|
||||
* 'config_path' => '/path/to/preferences'
|
||||
* 'resource' => 'resource name'
|
||||
* ),
|
||||
* $user // Instance of \Icinga\User
|
||||
* );
|
||||
@ -37,8 +39,52 @@ use Icinga\Data\Db\DbConnection;
|
||||
* $store->save($preferences);
|
||||
* </code>
|
||||
*/
|
||||
abstract class PreferencesStore
|
||||
class PreferencesStore
|
||||
{
|
||||
/**
|
||||
* Column name for username
|
||||
*/
|
||||
const COLUMN_USERNAME = 'username';
|
||||
|
||||
/**
|
||||
* Column name for section
|
||||
*/
|
||||
const COLUMN_SECTION = 'section';
|
||||
|
||||
/**
|
||||
* Column name for preference
|
||||
*/
|
||||
const COLUMN_PREFERENCE = 'name';
|
||||
|
||||
/**
|
||||
* Column name for value
|
||||
*/
|
||||
const COLUMN_VALUE = 'value';
|
||||
|
||||
/**
|
||||
* Column name for created time
|
||||
*/
|
||||
const COLUMN_CREATED_TIME = 'ctime';
|
||||
|
||||
/**
|
||||
* Column name for modified time
|
||||
*/
|
||||
const COLUMN_MODIFIED_TIME = 'mtime';
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'icingaweb_user_preference';
|
||||
|
||||
/**
|
||||
* Stored preferences
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $preferences = [];
|
||||
|
||||
/**
|
||||
* Store config
|
||||
*
|
||||
@ -71,7 +117,7 @@ abstract class PreferencesStore
|
||||
*
|
||||
* @return ConfigObject
|
||||
*/
|
||||
public function getStoreConfig()
|
||||
public function getStoreConfig(): ConfigObject
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
@ -81,7 +127,7 @@ abstract class PreferencesStore
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
@ -89,21 +135,190 @@ abstract class PreferencesStore
|
||||
/**
|
||||
* Initialize the store
|
||||
*/
|
||||
abstract protected function init();
|
||||
protected function init(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Load preferences from source
|
||||
* Load preferences from the database
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws NotReadableError In case the database operation failed
|
||||
*/
|
||||
abstract public function load();
|
||||
public function load(): array
|
||||
{
|
||||
try {
|
||||
$select = $this->getStoreConfig()->connection->getDbAdapter()->select();
|
||||
$result = $select
|
||||
->from($this->table, [self::COLUMN_SECTION, 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 %s from database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
if ($result !== false) {
|
||||
$values = [];
|
||||
foreach ($result as $row) {
|
||||
$values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE};
|
||||
}
|
||||
|
||||
$this->preferences = $values;
|
||||
}
|
||||
|
||||
return $this->preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the given preferences
|
||||
* Save the given preferences in the database
|
||||
*
|
||||
* @param Preferences $preferences The preferences to save
|
||||
*/
|
||||
abstract public function save(Preferences $preferences);
|
||||
public function save(Preferences $preferences): void
|
||||
{
|
||||
$preferences = $preferences->toArray();
|
||||
|
||||
$sections = array_keys($preferences);
|
||||
|
||||
foreach ($sections as $section) {
|
||||
if (! array_key_exists($section, $this->preferences)) {
|
||||
$this->preferences[$section] = [];
|
||||
}
|
||||
|
||||
if (! array_key_exists($section, $preferences)) {
|
||||
$preferences[$section] = [];
|
||||
}
|
||||
|
||||
$toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]);
|
||||
if (!empty($toBeInserted)) {
|
||||
$this->insert($toBeInserted, $section);
|
||||
}
|
||||
|
||||
$toBeUpdated = array_intersect_key(
|
||||
array_diff_assoc($preferences[$section], $this->preferences[$section]),
|
||||
array_diff_assoc($this->preferences[$section], $preferences[$section])
|
||||
);
|
||||
|
||||
if (!empty($toBeUpdated)) {
|
||||
$this->update($toBeUpdated, $section);
|
||||
}
|
||||
|
||||
$toBeDeleted = array_keys(array_diff_key($this->preferences[$section], $preferences[$section]));
|
||||
if (!empty($toBeDeleted)) {
|
||||
$this->delete($toBeDeleted, $section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given preferences into the database
|
||||
*
|
||||
* @param array $preferences The preferences to insert
|
||||
* @param string $section The preferences in section to update
|
||||
*
|
||||
* @throws NotWritableError In case the database operation failed
|
||||
*/
|
||||
protected function insert(array $preferences, string $section): void
|
||||
{
|
||||
/** @var \Zend_Db_Adapter_Abstract $db */
|
||||
$db = $this->getStoreConfig()->connection->getDbAdapter();
|
||||
|
||||
try {
|
||||
foreach ($preferences as $key => $value) {
|
||||
$db->insert(
|
||||
$this->table,
|
||||
[
|
||||
self::COLUMN_USERNAME => $this->getUser()->getUsername(),
|
||||
$db->quoteIdentifier(self::COLUMN_SECTION) => $section,
|
||||
$db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key,
|
||||
self::COLUMN_VALUE => $value,
|
||||
self::COLUMN_CREATED_TIME => new Zend_Db_Expr('NOW()'),
|
||||
self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
|
||||
]
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new NotWritableError(
|
||||
'Cannot insert preferences for user %s into database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given preferences in the database
|
||||
*
|
||||
* @param array $preferences The preferences to update
|
||||
* @param string $section The preferences in section to update
|
||||
*
|
||||
* @throws NotWritableError In case the database operation failed
|
||||
*/
|
||||
protected function update(array $preferences, string $section): void
|
||||
{
|
||||
/** @var \Zend_Db_Adapter_Abstract $db */
|
||||
$db = $this->getStoreConfig()->connection->getDbAdapter();
|
||||
|
||||
try {
|
||||
foreach ($preferences as $key => $value) {
|
||||
$db->update(
|
||||
$this->table,
|
||||
[
|
||||
self::COLUMN_VALUE => $value,
|
||||
self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
|
||||
],
|
||||
[
|
||||
self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(),
|
||||
$db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section,
|
||||
$db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key
|
||||
]
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new NotWritableError(
|
||||
'Cannot update preferences for user %s in database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given preference names from the database
|
||||
*
|
||||
* @param array $preferenceKeys The preference names to delete
|
||||
* @param string $section The preferences in section to update
|
||||
*
|
||||
* @throws NotWritableError In case the database operation failed
|
||||
*/
|
||||
protected function delete(array $preferenceKeys, string $section): void
|
||||
{
|
||||
/** @var \Zend_Db_Adapter_Abstract $db */
|
||||
$db = $this->getStoreConfig()->connection->getDbAdapter();
|
||||
|
||||
try {
|
||||
$db->delete(
|
||||
$this->table,
|
||||
[
|
||||
self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(),
|
||||
$db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section,
|
||||
$db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys
|
||||
]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
throw new NotWritableError(
|
||||
'Cannot delete preferences for user %s from database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create preferences storage adapter from config
|
||||
@ -115,29 +330,15 @@ abstract class PreferencesStore
|
||||
*
|
||||
* @throws ConfigurationError When the configuration defines an invalid storage type
|
||||
*/
|
||||
public static function create(ConfigObject $config, User $user)
|
||||
public static function create(ConfigObject $config, User $user): self
|
||||
{
|
||||
$type = ucfirst(strtolower($config->get('store', 'db')));
|
||||
$storeClass = 'Icinga\\User\\Preferences\\Store\\' . $type . 'Store';
|
||||
if (!class_exists($storeClass)) {
|
||||
throw new ConfigurationError(
|
||||
'Preferences configuration defines an invalid storage type. Storage type %s not found',
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
if ($type === 'Ini') {
|
||||
Logger::warning('The preferences backend of type INI is deprecated and will be removed with version 2.11');
|
||||
$config->location = Config::resolvePath('preferences');
|
||||
} elseif ($type === 'Db') {
|
||||
$resourceConfig = ResourceFactory::getResourceConfig($config->resource);
|
||||
if ($resourceConfig->db === 'mysql') {
|
||||
$resourceConfig->charset = 'utf8mb4';
|
||||
}
|
||||
|
||||
$config->connection = ResourceFactory::createResource($resourceConfig);
|
||||
}
|
||||
|
||||
return new $storeClass($config, $user);
|
||||
return new self($config, $user);
|
||||
}
|
||||
}
|
||||
|
@ -1,255 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\User\Preferences\Store;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Exception\NotWritableError;
|
||||
use Icinga\User\Preferences;
|
||||
use Icinga\User\Preferences\PreferencesStore;
|
||||
use Zend_Db_Expr;
|
||||
|
||||
/**
|
||||
* Load and save user preferences by using a database
|
||||
*/
|
||||
class DbStore extends PreferencesStore
|
||||
{
|
||||
/**
|
||||
* Column name for username
|
||||
*/
|
||||
const COLUMN_USERNAME = 'username';
|
||||
|
||||
/**
|
||||
* Column name for section
|
||||
*/
|
||||
const COLUMN_SECTION = 'section';
|
||||
|
||||
/**
|
||||
* Column name for preference
|
||||
*/
|
||||
const COLUMN_PREFERENCE = 'name';
|
||||
|
||||
/**
|
||||
* Column name for value
|
||||
*/
|
||||
const COLUMN_VALUE = 'value';
|
||||
|
||||
/**
|
||||
* Column name for created time
|
||||
*/
|
||||
const COLUMN_CREATED_TIME = 'ctime';
|
||||
|
||||
/**
|
||||
* Column name for modified time
|
||||
*/
|
||||
const COLUMN_MODIFIED_TIME = 'mtime';
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'icingaweb_user_preference';
|
||||
|
||||
/**
|
||||
* Stored preferences
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $preferences = array();
|
||||
|
||||
/**
|
||||
* Set the table to use
|
||||
*
|
||||
* @param string $table The table name
|
||||
*/
|
||||
public function setTable($table)
|
||||
{
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the store
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Load preferences from the database
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws NotReadableError In case the database operation failed
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
try {
|
||||
$select = $this->getStoreConfig()->connection->getDbAdapter()->select();
|
||||
$result = $select
|
||||
->from($this->table, array(self::COLUMN_SECTION, 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 %s from database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
if ($result !== false) {
|
||||
$values = array();
|
||||
foreach ($result as $row) {
|
||||
$values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE};
|
||||
}
|
||||
$this->preferences = $values;
|
||||
}
|
||||
|
||||
return $this->preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the given preferences in the database
|
||||
*
|
||||
* @param Preferences $preferences The preferences to save
|
||||
*/
|
||||
public function save(Preferences $preferences)
|
||||
{
|
||||
$preferences = $preferences->toArray();
|
||||
|
||||
$sections = array_keys($preferences);
|
||||
|
||||
foreach ($sections as $section) {
|
||||
if (! array_key_exists($section, $this->preferences)) {
|
||||
$this->preferences[$section] = array();
|
||||
}
|
||||
if (! array_key_exists($section, $preferences)) {
|
||||
$preferences[$section] = array();
|
||||
}
|
||||
$toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]);
|
||||
if (!empty($toBeInserted)) {
|
||||
$this->insert($toBeInserted, $section);
|
||||
}
|
||||
|
||||
$toBeUpdated = array_intersect_key(
|
||||
array_diff_assoc($preferences[$section], $this->preferences[$section]),
|
||||
array_diff_assoc($this->preferences[$section], $preferences[$section])
|
||||
);
|
||||
if (!empty($toBeUpdated)) {
|
||||
$this->update($toBeUpdated, $section);
|
||||
}
|
||||
|
||||
$toBeDeleted = array_keys(array_diff_key($this->preferences[$section], $preferences[$section]));
|
||||
if (!empty($toBeDeleted)) {
|
||||
$this->delete($toBeDeleted, $section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given preferences into the database
|
||||
*
|
||||
* @param array $preferences The preferences to insert
|
||||
* @param string $section The preferences in section to update
|
||||
*
|
||||
* @throws NotWritableError In case the database operation failed
|
||||
*/
|
||||
protected function insert(array $preferences, $section)
|
||||
{
|
||||
/** @var \Zend_Db_Adapter_Abstract $db */
|
||||
$db = $this->getStoreConfig()->connection->getDbAdapter();
|
||||
|
||||
try {
|
||||
foreach ($preferences as $key => $value) {
|
||||
$db->insert(
|
||||
$this->table,
|
||||
array(
|
||||
self::COLUMN_USERNAME => $this->getUser()->getUsername(),
|
||||
$db->quoteIdentifier(self::COLUMN_SECTION) => $section,
|
||||
$db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key,
|
||||
self::COLUMN_VALUE => $value,
|
||||
self::COLUMN_CREATED_TIME => new Zend_Db_Expr('NOW()'),
|
||||
self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new NotWritableError(
|
||||
'Cannot insert preferences for user %s into database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given preferences in the database
|
||||
*
|
||||
* @param array $preferences The preferences to update
|
||||
* @param string $section The preferences in section to update
|
||||
*
|
||||
* @throws NotWritableError In case the database operation failed
|
||||
*/
|
||||
protected function update(array $preferences, $section)
|
||||
{
|
||||
/** @var \Zend_Db_Adapter_Abstract $db */
|
||||
$db = $this->getStoreConfig()->connection->getDbAdapter();
|
||||
|
||||
try {
|
||||
foreach ($preferences as $key => $value) {
|
||||
$db->update(
|
||||
$this->table,
|
||||
array(
|
||||
self::COLUMN_VALUE => $value,
|
||||
self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
|
||||
),
|
||||
array(
|
||||
self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(),
|
||||
$db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section,
|
||||
$db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new NotWritableError(
|
||||
'Cannot update preferences for user %s in database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given preference names from the database
|
||||
*
|
||||
* @param array $preferenceKeys The preference names to delete
|
||||
* @param string $section The preferences in section to update
|
||||
*
|
||||
* @throws NotWritableError In case the database operation failed
|
||||
*/
|
||||
protected function delete(array $preferenceKeys, $section)
|
||||
{
|
||||
/** @var \Zend_Db_Adapter_Abstract $db */
|
||||
$db = $this->getStoreConfig()->connection->getDbAdapter();
|
||||
|
||||
try {
|
||||
$db->delete(
|
||||
$this->table,
|
||||
array(
|
||||
self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(),
|
||||
$db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section,
|
||||
$db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys
|
||||
)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
throw new NotWritableError(
|
||||
'Cannot delete preferences for user %s from database',
|
||||
$this->getUser()->getUsername(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\User\Preferences\Store;
|
||||
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Exception\NotWritableError;
|
||||
use Icinga\User\Preferences;
|
||||
use Icinga\User\Preferences\PreferencesStore;
|
||||
use Icinga\File\Ini\IniParser;
|
||||
|
||||
/**
|
||||
* Load and save user preferences from and to INI files
|
||||
*/
|
||||
class IniStore extends PreferencesStore
|
||||
{
|
||||
/**
|
||||
* Preferences file of the given user
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $preferencesFile;
|
||||
|
||||
/**
|
||||
* Stored preferences
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $preferences = array();
|
||||
|
||||
/**
|
||||
* Initialize the store
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
$this->preferencesFile = sprintf(
|
||||
'%s/%s/config.ini',
|
||||
$this->getStoreConfig()->location,
|
||||
strtolower($this->getUser()->getUsername())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load preferences from source
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws NotReadableError When the INI file of the user exists and is not readable
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
if (file_exists($this->preferencesFile)) {
|
||||
if (! is_readable($this->preferencesFile)) {
|
||||
throw new NotReadableError(
|
||||
'Preferences INI file %s for user %s is not readable',
|
||||
$this->preferencesFile,
|
||||
$this->getUser()->getUsername()
|
||||
);
|
||||
} else {
|
||||
$this->preferences = IniParser::parseIniFile($this->preferencesFile)->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the given preferences
|
||||
*
|
||||
* @param Preferences $preferences The preferences to save
|
||||
*/
|
||||
public function save(Preferences $preferences)
|
||||
{
|
||||
$this->preferences = $preferences->toArray();
|
||||
|
||||
// TODO: Elaborate whether we need to patch the contents
|
||||
// $preferences = $preferences->toArray();
|
||||
// $this->update(array_diff_assoc($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()
|
||||
{
|
||||
Config::fromArray($this->preferences)->saveIni($this->preferencesFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Exception\NotWritableError;
|
||||
use Icinga\File\Ini\IniParser;
|
||||
use Icinga\User;
|
||||
use Icinga\User\Preferences\Store\IniStore;
|
||||
use Icinga\User\Preferences\Store\DbStore;
|
||||
use Icinga\User\Preferences\PreferencesStore;
|
||||
use Icinga\Util\DirectoryIterator;
|
||||
|
||||
class PreferencesCommand extends Command
|
||||
@ -61,12 +61,15 @@ class PreferencesCommand extends Command
|
||||
|
||||
Logger::info('Migrating INI preferences for user "%s" to database...', $userName);
|
||||
|
||||
$iniStore = new IniStore(new ConfigObject(['location' => $preferencesPath]), new User($userName));
|
||||
$dbStore = new DbStore(new ConfigObject(['connection' => $connection]), new User($userName));
|
||||
$dbStore = new PreferencesStore(new ConfigObject(['connection' => $connection]), new User($userName));
|
||||
|
||||
try {
|
||||
$dbStore->load();
|
||||
$dbStore->save(new User\Preferences($iniStore->load()));
|
||||
$dbStore->save(
|
||||
new User\Preferences(
|
||||
$this->loadIniFile($preferencesPath, (new User($userName))->getUsername())
|
||||
)
|
||||
);
|
||||
} catch (NotReadableError $e) {
|
||||
if ($e->getPrevious() !== null) {
|
||||
Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage());
|
||||
@ -89,7 +92,6 @@ class PreferencesCommand extends Command
|
||||
if ($this->params->has('resource') && ! $this->params->has('no-set-config-backend')) {
|
||||
$appConfig = Config::app();
|
||||
$globalConfig = $appConfig->getSection('global');
|
||||
$globalConfig['config_backend'] = 'db';
|
||||
$globalConfig['config_resource'] = $resource;
|
||||
|
||||
try {
|
||||
@ -102,4 +104,28 @@ class PreferencesCommand extends Command
|
||||
|
||||
Logger::info('Successfully migrated all local user preferences to database');
|
||||
}
|
||||
|
||||
private function loadIniFile(string $filePath, string $username): array
|
||||
{
|
||||
$preferences = [];
|
||||
$preferencesFile = sprintf(
|
||||
'%s/%s/config.ini',
|
||||
$filePath,
|
||||
strtolower($username)
|
||||
);
|
||||
|
||||
if (file_exists($preferencesFile)) {
|
||||
if (! is_readable($preferencesFile)) {
|
||||
throw new NotReadableError(
|
||||
'Preferences INI file %s for user %s is not readable',
|
||||
$preferencesFile,
|
||||
$username
|
||||
);
|
||||
} else {
|
||||
$preferences = IniParser::parseIniFile($preferencesFile)->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $preferences;
|
||||
}
|
||||
}
|
||||
|
@ -178,34 +178,6 @@ class UserDomainMigration
|
||||
{
|
||||
$config = Config::app();
|
||||
|
||||
$type = $config->get('global', 'config_backend', 'ini');
|
||||
|
||||
switch ($type) {
|
||||
case 'ini':
|
||||
$directory = Config::resolvePath('preferences');
|
||||
|
||||
$migration = array();
|
||||
|
||||
if (DirectoryIterator::isReadable($directory)) {
|
||||
foreach (new DirectoryIterator($directory) as $username => $path) {
|
||||
$user = new User($username);
|
||||
|
||||
if (! $this->mustMigrate($user)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$migrated = $this->migrateUser($user);
|
||||
|
||||
$migration[$path] = dirname($path) . '/' . $migrated->getUsername();
|
||||
}
|
||||
|
||||
foreach ($migration as $from => $to) {
|
||||
rename($from, $to);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'db':
|
||||
$resourceConfig = ResourceFactory::getResourceConfig($config->get('global', 'config_resource'));
|
||||
if ($resourceConfig->db === 'mysql') {
|
||||
$resourceConfig->charset = 'utf8mb4';
|
||||
@ -253,7 +225,6 @@ class UserDomainMigration
|
||||
$conn->getDbAdapter()->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function migrateRoles()
|
||||
{
|
||||
|
@ -28,9 +28,7 @@ class GeneralConfigStep extends Step
|
||||
$config[$section][$property] = $value;
|
||||
}
|
||||
|
||||
if ($config['global']['config_backend'] === 'db') {
|
||||
$config['global']['config_resource'] = $this->data['resourceName'];
|
||||
}
|
||||
|
||||
try {
|
||||
Config::fromArray($config)
|
||||
@ -57,12 +55,7 @@ class GeneralConfigStep extends Step
|
||||
? t('An exception\'s stacktrace is shown to every user by default.')
|
||||
: t('An exception\'s stacktrace is hidden from every user by default.')
|
||||
) . '</li>'
|
||||
. '<li>' . sprintf(
|
||||
$this->data['generalConfig']['global_config_backend'] === 'ini' ? sprintf(
|
||||
t('Preferences will be stored per user account in INI files at: %s'),
|
||||
Config::resolvePath('preferences')
|
||||
) : t('Preferences will be stored using a database.')
|
||||
) . '</li>'
|
||||
. '<li>' . t('Preferences will be stored using a database.') . '</li>'
|
||||
. '</ul>';
|
||||
|
||||
$type = $this->data['generalConfig']['logging_log'];
|
||||
|
@ -1,71 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Tests\Icinga\User\Preferences\Store;
|
||||
|
||||
use Mockery;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
use Icinga\User\Preferences\Store\IniStore;
|
||||
|
||||
class IniStoreWithSetGetPreferencesAndEmptyWrite extends IniStore
|
||||
{
|
||||
public function write()
|
||||
{
|
||||
// Gets called by IniStore::save
|
||||
}
|
||||
|
||||
public function setPreferences($preferences)
|
||||
{
|
||||
$this->preferences = $preferences;
|
||||
}
|
||||
|
||||
public function getPreferences()
|
||||
{
|
||||
return $this->preferences;
|
||||
}
|
||||
}
|
||||
|
||||
class IniStoreTest extends BaseTestCase
|
||||
{
|
||||
public function testWhetherPreferenceChangesAreApplied()
|
||||
{
|
||||
$store = $this->getStore();
|
||||
$store->setPreferences(array('testsection' => array('key1' => '1')));
|
||||
|
||||
$store->save(
|
||||
Mockery::mock('Icinga\User\Preferences', array(
|
||||
'toArray' => array('testsection' => array('key1' => '11', 'key2' => '2'))
|
||||
))
|
||||
);
|
||||
$this->assertEquals(
|
||||
array('testsection' => array('key1' => '11', 'key2' => '2')),
|
||||
$store->getPreferences(),
|
||||
'IniStore::save does not properly apply changed preferences'
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhetherPreferenceDeletionsAreApplied()
|
||||
{
|
||||
$store = $this->getStore();
|
||||
$store->setPreferences(array('testsection' => array('key' => 'value')));
|
||||
|
||||
$store->save(Mockery::mock('Icinga\User\Preferences', array('toArray' => array('testsection' => array()))));
|
||||
|
||||
$result = $store->getPreferences();
|
||||
|
||||
$this->assertEmpty($result['testsection'], 'IniStore::save does not delete removed preferences');
|
||||
}
|
||||
|
||||
protected function getStore()
|
||||
{
|
||||
return new IniStoreWithSetGetPreferencesAndEmptyWrite(
|
||||
new ConfigObject(
|
||||
array(
|
||||
'location' => 'some/Path/To/Some/Directory'
|
||||
)
|
||||
),
|
||||
Mockery::mock('Icinga\User', array('getUsername' => 'unittest'))
|
||||
);
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Tests\Icinga\User\Preferences\Store;
|
||||
namespace Tests\Icinga\User\Preferences;
|
||||
|
||||
use Icinga\User\Preferences\PreferencesStore;
|
||||
use Mockery;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\NotWritableError;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
use Icinga\User\Preferences\Store\DbStore;
|
||||
|
||||
class DatabaseMock
|
||||
{
|
||||
@ -22,19 +22,19 @@ class DatabaseMock
|
||||
|
||||
public function insert($table, $row)
|
||||
{
|
||||
$this->insertions[$row[DbStore::COLUMN_PREFERENCE]] = $row[DbStore::COLUMN_VALUE];
|
||||
$this->insertions[$row[PreferencesStore::COLUMN_PREFERENCE]] = $row[PreferencesStore::COLUMN_VALUE];
|
||||
}
|
||||
|
||||
public function update($table, $columns, $where)
|
||||
{
|
||||
$this->updates[$where[DbStore::COLUMN_PREFERENCE . '=?']] = $columns[DbStore::COLUMN_VALUE];
|
||||
$this->updates[$where[PreferencesStore::COLUMN_PREFERENCE . '=?']] = $columns[PreferencesStore::COLUMN_VALUE];
|
||||
}
|
||||
|
||||
public function delete($table, $where)
|
||||
{
|
||||
$this->deletions = array_merge(
|
||||
$this->deletions,
|
||||
$where[DbStore::COLUMN_PREFERENCE . ' IN (?)']
|
||||
$where[PreferencesStore::COLUMN_PREFERENCE . ' IN (?)']
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -57,7 +57,7 @@ class FaultyDatabaseMock extends DatabaseMock
|
||||
}
|
||||
}
|
||||
|
||||
class DbStoreWithSetPreferences extends DbStore
|
||||
class PreferencesStoreWithSetPreferences extends PreferencesStore
|
||||
{
|
||||
public function setPreferences(array $preferences)
|
||||
{
|
||||
@ -65,7 +65,7 @@ class DbStoreWithSetPreferences extends DbStore
|
||||
}
|
||||
}
|
||||
|
||||
class DbStoreTest extends BaseTestCase
|
||||
class PreferencesStoreTest extends BaseTestCase
|
||||
{
|
||||
public function testWhetherPreferenceInsertionWorks()
|
||||
{
|
||||
@ -78,9 +78,9 @@ class DbStoreTest extends BaseTestCase
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertArrayHasKey('key', $dbMock->insertions, 'DbStore::save does not insert new preferences');
|
||||
$this->assertEmpty($dbMock->updates, 'DbStore::save updates *new* preferences');
|
||||
$this->assertEmpty($dbMock->deletions, 'DbStore::save deletes *new* preferences');
|
||||
$this->assertArrayHasKey('key', $dbMock->insertions, 'PreferencesStore::save does not insert new preferences');
|
||||
$this->assertEmpty($dbMock->updates, 'PreferencesStore::save updates *new* preferences');
|
||||
$this->assertEmpty($dbMock->deletions, 'PreferencesStore::save deletes *new* preferences');
|
||||
}
|
||||
|
||||
public function testWhetherPreferenceInsertionThrowsNotWritableError()
|
||||
@ -108,9 +108,9 @@ class DbStoreTest extends BaseTestCase
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertArrayHasKey('key', $dbMock->updates, 'DbStore::save does not update existing preferences');
|
||||
$this->assertEmpty($dbMock->insertions, 'DbStore::save inserts *existing* preferences');
|
||||
$this->assertEmpty($dbMock->deletions, 'DbStore::save inserts *existing* preferneces');
|
||||
$this->assertArrayHasKey('key', $dbMock->updates, 'PreferencesStore::save does not update existing preferences');
|
||||
$this->assertEmpty($dbMock->insertions, 'PreferencesStore::save inserts *existing* preferences');
|
||||
$this->assertEmpty($dbMock->deletions, 'PreferencesStore::save inserts *existing* preferneces');
|
||||
}
|
||||
|
||||
public function testWhetherPreferenceUpdatesThrowNotWritableError()
|
||||
@ -139,9 +139,9 @@ class DbStoreTest extends BaseTestCase
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertContains('key', $dbMock->deletions, 'DbStore::save does not delete removed preferences');
|
||||
$this->assertEmpty($dbMock->insertions, 'DbStore::save inserts *removed* preferences');
|
||||
$this->assertEmpty($dbMock->updates, 'DbStore::save updates *removed* preferences');
|
||||
$this->assertContains('key', $dbMock->deletions, 'PreferencesStore::save does not delete removed preferences');
|
||||
$this->assertEmpty($dbMock->insertions, 'PreferencesStore::save inserts *removed* preferences');
|
||||
$this->assertEmpty($dbMock->updates, 'PreferencesStore::save updates *removed* preferences');
|
||||
}
|
||||
|
||||
public function testWhetherPreferenceDeletionThrowsNotWritableError()
|
||||
@ -160,7 +160,7 @@ class DbStoreTest extends BaseTestCase
|
||||
|
||||
protected function getStore($dbMock)
|
||||
{
|
||||
return new DbStoreWithSetPreferences(
|
||||
return new PreferencesStoreWithSetPreferences(
|
||||
new ConfigObject(
|
||||
array(
|
||||
'connection' => Mockery::mock(array('getDbAdapter' => $dbMock))
|
Loading…
x
Reference in New Issue
Block a user