View | Details | Raw Unified | Return to bug 27113
Collapse All | Expand All

(-)a/admin/searchengine/elasticsearch/field_config.yaml (+4 lines)
Lines 47-52 search: Link Here
47
        type: text
47
        type: text
48
        analyzer: analyzer_phrase
48
        analyzer: analyzer_phrase
49
        search_analyzer: analyzer_phrase
49
        search_analyzer: analyzer_phrase
50
      autocomplete:
51
        type: text
52
        analyzer: autocomplete
53
        search_analyzer: standard
50
      raw:
54
      raw:
51
        type: keyword
55
        type: keyword
52
        normalizer: nfkc_cf_normalizer
56
        normalizer: nfkc_cf_normalizer
(-)a/admin/searchengine/elasticsearch/index_config.yaml (+14 lines)
Lines 2-7 Link Here
2
# Index configuration that defines how different analyzers work.
2
# Index configuration that defines how different analyzers work.
3
index:
3
index:
4
  analysis:
4
  analysis:
5
    tokenizer:
6
      autocomplete_tokenizer:
7
        type: edge_ngram
8
        min_gram: 1
9
        max_gram: 10
10
        token_chars: 
11
          - letter
12
          - digit
5
    analyzer:
13
    analyzer:
6
      # Phrase analyzer is used for phrases (exact phrase match)
14
      # Phrase analyzer is used for phrases (exact phrase match)
7
      analyzer_phrase:
15
      analyzer_phrase:
Lines 10-15 index: Link Here
10
          - icu_folding
18
          - icu_folding
11
        char_filter:
19
        char_filter:
12
          - punctuation
20
          - punctuation
21
      autocomplete:
22
        type: custom
23
        filter:
24
          - icu_folding
25
          - lowercase
26
        tokenizer: autocomplete_tokenizer     
13
      analyzer_standard:
27
      analyzer_standard:
14
        tokenizer: icu_tokenizer
28
        tokenizer: icu_tokenizer
15
        filter:
29
        filter:
(-)a/api/elasticsearch/intranet-autocomplete.pl (+87 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
use strict;
4
use warnings;
5
use CGI;
6
use JSON;
7
use Koha::SearchEngine::Search;
8
use Switch;
9
my $searcher = Koha::SearchEngine::Search->new({index => 'biblios'});
10
my $cgi = CGI->new;
11
use utf8;
12
use Text::Unaccent;
13
14
my $name_key = "autocomplete";
15
16
#Chose GET in key parametre
17
switch($cgi->param("key")) {
18
  case ($name_key) {
19
    my @prefix = split /,/, $cgi->param("prefix");
20
    # how fields for autocomplete 
21
    my $length = scalar @prefix;
22
    #name analyzer which is in mapping, setting ES
23
    my $analyzer = 'autocomplete';
24
    my @all_prefix;
25
    #search by many prefix fields
26
    if ($length > 1){
27
      foreach my $pref ( @prefix ) {
28
        push(@all_prefix, GetAutocompleteES($cgi->param("q"), $pref, $analyzer));
29
      }
30
      print $cgi->header("application/json");
31
      print to_json(\@all_prefix); 
32
    } 
33
    #search by one prefix field
34
    elsif ($length == 1) {
35
      my $test  = GetAutocompleteES($cgi->param("q"), $cgi->param("prefix"), $analyzer);
36
      print $cgi->header("application/json");
37
      print to_json(GetAutocompleteES($cgi->param("q"), $prefix[0], $analyzer)); 
38
    } 
39
    #no prefix 404
40
    else {
41
      response404JSON();
42
    }
43
  }
44
  #no key 404
45
  else {
46
    response404JSON();
47
  }
48
}
49
50
sub response404JSON {
51
  my $json = JSON->new->utf8;
52
  my $header_type = "application/json";
53
  my $header_status = "404";
54
  my $output = $json->encode({
55
    "error" => "No data",
56
    "description" => "Bad request",
57
  });
58
  print $cgi->header(
59
    -type => $header_type,
60
    -charset => "utf-8",
61
    -status => $header_status
62
  );
63
  print $output;
64
  print "\n";
65
}
66
67
sub GetAutocompleteES {
68
  my ($cgi_q, $prefix, $analyzer) = @_;
69
  my (%query, $results, @source);
70
  #prefix + analyzer
71
  my $prefix_analyzer = $prefix . '.' . $analyzer;  
72
  # we can change this variables
73
  my ($nb_fragments, $size_fragment, $pre_tags, $post_tags) = (3, 50, ["<strong>"], ["</strong>"]);
74
  push(@source, $prefix);
75
  $query{'_source'} = \@source; 
76
  $query{'query'}{'match'}{$prefix_analyzer}{'query'} = $cgi_q;
77
  $query{'query'}{'match'}{$prefix_analyzer}{'operator'} = 'and';
78
  #hightlight
79
  $query{'highlight'}{'number_of_fragments'} = $nb_fragments; 
80
  $query{'highlight'}{'fragment_size'} = $size_fragment; 
81
  $query{'highlight'}{'pre_tags'} = $pre_tags;
82
  $query{'highlight'}{'post_tags'} = $post_tags;
83
  $query{'highlight'}{'fields'}{$prefix_analyzer} = {};
84
  $results = $searcher->search(\%query);
85
86
  return $results->{'hits'}->{'hits'};
87
}
(-)a/installer/data/mysql/atomicupdate/bug_27113-elasticsearch_autocomplete_input_search.perl (+8 lines)
Line 0 Link Here
1
$DBversion = 'XXX'; # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
    # you can use $dbh here like:
4
    $dbh->do(q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('AutocompleteElasticSearch', '0', NULL, NULL, 'YesNo')});
5
6
    # Always end with this (adjust the bug info)
7
    NewVersion( $DBversion, 27113, "Autocomplete input on main page with elasticsearch");
