From aad52c74a234952ef9de66ed864bbe73923ff916 Mon Sep 17 00:00:00 2001 From: Ivan Dziuba Date: Tue, 12 Jan 2021 10:22:38 -0500 Subject: [PATCH] Bug 27379 - Autocomplete addresses for a patron using Google Places API (Maps). This patch adds the preference 'GooglePlacesAPI'. You need do ./updatedatabase.pl Now, we can fill API Key Google Places and use autocomplete for addresses. This patch works on the pages: 1. moremember.pl (Add/Edit patron). Field: - Address (Main address, Alternate address, Alternate contact) 2. supplier.pl (Add/Edit vendor). Fields: - Postal address. - Physical address. --- api/api-google-places.pl | 43 +++ ...379_autocomplete_addresses_google_api.perl | 8 + installer/data/mysql/mandatory/sysprefs.sql | 3 +- .../intranet-tmpl/js/api-google-places.js | 252 ++++++++++++++++++ .../prog/en/includes/js_includes.inc | 3 + .../en/modules/admin/preferences/patrons.pref | 6 + 6 files changed, 314 insertions(+), 1 deletion(-) create mode 100755 api/api-google-places.pl create mode 100644 installer/data/mysql/atomicupdate/bug_27379_autocomplete_addresses_google_api.perl create mode 100644 koha-tmpl/intranet-tmpl/js/api-google-places.js diff --git a/api/api-google-places.pl b/api/api-google-places.pl new file mode 100755 index 0000000000..61d2b9cf75 --- /dev/null +++ b/api/api-google-places.pl @@ -0,0 +1,43 @@ +#!/usr/bin/perl + +use strict; use warnings; +use LWP::Simple; +use LWP::UserAgent; +use HTTP::Request::Common; +use C4::Context; +use CGI qw ( -utf8 ); +use CGI::Session; + +my $cgi = CGI->new; +my $session = CGI::Session->new(); +if (defined $cgi->param("lang") && $cgi->param("lang") != "default"){ + $session->param(-name=>'lang', -value=>$cgi->param("lang")); +} else { + $session->param(-name=>'lang', -value=>"en-CA"); +} +$session->expire('+1h'); +my $res = _fetch_content_API_Google_Map($session->param("lang")); + +print $cgi->header( + -title => 'Google API Place', + -type => "text/javascript", + -script => { + -type => "text/javascript", + -code => $res->{'_content'} + } +); + +print $res->{'_content'}; + +sub _fetch_content_API_Google_Map { + my ( $lang) = @_; + my $key = C4::Context->preference('GooglePlacesAPI'); + my $url = "https://maps.googleapis.com/maps/api/js?key=${key}&libraries=places®ion=CA&language=${lang}"; + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + $ua->env_proxy; + my $response = $ua->get($url); + warn "could not retrieve $url" unless $response->content; + return $response; +} +1; \ No newline at end of file diff --git a/installer/data/mysql/atomicupdate/bug_27379_autocomplete_addresses_google_api.perl b/installer/data/mysql/atomicupdate/bug_27379_autocomplete_addresses_google_api.perl new file mode 100644 index 0000000000..019e5de5b0 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_27379_autocomplete_addresses_google_api.perl @@ -0,0 +1,8 @@ +$DBversion = 'XXX'; # will be replaced by the RM +if( CheckVersion( $DBversion ) ) { + # you can use $dbh here like: + $dbh->do(q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('GooglePlacesAPI', '', NULL, NULL, 'Free')}); + + # Always end with this (adjust the bug info) + NewVersion( $DBversion, 27379, "Autocomplete addresses using Google Places (Maps)."); +} diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index 7f0afaa594..38178165ef 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -722,5 +722,6 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('XSLTListsDisplay','default','','Enable XSLT stylesheet control over lists pages display on intranet','Free'), ('XSLTResultsDisplay','default','','Enable XSL stylesheet control over results page display on intranet','Free'), ('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'), -('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo') +('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo'), +('GooglePlacesAPI', '', 'This key must be obtained at https://developers.google.com/places/web-service/get-api-key.', NULL, 'Free') ; diff --git a/koha-tmpl/intranet-tmpl/js/api-google-places.js b/koha-tmpl/intranet-tmpl/js/api-google-places.js new file mode 100644 index 0000000000..7d36c1bf6a --- /dev/null +++ b/koha-tmpl/intranet-tmpl/js/api-google-places.js @@ -0,0 +1,252 @@ +function GoogleMapsInitIntranet(){ + /* Pages pour API GOOGLE*/ + var affectedPages = ['memberentry', 'supplier', 'branches', 'cities']; + + /* page memberentry Adresse principale */ + var inputs_memberenty_main = ['streetnumber', 'address', 'city', 'state', 'zipcode']; + var page_memberenty_main_input = 'address'; + /* page memberentry Autre adresse */ + var inputs_memberenty_alternate = ['B_streetnumber', 'B_address', 'B_city', 'B_state', 'B_country', 'B_zipcode']; + var page_memberenty_alternate_input = 'B_address'; + /* page memberentry Autre contact */ + var inputs_memberenty_contact = ['altcontactaddress1', 'altcontactcity', 'altcontactstate', 'altcontactzipcode']; + var page_memberenty_contact_input = 'altcontactaddress1'; + /* page supplier Adresse postale*/ + var page_supplier_input1 = 'company_postal'; + /* page supplier Adresse physique*/ + var page_supplier_input2 = 'physical'; + /* page supplier ranches */ + var inputs_branches = ['branchaddress1', 'branchcity', 'branchstate', 'branchcountry', 'branchzip']; + var page_branches_input = 'branchaddress1'; + /* page cities */ + var inputs_cities = ['city_name', 'city_state', 'city_country', 'city_zipcode']; + var page_cities_input = 'city_name'; + + // Si on tombe sur une de ces pages + if (affectedPages.indexOf(getPageName()) >= 0) { + + //Dictionaire pour comparer notre form (input->id) avec type API Google + var builder_dictionary = { + /* form_input_id : type_google */ + /* page memberentry Adresse principale */ + 'streetnumber' : ['street_number'], + 'address' : ['route'], + 'city' : ['locality'], + 'state' : ['administrative_area_level_1'], + 'zipcode' : ['postal_code'], + /* page memberentry Autre adresse */ + 'B_streetnumber' : ['street_number'], + 'B_address' : ['route'], + 'B_city' : ['locality'], + 'B_state' : ['administrative_area_level_1'], + 'B_zipcode' : ['postal_code'], + /* page memberentry Autre contact */ + 'altcontactaddress1' : ['street_number','route'], + 'delimiter_altcontactaddress1' : ', ', + 'altcontactcity' : ['locality'], + 'altcontactstate' : ['administrative_area_level_1'], + 'altcontactzipcode' : ['postal_code'] + }; + + // Format de sortie composantes d'une adresse Google Maps pour notre form + var format_api_google = { + 'street_number': 'short_name', + 'route': 'long_name', + 'locality': 'long_name', + 'administrative_area_level_1': 'short_name', + 'country': 'long_name', + 'postal_code': 'short_name', + 'postal_code_prefix': 'short_name' + }; + + /* Pages avec les champes pour changer */ + function initAutocomplete() { + /* Option pour Autocomplete de Google */ + var t = 0; + options = { + types: ['geocode'], + // Localisation de départ près de Montréal pour une recherch + // plus efficace si la géolocalisation est désactivée + bounds: new google.maps.LatLngBounds( + new google.maps.LatLng(45.5053543, -73.733018), + ), + componentRestrictions: {country: ['us', 'ca']} + }; + /* Verifier une page */ + switch (getPageName()) { + case 'memberentry': + initAutocompleteMemberentry(); + break; + case 'supplier': + initAutocompleteSupplier(); + break; + case 'branches': + initAutocompleteBranches(); + break; + case 'cities': + initAutocompleteCities(); + break; + default: + console.log ("This page is " + page); + break; + } + }; + + /* page memberentry */ + function initAutocompleteMemberentry(){ + /* Adresse principale */ + primaryAutocomplete = new google.maps.places.Autocomplete( + /** @type {!HTMLInputElement} */(document.getElementById(page_memberenty_main_input)), options); + // Remplir les champs lorsque l'utilisateur a choisi l'adresse + primaryAutocomplete.addListener('place_changed', + function () { + fillInAddress(primaryAutocomplete, inputs_memberenty_main); + } + ); + $('#'+page_memberenty_main_input).focus( + function () { + primaryAutocomplete + } + ); + /* Autre adresse */ + B_autocomplete = new google.maps.places.Autocomplete( + /** @type {!HTMLInputElement} */(document.getElementById(page_memberenty_alternate_input)), options); + B_autocomplete.addListener('place_changed', + function () { + fillInAddress(B_autocomplete, inputs_memberenty_alternate); + } + ); + $('#'+page_memberenty_alternate_input).focus( + function () { + B_autocomplete; + } + ); + /* Autre contact */ + altcontactaddress1 = new google.maps.places.Autocomplete( + /** @type {!HTMLInputElement} */(document.getElementById(page_memberenty_contact_input)), options); + altcontactaddress1.addListener('place_changed', + function () { + fillInAddress(altcontactaddress1, inputs_memberenty_contact); + } + ); + $('#'+page_memberenty_contact_input).focus( + function () { + B_autocomplete; + } + ); + } + + /* page branches */ + function initAutocompleteBranches(){ + branchAutocomplete = new google.maps.places.Autocomplete( + /** @type {!HTMLInputElement} */(document.getElementById(page_branches_input)), options); + branchAutocomplete.addListener('place_changed', + function () { + fillInAddress(branchAutocomplete, inputs_branches); + } + ); + $('#'+page_branches_input).focus( + function () { + branchAutocomplete + } + ); + } + + /* page cities */ + function initAutocompleteCities(){ + componentPlace = page_cities; + cityTownAutocomplete = new google.maps.places.Autocomplete( + /** @type {!HTMLInputElement} */(document.getElementById(page_cities_input)), options); + cityTownAutocomplete.addListener('place_changed', + function () { + fillInAddress(cityTownAutocomplete, inputs_cities); + } + ); + $('#'+page_cities_input).focus( + function () { + cityTownAutocomplete + } + ); + } + + /* page supplier */ + function initAutocompleteSupplier(){ + postalAutocomplete = new google.maps.places.Autocomplete( + /** @type {!HTMLInputElement} */(document.getElementById(page_supplier_input1)), options); + physicalAutocomplete = new google.maps.places.Autocomplete( + /** @type {!HTMLInputElement} */(document.getElementById(page_supplier_input2)), options); + $('#'+page_supplier_input1).focus( + function () { + postalAutocomplete; + } + ); + $('#'+page_supplier_input2).focus( + function () { + physicalAutocomplete; + } + ); + } + + function fillInAddress(autocomplete, componentPlace) { + //new + // Obtenir les infos de l'adresse + let place = autocomplete.getPlace(); + + //Verifier notre dictionaire + for (let i = 0; i < componentPlace.length; i++) + { + if (componentPlace[i] != null && document.getElementById(componentPlace[i]) != null) + { + let form_input_id = ''; + CompPlace = document.getElementById(componentPlace[i]); + CompPlace.value = ''; + + form_input_id = Object.keys(builder_dictionary).find(key => key === componentPlace[i]); + let type_google = builder_dictionary[form_input_id]; + switch (type_google.length) { + case 1: + for (let j = 0; j < place.address_components.length; j++) { + if (type_google[0] == place.address_components[j].types[0]){ + CompPlace.value = place.address_components[j][format_api_google[type_google[0]]]; + } + } + break; + default: + var str_output = ''; + var delimit = 'delimiter_' + form_input_id; + for (let b = 0; b < type_google.length; b++) { + for (let j = 0; j < place.address_components.length; j++) { + if (type_google[b] == place.address_components[j].types[0]){ + str_output += place.address_components[j][format_api_google[type_google[b]]] + builder_dictionary[delimit]; + } + } + } + str_output = str_output.slice(0, -Math.abs(builder_dictionary[delimit].length)); + CompPlace.value = str_output; + break; + } + } + } + } + + // Chercher le script de l'API + jQuery.ajax({ + url: '/cgi-bin/koha/api/api-google-places.pl?lang=' + $('html').attr('lang'), + dataType: 'script', + success: initAutocomplete, + async: true, + defer: true + }); + } + + //Retourne le nom de la page sans extension et paramètres + function getPageName() { + var url = window.location.pathname; + var index = url.lastIndexOf("/") + 1; + var pageNameWithExtension = url.substr(index); + var pageName = pageNameWithExtension.split(".")[0]; + return pageName; + } +} + +GoogleMapsInitIntranet(); \ No newline at end of file diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/js_includes.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/js_includes.inc index b832eec518..62888fc81e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/js_includes.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/js_includes.inc @@ -143,5 +143,8 @@ } }); +[% IF ( Koha.Preference('GooglePlacesAPI').length > 10) %] + [% Asset.js("js/api-google-places.js") %] +[% END %] [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref index b618fdfe4e..23416a27fe 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref @@ -379,3 +379,9 @@ Patrons: location: "Location" itemcallnumber: "Item's callnumber" ccode: "Collection code" + - + - Autocomplete addresses using Google Places (Maps).
+ - Places API key Google + - pref: GooglePlacesAPI + - Can be obtained at https://developers.google.com/places/web-service/get-api-key. + - -- 2.17.1