From 0cc5abc486009549f5cf683a1bbbbf27f72a1d61 Mon Sep 17 00:00:00 2001
From: Jesse Weaver <jweaver@bywatersolutions.com>
Date: Mon, 22 Feb 2016 11:14:22 -0700
Subject: [PATCH] Bug 19270: Rancor - add ability to search batches by
 arbitrary marc fields

To test:
1 - Open advanced cataloging editor
2 - In settings->import batches enable some batches for searching
3 - Click 'Advanced' in the search box
4 - Check an import batch, note a 'MARC field' searchbox appears
5 - Enter some test searches (use '*' as wildcard - add it to both ends
        of value unless you want exact search)
6 - Note results appear as expected
---
 Koha/MetaSearcher.pm                               | 25 ++++++++++++++++++++--
 koha-tmpl/intranet-tmpl/prog/css/cateditor.css     |  4 ++++
 .../prog/en/includes/cateditor-ui.inc              | 22 ++++++++++++++-----
 .../prog/en/modules/cataloguing/editor.tt          |  5 +++++
 4 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/Koha/MetaSearcher.pm b/Koha/MetaSearcher.pm
index bff7ce3..21674cc 100644
--- a/Koha/MetaSearcher.pm
+++ b/Koha/MetaSearcher.pm
@@ -219,7 +219,9 @@ sub _pqf_query_from_terms {
     my $query;
 
     while ( my ( $index, $value ) = each %$terms ) {
-        $value =~ s/"/\\"/;
+        next unless ( $_pqf_mapping->{$index} );
+
+        $value =~ s/"/\\"/g;
 
         my $term = '@attr ' . $_pqf_mapping->{$index} . ' "' . $value . '"';
 
@@ -237,7 +239,7 @@ sub _db_query_get_match_conditions {
     my ( $index, $value ) = @_;
 
     if ( $value =~ /\*/ ) {
-        $value =~ s/\*/%/;
+        $value =~ s/\*/%/g;
         return { -like => $value };
     } elsif ( $index eq 'isbn' ) {
         return C4::Koha::GetNormalizedISBN($value) || $value;
@@ -269,6 +271,25 @@ sub _batch_db_query_from_terms {
 
             # These are implicitly joined with OR because the arrayref doesn't start with -and
             push @db_terms, \@term;
+        } elsif ( $index =~ /^marc-(\w{3})(\w)?$/ ) {
+            my $column;
+
+            # Yes, this is MySQL-specific and ugly. There seems to be no other way to ask
+            # SQL::Abstract to compare a function value.
+            if ( defined $2 ) {
+                $column = q{ExtractValue(marcxml, '//datafield[@tag="} . $1 . q{"]/subfield[@code="} . $2 . q{"]')};
+            } else {
+                $column = q{ExtractValue(marcxml, '//controlfield[@tag="} . $1 . q{"]')};
+            }
+
+            my $op = '=';
+
+            if ( $value =~ /\*/ ) {
+                $value =~ s/\*/%/g;
+                $op = 'LIKE';
+            }
+
+            push @db_terms, \[ "$column $op ?", $value ];
         } elsif ( $_batch_db_mapping->{$index} ) {
             push @db_terms, $_batch_db_mapping->{$index} => [ -and => _db_query_get_match_conditions( $index, $value ) ];
         } else {
diff --git a/koha-tmpl/intranet-tmpl/prog/css/cateditor.css b/koha-tmpl/intranet-tmpl/prog/css/cateditor.css
index dd8b91c..afd5a1c 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/cateditor.css
+++ b/koha-tmpl/intranet-tmpl/prog/css/cateditor.css
@@ -311,6 +311,10 @@ body {
     width: 14em;
 }
 
+#advanced-search-fields .marc-subfield-input {
+    width: 4em;
+}
+
 .fa.fa-spinner {
     display: inline-block;
     height: 16px;
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 9c4aacd..5a7643f 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc
@@ -477,11 +477,19 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
         var terms = {};
 
         $('#advanced-search-ui .search-box').each( function() {
-            if ( !this.value ) return;
+            if ( !this.value || !$(this).data('qualifier') ) return;
 
             terms[ $(this).data('qualifier') ] = this.value;
         } );
 
+        var marc_subfield = $('#advanced-search-by-marc-subfield').val();
+        var marc_term = $('#advanced-search-by-marc').val();
+
+        // This mess (also used as a pattern="" in the form) allows 00X or XXXx).
+        if ( /^(00[0-9A-Za-z]|([1-9A-Za-z][0-9A-Za-z]{2}|0[1-9A-Za-z][0-9A-Za-z])[0-9A-Za-z])$/.test(marc_subfield) && marc_term ) {
+            terms[ 'marc-' + marc_subfield ] = marc_term;
+        }
+
         if ( $.isEmptyObject(terms) ) return;
 
         if ( Search.Run( z3950Servers, terms ) ) {
@@ -709,11 +717,16 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
                 // Macros loaded on first show of modal
                 break;
             case 'selected_search_targets':
+                var batch_selected_for_search = false;
                 $.each( z3950Servers, function( server_id, server ) {
                     var saved_val = Preferences.user.selected_search_targets[server_id];
 
                     if ( saved_val != null ) server.checked = saved_val;
+
+                    if ( server.checked && /^batch:/.test(server_id) ) batch_selected_for_search = true;
                 } );
+
+                $('#arbitrary-marc-search').toggle( batch_selected_for_search );
                 break;
             case 'selected_save_targets':
                 $.each( state.saveTargets, function( target_id, target ) {
@@ -729,7 +742,7 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
         function _addHandler( sel, event, handler ) {
             $( sel ).on( event, function (e) {
                 e.preventDefault();
-                handler( e, Preferences.user[pref] );
+                handler.call( this, e, Preferences.user[pref] );
                 Preferences.Save( [% USER_INFO.borrowernumber %] );
                 showPreference(pref);
             } );
@@ -771,17 +784,16 @@ require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'pr
                 } );
                 break;
             case 'selected_save_targets':
-                $( document ).on( 'change', 'input.save-toggle-target', function() {
+                _addLiveHandler( 'input.save-toggle-target', 'change', function() {
                     var target_id = $( this ).data('target-id');
                     Preferences.user.selected_save_targets[target_id] = this.checked;
                     Preferences.Save( [% USER_INFO.borrowernumber %] );
                 } );
                 break;
             case 'selected_search_targets':
-                $( document ).on( 'change', 'input.search-toggle-server', function() {
+                _addLiveHandler( 'input.search-toggle-server', 'change', function() {
                     var server_id = $( this ).closest('li').data('server-id');
                     Preferences.user.selected_search_targets[server_id] = this.checked;
-                    Preferences.Save( [% USER_INFO.borrowernumber %] );
                 } );
                 break;
         }
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 a324842..bc802c0 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt
@@ -176,6 +176,11 @@
                 <label for="advanced-search-by-publication-date">Publication date:</label>
                 <input class="search-box" data-qualifier="date" id="advanced-search-by-publication-date" />
             </li>
+            <li id="arbitrary-marc-search" style="display-none">
+                <label for="advanced-search-by-marc">MARC field/subfield (XXXx):</label>
+                <input class="marc-subfield-input" id="advanced-search-by-marc-subfield" pattern="00[0-9A-Za-z]|([1-9A-Za-z][0-9A-Za-z]{2}|0[1-9A-Za-z][0-9A-Za-z])[0-9A-Za-z]" />
+                <input class="search-box" id="advanced-search-by-marc" />
+            </li>
         </ul>
     </div>
     </div>
-- 
2.7.4