8
}
(-)a/installer/data/mysql/mandatory/sysprefs.sql (-8 / +9 lines)
Lines 53-63 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
53
('AmazonCoverImages','0','','Display Cover Images in staff interface from Amazon Web Services','YesNo'),
53
('AmazonCoverImages','0','','Display Cover Images in staff interface from Amazon Web Services','YesNo'),
54
('AmazonLocale','US','US|CA|DE|FR|IN|JP|UK','Use to set the Locale of your Amazon.com Web Services','Choice'),
54
('AmazonLocale','US','US|CA|DE|FR|IN|JP|UK','Use to set the Locale of your Amazon.com Web Services','Choice'),
55
('AnonSuggestions','0',NULL,'Set to enable Anonymous suggestions to AnonymousPatron borrowernumber','YesNo'),
55
('AnonSuggestions','0',NULL,'Set to enable Anonymous suggestions to AnonymousPatron borrowernumber','YesNo'),
56
('AnonymousPatron','0',NULL,'Set the identifier (borrowernumber) of the anonymous patron. Used for suggestion and checkout history privacy',''),
56
('AnonymousPatron','0',NULL,'Set the identifier (borrowernumber) of the anonymous patron. Used for Suggestion and reading history privacy',''),
57
('ArticleRequests', '0', NULL, 'Enables the article request feature', 'YesNo'),
57
('ArticleRequests', '0', NULL, 'Enables the article request feature', 'YesNo'),
58
('ArticleRequestsLinkControl', 'calc', 'always|calc', 'Control display of article request link on search results', 'Choice'),
58
('ArticleRequestsLinkControl', 'calc', 'always|calc', 'Control display of article request link on search results', 'Choice'),
59
('ArticleRequestsMandatoryFields', '', NULL, 'Comma delimited list of required fields for bibs where article requests rule = ''yes''', 'multiple'),
59
('ArticleRequestsMandatoryFields', '', NULL, 'Comma delimited list of required fields for bibs where article requests rule = ''yes''', 'multiple'),
60
('ArticleRequestsMandatoryFieldsItemOnly', '', NULL, 'Comma delimited list of required fields for bibs where article requests rule = ''item_only''', 'multiple'),
60
('ArticleRequestsMandatoryFieldsItemsOnly', '', NULL, 'Comma delimited list of required fields for bibs where article requests rule = ''item_only''', 'multiple'),
61
('ArticleRequestsMandatoryFieldsRecordOnly', '', NULL, 'Comma delimited list of required fields for bibs where article requests rule = ''bib_only''', 'multiple'),
61
('ArticleRequestsMandatoryFieldsRecordOnly', '', NULL, 'Comma delimited list of required fields for bibs where article requests rule = ''bib_only''', 'multiple'),
62
('AudioAlerts','0','','Enable circulation sounds during checkin and checkout in the staff interface.  Not supported by all web browsers yet.','YesNo'),
62
('AudioAlerts','0','','Enable circulation sounds during checkin and checkout in the staff interface.  Not supported by all web browsers yet.','YesNo'),
63
('AuthDisplayHierarchy','0','','Display authority hierarchies','YesNo'),
63
('AuthDisplayHierarchy','0','','Display authority hierarchies','YesNo'),
Lines 270-276 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
270
('IntranetmainUserblock','','70|10','Add a block of HTML that will display on the intranet home page','Textarea'),
270
('IntranetmainUserblock','','70|10','Add a block of HTML that will display on the intranet home page','Textarea'),
271
('IntranetNav','','70|10','Use HTML tabs to add navigational links to the top-hand navigational bar in the staff interface','Textarea'),
271
('IntranetNav','','70|10','Use HTML tabs to add navigational links to the top-hand navigational bar in the staff interface','Textarea'),
272
('IntranetNumbersPreferPhrase','0',NULL,'Control the use of phr operator in callnumber and standard number staff interface searches','YesNo'),
272
('IntranetNumbersPreferPhrase','0',NULL,'Control the use of phr operator in callnumber and standard number staff interface searches','YesNo'),
273
('intranetreadinghistory','1','','If ON, Checkout history is enabled for all patrons','YesNo'),
273
('intranetreadinghistory','1','','If ON, Reading History is enabled for all patrons','YesNo'),
274
('IntranetReportsHomeHTML', '', NULL, 'Show the following HTML in a div on the bottom of the reports home page', 'Free'),
274
('IntranetReportsHomeHTML', '', NULL, 'Show the following HTML in a div on the bottom of the reports home page', 'Free'),
275
('IntranetSlipPrinterJS','','','Use this JavaScript for printing slips. Define at least function printThenClose(). For use e.g. with Firefox PlugIn jsPrintSetup, see http://jsprintsetup.mozdev.org/','Free'),
275
('IntranetSlipPrinterJS','','','Use this JavaScript for printing slips. Define at least function printThenClose(). For use e.g. with Firefox PlugIn jsPrintSetup, see http://jsprintsetup.mozdev.org/','Free'),
276
('intranetstylesheet','','50','Enter a complete URL to use an alternate layout stylesheet in Intranet','free'),
276
('intranetstylesheet','','50','Enter a complete URL to use an alternate layout stylesheet in Intranet','free'),
Lines 284-290 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
284
('item-level_itypes','1','','If ON, enables Item-level Itemtype / Issuing Rules','YesNo'),
284
('item-level_itypes','1','','If ON, enables Item-level Itemtype / Issuing Rules','YesNo'),
285
('itemBarcodeFallbackSearch','',NULL,'If set, uses scanned item barcodes as a catalogue search if not found as barcodes','YesNo'),
285
('itemBarcodeFallbackSearch','',NULL,'If set, uses scanned item barcodes as a catalogue search if not found as barcodes','YesNo'),
286
('itemBarcodeInputFilter','','whitespace|T-prefix|cuecat|libsuite8|EAN13','If set, allows specification of a item barcode input filter','Choice'),
286
('itemBarcodeInputFilter','','whitespace|T-prefix|cuecat|libsuite8|EAN13','If set, allows specification of a item barcode input filter','Choice'),
287
('itemcallnumber','',NULL,'The MARC field/subfield that is used to calculate the itemcallnumber (Dewey would be 082ab or 092ab; LOC would be 050ab or 090ab) could be 852hi from an item record','free'),
287
('itemcallnumber','082ab',NULL,'The MARC field/subfield that is used to calculate the itemcallnumber (Dewey would be 082ab or 092ab; LOC would be 050ab or 090ab) could be 852hi from an item record','free'),
288
('ItemsDeniedRenewal','','','This syspref allows to define custom rules for denying renewal of specific items.','Textarea'),
288
('ItemsDeniedRenewal','','','This syspref allows to define custom rules for denying renewal of specific items.','Textarea'),
289
('KohaAdminEmailAddress','root@localhost','','Define the email address where patron modification requests are sent','free'),
289
('KohaAdminEmailAddress','root@localhost','','Define the email address where patron modification requests are sent','free'),
290
('KohaManualBaseURL','https://koha-community.org/manual/','','Where is the Koha manual/documentation located?','Free'),
290
('KohaManualBaseURL','https://koha-community.org/manual/','','Where is the Koha manual/documentation located?','Free'),
Lines 422-428 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
422
('OpacMaxItemsToDisplay','50','','Max items to display at the OPAC on a biblio detail','Integer'),
422
('OpacMaxItemsToDisplay','50','','Max items to display at the OPAC on a biblio detail','Integer'),
423
('OpacMetaDescription','','','This description will show in search engine results (160 characters).','Textarea'),
423
('OpacMetaDescription','','','This description will show in search engine results (160 characters).','Textarea'),
424
('OpacMoreSearches', '', NULL, 'Add additional elements to the OPAC more searches bar', 'Textarea'),
424
('OpacMoreSearches', '', NULL, 'Add additional elements to the OPAC more searches bar', 'Textarea'),
425
('OPACMySummaryHTML','','70|10','Enter the HTML that will appear in a column on the \'my summary\' and \'my checkout history\' tabs when a user is logged in to the OPAC. Enter {BIBLIONUMBER}, {TITLE}, {AUTHOR}, or {ISBN} in place of their respective variables in the HTML. Leave blank to disable.','Textarea'),
425
('OPACMySummaryHTML','','70|10','Enter the HTML that will appear in a column on the \'my summary\' and \'my reading history\' tabs when a user is logged in to the OPAC. Enter {BIBLIONUMBER}, {TITLE}, {AUTHOR}, or {ISBN} in place of their respective variables in the HTML. Leave blank to disable.','Textarea'),
426
('OPACMySummaryNote','','','Note to display on the patron summary page. This note only appears if the patron is connected.','Free'),
426
('OPACMySummaryNote','','','Note to display on the patron summary page. This note only appears if the patron is connected.','Free'),
427
('OpacNav','Important links here.','70|10','Use HTML tags to add navigational links to the left-hand navigational bar in OPAC','Textarea'),
427
('OpacNav','Important links here.','70|10','Use HTML tags to add navigational links to the left-hand navigational bar in OPAC','Textarea'),
428
('OpacNavBottom','Important links here.','70|10','Use HTML tags to add navigational links to the left-hand navigational bar in OPAC','Textarea'),
428
('OpacNavBottom','Important links here.','70|10','Use HTML tags to add navigational links to the left-hand navigational bar in OPAC','Textarea'),
Lines 435-444 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
435
('OpacPasswordChange','1',NULL,'If ON, enables patron-initiated password change in OPAC (disable it when using LDAP auth)','YesNo'),
435
('OpacPasswordChange','1',NULL,'If ON, enables patron-initiated password change in OPAC (disable it when using LDAP auth)','YesNo'),
436
('OPACPatronDetails','1','','If OFF the patron details tab in the OPAC is disabled.','YesNo'),
436
('OPACPatronDetails','1','','If OFF the patron details tab in the OPAC is disabled.','YesNo'),
437
('OPACpatronimages','0',NULL,'Enable patron images in the OPAC','YesNo'),
437
('OPACpatronimages','0',NULL,'Enable patron images in the OPAC','YesNo'),
438
('OpacPrivacy','0',NULL,'if ON, allows patrons to define their privacy rules (checkout history)','YesNo'),
438
('OpacPrivacy','0',NULL,'if ON, allows patrons to define their privacy rules (reading history)','YesNo'),
439
('OpacPublic','1',NULL,'Turn on/off public OPAC','YesNo'),
439
('OpacPublic','1',NULL,'Turn on/off public OPAC','YesNo'),
440
('opacreadinghistory','1','','If ON, enables display of Patron Circulation History in OPAC','YesNo'),
440
('opacreadinghistory','1','','If ON, enables display of Patron Circulation History in OPAC','YesNo'),
441
('OpacRenewalAllowed','1',NULL,'If ON, users can renew their issues directly from their OPAC account','YesNo'),
441
('OpacRenewalAllowed','0',NULL,'If ON, users can renew their issues directly from their OPAC account','YesNo'),
442
('OpacRenewalBranch','checkoutbranch','itemhomebranch|patronhomebranch|checkoutbranch|none','Choose how the branch for an OPAC renewal is recorded in statistics','Choice'),
442
('OpacRenewalBranch','checkoutbranch','itemhomebranch|patronhomebranch|checkoutbranch|none','Choose how the branch for an OPAC renewal is recorded in statistics','Choice'),
443
('OPACReportProblem', 0, NULL, 'Allow patrons to submit problem reports for OPAC pages to the library or Koha Administrator', 'YesNo'),
443
('OPACReportProblem', 0, NULL, 'Allow patrons to submit problem reports for OPAC pages to the library or Koha Administrator', 'YesNo'),
444
('OpacResetPassword','0','','Shows the ''Forgot your password?'' link in the OPAC','YesNo'),
444
('OpacResetPassword','0','','Shows the ''Forgot your password?'' link in the OPAC','YesNo'),
Lines 721-725 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
721
('XSLTListsDisplay','default','','Enable XSLT stylesheet control over lists pages display on intranet','Free'),
721
('XSLTListsDisplay','default','','Enable XSLT stylesheet control over lists pages display on intranet','Free'),
722
('XSLTResultsDisplay','default','','Enable XSL stylesheet control over results page display on intranet','Free'),
722
('XSLTResultsDisplay','default','','Enable XSL stylesheet control over results page display on intranet','Free'),
723
('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'),
723
('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'),
724
('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo')
724
('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo'),
725
('AutocompleteElasticSearch','0',NULL,NULL,'YesNo')
725
;
726
;
(-)a/koha-tmpl/intranet-tmpl/js/intranet-elasticsearch/intranet-autocomplete.js (+212 lines)
Line 0 Link Here
1
/* Intranet JS file AutocompleteElasticSearch */
2
/* prefix for search ES */
3
var es_prefix = {
4
    'au': 'author',
5
    'pb': 'publisher',
6
    'se': 'title-series',
7
    'su': 'subject',
8
    'ti': 'title',  
9
    /* for all */
10
    '': ['title', 'author', 'subject', 'title-series', 'publisher'], 
11
    'kw': ['title', 'author', 'subject', 'title-series', 'publisher']
12
};
13
14
/* count of lines for autocomplete */
15
var nb_autocomplete = 10;
16
/* key API */
17
var key = 'autocomplete';
18
19
function AutocompleteInitIntranet(){
20
    /* vars for class position absolute autocomplete */
21
    var left = "0px";
22
    var right = "0px";
23
    var top = "";
24
    /* get all input name q for search */ 
25
    var input_q = document.getElementsByName('q');   
26
    for (var nb = 0; nb < input_q.length; nb++){
27
        /* addEventListener for every 'input' */ 
28
        autocomplete(input_q[nb], nb, left, right, top);
29
    }
30
}
31
32
function autocomplete(inp, nb, left, right, top) {
33
    var select_idx = document.getElementsByName('idx');
34
    /* autocomplete off */ 
35
    inp.setAttribute("autocomplete", "off");
36
    /* get parent of input */   
37
    var parent_inp = $(inp).parent();
38
    /* get element after input */   
39
    var next_elem_inp = inp.nextElementSibling;
40
    /* create new div with position relative for class .autocomplete */
41
    var div_relative = document.createElement('div');    
42
    $(div_relative).addClass( "autocomplete" );
43
    div_relative.append(inp);
44
    /* input doesn't have an elem after, add to parent */ 
45
    if (next_elem_inp === null){
46
        parent_inp.append( div_relative ); 
47
    } 
48
    /* input has an elem after, add after elem */ 
49
    else {
50
        next_elem_inp.before(div_relative);
51
    };      
52
    var currentFocus;
53
    /*execute a function when someone writes in the text field:*/
54
    inp.addEventListener("input", function(e) {
55
        var a, b, val = this.value;
56
        /*close any already open lists of autocompleted values*/
57
        closeAllLists();
58
        if (!val) { return false;}
59
        currentFocus = -1;
60
        /*create a DIV element that will contain the items (values):*/
61
        a = document.createElement("DIV");
62
        a.setAttribute("id", this.id + "autocomplete-list");
63
        a.setAttribute("class", "autocomplete-items");
64
        /*append the DIV element as a child of the autocomplete container:*/
65
        this.parentNode.appendChild(a);
66
        /*append position absolute left/right:*/
67
        $(".autocomplete-items").css("left",left);
68
        $(".autocomplete-items").css("right",right);
69
        /* get es_prefix key for builder */
70
        var chose_prefix = (select_idx == null || select_idx.length == 0) ? '' : GetValueIdx(select_idx, nb); 
71
        chose_prefix = chose_prefix.replace(/([^,])[,-]([^,].*)?$/, '$1');         
72
        if (chose_prefix !== null){
73
            /* prefix value for autocomplete */
74
            var prefix = es_prefix[chose_prefix].toString();
75
            $.ajax({
76
                type: 'GET',
77
                url: '/cgi-bin/koha/api/elasticsearch/intranet-autocomplete.pl?q=' + val + '&key=' + key + '&prefix=' + prefix,
78
                success: function (data) {
79
                //console.log(data);
80
                    if (data.length != 0){
81
                        /* Set for autocomplete unique */
82
                        myset = new Set();
83
                        var i,j,k; 
84
                        /* autocomplete for all prefix */                       
85
                        if (chose_prefix === 'kw' || chose_prefix === ''){
86
                            var pref = prefix.split(",");
87
                            for (k = 0; k < data.length; k++){
88
                                for (i = 0; i < data[k].length; i++) {
89
                                    for (j = 0; j < data[k][i]['highlight'][pref[k] + '.' + key].length; j++){
90
                                        /* div with data for autocomplete */
91
                                        myset.add(data[k][i]['highlight'][pref[k] + '.' + key][j]);
92
                                        if (myset.size >= nb_autocomplete) break;
93
                                    }; 
94
                                    if (myset.size >= nb_autocomplete) break;            
95
                                };
96
                                if (myset.size >= nb_autocomplete) break;
97
                            }
98
                        }
99
                        /* autocomplete for one prefix */  
100
                        else {
101
                            for (i = 0; i < data.length; i++) {
102
                                for (j = 0; j < data[i]['highlight'][prefix + '.' + key].length; j++){
103
                                    /* div with data for autocomplete */
104
                                    myset.add(data[i]['highlight'][prefix + '.' + key][j]);
105
                                    if (myset.size >= nb_autocomplete) break;
106
                                }; 
107
                                if (myset.size >= nb_autocomplete) break;            
108
                            };
109
                        };
110
                        /* append set to autocomplete */
111
                        for (let item of myset){
112
                            a.appendChild(CreateDIV(item, val));
113
                        } 
114
                    };
115
                },
116
                error: function (data) {            
117
                    console.log(data);
118
                },
119
            });
120
        };
121
    });
122
    /* get value for tag with name idx */
123
    function GetValueIdx(elem, nb){
124
        switch (elem[0].tagName){
125
            case 'INPUT':
126
                return elem[0].value;
127
            case 'SELECT':
128
                return select_idx[nb].options[select_idx[nb].selectedIndex].value;
129
            default:
130
                return null;
131
        };
132
    };
133
    /*execute a function presses a key on the keyboard:*/
134
    inp.addEventListener("keydown", function(e) {
135
        var x = document.getElementById(this.id + "autocomplete-list");
136
        if (x) x = x.getElementsByTagName("div");
137
        if (e.keyCode == 40) { //DOWN
138
            /*If the arrow DOWN key is pressed,
139
            increase the currentFocus variable:*/
140
            currentFocus++;
141
            /*and and make the current item more visible:*/
142
            addActive(x);
143
        } else if (e.keyCode == 38) { //up
144
            /*If the arrow UP key is pressed,
145
            decrease the currentFocus variable:*/
146
            currentFocus--;
147
            /*and and make the current item more visible:*/
148
            addActive(x);
149
            e.preventDefault();        
150
        } else if (e.keyCode == 13) {
151
            /*If the ENTER key is pressed, prevent the form from being submitted,*/
152
            //e.preventDefault();
153
            if (currentFocus > -1) {
154
                /*and simulate a click on the "active" item:*/
155
                if (x) x[currentFocus].click();
156
            }
157
        } 
158
        /* press Esc clear all autocomplete */
159
        else if (e.keyCode == 27) {
160
            closeAllLists();
161
        };
162
    });
163
    function addActive(x) {
164
        /*a function to classify an item as "active":*/
165
        if (!x) return false;
166
        /*start by removing the "active" class on all items:*/
167
        removeActive(x);
168
        if (currentFocus >= x.length) currentFocus = 0;
169
        if (currentFocus < 0) currentFocus = (x.length - 1);
170
        /*add class "autocomplete-active":*/
171
        x[currentFocus].classList.add("autocomplete-active");
172
        inp.value = (x[currentFocus].textContent.replace(/<\/?[^>]+(>|$)/g, "")).trim();
173
    }
174
    function removeActive(x) {
175
        /*a function to remove the "active" class from all autocomplete items:*/
176
        for (var i = 0; i < x.length; i++) {
177
            x[i].classList.remove("autocomplete-active");
178
        }
179
    }
180
    function closeAllLists(elmnt) {
181
        /*close all autocomplete lists in the document */
182
        var x = document.getElementsByClassName("autocomplete-items");
183
        for (var i = 0; i < x.length; i++) {
184
            x[i].parentNode.removeChild(x[i])
185
        };
186
    };
187
188
    function CreateDIV (elem){
189
        var b = document.createElement("DIV");
190
        // add element ";
191
        b.innerHTML += elem;
192
        /*insert a input field that will hold the current array item's value:*/
193
        b.innerHTML += "<input type='hidden' value='" + elem + "'>";
194
        /*execute a function when someone clicks on the item value (DIV element):*/
195
        b.addEventListener("click", function(e) {
196
            /* insert the value for the autocomplete text field: */
197
            inp.value = this.getElementsByTagName("input")[0].value;
198
            /* normalyzer hightlight without tags */            
199
            inp.value = (inp.value.replace(/<\/?[^>]+(>|$)/g, "")).trim();
200
            /* Submit form click mouse in div */ 
201
            this.closest("form").submit();
202
        });
203
        return b;
204
    };
205
206
    /*execute a function when someone clicks in the document:*/
207
    document.addEventListener("click", function (e) {
208
        closeAllLists(e.target);
209
    });
210
};
211
212
AutocompleteInitIntranet();
(-)a/koha-tmpl/intranet-tmpl/prog/css/intranet-elasticsearch/intranet-autocomplete.css (+28 lines)
Line 0 Link Here
1
.autocomplete {
2
  /*the container must be positioned relative:*/
3
  position: relative;
4
  display: inline-block;
5
}
6
.autocomplete-items {
7
    position: absolute;
8
    border: 1px solid #d4d4d4;
9
    border-bottom: none;
10
    border-top: none;
11
    z-index: 99;
12
    /*position the autocomplete items to be the same width as the container:*/
13
  }
