1. Save button doesn’t reactivate when editing a JSON value (the original report)
KeyValueInputWidget emits change in exactly two places:
BlueSpiceFoundation/resources/bluespice.oojs/ui/widget/KeyValueInputWidget.js:270 — in onAddClick()
.../KeyValueInputWidget.js:332 — in onDeleteClick()
BlueSpiceConfigManager/resources/ui/pages/ConfigPage.js:92 is what enables the Save button, and it only listens for that change event.
Nothing connects the inputs of an existing row to it. getForm() (KeyValueInputWidget.js:111, addToWidgets at :117) stores the key/value widgets but never subscribes to
their change, and ObjectInputWidget.createInputs() (ObjectInputWidget.js:15–33) builds its sub-widgets without re-emitting change either.
So typing into “Plugin name”, “Data object (JSON)” or “Group sync settings (JSON)” on a saved row is genuinely invisible to the form — which is exactly why the button stays grey
and why the only workaround is delete + re-add (that path does go through onAddClick/onDeleteClick).
Fix direction: have ObjectInputWidget emit change when any sub-widget changes, and have getForm() connect the key and value widgets to re-emit change when
addToWidgets is set. I have not implemented or tested this one, unlike the two below.
2. Saved config is silently corrupted when re-rendered — this is the “fields restore to the previous profile” problem
This is the one I reported on 9 January, and it’s a one-character-class bug.
BlueSpiceFoundation/src/Html/OOUI/KeyObjectInputWidget.php:42, in getValueInput():
if ( $conf['type'] === 'json' ) {
$value[$key] = is_string( $value[$key] )
? $value[$key]
: FormatJson::encode( $value ); // encodes the WHOLE entry
}
It encodes $value (the entire entry) instead of $value[$key] (that field’s own value). And because the result is assigned back into $value[$key], the corruption compounds
across loop iterations — the second JSON field gets the already-corrupted first one nested inside it.
With this stored:
{"Login with Google":{"plugin":"OpenIDConnect","data":{"providerURL":"https://accounts.google.com","clientID":"…","clientsecret":"…"},"groupsyncs":[]}}
the form renders as:
Button label => Login with Google ✓
Plugin name => OpenIDConnect ✓
Data object (JSON) => {"plugin":"OpenIDConnect","data":{…},"groupsyncs":[]} ✗ the whole entry
Group sync settings (JSON) => {"plugin":"OpenIDConnect","data":"{\"plugin\":\"… ✗ that again, nested
Press Save from that state and the garbage is written back to bs_settings3. In our bs-config-manager log this shows up as a working config going straight to empty:
DistributionConnectorPluggableAuthConfig: {full config} -> {}
That is why the fields appear to “restore to the previous profile” and why the config can never be edited after the first save.
Fix (verified — after this, the sub-fields round-trip correctly):
--- a/src/Html/OOUI/KeyObjectInputWidget.php
+++ b/src/Html/OOUI/KeyObjectInputWidget.php
@@ -39,7 +39,7 @@
if ( $conf['type'] === 'json' ) {
$value[$key] = is_string( $value[$key] )
? $value[$key]
- : FormatJson::encode( $value );
+ : FormatJson::encode( $value[$key] );
}
After the patch:
Data object (JSON) => {"providerURL":"https://accounts.google.com","clientID":"…","clientsecret":"…"}
Group sync settings (JSON) => []
Note this widget is generic, but PluggableAuth/Config.php is the only config definition in the distribution that uses TYPE_JSON sub-fields (EventBus/EventServices.php uses
only TYPE_TEXT/TYPE_NUMBER), so PluggableAuth is the only visible victim.
3. Saving drops you back on the first tab
After saving, the ConfigManager jumps to Administration instead of staying on Authentication, which makes a successful save look like it did nothing.
BlueSpiceConfigManager/resources/ui/panel/ConfigManager.js:134:
if ( !this.selectedPage || !configPages.includes( this.selectedPage ) ) {
if ( !this.selectedPage || !configPages.includes( this.selectedPage ) ) {
The booklet’s select handler sets this.selectedPage = item.data — a page name string — but this compares it against configPages, an array of ConfigPage objects.
The check can never match, so every store reload falls through to selectFirstSelectablePage(). The else branch calling setPage( this.selectedPage ) confirms a string was
intended.
Fix:
--- a/resources/ui/panel/ConfigManager.js
+++ b/resources/ui/panel/ConfigManager.js
@@ -132,9 +132,10 @@
this.bookletLayout.addPages( configPages );
- if ( !this.selectedPage || !configPages.includes( this.selectedPage ) ) {
+ const configPageNames = configPages.map( ( configPage ) => configPage.getName() );
+ if ( !this.selectedPage || !configPageNames.includes( this.selectedPage ) ) {
this.bookletLayout.selectFirstSelectablePage();
- this.selectedPage = this.bookletLayout.getCurrentPage();
+ this.selectedPage = this.bookletLayout.getCurrentPage().getName();
} else {
this.bookletLayout.setPage( this.selectedPage );
}
One more, smaller
JsonArrayInputWidget.getValue() wraps JSON.parse in a try/catch and returns {} on failure, and the widget has no getValidity(), so it is skipped by
validateAddNewForm() entirely. A typo in “Data object (JSON)” is therefore accepted silently and saved as empty, with no error shown anywhere. That supports the earlier
suggestion in this thread for a proper JSON editor — or at minimum, validation that refuses to save unparseable input.
Also worth noting: ConfigManager.js’s save handler only checks response.error. The configmanager save task returns success: false with result.message when a record
fails validation, which the UI never surfaces — so a rejected save reports nothing at all to the user.