Skip to content

Commit

Permalink
feat(groups): allow group specific plugin settings
Browse files Browse the repository at this point in the history
fixes #10346
  • Loading branch information
jeabakker committed Jun 2, 2021
1 parent a9103de commit 28f7e6e
Show file tree
Hide file tree
Showing 24 changed files with 458 additions and 296 deletions.
2 changes: 1 addition & 1 deletion actions/plugins/settings/remove.php
Expand Up @@ -19,7 +19,7 @@
return elgg_error_response(elgg_echo('actionunauthorized'));
}

if (!$plugin->unsetAllUserAndPluginSettings()) {
if (!$plugin->unsetAllEntityAndPluginSettings()) {
return elgg_error_response(elgg_echo('plugins:settings:remove:fail', [$plugin->getDisplayName()]));
}

Expand Down
4 changes: 2 additions & 2 deletions actions/plugins/usersettings/save.php
Expand Up @@ -24,8 +24,8 @@

$result = false;

foreach ($params as $k => $v) {
$result = $plugin->setUserSetting($k, $v, $user->guid);
foreach ($params as $name => $value) {
$result = $user->setPluginSetting($plugin->getID(), $name, $value);
if (!$result) {
return elgg_error_response(elgg_echo('plugins:usersettings:save:fail', [$plugin_name]));
}
Expand Down
26 changes: 23 additions & 3 deletions docs/appendix/upgrade-notes/3.x-to-4.0.rst
Expand Up @@ -760,12 +760,28 @@ Miscellaneous API changes
* The interface ``Friendable`` has been removed. Implemented functions in ``ElggUser`` have been moved to ``Elgg\Traits\Entity\Friends``
* The config flag ``profile_using_custom`` is no longer available
* The return value of ``elgg_create_river_item()`` will be ``false`` in the case the creation was prevented by the ``'create:before', 'river'`` event
* The constant ``ELGG_PLUGIN_USER_SETTING_PREFIX`` has been removed use the helper function ``\ElggUser::getNamespacedPluginSettingName()``
* The constant ``ELGG_PLUGIN_INTERNAL_PREFIX`` has been removed to get the plugin priority private setting name use ``\ElggPlugin::PRIORITY_SETTING_NAME``

Deprecated APIs
---------------

Class functions
~~~~~~~~~~~~~~~

* ``ElggPlugin::getUserSetting()`` use ``ElggUser::getPluginSetting()``
* ``ElggPlugin::setUserSetting()`` use ``ElggUser::setPluginSetting()``

Lib functions
~~~~~~~~~~~~~

* ``forward()`` use ``Elgg\Exceptions\HttpException`` instances or ``elgg_redirect_response()``

Plugin hooks
~~~~~~~~~~~~

* ``'usersettings', 'plugin'`` use the hook ``'plugin_setting', '<entity type>'``

Removed functions
-----------------

Expand All @@ -776,8 +792,11 @@ Class functions
* ``ElggFile::setDescription()`` use ``$file->description = $new_description``
* ``ElggGroup::addObjectToGroup()``
* ``ElggGroup::removeObjectFromGroup()``
* ``ElggPlugin::getAllUserSettings()``
* ``ElggPlugin::getDependencyReport()``
* ``ElggPlugin::getError()``
* ``ElggPlugin::unsetAllUserSettings()``
* ``ElggPlugin::unsetAllUserAndPluginSettings()`` use ``ElggPlugin::unsetAllEntityAndPluginSettings()``
* ``ElggWidget::getContext()`` use ``$entity->context``
* ``ElggWidget::setContext()`` use ``$entity->context = $context``
* ``Elgg\Notifications\NotificationsService::getDeprecatedHandler()``
Expand All @@ -796,18 +815,19 @@ Lib functions
* ``diagnostics_md5_dir()``
* ``elgg_add_subscription()`` use ``\ElggEntity::addSubscription()``
* ``elgg_get_available_languages()`` use ``elgg()->translator->getAvailableLanguages()``
* ``elgg_get_all_plugin_user_settings()`` use ``$plugin->getAllUserSettings($user_guid)``
* ``elgg_get_entities_from_plugin_user_settings()`` use ``elgg_get_entities()`` with private settings parameters and prefix your setting name with ``ELGG_PLUGIN_USER_SETTING_PREFIX``
* ``elgg_get_all_plugin_user_settings()``
* ``elgg_get_entities_from_plugin_user_settings()`` use ``elgg_get_entities()`` with private settings parameters and prefix your setting name with ``plugin:user_setting:``
* ``elgg_get_filter_tabs()`` use menu hooks on ``'register', 'menu:filter:<filter_id>'``
* ``elgg_get_loaded_css()`` use ``elgg_get_loaded_external_files('css', 'head')``
* ``elgg_get_loaded_js()`` use ``elgg_get_loaded_external_files('js', $location)``
* ``elgg_get_system_messages()`` use ``elgg()->system_messages->loadRegisters()``
* ``elgg_prepend_css_urls()``
* ``elgg_remove_subscription()`` use ``\ElggEntity::removeSubscription()``
* ``elgg_set_plugin_setting()`` use ``$plugin->setSetting($name, $value)``
* ``elgg_set_plugin_user_setting()`` use ``ElggUser::setPluginSetting()``
* ``elgg_set_system_messages()`` use ``elgg()->system_messages->saveRegisters()``
* ``elgg_unset_plugin_setting()`` use ``$plugin->unsetSetting($name)``
* ``elgg_unset_plugin_user_setting()`` use ``$plugin->unsetUserSetting($name, $user_guid)``
* ``elgg_unset_plugin_user_setting()`` use ``ElggUser::removePluginSetting()``
* ``get_language_completeness()`` use ``elgg()->translator->getLanguageCompleteness()``
* ``get_installed_translations()`` use ``elgg()->translator->getInstalledTranslations()``
* ``group_access_options()``
Expand Down
20 changes: 11 additions & 9 deletions docs/guides/hooks-list.rst
Expand Up @@ -1036,15 +1036,6 @@ Other
**is_member, group**
Return boolean for if the user ``$params['user']`` is a member of the group ``$params['group']``.

**usersetting, plugin**
Filter user settings for plugins. ``$params`` contains:

- ``user`` - An ElggUser instance
- ``plugin`` - An ElggPlugin instance
- ``plugin_id`` - The plugin ID
- ``name`` - The name of the setting
- ``value`` - The value to set

**setting, plugin**
Filter plugin settings. ``$params`` contains:

Expand All @@ -1053,6 +1044,17 @@ Other
- ``name`` - The name of the setting
- ``value`` - The value to set

**plugin_setting, <entity type>**
Can be used to change the value of the setting being saved

Params contains:
- ``entity`` - The ``ElggEntity`` where the plugin setting is being saved
- ``plugin_id`` - The ID of the plugin for which the setting is being saved
- ``name`` - The name of the setting being saved
- ``value`` - The original value of the setting being saved

Return value should be a scalar in order to be able to save it to the database. An error will be logged if this is not the case.

**relationship:url, <relationship_name>**
Filter the URL for the relationship object ``$params['relationship']``.

Expand Down
3 changes: 2 additions & 1 deletion docs/guides/settings.rst
Expand Up @@ -84,7 +84,8 @@ or

.. code-block:: php
elgg_set_plugin_user_setting($name, $value, $user_guid, $plugin_id);
$user = elgg_get_logged_in_user_entity();
$user->setPluginSetting($plugin_id, $name, $value);
.. warning::

Expand Down
124 changes: 5 additions & 119 deletions engine/classes/Elgg/Database/Plugins.php
Expand Up @@ -366,8 +366,7 @@ public function generateEntities(): bool {
}

// remove the priority.
$name = $this->namespacePrivateSetting('internal', 'priority');
$plugin->removePrivateSetting($name);
$plugin->removePrivateSetting(\ElggPlugin::PRIORITY_SETTING_NAME);

$plugin->disable();
}
Expand Down Expand Up @@ -476,12 +475,10 @@ public function exists(string $id): bool {
* @return int
*/
public function getMaxPriority() {
$priority = $this->namespacePrivateSetting('internal', 'priority');

$qb = Select::fromTable('entities', 'e');
$qb->select('MAX(CAST(ps.value AS unsigned)) as max')
->join('e', 'private_settings', 'ps', 'e.guid = ps.entity_guid')
->where($qb->compare('ps.name', '=', $priority, ELGG_VALUE_STRING))
->where($qb->compare('ps.name', '=', \ElggPlugin::PRIORITY_SETTING_NAME, ELGG_VALUE_STRING))
->andWhere($qb->compare('e.type', '=', 'object', ELGG_VALUE_STRING))
->andWhere($qb->compare('e.subtype', '=', 'plugin', ELGG_VALUE_STRING));

Expand Down Expand Up @@ -805,7 +802,7 @@ public function find(string $status = 'active'): array {
$volatile_data_name = 'select:value';
$options['select'] = ['ps.value'];
$options['private_setting_names'] = [
$this->namespacePrivateSetting('internal', 'priority'),
\ElggPlugin::PRIORITY_SETTING_NAME,
];
break;

Expand Down Expand Up @@ -883,7 +880,7 @@ protected function orderPluginsByPriority($plugins = [], $volatile_data_name = n
* @return bool
*/
public function setPriorities(array $order) {
$name = $this->namespacePrivateSetting('internal', 'priority');
$name = \ElggPlugin::PRIORITY_SETTING_NAME;

$plugins = $this->find('any');
if (empty($plugins)) {
Expand All @@ -903,7 +900,6 @@ public function setPriorities(array $order) {
// disabled plugins should not have a priority
if ($plugin->getPriority() !== null) {
// remove the priority
$name = $this->namespacePrivateSetting('internal', 'priority');
$plugin->removePrivateSetting($name);
}
continue;
Expand Down Expand Up @@ -947,116 +943,6 @@ public function reindexPriorities() {
return $this->setPriorities([]);
}

/**
* Namespaces a string to be used as a private setting name for a plugin.
*
* For user_settings, two namespaces are added: a user setting namespace and the
* plugin id.
*
* For internal (plugin priority), there is a single internal namespace added.
*
* @param string $type The type of setting: user_setting or internal.
* @param string $name The name to namespace.
* @param string $id The plugin's ID to namespace with. Required for user_setting.
*
* @return string
* @throws InvalidArgumentException
*/
public function namespacePrivateSetting($type, $name, $id = null) {
switch ($type) {
case 'user_setting':
if (!$id) {
throw new InvalidArgumentException("You must pass the plugin id for user settings");
}
$name = ELGG_PLUGIN_USER_SETTING_PREFIX . "$id:$name";
break;

case 'internal':
$name = ELGG_PLUGIN_INTERNAL_PREFIX . $name;
break;
}

return $name;
}

/**
* Get all settings (excluding user settings) for a plugin
*
* @param \ElggPlugin $plugin Plugin
*
* @return string[]
*/
public function getAllSettings(\ElggPlugin $plugin) {
if (!$plugin->guid) {
return [];
}

$values = $this->private_settings_cache->load($plugin->guid);
if (isset($values)) {
return $values;
}

$us_prefix = $this->namespacePrivateSetting('user_setting', '', $plugin->getID());

// Get private settings for user
$qb = Select::fromTable('private_settings');
$qb->select('name')
->addSelect('value')
->where($qb->compare('name', 'not like', "$us_prefix%", ELGG_VALUE_STRING))
->andWhere($qb->compare('entity_guid', '=', $plugin->guid, ELGG_VALUE_GUID));

$rows = $this->db->getData($qb);

$settings = [];

if (!empty($rows)) {
foreach ($rows as $row) {
$settings[$row->name] = $row->value;
}
}

$this->private_settings_cache->save($plugin->guid, $settings);

return $settings;
}

/**
* Returns an array of all plugin user settings for a user
*
* @param \ElggPlugin $plugin Plugin
* @param \ElggUser $user User
*
* @return array
* @see \ElggPlugin::getAllUserSettings()
*/
public function getAllUserSettings(\ElggPlugin $plugin, \ElggUser $user = null) {

// send an empty name so we just get the first part of the namespace
$prefix = $this->namespacePrivateSetting('user_setting', '', $plugin->getID());

$qb = Select::fromTable('private_settings');
$qb->select('name')
->addSelect('value')
->where($qb->compare('name', 'like', "{$prefix}%"));

if ($user) {
$qb->andWhere($qb->compare('entity_guid', '=', $user->guid, ELGG_VALUE_INTEGER));
}

$rows = $this->db->getData($qb);
if (empty($rows)) {
return [];
}

$settings = [];
foreach ($rows as $row) {
$name = substr($row->name, strlen($prefix));
$settings[$name] = $row->value;
}

return $settings;
}

/**
* Set plugin priority and adjust the priorities of other plugins
*
Expand All @@ -1069,7 +955,7 @@ public function setPriority(\ElggPlugin $plugin, $priority) {

$old_priority = $plugin->getPriority() ? : 1;

$name = $this->namespacePrivateSetting('internal', 'priority');
$name = \ElggPlugin::PRIORITY_SETTING_NAME;

if (!$plugin->setPrivateSetting($name, $priority)) {
return false;
Expand Down
79 changes: 79 additions & 0 deletions engine/classes/Elgg/Traits/Entity/PluginSettings.php
@@ -0,0 +1,79 @@
<?php

namespace Elgg\Traits\Entity;

/**
* Handle CRUD for plugin settings
*
* @since 4.0
*/
trait PluginSettings {

/**
* Save a plugin setting
*
* @param string $plugin_id plugin ID
* @param string $name setting name
* @param mixed $value setting value (needs to be a scalar)
*
* @return bool
*/
public function setPluginSetting(string $plugin_id, string $name, $value): bool {
$value = _elgg_services()->hooks->trigger('plugin_setting', $this->getType(), [
'entity' => $this,
'plugin_id' => $plugin_id,
'name' => $name,
'value' => $value,
], $value);

if (isset($value) && !is_scalar($value)) {
elgg_log("Invalid value type provided to save plugin setting '{$name}' for plugin '{$plugin_id}' only scalars are allowed", 'ERROR');
return false;
}

$name = $this->getNamespacedPluginSettingName($plugin_id, $name);

return $this->setPrivateSetting($name, $value);
}

/**
* Get a plugin setting
*
* @param string $plugin_id plugin ID
* @param string $name setting name
* @param mixed $default default setting value (will be cast to string)
*
* @return string
*/
public function getPluginSetting(string $plugin_id, string $name, $default = null): string {
$name = $this->getNamespacedPluginSettingName($plugin_id, $name);

return (string) ($this->getPrivateSetting($name) ?? $default);
}

/**
* Remove a plugin setting
*
* @param string $plugin_id plugin ID
* @param string $name setting name
*
* @return bool
*/
public function removePluginSetting(string $plugin_id, string $name): bool {
$name = $this->getNamespacedPluginSettingName($plugin_id, $name);

return $this->removePrivateSetting($name);
}

/**
* Get the namespaced private setting name where the plugin setting is saved
*
* @param string $plugin_id plugin ID
* @param string $name setting name
*
* @return string
*/
final public function getNamespacedPluginSettingName(string $plugin_id, string $name): string {
return "plugin:{$this->getType()}_setting:{$plugin_id}:{$name}";
}
}

0 comments on commit 28f7e6e

Please sign in to comment.