summaryrefslogtreecommitdiffstats
path: root/library/Icinga/User
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Icinga/User.php649
-rw-r--r--library/Icinga/User/Preferences.php169
-rw-r--r--library/Icinga/User/Preferences/PreferencesStore.php344
3 files changed, 1162 insertions, 0 deletions
diff --git a/library/Icinga/User.php b/library/Icinga/User.php
new file mode 100644
index 0000000..8610dd0
--- /dev/null
+++ b/library/Icinga/User.php
@@ -0,0 +1,649 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga;
+
+use DateTimeZone;
+use Icinga\Authentication\AdmissionLoader;
+use InvalidArgumentException;
+use Icinga\Application\Config;
+use Icinga\Authentication\Role;
+use Icinga\Exception\ProgrammingError;
+use Icinga\User\Preferences;
+use Icinga\Web\Navigation\Navigation;
+
+/**
+ * This class represents an authorized user
+ *
+ * You can retrieve authorization information (@TODO: Not implemented yet) or user information
+ */
+class User
+{
+ /**
+ * Firstname
+ *
+ * @var string
+ */
+ protected $firstname;
+
+ /**
+ * Lastname
+ *
+ * @var string
+ */
+ protected $lastname;
+
+ /**
+ * Users email address
+ *
+ * @var string
+ */
+ protected $email;
+
+ /**
+ * {@link username} without {@link domain}
+ *
+ * @var string
+ */
+ protected $localUsername;
+
+ /**
+ * Domain
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * More information about this user
+ *
+ * @var array
+ */
+ protected $additionalInformation = array();
+
+ /**
+ * Information if the user is externally authenticated
+ *
+ * Keys:
+ *
+ * 0: origin username
+ * 1: origin field name
+ *
+ * @var array
+ */
+ protected $externalUserInformation = array();
+
+ /**
+ * Whether restrictions should not apply to this user
+ *
+ * @var bool
+ */
+ protected $unrestricted = false;
+
+ /**
+ * Set of permissions
+ *
+ * @var array
+ */
+ protected $permissions = array();
+
+ /**
+ * Set of restrictions
+ *
+ * @var array
+ */
+ protected $restrictions = array();
+
+ /**
+ * Groups for this user
+ *
+ * @var array
+ */
+ protected $groups = array();
+
+ /**
+ * Roles of this user
+ *
+ * @var Role[]
+ */
+ protected $roles = array();
+
+ /**
+ * Preferences object
+ *
+ * @var Preferences
+ */
+ protected $preferences;
+
+ /**
+ * Whether the user is authenticated using a HTTP authentication mechanism
+ *
+ * @var bool
+ */
+ protected $isHttpUser = false;
+
+ /**
+ * Creates a user object given the provided information
+ *
+ * @param string $username
+ * @param string $firstname
+ * @param string $lastname
+ * @param string $email
+ */
+ public function __construct($username, $firstname = null, $lastname = null, $email = null)
+ {
+ $this->setUsername($username);
+
+ if ($firstname !== null) {
+ $this->setFirstname($firstname);
+ }
+
+ if ($lastname !== null) {
+ $this->setLastname($lastname);
+ }
+
+ if ($email !== null) {
+ $this->setEmail($email);
+ }
+ }
+
+ /**
+ * Setter for preferences
+ *
+ * @param Preferences $preferences
+ *
+ * @return $this
+ */
+ public function setPreferences(Preferences $preferences)
+ {
+ $this->preferences = $preferences;
+ return $this;
+ }
+
+ /**
+ * Getter for preferences
+ *
+ * @return Preferences
+ */
+ public function getPreferences()
+ {
+ if ($this->preferences === null) {
+ $this->preferences = new Preferences();
+ }
+
+ return $this->preferences;
+ }
+
+ /**
+ * Return all groups this user belongs to
+ *
+ * @return array
+ */
+ public function getGroups()
+ {
+ return $this->groups;
+ }
+
+ /**
+ * Set the groups this user belongs to
+ *
+ * @param array $groups
+ *
+ * @return $this
+ */
+ public function setGroups(array $groups)
+ {
+ $this->groups = $groups;
+ return $this;
+ }
+
+ /**
+ * Return true if the user is a member of this group
+ *
+ * @param string $group
+ *
+ * @return boolean
+ */
+ public function isMemberOf($group)
+ {
+ return in_array($group, $this->groups);
+ }
+
+ /**
+ * Get whether restrictions should not apply to this user
+ *
+ * @return bool
+ */
+ public function isUnrestricted()
+ {
+ return $this->unrestricted;
+ }
+
+ /**
+ * Set whether restrictions should not apply to this user
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setIsUnrestricted($state)
+ {
+ $this->unrestricted = (bool) $state;
+
+ return $this;
+ }
+
+ /**
+ * Get the user's permissions
+ *
+ * @return array
+ */
+ public function getPermissions()
+ {
+ return $this->permissions;
+ }
+
+ /**
+ * Set the user's permissions
+ *
+ * @param array $permissions
+ *
+ * @return $this
+ */
+ public function setPermissions(array $permissions)
+ {
+ if (! empty($permissions)) {
+ natcasesort($permissions);
+ $this->permissions = array_combine($permissions, $permissions);
+ }
+ return $this;
+ }
+
+ /**
+ * Return restriction information for this user
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ public function getRestrictions($name)
+ {
+ if (array_key_exists($name, $this->restrictions)) {
+ return $this->restrictions[$name];
+ }
+
+ return array();
+ }
+
+ /**
+ * Set the user's restrictions
+ *
+ * @param string[] $restrictions
+ *
+ * @return $this
+ */
+ public function setRestrictions(array $restrictions)
+ {
+ $this->restrictions = $restrictions;
+ return $this;
+ }
+
+ /**
+ * Get the roles of the user
+ *
+ * @return Role[]
+ */
+ public function getRoles()
+ {
+ return $this->roles;
+ }
+
+ /**
+ * Set the roles of the user
+ *
+ * @param Role[] $roles
+ *
+ * @return $this
+ */
+ public function setRoles(array $roles)
+ {
+ $this->roles = $roles;
+ return $this;
+ }
+
+ /**
+ * Getter for username
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->domain === null ? $this->localUsername : $this->localUsername . '@' . $this->domain;
+ }
+
+ /**
+ * Setter for username
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setUsername($name)
+ {
+ $parts = explode('\\', $name, 2);
+ if (count($parts) === 2) {
+ list($this->domain, $this->localUsername) = $parts;
+ } else {
+ $parts = explode('@', $name, 2);
+ if (count($parts) === 2) {
+ list($this->localUsername, $this->domain) = $parts;
+ } else {
+ $this->localUsername = $name;
+ $this->domain = null;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Getter for firstname
+ *
+ * @return string
+ */
+ public function getFirstname()
+ {
+ return $this->firstname;
+ }
+
+ /**
+ * Setter for firstname
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setFirstname($name)
+ {
+ $this->firstname = $name;
+ return $this;
+ }
+
+ /**
+ * Getter for lastname
+ *
+ * @return string
+ */
+ public function getLastname()
+ {
+ return $this->lastname;
+ }
+
+ /**
+ * Setter for lastname
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setLastname($name)
+ {
+ $this->lastname = $name;
+ return $this;
+ }
+
+ /**
+ * Getter for email
+ *
+ * @return string
+ */
+ public function getEmail()
+ {
+ return $this->email;
+ }
+
+ /**
+ * Setter for mail
+ *
+ * @param string $mail
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException When an invalid mail is provided
+ */
+ public function setEmail($mail)
+ {
+ if ($mail !== null && !filter_var($mail, FILTER_VALIDATE_EMAIL)) {
+ throw new InvalidArgumentException(
+ sprintf('Invalid mail given for user %s: %s', $this->getUsername(), $mail)
+ );
+ }
+
+ $this->email = $mail;
+ return $this;
+ }
+
+ /**
+ * Set the domain
+ *
+ * @param string $domain
+ *
+ * @return $this
+ */
+ public function setDomain($domain)
+ {
+ if ($domain && ($domain = trim($domain))) {
+ $this->domain = $domain;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get whether the user has a domain
+ *
+ * @return bool
+ */
+ public function hasDomain()
+ {
+ return $this->domain !== null;
+ }
+
+ /**
+ * Get the domain
+ *
+ * @return string
+ *
+ * @throws ProgrammingError If the user does not have a domain
+ */
+ public function getDomain()
+ {
+ if ($this->domain === null) {
+ throw new ProgrammingError(
+ 'User does not have a domain.'
+ . ' Use User::hasDomain() to check whether the user has a domain beforehand.'
+ );
+ }
+ return $this->domain;
+ }
+
+ /**
+ * Get the local username, ie. the username without its domain
+ *
+ * @return string
+ */
+ public function getLocalUsername()
+ {
+ return $this->localUsername;
+ }
+
+ /**
+ * Set additional information about user
+ *
+ * @param string $key
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setAdditional($key, $value)
+ {
+ $this->additionalInformation[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Getter for additional information
+ *
+ * @param string $key
+ * @return mixed|null
+ */
+ public function getAdditional($key)
+ {
+ if (isset($this->additionalInformation[$key])) {
+ return $this->additionalInformation[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the user's timezone
+ *
+ * If the user did not set a timezone, the default timezone set via config.ini is returned
+ *
+ * @return DateTimeZone
+ */
+ public function getTimeZone()
+ {
+ $tz = $this->preferences->get('timezone');
+ if ($tz === null) {
+ $tz = date_default_timezone_get();
+ }
+
+ return new DateTimeZone($tz);
+ }
+
+ /**
+ * Set additional external user information
+ *
+ * @param string $username
+ * @param string $field
+ *
+ * @return $this
+ */
+ public function setExternalUserInformation($username, $field)
+ {
+ $this->externalUserInformation = array($username, $field);
+ return $this;
+ }
+
+ /**
+ * Get additional external user information
+ *
+ * @return array
+ */
+ public function getExternalUserInformation()
+ {
+ return $this->externalUserInformation;
+ }
+
+ /**
+ * Return true if user has external user information set
+ *
+ * @return bool
+ */
+ public function isExternalUser()
+ {
+ return ! empty($this->externalUserInformation);
+ }
+
+ /**
+ * Get whether the user is authenticated using a HTTP authentication mechanism
+ *
+ * @return bool
+ */
+ public function getIsHttpUser()
+ {
+ return $this->isHttpUser;
+ }
+
+ /**
+ * Set whether the user is authenticated using a HTTP authentication mechanism
+ *
+ * @param bool $isHttpUser
+ *
+ * @return $this
+ */
+ public function setIsHttpUser($isHttpUser = true)
+ {
+ $this->isHttpUser = (bool) $isHttpUser;
+ return $this;
+ }
+
+ /**
+ * Whether the user has a given permission
+ *
+ * @param string $requiredPermission
+ *
+ * @return bool
+ */
+ public function can($requiredPermission)
+ {
+ list($permissions, $refusals) = AdmissionLoader::migrateLegacyPermissions([$requiredPermission]);
+ if (! empty($permissions)) {
+ $requiredPermission = array_pop($permissions);
+ } elseif (! empty($refusals)) {
+ throw new InvalidArgumentException(
+ 'Refusals are not supported anymore. Check for a grant instead!'
+ );
+ }
+
+ $granted = false;
+ foreach ($this->getRoles() as $role) {
+ if ($role->denies($requiredPermission)) {
+ return false;
+ }
+
+ if (! $granted && $role->grants($requiredPermission)) {
+ $granted = true;
+ }
+ }
+
+ return $granted;
+ }
+
+ /**
+ * Load and return this user's configured navigation of the given type
+ *
+ * @param string $type
+ *
+ * @return Navigation
+ */
+ public function getNavigation($type)
+ {
+ $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type, $this->getUsername());
+
+ if ($type === 'dashboard-pane') {
+ $panes = array();
+ foreach ($config as $dashletName => $dashletConfig) {
+ // TODO: Throw ConfigurationError if pane or url is missing
+ $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
+ }
+
+ $navigation = new Navigation();
+ foreach ($panes as $paneName => $dashlets) {
+ $navigation->addItem(
+ $paneName,
+ array(
+ 'type' => 'dashboard-pane',
+ 'dashlets' => $dashlets
+ )
+ );
+ }
+ } else {
+ $navigation = Navigation::fromConfig($config);
+ }
+
+ return $navigation;
+ }
+}
diff --git a/library/Icinga/User/Preferences.php b/library/Icinga/User/Preferences.php
new file mode 100644
index 0000000..b09462b
--- /dev/null
+++ b/library/Icinga/User/Preferences.php
@@ -0,0 +1,169 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\User;
+
+use Countable;
+
+/**
+ * User preferences container
+ *
+ * Usage example:
+ * <code>
+ * <?php
+ *
+ * use Icinga\User\Preferences;
+ *
+ * $preferences = new Preferences(); // Start with empty preferences
+ *
+ * $preferences = new Preferences(array('aPreference' => '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 Countable
+{
+ /**
+ * Preferences key-value array
+ *
+ * @var array
+ */
+ protected $preferences = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $preferences Preferences key-value array
+ */
+ public function __construct(array $preferences = array())
+ {
+ $this->preferences = $preferences;
+ }
+
+ /**
+ * Count all preferences
+ *
+ * @return int The number of preferences
+ */
+ public function count(): int
+ {
+ return count($this->preferences);
+ }
+
+ /**
+ * Determine whether a preference exists
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->preferences);
+ }
+
+ /**
+ * Write data to a preference
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function __set($name, $value)
+ {
+ $this->preferences[$name] = $value;
+ }
+
+ /**
+ * Retrieve a preference section
+ *
+ * @param string $name
+ *
+ * @return array|null
+ */
+ public function get($name)
+ {
+ if (array_key_exists($name, $this->preferences)) {
+ return $this->preferences[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve a value from a specific section
+ *
+ * @param string $section
+ * @param string $name
+ * @param null $default
+ *
+ * @return array|null
+ */
+ public function getValue($section, $name, $default = null)
+ {
+ if (array_key_exists($section, $this->preferences)
+ && array_key_exists($name, $this->preferences[$section])
+ ) {
+ return $this->preferences[$section][$name];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Magic method so that $obj->value will work.
+ *
+ * @param string $name
+ *
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Remove a given preference
+ *
+ * @param string $name Preference name
+ */
+ public function remove($name)
+ {
+ unset($this->preferences[$name]);
+ }
+
+ /**
+ * Determine if a preference is set and is not NULL
+ *
+ * @param string $name Preference name
+ *
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->preferences[$name]);
+ }
+
+ /**
+ * Unset a given preference
+ *
+ * @param string $name Preference name
+ */
+ public function __unset($name)
+ {
+ $this->remove($name);
+ }
+
+ /**
+ * Get preferences as array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->preferences;
+ }
+}
diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php
new file mode 100644
index 0000000..8ecc677
--- /dev/null
+++ b/library/Icinga/User/Preferences/PreferencesStore.php
@@ -0,0 +1,344 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\User\Preferences;
+
+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 Zend_Db_Expr;
+
+/**
+ * Preferences store factory
+ *
+ * Load and save user preferences by using a database
+ *
+ * Usage example:
+ * <code>
+ * <?php
+ *
+ * use Icinga\Data\ConfigObject;
+ * use Icinga\User\Preferences;
+ * use Icinga\User\Preferences\PreferencesStore;
+ *
+ * // Create a db store
+ * $store = PreferencesStore::create(
+ * new ConfigObject(
+ * 'resource' => 'resource name'
+ * ),
+ * $user // Instance of \Icinga\User
+ * );
+ *
+ * $preferences = new Preferences($store->load());
+ * $preferences->aPreference = 'value';
+ * $store->save($preferences);
+ * </code>
+ */
+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
+ *
+ * @var ConfigObject
+ */
+ protected $config;
+
+ /**
+ * Given user
+ *
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * Create a new store
+ *
+ * @param ConfigObject $config The config for this adapter
+ * @param User $user The user to which these preferences belong
+ */
+ public function __construct(ConfigObject $config, User $user)
+ {
+ $this->config = $config;
+ $this->user = $user;
+ $this->init();
+ }
+
+ /**
+ * Getter for the store config
+ *
+ * @return ConfigObject
+ */
+ public function getStoreConfig(): ConfigObject
+ {
+ return $this->config;
+ }
+
+ /**
+ * Getter for the user
+ *
+ * @return User
+ */
+ public function getUser(): User
+ {
+ return $this->user;
+ }
+
+ /**
+ * Initialize the store
+ */
+ protected function init(): void
+ {
+ }
+
+ /**
+ * Load preferences from the database
+ *
+ * @return array
+ *
+ * @throws NotReadableError In case the database operation failed
+ */
+ 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 in the database
+ *
+ * @param Preferences $preferences The preferences to save
+ */
+ 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
+ *
+ * @param ConfigObject $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
+ */
+ public static function create(ConfigObject $config, User $user): self
+ {
+ $resourceConfig = ResourceFactory::getResourceConfig($config->resource);
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ $config->connection = ResourceFactory::createResource($resourceConfig);
+
+ return new self($config, $user);
+ }
+}