Bug 24606: Implement item templates
authorKyle Hall <kyle@bywatersolutions.com>
Thu, 29 Sep 2022 13:06:28 +0000 (09:06 -0400)
committerTomas Cohen Arazi <tomascohen@theke.io>
Thu, 10 Nov 2022 17:25:19 +0000 (14:25 -0300)
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>
cataloguing/additem.pl
koha-tmpl/intranet-tmpl/prog/en/includes/str/cataloging_additem.inc
koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt
koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js

index c722f60..6287982 100755 (executable)
 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} ) {
index 1a2009d..c51447d 100644 (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 -->
index b6d0af0..47286ab 100644 (file)
     [% 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>
         <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 %]" />
index 1427f91..9bd660a 100644 (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);