From 2060d9868fb8a8cd48da3cf3268f788bddd895f3 Mon Sep 17 00:00:00 2001
From: Cori Lynn Arnold <carnold@dgiinc.com>
Date: Wed, 15 Jan 2020 18:06:02 +0000
Subject: [PATCH] Bug 24429: Advanced editor - Add ability to generate an
 export file as cataloger saves

Adds the ability for catalogers to *automatically* save a list of bibs to export as they
are cataloging.

To test, apply patch:
0/Verify that the advanced editor is enabled by going to
"Administration" and searching for "EnableAdvancedCatalogingEditor"
1/Set it to "Enable" if it isn't already.
2/Go to "Cataloging", click on "Advanced editor"
3/Click on "Export" button (verify a modal window comes up)
4/Click on the link for "New Export File", name the export file
5/Close the modal window
6/Search for a record by entering "specious" in the keyword box and
hitting enter
7/Click "import" on a record
8/Make some changes and hit "Save", note the bib number
9/Repeat steps 6-8 for several records
10/Click on "Export" button
11/Click on the link for the name you created in step 4
12/Verify the bib numbers from step 8 are in the Export Files Editor
13/Click "Generate Export File"
14/Verify that all the records for every bib listed is in the file
generated
15/Repeat steps with different file names / records
---
 .../lib/koha/cateditor/preferences.js              |   1 +
 koha-tmpl/intranet-tmpl/prog/css/cateditor.css     |  35 ++++-
 .../prog/en/includes/cateditor-ui.inc              | 149 ++++++++++++++++++++-
 .../prog/en/modules/cataloguing/editor.tt          |  27 ++++
 tools/export.pl                                    |   7 +
 5 files changed, 216 insertions(+), 3 deletions(-)

diff --git a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js
index 5cf81dace6..5a9e32ae89 100644
--- a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js
+++ b/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js
@@ -33,6 +33,7 @@ define( function() {
                 font: 'monospace',
                 fontSize: '1em',
                 macros: {},
+                exportRecords: {},
                 selected_search_targets: {},
             }, saved_prefs );
         },
diff --git a/koha-tmpl/intranet-tmpl/prog/css/cateditor.css b/koha-tmpl/intranet-tmpl/prog/css/cateditor.css
index 277a1b1aa6..b499daebb0 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/cateditor.css
+++ b/koha-tmpl/intranet-tmpl/prog/css/cateditor.css
@@ -220,7 +220,7 @@ body {
 
 /*> Search */
 
-#advanced-search-ui .modal-lg, #search-results-ui .modal-lg, #macro-ui .modal-lg {
+#advanced-search-ui .modal-lg, #search-results-ui .modal-lg, #macro-ui .modal-lg, #exportReport-ui .modal-lg {
     width: 90%;
 }
 
@@ -368,6 +368,37 @@ body {
     z-index: 9001;
 }
 
+/* Export Records Editor */
+#export-save-message {
+    color: #666;
+    font-size: 13px;
+    float: right;
+    line-height: 26px;
+}
+
+#export-list > li {
+    border: 2px solid #F0F0F0;
+    border-radius: 6px;
+    display: block;
+    font-size: 115%;
+}
+
+#export-editor {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+}
+
+#export-editor .CodeMirror {
+    flex: 1;
+    font-size: 13px;
+}
+
+/* Hotpatch from latest CodeMirror: Fix gutter positioning */
+.CodeMirror-gutter-wrapper {
+    position: absolute;
+}
+
 /*> Macros */
 #macro-save-message {
     color: #666;
@@ -473,4 +504,4 @@ body {
 
 .ui-keyboard-accept {
     display: none;
-}
\ No newline at end of file
+}
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc
index e4c76b95f5..7b35898819 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc
@@ -79,6 +79,7 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
 
     var editor;
     var macroEditor;