14
  .autocomplete-items div {
15
    padding: 10px;
16
    cursor: pointer;
17
    background-color: #fff;
18
    border-bottom: 1px solid #d4d4d4;
19
  }
20
  .autocomplete-items div:hover {
21
    /*when hovering an item:*/
22
    background-color: #e9e9e9;
23
  }
24
  .autocomplete-active {
25
    /*when navigating through the items using the arrow keys:*/
26
    background-color: #cedfb1 !important;
27
    color: #ffffff;
28
  }
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc (+5 lines)
Lines 21-26 Link Here
21
[% Asset.css("css/print.css", { media = "print" }) | $raw %]
21
[% Asset.css("css/print.css", { media = "print" }) | $raw %]
22
[% INCLUDE intranetstylesheet.inc %]
22
[% INCLUDE intranetstylesheet.inc %]
23
[% IF ( bidi ) %][% Asset.css("css/right-to-left.css") | $raw %][% END %]
23
[% IF ( bidi ) %][% Asset.css("css/right-to-left.css") | $raw %][% END %]
24
<!-- Intranet inc CSS AutocompleteElasticSearch -->
25
[% IF ( Koha.Preference('AutocompleteElasticSearch') ) %]
26
    [% SET Optylesheet = 'intranet-elasticsearch/intranet-autocomplete.css' %]
