diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt index 179875227f..c8fe1d2f98 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt @@ -5,6 +5,7 @@ [% USE Branches %] [% USE KohaDates %] [% USE TablesSettings %] +[% USE To %] [% PROCESS 'i18n.inc' %] [% SET footerjs = 1 %] [% INCLUDE 'doc-head-open.inc' %] @@ -272,207 +273,28 @@ [% IF ( do_it ) %] - [% IF ( total ) %] -

Log entries

-
- Check none - -
-
- - - - - - - - - - - - - - - [% FOREACH loopro IN looprow %] - - - - - - - - - - - [% END %] - -
DateLibrarianModuleActionObjectInfoInterfaceDiff
[% loopro.timestamp | $KohaDates with_hours=1 %] - [% IF loopro.librarian %] - - [% IF ( loopro.librarian.firstname ) || ( loopro.librarian.surname ) %][% loopro.librarian.firstname | html %] [% loopro.librarian.surname | html %] ([% loopro.librarian.borrowernumber | html %])[% ELSE %][% loopro.librarian.borrowernumber | html %][% END %] - [% ELSE %] - [% loopro.user | html %] - [% END %] - [% PROCESS translate_log_module module=loopro.module %][% PROCESS translate_log_action action=loopro.action %] - [% IF ( loopro.module == 'MEMBERS' ) || ( loopro.module == 'CIRCULATION' ) || ( loopro.module == 'FINES' ) || ( loopro.module == 'APIKEYS' ) %] - [% IF loopro.patron %] - [% IF ( loopro.module == 'APIKEYS' ) %] - - API keys for [% loopro.patron.firstname | html %] [% loopro.patron.surname | html %] ([% loopro.object | html %]) - - [% ELSE %] - - [% IF ( loopro.object ) %][% IF ( loopro.patron.firstname ) || ( loopro.patron.surname ) %] - [% loopro.patron.firstname | html %] - [% loopro.patron.surname | html %] - ([% loopro.object | html %]) - [% ELSE %] - Member [% loopro.object | html %] - [% END %][% END %] - - [% END %] - [% ELSE %] - [% IF ( loopro.module == 'APIKEYS' ) %] - API keys for patron [% loopro.object | html %] - [% ELSE %] - [% loopro.object | html %] - [% END %] - [% END %] - [% ELSIF ( loopro.module == 'CATALOGUING' ) %] - [% IF ( loopro.info.substr(0, 4) == 'item' ) %] - Item [% loopro.object | html %] - from - Bibliographic record [% loopro.biblionumber | html %] - [% ELSIF ( loopro.info.substr(0, 6) == 'biblio' ) %] - Bibliographic record [% loopro.object | html %] - [% ELSE %] - [% loopro.object | html %] - [% END %] - [% ELSIF ( loopro.module == 'SERIAL' ) %] - Subscription [% loopro.object | html %] - [% ELSIF ( loopro.module == 'AUTHORITIES' ) %] - Authority [% loopro.object | html %] - [% ELSIF ( loopro.module == 'NOTICES' ) %] - [% IF ( loopro.notice ) %] - Notice [% loopro.notice.title | html %] ([% loopro.notice.code | html %]) - [% ELSE %] - [% loopro.object | html %] - [% END %] - [% ELSIF ( loopro.module == 'RECALLS' ) %] - [% IF loopro.recall.item_level %] - Item-level recall on - item - [% ELSE %] - Bibliographic record-level recall on biblio - [% END %] - [% ELSIF ( loopro.module == 'ACQUISITIONS' && loopro.action == 'ACQUISITION ORDER' && loopro.object ) %] - [% IF ( CAN_user_acquisition_order_manage ) %] - Basket [% loopro.object | html %] - [% ELSE %] - Basket [% loopro.object | html %] - [% END %] - [% ELSIF ( loopro.module == "SUGGESTION" ) %] - [% loopro.object | html %] - [% ELSE %] - [% loopro.object | html %] - [% END %] - - [% IF ( loopro.module == 'CIRCULATION' && loopro.object_found ) %] - Item [% loopro.barcode | html %] - [% ELSIF ( loopro.module == 'CIRCULATION' && loopro.json_found ) %] -
- - [% FOREACH force IN loopro.json.forced %] -
[% PROCESS translate_log_override override=force %]
- [% END %] - [% FOREACH confirmation IN loopro.json.confirmations %] -
[% PROCESS translate_log_override override=confirmation %]
- [% END %] -
- [% ELSIF ( loopro.module == 'HOLDS' && loopro.json_found ) %] -
- - [% FOREACH force IN loopro.json.forced %] -
[% PROCESS translate_log_override override=force %]
- [% END %] - [% FOREACH confirmation IN loopro.json.confirmations %] -
[% PROCESS translate_log_override override=confirmation %]
- [% END %] -
- [% ELSIF loopro.module == "SYSTEMPREFERENCE" || loopro.module == "REPORTS" || loopro.module == "NEWS" %] -
[% loopro.info | trim | html %]
-
- - - -
- [% ELSIF loopro.module == "NOTICES" %] -
[% loopro.info | trim | html %]
-
- -
- [% ELSE %] -
[% loopro.info | html %]
- [% END %] -
[% PROCESS translate_log_interface log_interface=loopro.interface %][% loopro.diff | html %]
-
- - [% ELSE %] -
- [% IF ( CATALOGUING ) %] - No log found for Bibliographic record [% object | html %]. - [% ELSIF ( MEMBERS ) %] - No log found for [% INCLUDE 'patron-title.inc' %]. - [% ELSE %] - No log found. - [% END %] -
- [% END %] +