+    var exportRecordEditor;
 
     function makeAuthorisedValueWidgets( frameworkCode ) {
         $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
@@ -639,6 +640,9 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
             case 'macros':
                 // Macros loaded on first show of modal
                 break;
+            case 'exportRecords':
+                // Export Records loaded on first show of modal
+                break;
             case 'selected_search_targets':
                 $.each( z3950Servers, function( server_id, server ) {
                     var saved_val = Preferences.user.selected_search_targets[server_id];
@@ -720,6 +724,88 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
         Preferences.Save( [% logged_in_user.borrowernumber | html %] );
     }
 
+    function loadExportRecord( name ) {
+        $( '#export-list li' ).removeClass( 'active' );
+        exportRecordEditor.activeExportRecord = name;
+
+        if ( !name ) {
+            exportRecordEditor.setValue( '' );
+            return;
+        }
+
+        $( '#export-list li[data-name="' + name + '"]' ).addClass( 'active' );
+        var exportRecord = Preferences.user.exportRecords[name];
+        exportRecord.contents = dedupeString(exportRecord.contents);
+        exportRecordEditor.setValue( exportRecord.contents );
+        exportRecordEditor.setOption( 'readOnly', false );
+        if ( exportRecord.history ) exportRecordEditor.setHistory( exportRecord.history );
+    }
+
+    function dedupeString( strg ) {
+
+        var pieces = strg.split("\n");
+        var output = []; //Output array
+
+        for (var i = 0; i < pieces.length; i++) {
+               if (output.indexOf(pieces[i]) < 0) {
+                         output.push(pieces[i]);
+               }
+        }
+
+        return output.join("\n");
+    }
+
+    function storeExportRecord( name, exportRecord ) {
+        if ( exportRecord ) {
+            Preferences.user.exportRecords[name] = exportRecord;
+        } else {
+            delete Preferences.user.exportRecords[name];
+        }
+
+        Preferences.Save( [% logged_in_user.borrowernumber | html %] );
+    }
+
+    function showSavedExportRecords( exportRecords ) {
+        var scrollTop = $('#export-list').scrollTop();
+        $( '#export-list').empty();
+        var export_list = $.map (Preferences.user.exportRecords, function (exportRecord, name) {
+                return $.extend( { name: name}, exportRecord );
+            } );
+            export_list.sort ( function ( a, b ) {
+                    return a.name.localeCompare(b.name);
+                } );
+        $.each ( export_list, function ( undef, exportRecord ) {
+                var $li = $( '<li data-name="' + exportRecord.name + '"><a href"#">' + exportRecord.name + '</a><ol class="exportRecord-info"><ol></li>' );
+                $li.click ( function() {
+                        loadExportRecord(exportRecord.name);
+                        return false;
+                    } );
+        if ( exportRecord.name == exportRecordEditor.activeExportRecord ) $li.addClass ( 'active' );
+        var modified = exportRecord.modified && new Date(exportRecord.modified);
+        $('#export-list').append($li);
+      });
+        var $new_li = $( '<li class="new-exportRecord"><a href="#">' + _("New Export File...") + '</a></li>' );
+        $new_li.click( function() {
+            var name = prompt(_("Please enter the name for the new export record:"));
+            if (!name) return;
+
+            if ( !Preferences.user.exportRecords[name] ) storeExportRecord( name, { contents: "" } );
+            showSavedExportRecords();
+            loadExportRecord( name );
+        } );
+        $('#export-list').append($new_li);
+        $('#export-list').scrollTop(scrollTop);
+    }
+
+    function saveExportRecord() {
+        var name = exportRecordEditor.activeExportRecord;
+
+        if ( !name || exportRecordEditor.savedGeneration == exportRecordEditor.changeGeneration() ) return;
+
+        exportRecordEditor.savedGeneration = exportRecordEditor.changeGeneration();
+        storeExportRecord( name, { contents: exportRecordEditor.getValue(), modified: (new Date()).valueOf(), history: exportRecordEditor.getHistory() } );
+    }
+
     function showSavedMacros( macros ) {
         var scrollTop = $('#macro-list').scrollTop();
         $( '#macro-list' ).empty();
@@ -819,6 +905,32 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
             }, 100);
         }
 
+        $( '#exporter-ui' ).on( 'shown.bs.modal', function() {
+            if ( exportRecordEditor ) return;
+
+            exportRecordEditor = CodeMirror(
+                $('#export-editor')[0],
+                {
+                    mode: 'null',
+                    lineNumbers: true,
+                    readOnly: true,
+                }
+            );
+            var saveTimeout;
+            exportRecordEditor.on( 'change', function( cm, change ) {
+                $('#export-save-message').empty();
+                if ( change.origin == 'setValue' ) return;
+
+                if ( saveTimeout ) clearTimeout( saveTimeout );
+                saveTimeout = setTimeout( function() {
+                    saveExportRecord();
+                    saveTimeout = null;
+                }, 500 );
+            } );
+
+            showSavedExportRecords();
+        } );
+
         $( '#macro-ui' ).on( 'shown.bs.modal', function() {
             if ( macroEditor ) return;
 
@@ -935,6 +1047,16 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
                 saveRecord( backend + '/', editor, finishCb );
             }
 
+            //Add the record to the exportRecords list if there is one
+            if(Preferences.user.exportRecords) {
+                var export_list = $.map (Preferences.user.exportRecords, function (exportRecord, name) {
+                            return $.extend( { name: name}, exportRecord );
+                        });
+                $.each ( export_list, function ( undef, exportRecord ) {
+                        exportRecord.contents += "\n" + state.recordID;
+                        storeExportRecord( exportRecord.name, { contents: exportRecord.contents} );
+                    } );
+            }
             return false;
         } );
 