27
    <link rel="stylesheet" type="text/css" href="[% interface | url %]/[% theme | url %]/css/[% Optylesheet | url %]" />
28
[% END %]
24
29
25
<script>
30
<script>
26
var Koha = {};
31
var Koha = {};
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/js_includes.inc (+21 lines)
Lines 37-42 Link Here
37
37
38
<!-- js_includes.inc -->
38
<!-- js_includes.inc -->
39
[% IF ( virtualshelves || intranetbookbag ) %]
39
[% IF ( virtualshelves || intranetbookbag ) %]
40
    <script>
41
        // virtualshelves || intranetbookbag
42
        var MSG_BASKET_EMPTY = _("Your cart is currently empty");
43
        var MSG_RECORD_IN_BASKET = _("This item is already in your cart");
44
        var MSG_RECORD_ADDED = _("This item has been added to your cart");
45
        var MSG_NRECORDS_ADDED = _("%s item(s) added to your cart");
46
        var MSG_NRECORDS_IN_BASKET = _("%s already in your cart");
47
        var MSG_NO_RECORD_SELECTED = _("No item was selected");
48
        var MSG_NO_RECORD_ADDED = _("No item was added to your cart (already in your cart)!");
49
        var MSG_CONFIRM_DEL_BASKET = _("Are you sure you want to empty your cart?");
