Extend plugin config with a grouping class

This commit is contained in:
the-djmaze 2022-02-09 17:48:05 +01:00
parent 05f1d62041
commit e0ebc999d5
10 changed files with 162 additions and 129 deletions

View file

@ -21,11 +21,11 @@ class PluginPopupView extends AbstractViewPopup {
readme: '' readme: ''
}); });
this.configures = ko.observableArray(); this.config = ko.observableArray();
this.addComputables({ this.addComputables({
hasReadme: () => !!this.readme(), hasReadme: () => !!this.readme(),
hasConfiguration: () => 0 < this.configures().length hasConfiguration: () => 0 < this.config().length
}); });
this.bDisabeCloseOnEsc = true; this.bDisabeCloseOnEsc = true;
@ -44,7 +44,7 @@ class PluginPopupView extends AbstractViewPopup {
Settings: {} Settings: {}
}; };
this.configures.forEach(oItem => { this.config.forEach(oItem => {
let value = oItem.value(); let value = oItem.value();
if (false === value || true === value) { if (false === value || true === value) {
value = value ? 1 : 0; value = value ? 1 : 0;
@ -64,7 +64,7 @@ class PluginPopupView extends AbstractViewPopup {
this.id(''); this.id('');
this.name(''); this.name('');
this.readme(''); this.readme('');
this.configures([]); this.config([]);
if (oPlugin) { if (oPlugin) {
this.id(oPlugin.Id); this.id(oPlugin.Id);
@ -73,16 +73,11 @@ class PluginPopupView extends AbstractViewPopup {
const config = oPlugin.Config; const config = oPlugin.Config;
if (arrayLength(config)) { if (arrayLength(config)) {
this.configures( this.config(
config.map(item => ({ config.map(item => {
value: ko.observable(item[0]), item.value = ko.observable(item.value);
placeholder: ko.observable(item[6]), return item;
Name: item[1], })
Type: item[2],
Label: item[3],
Default: item[4],
Desc: item[5]
}))
); );
} }
} }

View file

@ -6,9 +6,9 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = 'Change Password', NAME = 'Change Password',
VERSION = '2.9', VERSION = '2.12',
RELEASE = '2021-07-20', RELEASE = '2022-02-10',
REQUIRED = '2.9.1', REQUIRED = '2.12.0',
CATEGORY = 'Security', CATEGORY = 'Security',
DESCRIPTION = 'Extension to allow users to change their passwords'; DESCRIPTION = 'Extension to allow users to change their passwords';
@ -96,17 +96,21 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
->SetDefaultValue(70), ->SetDefaultValue(70),
]; ];
foreach ($this->getSupportedDrivers(true) as $name => $class) { foreach ($this->getSupportedDrivers(true) as $name => $class) {
$result[] = \RainLoop\Plugins\Property::NewInstance("driver_{$name}_enabled") $group = new \RainLoop\Plugins\PropertyCollection($name);
->SetLabel('Enable ' . $class::NAME) $props = [
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) \RainLoop\Plugins\Property::NewInstance("driver_{$name}_enabled")
->SetDescription($class::DESCRIPTION) ->SetLabel('Enable ' . $class::NAME)
->SetDefaultValue(false); ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
$result[] = \RainLoop\Plugins\Property::NewInstance("driver_{$name}_allowed_emails") ->SetDescription($class::DESCRIPTION)
->SetLabel('Allowed emails') ->SetDefaultValue(false),
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) \RainLoop\Plugins\Property::NewInstance("driver_{$name}_allowed_emails")
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@example.net user2@example1.net *@example2.net') ->SetLabel('Allowed emails')
->SetDefaultValue('*'); ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
$result = \array_merge($result, \call_user_func("{$class}::configMapping")); ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@example.net user2@example1.net *@example2.net')
->SetDefaultValue('*')
];
$group->exchangeArray(\array_merge($props, \call_user_func("{$class}::configMapping")));
$result[] = $group;
} }
return $result; return $result;
} }

View file

@ -791,34 +791,41 @@ trait Admin
$mResult = false; $mResult = false;
$sId = (string) $this->GetActionParam('Id', ''); $sId = (string) $this->GetActionParam('Id', '');
if (!empty($sId)) if (!empty($sId)) {
{
$oPlugin = $this->Plugins()->CreatePluginByName($sId); $oPlugin = $this->Plugins()->CreatePluginByName($sId);
if ($oPlugin) if ($oPlugin) {
{
$mResult = array( $mResult = array(
'Id' => $sId, '@Object' => 'Object/Plugin',
'Name' => $oPlugin->Name(), 'Id' => $sId,
'Readme' => $oPlugin->Description(), 'Name' => $oPlugin->Name(),
'Config' => array() 'Readme' => $oPlugin->Description(),
'Config' => array()
); );
$aMap = $oPlugin->ConfigMap(); $aMap = $oPlugin->ConfigMap();
$oConfig = $oPlugin->Config(); if (\is_array($aMap)) {
if (is_array($aMap)) $oConfig = $oPlugin->Config();
{ foreach ($aMap as $oItem) {
foreach ($aMap as $oItem) if ($oItem) {
{ if ($oItem instanceof \RainLoop\Plugins\Property) {
if ($oItem && ($oItem instanceof \RainLoop\Plugins\Property)) if (PluginPropertyType::PASSWORD === $oItem->Type()) {
{ $oItem->SetValue(APP_DUMMY);
$aItem = $oItem->ToArray(); } else {
$aItem[0] = $oConfig->Get('plugin', $oItem->Name(), ''); $oItem->SetValue($oConfig->Get('plugin', $oItem->Name(), ''));
if (PluginPropertyType::PASSWORD === $oItem->Type()) }
{ $mResult['Config'][] = $oItem;
$aItem[0] = APP_DUMMY; } else if ($oItem instanceof \RainLoop\Plugins\PropertyCollection) {
foreach ($oItem as $oSubItem) {
if ($oSubItem && $oSubItem instanceof \RainLoop\Plugins\Property) {
if (PluginPropertyType::PASSWORD === $oSubItem->Type()) {
$oSubItem->SetValue(APP_DUMMY);
} else {
$oSubItem->SetValue($oConfig->Get('plugin', $oSubItem->Name(), ''));
}
}
}
$mResult['Config'][] = $oItem;
} }
$mResult['Config'][] = $aItem;
} }
} }
} }
@ -841,7 +848,7 @@ trait Admin
if ($oPlugin) if ($oPlugin)
{ {
$oConfig = $oPlugin->Config(); $oConfig = $oPlugin->Config();
$aMap = $oPlugin->ConfigMap(); $aMap = $oPlugin->ConfigMap(true);
if (is_array($aMap)) if (is_array($aMap))
{ {
$aSettings = (array) $this->GetActionParam('Settings', []); $aSettings = (array) $this->GetActionParam('Settings', []);

View file

@ -7,39 +7,28 @@ class Plugin extends \RainLoop\Config\AbstractConfig
/** /**
* @var array * @var array
*/ */
private $aMap; private $aMap = array();
public function __construct(string $sPluginName, array $aMap = array()) public function __construct(string $sPluginName, array $aMap = array())
{ {
$this->aMap = is_array($aMap) ? $this->convertConfigMap($aMap) : array(); if (\count($aMap)) {
parent::__construct('plugin-'.$sPluginName.'.ini', '; SnappyMail plugin ('.$sPluginName.')');
}
private function convertConfigMap(array $aMap) : array
{
if (\count($aMap))
{
$aResultMap = array(); $aResultMap = array();
foreach ($aMap as /* @var $oProperty \RainLoop\Plugins\Property */ $oProperty) foreach ($aMap as /* @var $oProperty \RainLoop\Plugins\Property */ $oProperty) {
{ if ($oProperty) {
if ($oProperty)
{
$mValue = $oProperty->DefaultValue(); $mValue = $oProperty->DefaultValue();
$sValue = \is_array($mValue) && isset($mValue[0]) ? $mValue[0] : $mValue; $sValue = \is_array($mValue) && isset($mValue[0]) ? $mValue[0] : $mValue;
$aResultMap[$oProperty->Name()] = array($sValue, ''); $aResultMap[$oProperty->Name()] = array($sValue, '');
} }
} }
if (\count($aResultMap)) if (\count($aResultMap)) {
{ $this->aMap = array(
return array(
'plugin' => $aResultMap 'plugin' => $aResultMap
); );
} }
} }
return array(); parent::__construct('plugin-'.$sPluginName.'.ini', '; SnappyMail plugin ('.$sPluginName.')');
} }
protected function defaultValues() : array protected function defaultValues() : array

View file

@ -5,6 +5,7 @@ namespace RainLoop\Enumerations;
class PluginPropertyType class PluginPropertyType
{ {
const const
GROUP = 7,
STRING = 0, STRING = 0,
INT = 1, INT = 1,
STRING_TEXT = 2, STRING_TEXT = 2,

View file

@ -111,13 +111,31 @@ abstract class AbstractPlugin
return $this->sPath; return $this->sPath;
} }
final public function ConfigMap() : array final public function ConfigMap(bool $flatten = false) : array
{ {
if (null === $this->aConfigMap) if (null === $this->aConfigMap)
{ {
$this->aConfigMap = $this->configMapping(); $this->aConfigMap = $this->configMapping();
} }
if ($flatten) {
$result = [];
foreach ($this->aConfigMap as $oItem) {
if ($oItem) {
if ($oItem instanceof \RainLoop\Plugins\Property) {
$result[] = $oItem;
} else if ($oItem instanceof \RainLoop\Plugins\PropertyCollection) {
foreach ($oItem as $oSubItem) {
if ($oSubItem && $oSubItem instanceof \RainLoop\Plugins\Property) {
$result[] = $oSubItem;
}
}
}
}
}
return $result;
}
return $this->aConfigMap; return $this->aConfigMap;
} }

View file

@ -82,7 +82,7 @@ class Manager
->SetName($sName) ->SetName($sName)
->SetPath(static::getPluginPath($sName)) ->SetPath(static::getPluginPath($sName))
->SetPluginManager($this) ->SetPluginManager($this)
->SetPluginConfig(new \RainLoop\Config\Plugin($sName, $oPlugin->ConfigMap())) ->SetPluginConfig(new \RainLoop\Config\Plugin($sName, $oPlugin->ConfigMap(true)))
; ;
} }
@ -223,7 +223,7 @@ class Manager
if ($oPlugin) if ($oPlugin)
{ {
$aConfig = array(); $aConfig = array();
$aMap = $oPlugin->ConfigMap(); $aMap = $oPlugin->ConfigMap(true);
if (\is_array($aMap)) if (\is_array($aMap))
{ {
foreach ($aMap as /* @var $oPluginProperty \RainLoop\Plugins\Property */ $oPluginProperty) foreach ($aMap as /* @var $oPluginProperty \RainLoop\Plugins\Property */ $oPluginProperty)
@ -468,16 +468,15 @@ class Manager
return false; return false;
} }
public function ReadLang(string $sLang, array &$aLang) : self public function ReadLang(string $sLang, array &$aLang) : void
{ {
if ($this->bIsEnabled) if ($this->bIsEnabled) {
{ foreach ($this->aPlugins as $oPlugin) {
foreach ($this->aPlugins as $oPlugin) if ($oPlugin->UseLangs()) {
{
if ($oPlugin->UseLangs())
{
$sPath = $oPlugin->Path().'/langs/'; $sPath = $oPlugin->Path().'/langs/';
$aPLang = []; $aPLang = [];
// First get english
if (\is_file("{$sPath}en.ini")) { if (\is_file("{$sPath}en.ini")) {
$aPLang = \parse_ini_file("{$sPath}en.ini", true); $aPLang = \parse_ini_file("{$sPath}en.ini", true);
} else if (\is_file("{$sPath}en.json")) { } else if (\is_file("{$sPath}en.json")) {
@ -487,8 +486,8 @@ class Manager
$aLang = \array_replace_recursive($aLang, $aPLang); $aLang = \array_replace_recursive($aLang, $aPLang);
} }
if ('en' !== $sLang) // Now get native
{ if ('en' !== $sLang) {
$aPLang = []; $aPLang = [];
if (\is_file("{$sPath}{$sLang}.ini")) { if (\is_file("{$sPath}{$sLang}.ini")) {
$aPLang = \parse_ini_file("{$sPath}{$sLang}.ini", true); $aPLang = \parse_ini_file("{$sPath}{$sLang}.ini", true);
@ -504,8 +503,6 @@ class Manager
} }
} }
} }
return $this;
} }
public function IsEnabled() : bool public function IsEnabled() : bool

View file

@ -2,13 +2,18 @@
namespace RainLoop\Plugins; namespace RainLoop\Plugins;
class Property class Property implements \JsonSerializable
{ {
/** /**
* @var string * @var string
*/ */
private $sName; private $sName;
/**
* @var mixed
*/
private $mValue;
/** /**
* @var string * @var string
*/ */
@ -62,6 +67,14 @@ class Property
return $this; return $this;
} }
/**
* @param mixed $mValue
*/
public function SetValue($mValue) : void
{
$this->mValue = $mValue;
}
/** /**
* @param mixed $mDefaultValue * @param mixed $mDefaultValue
*/ */
@ -138,16 +151,17 @@ class Property
return $this->sPlaceholder; return $this->sPlaceholder;
} }
public function ToArray() : array public function jsonSerialize()
{ {
return array( return array(
'', '@Object' => 'Object/PluginProperty',
$this->sName, 'value' => $this->mValue,
$this->iType, 'placeholder' => $this->sPlaceholder,
$this->sLabel, 'Name' => $this->sName,
$this->mDefaultValue, 'Type' => $this->iType,
$this->sDesc, 'Label' => $this->sLabel,
$this->sPlaceholder 'Default' => $this->mDefaultValue,
'Desc' => $this->sDesc
); );
} }
} }

View file

@ -1,37 +1,35 @@
<label data-bind="text: Label, visible: 5 !== Type"></label> <label data-bind="text: Label, visible: 5 !== Type"></label>
<div> <!-- ko if: 0 === Type -->
<!-- ko if: 0 === Type --> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" data-bind="value: value, attr: {placeholder: placeholder}" />
data-bind="value: value, attr: {placeholder: placeholder}" /> <!-- /ko -->
<!-- /ko --> <!-- ko if: 1 === Type -->
<!-- ko if: 1 === Type --> <input type="number" step="1"
<input type="number" step="1" data-bind="value: value, attr: {placeholder: placeholder}" />
data-bind="value: value, attr: {placeholder: placeholder}" /> <!-- /ko -->
<!-- /ko --> <!-- ko if: 3 === Type -->
<!-- ko if: 3 === Type --> <input type="password" autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
<input type="password" autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false" data-bind="value: value, attr: {placeholder: placeholder}" />
data-bind="value: value, attr: {placeholder: placeholder}" /> <!-- /ko -->
<!-- /ko --> <!-- ko if: 2 === Type -->
<!-- ko if: 2 === Type --> <div data-bind="component: {
<div data-bind="component: { name: 'TextArea',
name: 'TextArea', params: { value: value, placeholder: placeholder }
params: { value: value, placeholder: placeholder } }"></div>
}"></div> <!-- /ko -->
<!-- /ko --> <!-- ko if: 4 === Type -->
<!-- ko if: 4 === Type --> <select data-bind="options: Default, value: value"></select>
<select data-bind="options: Default, value: value"></select> <!-- /ko -->
<!-- /ko --> <!-- ko if: 5 === Type -->
<!-- ko if: 5 === Type --> <div data-bind="component: {
<div data-bind="component: { name: 'Checkbox',
name: 'Checkbox', params: { value: value, label: Label }
params: { value: value, label: Label } }"></div>
}"></div> <!-- /ko -->
<!-- /ko --> <!-- ko if: 6 === Type -->
<!-- ko if: 6 === Type --> <input type="url"
<input type="url" data-bind="value: value, attr: {placeholder: placeholder}" />
data-bind="value: value, attr: {placeholder: placeholder}" /> <!-- /ko -->
<!-- /ko --> <!-- ko if: '' !== Desc -->
<!-- ko if: '' !== Desc --> <span tabindex="0" class="help-block"><span data-bind="text: Desc"></span></span>
<span tabindex="0" class="help-block"><span data-bind="text: Desc"></span></span> <!-- /ko -->
<!-- /ko -->
</div>

View file

@ -14,8 +14,18 @@
<a href="#" class="close" data-bind="click: function () { saveError('') }">×</a> <a href="#" class="close" data-bind="click: function () { saveError('') }">×</a>
<span data-bind="text: saveError"></span> <span data-bind="text: saveError"></span>
</div> </div>
<div data-bind="foreach: configures, visible: hasConfiguration"> <div data-bind="foreach: config, visible: hasConfiguration">
<!-- ko if: 7 == Type -->
<details open="">
<summary class="legend" data-bind="text: Label"></summary>
<div data-bind="foreach: config">
<div class="control-group" data-bind="template: { name: 'AdminSettingsPluginProperty' }"></div>
</div>
</details>
<!-- /ko -->
<!-- ko if: 7 != Type -->
<div class="control-group" data-bind="template: { name: 'AdminSettingsPluginProperty' }"></div> <div class="control-group" data-bind="template: { name: 'AdminSettingsPluginProperty' }"></div>
<!-- /ko -->
</div> </div>
</form> </form>
</div> </div>