Bug 24606: Implement item templates

This patch set implements item editor templates for community Koha.

Test Plan:
1) Apply this patch set
2) Run updatedatabase.pl
3) Restart all the things!
4) prove t/db_dependent/Koha/Item/Template*
5) As a non superlibrarian, enter the item editor
6) Set some item fields, save as a new template using the buttom and
   form below the editor.
7) Test loading a template without remembering for the session
8) Test loading a template while remembering for the session
9) Test deleting a template
10) Test updating a template
11) Create one or more shared templates
12) Log in as another non superlibrarian without the new permission manage_item_editor_templates,
    verify you cannot edit/delete templates shared to you
13) Enable the new permission manage_item_editor_templates,
    verify you can now edit and delete templates shared to you

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Kyle Hall 2022-09-29 09:06:28 -04:00 committed by Tomas Cohen Arazi
parent c5a3b14bf8
commit 859a917618
Signed by: tomascohen
GPG key ID: 0A272EA1B2F3C15F
4 changed files with 210 additions and 25 deletions

View file

@ -22,39 +22,35 @@
use Modern::Perl;
use CGI qw ( -utf8 );
use C4::Auth qw( get_template_and_user haspermission );
use C4::Output qw( output_and_exit_if_error output_and_exit output_html_with_http_headers );
use C4::Biblio qw(
GetFrameworkCode
GetMarcFromKohaField
GetMarcStructure
IsMarcStructureInternal
ModBiblio
);
use C4::Context;
use C4::Circulation qw( barcodedecode LostItem );
use C4::Barcodes;
use C4::Barcodes::ValueBuilder;
use C4::Barcodes;
use C4::Biblio qw( GetFrameworkCode GetMarcFromKohaField GetMarcStructure IsMarcStructureInternal ModBiblio );
use C4::Circulation qw( barcodedecode LostItem );
use C4::Context;
use C4::Members;
use C4::Output qw( output_and_exit_if_error output_and_exit output_html_with_http_headers );
use C4::Search qw( enabled_staff_search_views );
use Koha::Biblios;
use Koha::Items;
use Koha::Item::Templates;
use Koha::ItemTypes;
use Koha::Items;
use Koha::Items;
use Koha::Libraries;
use Koha::Patrons;
use Koha::SearchEngine::Indexer;
use C4::Search qw( enabled_staff_search_views );
use Storable qw( freeze thaw );
use URI::Escape qw( uri_escape_utf8 );
use C4::Members;
use Koha::UI::Form::Builder::Item;
use Koha::Result::Boolean;
use MARC::File::XML;
use URI::Escape qw( uri_escape_utf8 );
use Encode qw( encode_utf8 );
use MIME::Base64 qw( decode_base64url encode_base64url );
use List::Util qw( first );
use List::MoreUtils qw( any uniq );
use List::Util qw( first );
use MARC::File::XML;
use MIME::Base64 qw( decode_base64url encode_base64url );
use Storable qw( freeze thaw );
use URI::Escape qw( uri_escape_utf8 );
use URI::Escape qw( uri_escape_utf8 );
our $dbh = C4::Context->dbh;
@ -86,6 +82,14 @@ sub add_item_to_item_group {
)->store();
}
sub get_item_from_template {
my ( $template_id ) = @_;
my $template = Koha::Item::Templates->find($template_id);
return $template->decoded_contents if $template;
}
sub get_item_from_cookie {
my ( $input ) = @_;
@ -175,12 +179,51 @@ my @errors; # store errors found while checking data BEFORE saving item.
# Getting last created item cookie
my $prefillitem = C4::Context->preference('PrefillItem');
my $load_template_submit = $input->param('load_template_submit');
my $delete_template_submit = $input->param('delete_template_submit');
my $unload_template_submit = $input->param('unload_template_submit');
my $use_template_for_session = $input->param('use_template_for_session') || $input->cookie('ItemEditorSessionTemplateId');
my $template_id = $input->param('template_id') || $input->cookie('ItemEditorSessionTemplateId');
if ( $delete_template_submit ) {
my $t = Koha::Item::Templates->find($template_id);
$t->delete if $t && $t->borrowernumber eq $loggedinuser;
$template_id = undef;
$use_template_for_session = undef;
}
if ($load_template_submit || $unload_template_submit) {
$op = q{} if $template_id;
$template_id = undef if !$input->param('template_id');
$template_id = undef if $unload_template_submit;
# Unset the cookie if either no template id as submitted, or "use for session" checkbox as unchecked
my $cookie_value = $input->param('use_template_for_session') && $template_id ? $template_id : q{};
$use_template_for_session = $cookie_value;
# Update the cookie
my $template_cookie = $input->cookie(
-name => 'ItemEditorSessionTemplateId',
-value => $cookie_value,
-HttpOnly => 1,
-expires => '',
-sameSite => 'Lax'
);
$cookie = [ $cookie, $template_cookie ];
}
$template->param(
template_id => $template_id,
item_templates => Koha::Item::Templates->get_available($loggedinuser),
use_template_for_session => $use_template_for_session,
);
#-------------------------------------------------------------------------------
if ($op eq "additem") {
my $add_submit = $input->param('add_submit');
my $add_duplicate_submit = $input->param('add_duplicate_submit');
my $add_multiple_copies_submit = $input->param('add_multiple_copies_submit');
my $save_as_template_submit = $input->param('save_as_template_submit');
my $number_of_copies = $input->param('number_of_copies');
my @columns = Koha::Items->columns;
@ -226,8 +269,36 @@ if ($op eq "additem") {
$item->barcode(barcodedecode($item->barcode));
if ($save_as_template_submit) {
my $template_name = $input->param('template_name');
my $template_is_shared = $input->param('template_is_shared');
my $replace_template_id = $input->param('replace_template_id');
if ($replace_template_id) {
my $template = Koha::Item::Templates->find($replace_template_id);
if ($template) {
$template->update(
{
id => $replace_template_id,
is_shared => $template_is_shared ? 1 : 0,
contents => $item->unblessed,
}
);
}
}
else {
my $template = Koha::Item::Template->new(
{
name => $template_name,
borrowernumber => $loggedinuser,
is_shared => $template_is_shared ? 1 : 0,
contents => $item->unblessed,
}
)->store();
}
}
# If we have to add or add & duplicate, we add the item
if ( $add_submit || $add_duplicate_submit || $prefillitem) {
elsif ( $add_submit || $add_duplicate_submit || $prefillitem) {
# check for item barcode # being unique
if ( defined $item->barcode
@ -591,13 +662,19 @@ my @header_value_loop = map {
} sort keys %$subfieldcode_attribute_mappings;
# Using last created item if it exists
if ( $prefillitem
&& $op ne "additem"
if (
$op ne "additem"
&& $op ne "edititem"
&& $op ne "dupeitem" )
{
my $item_from_cookie = get_item_from_cookie($input);
$current_item = $item_from_cookie if $item_from_cookie;
if ( $template_id ) {
my $item_from_template = get_item_from_template($template_id);
$current_item = $item_from_template if $item_from_template;
}
elsif ( $prefillitem ) {
my $item_from_cookie = get_item_from_cookie($input);
$current_item = $item_from_cookie if $item_from_cookie;
}
}
if ( $current_item->{more_subfields_xml} ) {

View file

@ -20,6 +20,7 @@
var MSG_CONFIRM_DELETE_ITEM = _("Are you sure you want to delete this item?");
var MSG_CONFIRM_ADD_ITEM = _("Are you sure you want to add a new item? Any changes made on this page will be lost.");
var MSG_CONFIRM_SAVE = _("Are you sure you want to save?");
var MSG_TEMPLATE_NAME_REQUIRED = _("Template name is required.");
var table_settings = [% TablesSettings.GetTableSettings( 'cataloguing', 'additem', 'itemst', 'json' ) | $raw %];
</script>
<!-- / str/cataloging_additem.inc -->

View file

@ -162,6 +162,44 @@
[% ELSE %]
<h2 id="edititem">Edit item #[% itemnumber | html %][% IF ( barcode ) %] / Barcode [% barcode | html %][% END %]</h2>
[% END %]
<div id="item-template-toolbar" class="btn-toolbar">
<select name="template_id" id="template_id" class="select2" style="width: 20em">
<option value="0" selected="selected">Do not use template</option>
<optgroup label="My templates">
[% FOREACH t IN item_templates.owned %]
[% IF t.id == template_id %]
<option data-owner="1" value="[% t.id | html %]" selected="selected">[% t.name | html %][% IF t.is_shared %] (shared)[% END %]</option>
[% ELSE %]
<option data-owner="1" value="[% t.id | html %]">[% t.name | html %][% IF t.is_shared %] (shared)[% END %]</option>
[% END %]
[% END %]
</optgroup>
<optgroup label="Shared templates">
[% FOREACH t IN item_templates.shared %]
[% IF t.id == template_id %]
<option data-owner="0" value="[% t.id | html %]" selected="selected">[% t.name | html %]</option>
[% ELSE %]
<option data-owner="0" value="[% t.id | html %]">[% t.name | html %]</option>
[% END %]
[% END %]
</optgroup>
</select>
<button type="submit" id="load_template_submit" name="load_template_submit" value="1"><i class="fa fa-wpforms"></i> Apply template</button>
<label for="use_template_for_session">
[% IF use_template_for_session %]
<input type="checkbox" id="use_template_for_session" name="use_template_for_session" checked="checked">
[% ELSE %]
<input type="checkbox" id="use_template_for_session" name="use_template_for_session">
[% END %]
For session</label>
<button type="submit" id="unload_template_submit" name="unload_template_submit" value="1"><i class="fa fa-eraser"></i> Clear template</button>
<button type="submit" id="delete_template_submit" name="delete_template_submit" value="1" disabled><i class="fa fa-trash"></i> Delete template</button>
</div>
<fieldset class="rows">
[% PROCESS subfields_for_item subfields => subfields %]
</fieldset>
@ -218,6 +256,35 @@
<div class="hint"><p>The barcode you enter will be incremented for each additional item.</p></div>
</fieldset>
<span id="savetemplate">
<input type="button" name="save_as_template" id="save_as_template" value="Save as template" />
</span>
<fieldset id="save_as_template_span">
<legend>Save template</legend>
<select name="replace_template_id" id="replace_template_id" class="select2" style="width: 20em">
<option value="0" selected="selected">Save as new</option>
<optgroup label="Update existing">
[% FOREACH t IN item_templates.owned %]
<option data-owner="1" value="[% t.id | html %]">[% t.name | html %][% IF t.is_shared %] (shared)[% END %]</option>
[% END %]
</optgroup>
</select>
<span id="template_name_block">
<label for="template_name" class="required">Template name: </label>
<input type="text" id="template_name" name="template_name" class="required"/>
<span class="required">Required</span>
</span>
<label for="template_is_shared">
<input type="checkbox" id="template_is_shared" name="template_is_shared"/>
Share template
</label>
<input type="submit" id="save_as_template_submit" name="save_as_template_submit" value="Save" onclick="javascript:return CheckTemplateForm(this.form);" />
<a href="#" id="cancel_save_as_template" class="cancel">Cancel</a>
</fieldset>
[% ELSE %]
[% IF op != 'add_item' %]
<input type="hidden" name="itemnumber" value="[% itemnumber | html %]" />

View file

@ -69,6 +69,37 @@ $(document).ready(function(){
multiCopyControl.toggle();
});
var saveAsTemplateControl = $("#save_as_template_span");
var saveTemplateBlock = $("#savetemplate");
saveAsTemplateControl.hide();
$("#save_as_template").on("click",function(e){
e.preventDefault;
saveTemplateBlock.toggle();
saveAsTemplateControl.toggle();
$('#template_name').focus();
});
$("#cancel_save_as_template").on("click",function(e){
e.preventDefault();
saveTemplateBlock.toggle();
saveAsTemplateControl.toggle();
});
$("#template_id").on("change", function() {
if ( $(this).find(":selected").data("owner") ) {
$("#delete_template_submit").removeAttr("disabled");
} else {
$("#delete_template_submit").attr("disabled", "disabled");
}
});
$("#template_id").change(); // Trigger to enable delete button if patron's template is in use
$("#replace_template_id").on("change", function() {
if ( $(this).find(":selected").val() > 0 ) {
$("#template_name_block").hide();
} else {
$("#template_name_block").show();
}
});
// Add new item to an item group
if ( has_item_groups ) {
$('#item-group-add-or-create-form-description-block').hide();
@ -98,6 +129,15 @@ $(document).ready(function(){
});
});
function CheckTemplateForm(f) {
if ( $('#replace_template_id').val() == "0" && $('#template_name').val() == "" ) {
alert(MSG_TEMPLATE_NAME_REQUIRED);
return false;
} else {
return true;
}
}
function Check(f) {
var total_mandatory = CheckMandatorySubfields(f);
var total_important = CheckImportantSubfields(f);