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

Sponsored-by: Open Fifth <https://openfifth.co.uk>
Co-authored-by: Mark Hofstetter <koha@trust-box.at>
Signed-off-by: Catherine Small <c.small@rcs.ac.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Jacob O'Mara 2025-04-02 10:28:15 +01:00 committed by Lucas Gass
parent 77ee024313
commit 8e6b687f51
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
4 changed files with 137 additions and 1 deletions

View File

@ -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 $@;

View File

@ -428,6 +428,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__

View File

@ -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__

View File

@ -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 },