From 4682e4e534799644374a70b0688b71f4ff6d96c2 Mon Sep 17 00:00:00 2001 From: Jacob O'Mara Date: Wed, 2 Apr 2025 10:28:15 +0100 Subject: [PATCH] Bug 39522: Allow Valuebuilders to be installable as plugins This patch adds hooks to allow 'Valuebuilder' plugins to be installable using the standard plugin architecture for Koha. This enables cataloguing plugins to be dynamically installed, enabled, and disabled through the plugin system rather than being hardcoded framework components. Test plan: 1. Install the example valuebuilder plugin from GitHub (https://github.com/Jacobomara901/koha-plugin-value-builder) 2. Go to Administration > Plugins 3. Verify the valuebuilder plugin appears in the plugins list 4. Enable/disable the plugin and confirm the custom valuebuilder appears/disappears in the framework configuration 5. Assign the custom valuebuilder to a MARC subfield in the bibliographic framework 6. Create/edit a bibliographic record and verify the valuebuilder button appears next to the configured subfield 7. Click the valuebuilder button and confirm it functions correctly 8. Disable the plugin and verify the valuebuilder button disappears 9. Re-enable the plugin and confirm functionality is restored 10. Uninstall the plugin and verify it's completely removed Co-authored-by: Mark Hofstetter --- Koha/FrameworkPlugin.pm | 41 ++++++++++++++++++++++++++++++- Koha/Plugins.pm | 36 +++++++++++++++++++++++++++ Koha/Plugins/Base.pm | 36 +++++++++++++++++++++++++++ admin/marc_subfields_structure.pl | 25 +++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) mode change 100755 => 100644 admin/marc_subfields_structure.pl diff --git a/Koha/FrameworkPlugin.pm b/Koha/FrameworkPlugin.pm index d3876c606de..668e55b4592 100644 --- a/Koha/FrameworkPlugin.pm +++ b/Koha/FrameworkPlugin.pm @@ -213,6 +213,37 @@ sub _error { sub _load { my ($self) = @_; + # Try to find the class that can handle this plugin + my @plugins = Koha::Plugins->new()->get_valuebuilders_installed(); + + foreach my $vb (@plugins) { + my $plugin = $vb->{plugin}; + + # Check if this plugin provides the value builder we need + if ( $vb->{name} eq $self->{name} ) { + + # Store the plugin object for later use + $self->{plugin} = $plugin; + + # Get the builder and launcher directly from the plugin object + if ( $plugin->can('builder_code') ) { + $self->{builder} = sub { return $plugin->builder_code(@_); }; + } + + if ( $plugin->can('launcher') ) { + $self->{launcher} = sub { return $plugin->launcher(@_); }; + } + + if ( !$self->{builder} && !$self->{launcher} ) { + return $self->_error('Plugin does not contain builder_code nor launcher methods'); + } + + $self->{_loaded} = 1; + return 1; + } + } + + # If not found via plugin, try in standard dir my ( $rv, $file ); return $self->_error('Plugin needs a name') if !$self->{name}; #2chk $self->{path} //= _valuebuilderpath(); @@ -291,12 +322,20 @@ sub _generate_js { return $self->_error('Builder sub not defined'); } + # Make a copy of params and add the plugin object to it + my $builder_params = {%$params}; + + # Add the value builder's plugin if available + if ( $self->{name} && $self->{plugin} ) { + $builder_params->{plugin} = $self->{plugin}; + } + my @params = $self->{oldschool} // 0 ? ( $params->{dbh}, $params->{record}, $params->{tagslib}, $params->{id} ) - : ($params); + : ($builder_params); my @rv = &$sub(@params); return $self->_error( 'Builder sub failed: ' . $@ ) if $@; diff --git a/Koha/Plugins.pm b/Koha/Plugins.pm index 598c8152798..71650c66a0e 100644 --- a/Koha/Plugins.pm +++ b/Koha/Plugins.pm @@ -378,6 +378,42 @@ sub _restart_after_change { kill 'HUP', $parent_pid; } +=head2 get_valuebuilders_installed + + my @valuebuilders = Koha::Plugins->new->get_valuebuilders_installed(); + +Returns a list of all valuebuilder plugins provided by plugins. + +=cut + +sub get_valuebuilders_installed { + my ($self) = @_; + + # Get ENABLED plugins + my @plugins = $self->get_enabled_plugins(); + + my @valuebuilders; + + foreach my $plugin (@plugins) { + + # Check if plugin implements get_valuebuilder method + if ( $plugin->can('get_valuebuilder') ) { + + # Get the value builder from the plugin + my $valuebuilder = $plugin->get_valuebuilder(); + + if ($valuebuilder) { + push @valuebuilders, { + name => $valuebuilder, + plugin => $plugin, + }; + } + } + } + + return @valuebuilders; +} + 1; __END__ diff --git a/Koha/Plugins/Base.pm b/Koha/Plugins/Base.pm index e0e8f9a49f9..28d0244b35e 100644 --- a/Koha/Plugins/Base.pm +++ b/Koha/Plugins/Base.pm @@ -212,6 +212,26 @@ sub get_metadata { return $metadata; } +=head2 get_valuebuilders + + Get valuebuilder identifier provided by this plugin + my $valuebuilder_id = $plugin->get_valuebuilders(); + +=cut + +sub get_valuebuilders { + my ($self) = @_; + + # If this plugin has a get_valuebuilder method that returns a non-empty value, + # return that value in an array reference + if ( $self->can('get_valuebuilder') ) { + my $vb = $self->get_valuebuilder(); + return $vb ? [$vb] : []; + } + + return []; +} + =head2 get_qualified_table_name To avoid naming conflict, each plugins tables should use a fully qualified namespace. @@ -403,6 +423,22 @@ sub disable { return $self; } +=head2 get_valuebuilder_url + + Returns the full URL to launch this plugin's value builder + + my $url = $plugin->get_valuebuilder_url(); + +=cut + +sub get_valuebuilder_url { + my ($self) = @_; + + # Generate the URL to run.pl with the launcher method + my $plugin_class = ref($self); + return "/cgi-bin/koha/plugins/run.pl?class=$plugin_class&method=launcher"; +} + 1; __END__ diff --git a/admin/marc_subfields_structure.pl b/admin/marc_subfields_structure.pl old mode 100755 new mode 100644 index d150ea5c008..72ee9bd1513 --- a/admin/marc_subfields_structure.pl +++ b/admin/marc_subfields_structure.pl @@ -126,6 +126,31 @@ if ( $op eq 'add_form' ) { @value_builder = sort { $a cmp $b } @value_builder; closedir $dir_h; + # Add valuebuilders from plugins + if ( C4::Context->config("enable_plugins") ) { + require Koha::Plugins; + my $plugins = Koha::Plugins->new(); + + # Get all plugins first to debug + my @all_plugins = $plugins->GetPlugins(); + foreach my $plugin (@all_plugins) { + ( $plugin->can('get_valuebuilders') ? "yes" : "no" ); + } + + # Use the dedicated get_valuebuilders_installed method + my @plugin_valuebuilders = $plugins->get_valuebuilders_installed(); + + foreach my $vb_entry (@plugin_valuebuilders) { + my $vb_name = $vb_entry->{name}; + + # Avoid duplicates + push( @value_builder, $vb_name ) unless grep { $_ eq $vb_name } @value_builder; + } + + # Re-sort the list after adding plugin valuebuilders + @value_builder = sort { $a cmp $b } @value_builder; + } + # build values list my $mss = Koha::MarcSubfieldStructures->search( { tagfield => $tagfield, frameworkcode => $frameworkcode }, -- 2.49.0