From b8c6286b696558701c6a6fa5e52a81d26ff8d225 Mon Sep 17 00:00:00 2001
From: Axel Amghar <axel.amghar@biblibre.com>
Date: Thu, 23 May 2019 11:48:01 +0200
Subject: [PATCH] Bug 22639: Elasticsearch ability to export mappings and
 facets settings

With this patch you can download search fields , biblios and authorities table in search engine configuration

Test plan:
- apply the patch
- make sure you have "elasticsearch" as value for the syspref "SearchEngine"
- go to search engine configuration in the admin page
- in the search fields part, you have a button "Download ... table"
- download in (.csv), (.tab), (.ods)
- verify that you have the same table in your file than in the search fields table
- make the same test for bibliographic records and authorities
- Then add some mappings and dowload, your new file has to be modified
---
 admin/searchengine/elasticsearch/mappings.pl       | 166 +++++++++++++++++++++
 .../admin/searchengine/elasticsearch/mappings.tt   |  88 ++++++++++-
 2 files changed, 249 insertions(+), 5 deletions(-)

diff --git a/admin/searchengine/elasticsearch/mappings.pl b/admin/searchengine/elasticsearch/mappings.pl
index f91399a..86df22c 100755
--- a/admin/searchengine/elasticsearch/mappings.pl
+++ b/admin/searchengine/elasticsearch/mappings.pl
@@ -21,12 +21,16 @@ use Scalar::Util qw(looks_like_number);
 use List::Util qw( first );
 use C4::Koha;
 use C4::Output;
+use C4::Context;
 use C4::Auth;
+use Text::CSV::Encoded;
+use C4::Reports::Guided;
 
 use Koha::SearchEngine::Elasticsearch;
 use Koha::SearchEngine::Elasticsearch::Indexer;
 use Koha::SearchMarcMaps;
 use Koha::SearchFields;
+use Koha::Util::OpenDocument;
 
 use Try::Tiny;
 
