[Bug 43666] New: Allow plugins to register additional Single Table Inheritance subclasses
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 Bug ID: 43666 Summary: Allow plugins to register additional Single Table Inheritance subclasses Initiative type: --- Sponsorship --- status: Product: Koha Version: Main Hardware: All OS: All Status: NEW Severity: enhancement Priority: P5 - low Component: Architecture, internals, and plumbing Assignee: koha-bugs@lists.koha-community.org Reporter: martin.renvoize@openfifth.co.uk QA Contact: testopia@bugs.koha-community.org Target Milestone: --- This patch adds Koha::Objects::Mixin::SingleTableInheritance::Pluggable, an extension of the Koha::Objects::Mixin::SingleTableInheritance mixin added in bug 43663. It lets an installed and enabled Koha Plugin register additional Single Table Inheritance (STI) subclasses against a Koha::Objects plural class, provided all of the following are true: 1. The plural class itself opts in at the code level, by composing this mixin instead of the base one. This is a core developer decision, not overridable at runtime. 2. The contributing plugin has been explicitly permitted, for that specific target plural class, via a new per-plugin, per-target toggle in the Plugin management UI. Off by default for every (plugin, target) pair. 3. A new koha-conf.xml entry, enable_plugin_sti_registration, is enabled. Off by default; a full kill-switch overriding both of the above when off, for hosts who want to forbid the capability outright. Discovery reuses Koha's existing Koha::Plugins::GetPlugins({ method => ... }) capability-declaration mechanism (the same approach Koha::SuggestionEngine and Koha::RecordProcessor already use for their own plugin discovery) - no new registry, no filesystem scanning, no Module::Pluggable. A permitted plugin declares its contributions via one public method, additional_sti_classes, returning a hash keyed by target plural class name; each named class self-declares its own dispatch key exactly like a core STI subclass does (see bug 43663), so that key is never duplicated between the plugin and core. A core-registered dispatch key always wins over any plugin contribution. An invalid contribution (fails to load, isn't a subclass of the target's own base class, or doesn't implement the dispatch-key method) is dropped with a logged warning, never a fatal error, so one misbehaving plugin cannot break dispatch for any other row or any other plugin. Koha::File::Transports (already migrated onto the base mixin in bug 43663) is opted into this new capability as the proving-ground consumer, with no other behaviour change - confirmed by the full existing File::Transport test suite passing unmodified with the new capability left at its default (off). This is designed to let a plugin trial a new subclass (for example, an additional file transport backend) without requiring a core patch first; if it proves itself, promoting it into core is a small, two-line change (move the file, add it to the plural class's own reviewed subclass list) with no data migration, since dispatch is driven purely by the persisted column value, not by which mechanism supplied the class. Depends on bug 43663 (Koha::Objects::Mixin::SingleTableInheritance), which this bug builds directly on top of. Test plan: 1. Apply the patches and restart_all. 2. Confirm Administration > File transports behaves identically to before this patch, with everything left at its defaults (no visible change is the expected result at this step). 3. Set enable_plugin_sti_registration to 1 in koha-conf.xml, restart_all. 4. Install and enable a plugin implementing additional_sti_classes for Koha::File::Transports (a companion example plugin will be linked from this bug's comments). 5. In the Plugin management UI's actions menu for that plugin, confirm an option to allow registering classes for Koha::File::Transports appears, and toggle it on. 6. Go to Administration > File transports, create a new file transport using the plugin-contributed transport type, and confirm it dispatches to the plugin's class. 7. Toggle the permission back off. Confirm the plugin-contributed transport type is no longer offered, and any already-created row of that type falls back to generic behaviour rather than erroring. 8. Set enable_plugin_sti_registration back to 0. Confirm re-toggling permission on in the Plugin management UI has no effect while the kill-switch is off. -- You are receiving this mail because: You are watching all bug changes. You are the assignee for the bug.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> changed: What |Removed |Added ---------------------------------------------------------------------------- Depends on| |43663 Referenced Bugs: https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43663 [Bug 43663] Add Koha::Objects::Mixin::SingleTableInheritance for polymorphic subclass dispatch -- You are receiving this mail because: You are watching all bug changes. You are the assignee for the bug.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> changed: What |Removed |Added ---------------------------------------------------------------------------- Assignee|koha-bugs@lists.koha-commun |martin.renvoize@openfifth.c |ity.org |o.uk -- You are receiving this mail because: You are the assignee for the bug. You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> changed: What |Removed |Added ---------------------------------------------------------------------------- CC| |dcook@prosentient.com.au, | |kyle@bywatersolutions.com -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> changed: What |Removed |Added ---------------------------------------------------------------------------- Status|NEW |ASSIGNED -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> changed: What |Removed |Added ---------------------------------------------------------------------------- Sponsorship status|--- |Unsponsored Status|ASSIGNED |Needs Signoff Patch complexity|--- |Medium patch -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #1 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Created attachment 207006 --> https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=207006&action=edit Bug 43666: Add Koha::Objects::Mixin::SingleTableInheritance::Pluggable Adds Koha::Objects::Mixin::SingleTableInheritance::Pluggable, which inherits from ::SingleTableInheritance (bug 43663) and overrides only _sti_map, merging in classes contributed by plugins via a public additional_sti_classes() method - reusing the same GetPlugins({ method => ... }) discovery pattern Koha::SuggestionEngine/Koha::RecordProcessor already use. No filesystem scanning, no new registry. Composing ::Pluggable is itself the code-level opt-in gate. Two further gates apply: a koha-conf.xml kill-switch, enable_plugin_sti_registration, defaulting off; and the master enable_plugins switch, which _sti_map now honours the same way every other plugin discovery call site does. Plugin contributions are merged defensively - eval-wrapped per plugin, dropped with a warning if a plugin's method dies, returns the wrong shape, or contributes a class that fails to load or doesn't subclass the target's base class. Results are cached per target class via Koha::Cache::Memory::Lite. Core-declared keys are always merged in last, so they structurally win over any plugin contribution. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #2 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Created attachment 207007 --> https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=207007&action=edit Bug 43666: Add plugins-sti-toggle.pl controller Adds plugins/plugins-sti-toggle.pl: loads the requested plugin, calls set_sti_registration_allowed_for($target, $allowed), and redirects back to the plugin list. CSRF-protected (op=cud-toggle + validated csrf_token). The class load and toggle call are eval-wrapped with a can() guard, so an unloadable or unrelated class logs a warning and falls through to the redirect instead of a raw 500. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #3 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Created attachment 207008 --> https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=207008&action=edit Bug 43666: Show per-target STI registration toggles in the Plugin management UI Adds a per-target toggle entry to each plugin's actions menu in plugins-home.tt, driven by plugin.sti_registration_targets - one entry per target class the plugin contributes to, labelled "Allow/Disallow registering classes for <target>" based on its current state. Each toggle is a CSRF-protected inline POST form (csrf_token, op=cud-toggle, hidden class/target/allowed fields), styled as a dropdown-item to match the existing menu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #4 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Created attachment 207009 --> https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=207009&action=edit Bug 43666: Opt Koha::File::Transports into pluggable STI registration Switches Koha::File::Transports from ::SingleTableInheritance to ::Pluggable, so a permitted, enabled plugin can register additional file transport backends (e.g. WebDAV) without a core patch. With the kill-switch off and no plugin permitted, behaviour is unchanged - confirmed by the full existing File::Transport suite passing unmodified. store()'s post-store re-dispatch used a separate, hardcoded sftp/ftp/ local map rather than the real STI dispatch - a plugin-contributed transport stored through it would silently re-bless to the generic base class instead of its own subclass. Now resolves via Koha::File::Transports->_sti_object_class($self), the same mechanism find()/search() use. Test plan: 1. Apply the patches and restart_all. 2. Confirm Administration > File transports behaves identically to before this patch, with everything left at its defaults (no visible change is the expected result at this step). 3. prove t/db_dependent/Koha/File/Transport.t - confirm the "Test store() re-blesses via the STI dispatch map" subtest passes, verifying store() returns the correct subclass instance for sftp, ftp, local, and an unrecognized transport value. 4. Set <enable_plugin_sti_registration>1</enable_plugin_sti_registration> in koha-conf.xml, restart_all. 5. Install and enable a plugin implementing additional_sti_classes for Koha::File::Transports (e.g. a companion WebDAV transport plugin, linked from this bug's comments). 6. In the Plugin management UI's actions menu for that plugin, confirm a "Allow registering classes for Koha::File::Transports" option appears, and toggle it on. 7. Go to Administration > File transports, create a new file transport using the plugin-contributed transport type, and confirm it dispatches to the plugin's class (e.g. via a real or local test endpoint for that transport). 8. Toggle the permission back off in the Plugin management UI. Confirm the plugin-contributed transport type is no longer offered when creating a new file transport, and any already-created row of that type falls back to generic Koha::File::Transport behaviour rather than erroring. 9. Set <enable_plugin_sti_registration>0</enable_plugin_sti_registration> again. Confirm re-toggling permission on in the Plugin management UI has no effect while the kill-switch is off. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #5 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Created attachment 207010 --> https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=207010&action=edit Bug 43666: Relax file_transports.transport from enum to varchar The transport column being a MySQL enum('ftp','sftp','local') means a plugin-contributed transport type could never actually be persisted. Converts the column to varchar(191), keeping NOT NULL and the 'sftp' default. Validation now lives in the STI dispatch map and the admin UI's option list, not a hardcoded database enum. The atomicupdate is idempotent, checking the current column type via INFORMATION_SCHEMA. Verified end-to-end by storing a transport='webdav' row and confirming it persists and falls back to generic Koha::File::Transport behaviour when no plugin registers that key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #6 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Created attachment 207011 --> https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=207011&action=edit Bug 43666: Automated Schema Update Regenerates Koha::Schema::Result::FileTransport after the file_transports.transport enum-to-varchar conversion. Auto-generated by dbic; nothing below the "DO NOT MODIFY" marker needed changing for this column-type-only update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #7 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Created attachment 207012 --> https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=207012&action=edit Bug 43666: Drive transport options from the STI map, relax API enum With the transport column relaxed to varchar, the remaining blockers are the hardcoded <option> lists in the admin template and the fixed enum in the REST API definition. Admin UI: admin/file_transports.pl now builds the option list from a curated core label map (FTP/SFTP) plus any new key present in Koha::File::Transports->_sti_map that core's own _sti_classes don't already declare - so only plugin-contributed keys are appended. 'local' still doesn't appear, matching prior behaviour. Both add and edit forms iterate this list. Mirrors how admin/smtp_servers.pl already drives its transport select from metadata. REST API: removes the enum constraint on transport (plain string now) - incompatible with plugin-extensible values. The enum also listed 'file' where the database said 'local'; moot now the constraint is gone. t/db_dependent/api/v1/file_transports.t passes after yarn build and restart_all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #8 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- And, the promised plugin that showcases this: https://github.com/openfifth/koha-plugin-file-transport-webdav -- You are receiving this mail because: You are watching all bug changes.
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43666 --- Comment #9 from Martin Renvoize (ashimema) <martin.renvoize@openfifth.co.uk> --- Full test plan: pluggable STI registration end to end, using the WebDAV companion plugin This walks through the whole feature from a clean KTD instance: bringing up an instance with the plugin mounted, pointing it at a WebDAV server, enabling the registration through the Plugin management UI, and creating a file transport that dispatches to the plugin's class. A bonus final section uses a second, unrelated plugin (Transport Browser) to show that a plugin-contributed transport type is a first-class citizen to any code that consumes Koha::File::Transport generically, not just this bug's own admin screen. The companion plugin (koha-plugin-file-transport-webdav) is a separate repository, not part of this bug's own patch series: https://github.com/openfifth/koha-plugin-file-transport-webdav 1. Get a WebDAV server to point Koha at Option A, self-hosted with Docker (recommended - repeatable, and you control it): docker run -d --name webdav-test \ -e AUTH_TYPE=Basic -e USERNAME=koha -e PASSWORD=koha \ -p 8090:80 \ bytemark/webdav Note: this image's SSL_CERT=selfsigned option is currently broken (confirmed against a matching upstream issue on the image's own repo), so this uses plain HTTP rather than HTTPS with a self-signed cert. That is fine for this test - the plugin's TLS-verification-skip code path (see step 5) simply won't be exercised this way, only via mocked unit tests. Option B, a public test WebDAV account, for a quick manual check without running anything locally: https://www.dlp-test.com/webdav_pub/ (Note: a public, third-party service - fine for a one-off manual look, but not something to rely on for repeatable testing, and not something this plan builds the rest of these steps around.) The rest of this plan assumes Option A, reachable at http://localhost:8090/ from the host, and at http://webdav:80/ from inside a KTD container if you add the same server as a docker-compose service on the KTD instance's own network instead of running it standalone (see compose/webdav.yml in the plugin's repo for a ready-made fragment that does this). 2. Get the plugin git clone https://github.com/openfifth/koha-plugin-file-transport-webdav For KTD development testing, mount it directly rather than packaging it: ktd --proxy --name kohadev \ --single-plugin "$(pwd)/koha-plugin-file-transport-webdav" \ up -d ktd --name kohadev --wait-ready 180 (Use your own instance name in place of "kohadev" throughout. --proxy is required for the instance to be reachable at the usual <instance>-intra.kohadev.home / <instance>.kohadev.home hostnames - without it you'll get a 404 from the shared proxy, not a connection error, which can be confusing.) If testing against a packaged install instead of KTD, no .kpz release has been cut yet - download the repository as a zip from GitHub and upload it via Administration > Plugins > Upload plugin. 3. Turn on the kill-switch The enable_plugin_sti_registration key in koha-conf.xml defaults to 0 (off) and is a full kill-switch: nothing in this bug does anything at all while it's off, regardless of any other setting. In your instance's koha-conf.xml (for a KTD instance, /etc/koha/sites/<instance>/koha-conf.xml inside the container): <enable_plugin_sti_registration>1</enable_plugin_sti_registration> placed alongside the existing <enable_plugins> entry, then: ktd --name kohadev --shell --run 'restart_all' 4. Confirm the plugin is installed and enabled Log in to the staff interface, go to Administration > Plugins. Confirm "File Transport: WebDAV" is listed. If its status isn't already "Enabled", enable it from the actions menu. 5. Allow the plugin to register its class Still in Administration > Plugins, open the actions menu for "File Transport: WebDAV" again. You should see an entry reading: Allow registering classes for Koha::File::Transports Click it. The entry should flip to read "Forbid registering classes for Koha::File::Transports" - this is the per-plugin-per-target-class permission toggle, off by default, independent of the kill-switch in step 3 (both must be on for anything to actually appear). 6. Confirm no visible change until this point Go to Administration > File transports. With everything above done, this should look exactly as it did before this bug - the point of the gating is that installing and even permitting a plugin has zero effect on any existing instance unless every gate above is deliberately opened. 7. Create a WebDAV file transport Administration > File transports > New file transport. "WebDAV" should now appear as a Transport option (it will not appear if any of steps 3-5 were skipped). Fill in: Transport: WebDAV Host: http://webdav/ (from inside a KTD container, if you added the WebDAV server as a compose service on the same network - see compose/webdav.yml in the plugin's repo) or http://<your-docker-host-ip>:8090/ if reaching it via the host's own published port instead User name: koha Password: koha Note the host field needs an explicit http:// or https:// prefix - a bare hostname defaults to HTTPS, and this test server has no working TLS (see step 1). Save. This triggers an automatic background connection test; refresh the list after a few seconds and confirm the row shows "Tests passing". If it shows "Tests failing", the most common causes are: host missing its http:// prefix, wrong host/port, or (if testing from outside Docker) a typo in the IP address. 8. Exercise it From the row's actions menu, or via the "Files" view for that transport, list, upload, download and rename a file against the WebDAV server, and confirm each operation succeeds and is reflected on the server (e.g. via a WebDAV client pointed at the same server, or docker exec into the container and looking at the filesystem directly). 9. Confirm the permission actually gates behaviour, not just the option list Go back to Administration > Plugins and click "Forbid registering classes for Koha::File::Transports" to toggle the permission back off. Return to Administration > File transports: - "WebDAV" should no longer be offered as a Transport option when creating a new one. - The already-created WebDAV row from step 7 should still be listed, and should fall back to generic Koha::File::Transport behaviour (e.g. attempting to use it should fail gracefully, not throw an unhandled error) rather than erroring. Toggle the permission back on afterwards if you want to keep testing. 10. Confirm the kill-switch overrides everything Set enable_plugin_sti_registration back to 0 in koha-conf.xml and restart_all again. Confirm that even with the per-plugin permission still on from step 5, "WebDAV" is no longer offered anywhere - the kill-switch is the final word regardless of any other toggle. Set it back to 1 if you want to keep testing further. 11. Optional: confirm a plugin-contributed transport is a first-class Koha::File::Transport to other code, not just this bug's own screen Install a second, unrelated plugin, Transport Browser (https://github.com/openfifth/koha-plugin-transport-browser), the same way as step 2 (note: --single-plugin only mounts one plugin directory at a time - to run both together, use --plugins instead, which mounts your whole local plugins folder and enables everything found in it, or add a second docker volume manually). Its tool page lists every configured file transport generically and lets you browse it - open it and confirm the WebDAV transport from step 7 appears alongside any FTP/SFTP ones and can be browsed the same way, with no special-casing needed for it to work. (Its badge colouring only has CSS defined for SFTP/FTP today, so the WebDAV badge will show unstyled/plain - a cosmetic gap in that plugin, not a defect in this bug.) -- You are receiving this mail because: You are watching all bug changes.
participants (1)
-
bugzilla-daemon@bugs.koha-community.org