50
        var MSG_CONFIRM_DEL_RECORDS = _("Are you sure you want to remove the selected items?");
51
        var MSG_IN_YOUR_CART = _("Items in your cart: %s");
52
        var MSG_ITEM_NOT_IN_CART = _("Add to cart");
53
        var MSG_ITEM_IN_CART = _("In your cart");
54
        var MSG_RECORD_REMOVED = _("The item has been removed from your cart");
55
    </script>
56
40
    [% Asset.js("js/basket.js") | $raw %]
57
    [% Asset.js("js/basket.js") | $raw %]
41
[% END %]
58
[% END %]
42
59
Lines 143-146 Link Here
143
    });
160
    });
144
    </script>
161
    </script>
145
[% END %]
162
[% END %]
163
<!-- Intranet inc JS AutocompleteElasticSearch -->
164
[% IF ( Koha.Preference('AutocompleteElasticSearch') ) %]
165
	[% Asset.js("js/intranet-elasticsearch/intranet-autocomplete.js") %]
166
[% END %]
146
<!-- / js_includes.inc -->
167
<!-- / js_includes.inc -->
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/searching.pref (+9 lines)
Lines 275-277 Searching: Link Here
275
            - LIBRIS base URL
275
            - LIBRIS base URL
276
            - pref: LibrisURL
276
            - pref: LibrisURL
277
            - "Please only change this if you are sure it needs changing."
277
            - "Please only change this if you are sure it needs changing."
278
        -
279
            - pref: AutocompleteElasticSearch
280
              type: boolean
281
              default: no
282
              choices:
283
                  yes: Show
284
                  no: "Don't show"
285
            - looking terms based on a provided text by using an ElasticSearch.
286
        -
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt (-5 / +5 lines)
Lines 94-104 Link Here
94
    <fieldset id="searchterms">
94
    <fieldset id="searchterms">
95
    <legend>Search for </legend>
95
    <legend>Search for </legend>
96
    [% FOREACH search_box IN search_boxes_loop %]
96
    [% FOREACH search_box IN search_boxes_loop %]
97
        [% IF ( search_boxes_label ) %]