Log entries

+
+ Check none + +
+
+ + + + + + + + + + + + + +
DateLibrarianModuleActionObjectInfoInterfaceDiff
+
+ [% END %] [% END %] @@ -519,6 +341,20 @@ [% Asset.js("js/viewlog.js") | $raw %] [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/viewlog.js b/koha-tmpl/intranet-tmpl/prog/js/viewlog.js index 9a8e47955b..35ea7018e3 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/viewlog.js +++ b/koha-tmpl/intranet-tmpl/prog/js/viewlog.js @@ -150,58 +150,355 @@ function untickAll(section) { } function limitCheckboxes() { - var checkboxes = $(".compare"); var limit = 2; var compare_link = ' ' + __("View comparison") + ""; - checkboxes.each(function () { - $(this).on("change", function () { - var checked = []; - checkboxes.each(function () { - if ($(this).prop("checked")) { - checked.push($(this).data("actionid")); - } - }); - if (checked.length > 0) { - $("#select_none").removeClass("disabled"); - } else { - $("#select_none").addClass("disabled"); - $("#logst").DataTable().search("").draw(); - } - if (checked.length == 1) { - $("#logst").DataTable().search($(this).data("filter")).draw(); - humanMsg.displayAlert( - __("Showing results for %s").format($(this).data("filter")) - ); - } - if (checked.length == 2) { - $("#compare_info" + checked[0]).prepend(compare_link); - $("#compare_info" + checked[1]).prepend(compare_link); - $("button.compare_link").removeClass("disabled"); - } else if (checked.length > limit) { - humanMsg.displayAlert( - __("You can select maximum of two checkboxes") - ); - $(this).prop("checked", false); - } else if (checked.length < limit) { - $("a.compare_link").remove(); - $("button.compare_link").addClass("disabled"); + $(document).on("change", ".compare", function () { + var checked = []; + $(".compare").each(function () { + if ($(this).prop("checked")) { + checked.push($(this).data("actionid")); } }); + if (checked.length > 0) { + $("#select_none").removeClass("disabled"); + } else { + $("#select_none").addClass("disabled"); + } + if (checked.length == 2) { + $("a.compare_link").remove(); + $("#compare_info" + checked[0]).prepend(compare_link); + $("#compare_info" + checked[1]).prepend(compare_link); + $("button.compare_link").removeClass("disabled"); + } else if (checked.length > limit) { + humanMsg.displayAlert( + __("You can select maximum of two checkboxes") + ); + $(this).prop("checked", false); + } else if (checked.length < limit) { + $("a.compare_link").remove(); + $("button.compare_link").addClass("disabled"); + } }); } +var module_labels = { + APIKEYS: __("API keys"), + AUTH: __("Authentication"), + CATALOGUING: __("Cataloging"), + AUTHORITIES: __("Authorities"), + MEMBERS: __("Patrons"), + ACQUISITIONS: __("Acquisitions"), + SERIAL: __("Serials"), + HOLDS: __("Holds"), + ILL: __("Interlibrary loans"), + CIRCULATION: __("Circulation"), + CLAIMS: __("Claims"), + FINES: __("Fines"), + SYSTEMPREFERENCE: __("System preferences"), + CRONJOBS: __("Cron jobs"), + REPORTS: __("Reports"), + SEARCHENGINE: __("Search engine"), + NOTICES: __("Notices"), + NEWS: __("Additional content"), + RECALLS: __("Recalls"), + SUGGESTION: __("Suggestions"), + TRANSFERS: __("Transfers"), +}; + +var action_labels = { + ACTIVATE: __("Activate"), + ADD: __("Add"), + DELETE: __("Delete"), + MODIFY: __("Modify"), + REVOKE: __("Revoke"), + ISSUE: __("Checkout"), + RETURN: __("Return"), + CREATE: __("Create"), + CANCEL: __("Cancel"), + FILL: __("Fill"), + RESUME: __("Resume"), + SUSPEND: __("Suspend"), + RENEW: __("Renew"), + RENEWAL: __("Renew"), + "CHANGE PASS": __("Change password"), + "RESET PASS": __("Reset password"), + ADDCIRCMESSAGE: __("Add circulation message"), + MODCIRCMESSAGE: __("Modify circulation message"), + DELCIRCMESSAGE: __("Delete circulation message"), + STATUS_CHANGE: __("Change ILL request status"), + PATRON_NOTICE: __("ILL notice sent to patron"), + ADD_BASKET: __("Create an acquisitions basket"), + MODIFY_BASKET: __("Modify an acquisitions basket"), + MODIFY_BASKET_HEADER: __("Modify an acquisitions basket header"), + MODIFY_BASKET_USERS: __("Modify an acquisitions basket's users"), + CLOSE_BASKET: __("Close an acquisitions basket"), + APPROVE_BASKET: __("Approve an acquisitions basket"), + REOPEN_BASKET: __("Reopen an acquisitions basket"), + CANCEL_ORDER: __("Cancel an order"), + CREATE_ORDER: __("Create an order"), + MODIFY_ORDER: __("Modify an order"), + CREATE_INVOICE_ADJUSTMENT: __("Create an invoice adjustment"), + UPDATE_INVOICE_ADJUSTMENT: __("Modify an invoice adjustment"), + DELETE_INVOICE_ADJUSTMENT: __("Delete an invoice adjustment"), + RECEIVE_ORDER: __("Receive an order"), + MODIFY_BUDGET: __("Modify a budget"), + CREATE_FUND: __("Create a fund"), + MODIFY_FUND: __("Modify a fund"), + DELETE_FUND: __("Delete a fund"), + CREATE_RESTRICTION: __("Create restriction"), + MODIFY_RESTRICTION: __("Modify restriction"), + DELETE_RESTRICTION: __("Delete restriction"), + Run: __("Run"), + End: __("End"), + PATRON_MERGE: __("Merge"), + EDIT_MAPPINGS: __("Edit mappings"), + RESET_MAPPINGS: __("Reset mappings"), + "SERIAL CLAIM": __("Serial claim"), + "ACQUISITION CLAIM": __("Acquisition claim"), + "ACQUISITION ORDER": __("Acquisition order"), + OVERDUE: __("Overdue"), + EXPIRE: __("Expire"), + MODIFY_CARDNUMBER: __("Modify cardnumber"), + "RESET 2FA": __("Reset 2FA"), +}; + +var interface_labels = { + INTRANET: __("Staff interface"), + OPAC: __("OPAC"), + SIP: __("SIP"), + COMMANDLINE: __("Command-line"), + API: __("REST API"), + CRON: __("Cron job"), + UPGRADE: __("Upgrade"), +}; + +function escapeHtml(str) { + if (str == null) return ""; + return $("").text(String(str)).html(); +} + +function renderObject(data, type, row) { + var mod = row.module; + var obj = row.object; + if (obj == null) return ""; + + if ( + mod == "MEMBERS" || + mod == "CIRCULATION" || + mod == "FINES" || + mod == "APIKEYS" + ) { + if (mod == "APIKEYS") { + return ( + '' + + __("API keys for patron %s").format(escapeHtml(obj)) + + "" + ); + } + return ( + '' + + escapeHtml(obj) + + "" + ); + } + + if (mod == "CATALOGUING") { + var info = row.info || ""; + if (info.substr(0, 6) == "biblio") { + return ( + 'Bibliographic record ' + + escapeHtml(obj) + + "" + ); + } + return escapeHtml(obj); + } + + if (mod == "SERIAL") { + return ( + 'Subscription ' + + escapeHtml(obj) + + "" + ); + } + + if (mod == "AUTHORITIES") { + return ( + 'Authority ' + + escapeHtml(obj) + + "" + ); + } + + if (mod == "NOTICES") { + return escapeHtml(obj); + } + + if (mod == "ACQUISITIONS" && row.action == "ACQUISITION ORDER" && obj) { + if (CAN_user_acquisition_order_manage) { + return ( + 'Basket ' + + escapeHtml(obj) + + "" + ); + } + return "Basket " + escapeHtml(obj); + } + + if (mod == "SUGGESTION") { + return ( + '' + + escapeHtml(obj) + + "" + ); + } + + return escapeHtml(obj); +} + +function renderInfo(data, type, row) { + var mod = row.module; + var info = row.info; + var action_id = row.action_id; + + if (info == null) return ""; + + if (mod == "SYSTEMPREFERENCE" || mod == "REPORTS" || mod == "NEWS") { + var split_info = info.split(" | "); + var filter_value = escapeHtml(split_info[0]); + return ( + '
' + + escapeHtml(info) + + "
" + + '
' + ); + } + + if (mod == "NOTICES") { + return ( + '
' + + escapeHtml(info) + + "
" + + '
' + ); + } + + var rendered = renderRecordInfo(info); + var body = rendered !== null ? rendered : escapeHtml(info); + return ( + '
' + + body + + "
" + ); +} + +function buildApiFilters(params) { + var filters = {}; + + if (params.user) { + filters.user = params.user; + } + + if (params.object) { + filters.object = params.object; + } + + if (params.info) { + filters.info = { like: "%" + params.info + "%" }; + } + + if (params.modules && params.modules.length > 0) { + var non_empty = params.modules.filter(function (m) { + return m !== ""; + }); + if (non_empty.length > 0) { + filters.module = non_empty; + } + } + + if (params.actions && params.actions.length > 0) { + var non_empty_actions = params.actions.filter(function (a) { + return a !== ""; + }); + if (non_empty_actions.length > 0) { + // Circulation uses RENEWAL, but Patrons uses RENEW + if (non_empty_actions.indexOf("RENEW") !== -1) { + non_empty_actions.push("RENEWAL"); + } + filters.action = non_empty_actions; + } + } + + if (params.interfaces && params.interfaces.length > 0) { + var non_empty_ifaces = params.interfaces.filter(function (i) { + return i !== ""; + }); + if (non_empty_ifaces.length > 0) { + filters.interface = non_empty_ifaces; + } + } + + // Flatpickr always submits dates in YYYY-MM-DD format (dateFormat: "Y-m-d") + if (params.datefrom && params.dateto) { + filters.timestamp = { + ">=": params.datefrom + "T00:00:00Z", + "<=": params.dateto + "T23:59:59Z", + }; + } else if (params.datefrom) { + filters.timestamp = { + ">=": params.datefrom + "T00:00:00Z", + }; + } else if (params.dateto) { + filters.timestamp = { + "<=": params.dateto + "T23:59:59Z", + }; + } + + return filters; +} + $(document).ready(function () { limitCheckboxes(); - if ($(".compare_info").length == 0) { - /* Remove toolbar if there are no system preference - entries to compare */ - $("#toolbar").remove(); - } - if ($('input[type="checkbox"][name="modules"]:checked').length == 0) { tickAll("modules"); } @@ -259,14 +556,110 @@ $(document).ready(function () { } }); - var logst = $("#logst").kohaTable( - { - autoWidth: false, - order: [[0, "desc"]], - pagingType: "full", - }, - table_settings - ); + if (do_it && typeof search_params !== "undefined") { + var api_filters = buildApiFilters(search_params); + + $("#logst").kohaTable( + { + ajax: { + url: "/api/v1/action_logs", + }, + order: [[0, "desc"]], + pagingType: "full", + autoWidth: false, + columns: [ + { + data: "timestamp", + title: __("Date"), + searchable: true, + orderable: true, + render: function (data, type, row) { + if (type == "sort" || type == "type") { + return data; + } + return $datetime(data); + }, + }, + { + data: "user", + title: __("Librarian"), + searchable: true, + orderable: true, + render: function (data, type, row) { + if (data) { + return ( + '' + + escapeHtml(data) + + "" + ); + } + return escapeHtml(data); + }, + }, + { + data: "module", + title: __("Module"), + searchable: true, + orderable: true, + render: function (data, type, row) { + return module_labels[data] || escapeHtml(data); + }, + }, + { + data: "action", + title: __("Action"), + searchable: true, + orderable: true, + render: function (data, type, row) { + return action_labels[data] || escapeHtml(data); + }, + }, + { + data: "object", + title: __("Object"), + searchable: true, + orderable: true, + render: renderObject, + }, + { + data: "info", + title: __("Info"), + searchable: true, + orderable: false, + render: renderInfo, + }, + { + data: "interface", + title: __("Interface"), + searchable: true, + orderable: true, + render: function (data, type, row) { + if (!data) return ""; + return ( + interface_labels[data.toUpperCase()] || + escapeHtml(data) + ); + }, + }, + { + data: "diff", + title: __("Diff"), + searchable: false, + orderable: false, + render: function (data, type, row) { + if (!data) return ""; + return renderStructDiff(data); + }, + }, + ], + }, + table_settings, + 0, + api_filters + ); + } $("body").on("click", ".compare_link", function (e) { e.preventDefault(); @@ -291,20 +684,6 @@ $(document).ready(function () { $(".compare:checked").prop("checked", false).change(); }); - $("td.diff-col").each(function () { - var raw = $(this).text().trim(); - if (raw) { - $(this).html(renderStructDiff(raw)); - } - }); - - $("div.loginfo").each(function () { - var raw = $(this).text().trim(); - var rendered = renderRecordInfo(raw); - if (rendered) { - $(this).html(rendered); - } - }); patron_autocomplete($("#user"), { "on-select-callback": function (event, ui) { $("#user").val(ui.item.patron_id); diff --git a/tools/viewlog.pl b/tools/viewlog.pl index d3de818f5a..8c711433a5 100755 --- a/tools/viewlog.pl +++ b/tools/viewlog.pl @@ -89,171 +89,11 @@ $template->param( ); if ($do_it) { - my $dtf = Koha::Database->new->schema->storage->datetime_parser; - my %search_params; - - if ( $datefrom and $dateto ) { - my $dateto_endday = dt_from_string($dateto); - $dateto_endday->set( # We set last second of day to see all log from that day - hour => 23, - minute => 59, - second => 59 - ); - $search_params{'timestamp'} = { - -between => [ - $dtf->format_datetime( dt_from_string($datefrom) ), - $dtf->format_datetime($dateto_endday), - ] - }; - } elsif ($datefrom) { - $search_params{'timestamp'} = { '>=' => $dtf->format_datetime( dt_from_string($datefrom) ) }; - } elsif ($dateto) { - my $dateto_endday = dt_from_string($dateto); - $dateto_endday->set( # We set last second of day to see all log from that day - hour => 23, - minute => 59, - second => 59 - ); - $search_params{'timestamp'} = { '<=' => $dtf->format_datetime($dateto_endday) }; - } - - # Circulation uses RENEWAL, but Patrons uses RENEW, this helps to find both - if ( grep { $_ eq 'RENEW' } @actions ) { - push @actions, 'RENEWAL'; - } - - $search_params{user} = $user if $user; - $search_params{module} = { -in => [@modules] } if ( defined $modules[0] and $modules[0] ne '' ); - $search_params{action} = { -in => [@actions] } if ( defined $actions[0] && $actions[0] ne '' ); - $search_params{interface} = { -in => [@interfaces] } if ( defined $interfaces[0] && $interfaces[0] ne '' ); - - if ( @modules == 1 && $object_type eq 'biblio' ) { - - # Handle 'Modification log' from cataloguing - my $biblio = Koha::Biblios->find($object); - my @itemnumbers = $biblio->items->get_column('itemnumber'); - $search_params{'-or'} = [ - { -and => { object => $object, info => { -like => 'biblio%' } } }, - { -and => { object => \@itemnumbers, info => { -like => 'item%' } } }, - ]; - $template->param( biblio => $biblio ); - } else { - $search_params{info} = { -like => '%' . $info . '%' } if $info; - $search_params{object} = $object if $object; - } - - my @logs = Koha::ActionLogs->search( \%search_params )->as_list; - - my @data; - foreach my $log (@logs) { - my $result = $log->unblessed; - - # Init additional columns for CSV export - $result->{'biblionumber'} = q{}; - $result->{'biblioitemnumber'} = q{}; - $result->{'barcode'} = q{}; - - if ( defined $log->info && substr( $log->info, 0, 4 ) eq 'item' ) { - - # get item information so we can create a working link - my $itemnumber = $log->object; - my $item = Koha::Items->find($itemnumber); - if ($item) { - $result->{'object_found'} = 1; - $result->{'biblionumber'} = $item->biblionumber; - $result->{'biblioitemnumber'} = $item->biblionumber; - $result->{'barcode'} = $item->barcode; - } - } - - if ( $log->module eq "CIRCULATION" ) { - my $info = $log->info; - my $decoded; - my $is_json = eval { - $decoded = decode_json($info); - 1; - }; - - if ( $is_json && ref($decoded) ) { - $result->{'json_found'} = 1; - my $item = Koha::Items->find( $decoded->{itemnumber} ); - if ($item) { - $decoded->{biblionumber} = $item->biblionumber; - $decoded->{biblioitemnumber} = $item->biblionumber; - $decoded->{barcode} = $item->barcode; - } - $result->{'json'} = $decoded; - } else { - my $item = Koha::Items->find($info); - if ($item) { - $result->{'object_found'} = 1; - $result->{'biblionumber'} = $item->biblionumber; - $result->{'biblioitemnumber'} = $item->biblionumber; - $result->{'barcode'} = $item->barcode; - } - } - } - - #always add firstname and surname for librarian/user - if ( $log->user ) { - my $patron = Koha::Patrons->find( $log->user ); - if ( $patron && $output eq 'screen' ) { - $result->{librarian} = $patron; - } - } - - #add firstname and surname for borrower, when using the CIRCULATION, MEMBERS, FINES, APIKEYS - if ( $log->module eq "CIRCULATION" - || $log->module eq "MEMBERS" - || $log->module eq "FINES" - || $log->module eq "APIKEYS" ) - { - if ( $log->object ) { - my $patron = Koha::Patrons->find( $log->object ); - if ( $patron && $output eq 'screen' ) { - $result->{patron} = $patron; - } - } - } - - if ( $log->module eq 'NOTICES' ) { - if ( $log->object ) { - my $notice = Koha::Notice::Templates->find( { id => $log->object } ); - if ( $notice && $output eq 'screen' ) { - $result->{notice} = $notice->unblessed; - } - } - } - - # get recall information - if ( $log->module eq "RECALLS" ) { - if ( $log->object ) { - my $recall = Koha::Recalls->find( $log->object ); - if ( $recall && $output eq 'screen' ) { - $result->{recall} = $recall; - } - } - } - if ( $result->{module} eq 'MEMBERS' ) { - my $json_engine = JSON->new->utf8->canonical(1)->pretty(1); - if ( $result->{info} && $result->{info} =~ /^[{\[]/ ) { - my $perl_info = $json_engine->decode( $result->{info} ); - $result->{info} = $json_engine->encode($perl_info); - } - if ( $result->{diff} && $result->{diff} =~ /^[{\[]/ ) { - my $perl_diff = $json_engine->decode( $result->{diff} ); - $result->{diff} = $json_engine->encode($perl_diff); - } - } - push @data, $result; - } if ( $output eq "screen" ) { - # Printing results to screen + # Screen output: pass search params to template, DataTable loads via REST API $template->param( logview => 1, - total => scalar @data, - looprow => \@data, do_it => 1, datefrom => $datefrom, dateto => $dateto, @@ -272,6 +112,110 @@ if ($do_it) { output_html_with_http_headers $input, $cookie, $template->output; } else { + # CSV export: fetch and enrich data server-side + my $dtf = Koha::Database->new->schema->storage->datetime_parser; + my %search_params; + + if ( $datefrom and $dateto ) { + my $dateto_endday = dt_from_string($dateto); + $dateto_endday->set( # We set last second of day to see all log from that day + hour => 23, + minute => 59, + second => 59 + ); + $search_params{'timestamp'} = { + -between => [ + $dtf->format_datetime( dt_from_string($datefrom) ), + $dtf->format_datetime($dateto_endday), + ] + }; + } elsif ($datefrom) { + $search_params{'timestamp'} = { '>=' => $dtf->format_datetime( dt_from_string($datefrom) ) }; + } elsif ($dateto) { + my $dateto_endday = dt_from_string($dateto); + $dateto_endday->set( # We set last second of day to see all log from that day + hour => 23, + minute => 59, + second => 59 + ); + $search_params{'timestamp'} = { '<=' => $dtf->format_datetime($dateto_endday) }; + } + + # Circulation uses RENEWAL, but Patrons uses RENEW, this helps to find both + if ( grep { $_ eq 'RENEW' } @actions ) { + push @actions, 'RENEWAL'; + } + + $search_params{user} = $user if $user; + $search_params{module} = { -in => [@modules] } if ( defined $modules[0] and $modules[0] ne '' ); + $search_params{action} = { -in => [@actions] } if ( defined $actions[0] && $actions[0] ne '' ); + $search_params{interface} = { -in => [@interfaces] } if ( defined $interfaces[0] && $interfaces[0] ne '' ); + + if ( @modules == 1 && $object_type eq 'biblio' ) { + + # Handle 'Modification log' from cataloguing + my $biblio = Koha::Biblios->find($object); + my @itemnumbers = $biblio->items->get_column('itemnumber'); + $search_params{'-or'} = [ + { -and => { object => $object, info => { -like => 'biblio%' } } }, + { -and => { object => \@itemnumbers, info => { -like => 'item%' } } }, + ]; + } else { + $search_params{info} = { -like => '%' . $info . '%' } if $info; + $search_params{object} = $object if $object; + } + + my @logs = Koha::ActionLogs->search( \%search_params )->as_list; + + my @data; + foreach my $log (@logs) { + my $result = $log->unblessed; + + # Init additional columns for CSV export + $result->{'biblionumber'} = q{}; + $result->{'biblioitemnumber'} = q{}; + $result->{'barcode'} = q{}; + + if ( defined $log->info && substr( $log->info, 0, 4 ) eq 'item' ) { + + # get item information so we can create a working link + my $itemnumber = $log->object; + my $item = Koha::Items->find($itemnumber); + if ($item) { + $result->{'biblionumber'} = $item->biblionumber; + $result->{'biblioitemnumber'} = $item->biblionumber; + $result->{'barcode'} = $item->barcode; + } + } + + if ( $log->module eq "CIRCULATION" ) { + my $info = $log->info; + my $decoded; + my $is_json = eval { + $decoded = decode_json($info); + 1; + }; + + if ( $is_json && ref($decoded) ) { + my $item = Koha::Items->find( $decoded->{itemnumber} ); + if ($item) { + $decoded->{biblionumber} = $item->biblionumber; + $decoded->{biblioitemnumber} = $item->biblionumber; + $decoded->{barcode} = $item->barcode; + } + } else { + my $item = Koha::Items->find($info); + if ($item) { + $result->{'biblionumber'} = $item->biblionumber; + $result->{'biblioitemnumber'} = $item->biblionumber; + $result->{'barcode'} = $item->barcode; + } + } + } + + push @data, $result; + } + # Printing to a csv file my $content = q{}; if (@data) {