3 # Copyright 2012 Kyle Hall
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 use Array::Utils qw( array_minus );
24 use List::MoreUtils qw( any );
25 use Module::Load::Conditional qw( can_load );
27 use Module::Pluggable search_path => ['Koha::Plugin'], except => qr/::Edifact(|::Line|::Message|::Order|::Segment|::Transport)$/;
33 use Koha::Cache::Memory::Lite;
34 use Koha::Exceptions::Plugin;
35 use Koha::Plugins::Datas;
36 use Koha::Plugins::Methods;
38 use constant ENABLED_PLUGINS_CACHE_KEY => 'enabled_plugins';
41 my $pluginsdir = C4::Context->config("pluginsdir");
42 my @pluginsdir = ref($pluginsdir) eq 'ARRAY' ? @$pluginsdir : $pluginsdir;
43 push @INC, array_minus(@pluginsdir, @INC) ;
44 pop @INC if $INC[-1] eq '.';
49 Koha::Plugins - Module for loading and managing plugins.
58 my ( $class, $args ) = @_;
60 return unless ( C4::Context->config("enable_plugins") || $args->{'enable_plugins'} );
62 $args->{'pluginsdir'} = C4::Context->config("pluginsdir");
64 return bless( $args, $class );
69 Calls a plugin method for all enabled plugins
71 @responses = Koha::Plugins->call($method, @args)
73 Note: Pass your arguments as refs, when you want subsequent plugins to use the value
74 updated by preceding plugins, provided that these plugins support that.
79 my ($class, $method, @args) = @_;
81 return unless C4::Context->config('enable_plugins');
84 my @plugins = $class->get_enabled_plugins( { verbose => 0 } );
85 @plugins = grep { $_->can($method) } @plugins;
87 # TODO: Remove warn when after_hold_create is removed from the codebase
88 warn "after_hold_create is deprecated and will be removed soon. Contact the following plugin's authors: " . join( ', ', map {$_->{metadata}->{name}} @plugins)
89 if $method eq 'after_hold_create' and @plugins;
91 foreach my $plugin (@plugins) {
92 my $response = eval { $plugin->$method(@args) };
94 warn sprintf("Plugin error (%s): %s", $plugin->get_metadata->{name}, $@);
98 push @responses, $response;
104 =head2 get_enabled_plugins
106 Returns a list of enabled plugins.
108 @plugins = Koha::Plugins->get_enabled_plugins( [ verbose => 1 ] );
112 sub get_enabled_plugins {
113 my ( $class, $params ) = @_;
115 return unless C4::Context->config('enable_plugins');
117 my $enabled_plugins = Koha::Cache::Memory::Lite->get_from_cache(ENABLED_PLUGINS_CACHE_KEY);
118 unless ($enabled_plugins) {
119 my $verbose = $params->{verbose} // $class->_verbose;
120 $enabled_plugins = [];
124 my $rs = Koha::Plugins::Datas->search({ plugin_key => '__ENABLED__', plugin_value => 1 });
125 @plugin_classes = $rs->get_column('plugin_class');
130 foreach my $plugin_class (@plugin_classes) {
131 next unless can_load( modules => { $plugin_class => undef }, verbose => $verbose, nocache => 1 );
133 my $plugin = eval { $plugin_class->new() };
134 if ($@ || !$plugin) {
135 warn "Failed to instantiate plugin $plugin_class: $@";
139 push @$enabled_plugins, $plugin;
141 Koha::Cache::Memory::Lite->set_in_cache(ENABLED_PLUGINS_CACHE_KEY, $enabled_plugins);
144 return @$enabled_plugins;
149 # Return false when running unit tests
150 return exists $ENV{_} && $ENV{_} =~ /\/prove(\s|$)|\/koha-qa\.pl$|\.t$/ ? 0 : 1;
153 =head2 feature_enabled
155 Returns a boolean denoting whether a plugin based feature is enabled or not.
157 $enabled = Koha::Plugins->feature_enabled('method_name');
161 sub feature_enabled {
162 my ( $class, $method ) = @_;
164 return 0 unless C4::Context->config('enable_plugins');
166 my $key = "ENABLED_PLUGIN_FEATURE_" . $method;
167 my $feature = Koha::Cache::Memory::Lite->get_from_cache($key);
168 unless ( defined($feature) ) {
169 my @plugins = $class->get_enabled_plugins( { verbose => 0 } );
170 my $enabled = any { $_->can($method) } @plugins;
171 Koha::Cache::Memory::Lite->set_in_cache( $key, $enabled );
178 This will return a list of all available plugins, optionally limited by
179 method or metadata value.
181 my @plugins = Koha::Plugins::GetPlugins({
182 method => 'some_method',
183 metadata => { some_key => 'some_value' },
184 [ all => 1, errors => 1, verbose => 1 ],
187 The method and metadata parameters are optional.
188 If you pass multiple keys in the metadata hash, all keys must match.
190 If you pass errors (only used in plugins-home), we return two arrayrefs:
192 ( $good, $bad ) = Koha::Plugins::GetPlugins( { errors => 1 } );
194 If you pass verbose, you can enable or disable explicitly warnings
195 from Module::Load::Conditional. Disabled by default to not flood
201 my ( $self, $params ) = @_;
203 my $method = $params->{method};
204 my $req_metadata = $params->{metadata} // {};
205 my $errors = $params->{errors};
207 # By default dont warn here unless asked to do so.
208 my $verbose = $params->{verbose} // 0;
210 my $filter = ( $method ) ? { plugin_method => $method } : undef;
212 my $plugin_classes = Koha::Plugins::Methods->search(
214 { columns => 'plugin_class',
217 )->_resultset->get_column('plugin_class');
220 # Loop through all plugins that implement at least a method
221 my ( @plugins, @failing );
222 while ( my $plugin_class = $plugin_classes->next ) {
223 if ( can_load( modules => { $plugin_class => undef }, verbose => $verbose, nocache => 1 ) ) {
226 my $failed_instantiation;
229 $plugin = $plugin_class->new({
230 enable_plugins => $self->{'enable_plugins'}
231 # loads even if plugins are disabled
232 # FIXME: is this for testing without bothering to mock config?
237 $failed_instantiation = 1;
240 next if $failed_instantiation;
242 next unless $plugin->is_enabled or
243 defined($params->{all}) && $params->{all};
245 # filter the plugin out by metadata
246 my $plugin_metadata = $plugin->get_metadata;
250 and any { !$plugin_metadata->{$_} || $plugin_metadata->{$_} ne $req_metadata->{$_} } keys %$req_metadata;
252 push @plugins, $plugin;
254 push @failing, { error => 1, name => $plugin_class };
258 return $errors ? ( \@plugins, \@failing ) : @plugins;
261 =head2 InstallPlugins
263 Koha::Plugins::InstallPlugins( [ verbose => 1 ] )
265 This method iterates through all plugins physically present on a system.
266 For each plugin module found, it will test that the plugin can be loaded,
267 and if it can, will store its available methods in the plugin_methods table.
269 NOTE: We reload all plugins here as a protective measure in case someone
270 has removed a plugin directly from the system without using the UI
275 my ( $self, $params ) = @_;
276 my $verbose = $params->{verbose} // $self->_verbose;
278 my @plugin_classes = $self->plugins();
281 foreach my $plugin_class (@plugin_classes) {
282 if ( can_load( modules => { $plugin_class => undef }, verbose => $verbose, nocache => 1 ) ) {
283 next unless $plugin_class->isa('Koha::Plugins::Base');
286 my $failed_instantiation;
289 $plugin = $plugin_class->new({ enable_plugins => $self->{'enable_plugins'} });
293 $failed_instantiation = 1;
296 next if $failed_instantiation;
298 Koha::Plugins::Methods->search({ plugin_class => $plugin_class })->delete();
300 foreach my $method ( @{ Class::Inspector->methods( $plugin_class, 'public' ) } ) {
301 Koha::Plugins::Method->new(
303 plugin_class => $plugin_class,
304 plugin_method => $method,
309 push @plugins, $plugin;
313 Koha::Cache::Memory::Lite->clear_from_cache(ENABLED_PLUGINS_CACHE_KEY);
320 Koha::Plugins->RemovePlugins( {
321 [ plugin_class => MODULE_NAME, destructive => 1, disable => 1 ],
324 This is primarily for unit testing. Take care when you pass the
325 destructive flag (know what you are doing)!
327 The method removes records from plugin_methods for one or all plugins.
329 If you pass the destructive flag, it will remove records too from
330 plugin_data for one or all plugins. Destructive overrules disable.
332 If you pass disable, it will disable one or all plugins (in plugin_data).
334 If you do not pass destructive or disable, this method does not touch
335 records in plugin_data. The cache key for enabled plugins will be cleared
336 only if you pass disabled or destructive.
341 my ( $class, $params ) = @_;
344 $params->{plugin_class}
345 ? ( plugin_class => $params->{plugin_class} )
348 Koha::Plugins::Methods->search($cond)->delete;
349 if ( $params->{destructive} ) {
350 Koha::Plugins::Datas->search($cond)->delete;
351 Koha::Cache::Memory::Lite->clear_from_cache( Koha::Plugins->ENABLED_PLUGINS_CACHE_KEY );
352 } elsif ( $params->{disable} ) {
353 $cond->{plugin_key} = '__ENABLED__';
354 Koha::Plugins::Datas->search($cond)->update( { plugin_value => 0 } );
355 Koha::Cache::Memory::Lite->clear_from_cache( Koha::Plugins->ENABLED_PLUGINS_CACHE_KEY );
364 Kyle M Hall <kyle.m.hall@gmail.com>