From 2991a5b434cdc314a2446cb9a2bba8e12f6d27e1 Mon Sep 17 00:00:00 2001 From: Kyle M Hall <kyle.m.hall@gmail.com> Date: Fri, 22 Jan 2010 16:34:52 +0000 Subject: [PATCH] Bug 7804 - Add Koha Plugin System Adds support for custom plugins. At the moment the Plugins feature supports two types of plugins, reports and tools. Plugins are installed by uploading KPZ ( Koha Plugin Zip ) packages. A KPZ file is just a zip file containing the perl files, template files, and any other files neccessary to make the plugin work. Test plan: 1) Apply patch 2) Run updatedatabase.pl 3) Create the directory /var/lib/koha/plugins 4) Add the lines <pluginsdir>/var/lib/koha/plugins</pluginsdir> <enable_plugins>1</enable_plugins>" to your koha-conf.xml file 5) Add the line Alias /plugin/ "/var/lib/koha/plugins/" to your koha-httpd.conf file 6) Restart your webserver 7) Access the plugins system from the "More" pulldown 8) Upload the example plugin file provided here 9) Try it out! --- C4/Auth.pm | 4 +- C4/Installer/PerlDependencies.pm | 40 ++++ C4/Templates.pm | 4 +- Koha/Plugins.pm | 87 +++++++++ Koha/Plugins/Base.pm | 190 ++++++++++++++++++++ Koha/Plugins/Handler.pm | 100 ++++++++++ Makefile.PL | 10 +- debian/templates/koha-conf-site.xml.in | 2 + etc/koha-conf.xml | 1 + etc/koha-httpd.conf | 1 + installer/data/mysql/kohastructure.sql | 11 ++ installer/data/mysql/sysprefs.sql | 1 + installer/data/mysql/updatedatabase.pl | 24 +++ .../intranet-tmpl/prog/en/includes/header.inc | 3 + .../admin/preferences/enhanced_content.pref | 7 + .../prog/en/modules/plugins/plugins-home.tt | 116 ++++++++++++ .../prog/en/modules/plugins/plugins-upload.tt | 55 ++++++ .../prog/en/modules/reports/reports-home.tt | 11 +- .../prog/en/modules/tools/tools-home.tt | 5 + plugins/plugins-home.pl | 57 ++++++ plugins/plugins-uninstall.pl | 52 ++++++ plugins/plugins-upload.pl | 98 ++++++++++ plugins/run.pl | 50 +++++ rewrite-config.PL | 3 +- skel/var/lib/koha/plugins/README | 1 + 25 files changed, 928 insertions(+), 5 deletions(-) create mode 100644 Koha/Plugins.pm create mode 100644 Koha/Plugins/Base.pm create mode 100644 Koha/Plugins/Handler.pm create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-upload.tt create mode 100755 plugins/plugins-home.pl create mode 100755 plugins/plugins-uninstall.pl create mode 100755 plugins/plugins-upload.pl create mode 100755 plugins/run.pl create mode 100644 skel/var/lib/koha/plugins/README diff --git a/C4/Auth.pm b/C4/Auth.pm index 2eb63f5..a3613c3 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -133,7 +133,7 @@ EOQ sub get_template_and_user { my $in = shift; my $template = - C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} ); + C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'}, $in->{'is_plugin'} ); my ( $user, $cookie, $sessionID, $flags ); if ( $in->{'template_name'} !~m/maintenance/ ) { ( $user, $cookie, $sessionID, $flags ) = checkauth( @@ -203,6 +203,7 @@ sub get_template_and_user { $template->param( CAN_user_serials => 1 ); $template->param( CAN_user_reports => 1 ); $template->param( CAN_user_staffaccess => 1 ); + $template->param( CAN_user_plugins => 1 ); foreach my $module (keys %$all_perms) { foreach my $subperm (keys %{ $all_perms->{$module} }) { $template->param( "CAN_user_${module}_${subperm}" => 1 ); @@ -364,6 +365,7 @@ sub get_template_and_user { OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'), AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'), EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'), + UseKohaPlugins => C4::Context->preference('UseKohaPlugins'), ); } else { diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index 41fa8db..8c7c380 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/C4/Installer/PerlDependencies.pm @@ -634,6 +634,46 @@ our $PERL_DEPS = { 'required' => '0', 'min_ver' => '0.22', }, + 'File::Temp' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '0.22', + }, + 'File::Copy' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '2.08', + }, + 'File::Path' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '2.07', + }, + 'Archive::Extract' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '0.60', + }, + 'Archive::Zip' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '1.30', + }, + 'Module::Load::Conditional' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '0.38', + }, + 'Module::Bundled::Files' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '0.03', + }, + 'Module::Pluggable' => { + 'usage' => 'Plugins', + 'required' => '0', + 'min_ver' => '3.9', + }, }; 1; diff --git a/C4/Templates.pm b/C4/Templates.pm index 507e077..1702526 100644 --- a/C4/Templates.pm +++ b/C4/Templates.pm @@ -228,13 +228,15 @@ sub _get_template_file { sub gettemplate { - my ( $tmplbase, $interface, $query ) = @_; + my ( $tmplbase, $interface, $query, $is_plugin ) = @_; ($query) or warn "no query in gettemplate"; my $path = C4::Context->preference('intranet_includes') || 'includes'; $tmplbase =~ s/\.tmpl$/.tt/; my ($htdocs, $theme, $lang, $filename) = _get_template_file($tmplbase, $interface, $query); + $filename = $tmplbase if ( $is_plugin ); my $template = C4::Templates->new($interface, $filename, $tmplbase, $query); + # NOTE: Commenting these out rather than deleting them so that those who need # to know how we previously shimmed these directories will be able to understand. # my $is_intranet = $interface eq 'intranet'; diff --git a/Koha/Plugins.pm b/Koha/Plugins.pm new file mode 100644 index 0000000..cad5eb5 --- /dev/null +++ b/Koha/Plugins.pm @@ -0,0 +1,87 @@ +package Koha::Plugins; + +# Copyright 2012 Kyle Hall +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Module::Load::Conditional qw(can_load); +use Module::Pluggable search_path => ['Koha::Plugin']; + +use C4::Context; +use C4::Output; + +BEGIN { + die('Plugins not enabled in config') unless ( C4::Context->config("enable_plugins") ); + + push @INC, C4::Context->config("pluginsdir"); +} + +=head1 NAME + +Koha::Plugins - Module for loading and managing plugins. + +=cut + +sub new { + my ( $class, $args ) = @_; + + $args->{'pluginsdir'} = C4::Context->config("pluginsdir"); + + return bless( $args, $class ); +} + +=item GetPlugins() + +This will return a list of all the available plugins of the passed type. + +Usage: my @plugins = C4::Plugins::GetPlugins( $method ); + +At the moment, the available types are 'report' and 'tool'. +=cut + +sub GetPlugins { + my $self = shift; + my $method = shift; + + my @plugin_classes = $self->plugins(); + my @plugins; + + foreach my $plugin_class (@plugin_classes) { + if ( can_load( modules => { $plugin_class => undef } ) ) { + my $plugin = $plugin_class->new(); + + if ($method) { + if ( $plugin->can($method) ) { + push( @plugins, $plugin ); + } + } else { + push( @plugins, $plugin ); + } + } + } + return @plugins; +} + +1; +__END__ + +=head1 AUTHOR + +Kyle M Hall <kyle.m.hall@gmail.com> + +=cut diff --git a/Koha/Plugins/Base.pm b/Koha/Plugins/Base.pm new file mode 100644 index 0000000..4f2d5ee --- /dev/null +++ b/Koha/Plugins/Base.pm @@ -0,0 +1,190 @@ +package Koha::Plugins::Base; + +# Copyright 2012 Kyle Hall +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Module::Pluggable require => 1; + +use base qw{Module::Bundled::Files}; + +use C4::Context; +use C4::Auth; + +BEGIN { + die('Plugins not enabled in config') unless ( C4::Context->config("enable_plugins") ); + + push @INC, C4::Context->config("pluginsdir"); +} + +=head1 NAME + +C4::Plugins::Base - Base Module for plugins + +=cut + +sub new { + my ( $class, $args ) = @_; + + $args->{'class'} = $class; + $args->{'template'} = Template->new( { ABSOLUTE => 1 } ); + + my $self = bless( $args, $class ); + + ## Run the installation method if it exists and hasn't been run before + if ( $self->can('install') && !$self->retrieve_data('__INSTALLED__') ) { + if ( $self->install() ) { + $self->store_data( { '__INSTALLED__' => 1 } ); + } else { + warn "Plugin $class failed during installation!"; + } + } + + return $self; +} + +=head2 store_data + +set_data allows a plugin to store key value pairs in the database for future use. + +usage: $self->set_data({ param1 => 'param1val', param2 => 'param2value' }) + +=cut + +sub store_data { + my ( $self, $data ) = @_; + + my $dbh = C4::Context->dbh; + my $sql = "REPLACE INTO plugin_data SET plugin_class = ?, plugin_key = ?, plugin_value = ?"; + my $sth = $dbh->prepare($sql); + + foreach my $key ( keys %$data ) { + $sth->execute( $self->{'class'}, $key, $data->{$key} ); + } +} + +=head2 retrieve_data + +retrieve_data allows a plugin to read the values that were previously saved with store_data + +usage: my $value = $self->retrieve_data( $key ); + +=cut + +sub retrieve_data { + my ( $self, $key ) = @_; + + my $dbh = C4::Context->dbh; + my $sql = "SELECT plugin_value FROM plugin_data WHERE plugin_class = ? AND plugin_key = ?"; + my $sth = $dbh->prepare($sql); + $sth->execute( $self->{'class'}, $key ); + my $row = $sth->fetchrow_hashref(); + + return $row->{'plugin_value'}; +} + +=head2 get_template + +get_template returns a Template object. Eventually this will probably be calling +C4:Template, but at the moment, it does not. + +=cut + +sub get_template { + my ( $self, $args ) = @_; + + # my $template = + # C4::Templates->new( my $interface = 'intranet', my $filename = $self->mbf_path( $args->{'file'} ), my $tmplbase = '', my $query = $self->{'cgi'} ); + + my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { template_name => $self->mbf_path( $args->{'file'} ), + query => $self->{'cgi'}, + type => "intranet", + authnotrequired => 1, +# flagsrequired => { tools => '*' }, + is_plugin => 1, + } + ); + + $template->param( + CLASS => $self->{'class'}, + METHOD => $self->{'cgi'}->param('method'), + PLUGIN_PATH => $self->get_plugin_http_path(), + ); + + return $template; +} + +sub get_metadata { + my ( $self, $args ) = @_; + + return $self->{'metadata'}; +} + +=head2 get_qualified_table_name + +To avoid naming conflict, each plugins tables should use a fully qualified namespace. +To avoid hardcoding and make plugins more flexible, this method will return the proper +fully qualified table name. + +usage: my $table = $self->get_qualified_table_name( 'myTable' ); + +=cut + +sub get_qualified_table_name { + my ( $self, $table_name ) = @_; + + return lc( join( '_', split( '::', $self->{'class'} ), $table_name ) ); +} + +=head2 get_plugin_http_path + +To access a plugin's own resources ( images, js files, css files, etc... ) +a plugin will need to know what path to use in the template files. This +method returns that path. + +usage: my $path = $self->get_plugin_http_path(); + +=cut + +sub get_plugin_http_path { + my ($self) = @_; + + return "/plugin/" . join( '/', split( '::', $self->{'class'} ) ); +} + +=head2 + + go_home is a quick redirect to the Koha plugins home page + +=cut + +sub go_home { + my ( $self, $params ) = @_; + + print $self->{'cgi'}->redirect("/cgi-bin/koha/plugins/plugins-home.pl"); +} + +1; +__END__ + +=head1 AUTHOR + +Kyle M Hall <kyle.m.hall@gmail.com> + +=cut diff --git a/Koha/Plugins/Handler.pm b/Koha/Plugins/Handler.pm new file mode 100644 index 0000000..7ecdb79 --- /dev/null +++ b/Koha/Plugins/Handler.pm @@ -0,0 +1,100 @@ +package Koha::Plugins::Handler; + +# Copyright 2012 Kyle Hall +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use File::Path qw(remove_tree); + +use Module::Load::Conditional qw(can_load); + +use C4::Context; + +BEGIN { + die('Plugins not enabled in config') unless ( C4::Context->config("enable_plugins") ); + + push @INC, C4::Context->config("pluginsdir"); +} + +=head1 NAME + +C4::Plugins::Handler - Handler Module for running plugins + +=head1 SYNOPSIS + + Koha::Plugins::Handler->run({ class => $class, method => $method, cgi => $cgi }); + $p->run(); + +=over 2 + +=cut + +=item run + +Runs a plugin + +=cut + +sub run { + my ( $class, $args ) = @_; + my $plugin_class = $args->{'class'}; + my $plugin_method = $args->{'method'}; + my $cgi = $args->{'cgi'}; + + if ( can_load( modules => { $plugin_class => undef } ) ) { + my $plugin = $plugin_class->new( { cgi => $cgi } ); + if ( $plugin->can($plugin_method) ) { + $plugin->$plugin_method(); + } else { + warn "Plugin does not have method $plugin_method"; + } + } else { + warn "Plugin $plugin_class cannot be loaded"; + } +} + +=item delete + +Deletes a plugin + +=cut + +sub delete { + my ( $class, $args ) = @_; + my $plugin_class = $args->{'class'}; + my $plugin_dir = C4::Context->config("pluginsdir"); + my $plugin_path = "$plugin_dir/" . join( '/', split( '::', $args->{'class'} ) ); + + Koha::Plugins::Handler->run( { class => $plugin_class, method => 'uninstall' } ); + + C4::Context->dbh->do( "DELETE FROM plugin_data WHERE plugin_class = ?", undef, ($plugin_class) ); + + unlink("$plugin_path.pm"); + remove_tree($plugin_path); +} + +1; +__END__ + +=back + +=head1 AUTHOR + +Kyle M Hall <kyle.m.hall@gmail.com> + +=cut diff --git a/Makefile.PL b/Makefile.PL index 6c6e186..c0d5b90 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -230,6 +230,10 @@ Directory for Apache and Zebra logs produced by Koha. Directory for backup files produced by Koha. +=item PLUGINS_DIR + +Directory for external Koha plugins. + =item PAZPAR2_CONF_DIR Directory for PazPar2 configuration files. @@ -309,6 +313,7 @@ my $target_map = { './skel/var/lib/koha/zebradb/biblios/register' => { target => 'ZEBRA_DATA_DIR', trimdir => 6 }, './skel/var/lib/koha/zebradb/biblios/shadow' => { target => 'ZEBRA_DATA_DIR', trimdir => 6 }, './skel/var/lib/koha/zebradb/biblios/tmp' => { target => 'ZEBRA_DATA_DIR', trimdir => 6 }, + './skel/var/lib/koha/plugins' => { target => 'PLUGINS_DIR', trimdir => 6 }, './sms' => 'INTRANET_CGI_DIR', './suggestion' => 'INTRANET_CGI_DIR', './svc' => 'INTRANET_CGI_DIR', @@ -1235,6 +1240,7 @@ sub get_target_directories { $dirmap{'ZEBRA_LOCK_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'lock', 'zebradb'); $dirmap{'LOG_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'log'); $dirmap{'BACKUP_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'spool'); + $dirmap{'PLUGINS_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'lib', 'koha', 'plugins'); $dirmap{'ZEBRA_DATA_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'lib', 'zebradb'); $dirmap{'ZEBRA_RUN_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'run', 'zebradb'); } elsif ($mode eq 'dev') { @@ -1265,6 +1271,7 @@ sub get_target_directories { $dirmap{'ZEBRA_LOCK_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'lock', 'zebradb'); $dirmap{'LOG_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'log'); $dirmap{'BACKUP_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'spool'); + $dirmap{'PLUGINS_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'lib', 'plugins'); $dirmap{'ZEBRA_DATA_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'lib', 'zebradb'); $dirmap{'ZEBRA_RUN_DIR'} = File::Spec->catdir(@basedir, $package, 'var', 'run', 'zebradb'); } else { @@ -1287,6 +1294,7 @@ sub get_target_directories { $dirmap{'ZEBRA_LOCK_DIR'} = File::Spec->catdir(File::Spec->rootdir(), 'var', 'lock', $package, 'zebradb'); $dirmap{'LOG_DIR'} = File::Spec->catdir(File::Spec->rootdir(), 'var', 'log', $package); $dirmap{'BACKUP_DIR'} = File::Spec->catdir(File::Spec->rootdir(), 'var', 'spool', $package); + $dirmap{'PLUGINS_DIR'} = File::Spec->catdir(File::Spec->rootdir(), 'var', 'lib', $package, 'plugins'); $dirmap{'ZEBRA_DATA_DIR'} = File::Spec->catdir(File::Spec->rootdir(), 'var', 'lib', $package, 'zebradb'); $dirmap{'ZEBRA_RUN_DIR'} = File::Spec->catdir(File::Spec->rootdir(), 'var', 'run', $package, 'zebradb'); } @@ -1560,7 +1568,7 @@ make_upgrade_backup :: \t\$(NOECHO) umask 022; \$(MOD_BACKUP) \\ /; foreach my $key (qw/KOHA_CONF_DIR INTRANET_TMPL_DIR INTRANET_WWW_DIR OPAC_TMPL_DIR OPAC_WWW_DIR - PAZPAR2_CONF_DIR ZEBRA_CONF_DIR/) { + PAZPAR2_CONF_DIR ZEBRA_CONF_DIR PLUGINS_DIR/) { $upgrade .= "\t\t\$(KOHA_INST_$key) \$(KOHA_DEST_$key) \\\n" unless ($config{'INSTALL_ZEBRA'} ne "yes" and $key =~ /ZEBRA/) or exists $skip_directories->{$key} or diff --git a/debian/templates/koha-conf-site.xml.in b/debian/templates/koha-conf-site.xml.in index 0858059..f433487 100644 --- a/debian/templates/koha-conf-site.xml.in +++ b/debian/templates/koha-conf-site.xml.in @@ -257,6 +257,8 @@ <biblioservershadow>1</biblioservershadow> <authorityserver>authorities</authorityserver> <authorityservershadow>1</authorityservershadow> + <pluginsdir>__PLUGINS_DIR__</pluginsdir> + <enable_plugins>0</enable_plugins> <intranetdir>/usr/share/koha/intranet/cgi-bin</intranetdir> <opacdir>/usr/share/koha/opac/cgi-bin/opac</opacdir> <opachtdocs>/usr/share/koha/opac/htdocs/opac-tmpl</opachtdocs> diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml index 53d7ce8..71eb05b 100644 --- a/etc/koha-conf.xml +++ b/etc/koha-conf.xml @@ -276,6 +276,7 @@ __PAZPAR2_TOGGLE_XML_POST__ <biblioservershadow>1</biblioservershadow> <authorityserver>authorities</authorityserver> <authorityservershadow>1</authorityservershadow> + <pluginsdir>__PLUGINS_DIR__</pluginsdir> <intranetdir>__INTRANET_CGI_DIR__</intranetdir> <opacdir>__OPAC_CGI_DIR__/opac</opacdir> <opachtdocs>__OPAC_TMPL_DIR__</opachtdocs> diff --git a/etc/koha-httpd.conf b/etc/koha-httpd.conf index dc82d08..0e7af51 100644 --- a/etc/koha-httpd.conf +++ b/etc/koha-httpd.conf @@ -105,6 +105,7 @@ ScriptAlias /cgi-bin/koha/ "__INTRANET_CGI_DIR__/" ScriptAlias /index.html "__INTRANET_CGI_DIR__/mainpage.pl" ScriptAlias /search "__INTRANET_CGI_DIR__/search.pl" + Alias /plugin/ "__PLUGINS_DIR__/" ErrorLog __LOG_DIR__/koha-error_log # TransferLog __LOG_DIR__/koha-access.log SetEnv KOHA_CONF "__KOHA_CONF_DIR__/koha-conf.xml" diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index ed673da..4da1fc3 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -3056,6 +3056,17 @@ CREATE TABLE IF NOT EXISTS `borrower_modifications` ( KEY `borrowernumber` (`borrowernumber`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table structure for table 'plugin_data' +-- + +CREATE TABLE IF NOT EXISTS plugin_data ( + plugin_class varchar(255) NOT NULL, + plugin_key varchar(255) NOT NULL, + plugin_value text, + PRIMARY KEY (plugin_class,plugin_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index d5d15d7..1cd5162 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -406,3 +406,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES(' INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('NotesBlacklist','','List of notes fields that should not appear in the title notes/description separator of details',NULL,'free'); INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('SCOUserCSS', '', NULL, 'Add CSS to be included in the SCO module in an embedded <style> tag.', 'free'); INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('SCOUserJS', '', NULL, 'Define custom javascript for inclusion in the SCO module', 'free'); +INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('UseKohaPlugins','1','Enable or disable the ability to use Koha Plugins.','','YesNo'); diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index 77265f4..144a45d 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -6339,6 +6339,30 @@ if ( CheckVersion($DBversion) ) { SetVersion ($DBversion); } +$DBversion = "3.11.00.XXX"; +if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { + $dbh->do("INSERT INTO userflags (bit, flag, flagdesc, defaulton) VALUES ('19', 'plugins', 'Koha plugins', '0')"); + $dbh->do("INSERT INTO permissions (module_bit, code, description) VALUES + ('19', 'manage', 'Manage plugins ( install / uninstall )'), + ('19', 'tool', 'Use tool plugins'), + ('19', 'report', 'Use report plugins'), + ('19', 'configure', 'Configure plugins') + "); + $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('UseKohaPlugins','1','Enable or disable the ability to use Koha Plugins.','','YesNo')"); + + $dbh->do(" + CREATE TABLE IF NOT EXISTS plugin_data ( + plugin_class varchar(255) NOT NULL, + plugin_key varchar(255) NOT NULL, + plugin_value text, + PRIMARY KEY (plugin_class,plugin_key) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + "); + + print "Upgrade to $DBversion done (Added plugin system.)\n"; + SetVersion($DBversion); +} + =head1 FUNCTIONS =head2 TableExists($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc index f4037ac..9f04541 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc @@ -34,6 +34,9 @@ [% IF ( CAN_user_tools ) %] <li><a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a></li> [% END %] + [% IF ( UseKohaPlugins && CAN_user_plugins ) %] + <li><a href="/cgi-bin/koha/plugins/plugins-home.pl">Plugins</a></li> + [% END %] [% IF ( CAN_user_parameters ) %] <li><a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a></li> [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/enhanced_content.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/enhanced_content.pref index 655ae34..ca26073 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/enhanced_content.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/enhanced_content.pref @@ -300,3 +300,10 @@ Enhanced Content: - pref: HTML5MediaExtensions class: multi - (separated with |). + Plugins: + - + - pref: UseKohaPlugins + choices: + yes: Enable + no: "Don't enable" + - the ability to use Koha Plugins. Note, the plugin system must also be enabled in the Koha configuration file to be fully enabled. diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt new file mode 100644 index 0000000..7f9a581 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt @@ -0,0 +1,116 @@ +[% USE KohaDates %] +[% INCLUDE 'doc-head-open.inc' %] +<title>Koha › Tools › Plugins </title> +[% INCLUDE 'doc-head-close.inc' %] +[% INCLUDE 'calendar.inc' %] +</head> + +<body> +[% INCLUDE 'header.inc' %] +[% INCLUDE 'circ-search.inc' %] + +<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> +› Plugins +</div> + +<div id="doc3" class="yui-t1"> + <div id="bd"> + <div id="yui-main"> + <div class="yui-b"> + <div class="details"> + <h1>Plugins</h1> + + [% UNLESS ( plugins ) %] + [% UNLESS ( method ) %] + <h3>No plugins installed</h3> + [% ELSE %] + [% IF method == 'tool' %] + <h3>No plugins that can be used as a tool are installed</h3> + [% ELSIF method == 'report' %] + <h3>No plugins that can create a report are installed</h3> + [% ELSE %] + <h3>Unknown plugin type <i>[% method %]</i> + [% END %] + [% END %] + [% ELSE %] + <table> + <tr> + <th>Name</th> + <th> </th> + <th>Description</th> + <th>Author</th> + <th>Plugin Version</th> + <th>Minimum Koha Version</th> + <th>Maximum Koha Version</th> + <th>Last Updated</th> + [% IF ( CAN_user_plugins_configure ) %]<th>Configure</th>[% END %] + [% IF ( CAN_user_plugins_manage ) %]<th>Uninstall</th>[% END %] + </tr> + + [% FOREACH plugin IN plugins %] + <tr> + <td><strong>[% plugin.metadata.name %]</strong></td> + <td> + [% IF ( CAN_user_plugins_report ) %] + [% IF plugin.can('report') %] + <p style="white-space:nowrap"><a href="/cgi-bin/koha/plugins/run.pl?class=[% plugin.class %]&method=report">Run report</a></p> + [% END %] + [% END %] + + [% IF ( CAN_user_plugins_tool ) %] + [% IF plugin.can('tool') %] + <p style="white-space:nowrap"><a href="/cgi-bin/koha/plugins/run.pl?class=[% plugin.class %]&method=tool">Run tool</a></p> + [% END %] + [% END %] + </td> + <td> + [% plugin.metadata.description %] + + [% IF ( plugin.metadata.minimum_version && koha_version < plugin.metadata.minimum_version ) %] + <div class="error">Warning: This report was written for a newer version of Koha. Run at your own risk.</div> + [% END %] + + [% IF ( plugin.metadata.maximum_version && koha_version > plugin.metadata.maximum_version ) %] + <div class="error">Warning: This report was written for an older version of Koha. Run at your own risk.</div> + [% END %] + </td> + <td>[% plugin.metadata.author %]</td> + <td>[% plugin.metadata.version %]</td> + <td>[% plugin.metadata.minimum_version %]</td> + <td>[% plugin.metadata.maximum_version %]</td> + <td>[% plugin.metadata.date_updated | $KohaDates %]</td> + [% IF ( CAN_user_plugins_configure ) %] + <td> + [% IF plugin.can('configure') %] + <a href="/cgi-bin/koha/plugins/run.pl?class=[% plugin.class %]&method=configure">Configure</a> + [% END %] + </td> + [% END %] + [% IF ( CAN_user_plugins_manage ) %] + <td> + [% IF plugin.can('uninstall') %] + <a href="/cgi-bin/koha/plugins/plugins-uninstall.pl?class=[% plugin.class %]" onclick="return confirm('Are you sure you want to uninstall the plugin [% plugin.metadata.name %]?')">Uninstall</a> + [% END %] + </td> + [% END %] + [% END %] + </table> + [% END %] + </div> + </div> + </div> + + <div class="yui-b noprint"> + <div id="navmenu"> + <ul id="navmenulist"> + [% IF ( CAN_user_plugins_manage ) %] + <li><a href="plugins-upload.pl">Upload a plugin</a></li> + [% END %] + </ul> + </div> + </div> + </div> +</div> + + +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-upload.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-upload.tt new file mode 100644 index 0000000..94d3373 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-upload.tt @@ -0,0 +1,55 @@ +[% INCLUDE 'doc-head-open.inc' %] +<title>Koha › Tools › Plugins › Upload Plugin + </title> +[% INCLUDE 'doc-head-close.inc' %] +[% INCLUDE 'calendar.inc' %] +</head> + +<body> +[% INCLUDE 'header.inc' %] +[% INCLUDE 'circ-search.inc' %] + +<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> +› <a href="/cgi-bin/koha/plugins/plugins-home.pl">Plugins</a> +› Upload Plugins +</div> + +<div id="doc3" class="yui-t2"> + <div id="bd"> + <div id="yui-main"> + <div class="yui-b"> + <div class="yui-g"> + <div class="yui-u first"> + <h1>Upload Koha Plugin</h1> + [% IF ( ERRORS ) %] + <div class="dialog alert"> + [% FOREACH ERROR IN ERRORS %] + [% IF ( ERROR.NOTKPZ ) %]<li><b>The upload file does not appear to be a kpz file. The extention is not '.kpz'.</b></li> + [% ELSIF ( ERROR.NOWRITETEMP ) %]<li><b>This script is not able to create/write to the necessary temporary directory.</b></li> + [% ELSIF ( ERROR.EMPTYUPLOAD ) %]<li><b>The upload file appears to be empty.</b></li> + [% ELSIF ( ERROR.UZIPFAIL ) %]<li><b>[% ERROR.UZIPFAIL %] failed to unpack.<br />Please verify the integrity of the zip file and retry.</b></li> + [% ELSIF ( ERROR.NOWRITEPLUGINS ) %]<li><b>Cannot unpack file to the plugins directory.<br />Please verify that the Apache user can write to the plugins directory.</b></li> + [% ELSE %]<li><b>[% ERROR.CORERR %] An unknown error has occurred.<br />Please review the error log for more details.</b></li>[% END %] + [% END %] + </div> + [% END %] + <form method="post" action="/cgi-bin/koha/plugins/plugins-upload.pl" enctype="multipart/form-data"> + <fieldset class="brief"> + <div class="hint"><b>NOTE:</b> Only KPZ file format is supported.</div> + <ol> + <li> + <label for="uploadfile">Select the file to upload: </label><input type="file" id="uploadfile" name="uploadfile" /> + </li> + </ol> + </fieldset> + <fieldset class="action"> + <input type="hidden" name="op" value="Upload" /> + <input type="submit" value="Upload" class="submit" /> + </fieldset> + </form> + + </div> + </div> + </div> +</div> +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reports/reports-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reports/reports-home.tt index 32c1803..c7744fa 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reports/reports-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reports/reports-home.tt @@ -35,7 +35,16 @@ <li><a href="/cgi-bin/koha/reports/issues_stats.pl">Circulation</a></li> <li><a href="/cgi-bin/koha/reports/serials_stats.pl">Serials</a></li> <li><a href="/cgi-bin/koha/reports/reserves_stats.pl">Holds</a></li> - </ul></div> + </ul> + + [% IF UseKohaPlugins %] + <h2>Report Plugins</h2> + <ul> + <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=report">Report Plugins</a></li> + </ul> + [% END %] + + </div> <div class="yui-u"><h2>Top lists</h2> <ul> diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt index 52cc1e6..921546a 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt @@ -97,6 +97,11 @@ <dd>Quote editor for Quote-of-the-day feature in OPAC</dd> [% END %] + [% IF ( UseKohaPlugins && CAN_user_plugins_tool ) %] + <dt><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=tool">Tool Plugins</a></dt> + <dd>Use tool plugins</dd> + [% END %] + </dl> </div> <div class="yui-u"> diff --git a/plugins/plugins-home.pl b/plugins/plugins-home.pl new file mode 100755 index 0000000..8b7a42a --- /dev/null +++ b/plugins/plugins-home.pl @@ -0,0 +1,57 @@ +#!/usr/bin/perl + +# Copyright 2010 Kyle M Hall <kyle.m.hall@gmail.com> +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use strict; +use warnings; + +use CGI; + +use Koha::Plugins; +use C4::Auth; +use C4::Output; +use C4::Dates; +use C4::Debug; +use C4::Context; + +die("Koha plugins are disabled!") + unless C4::Context->preference('UseKohaPlugins'); + +my $input = new CGI; +my $method = $input->param('method'); + +my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { template_name => "plugins/plugins-home.tmpl", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { plugins => '*' }, + debug => 1, + } +); + +$template->param( + koha_version => C4::Context->preference("Version"), + method => $method, +); + +my @plugins = Koha::Plugins->new()->GetPlugins($method); + +$template->param( plugins => \@plugins ); + +output_html_with_http_headers( $input, $cookie, $template->output ); diff --git a/plugins/plugins-uninstall.pl b/plugins/plugins-uninstall.pl new file mode 100755 index 0000000..6324010 --- /dev/null +++ b/plugins/plugins-uninstall.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use strict; +use warnings; + +use Archive::Extract; +use File::Temp; +use File::Copy; +use CGI; + +use C4::Context; +use C4::Auth; +use C4::Output; +use C4::Members; +use C4::Debug; +use Koha::Plugins::Handler; + +die("Koha plugins are disabled!") + unless C4::Context->preference('UseKohaPlugins'); + +my $input = new CGI; + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { template_name => "plugins/plugins-upload.tmpl", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { plugins => 'manage' }, + debug => 1, + } +); + +my $class = $input->param('class'); + +Koha::Plugins::Handler->delete( { class => $class } ); + +print $input->redirect("/cgi-bin/koha/plugins/plugins-home.pl"); diff --git a/plugins/plugins-upload.pl b/plugins/plugins-upload.pl new file mode 100755 index 0000000..4548104 --- /dev/null +++ b/plugins/plugins-upload.pl @@ -0,0 +1,98 @@ +#!/usr/bin/perl +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use strict; +use warnings; + +use Archive::Extract; +use File::Temp; +use File::Copy; +use CGI; + +use C4::Context; +use C4::Auth; +use C4::Output; +use C4::Members; +use C4::Debug; +use Koha::Plugins; + +die("Koha plugins are disabled!") + unless C4::Context->preference('UseKohaPlugins'); + +my $input = new CGI; + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { template_name => "plugins/plugins-upload.tmpl", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { plugins => 'manage' }, + debug => 1, + } +); + +my $uploadfilename = $input->param('uploadfile'); +my $uploadfile = $input->upload('uploadfile'); +my $op = $input->param('op'); + +my ( $total, $handled, @counts, $tempfile, $tfh ); + +my %errors; + +if ( ( $op eq 'Upload' ) && $uploadfile ) { + my $plugins_dir = C4::Context->config("pluginsdir"); + + my $dirname = File::Temp::tempdir( CLEANUP => 1 ); + $debug and warn "dirname = $dirname"; + + my $filesuffix; + $filesuffix = $1 if $uploadfilename =~ m/(\..+)$/i; + ( $tfh, $tempfile ) = File::Temp::tempfile( SUFFIX => $filesuffix, UNLINK => 1 ); + + $debug and warn "tempfile = $tempfile"; + + $errors{'NOTKPZ'} = 1 if ( $uploadfilename !~ /\.kpz$/i ); + $errors{'NOWRITETEMP'} = 1 unless ( -w $dirname ); + $errors{'NOWRITEPLUGINS'} = 1 unless ( -w $plugins_dir ); + $errors{'EMPTYUPLOAD'} = 1 unless ( length($uploadfile) > 0 ); + + if (%errors) { + $template->param( ERRORS => [ \%errors ] ); + } else { + while (<$uploadfile>) { + print $tfh $_; + } + close $tfh; + + my $ae = Archive::Extract->new( archive => $tempfile, type => 'zip' ); + unless ( $ae->extract( to => $plugins_dir ) ) { + warn "ERROR: " . $ae->error; + $errors{'UZIPFAIL'} = $uploadfilename; + $template->param( ERRORS => [ \%errors ] ); + output_html_with_http_headers $input, $cookie, $template->output; + exit; + } + } +} elsif ( ( $op eq 'Upload' ) && !$uploadfile ) { + warn "Problem uploading file or no file uploaded."; +} + +if ( $uploadfile && !%errors && !$template->param('ERRORS') ) { + print $input->redirect("/cgi-bin/koha/plugins/plugins-home.pl"); +} else { + output_html_with_http_headers $input, $cookie, $template->output; +} diff --git a/plugins/run.pl b/plugins/run.pl new file mode 100755 index 0000000..6b8ef28 --- /dev/null +++ b/plugins/run.pl @@ -0,0 +1,50 @@ +#!/usr/bin/perl + +# Copyright 2010 Kyle M Hall <kyle.m.hall@gmail.com> +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use strict; +use warnings; + +use CGI; + +use Koha::Plugins::Handler; +use C4::Auth; +use C4::Output; +use C4::Dates; +use C4::Debug; +use C4::Context; + +die("Koha plugins are disabled!") + unless C4::Context->preference('UseKohaPlugins'); + +my $cgi = new CGI; + +my $class = $cgi->param('class'); +my $method = $cgi->param('method'); + +my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { template_name => "plugins/plugins-home.tmpl", + query => $cgi, + type => "intranet", + authnotrequired => 0, + flagsrequired => { plugins => $method }, + debug => 1, + } +); + +my $plugin = Koha::Plugins::Handler->run( { class => $class, method => $method, cgi => $cgi } ); diff --git a/rewrite-config.PL b/rewrite-config.PL index 02715ab..90fd5ff 100644 --- a/rewrite-config.PL +++ b/rewrite-config.PL @@ -43,7 +43,7 @@ guesses worked out by the script. The following configuration keywords are available: PREFIX, -BASE_DIR, CGI_DIR, LOG_DIR, INSTALL_BASE, +BASE_DIR, CGI_DIR, LOG_DIR, PLUGINS_DIR, INSTALL_BASE, DB_TYPE, DB_HOST, DB_PORT, DB_NAME, DB_PASS, DB_USER, WEBMASTER_EMAIL, WEBSERVER_DOMAIN, WEBSERVER_HOST, WEBSERVER_IP, WEBSERVER_PORT, WEBSERVER_PORT_LIBRARIAN, ZEBRA_PASS, ZEBRA_USER @@ -82,6 +82,7 @@ $prefix = $ENV{'INSTALL_BASE'} || "/usr"; %configuration = ( "__KOHA_INSTALLED_VERSION__" => "no_version_found", "__LOG_DIR__" => "/var/log", + "__PLUGINS_DIR__" => "/var/lib/koha/plugins", "__DB_TYPE__" => "mysql", "__DB_NAME__" => "koha", "__DB_HOST__" => $myhost, diff --git a/skel/var/lib/koha/plugins/README b/skel/var/lib/koha/plugins/README new file mode 100644 index 0000000..d4290ea --- /dev/null +++ b/skel/var/lib/koha/plugins/README @@ -0,0 +1 @@ +plugins dir \ No newline at end of file -- 1.7.2.5