@@ -151,6 +155,158 @@ elsif( $op eq 'reset_confirmed' ) {
 elsif( $op eq 'reset_confirm' ) {
     $template->param( reset_confirm => 1 );
 }
+elsif ($op eq 'download'){
+    my $format = $input->param("format");
+    my $table  = $input->param("table");
+    my ($type, $content, $reportfilename, $csv, $ods_fh, $ods_filepath, $ods_content);
+    my $i = 0;
+
+    if ($format eq "tab"){
+        $type = 'application/octet-stream';
+
+    } elsif ($format eq "csv"){
+        $type = 'application/csv';
+        my $delimiter = C4::Context->preference('delimiter') || ',';
+        $delimiter = "\t" if ($delimiter eq 'tabulation');
+        $csv = Text::CSV::Encoded->new({ encoding_out => 'UTF-8', sep_char => $delimiter});
+        $csv or die "Text::CSV::Encoded->new({binary => 1}) FAILED: " . Text::CSV::Encoded->error_diag();
+
+    } elsif ($format eq "ods"){
+        $type = 'application/vnd.oasis.opendocument.spreadsheet';
+        $ods_fh = File::Temp->new( UNLINK => 0 );
+        $ods_filepath = $ods_fh->filename;
+    }
+
+    if ($table eq "search_fields"){
+        $reportfilename = "search_fields_table";
+        my @search_field_names   = $input->multi_param("search_field_name");
+        my @search_fileds_labels = $input->multi_param("search_field_label");
+        my @search_fileds_types  = $input->multi_param("search_field_type");
+        my @search_field_weights = $input->multi_param("search_field_weight");
+
+        if ($format eq 'tab') {
+            my @header_row = ("Name\t\t\t","Label\t\t\t","Type","Weight");
+            $content .= join("\t\t", @header_row) . "\n";
+            $content = Encode::decode('UTF-8', $content);
+
+            foreach my $search_field_name (@search_field_names){
+                my @row;
+                $search_field_name .= ' ' while (length($search_field_name)< 25);
+                $search_fileds_labels[$i] .= ' ' while (length($search_fileds_labels[$i])< 25);
+                @row = ($search_field_name, $search_fileds_labels[$i], $search_fileds_types[$i], $search_field_weights[$i]);
+                $content .= join("\t\t", @row) . "\n";
+                $i++;
+            }
+        } else {
+            my @header_row = ("Name","Label","Type","Weight");
+
+            if ( $format eq 'csv' ) {
+                if ($csv->combine(@header_row)) {
+                    $content .= Encode::decode('UTF-8', $csv->string()) . "\n";
+                    foreach my $search_field_name (@search_field_names){
+                        my @row;
+                        @row = ($search_field_name,$search_fileds_labels[$i],$search_fileds_types[$i],$search_field_weights[$i]);
+                        if ($csv->combine(@row)) {
+                            $content .= $csv->string() . "\n";
+                        }
+                        $i++;
+                    }
+                }
+            } elsif ( $format eq 'ods' ) {
+                my @headers = @header_row;
+                push @$ods_content, \@headers;
+
+                foreach my $search_field_name (@search_field_names){
+                    my @content_row;
+                    push @content_row, Encode::encode( 'UTF8', $search_field_name ), Encode::encode( 'UTF8', $search_fileds_labels[$i] ) , $search_fileds_types[$i],$search_field_weights[$i];
+                    push @$ods_content, \@content_row;
+                    $i++;
+                }
+                generate_ods($ods_filepath, $ods_content);
+                binmode(STDOUT);
+                open $ods_fh, '<', $ods_filepath;
+                $content .= $_ while <$ods_fh>;
+                unlink $ods_filepath;
+            }
+        }
+    } else {
+        my (@search_field_names, @sort, @facet, @suggestible, @marc_field);
+        if ($table eq "biblios"){
+            $reportfilename = "biblios_table";
+            @search_field_names = $input->multi_param("search_field_names_biblios");
+            @sort               = $input->multi_param("sort_biblios");
+            @facet              = $input->multi_param("facet_biblios");
+            @suggestible        = $input->multi_param("suggestible_biblios");
+            @marc_field         = $input->multi_param("marc_field_biblios");
+
+        } elsif ($table eq "authorities"){
+            $reportfilename = "authorities_table";
+            @search_field_names = $input->multi_param("search_field_names_authorities");
+            @sort               = $input->multi_param("sort_authorities");
+            @facet              = $input->multi_param("facet_authorities");
+            @suggestible        = $input->multi_param("suggestible_authorities");
+            @marc_field         = $input->multi_param("marc_field_authorities");
+        }
+        if ($format eq "tab"){
+            my @header_row = ("Search field\t\t","Sortable","Facetable","Suggestible","Mapping");
+            $content .= join("\t", @header_row) . "\n";
+            $content = Encode::decode('UTF-8', $content);
+
+            foreach my $search_field_name (@search_field_names){
+                my @row;
+                $search_field_name .= ' ' while (length($search_field_name)< 20);
+                @row = ($search_field_name,$sort[$i],$facet[$i],$suggestible[$i],$marc_field[$i]);
+                $content .= join("\t\t", @row) . "\n";
+                $i++;
+            }
+        } else {
+            my @header_row = ("Search field","Sortable","Facetable","Suggestible","Mapping ");
+            if ( $format eq 'csv' ) {
+                if ($csv->combine(@header_row)) {
+                    $content .= Encode::decode('UTF-8', $csv->string()) . "\n";
+                    foreach my $search_field_name (@search_field_names){
+                        my @row;
+                        @row = ($search_field_name,$sort[$i],$facet[$i],$suggestible[$i],$marc_field[$i]);
+                        if ($csv->combine(@row)) {
+                            $content .= $csv->string() . "\n";
+                        }
+                        $i++;
+                    }
+                }
+            } elsif ( $format eq 'ods' ) {
+                my @headers = @header_row;
+                push @$ods_content, \@headers;
+
+                foreach my $search_field_name (@search_field_names){
+                    my @content_row;
+                    push @content_row, Encode::encode( 'UTF8', $search_field_name ), $sort[$i] , $facet[$i], $suggestible[$i], Encode::encode( 'UTF8', $marc_field[$i] );
+                    push @$ods_content, \@content_row;
+                    $i++;
+                }
+                generate_ods($ods_filepath, $ods_content);
+                binmode(STDOUT);
+                open $ods_fh, '<', $ods_filepath;
+                $content .= $_ while <$ods_fh>;
+                unlink $ods_filepath;
+            }
+        }
+    }
+
+    if ($format eq "tab"){
+        $reportfilename .= ".tab";
+    } elsif ($format eq "csv"){
+        $reportfilename .= ".csv";
+    } elsif ($format eq "ods"){
+        $reportfilename .= ".ods";
+    }
+
+    print $input->header(
+        -type => $type,
+        -attachment=> $reportfilename
+    );
+    print $content;
+    exit;
+}
 
 
 my @indexes;
@@ -215,11 +371,21 @@ while ( my $search_field = $search_fields->next ) {
     push @all_search_fields, $search_field_unblessed;
 }
 
+my @extensions = ("csv","tab","ods");
+my @formats_and_extensions =("Semicolon separated test (.csv)","Tab separated text (.tab)","Open Document Spreadsheet (.ods)");
+my @formats;
+my $j = 0;
+foreach my $extension (@extensions){
+    push (@formats , { extension => $extension, format_and_extension =>$formats_and_extensions[$j] });
+    $j++;
+}
+
 $template->param(
     indexes           => \@indexes,
     all_search_fields => \@all_search_fields,
     facetable_fields  => \@facetable_fields,
     messages          => \@messages,
+    formats           => \@formats,
 );
 
 output_html_with_http_headers $input, $cookie, $template->output;
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/searchengine/elasticsearch/mappings.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/searchengine/elasticsearch/mappings.tt
index 33df1fb..8b8eaf3 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/searchengine/elasticsearch/mappings.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/searchengine/elasticsearch/mappings.tt
@@ -26,6 +26,18 @@
         return new_line;
     }
 
