From 80e6083b8c628e7a5aed9e99f0c6960289593867 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Wed, 8 Jul 2026 07:13:27 +0200 Subject: [PATCH 1/2] feat(perms): add negative-permissions admin UI with tri-state controls --- .horde.yml | 2 +- js/perms-tristate.js | 293 +++++++++ src/DefaultInjectorBindings.php | 3 + src/Factory/PermsUi.php | 196 ++++++ src/Factory/ServerRequestFactory.php | 43 ++ src/Perms/PermsUiInterface.php | 114 ++++ src/Perms/Ui.php | 890 +++++++++++++++++++++++++++ 7 files changed, 1540 insertions(+), 1 deletion(-) create mode 100644 js/perms-tristate.js create mode 100644 src/Factory/PermsUi.php create mode 100644 src/Factory/ServerRequestFactory.php create mode 100644 src/Perms/PermsUiInterface.php create mode 100644 src/Perms/Ui.php diff --git a/.horde.yml b/.horde.yml index 0e67fd66..fb9c4ed8 100644 --- a/.horde.yml +++ b/.horde.yml @@ -77,7 +77,7 @@ dependencies: horde/mime_viewer: ^3 horde/notification: ^3 horde/pack: ^2 - horde/perms: ^3 + horde/perms: ^3.1 horde/prefs: ^3 horde/rpc: ^3 horde/secret: ^3 diff --git a/js/perms-tristate.js b/js/perms-tristate.js new file mode 100644 index 00000000..ac293845 --- /dev/null +++ b/js/perms-tristate.js @@ -0,0 +1,293 @@ +/** + * perms-tristate.js. Client-side niceties for the negative-permissions + * administration dialog. + * + * Nothing here is required for the form to submit. The tri-state radios + * are plain elements. The server reads the POST + * body directly and folds the verdicts into grant / deny bitmasks in + * Horde\Core\Perms\Ui::foldTriState(). + * + * What this script adds: + * 1. Arrow-key navigation between cells in a tri-state grid. + * 2. Per-column bulk actions (grant / neutral / deny all). + * 3. Optional live "N rules will change" preview under the submit + * button, so admins see the impact of a bulk action before it + * hits the server. + * + * The script uses only native browser APIs. No jQuery, no Prototype. + * It attaches through DOMContentLoaded and cleans up nothing on + * unload. A single admin form has a page lifetime, so leak-free + * teardown is not a concern. + */ + +(() => { + 'use strict'; + + /* Cell verdict values must match Horde\Core\Perms\Ui::foldTriState. */ + const VERDICTS = ['grant', 'neutral', 'deny']; + + document.addEventListener('DOMContentLoaded', () => { + const forms = document.querySelectorAll('form.perms-form'); + forms.forEach(wireForm); + wireTreeToggles(); + }); + + /** + * Wires one perms form. Idempotent per form because the DOM only + * gets one DOMContentLoaded pass. + */ + function wireForm(form) { + wireKeyboardNav(form); + wireBulkActions(form); + wireLivePreview(form); + } + + /** + * Wires the client-side expand/collapse buttons in the perms + * admin tree. The server emits every node with its children + * inline in the DOM (Horde\Core\Perms\Ui::renderTree passes + * static=true to the ResponsiveRenderer) so the toggle is a pure + * display: none flip on the sibling