From 590b7f2f2b5e48f3df0161b11e9cb443fc538cde Mon Sep 17 00:00:00 2001
From: David Cook
Date: Mon, 16 Nov 2020 09:59:59 +0000
Subject: [PATCH] Bug 26791: Replace export.pl with export Mojolicious
controller
---
Koha/Auth.pm | 151 ++++++++++++++++++++
Koha/Staff.pm | 85 +++++++++++
Koha/Staff/Controller/Tools/Export.pm | 155 +++++++++++++++++++++
Koha/Template.pm | 40 ++++++
debian/templates/plack.psgi | 20 +++
.../intranet-tmpl/prog/en/includes/tools-menu.inc | 2 +-
.../intranet-tmpl/prog/en/modules/tools/export.tt | 8 +-
.../prog/en/modules/tools/tools-home.tt | 2 +-
staff | 21 +++
9 files changed, 478 insertions(+), 6 deletions(-)
create mode 100644 Koha/Auth.pm
create mode 100644 Koha/Staff.pm
create mode 100644 Koha/Staff/Controller/Tools/Export.pm
create mode 100644 Koha/Template.pm
create mode 100755 staff
diff --git a/Koha/Auth.pm b/Koha/Auth.pm
new file mode 100644
index 0000000000..38e86d150c
--- /dev/null
+++ b/Koha/Auth.pm
@@ -0,0 +1,151 @@
+package Koha::Auth;
+
+use strict;
+use warnings;
+
+use C4::Auth qw//;
+use C4::Context qw//;
+
+=head2 authenticate
+
+ my $user = Koha::Auth->authenticate({
+ sessionID => $sessionID,
+ });
+
+Verifies that this user has an authenticated Koha session
+
+=cut
+
+#NOTE: This method requires that a Koha session exists
+sub authenticate {
+ my ($class,$args) = @_;
+ my ($auth_user,$auth_session,$auth_cookie);
+ my $sessionID = $args->{sessionID};
+ if ($sessionID){
+ my $session = C4::Auth::get_session($sessionID);
+ if ($session){
+ my $id = $session->param('id');
+ if ( $id ){
+ my $patrons = Koha::Patrons->search({ userid => $id });
+ if ($patrons->count){
+ $auth_user = $patrons->next;
+ $auth_session = $session;
+ _set_userenv_compat({ session => $session });
+ }
+ }
+ }
+ }
+ if ($auth_user){
+ #FIXME: Check for timeout
+ }
+ #FIXME: Ideally, it would be nice to calculate patron authorizations at login time,
+ #rather than on each page load (like we do currently)...
+ return ($auth_user,$auth_session,$auth_cookie);
+}
+
+#NOTE: Needed since C4::Context->userenv is so ubiquitous
+sub _set_userenv_compat {
+ my ($args) = @_;
+ my $session = $args->{session};
+ if ($session){
+ C4::Context->_new_userenv($session->param('id'));
+ C4::Context->set_userenv(
+ $session->param('number'), $session->param('id'),
+ $session->param('cardnumber'), $session->param('firstname'),
+ $session->param('surname'), $session->param('branch'),
+ $session->param('branchname'), $session->param('flags'),
+ $session->param('emailaddress'), $session->param('shibboleth'),
+ $session->param('desk_id'), $session->param('desk_name')
+ );
+ }
+}
+
+=head2 authorize
+
+ my $flags = Koha::Auth->authorize({
+ session => $session,
+ flagsrequired => { self_check => 'self_checkout_module' },
+ });
+
+=cut
+
+sub authorize {
+ my ($class,$args) = @_;
+ my $flags;
+ my $session = $args->{session};
+ my $flagsrequired = $args->{flagsrequired};
+ if ($session && $flagsrequired){
+ my $userid = $session->param('id');
+ if ($userid){
+ $flags = C4::Auth::haspermission($userid,$flagsrequired);
+ }
+ }
+ return $flags;
+}
+
+sub get_authz_from_flags {
+ my ($args) = @_;
+ my $flags = $args->{flags};
+ my $authz;
+
+ #FIXME: This contains a lot of copy/paste from C4::Auth
+ my $all_perms = C4::Auth::get_all_subpermissions();
+
+ if ($flags){
+ if ( $flags->{superlibrarian} == 1 ){
+ #NOTE: These are similar but slightly different to @flatroots...
+ my @perms = qw/
+ circulate
+ catalogue
+ parameters
+ borrowers
+ permissions
+ reserveforothers
+ editcatalogue
+ updatecharges
+ acquisition
+ tools
+ editauthorities
+ serials
+ reports
+ staffaccess
+ plugins
+ coursereserves
+ clubs
+ ill
+ stockrotation
+ problem_reports
+ /;
+ foreach my $perm (@perms){
+ $authz->{ "CAN_user_${perm}" } = 1;
+ }
+ foreach my $module ( keys %$all_perms ) {
+ foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
+ $authz->{ "CAN_user_${module}_${subperm}" } = 1;
+ }
+ }
+ }
+ else {
+ foreach my $module ( keys %$all_perms ) {
+ if ( defined($flags->{$module}) && $flags->{$module} == 1 ) {
+ foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
+ $authz->{ "CAN_user_${module}_${subperm}" } = 1;
+ }
+ } elsif ( ref( $flags->{$module} ) ) {
+ foreach my $subperm ( keys %{ $flags->{$module} } ) {
+ $authz->{ "CAN_user_${module}_${subperm}" } = 1;
+ }
+ }
+ }
+ foreach my $module ( keys %$flags ) {
+ if ( $flags->{$module} == 1 or ref( $flags->{$module} ) ) {
+ $authz->{ "CAN_user_$module" } = 1;
+ }
+ }
+ }
+ }
+
+ return $authz;
+}
+
+1;
diff --git a/Koha/Staff.pm b/Koha/Staff.pm
new file mode 100644
index 0000000000..8654752292
--- /dev/null
+++ b/Koha/Staff.pm
@@ -0,0 +1,85 @@
+package Koha::Staff;
+
+use Mojo::Base 'Mojolicious';
+use Koha::Auth;
+use Koha::Template;
+use C4::Context;
+
+sub startup {
+ my $self = shift;
+
+ #FIXME: Move into a plugin?
+ $self->helper(
+ staff_authorize => sub {
+ my ($c,$args) = @_;
+ my $session = $c->stash->{__koha_session__};
+ my $flags = Koha::Auth->authorize({
+ session => $session,
+ flagsrequired => $args->{flagsrequired},
+ });
+ if($flags){
+ $c->stash->{__koha_flags__} = $flags;
+ $c->stash->{__koha_authz__} = Koha::Auth::get_authz_from_flags({ flags => $flags });
+ return ($flags,$c->stash->{__koha__user__});
+ }
+ else {
+ $c->render( text => 'Forbidden', status => 403 );
+ return;
+ }
+ }
+ );
+ #FIXME: Move into a plugin?
+ $self->helper(
+ prepare_template => sub {
+ my ($c,$args) = @_;
+ my $template_filename = $args->{template_filename};
+ my $interface = $args->{interface};
+ my $template = Koha::Template::prepare_template({
+ template_filename => $template_filename,
+ interface => $interface,
+ koha_session => $c->stash->{__koha_session__},
+ koha_user => $c->stash->{__koha_user__},
+ koha_authz => $c->stash->{__koha_authz__},
+ });
+ return $template;
+ }
+ );
+
+ # Router
+ my $r = $self->routes;
+
+ #NOTE: The /login route is public and does not require authentication
+ #FIXME: Implement a Mojolicious login route
+ #$r->get('/login')->to( controller => 'login', action => 'index' );
+
+ #NOTE: All other routes require authentication
+ my $auth = $r->under('/' => sub {
+ my $c = shift;
+ my $sessionID = $c->cookie('CGISESSID');
+ my ($user,$session) = Koha::Auth->authenticate({
+ sessionID => $sessionID,
+ });
+ if ($user && $session){
+ $c->stash->{__koha_user__} = $user;
+ $c->stash->{__koha_session__} = $session;
+ $c->cookie(
+ 'CGISESSID' => $session->id,
+ {
+ httponly => 1,
+ secure => ( C4::Context->https_enabled() ? 1 : 0 ),
+ path => '/',
+ }
+ );
+ return 1;
+ }
+ else {
+ #FIXME: In future, redirect to a /login route
+ $c->redirect_to('/index.html');
+ return;
+ }
+ });
+ my $tools = $auth->under('tools');
+ $tools->any(['GET','POST'] => '/:controller/:action' => { action => 'index' })->to(namespace => 'Koha::Staff::Controller::Tools');
+}
+
+1;
diff --git a/Koha/Staff/Controller/Tools/Export.pm b/Koha/Staff/Controller/Tools/Export.pm
new file mode 100644
index 0000000000..1440652094
--- /dev/null
+++ b/Koha/Staff/Controller/Tools/Export.pm
@@ -0,0 +1,155 @@
+package Koha::Staff::Controller::Tools::Export;
+
+use Mojo::Base 'Mojolicious::Controller';
+
+use Koha::Exporter::Record;
+use C4::Biblio qw//;
+use Encode;
+use Carp;
+
+use Koha::ItemTypes;
+use Koha::Authority::Types;
+use Koha::Libraries;
+
+use Koha::Auth;
+
+sub index {
+ my $c = shift;
+ my ($flags,$loggedinuser) = $c->staff_authorize({ flagsrequired => { 'tools' => 'export_catalog' } });
+ my $template = $c->prepare_template({
+ template_filename => 'tools/export.tt',
+ interface => 'intranet',
+ });
+
+ #FIXME: This is for showing messages about the download...
+ my @messages = ();
+ $template->{VARS}->{messages} = \@messages;
+
+ my $itemtypes = Koha::ItemTypes->search_with_localization;
+ $template->{VARS}->{itemtypes} = $itemtypes;
+
+ my $authority_types = Koha::Authority::Types->search( {}, { order_by => ['authtypecode'] } );
+ $template->{VARS}->{authority_types} = $authority_types;
+
+ my $branch = $c->every_param('branch');
+ my $libraries = Koha::Libraries->search_filtered({}, { order_by => ['branchname'] })->unblessed;
+ #NOTE: This will only work if we return to this action...
+ for my $library ( @$libraries ) {
+ $library->{selected} = 1 if grep { $library->{branchcode} eq $_ } @$branch;
+ }
+ $template->{VARS}->{libraries} = $libraries;
+
+ my $backupdir = C4::Context->config('backupdir');
+ if ( $flags->{superlibrarian}
+ && C4::Context->config('backup_db_via_tools')
+ && $backupdir
+ && -d $backupdir )
+ {
+ $template->{VARS}->{'allow_db_export'} = 1;
+ $template->{VARS}->{'dbfiles'} = _getbackupfilelist(
+ { directory => "$backupdir", extension => 'sql' } );
+ }
+
+ if ( $flags->{superlibrarian}
+ && C4::Context->config('backup_conf_via_tools')
+ && $backupdir
+ && -d $backupdir )
+ {
+ $template->{VARS}->{'allow_conf_export'} = 1;
+ $template->{VARS}->{'conffiles'} = _getbackupfilelist(
+ { directory => "$backupdir", extension => 'tar' } );
+ }
+
+ $c->render( text => $template->output );
+}
+
+sub _getbackupfilelist {
+ my $args = shift;
+ my $directory = $args->{directory};
+ my $extension = $args->{extension};
+ my @files;
+
+ if ( opendir( my $dir, $directory ) ) {
+ while ( my $file = readdir($dir) ) {
+ next unless ( $file =~ m/\.$extension(\.(gz|bz2|xz))?/ );
+ push @files, $file
+ if ( -f "$directory/$file" && -r "$directory/$file" );
+ }
+ closedir($dir);
+ }
+ use Data::Dumper;
+ warn Dumper(\@files);
+ return \@files;
+}
+
+
+sub download {
+ my $c = shift;
+ my ($flags,$loggedinuser) = $c->staff_authorize({ flagsrequired => { 'tools' => 'export_catalog' } });
+
+ my $record_type = $c->param('record_type') || 'bibs'; #FIXME: Remove bibs?
+ my $backupdir = C4::Context->config('backupdir');
+
+ if ( $record_type eq 'bibs' or $record_type eq 'auths' ){
+ #TODO: Refactor code from export.pl
+ }
+ elsif ( $record_type eq 'db' or $record_type eq 'conf' ){
+ #TODO: Most of this code should go in a model module
+ if ( $flags->{superlibrarian}
+ and (
+ $record_type eq 'db' and C4::Context->config('backup_db_via_tools')
+ or
+ $record_type eq 'conf' and C4::Context->config('backup_conf_via_tools')
+ )
+ ) {
+ my $extension = $record_type eq 'db' ? 'sql' : 'tar';
+ my $filename = $c->param('filename');
+ $filename =~ s/(\r|\n)//;
+ my $fh = _get_backup_filehandle({
+ directory => $backupdir,
+ extension => $extension,
+ filename => $filename,
+ });
+ my $content_type = 'application/octet-stream';
+ if ( $filename =~ m/\.gz$/ ){
+ $content_type = 'application/x-gzip';
+ }
+ elsif ( $filename =~ m/\.bz2$/ ){
+ $content_type = 'application/x-bzip2';
+ }
+ #FIXME: Add error handling
+ $c->res->headers->content_type($content_type);
+ $c->res->headers->content_disposition("attachment; filename=$filename;");
+ my $drain;
+ $drain = sub {
+ my $c = shift;
+ read($fh,my $chunk, 64 * 1024);
+ if ($chunk){
+ $c->write($chunk,$drain);
+ }
+ else {
+ $c->write('');
+ }
+ };
+ $c->$drain;
+ }
+ }
+}
+
+#FIXME: Move this into a Koha::Exporter module
+sub _get_backup_filehandle {
+ my ($args) = @_;
+ my $directory = $args->{directory};
+ my $extension = $args->{extension};
+ my $filename = $args->{filename};
+
+ return unless ( $directory && -d $directory );
+ return unless ( $filename =~ m/\.$extension(\.(gz|bz2|xz))?$/ );
+ return if ( $filename =~ m#/# );
+ $filename = "$directory/$filename";
+ return unless ( -f $filename && -r $filename );
+ return unless ( open( my $fh, '<', $filename ) );
+ return $fh;
+}
+
+1;
diff --git a/Koha/Template.pm b/Koha/Template.pm
new file mode 100644
index 0000000000..fa0b629369
--- /dev/null
+++ b/Koha/Template.pm
@@ -0,0 +1,40 @@
+package Koha::Template;
+
+use strict;
+use warnings;
+
+use C4::Templates;
+
+sub prepare_template {
+ my ($args) = @_;
+ my $template;
+ my $session = $args->{koha_session};
+ my $user = $args->{koha_user};
+ my $authz = $args->{koha_authz};
+ my $template_filename = $args->{template_filename};
+ my $interface = $args->{interface};
+ if ($template_filename && $interface){
+ $template = C4::Templates::gettemplate($template_filename,$interface);
+ if ($template){
+ if ($session){
+ $template->{VARS}->{ sessionID } = $session->param('id');
+ }
+ if ($user){
+ $template->{VARS}->{ logged_in_user } = $user;
+ $template->{VARS}->{ loggedinusernumber } = $user->borrowernumber;
+ $template->{VARS}->{ loggedinusername } = $user->userid;
+ }
+ if ($authz){
+ foreach my $key ( keys %$authz ){
+ $template->{VARS}->{ $key } = $authz->{$key};
+ }
+ }
+
+ #NOTE: You could include all the syspref stuff here that exists in C4::Auth,
+ #but I rather avoid it, and have templates update to use the Koha.Preference mechanism instead.
+ }
+ }
+ return $template;
+}
+
+1;
diff --git a/debian/templates/plack.psgi b/debian/templates/plack.psgi
index 99438c595a..fc20ed1f40 100644
--- a/debian/templates/plack.psgi
+++ b/debian/templates/plack.psgi
@@ -67,6 +67,19 @@ my $apiv1 = builder {
$server->to_psgi_app;
};
+my $staff_interface = builder {
+ my $server = Mojo::Server::PSGI->new;
+ $server->build_app('Koha::Staff');
+ my $app = $server->app;
+ $app->hook( before_dispatch => sub {
+ my $c = shift;
+ #NOTE: Rewrite the base path to strip off the mount prefix
+ my $path = $c->req->url->base->path(Mojo::Path->new);
+
+ });
+ $server->to_psgi_app;
+};
+
Koha::Logger->_init;
builder {
@@ -98,4 +111,11 @@ builder {
}
$apiv1;
};
+ mount '/intranet/staff' => builder {
+ if ( Log::Log4perl->get_logger('plack-intranet')->has_appenders ){
+ enable 'Log4perl', category => 'plack-intranet';
+ enable 'LogWarn';
+ }
+ $staff_interface;
+ };
};
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
index ba3f54a281..b0d7deddaa 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc
@@ -72,7 +72,7 @@
Automatic item modifications by age
[% END %]
[% IF ( CAN_user_tools_export_catalog ) %]
- Export data
+ Export data
[% END %]
[% IF ( CAN_user_tools_inventory ) %]
Inventory
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt
index 4e9a992c04..f65cafce39 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt
@@ -48,7 +48,7 @@
Note : The items are exported by this tool unless specified.
-