+    function create_input(value, name){
+        $("#save_or_reset").append('<input class="remove" type="hidden" value="' + value + '" name="' + name + '"/>' );
+    }
+
+    function create_submit(format, table_name){
+        $(".remove").remove();
+        create_input("download","op");
+        create_input(format,"format");
+        create_input(table_name,"table");
+        $("#save_or_reset").submit();
+    }
+
     $(document).ready(function() {
         $("#tabs").tabs();
         $('.delete').click(function() {
@@ -56,12 +68,40 @@
         $("#facet_biblios > table").tableDnD( {
             onDragClass: "dragClass",
         } );
+
+        $("#edit").click(function () {
+            $(".remove").remove();
+            create_input("edit","op");
+            $("#save_or_reset").submit();
+        });
+        $("#reset_confirm").click(function () {
+            $(".remove").remove();
+            create_input("reset_confirm","op");
+            $("#save_or_reset").submit();
+        });
+
+        [% FOREACH format IN formats %]
+            $("#link_[% format.extension | html %]").click( function () {
+                create_submit("[% format.extension | html %]","search_fields");
+            });
+
+            [% FOREACH index IN indexes %]
+            $("#link_[% index.index_name | html %]_[% format.extension | html %]").click( function () {
+                create_submit("[% format.extension | html %]","[% index.index_name | html %]");
+            });
+          [% END %]
+        [% END %]
     });
 </script>
 <style>
 a.add, a.delete {
     cursor: pointer;
 }
+
+#inline {
+    display : inline;
+}
+
 </style>
 </head>
 <body id="admin_searchengine_mappings" class="admin">
@@ -142,7 +182,7 @@ a.add, a.delete {
             </form>
         </div>
     [% END %]
-    <form method="post">
+    <form method="post" id="save_or_reset" >
         <div id="tabs" class="toptabs" style="clear:both">
             <ul>
                 <li><a href="#search_fields">Search fields</a></li>
@@ -154,6 +194,20 @@ a.add, a.delete {
                 [% END %]
             </ul>
             <div id="search_fields">
+                <div id="inline">
+                    <button class="btn btn-default" type="submit" name="op" value="edit"><i class="fa fa-hdd-o" aria-hidden="true"></i> Save</button>
+                    <button class="btn btn-default" type="submit" name="op" value="reset_confirm"><i class="fa fa-refresh" aria-hidden="true"></i> Reset Mappings</button>
+
+                    <div class="btn-group">
+                        <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" class="format"><i class="fa fa-upload"></i> Download Search fields table <span class="caret"></span></button>
+                        <ul class="dropdown-menu">
+                          [% FOREACH format IN formats%]
+                            <li><a href="#" id="link_[% format.extension | html %]">[% format.format_and_extension | html%]</a></li>
+                          [% END %]
+                        </ul>
+                    </div><br/>
+                </div><br/>
+
               <table class="search_fields">
                 <thead>
                   <tr>
@@ -222,8 +276,31 @@ a.add, a.delete {
                 </tbody>
               </table>
             </div>
+
             [% FOREACH index IN indexes %]
                 <div id="mapping_[% index.index_name | html %]">
+                    <div id="inline">
+                      <button class="btn btn-default" type="submit" name="op" value="edit"><i class="fa fa-hdd-o" aria-hidden="true"></i> Save</button>
+                      <button class="btn btn-default" type="submit" name="op" value="reset_confirm"><i class="fa fa-refresh" aria-hidden="true"></i> Reset Mappings</button>
+
+                      <div class="btn-group">
+                        <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" class="format"><i class="fa fa-upload"></i> Download [% index.index_name | html %] table <span class="caret"></span></button>
+                        <ul class="dropdown-menu">
+                          [% FOREACH format IN formats%]
+                            <li><a href="#" id="link_[% index.index_name | html %]_[% format.extension | html %]">[% format.format_and_extension | html%]</a></li>
+                          [% END %]
+                        </ul>
+                      </div><br/>
+                    </div><br/>
+
+                    [% FOREACH mappings IN index.mappings %]
+                      <input type="hidden" value="[% mappings.search_field_name | html %]" name="search_field_names_[% index.index_name | html %]" />
+                      <input type="hidden" value="[% mappings.sort | html %]" name="sort_[% index.index_name | html %]" />
+                      <input type="hidden" value="[% mappings.facet | html %]" name="facet_[% index.index_name | html %]" />
+                      <input type="hidden" value="[% mappings.suggestible | html %]" name="suggestible_[% index.index_name | html %]" />
+                      <input  type="hidden" value="[% mappings.marc_field | html %]" name="marc_field_[% index.index_name | html %]" />
+                    [% END %]
+
                     <table class="mappings" data-index_name="[% index.index_name | html %]">
                       <thead>
                         <tr class="nodrag nodrop">
@@ -389,12 +466,13 @@ a.add, a.delete {
                 </div>
             [% END %]
         </div>
-        <p>
-            <button class="btn btn-default" type="submit" name="op" value="edit"><i class="fa fa-hdd-o" aria-hidden="true"></i> Save</button>
-            <button class="btn btn-default" type="submit" name="op" value="reset_confirm"><i class="fa fa-refresh" aria-hidden="true"></i> Reset Mappings</button>
-        </p>
+
     </form>
 
+    <p>
+      <button id ="edit" class="btn btn-default" type="submit" ><i class="fa fa-hdd-o" aria-hidden="true"></i> Save</button>
+      <button id="reset_confirm" class="btn btn-default" type="submit"><i class="fa fa-refresh" aria-hidden="true"></i> Reset Mappings</button>
+    </p>
             </main>
         </div> <!-- /.col-sm-10.col-sm-push-2 -->
 
-- 
2.7.4