@@ -1027,6 +1149,31 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
             }
         } );
 
+        $('#open-exporter').click( function() {
+            $('#exporter-ui').modal('show');
+
+            return false;
+        } );
+
+        $('#generate-file').click (function() {
+                var bibs = exportRecordEditor.getValue();
+                bibs = bibs.replace(/\r?\n/g, ",") + ",";
+                $.post("/cgi-bin/koha/tools/export.pl", {"record_type":"bibs", "export_bibs":bibs, "filename":"test.mrc", "op":"export" }, function (data) {
+                        var blob = new Blob([data], { 'type': 'application/octet-stream;charset=utf-8'});
+                        saveAs(blob, exportRecordEditor.activeExportRecord + ".mrc" );
+                        });
+        });
+
+        $('#delete-export-file').click( function() {
+            if ( !exportRecordEditor.activeExportRecord || !confirm( _("Are you sure you want to delete this Export File?") ) ) return;
+
+            storeExportRecord( exportRecordEditor.activeExportRecord, undefined );
+            showSavedExportRecords();
+            loadExportRecord( undefined );
+
+            return false;
+        } );
+
         $( '#show-advanced-search' ).click( function() {
             showAdvancedSearch();
 
@@ -1078,7 +1225,7 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
         bindGlobalKeys();
 
         // Setup UI
-        $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
+        $("#advanced-search-ui, #search-results-ui, #macro-ui", "#exporter-ui").each( function() {
             $(this).modal({ show: false });
         } );
 
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt
index 8ed226e5c9..bc37136721 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt
@@ -41,6 +41,7 @@
         </div>
         <button class="btn btn-default" id="import-records" title="Import an MARC (ISO 2709) or MARCXML record"><i class="fa fa-download"></i> <span>Import record...</span></button>
         <button class="btn btn-default" id="open-macros" title="Run and edit macros"><i class="fa fa-play"></i> <span>Macros...</span></button>
+        <button class="btn btn-default" id="open-exporter" title="Generate export files"><i class="fa fa-cogs"></i> <span>Export...</span></button>
         <div class="btn-group">
             <button class="btn btn-default dropdown-toggle" data-toggle="dropdown"><i class="fa fa-cog"></i> Settings <span class="caret"></span></button>
             <ul id="prefs-menu" class="dropdown-menu">
@@ -274,6 +275,32 @@
 </div>
 </div>
 
+
+<div id="exporter-ui" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="macro-title" aria-hidden="true">
+<div class="modal-dialog modal-lg">
+<div class="modal-content">
+
+<div class="modal-header">
+    <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
+    <h3 id="exporter-title">Export Files</h3>
+</div>
+
+<div class="modal-body">
+    <div class="row">
+    <div class="col-md-3"><ul id="export-list"></ul></div>
+    <div class="col-md-9" id="export-editor">
+        <div id="exporter-toolbar" class="btn-toolbar">
+            <button class="btn btn-default" id="generate-file" title="Generate export file"><i class="fa fa-play"></i> Generate Export File</button>
+            <button class="btn btn-default" id="delete-export-file" title="Stop generating the export file and clear the results from current export file"><i class="fa fa-trash"></i> Clear Export Generation</button>
+        </div>
+    </div>
+    </div>
+</div>
+</div>
+</div>
+</div>
+
+
 <div id="keyboard-layout" class="modal fade" role="dialog">
     <div class="modal-dialog">
         <div class="modal-content">
diff --git a/tools/export.pl b/tools/export.pl
index 0704b57d6d..49b6941c2f 100755
--- a/tools/export.pl
+++ b/tools/export.pl
@@ -51,6 +51,13 @@ if ( $query->param("biblionumbers") ) {
     @record_ids = $query->multi_param("biblionumbers");
 }
 
+# export_bibs is sent from advanced editor only
+if ( $query->param("export_bibs") ) {
+    $record_type = 'bibs';
+    my $b = $query->param("export_bibs");
+    @record_ids = split(/,/, $b);
+}
+
 # Default value for output_format is 'iso2709'
 $output_format ||= 'iso2709';
 # Retrocompatibility for the format parameter
-- 
2.11.0