97
        [% IF ( search_boxes_label ) %]<div style="text-indent: 4.5em;">[% ELSE %]<div>[% END %]
98
        <div class="search-term-row" style="text-indent: 4.5em;">
99
        [% ELSE %]
100
        <div class="search-term-row">
101
        [% END %]
102
			[% IF ( expanded_options ) %]
98
			[% IF ( expanded_options ) %]
103
            [% IF ( search_box.boolean ) %]
99
            [% IF ( search_box.boolean ) %]
104
                <select name="op">
100
                <select name="op">
Lines 325-330 Link Here
325
            var dad  = line.parentNode;
321
            var dad  = line.parentNode;
326
            dad.appendChild(line.cloneNode(true));
322
            dad.appendChild(line.cloneNode(true));
327
            line.removeChild(ButtonPlus);
323
            line.removeChild(ButtonPlus);
324
            /* Intranet JS AutocompleteElasticSearch */
325
            [% IF ( Koha.Preference('AutocompleteElasticSearch') ) %]
326
                AutocompleteInitIntranet();
327
            [% END %]
328
        }
328
        }
329
        var Sticky;
329
        var Sticky;
330
        $(document).ready(function() {
330
        $(document).ready(function() {
(-)a/koha-tmpl/intranet-tmpl/prog/js/staff-global.js (+6 lines)
Lines 76-81 $.fn.selectTabByID = function (tabID) { Link Here
76
    $(".keep_text").on("click",function(){
76
    $(".keep_text").on("click",function(){
77
        var field_index = $(this).parent().index();
77
        var field_index = $(this).parent().index();
78
        keep_text( field_index );
78
        keep_text( field_index );
79
        /* AutocompleteElasticSearch Tab */
80
        var tab = this.hash.substr(1, this.hash.length-1);
81
        /*  Koha.Preference('AutocompleteElasticSearch') == Show */
82
        if (typeof AutocompleteInitIntranet !== "undefined" && tab === 'catalog_search' ){
83
            AutocompleteInitIntranet();
84
        }
79
    });
85
    });
80
86
81
    $(".toggle_element").on("click",function(e){
87
    $(".toggle_element").on("click",function(e){
(-)a/koha-tmpl/opac-tmpl/bootstrap/css/opac-elasticsearch/opac-autocomplete.css (+29 lines)
Line 0 Link Here
1
/* CSS file AutocompleteElasticSearch */
2
.autocomplete {
3
  /*the container must be positioned relative:*/
4
  position: relative;
5
  display: inline-block;
6
  width: 100%;
7
}
8
.autocomplete-items {
9
    position: absolute;
10
    border: 1px solid #d4d4d4;
11
    border-bottom: none;
12
    border-top: none;
13
    z-index: 99;
14
  }
15
  .autocomplete-items div {
16
    padding: 10px;
17
    cursor: pointer;
18
    background-color: #fff;
19
    border-bottom: 1px solid #d4d4d4;
20
  }
21
  .autocomplete-items div:hover {
22
    /*when hovering an item:*/
23
    background-color: #e9e9e9;
24
  }
25
  .autocomplete-active {
26
    /*when navigating through the items using the arrow keys:*/
27
    background-color: #cedfb1 !important;
28
    color: #ffffff;
29
  }
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/doc-head-close.inc (+5 lines)
Lines 23-28 Link Here
23
        [% SET opaclayoutstylesheet = 'opac.css' %]
23
        [% SET opaclayoutstylesheet = 'opac.css' %]
24
    [% END %]
24
    [% END %]
25
[% END %]
25
[% END %]
26
<!-- OPAC inc CSS AutocompleteElasticSearch -->
27
[% IF ( Koha.Preference('AutocompleteElasticSearch') ) %]
28
    [% SET Optylesheet = 'opac-elasticsearch/opac-autocomplete.css' %]
29
    <link rel="stylesheet" type="text/css" href="[% interface | url %]/[% theme | url %]/css/[% Optylesheet | url %]" />
30
[% END %]
26
[% IF (opaclayoutstylesheet.match('^https?:|^\/')) %]
31
[% IF (opaclayoutstylesheet.match('^https?:|^\/')) %]
27
    <link rel="stylesheet" type="text/css" href="[% opaclayoutstylesheet | url %]" />
32
    <link rel="stylesheet" type="text/css" href="[% opaclayoutstylesheet | url %]" />
28
[% ELSE %]
33
[% ELSE %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-bottom.inc (+5 lines)
Lines 207-212 $.widget.bridge('uitooltip', $.ui.tooltip); Link Here
207
    [% END %]
207
    [% END %]
208
    [% IF OpenLibraryCovers || OpenLibrarySearch %]
208
    [% IF OpenLibraryCovers || OpenLibrarySearch %]
209
        var NO_OL_JACKET = _("No cover image available");
209
        var NO_OL_JACKET = _("No cover image available");
210
        var OL_PREVIEW = _("Preview");
210
    [% END %]
211
    [% END %]
211
    [% IF (query_desc) %]
212
    [% IF (query_desc) %]
212
        var query_desc = "[% query_desc | html %]";
213
        var query_desc = "[% query_desc | html %]";
Lines 323-328 $(document).ready(function() { Link Here
323
        [% Koha.Preference('OPACUserJS') | $raw %]
324
        [% Koha.Preference('OPACUserJS') | $raw %]
324
    </script>
325
    </script>
325
[% END %]
326
[% END %]
327
<!-- OPAC *.inc JS AutocompleteElasticSearch -->
328
[% IF ( Koha.Preference('AutocompleteElasticSearch') ) %]
329
    [% Asset.js("js/opac-elasticsearch/opac-autocomplete.js") %]
330
[% END %]
326
[% IF SCO_login %]
331
[% IF SCO_login %]
327
    [% SET SCOUserJS = Koha.Preference('SCOUserJS') %]
332
    [% SET SCOUserJS = Koha.Preference('SCOUserJS') %]
328
    [% IF ( SCOUserJS ) %]
333
    [% IF ( SCOUserJS ) %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-advsearch.tt (+8 lines)
Lines 526-531 $(document).ready(function() { Link Here
526
        var newLine = thisLine.clone();
526
        var newLine = thisLine.clone();
527
        newLine.find('input').val('');
527
        newLine.find('input').val('');
528
        thisLine.after(newLine);
528
        thisLine.after(newLine);
529
        /* OPAC JS AutocompleteElasticSearch */
530
        [% IF ( Koha.Preference('AutocompleteElasticSearch') ) %]
531
            AutocompleteInitOpac();
532
        [% END %]
529
    });
533
    });
530
534
531
    $(document).on("click", '.ButtonLess', function(e) {
535
    $(document).on("click", '.ButtonLess', function(e) {
Lines 534-539 $(document).ready(function() { Link Here
534
           $('.ButtonLess').hide();
538
           $('.ButtonLess').hide();
535
        }
539
        }
536
        $(this).parent().parent().remove();
540
        $(this).parent().parent().remove();
541
        /* OPAC JS AutocompleteElasticSearch */
542
        [% IF ( Koha.Preference('AutocompleteElasticSearch') ) %]
543
            AutocompleteInitOpac();
544
        [% END %]
537
    });
545
    });
538
546
539
</script>
547
</script>
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/opac-elasticsearch/opac-autocomplete.js (+213 lines)
Line 0 Link Here
1
/* OPAC JS file AutocompleteElasticSearch */
2
/* prefix for search ES */
3
var es_prefix = {
4
    'au': 'author',
5
    'pb': 'publisher',
6
    'se': 'title-series',
7
    'su': 'subject',
8
    'ti': 'title',   
9
    /* for all */
10
    '': ['title', 'author', 'subject', 'title-series', 'publisher'], 
11
    'kw': ['title', 'author', 'subject', 'title-series', 'publisher']
12
};
13
14
/* count of lines for autocomplete */
15
var nb_autocomplete = 10;
16
/* key API */
17
var key = 'autocomplete';
18
19
function AutocompleteInitOpac(){
20
    /* vars for class position absolute autocomplete */
21
    var left = "0px";
22
    var right = "0px";
23
    var top = "";
24
    /* get all input name q for search */ 
25
    var input_q = document.getElementsByName("q");
26
    for (var nb = 0; nb < input_q.length; nb++){
27
        /* addEventListener for every 'input' */ 
28
        autocomplete(input_q[nb], nb, left, right, top);
29
    }
30
}
31
32
function autocomplete(inp, nb, left, right) {
33
    var select_idx = document.getElementsByName("idx");
34
    /* autocomplete off for input */ 
35
    inp.setAttribute("autocomplete", "off");
36
    /* get parent of input */   
37
    var parent_inp = $(inp).parent();
38
    /* get element after input */   
39
    var next_elem_inp = inp.nextElementSibling;
40
    /* create new div with position relative for class .autocomplete with absolute */
41
    var div_relative = document.createElement('div');    
42
    $(div_relative).addClass( "autocomplete" );
43
    div_relative.append(inp);
44
    /* input doesn't have an elem after, add him to parent */ 
45
    if (next_elem_inp === null){
46
        parent_inp.append( div_relative ); 
47
    } 
48
    /* input has an elem after, add him after elem */ 
49
    else {
50
        next_elem_inp.before(div_relative);
51
    };    
52
    var currentFocus;
53
    /*execute a function when someone writes in the text field:*/
54
    inp.addEventListener("input", function(e) {
55
        var a, b, i, val = this.value;
56
        /*close any already open lists of autocompleted values*/
57
        closeAllLists();
58
        if (!val) { return false;}
59
        currentFocus = -1;
60
        /*create a DIV element that will contain the items (values):*/
61
        a = document.createElement("DIV");
62
        a.setAttribute("id", this.id + "autocomplete-list");
63
        a.setAttribute("class", "autocomplete-items");
64
        /*append the DIV element as a child of the autocomplete container:*/
65
        this.parentNode.appendChild(a);
66
        /*append position absolute left/right:*/
67
        $(".autocomplete-items").css("left",left);
68
        $(".autocomplete-items").css("right",right);
69
        /* get es_prefix key for builder */
70
        var chose_prefix = (select_idx == null || select_idx.length == 0) ? '' : GetValueIdx(select_idx, nb); 
71
        chose_prefix = chose_prefix.replace(/([^,])[,-]([^,].*)?$/, '$1');      
72
        if (chose_prefix !== null){
73
            var prefix = es_prefix[chose_prefix].toString();
74
            $.ajax({
75
                type: 'GET',
76
                url: '/cgi-bin/koha/svc/elasticsearch/opac-autocomplete.pl?q=' + val + '&key=' + key + '&prefix=' + prefix,
77
                success: function (data) {
78
                //console.log(data);
79
                    if (data.length != 0){
80
                        /* Set for autocomplete unique */
81
                        myset = new Set();
82
                        var i,j,k; 
83
                        /* autocomplete for all prefix */                       
84
                        if (chose_prefix === 'kw' || chose_prefix === ''){
85
                            var pref = prefix.split(",");
86
                            for (k = 0; k < data.length; k++){
87
                                for (i = 0; i < data[k].length; i++) {
88
                                    for (j = 0; j < data[k][i]['highlight'][pref[k] + '.' + key].length; j++){
89
                                        /* div with data for autocomplete */
90
                                        myset.add(data[k][i]['highlight'][pref[k] + '.' + key][j]);
91
                                        if (myset.size >= nb_autocomplete) break;
92
                                    }; 
93
                                    if (myset.size >= nb_autocomplete) break;            
94
                                };
95
                                if (myset.size >= nb_autocomplete) break;
96
                            }
97
                        }
98
                        /* autocomplete for one prefix */  
99
                        else {
100
                            for (i = 0; i < data.length; i++) {
101
                                for (j = 0; j < data[i]['highlight'][prefix + '.' + key].length; j++){
102
                                    /* div with data for autocomplete */
103
                                    myset.add(data[i]['highlight'][prefix + '.' + key][j]);
104
                                    if (myset.size >= nb_autocomplete) break;
105
                                }; 
106
                                if (myset.size >= nb_autocomplete) break;            
107
                            };
108
                        };
109
                        /* append set to autocomplete */
110
                        for (let item of myset){
111
                            a.appendChild(CreateDIV(item, val));
112
                        } 
113
                    };
114
                },
115
                error: function (data) {            
116
                    console.log(data);
117
                },
118
            });
119
        };
120
    });
121
    /* get value for tag with name idx */
122
    function GetValueIdx(elem, nb){
123
        switch (elem[0].tagName){
124
            case 'INPUT':
125
                return elem[0].value;
126
            case 'SELECT':
127
                return select_idx[nb].options[select_idx[nb].selectedIndex].value;
128
            default:
129
                return null;
130
        };
131
    };
132
    /*execute a function presses a key on the keyboard:*/
133
    inp.addEventListener("keydown", function(e) {
134
        var x = document.getElementById(this.id + "autocomplete-list");
135
        if (x) x = x.getElementsByTagName("div");
136
        if (e.keyCode == 40) { //DOWN
137
            /*If the arrow DOWN key is pressed,
138
            increase the currentFocus variable:*/
139
            currentFocus++;
140
            /*and and make the current item more visible:*/
141
            addActive(x);
142
        } else if (e.keyCode == 38) { //up
143
            /*If the arrow UP key is pressed,
144
            decrease the currentFocus variable:*/
145
            currentFocus--;
146
            /*and and make the current item more visible:*/
147
            addActive(x);
148
            e.preventDefault();        
149
        } else if (e.keyCode == 13) {
150
            /*If the ENTER key is pressed, prevent the form from being submitted,*/
151
            //e.preventDefault();
152
            if (currentFocus > -1) {
153
                /*and simulate a click on the "active" item:*/
154
                if (x) x[currentFocus].click();
155
            }
156
        } 
157
        /* press Esc clear all autocomplete */
158
        else if (e.keyCode == 27) {
159
            closeAllLists();
160
        };
161
    });
162
    function addActive(x) {
163
        /*a function to classify an item as "active":*/
164
        if (!x) return false;
165
        /*start by removing the "active" class on all items:*/
166
        removeActive(x);
167
        if (currentFocus >= x.length) currentFocus = 0;
168
        if (currentFocus < 0) currentFocus = (x.length - 1);
169
        /*add class "autocomplete-active":*/
170
        x[currentFocus].classList.add("autocomplete-active");
171
        inp.value = (x[currentFocus].textContent.replace(/<\/?[^>]+(>|$)/g, "")).trim();
172
    };
173
    function removeActive(x) {
174
        /*a function to remove the "active" class from all autocomplete items:*/
175
        for (var i = 0; i < x.length; i++) {
176
            x[i].classList.remove("autocomplete-active");
177
        };
178
    };
179
180
    function closeAllLists(elmnt) {
181
        /*close all autocomplete lists in the document */
182
        var x = document.getElementsByClassName("autocomplete-items");
183
        for (var i = 0; i < x.length; i++) {
184
            x[i].parentNode.removeChild(x[i])
185
        };
186
    };
187
    
188
    function CreateDIV (elem){
189
        var b = document.createElement("DIV");
190
        // add element ";
191
        b.innerHTML += elem;
192
        /*insert a input field that will hold the current array item's value:*/
193
        b.innerHTML += "<input type='hidden' value='" + elem + "'>";
194
        /*execute a function when someone clicks on the item value (DIV element):*/
195
        b.addEventListener("click", function(e) {
196
            /* insert the value for the autocomplete text field: */
197
            inp.value = this.getElementsByTagName("input")[0].value;
198
            /* normalyzer hightlight without tags */            
199
            inp.value = (inp.value.replace(/<\/?[^>]+(>|$)/g, "")).trim();
200
            /* Submit form click mouse in div */ 
201
            this.closest("form").submit();
202
        });
203
        return b;
204
    };
205
    
206
    /*execute a function when someone clicks in the document:*/
207
    document.addEventListener("click", function (e) {
208
        closeAllLists(e.target);
209
    });
210
211
};
212
213
AutocompleteInitOpac();
(-)a/opac/svc/elasticsearch/opac-autocomplete.pl (-1 / +87 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
use strict;
4
use warnings;
5
use CGI;
6
use JSON;
7
use Koha::SearchEngine::Search;
8
use Switch;
9
my $searcher = Koha::SearchEngine::Search->new({index => 'biblios'});
10
my $cgi = CGI->new;
11
use utf8;
12
use Text::Unaccent;
13
14
my $name_key = "autocomplete";
15
16
#Chose GET in key parametre
17
switch($cgi->param("key")) {
18
  case ($name_key) {
19
    my @prefix = split /,/, $cgi->param("prefix");
20
    # how fields for autocomplete 
21
    my $length = scalar @prefix;
22
    #name analyzer which is in mapping, setting ES
23
    my $analyzer = 'autocomplete';
24
    my @all_prefix;
25
    #search by many prefix fields
26
    if ($length > 1){
27
      foreach my $pref ( @prefix ) {
28
        push(@all_prefix, GetAutocompleteES(unac_string("UTF-8", $cgi->param("q")), $pref, $analyzer));
29
      }
30
      print $cgi->header("application/json");
31
      print to_json(\@all_prefix); 
32
    } 
33
    #search by one prefix field
34
    elsif ($length == 1) {
35
      my $test  = GetAutocompleteES(unac_string("UTF-8", $cgi->param("q")), $cgi->param("prefix"), $analyzer);
36
      print $cgi->header("application/json");
37
      print to_json(GetAutocompleteES(unac_string("UTF-8", $cgi->param("q")), $prefix[0], $analyzer)); 
38
    } 
39
    #no prefix 404
40
    else {
41
      response404JSON();
42
    }
43
  }
44
  #no key 404
45
  else {
46
    response404JSON();
47
  }
48
}
49
50
sub response404JSON {
51
  my $json = JSON->new->utf8;
52
  my $header_type = "application/json";
53
  my $header_status = "404";
54
  my $output = $json->encode({
55
    "error" => "No data",
56
    "description" => "Bad request",
57
  });
58
  print $cgi->header(
59
    -type => $header_type,
60
    -charset => "utf-8",
61
    -status => $header_status
62
  );
63
  print $output;
64
  print "\n";
65
}
66
67
sub GetAutocompleteES {
68
  my ($cgi_q, $prefix, $analyzer) = @_;
69
  my (%query, $results, @source);
70
  #prefix + analyzer
71
  my $prefix_analyzer = $prefix . '.' . $analyzer;  
72
  # we can change this variables
73
  my ($nb_fragments, $size_fragment, $pre_tags, $post_tags) = (3, 50, ["<strong>"], ["</strong>"]);
74
  push(@source, $prefix);
75
  $query{'_source'} = \@source; 
76
  $query{'query'}{'match'}{$prefix_analyzer}{'query'} = $cgi_q;
77
  $query{'query'}{'match'}{$prefix_analyzer}{'operator'} = 'and';
78
  #hightlight
79
  $query{'highlight'}{'number_of_fragments'} = $nb_fragments; 
80
  $query{'highlight'}{'fragment_size'} = $size_fragment; 
81
  $query{'highlight'}{'pre_tags'} = $pre_tags;
82
  $query{'highlight'}{'post_tags'} = $post_tags;
83
  $query{'highlight'}{'fields'}{$prefix_analyzer} = {};
84
  $results = $searcher->search(\%query);
85
86
  return $results->{'hits'}->{'hits'};
87
}

Return to bug 27113