Bugzilla – Attachment 31816 Details for
Bug 11401
Add support for Norwegian national library card
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 11401 - Add support for Norwegian national library card
Bug-11401---Add-support-for-Norwegian-national-lib.patch (text/plain), 102.83 KB, created by
Chris Cormack
on 2014-09-23 04:32:58 UTC
(
hide
)
Description:
Bug 11401 - Add support for Norwegian national library card
Filename:
MIME Type:
Creator:
Chris Cormack
Created:
2014-09-23 04:32:58 UTC
Size:
102.83 KB
patch
obsolete
>From c929f9e79d043ebc79e0a47d0473c55946c86aeb Mon Sep 17 00:00:00 2001 >From: Magnus Enger <digitalutvikling@gmail.com> >Date: Wed, 17 Sep 2014 10:16:45 +0200 >Subject: [PATCH] Bug 11401 - Add support for Norwegian national library card > >This patch makes it possible to sync patron data between Koha and the >Norwegian national patron database, in both directions. > >In order to use this, the following information is necessary: >- a username/password from the Norwegian national database of libraries > ("Base Bibliotek"), available to all Norwegian libraries >- a special key in order to decrypt and encrypt PIN-codes/passwords, > which is only available to Norwegian library system vendors >- a norwegian library vendor username/password > >See http://www.lanekortet.no/ for more information (in Norwegian). > >While this is of course an implementation of a specific synchronization scheme >for borrower data, attempts have been made to prepare the ground for other sync >schemes that might be implemented later. Especially the structure of the new >borrower_sync table might be reviewed with an eye to how it might fit other >schemes. > >To test: > >Since the password and cryptographic key needed to use this functionality >is only available to Norwegian library system vendors, only regression testing >can be done on the submitted code. Suggested things to check: > >- Apply the patch and make sure the database update is done. This should add > the new "borrower_sync" table and five new systmpreferences under the > "Patrons" > "Norwegian patron database" category: > - NorwegianPatronDBEnable > - NorwegianPatronDBEndpoint > - NorwegianPatronDBUsername > - NorwegianPatronDBPassword > - NorwegianPatronDBSearchNLAfterLocalHit >- Check that patrons can be created, edited and deleted as usual, when > NorwegianPatronDBEnable is set to "Disable" >- Check that the new tests in t/NorwegianPatronDB.pm run ok, e.g. on a > gitified setup: > $ sudo koha-shell -c "PERL5LIB=/path/to/kohaclone prove -v t/NorwegianPatronDB.t" instancename >- Check that all the other tests still run ok >- Check that the POD in the new files itroduced by this patch looks ok: > - Koha/NorwegianPatronDB.pm > - members/nl-search.pl > - misc/cronjobs/nl-sync-from-koha.pl > - misc/cronjobs/nl-sync-to-koha.pl > - t/NorwegianPatronDB.t > >Note: koha-qa.pl complains about tabs in deletemem.tt, but the lines it > complains about have not been touched by this patch. > >Update 2014-09-18: Rebase on master, split out changes to Koha::Schema, >incorporate new way of authenticating with NL > >Signed-off-by: Chris Cormack <chrisc@catalyst.net.nz> > >Have tested for regressions, none found, will sign off. > >The only suggestion I have is that we could perhaps use Module::Load::Conditional >in C4/Members.pm, so that we don't have to load the module unless we need it. >--- > C4/Installer/PerlDependencies.pm | 25 + > C4/Members.pm | 41 +- > Koha/NorwegianPatronDB.pm | 657 +++++++++++++++++++++ > installer/data/mysql/kohastructure.sql | 18 + > installer/data/mysql/sysprefs.sql | 5 + > installer/data/mysql/updatedatabase.pl | 25 + > .../prog/en/includes/members-toolbar.inc | 48 +- > .../prog/en/includes/nl-search-form.tt | 9 + > .../prog/en/modules/admin/preferences/patrons.pref | 22 + > .../prog/en/modules/members/deletemem.tt | 34 +- > .../prog/en/modules/members/member.tt | 6 + > .../prog/en/modules/members/memberentrygen.tt | 13 + > .../prog/en/modules/members/moremember.tt | 10 + > .../prog/en/modules/members/nl-search.tt | 176 ++++++ > members/deletemem.pl | 25 +- > members/memberentry.pl | 16 +- > members/moremember.pl | 11 + > members/nl-search.pl | 161 +++++ > misc/cronjobs/nl-sync-from-koha.pl | 184 ++++++ > misc/cronjobs/nl-sync-to-koha.pl | 192 ++++++ > t/NorwegianPatronDB.t | 583 ++++++++++++++++++ > 21 files changed, 2242 insertions(+), 19 deletions(-) > create mode 100644 Koha/NorwegianPatronDB.pm > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/nl-search-form.tt > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/members/nl-search.tt > create mode 100755 members/nl-search.pl > create mode 100644 misc/cronjobs/nl-sync-from-koha.pl > create mode 100644 misc/cronjobs/nl-sync-to-koha.pl > create mode 100644 t/NorwegianPatronDB.t > >diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm >index ad1f5a6..58862db 100644 >--- a/C4/Installer/PerlDependencies.pm >+++ b/C4/Installer/PerlDependencies.pm >@@ -742,6 +742,31 @@ our $PERL_DEPS = { > 'required' => '0', > 'min_ver' => '5.836', > }, >+ 'SOAP::Lite' => { >+ 'usage' => 'Norwegian national library card', >+ 'required' => '0', >+ 'min_ver' => '0.712', >+ }, >+ 'Crypt::GCrypt' => { >+ 'usage' => 'Norwegian national library card', >+ 'required' => '0', >+ 'min_ver' => '1.24', >+ }, >+ 'Convert::BaseN' => { >+ 'usage' => 'Norwegian national library card', >+ 'required' => '0', >+ 'min_ver' => '0.01', >+ }, >+ 'Test::DBIx::Class' => { >+ 'usage' => 'Testing suite', >+ 'required' => '0', >+ 'min_ver' => '0.41', >+ }, >+ 'Digest::SHA' => { >+ 'usage' => 'Norwegian national library card', >+ 'required' => '0', >+ 'min_ver' => '5.84_01', >+ }, > }; > > 1; >diff --git a/C4/Members.pm b/C4/Members.pm >index e3a5538..1f427d6 100644 >--- a/C4/Members.pm >+++ b/C4/Members.pm >@@ -33,15 +33,17 @@ use C4::Accounts; > use C4::Biblio; > use C4::Letters; > use C4::SQLHelper qw(InsertInTable UpdateInTable SearchInTable); >-use C4::Members::Attributes qw(SearchIdMatchingAttribute); >+use C4::Members::Attributes qw(SearchIdMatchingAttribute UpdateBorrowerAttribute); > use C4::NewsChannels; #get slip news > use DateTime; > use DateTime::Format::DateParse; >+use Koha::Database; > use Koha::DateUtils; > use Koha::Borrower::Debarments qw(IsDebarred); > use Text::Unaccent qw( unac_string ); > use Koha::AuthUtils qw(hash_password); > use Koha::Database; >+use Koha::NorwegianPatronDB qw( NLUpdateHashedPIN NLEncryptPIN NLSync ); > > our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug); > >@@ -783,6 +785,10 @@ sub ModMember { > if ($data{password} eq '****' or $data{password} eq '') { > delete $data{password}; > } else { >+ if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { >+ # Update the hashed PIN in borrower_sync.hashed_pin, before Koha hashes it >+ NLUpdateHashedPIN( $data{'borrowernumber'}, $data{password} ); >+ } > $data{password} = hash_password($data{password}); > } > } >@@ -803,6 +809,25 @@ sub ModMember { > AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} ); > } > >+ # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a >+ # cronjob will use for syncing with NL >+ if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { >+ my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({ >+ 'synctype' => 'norwegianpatrondb', >+ 'borrowernumber' => $data{'borrowernumber'} >+ }); >+ # Do not set to "edited" if syncstatus is "new". We need to sync as new before >+ # we can sync as changed. And the "new sync" will pick up all changes since >+ # the patron was created anyway. >+ if ( $borrowersync->syncstatus ne 'new' && $borrowersync->syncstatus ne 'delete' ) { >+ $borrowersync->update( { 'syncstatus' => 'edited' } ); >+ } >+ # Set the value of 'sync' >+ $borrowersync->update( { 'sync' => $data{'sync'} } ); >+ # Try to do the live sync >+ NLSync({ 'borrowernumber' => $data{'borrowernumber'} }); >+ } >+ > logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if C4::Context->preference("BorrowersLog"); > } > return $execute_success; >@@ -845,11 +870,25 @@ sub AddMember { > : $patron_category->default_privacy() eq 'never' ? 2 > : $patron_category->default_privacy() eq 'forever' ? 0 > : undef; >+ # Make a copy of the plain text password for later use >+ my $plain_text_password = $data{'password'}; > > # create a disabled account if no password provided > $data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!'; > $data{'borrowernumber'}=InsertInTable("borrowers",\%data); > >+ # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a >+ # cronjob will use for syncing with NL >+ if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { >+ Koha::Database->new->schema->resultset('BorrowerSync')->create({ >+ 'borrowernumber' => $data{'borrowernumber'}, >+ 'synctype' => 'norwegianpatrondb', >+ 'sync' => 1, >+ 'syncstatus' => 'new', >+ 'hashed_pin' => NLEncryptPIN( $plain_text_password ), >+ }); >+ } >+ > # mysql_insertid is probably bad. not necessarily accurate and mysql-specific at best. > logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog"); > >diff --git a/Koha/NorwegianPatronDB.pm b/Koha/NorwegianPatronDB.pm >new file mode 100644 >index 0000000..9d94034 >--- /dev/null >+++ b/Koha/NorwegianPatronDB.pm >@@ -0,0 +1,657 @@ >+package Koha::NorwegianPatronDB; >+ >+# Copyright 2014 Oslo Public Library >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 3 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# with Koha; if not, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+=head1 NAME >+ >+Koha::NorwegianPatronDB >+ >+=head1 SYNOPSIS >+ >+ use Koha::NorwegianPatronDB; >+ >+=head1 FUNCTIONS >+ >+=cut >+ >+use Modern::Perl; >+use C4::Context; >+use C4::Members::Attributes qw( UpdateBorrowerAttribute ); >+use SOAP::Lite; >+use Crypt::GCrypt; >+use Digest::SHA qw( sha256_hex ); >+use Convert::BaseN; >+use DateTime; >+ >+use base 'Exporter'; >+use version; our $VERSION = qv('1.0.0'); >+ >+our %EXPORT_TAGS = ( all => [qw( >+ NLCheckSysprefs >+ NLSearch >+ NLSync >+ NLGetChanged >+ NLMarkForDeletion >+ NLDecodePin >+ NLEncryptPIN >+ NLUpdateHashedPIN >+ NLGetFirstname >+ NLGetSurname >+ NLGetSyncDataFromBorrowernumber >+)] ); >+Exporter::export_ok_tags('all'); >+ >+my $nl_uri = 'http://lanekortet.no'; >+my $nl_proxy = C4::Context->preference("NorwegianPatronDBEndpoint"); >+ >+=head2 SOAP::Transport::HTTP::Client::get_basic_credentials >+ >+This is included to set the username and password used by SOAP::Lite. >+ >+=cut >+ >+sub SOAP::Transport::HTTP::Client::get_basic_credentials { >+ # Library username and password from Base Bibliotek (stored as system preferences) >+ my $library_username = C4::Context->preference("NorwegianPatronDBUsername"); >+ my $library_password = C4::Context->preference("NorwegianPatronDBPassword"); >+ # Vendor username and password (stored in koha-conf.xml) >+ my $vendor_username = C4::Context->config( 'nlvendoruser' ); >+ my $vendor_password = C4::Context->config( 'nlvendorpass' ); >+ # Combine usernames and passwords, and encrypt with SHA256 >+ my $combined_username = "$vendor_username-$library_username"; >+ my $combined_password = sha256_hex( "$library_password-$vendor_password" ); >+ warn "$combined_username => $combined_password"; >+ return $combined_username => $combined_password; >+} >+ >+=head2 NLCheckSysprefs >+ >+Check that sysprefs relevant to NL are set. >+ >+=cut >+ >+sub NLCheckSysprefs { >+ >+ my $response = { >+ 'error' => 0, >+ 'nlenabled' => 0, >+ 'endpoint' => 0, >+ 'userpass' => 0, >+ }; >+ >+ # Check that the Norwegian national paron database is enabled >+ if ( C4::Context->preference("NorwegianPatronDBEnable") == 1 ) { >+ $response->{ 'nlenabled' } = 1; >+ } else { >+ $response->{ 'error' } = 1; >+ } >+ >+ # Check that an endpoint is specified >+ if ( C4::Context->preference("NorwegianPatronDBEndpoint") ne '' ) { >+ $response->{ 'endpoint' } = 1; >+ } else { >+ $response->{ 'error' } = 1; >+ } >+ >+ # Check that the username and password for the patron database is set >+ if ( C4::Context->preference("NorwegianPatronDBUsername") ne '' && C4::Context->preference("NorwegianPatronDBPassword") ne '' ) { >+ $response->{ 'userpass' } = 1; >+ } else { >+ $response->{ 'error' } = 1; >+ } >+ >+ return $response; >+ >+} >+ >+=head2 NLSearch >+ >+Search the NL patron database. >+ >+SOAP call: "hent" (fetch) >+ >+=cut >+ >+sub NLSearch { >+ >+ my ( $identifier ) = @_; >+ >+ my $client = SOAP::Lite >+ ->on_action( sub { return '""';}) >+ ->uri( $nl_uri ) >+ ->proxy( $nl_proxy ); >+ >+ my $id = SOAP::Data->type('string'); >+ $id->name('identifikator'); >+ $id->value( $identifier ); >+ my $som = $client->hent( $id ); >+ >+ return $som; >+ >+} >+ >+=head2 NLSync >+ >+Sync a patron that has been changed or created in Koha "upstream" to NL. >+ >+Input is a hashref with one of two possible elements, either a patron retrieved >+from the database: >+ >+ my $result = NLSync({ 'patron' => $borrower_from_dbic }); >+ >+or a plain old borrowernumber: >+ >+ my $result = NLSync({ 'borrowernumber' => $borrowernumber }); >+ >+In the latter case, this function will retrieve the patron record from the >+database using DBIC. >+ >+Which part of the API is called depends on the value of the "syncstatus" column: >+ >+=over 4 >+ >+=item * B<new> = The I<nyPost> ("new record") method is called. >+ >+=item * B<edited> = The I<endre> ("change/update") method is called. >+ >+=item * B<delete> = The I<slett> ("delete") method is called. >+ >+=back >+ >+Required values for B<new> and B<edited>: >+ >+=over 4 >+ >+=item * sist_endret (last updated) >+ >+=item * adresse, postnr eller sted (address, zip or city) >+ >+=item * fdato (birthdate) >+ >+=item * fnr_hash (social security number, but not hashed...) >+ >+=item * kjonn (gender, M/F) >+ >+=back >+ >+=cut >+ >+sub NLSync { >+ >+ my ( $input ) = @_; >+ >+ my $patron; >+ if ( defined $input->{'borrowernumber'} ) { >+ $patron = Koha::Database->new->schema->resultset('Borrower')->find( $input->{'borrowernumber'} ); >+ } elsif ( defined $input->{'patron'} ) { >+ $patron = $input->{'patron'}; >+ } >+ >+ # There should only be one sync, so we use the first one >+ my @syncs = $patron->borrower_syncs; >+ my $sync; >+ foreach my $this_sync ( @syncs ) { >+ if ( $this_sync->synctype eq 'norwegianpatrondb' ) { >+ $sync = $this_sync; >+ } >+ } >+ >+ my $client = SOAP::Lite >+ ->on_action( sub { return '""';}) >+ ->uri( $nl_uri ) >+ ->proxy( $nl_proxy ); >+ >+ my $cardnumber = SOAP::Data->name( 'lnr' => $patron->cardnumber ); >+ >+ # Call the appropriate method based on syncstatus >+ my $response; >+ if ( $sync->syncstatus eq 'edited' || $sync->syncstatus eq 'new' ) { >+ my $soap_patron = _koha_patron_to_soap( $patron ); >+ if ( $sync->syncstatus eq 'edited' ) { >+ $response = $client->endre( $cardnumber, $soap_patron ); >+ } elsif ( $sync->syncstatus eq 'new' ) { >+ $response = $client->nyPost( $soap_patron ); >+ } >+ } >+ if ( $sync->syncstatus eq 'delete' ) { >+ $response = $client->slett( $cardnumber ); >+ } >+ >+ # Update the sync data according to the results >+ if ( $response->{'status'} && $response->{'status'} == 1 ) { >+ if ( $sync->syncstatus eq 'delete' ) { >+ # Turn off any further syncing >+ $sync->update( { 'sync' => 0 } ); >+ } >+ # Update the syncstatus to 'synced' >+ $sync->update( { 'syncstatus' => 'synced' } ); >+ # Update the 'synclast' attribute with the "server time" ("server_tid") returned by the method >+ $sync->update( { 'lastsync' => $response->{'server_tid'} } ); >+ } >+ return $response; >+ >+} >+ >+=head2 NLGetChanged >+ >+Fetches patrons from NL that have been changed since a given timestamp. This includes >+patrons that have been changed by the library that runs the sync, so we have to >+check which library was the last one to change a patron, before we update patrons >+locally. >+ >+This is supposed to be executed once per night. >+ >+SOAP call: soekEndret >+ >+=cut >+ >+sub NLGetChanged { >+ >+ my ( $from_arg ) = @_; >+ >+ my $client = SOAP::Lite >+ ->on_action( sub { return '""';}) >+ ->uri( $nl_uri ) >+ ->proxy( $nl_proxy ); >+ >+ my $from_string; >+ if ( $from_arg && $from_arg ne '' ) { >+ $from_string = $from_arg; >+ } else { >+ # Calculate 1 second past midnight of the day before >+ my $dt = DateTime->now( time_zone => 'Europe/Oslo' ); >+ $dt->subtract( days => 1 ); >+ my $from = DateTime->new( >+ year => $dt->year(), >+ month => $dt->month(), >+ day => $dt->day(), >+ hour => 00, >+ minute => 00, >+ second => 01, >+ time_zone => 'Europe/Oslo', >+ ); >+ $from_string = $from->ymd . "T" . $from->hms; >+ } >+ >+ my $timestamp = SOAP::Data->name( 'tidspunkt' => $from_string ); >+ my $max_results = SOAP::Data->name( 'max_antall' => 0 ); # 0 = no limit >+ my $start_index = SOAP::Data->name( 'start_indeks' => 0 ); # 1 is the first record >+ >+ # Call the appropriate method based on syncstatus >+ my $som = $client->soekEndret( $timestamp, $max_results, $start_index ); >+ >+ # Extract and massage patron data >+ my $result = $som->result; >+ foreach my $patron ( @{ $result->{'respons_poster'} } ) { >+ # Only handle patrons that have lnr (barcode) and fnr_hash (social security number) >+ # Patrons that lack these two have been deleted from NL >+ if ( $patron->{'lnr'} && $patron->{'fnr_hash'} ) { >+ push @{ $result->{'kohapatrons'} }, _soap_to_kohapatron( $patron ); >+ } >+ } >+ return $result; >+ >+} >+ >+=head2 NLMarkForDeletion >+ >+Mark a borrower for deletion, but do not do the actual deletion. Deleting the >+borrower from NL will be done later by the nl-sync-from-koha.pl script. >+ >+=cut >+ >+sub NLMarkForDeletion { >+ >+ my ( $borrowernumber ) = @_; >+ >+ my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({ >+ 'synctype' => 'norwegianpatrondb', >+ 'borrowernumber' => $borrowernumber, >+ }); >+ return $borrowersync->update( { 'syncstatus' => 'delete' } ); >+ >+} >+ >+=head2 NLDecodePin >+ >+Takes a string encoded with AES/ECB/PKCS5PADDING and a 128-bits key, and returns >+the decoded string as plain text. >+ >+The key needs to be stored in koha-conf.xml, like so: >+ >+<yazgfs> >+ ... >+ <config> >+ ... >+ <nlkey>xyz</nlkey> >+ </config> >+</yazgfs> >+ >+=cut >+ >+sub NLDecodePin { >+ >+ my ( $hash ) = @_; >+ my $key = C4::Context->config( 'nlkey' ); >+ >+ # Convert the hash from Base16 >+ my $cb = Convert::BaseN->new( base => 16 ); >+ my $decoded_hash = $cb->decode( $hash ); >+ >+ # Do the decryption >+ my $cipher = Crypt::GCrypt->new( >+ type => 'cipher', >+ algorithm => 'aes', >+ mode => 'ecb', >+ padding => 'standard', # "This is also known as PKCS#5" >+ ); >+ $cipher->start( 'decrypting' ); >+ $cipher->setkey( $key ); # Must be called after start() >+ my $plaintext = $cipher->decrypt( $decoded_hash ); >+ $plaintext .= $cipher->finish; >+ >+ return $plaintext; >+ >+} >+ >+=head2 NLEncryptPIN >+ >+Takes a plain text PIN as argument, returns the encrypted PIN, according to the >+NL specs. >+ >+ my $encrypted_pin = NLEncryptPIN( $plain_text_pin ); >+ >+=cut >+ >+sub NLEncryptPIN { >+ >+ my ( $pin ) = @_; >+ return _encrypt_pin( $pin ); >+ >+} >+ >+=head2 NLUpdateHashedPIN >+ >+Takes two arguments: >+ >+=over 4 >+ >+=item * Borrowernumber >+ >+=item * Clear text PIN code >+ >+=back >+ >+Hashes the password and saves it in borrower_sync.hashed_pin. >+ >+=cut >+ >+sub NLUpdateHashedPIN { >+ >+ my ( $borrowernumber, $pin ) = @_; >+ my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({ >+ 'synctype' => 'norwegianpatrondb', >+ 'borrowernumber' => $borrowernumber, >+ }); >+ return $borrowersync->update({ 'hashed_pin', _encrypt_pin( $pin ) }); >+ >+} >+ >+=head2 _encrypt_pin >+ >+Takes a plain text PIN and returns the encrypted version, according to the NL specs. >+ >+=cut >+ >+sub _encrypt_pin { >+ >+ my ( $pin ) = @_; >+ my $key = C4::Context->config( 'nlkey' ); >+ >+ # Do the encryption >+ my $cipher = Crypt::GCrypt->new( >+ type => 'cipher', >+ algorithm => 'aes', >+ mode => 'ecb', >+ padding => 'standard', # "This is also known as PKCS#5" >+ ); >+ $cipher->start( 'encrypting' ); >+ $cipher->setkey( $key ); # Must be called after start() >+ my $ciphertext = $cipher->encrypt( $pin ); >+ $ciphertext .= $cipher->finish; >+ >+ # Encode as Bas16 >+ my $cb = Convert::BaseN->new( base => 16 ); >+ my $encoded_ciphertext = $cb->encode( $ciphertext ); >+ >+ return $encoded_ciphertext; >+ >+} >+ >+=head2 NLGetSyncDataFromBorrowernumber >+ >+Takes a borrowernumber as argument, returns a Koha::Schema::Result::BorrowerSync >+object. >+ >+ my $syncdata = NLGetSyncDataFromBorrowernumber( $borrowernumber ); >+ >+=cut >+ >+sub NLGetSyncDataFromBorrowernumber { >+ >+ my ( $borrowernumber ) = @_; >+ my $data = Koha::Database->new->schema->resultset('BorrowerSync')->find({ >+ 'synctype' => 'norwegianpatrondb', >+ 'borrowernumber' => $borrowernumber, >+ }); >+ return $data; >+ >+} >+ >+=head2 NLGetFirstname >+ >+Takes a string like "Surname, Firstname" and returns the "Firstname" part. >+ >+If there is no comma, the string is returned unaltered. >+ >+ my $firstname = NLGetFirstname( $name ); >+ >+=cut >+ >+sub NLGetFirstname { >+ >+ my ( $s ) = @_; >+ my ( $surname, $firstname ) = _split_name( $s ); >+ if ( $surname eq $s ) { >+ return $s; >+ } else { >+ return $firstname; >+ } >+ >+} >+ >+=head2 NLGetSurname >+ >+Takes a string like "Surname, Firstname" and returns the "Surname" part. >+ >+If there is no comma, the string is returned unaltered. >+ >+ my $surname = NLGetSurname( $name ); >+ >+=cut >+ >+sub NLGetSurname { >+ >+ my ( $s ) = @_; >+ my ( $surname, $firstname ) = _split_name( $s ); >+ return $surname; >+ >+} >+ >+=head2 _split_name >+ >+Takes a string like "Surname, Firstname" and returns a list of surname and firstname. >+ >+If there is no comma, the string is returned unaltered. >+ >+ my ( $surname, $firstname ) = _split_name( $name ); >+ >+=cut >+ >+sub _split_name { >+ >+ my ( $s ) = @_; >+ >+ # Return the string if there is no comma >+ unless ( $s =~ m/,/ ) { >+ return $s; >+ } >+ >+ my ( $surname, $firstname ) = split /, /, $s; >+ >+ return ( $surname, $firstname ); >+ >+} >+ >+=head2 _format_soap_error >+ >+Takes a soap result object as input and returns a formatted string containing SOAP error data. >+ >+=cut >+ >+sub _format_soap_error { >+ >+ my ( $result ) = @_; >+ if ( $result ) { >+ return join ', ', $result->faultcode, $result->faultstring, $result->faultdetail; >+ } else { >+ return 'No result'; >+ } >+ >+} >+ >+=head2 _soap_to_koha_patron >+ >+Convert a SOAP object of type "Laaner" into a hash that can be sent to AddMember or ModMember. >+ >+=cut >+ >+sub _soap_to_kohapatron { >+ >+ my ( $soap ) = @_; >+ >+ return { >+ 'cardnumber' => $soap->{ 'lnr' }, >+ 'surname' => NLGetSurname( $soap->{ 'navn' } ), >+ 'firstname' => NLGetFirstname( $soap->{ 'navn' } ), >+ 'sex' => $soap->{ 'kjonn' }, >+ 'dateofbirth' => $soap->{ 'fdato' }, >+ 'address' => $soap->{ 'p_adresse1' }, >+ 'address2' => $soap->{ 'p_adresse2' }, >+ 'zipcode' => $soap->{ 'p_postnr' }, >+ 'city' => $soap->{ 'p_sted' }, >+ 'country' => $soap->{ 'p_land' }, >+ 'b_address' => $soap->{ 'm_adresse1' }, >+ 'b_address2' => $soap->{ 'm_adresse2' }, >+ 'b_zipcode' => $soap->{ 'm_postnr' }, >+ 'b_city' => $soap->{ 'm_sted' }, >+ 'b_country' => $soap->{ 'm_land' }, >+ 'password' => $soap->{ 'pin' }, >+ 'dateexpiry' => $soap->{ 'gyldig_til' }, >+ 'email' => $soap->{ 'epost' }, >+ 'mobile' => $soap->{ 'tlf_mobil' }, >+ 'phone' => $soap->{ 'tlf_hjemme' }, >+ 'phonepro' => $soap->{ 'tlf_jobb' }, >+ '_extra' => { # Data that should not go in the borrowers table >+ 'socsec' => $soap->{ 'fnr_hash' }, >+ 'created' => $soap->{ 'opprettet' }, >+ 'created_by' => $soap->{ 'opprettet_av' }, >+ 'last_change' => $soap->{ 'sist_endret' }, >+ 'last_change_by' => $soap->{ 'sist_endret_av' }, >+ }, >+ }; >+ >+} >+ >+=head2 _koha_patron_to_soap >+ >+Convert a patron (in the form of a Koha::Schema::Result::Borrower) into a SOAP >+object that can be sent to NL. >+ >+=cut >+ >+sub _koha_patron_to_soap { >+ >+ my ( $patron ) = @_; >+ >+ # Extract attributes >+ my $patron_attributes = {}; >+ foreach my $attribute ( $patron->borrower_attributes ) { >+ $patron_attributes->{ $attribute->code->code } = $attribute->attribute; >+ } >+ >+ # There should only be one sync, so we use the first one >+ my @syncs = $patron->borrower_syncs; >+ my $sync = $syncs[0]; >+ >+ # Create SOAP::Data object >+ my $soap_patron = SOAP::Data->name( >+ 'post' => \SOAP::Data->value( >+ SOAP::Data->name( 'lnr' => $patron->cardnumber ), >+ SOAP::Data->name( 'fnr_hash' => $patron_attributes->{ 'fnr' } )->type( 'string' )->type( 'string' ), >+ SOAP::Data->name( 'navn' => $patron->surname . ', ' . $patron->firstname )->type( 'string' ), >+ SOAP::Data->name( 'sist_endret' => $sync->lastsync )->type( 'string' ), >+ SOAP::Data->name( 'kjonn' => $patron->sex )->type( 'string' ), >+ SOAP::Data->name( 'fdato' => $patron->dateofbirth )->type( 'string' ), >+ SOAP::Data->name( 'p_adresse1' => $patron->address )->type( 'string' ), >+ SOAP::Data->name( 'p_adresse2' => $patron->address2 )->type( 'string' ), >+ SOAP::Data->name( 'p_postnr' => $patron->zipcode )->type( 'string' ), >+ SOAP::Data->name( 'p_sted' => $patron->city )->type( 'string' ), >+ SOAP::Data->name( 'p_land' => $patron->country )->type( 'string' ), >+ SOAP::Data->name( 'm_adresse1' => $patron->b_address )->type( 'string' ), >+ SOAP::Data->name( 'm_adresse2' => $patron->b_address2 )->type( 'string' ), >+ SOAP::Data->name( 'm_postnr' => $patron->b_zipcode )->type( 'string' ), >+ SOAP::Data->name( 'm_sted' => $patron->b_city )->type( 'string' ), >+ SOAP::Data->name( 'm_land' => $patron->b_country )->type( 'string' ), >+ # Do not send the PIN code as it has been hashed by Koha, but use the version hashed according to NL >+ SOAP::Data->name( 'pin' => $sync->hashed_pin )->type( 'string' ), >+ SOAP::Data->name( 'gyldig_til' => $patron->dateexpiry )->type( 'string' ), >+ SOAP::Data->name( 'epost' => $patron->email )->type( 'string' ), >+ SOAP::Data->name( 'tlf_mobil' => $patron->mobile )->type( 'string' ), >+ SOAP::Data->name( 'tlf_hjemme' => $patron->phone )->type( 'string' ), >+ SOAP::Data->name( 'tlf_jobb' => $patron->phonepro )->type( 'string' ), >+ ), >+ )->type("Laaner"); >+ >+ return $soap_patron; >+ >+} >+ >+=head1 EXPORT >+ >+None by default. >+ >+=head1 AUTHOR >+ >+Magnus Enger <digitalutvikling@gmail.com> >+ >+=cut >+ >+1; >+ >+__END__ >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index aeeba17..2d5e1d6 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -357,6 +357,24 @@ CREATE TABLE `branch_item_rules` ( -- information entered in the circulation and > ) ENGINE=InnoDB DEFAULT CHARSET=utf8; > > -- >+-- Table structure for table borrower_sync >+-- >+ >+CREATE TABLE borrower_sync ( >+ borrowersyncid int(11) NOT NULL AUTO_INCREMENT, -- Primary key, unique identifier >+ borrowernumber int(11) NOT NULL, -- Connects data about synchronisations to a borrower >+ synctype varchar(32) NOT NULL, -- There could potentially be more than one kind of syncing going on, a text string here can be used to tell them apart. E.g.: The Norwegian national patron database uses 'norwegianpatrondb' in this column >+ sync tinyint(1) NOT NULL DEFAULT '0', -- A boolean (1/0) for turning syncing off and on for individual borrowers >+ syncstatus varchar(10) DEFAULT NULL, -- The sync status for any given borrower. Could be text strings like 'new', 'edited', 'synced' etc. The values used here will depend on the actual syncing being done. >+ lastsync varchar(50) DEFAULT NULL, -- Date of the last successfull sync. The date format might be different depending on the service that is being used, so no special date format is being enforced here. >+ hashed_pin varchar(64) DEFAULT NULL, -- Perhaps specific to The Norwegian national patron database, this column holds a hashed PIN code >+ PRIMARY KEY (borrowersyncid), >+ KEY borrowernumber (borrowernumber), >+ CONSTRAINT borrower_sync_ibfk_1 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE >+) ENGINE=InnoDB DEFAULT CHARSET=utf8 >+ >+ >+-- > -- Table structure for table `branchcategories` > -- > >diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql >index b06b163..88622ea 100644 >--- a/installer/data/mysql/sysprefs.sql >+++ b/installer/data/mysql/sysprefs.sql >@@ -197,6 +197,11 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('noissuescharge','5','','Define maximum amount withstanding before check outs are blocked','Integer'), > ('noItemTypeImages','0',NULL,'If ON, disables item-type images','YesNo'), > ('NoLoginInstructions', '', '60|10', 'Instructions to display on the OPAC login form when a patron is not logged in', 'Textarea'), >+('NorwegianPatronDBEnable','0',NULL,'Enable communication with the Norwegian national patron database.', 'YesNo'), >+('NorwegianPatronDBEndpoint','',NULL,'Which NL endpoint to use.', 'Free'), >+('NorwegianPatronDBUsername','',NULL,'Username for communication with the Norwegian national patron database.','Free'), >+('NorwegianPatronDBPassword','',NULL,'Password for communication with the Norwegian national patron database.','Free'), >+('NorwegianPatronDBSearchNLAfterLocalHit','0',NULL,'Search NL if a search has already given one or more local hits?.','YesNo'), > ('NotesBlacklist','',NULL,'List of notes fields that should not appear in the title notes/description separator of details','free'), > ('NotHighlightedWords','and|or|not',NULL,'List of words to NOT highlight when OpacHitHighlight is enabled','free'), > ('NoticeCSS','',NULL,'Notices CSS url.','free'), >diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl >index aae80f1..753d98c 100755 >--- a/installer/data/mysql/updatedatabase.pl >+++ b/installer/data/mysql/updatedatabase.pl >@@ -8776,6 +8776,31 @@ if ( CheckVersion($DBversion) ) { > SetVersion($DBversion); > } > >+$DBversion = "3.17.00.XXX"; >+if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { >+ $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBEnable', '0', NULL, 'Enable communication with the Norwegian national patron database.', 'YesNo')"); >+ $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBEndpoint', '', NULL, 'Which NL endpoint to use.', 'Free')"); >+ $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBUsername', '', NULL, 'Username for communication with the Norwegian national patron database.', 'Free')"); >+ $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBPassword', '', NULL, 'Password for communication with the Norwegian national patron database.', 'Free')"); >+ $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBSearchNLAfterLocalHit','0',NULL,'Search NL if a search has already given one or more local hits?.','YesNo')"); >+ $dbh->do(" >+CREATE TABLE borrower_sync ( >+ borrowersyncid int(11) NOT NULL AUTO_INCREMENT, >+ borrowernumber int(11) NOT NULL, >+ synctype varchar(32) NOT NULL, >+ sync tinyint(1) NOT NULL DEFAULT '0', >+ syncstatus varchar(10) DEFAULT NULL, >+ lastsync varchar(50) DEFAULT NULL, >+ hashed_pin varchar(64) DEFAULT NULL, >+ PRIMARY KEY (borrowersyncid), >+ KEY borrowernumber (borrowernumber), >+ CONSTRAINT borrower_sync_ibfk_1 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE >+) ENGINE=InnoDB DEFAULT CHARSET=utf8" >+); >+ print "Upgrade to $DBversion done (Bug 11401 - Add support for Norwegian national library card)\n"; >+ SetVersion($DBversion); >+} >+ > =head1 FUNCTIONS > > =head2 TableExists($table) >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc >index caa2fbf..870f254 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc >@@ -1,12 +1,32 @@ >+[% USE Koha %] >+[% SET NorwegianPatronDBEnable = Koha.Preference( 'NorwegianPatronDBEnable' ) %] > <script type="text/javascript"> > //<![CDATA[ > $(document).ready(function(){ > [% IF ( CAN_user_borrowers ) %] >- $("#deletepatron").click(function(){ >+ [% IF ( NorwegianPatronDBEnable == 1 ) %] >+ $("#deletepatronlocal").click(function(){ >+ confirm_local_deletion(); >+ $(".btn-group").removeClass("open"); >+ return false; >+ }); >+ $("#deletepatronremote").click(function(){ >+ confirm_remote_deletion(); >+ $(".btn-group").removeClass("open"); >+ return false; >+ }); >+ $("#deletepatronboth").click(function(){ >+ confirm_both_deletion(); >+ $(".btn-group").removeClass("open"); >+ return false; >+ }); >+ [% ELSE %] >+ $("#deletepatron").click(function(){ > confirm_deletion(); > $(".btn-group").removeClass("open"); > return false; > }); >+ [% END %] > $("#renewpatron").click(function(){ > confirm_reregistration(); > $(".btn-group").removeClass("open"); >@@ -49,6 +69,24 @@ function confirm_deletion() { > window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]'; > } > } >+function confirm_local_deletion() { >+ var is_confirmed = window.confirm(_("Are you sure you want to delete this patron from the local database? This cannot be undone.")); >+ if (is_confirmed) { >+ window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]&deletelocal=true&deleteremote=false'; >+ } >+} >+function confirm_remote_deletion() { >+ var is_confirmed = window.confirm(_("Are you sure you want to delete this patron from the Norwegian national patron database? This cannot be undone.")); >+ if (is_confirmed) { >+ window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]&deletelocal=false&deleteremote=true'; >+ } >+} >+function confirm_both_deletion() { >+ var is_confirmed = window.confirm(_("Are you sure you want to delete this patron both from the local database and from the Norwegian national patron database? This cannot be undone.")); >+ if (is_confirmed) { >+ window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]&deletelocal=true&deleteremote=true'; >+ } >+} > > [% IF ( is_child ) %]function confirm_updatechild() { > var is_confirmed = window.confirm(_("Are you sure you want to update this child to an Adult category? This cannot be undone.")); >@@ -141,7 +179,13 @@ function searchToHold(){ > <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to set permissions" id="patronflags" href="#">Set permissions</a></li> > [% END %] > [% IF ( CAN_user_borrowers ) %] >- <li><a id="deletepatron" href="#">Delete</a></li> >+ [% IF ( NorwegianPatronDBEnable == 1 ) %] >+ <li><a id="deletepatronlocal" href="#">Delete local</a></li> >+ <li><a id="deletepatronremote" href="#">Delete remote</a></li> >+ <li><a id="deletepatronboth" href="#">Delete local and remote</a></li> >+ [% ELSE %] >+ <li><a id="deletepatron" href="#">Delete</a></li> >+ [% END %] > [% ELSE %] > <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to delete patrons" id="deletepatron" href="#">Delete</a></li> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/nl-search-form.tt b/koha-tmpl/intranet-tmpl/prog/en/includes/nl-search-form.tt >new file mode 100644 >index 0000000..a8eeef5 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/nl-search-form.tt >@@ -0,0 +1,9 @@ >+<form method="POST" action="nl-search.pl" autocomplete="off"> <!-- This form will be used for things like social security numbers, so it makes sense not to remember them. --> >+<input type="hidden" name="op" value="search" /> >+ <fieldset> >+ <legend>[% nl_search_form_title %]</legend> >+ <label for="q">Social security or card number: </label> >+ <input type="text" name="q" value="[% q %]"> >+ <input type="submit" value="Search"> >+ </fieldset> >+</form> >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 2469431..b245d1a 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 >@@ -1,4 +1,5 @@ > Patrons: >+ General: > - > - List > - pref: AddPatronLists >@@ -138,3 +139,24 @@ Patrons: > - pref: CardnumberLength > - "characters long. The length can be a single number to specify an exact length, a range separated by a comma (i.e., 'Min,Max'), or a maximum with no minimum (i.e., ',Max')." > - "If 'cardnumber' is included in the BorrowerMandatoryField list, the minimum length, if not specified here, defaults to one." >+ Norwegian patron database: >+ - >+ - pref: NorwegianPatronDBEnable >+ choices: >+ yes: Enable >+ no: Disable >+ - the ability to communicate with the Norwegian national patron database via the >+ - pref: NorwegianPatronDBEndpoint >+ - endpoint. >+ - >+ - Communicate with the Norwegian national patron database using the username >+ - pref: NorwegianPatronDBUsername >+ - and the password >+ - pref: NorwegianPatronDBPassword >+ - . You can get these from "Base Bibliotek", which is maintained by the Norwegian National Library. >+ - >+ - pref: NorwegianPatronDBSearchNLAfterLocalHit >+ choices: >+ yes: Do >+ no: "Don't" >+ - search the Norwegian national patron database after a local search result was found. >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/deletemem.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/deletemem.tt >index 082b066..b100361 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/deletemem.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/deletemem.tt >@@ -13,18 +13,28 @@ > <div id="yui-main"> > <div class="yui-b"> > [% INCLUDE 'members-toolbar.inc' %] >- <div class="dialog alert"> >- <h3>Cannot delete patron</h3> >- <ul>[% IF ( ItemsOnIssues ) %] >- <li>Patron has [% ItemsOnIssues %] item(s) checked out.</li> >- [% END %] >- [% IF ( charges ) %] >- <li>Patron has [% charges %] in fines.</li> >- [% END %] >- [% IF ( guarantees ) %] >- <li>Patron's record has guaranteed accounts attached.</li> >- [% END %]</ul> >+ [% IF ( ItemsOnIssues || charges || guarantees ) %] >+ <div class="dialog alert"> >+ <h3>Cannot delete patron</h3> >+ <ul> >+ [% IF ( ItemsOnIssues ) %] >+ <li>Patron has [% ItemsOnIssues %] item(s) checked out.</li> >+ [% END %] >+ [% IF ( charges ) %] >+ <li>Patron has [% charges %] in fines.</li> >+ [% END %] >+ [% IF ( guarantees ) %] >+ <li>Patron's record has guaranteed accounts attached.</li> >+ [% END %] >+ </ul> > </div> >+ [% END %] >+ [% IF ( keeplocal ) %] >+ <div class="dialog message"> >+ <h3>Remote record deleted, local record kept</h3> >+ <p>Patron was marked for deletion from Norwegian national patron database, but the local record was kept.</p> >+ </div> >+ [% END %] > </div> > </div> > >@@ -32,4 +42,4 @@ > [% INCLUDE 'circ-menu.inc' %] > </div> > </div> >-[% INCLUDE 'intranet-bottom.inc' %] >\ No newline at end of file >+[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt >index f167e32..34d0b7d 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt >@@ -1,3 +1,4 @@ >+[% USE Koha %] > [% INCLUDE 'doc-head-open.inc' %] > <title>Koha › Patrons [% IF ( searching ) %]› Search results[% END %]</title> > [% INCLUDE 'doc-head-close.inc' %] >@@ -293,6 +294,11 @@ function filterByFirstLetterSurname(letter) { > </div> > [% END %] > >+ [% IF Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 %] >+ [% SET nl_search_form_title='Search the Norwegian national patron database' %] >+ [% INCLUDE 'nl-search-form.tt' %] >+ [% END %] >+ > [% INCLUDE 'patron-toolbar.inc' %] > [% IF ( no_add ) %] > <div class="dialog alert"> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt >index 56cc570..96777e1 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt >@@ -1,4 +1,5 @@ > [% IF ( opduplicate ) %][% SET focusAction = "clearDupe" %][% END %] >+[% USE Koha %] > [% USE KohaDates %] > [% INCLUDE 'doc-head-open.inc' %] > <title>Koha › Patrons › >@@ -973,6 +974,18 @@ > [% IF ( mandatorysort2 ) %]<span class="required">Required</span>[% END %] > </li> > [% END %] >+ [% IF ( Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 ) %] >+ <li> >+ <label for="sort2">Sync with the Norwegian national patron database:</label> >+ [% IF ( sync == 0 ) %] >+ <input type="radio" id="sync" name="sync" value="1"> Yes >+ <input type="radio" id="sync" name="sync" value="0" checked> No >+ [% ELSE %] >+ <input type="radio" id="sync" name="sync" value="1" checked> Yes >+ <input type="radio" id="sync" name="sync" value="0"> No >+ [% END %] >+ </li> >+ [% END %] > </ol> > </fieldset> > [% UNLESS nodateenrolled && noopacnote && noborrowernotes %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >index 64f1fe3..454651c 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >@@ -1,3 +1,4 @@ >+[% USE Koha %] > [% USE KohaDates %] > [% INCLUDE 'doc-head-open.inc' %] > <title>Koha › Patrons › >@@ -350,6 +351,15 @@ function validate1(date) { > </li> > [% IF ( borrowernotes ) %]<li><span class="label">Circulation note: </span>[% borrowernotes %]</li>[% END %] > [% IF ( opacnote ) %]<li><span class="label">OPAC note:</span>[% opacnote %]</li>[% END %] >+ [% IF Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 %] >+ [% IF ( sync == 1 ) %] >+ <li><span class="label">Activate sync: </span>Yes</li> >+ [% IF ( syncstatus ) %]<li><span class="label">Sync status: </span>[% syncstatus %]</li>[% END %] >+ [% IF ( lastsync ) %]<li><span class="label">Last sync: </span>[% lastsync | $KohaDates %]</li>[% END %] >+ [% ELSE %] >+ <li><span class="label">Activate sync: </span>No</li> >+ [% END %] >+ [% END %] > </ol> > </div> > </div> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/nl-search.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/nl-search.tt >new file mode 100644 >index 0000000..56cb76e >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/nl-search.tt >@@ -0,0 +1,176 @@ >+[% USE KohaDates %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title>Search the Norwegian national patron database</title> >+[% INCLUDE 'doc-head-close.inc' %] >+</head> >+<body> >+[% INCLUDE 'header.inc' %] >+[% INCLUDE 'patron-search.inc' %] >+ >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/members/members-home.pl">Patrons</a> › Search the Norwegian national patron database</div> >+ >+<div id="doc3" class="yui-t2"> >+ <div id="bd"> >+ <div id="yui-main"> >+ <div class="yui-b"> >+ >+ <h1>Search the Norwegian national patron database</h1> >+ >+ [% IF (error) %] >+ <div class="dialog alert"> >+ [% IF ( error.nlenabled == 0 ) %]<p>You need to activate this function with the NorwegianPatronDBEnable system preference in order to use it.</p>[% END %] >+ [% IF ( error.endpoint == 0 ) %]<p>You need to specify an endpoint with the NorwegianPatronDBEndpoint system preference.</p>[% END %] >+ [% IF ( error.userpass == 0 ) %]<p>You need to fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences in order to use this function.</p>[% END %] >+ [% IF ( error == 'COULD_NOT_ADD_PATRON' ) %]<p>Could not add a new patron.</p>[% END %] >+ </div> >+ [% ELSE %] >+ [% SET nl_search_form_title='Search' %] >+ [% INCLUDE 'nl-search-form.tt' %] >+ [% END %] >+ >+ [% IF ( local_result ) %] >+ <h3>Existing patrons</h3> >+ <ul> >+ [% FOREACH patron IN local_result %] >+ [%# Find the category_type %] >+ [% FOREACH category IN categories %] >+ [% IF category.categorycode == patron.categorycode %] >+ [% patron.category_type = category.category_type %] >+ [% END %] >+ [% END %] >+ <li>[% patron.firstname %] [% patron.surname %] [% patron.cardnumber %] | >+ <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% patron.borrowernumber %]">Details</a> | >+ <a href="/cgi-bin/koha/members/memberentry.pl?op=modify&destination=circ&borrowernumber=[% patron.borrowernumber %]&category_type=[% patron.category_type %]">Edit</a> | >+ <a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=[% patron.borrowernumber %]">Check out</a> >+ </li> >+ [% END %] >+ </ul> >+ [% END %] >+ >+ [% IF ( result ) %] >+ >+ [% IF result.antall_poster_returnert == 0 %] >+ >+ <div class="dialog alert"> >+ <p>No results found in the Norwegian national patron database. Message: "[% result.melding %]"</p> >+ </div> >+ >+ [% ELSE %] >+ >+ <h3>Results from the Norwegian national patron database</h3> >+ <div class="yui-g"> >+ <div class="yui-u first"> >+ [% PROCESS patron_detail p=result.respons_poster.0 %] >+ </div> >+ [% IF ( result.respons_poster.1 ) %] >+ <div class="yui-u"> >+ [% PROCESS patron_detail p=result.respons_poster.1 %] >+ </div> >+ [% END %] >+ </div> >+ >+ [% END %] >+ >+ [% END %] >+ >+ </div> >+ </div> >+ >+ <div class="yui-b"> >+ [% INCLUDE 'circ-menu.inc' %] >+ </div> >+ </div> >+[% INCLUDE 'intranet-bottom.inc' %] >+ >+[% BLOCK patron_detail %] >+<div class="rows"> >+<h4>[% p.navn | html_entity %]</h4> >+<ol> >+[% IF ( p.kjonn ) %]<li><span class="label">kjonn: </span>[% p.kjonn | html_entity %]</li>[% END %] >+[% IF ( p.fdato ) %]<li><span class="label">fdato: </span>[% p.fdato | html_entity %]</li>[% END %] >+[% IF ( p.lnr ) %]<li><span class="label">lnr: </span>[% p.lnr | html_entity %]</li>[% END %] >+[% IF ( p.fnr_hash ) %]<li><span class="label">fnr_hash: </span>[% p.fnr_hash | html_entity %]</li>[% END %] >+ >+[% IF ( p.epost ) %]<li><span class="label">epost: </span>[% p.epost | html_entity %]</li>[% END %] >+[% IF ( p.epost_sjekk ) %]<li><span class="label">epost_sjekk: </span>[% p.epost_sjekk | html_entity %]</li>[% END %] >+ >+[% IF ( p.tlf_mobil ) %]<li><span class="label">tlf_mobil: </span>[% p.tlf_mobil | html_entity %]</li>[% END %] >+[% IF ( p.tlf_hjemme ) %]<li><span class="label">tlf_hjemme: </span>[% p.tlf_hjemme | html_entity %]</li>[% END %] >+[% IF ( p.tlf_jobb ) %]<li><span class="label">tlf_jobb: </span>[% p.tlf_jobb | html_entity %]</li>[% END %] >+ >+[% IF ( p.p_adresse1 ) %]<li><span class="label">p_adresse1: </span>[% p.p_adresse1 | html_entity %]</li>[% END %] >+[% IF ( p.p_adresse2 ) %]<li><span class="label">p_adresse2: </span>[% p.p_adresse2 | html_entity %]</li>[% END %] >+[% IF ( p.p_postnr ) %]<li><span class="label">p_postnr: </span>[% p.p_postnr | html_entity %]</li>[% END %] >+[% IF ( p.p_sted ) %]<li><span class="label">p_sted: </span>[% p.p_sted | html_entity %]</li>[% END %] >+[% IF ( p.p_land ) %]<li><span class="label">p_land: </span>[% p.p_land | html_entity %]</li>[% END %] >+[% IF ( p.p_sjekk ) %]<li><span class="label">p_sjekk: </span>[% p.p_sjekk | html_entity %]</li>[% END %] >+ >+[% IF ( p.m_adresse1 ) %]<li><span class="label">m_adresse1: </span>[% p.m_adresse1 | html_entity %]</li>[% END %] >+[% IF ( p.m_adresse2 ) %]<li><span class="label">m_adresse2: </span>[% p.m_adresse2 | html_entity %]</li>[% END %] >+[% IF ( p.m_postnr ) %]<li><span class="label">m_postnr: </span>[% p.m_postnr | html_entity %]</li>[% END %] >+[% IF ( p.m_sted ) %]<li><span class="label">m_sted: </span>[% p.m_sted | html_entity %]</li>[% END %] >+[% IF ( p.m_land ) %]<li><span class="label">m_land: </span>[% p.m_land | html_entity %]</li>[% END %] >+[% IF ( p.m_sjek ) %]<li><span class="label">m_sjekk: </span>[% p.m_sjekk | html_entity %]</li>[% END %] >+[% IF ( p.m_gyldig_til ) %]<li><span class="label">m_gyldig_til: </span>[% p.m_gyldig_til | html_entity %]</li>[% END %] >+ >+[% IF ( p.pin ) %]<li><span class="label">pin: </span>[% p.pin | html_entity %]</li>[% END %] >+[% IF ( p.passord ) %]<li><span class="label">passsord: </span>[% p.passord | html_entity %]</li>[% END %] >+[% IF ( p.feide ) %]<li><span class="label">feide: </span>[% p.feide | html_entity %]</li>[% END %] >+[% IF ( p.folkeregsjekk_dato ) %]<li><span class="label">folkeregsjekk_dato: </span>[% p.folkeregsjekk_dato | html_entity %]</li>[% END %] >+ >+[% IF ( p.hjemmebibliotek ) %]<li><span class="label">hjemmebibliotek: </span>[% p.hjemmebibliotek | html_entity %]</li>[% END %] >+[% IF ( p.opprettet ) %]<li><span class="label">opprettet: </span>[% p.opprettet | html_entity %]</li>[% END %] >+[% IF ( p.opprettet_av ) %]<li><span class="label">opprettet_av: </span>[% p.opprettet_av | html_entity %]</li>[% END %] >+[% IF ( p.sist_endret ) %]<li><span class="label">sist_endret: </span>[% p.sist_endret | html_entity %]</li>[% END %] >+[% IF ( p.sist_endret_av ) %]<li><span class="label">sist_endret_av: </span>[% p.sist_endret_av | html_entity %]</li>[% END %] >+[% IF ( p.gyldig_til ) %]<li><span class="label">gyldig_til: </span>[% p.gyldig_til | html_entity %]</li>[% END %] >+ >+[% IF ( p.prim_kontakt ) %]<li><span class="label">prim_kontakt: </span>[% p.prim_kontakt | html_entity %]</li>[% END %] >+</ol> >+ >+<form action="nl-search.pl" method="POST"> >+<input type="hidden" name="op" value="save" /> >+<input type="hidden" name="navn" value="[% p.navn | html_entity %]" /> >+<input type="hidden" name="kjonn" value="[% p.kjonn | html_entity %]" /> >+<input type="hidden" name="fdato" value="[% p.fdato | html_entity %]" /> >+<input type="hidden" name="lnr" value="[% p.lnr | html_entity %]" /> >+<input type="hidden" name="fnr_hash" value="[% p.fnr_hash | html_entity %]" /> >+<input type="hidden" name="p_adresse1" value="[% p.p_adresse1 | html_entity %]" /> >+<input type="hidden" name="p_adresse2" value="[% p.p_adresse2 | html_entity %]" /> >+<input type="hidden" name="p_postnr" value="[% p.p_postnr | html_entity %]" /> >+<input type="hidden" name="p_sted" value="[% p.p_sted | html_entity %]" /> >+<input type="hidden" name="p_land" value="[% p.p_land | html_entity %]" /> >+<input type="hidden" name="p_sjekk" value="[% p.p_sjekk | html_entity %]" /> >+<input type="hidden" name="m_adresse1" value="[% p.m_adresse1 | html_entity %]" /> >+<input type="hidden" name="m_adresse2" value="[% p.m_adresse2 | html_entity %]" /> >+<input type="hidden" name="m_postnr" value="[% p.m_postnr | html_entity %]" /> >+<input type="hidden" name="m_sted" value="[% p.m_sted | html_entity %]" /> >+<input type="hidden" name="m_land" value="[% p.m_land | html_entity %]" /> >+<input type="hidden" name="m_sjekk" value="[% p.m_sjekk | html_entity %]" /> >+<input type="hidden" name="m_gyldig_til" value="[% p.m_gyldig_til | html_entity %]" /> >+<input type="hidden" name="pin" value="[% p.pin %]" /> >+<input type="hidden" name="passord" value="[% p.passord | html_entity %]" /> >+<input type="hidden" name="feide" value="[% p.feide | html_entity %]" /> >+<input type="hidden" name="folkeregsjekk_dato" value="[% p.folkeregsjekk_dato | html_entity %]" /> >+<input type="hidden" name="hjemmebibliotek" value="[% p.hjemmebibliotek | html_entity %]" /> >+<input type="hidden" name="opprettet" value="[% p.opprettet | html_entity %]" /> >+<input type="hidden" name="opprettet_av" value="[% p.opprettet_av | html_entity %]" /> >+<input type="hidden" name="sist_endret" value="[% p.sist_endret | html_entity %]" /> >+<input type="hidden" name="sist_endret_av" value="[% p.sist_endret_av | html_entity %]" /> >+<input type="hidden" name="gyldig_til" value="[% p.gyldig_til | html_entity %]" /> >+<input type="hidden" name="epost" value="[% p.epost | html_entity %]" /> >+<input type="hidden" name="epost_sjekk" value="[% p.epost_sjekk | html_entity %]" /> >+<input type="hidden" name="tlf_mobil" value="[% p.tlf_mobil | html_entity %]" /> >+<input type="hidden" name="tlf_hjemme" value="[% p.tlf_hjemme | html_entity %]" /> >+<input type="hidden" name="tlf_jobb" value="[% p.tlf_jobb | html_entity %]" /> >+<input type="hidden" name="prim_kontakt" value="[% p.prim_kontakt | html_entity %]" /> >+<input type="submit" value="Import this patron" /> >+as >+<select name="categorycode"> >+[% FOREACH c IN categories %] >+ <option value="[% c.categorycode %]">[% c.description %]</option> >+[% END %] >+</select> >+</form> >+</div> >+[% END %] >diff --git a/members/deletemem.pl b/members/deletemem.pl >index 32801e0..dec9005 100755 >--- a/members/deletemem.pl >+++ b/members/deletemem.pl >@@ -31,6 +31,7 @@ use C4::Auth; > use C4::Members; > use C4::Branch; # GetBranches > use C4::VirtualShelves (); #no import >+use Koha::NorwegianPatronDB qw( NLMarkForDeletion NLSync ); > > my $input = new CGI; > >@@ -44,7 +45,22 @@ my ($template, $borrowernumber, $cookie) > }); > > #print $input->header; >-my $member=$input->param('member'); >+my $member = $input->param('member'); >+ >+# Handle deletion from the Norwegian national patron database, if it is enabled >+# If the "deletelocal" parameter is set to "false", the regular deletion will be >+# short circuited, and only a deletion from the national database can be carried >+# out. If "deletelocal" is set to "true", or not set to anything normal >+# deletion will be done. >+my $deletelocal = $input->param('deletelocal') eq 'false' ? 0 : 1; # Deleting locally is the default >+if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { >+ if ( $input->param('deleteremote') eq 'true' ) { >+ # Mark for deletion, then try a live sync >+ NLMarkForDeletion( $member ); >+ NLSync({ 'borrowernumber' => $member }); >+ } >+} >+ > my $issues = GetPendingIssues($member); # FIXME: wasteful call when really, we only want the count > my $countissues = scalar(@$issues); > >@@ -80,7 +96,7 @@ my $dbh = C4::Context->dbh; > my $sth=$dbh->prepare("Select * from borrowers where guarantorid=?"); > $sth->execute($member); > my $data=$sth->fetchrow_hashref; >-if ($countissues > 0 or $flags->{'CHARGES'} or $data->{'borrowernumber'}){ >+if ($countissues > 0 or $flags->{'CHARGES'} or $data->{'borrowernumber'} or $deletelocal == 0){ > # print $input->header; > > my ($picture, $dberror) = GetPatronImage($bor->{'borrowernumber'}); >@@ -112,9 +128,12 @@ if ($countissues > 0 or $flags->{'CHARGES'} or $data->{'borrowernumber'}){ > if ($flags->{'CHARGES'} ne '') { > $template->param(charges => $flags->{'CHARGES'}->{'amount'}); > } >- if ($data) { >+ if ($data->{'borrowernumber'}) { > $template->param(guarantees => 1); > } >+ if ($deletelocal == 0) { >+ $template->param(keeplocal => 1); >+ } > output_html_with_http_headers $input, $cookie, $template->output; > > } else { >diff --git a/members/memberentry.pl b/members/memberentry.pl >index 3c7f648..b887cb2 100755 >--- a/members/memberentry.pl >+++ b/members/memberentry.pl >@@ -43,6 +43,7 @@ use C4::Branch; # GetBranches > use C4::Form::MessagingPreferences; > use Koha::Borrower::Debarments; > use Koha::DateUtils; >+use Koha::NorwegianPatronDB qw(NLGetSyncDataFromBorrowernumber); > > use vars qw($debug); > >@@ -416,12 +417,16 @@ if ((!$nok) and $nodouble and ($op eq 'insert' or $op eq 'save')){ > if (C4::Context->preference('EnhancedMessagingPreferences') and $input->param('setting_messaging_prefs')) { > C4::Form::MessagingPreferences::handle_form_action($input, { borrowernumber => $borrowernumber }, $template, 1, $newdata{'categorycode'}); > } >+ # Try to do the live sync with the Norwegian national patron database, if it is enabled >+ if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { >+ NLSync({ 'borrowernumber' => $borrowernumber }); >+ } > } elsif ($op eq 'save'){ > if ($NoUpdateLogin) { > delete $newdata{'password'}; > delete $newdata{'userid'}; > } >- &ModMember(%newdata) unless scalar(keys %newdata) <= 1; # bug 4508 - avoid crash if we're not >+ &ModMember(%newdata) unless scalar(keys %newdata) <= 1; # bug 4508 - avoid crash if we're not > # updating any columns in the borrowers table, > # which can happen if we're only editing the > # patron attributes or messaging preferences sections >@@ -470,6 +475,15 @@ if ($op eq "modify") { > if ( $step == 4 ) { > $template->param( categorycode => $borrower_data->{'categorycode'} ); > } >+ # Add sync data to the user data >+ if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { >+ my $sync = NLGetSyncDataFromBorrowernumber( $borrowernumber ); >+ if ( $sync ) { >+ $template->param( >+ sync => $sync->sync, >+ ); >+ } >+ } > } > if ( $op eq "duplicate" ) { > $template->param( updtype => 'I' ); >diff --git a/members/moremember.pl b/members/moremember.pl >index b1df1dd..80a4cae 100755 >--- a/members/moremember.pl >+++ b/members/moremember.pl >@@ -53,6 +53,7 @@ use C4::Form::MessagingPreferences; > use List::MoreUtils qw/uniq/; > use C4::Members::Attributes qw(GetBorrowerAttributes); > use Koha::Borrower::Debarments qw(GetDebarments IsDebarred); >+use Koha::NorwegianPatronDB qw(NLGetSyncDataFromBorrowernumber); > #use Smart::Comments; > #use Data::Dumper; > use DateTime; >@@ -290,6 +291,16 @@ else { > } > } > >+# Add sync data to the user data >+if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) { >+ my $sync = NLGetSyncDataFromBorrowernumber( $borrowernumber ); >+ if ( $sync ) { >+ $data->{'sync'} = $sync->sync; >+ $data->{'syncstatus'} = $sync->syncstatus; >+ $data->{'lastsync'} = $sync->lastsync; >+ } >+} >+ > # check to see if patron's image exists in the database > # basically this gives us a template var to condition the display of > # patronimage related interface on >diff --git a/members/nl-search.pl b/members/nl-search.pl >new file mode 100755 >index 0000000..e9bee0b >--- /dev/null >+++ b/members/nl-search.pl >@@ -0,0 +1,161 @@ >+#!/usr/bin/perl >+ >+# Copyright 2013 Oslo Public Library >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+=head1 NAME >+ >+nl-search.pl - Script for searching the Norwegian national patron database >+ >+=head1 DESCRIPTION >+ >+This script will search the Norwegian national patron database, and let staff >+import patrons from the natial database into the local database. >+ >+In order to use this, a username/password from the Norwegian national database >+of libraries ("Base Bibliotek") is needed. A special key is also needed, in >+order to decrypt and encrypt PIN-codes/passwords. >+ >+See http://www.lanekortet.no/ for more information (in Norwegian). >+ >+=cut >+ >+use Modern::Perl; >+use CGI; >+use C4::Auth; >+use C4::Category; >+use C4::Context; >+use C4::Output; >+use C4::Members; >+use C4::Members::Attributes qw( SetBorrowerAttributes ); >+use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLSearch NLDecodePin NLGetFirstname NLGetSurname NLSync ); >+use Koha::Database; >+use Koha::DateUtils; >+ >+my $cgi = CGI->new; >+my $dbh = C4::Context->dbh; >+my $op = $cgi->param('op'); >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user( >+ { >+ template_name => "members/nl-search.tt", >+ query => $cgi, >+ type => "intranet", >+ authnotrequired => 0, >+ flagsrequired => { borrowers => 1 }, >+ debug => 1, >+ } >+); >+ >+my $userenv = C4::Context->userenv; >+ >+# Check sysprefs >+my $check_result = NLCheckSysprefs(); >+if ( $check_result->{'error'} == 1 ) { >+ $template->param( 'error' => $check_result ); >+ output_html_with_http_headers $cgi, $cookie, $template->output; >+ exit 0; >+} >+ >+if ( $op && $op eq 'search' ) { >+ >+ # Get the string we are searching for >+ my $identifier = $cgi->param('q'); >+ if ( $identifier ) { >+ # Local search >+ my @local_results = Search( $identifier ); >+ $template->param( 'local_result' => @local_results ); >+ # Search NL, unless we got at least one hit and further searching is >+ # disabled >+ if ( scalar @local_results == 0 || C4::Context->preference("NorwegianPatronDBSearchNLAfterLocalHit") == 1 ) { >+ # TODO Check the format of the identifier before searching NL >+ my $result = NLSearch( $identifier ); >+ unless ($result->fault) { >+ my $r = $result->result(); >+ # Send the data to the template >+ my @categories = C4::Category->all; >+ $template->param( >+ 'result' => $r, >+ 'categories' => \@categories, >+ ); >+ } else { >+ $template->param( 'error' => join ', ', $result->faultcode, $result->faultstring, $result->faultdetail ); >+ } >+ } >+ $template->param( 'q' => $identifier ); >+ } >+ >+} elsif ( $op && $op eq 'save' ) { >+ >+ # This is where we map from fields in NL to fields in Koha >+ my %borrower = ( >+ 'surname' => NLGetSurname( $cgi->param('navn') ), >+ 'firstname' => NLGetFirstname( $cgi->param('navn') ), >+ 'sex' => $cgi->param('kjonn'), >+ 'dateofbirth' => $cgi->param('fdato'), >+ 'cardnumber' => $cgi->param('lnr'), >+ 'userid' => $cgi->param('lnr'), >+ 'address' => $cgi->param('p_adresse1'), >+ 'address2' => $cgi->param('p_adresse2'), >+ 'zipcode' => $cgi->param('p_postnr'), >+ 'city' => $cgi->param('p_sted'), >+ 'country' => $cgi->param('p_land'), >+ 'B_address' => $cgi->param('m_adresse1'), >+ 'B_address2' => $cgi->param('m_adresse2'), >+ 'B_zipcode' => $cgi->param('m_postnr'), >+ 'B_city' => $cgi->param('m_sted'), >+ 'B_country' => $cgi->param('m_land'), >+ 'password' => NLDecodePin( $cgi->param('pin') ), >+ 'dateexpiry' => $cgi->param('gyldig_til'), >+ 'email' => $cgi->param('epost'), >+ 'mobile' => $cgi->param('tlf_mobil'), >+ 'phone' => $cgi->param('tlf_hjemme'), >+ 'phonepro' => $cgi->param('tlf_jobb'), >+ 'branchcode' => $userenv->{'branch'}, >+ 'categorycode' => $cgi->param('categorycode'), >+ ); >+ # Add the new patron >+ my $borrowernumber = &AddMember(%borrower); >+ if ( $borrowernumber ) { >+ # Add extended patron attributes >+ SetBorrowerAttributes($borrowernumber, [ >+ { code => 'fnr', value => $cgi->param('fnr_hash') }, >+ ] ); >+ # Override the default sync data created by AddMember >+ my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({ >+ 'synctype' => 'norwegianpatrondb', >+ 'borrowernumber' => $borrowernumber, >+ }); >+ $borrowersync->update({ 'syncstatus', 'synced' }); >+ $borrowersync->update({ 'lastsync', $cgi->param('sist_endret') }); >+ $borrowersync->update({ 'hashed_pin', $cgi->param('pin') }); >+ # Try to sync in real time. If this fails it will be picked up by the cronjob >+ NLSync({ 'borrowernumber' => $borrowernumber }); >+ # Redirect to the edit screen >+ print $cgi->redirect( "/cgi-bin/koha/members/memberentry.pl?op=modify&destination=circ&borrowernumber=$borrowernumber" ); >+ } else { >+ $template->param( 'error' => 'COULD_NOT_ADD_PATRON' ); >+ } >+} >+ >+output_html_with_http_headers $cgi, $cookie, $template->output; >+ >+=head1 AUTHOR >+ >+Magnus Enger <digitalutvikling@gmail.com> >+ >+=cut >diff --git a/misc/cronjobs/nl-sync-from-koha.pl b/misc/cronjobs/nl-sync-from-koha.pl >new file mode 100644 >index 0000000..f62fd7a >--- /dev/null >+++ b/misc/cronjobs/nl-sync-from-koha.pl >@@ -0,0 +1,184 @@ >+#!/usr/bin/perl >+ >+# Copyright 2014 Oslo Public Library >+ >+=head1 NAME >+ >+nl-sync-from-koha.pl - Sync patrons from Koha to the Norwegian national patron database (NL). >+ >+=head1 SYNOPSIS >+ >+ perl nl-sync-from-koha.pl -v --run >+ >+=cut >+ >+use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLSync ); >+use Koha::Database; >+use Getopt::Long; >+use Pod::Usage; >+use Modern::Perl; >+ >+# Get options >+my ( $run, $verbose, $debug ) = get_options(); >+ >+=head1 ACTIONS >+ >+=head2 >+ >+Find local patrons that have been changed and need to be sent upstream to NL. >+These patrons will be distinguished by two borrower attributes: >+ >+=over 4 >+ >+=item * The "nlstatus" attribute will have a value of "needsync". (Which means >+that the patron has been changed in Koha, but not yet successfully synced >+upstream.) >+ >+=item * The "nlsync" attribute will have a value of 1. (Which means that this >+patron has accepted to be synced with NL, as opposed to a value of 0 which >+would indicate that the patron has asked not to be synced with NL.) >+ >+=back >+ >+=head1 STEPS >+ >+This script performs the following steps: >+ >+=head2 Check sysprefs >+ >+=cut >+ >+my $check_result = NLCheckSysprefs(); >+if ( $check_result->{'error'} == 1 ) { >+ if ( $check_result->{'nlenabled'} == 0 ) { say "* Please activate this function with the NorwegianPatronDBEnable system preference." }; >+ if ( $check_result->{'endpoint'} == 0 ) { say "* Please specify an endpoint with the NorwegianPatronDBEndpoint system preference." }; >+ if ( $check_result->{'userpass'} == 0 ) { say "* Please fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences." }; >+ exit 0; >+} >+ >+unless ( $run ) { >+ say "* You have not specified --run, no real syncing will be done."; >+} >+ >+=head2 Find patrons that need to be synced >+ >+=cut >+ >+my @needs_sync = Koha::Database->new->schema->resultset('BorrowerSync')->search({ >+ -and => [ >+ sync => 1, >+ synctype => 'norwegianpatrondb', >+ -or => [ >+ syncstatus => 'edited', >+ syncstatus => 'new', >+ syncstatus => 'delete', >+ ], >+ ], >+}); >+ >+=head2 Do the actual sync >+ >+=cut >+ >+my $sync_success = 0; >+my $sync_failed = 0; >+foreach my $borrower ( @needs_sync ) { >+ my $cardnumber = $borrower->borrowernumber->cardnumber; >+ my $firstname = $borrower->borrowernumber->firstname; >+ my $surname = $borrower->borrowernumber->surname; >+ my $syncstatus = $borrower->syncstatus; >+ say "*** Syncing patron: $cardnumber - $firstname $surname ($syncstatus)" if $verbose; >+ if ( $run ) { >+ my $response = NLSync({ 'patron' => $borrower->borrowernumber }); >+ if ( $response ) { >+ my $result = $response->result; >+ if ( $result->{'status'} && $result->{'status'} == 1 ) { >+ $sync_success++; >+ } else { >+ $sync_failed++; >+ } >+ if ( $result->{'melding'} && $verbose ) { >+ say $result->{'melding'}; >+ } >+ } >+ } >+} >+ >+=head2 Summarize if verbose mode is enabled >+ >+=cut >+ >+if ( $verbose ) { >+ say "-----------------------------"; >+ say "Sync succeeded: $sync_success"; >+ say "Sync failed : $sync_failed"; >+} >+ >+=head1 OPTIONS >+ >+=over 4 >+ >+=item B<-r, --run> >+ >+Actually carry out syncing operations. Without this option, the script will >+only report what it would have done, but not change any data, locally or >+remotely. >+ >+=item B<-v --verbose> >+ >+Report on the progress of the script. >+ >+=item B<-d --debug> >+ >+Even more output. >+ >+=item B<-h, -?, --help> >+ >+Prints this help message and exits. >+ >+=back >+ >+=cut >+ >+sub get_options { >+ >+ # Options >+ my $run = '', >+ my $verbose = ''; >+ my $debug = ''; >+ my $help = ''; >+ >+ GetOptions ( >+ 'r|run' => \$run, >+ 'v|verbose' => \$verbose, >+ 'd|debug' => \$debug, >+ 'h|?|help' => \$help >+ ); >+ >+ pod2usage( -exitval => 0 ) if $help; >+ >+ return ( $run, $verbose, $debug ); >+ >+} >+ >+=head1 AUTHOR >+ >+Magnus Enger <digitalutvikling@gmail.com> >+ >+=head1 COPYRIGHT >+ >+Copyright 2014 Oslo Public Library >+ >+=head1 LICENSE >+ >+This file is part of Koha. >+ >+Koha is free software; you can redistribute it and/or modify it under the terms >+of the GNU General Public License as published by the Free Software Foundation; >+either version 3 of the License, or (at your option) any later version. >+ >+You should have received a copy of the GNU General Public License along with >+Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, >+Fifth Floor, Boston, MA 02110-1301 USA. >+ >+=cut >diff --git a/misc/cronjobs/nl-sync-to-koha.pl b/misc/cronjobs/nl-sync-to-koha.pl >new file mode 100644 >index 0000000..84e2378 >--- /dev/null >+++ b/misc/cronjobs/nl-sync-to-koha.pl >@@ -0,0 +1,192 @@ >+#!/usr/bin/perl >+ >+# Copyright 2014 Oslo Public Library >+ >+=head1 NAME >+ >+nl-sync-to-koha.pl - Sync patrons from the Norwegian national patron database (NL) to Koha. >+ >+=head1 SYNOPSIS >+ >+ perl nl-sync-to-koha.pl -v --run >+ >+=cut >+ >+use C4::Members; >+use C4::Members::Attributes qw( UpdateBorrowerAttribute ); >+use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLGetChanged ); >+use Koha::Database; >+use Getopt::Long; >+use Pod::Usage; >+use Modern::Perl; >+ >+# Get options >+my ( $run, $from, $verbose, $debug ) = get_options(); >+ >+my $check_result = NLCheckSysprefs(); >+if ( $check_result->{'error'} == 1 ) { >+ if ( $check_result->{'nlenabled'} == 0 ) { say "* Please activate this function with the NorwegianPatronDBEnable system preference." }; >+ if ( $check_result->{'endpoint'} == 0 ) { say "* Please specify an endpoint with the NorwegianPatronDBEndpoint system preference." }; >+ if ( $check_result->{'userpass'} == 0 ) { say "* Please fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences." }; >+ exit 0; >+} >+ >+unless ( $run ) { >+ say "* You have not specified --run, no real syncing will be done."; >+} >+ >+# Do the sync >+my $sync_success = 0; >+my $sync_failed = 0; >+my $skipped_local_change = 0; >+ >+# Get the borrowers that have been changed >+my $result = NLGetChanged( $from ); >+ >+if ( $verbose ) { >+ say 'Number of records: ' . $result->{'antall_poster_returnert'}; >+ say 'Number of hits: ' . $result->{'antall_treff'}; >+ say 'Message: ' . $result->{'melding'}; >+ say 'Status: ' . $result->{'status'}; >+ say 'Server time: ' . $result->{'server_tid'}; >+ say "-----------------------------"; >+} >+ >+# Loop through the patrons >+foreach my $patron ( @{ $result->{'kohapatrons'} } ) { >+ if ( $verbose ) { >+ if ( $patron->{'surname'} ) { >+ say "*** Name: " . $patron->{'surname'}; >+ } else { >+ say "*** No name"; >+ } >+ say 'Created by: ' . $patron->{'_extra'}->{'created_by'}; >+ say 'Last change by: ' . $patron->{'_extra'}->{'last_change_by'}; >+ } >+ # Only sync in changes made by other libraries >+ if ( C4::Context->preference("NorwegianPatronDBUsername") ne $patron->{'_extra'}->{'last_change_by'} ) { >+ # Make a copy of the data in the hashref and store it as a hash >+ my %clean_patron = %$patron; >+ # Delete the extra data from the copy of the hashref >+ delete $clean_patron{'_extra'}; >+ # Find the borrowernumber based on cardnumber >+ my $stored_patron = Koha::Database->new->schema->resultset('Borrower')->find({ >+ 'cardnumber' => $patron->{'cardnumber'} >+ }); >+ my $borrowernumber = $stored_patron->borrowernumber; >+ if ( $run ) { >+ # Call ModMember >+ my $success = ModMember( >+ 'borrowernumber' => $borrowernumber, >+ %clean_patron, >+ ); >+ if ( $success ) { >+ # Get the sync object >+ my $sync = Koha::Database->new->schema->resultset('BorrowerSync')->find({ >+ 'synctype' => 'norwegianpatrondb', >+ 'borrowernumber' => $borrowernumber, >+ }); >+ # Update the syncstatus to 'synced' >+ $sync->update( { 'syncstatus' => 'synced' } ); >+ # Update the 'synclast' attribute with the "server time" ("server_tid") returned by the method >+ $sync->update( { 'lastsync' => $result->{'result'}->{'server_tid'} } ); >+ # Save social security number as attribute >+ UpdateBorrowerAttribute( >+ $borrowernumber, >+ { code => 'fnr', attribute => $patron->{'_extra'}->{'socsec'} }, >+ ); >+ $sync_success++; >+ } else { >+ $sync_failed++; >+ } >+ } >+ } else { >+ say "Skipped, local change" if $verbose; >+ $skipped_local_change++; >+ } >+} >+ >+if ( $verbose ) { >+ say "-----------------------------"; >+ say "Sync succeeded: $sync_success"; >+ say "Sync failed : $sync_failed"; >+ say "Skipped local change: $skipped_local_change"; >+} >+ >+=head1 OPTIONS >+ >+=over 4 >+ >+=item B<-r, --run> >+ >+Actually carry out syncing operations. Without this option, the script will >+only report what it would have done, but not change any data, locally or >+remotely. >+ >+=item B<-v --verbose> >+ >+Report on the progress of the script. >+ >+=item B<-f --from> >+ >+Date and time to sync from, if this should be different from "1 second past >+midnight of the day before". The date should be in this format: >+ >+ 2014-06-03T00:00:01 >+ >+=item B<-d --debug> >+ >+Even more output. >+ >+=item B<-h, -?, --help> >+ >+Prints this help message and exits. >+ >+=back >+ >+=cut >+ >+sub get_options { >+ >+ # Options >+ my $run = '', >+ my $from = '', >+ my $verbose = ''; >+ my $debug = ''; >+ my $help = ''; >+ >+ GetOptions ( >+ 'r|run' => \$run, >+ 'f|from=s' => \$from, >+ 'v|verbose' => \$verbose, >+ 'd|debug' => \$debug, >+ 'h|?|help' => \$help >+ ); >+ >+ pod2usage( -exitval => 0 ) if $help; >+ >+ return ( $run, $from, $verbose, $debug ); >+ >+} >+ >+=head1 AUTHOR >+ >+Magnus Enger <digitalutvikling@gmail.com> >+ >+=head1 COPYRIGHT >+ >+Copyright 2014 Oslo Public Library >+ >+=head1 LICENSE >+ >+This file is part of Koha. >+ >+Koha is free software; you can redistribute it and/or modify it under the terms >+of the GNU General Public License as published by the Free Software Foundation; >+either version 3 of the License, or (at your option) any later version. >+ >+You should have received a copy of the GNU General Public License along with >+Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, >+Fifth Floor, Boston, MA 02110-1301 USA. >+ >+=cut >diff --git a/t/NorwegianPatronDB.t b/t/NorwegianPatronDB.t >new file mode 100644 >index 0000000..483fe2a >--- /dev/null >+++ b/t/NorwegianPatronDB.t >@@ -0,0 +1,583 @@ >+#!/usr/bin/perl >+ >+use Modern::Perl; >+use Test::More; >+use Test::MockModule; >+use t::lib::Mocks; >+use Data::Dumper; >+use SOAP::Lite; >+ >+# Check that all the modules we need are installed, or bail out >+BEGIN { >+ eval { >+ require Test::DBIx::Class; >+ 1; >+ } or do { >+ plan skip_all => "Test::DBIx::Class is not available"; >+ }; >+} >+BEGIN { >+ eval { >+ require SOAP::Lite; >+ 1; >+ } or do { >+ plan skip_all => "SOAP::Lite is not available"; >+ }; >+} >+BEGIN { >+ eval { >+ require Crypt::GCrypt; >+ 1; >+ } or do { >+ plan skip_all => "Crypt::GCrypt is not available"; >+ }; >+} >+BEGIN { >+ eval { >+ require Convert::BaseN; >+ 1; >+ } or do { >+ plan skip_all => "Convert::BaseN is not available"; >+ }; >+} >+ >+use Test::DBIx::Class { >+ schema_class => 'Koha::Schema', >+ connect_info => ['dbi:SQLite:dbname=:memory:','',''], >+ connect_opts => { name_sep => '.', quote_char => '`', }, >+ fixture_class => '::Populate', >+}, 'Borrower', 'BorrowerSync'; >+ >+# Make the code in the module use our mocked Koha::Schema/Koha::Database >+my $db = Test::MockModule->new('Koha::Database'); >+$db->mock( >+ # Schema() gives us the DB connection set up by Test::DBIx::Class >+ _new_schema => sub { return Schema(); } >+); >+ >+fixtures_ok [ >+ 'Borrower' => [ >+ [qw/firstname surname borrowernumber address city/], >+ ['Test', 'Borrower', 1, 'Test road', 'Test city'], >+ ['Test', 'Borrower', 2, 'Test road', 'Test city'], >+ ['Test', 'Borrower', 3, 'Test road', 'Test city'], >+ ['Test', 'Borrower', 4, 'Test road', 'Test city'], >+ ], >+ 'BorrowerSync' => [ >+ [qw/borrowernumber sync syncstatus lastsync hashed_pin synctype/], >+ [1, 1, 'new', '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ], >+ [2, 1, 'edited', '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ], >+ [3, 1, 'new', '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ], >+ [4, 1, 'new', '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ], >+ ], >+], 'installed some fixtures'; >+ >+=head1 LOADING THE MODULE >+ >+=cut >+ >+BEGIN { use_ok( 'Koha::NorwegianPatronDB', ':all' ) } >+ >+ >+=head1 UTILITY SUBROUTINES >+ >+=head2 NLCheckSysprefs >+ >+Relevant sysprefs: >+ >+=over 4 >+ >+=item * NorwegianPatronDBEnable >+ >+=item * NorwegianPatronDBEndpoint >+ >+=item * NorwegianPatronDBUsername >+ >+=item * NorwegianPatronDBPassword >+ >+=back >+ >+=cut >+ >+t::lib::Mocks::mock_preference('NorwegianPatronDBEnable', 0); >+t::lib::Mocks::mock_preference('NorwegianPatronDBEndpoint', ''); >+t::lib::Mocks::mock_preference('NorwegianPatronDBUsername', ''); >+t::lib::Mocks::mock_preference('NorwegianPatronDBPassword', ''); >+ok( my $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' ); >+is( $result->{ 'error' }, 1, 'error detected' ); >+is( $result->{ 'nlenabled' }, 0, 'NL is not enabled' ); >+is( $result->{ 'endpoint' }, 0, 'an endpoint is not specified' ); >+is( $result->{ 'userpass' }, 0, 'username and/or password is missing' ); >+ >+t::lib::Mocks::mock_preference('NorwegianPatronDBEnable', 1); >+ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' ); >+is( $result->{ 'error' }, 1, 'error detected' ); >+is( $result->{ 'nlenabled' }, 1, 'NL is enabled' ); >+is( $result->{ 'endpoint' }, 0, 'an endpoint is not specified' ); >+is( $result->{ 'userpass' }, 0, 'username and/or password is missing' ); >+ >+t::lib::Mocks::mock_preference('NorwegianPatronDBEnable', 0); >+t::lib::Mocks::mock_preference('NorwegianPatronDBUsername', 'user'); >+t::lib::Mocks::mock_preference('NorwegianPatronDBPassword', 'pass'); >+ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' ); >+is( $result->{ 'error' }, 1, 'error detected' ); >+is( $result->{ 'nlenabled' }, 0, 'NL is not enabled' ); >+is( $result->{ 'endpoint' }, 0, 'an endpoint is not specified' ); >+is( $result->{ 'userpass' }, 1, 'username and/or password is present' ); >+ >+t::lib::Mocks::mock_preference('NorwegianPatronDBEnable', 1); >+t::lib::Mocks::mock_preference('NorwegianPatronDBEndpoint', 'http://example.com/'); >+ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' ); >+is( $result->{ 'error' }, 0, 'no error detected' ); >+is( $result->{ 'nlenabled' }, 1, 'NL is enabled' ); >+is( $result->{ 'endpoint' }, 1, 'an endpoint is specified' ); >+is( $result->{ 'userpass' }, 1, 'username and/or password is present' ); >+ >+=head2 NLGetFirstname and NLGetSurname >+ >+=cut >+ >+my $firstname = 'Firstname'; >+my $surname = 'Surname'; >+my $fullname = "$surname, $firstname"; >+my $wrongname = "$surname $firstname"; >+ >+is( NLGetFirstname( $fullname ), $firstname, 'can get firstname from name' ); >+is( NLGetSurname( $fullname ), $surname, 'can get surname from name' ); >+is( NLGetFirstname( $wrongname ), $wrongname, 'returns full string when name misses comma' ); >+is( NLGetSurname( $wrongname ), $wrongname, 'returns full string when name misses comma' ); >+ >+=head2 NLDecodePin and NLEncryptPIN >+ >+=cut >+ >+my $pin = '1234'; >+my $hash = NLEncryptPIN( $pin ); >+ >+is( NLEncryptPIN( $pin ), $hash, 'NLEncryptPIN works' ); >+is( NLDecodePin( $hash ), $pin, 'NLDecodePin works' ); >+ >+=head2 NLUpdateHashedPIN >+ >+TODO >+ >+=cut >+ >+is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('hashed_pin'), 'abc', 'hashed_pin is "abc"' ); >+# Set a new pin >+my $new_pin = 'bcd'; >+ok( NLUpdateHashedPIN( 1, $new_pin ), 'NLUpdateHashedPIN runs ok' ); >+# Hash the new pin and compare it to the one stored in the database >+my $hashed_pin = Koha::NorwegianPatronDB::_encrypt_pin( $new_pin ); >+is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('hashed_pin'), $hashed_pin, 'hashed_pin was updated' ); >+ >+=head2 NLMarkForDeletion >+ >+=cut >+ >+is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'new', 'syncstatus is "new"' ); >+ok( NLMarkForDeletion( 3 ), 'NLMarkForDeletion runs ok' ); >+# Check that the syncstatus was updated. Note: We will use this status later, to check syncing of deleted borrowers >+is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'delete', 'syncstatus is "delete"' ); >+ >+=head2 NLGetSyncDataFromBorrowernumber >+ >+=cut >+ >+ok( my $sync_data = NLGetSyncDataFromBorrowernumber( 1 ), 'NLGetSyncDataFromBorrowernumber runs ok' ); >+isa_ok( $sync_data, 'Koha::Schema::Result::BorrowerSync' ); >+is( $sync_data->sync, 1, 'the sync is on' ); >+is( $sync_data->syncstatus, 'new', 'syncstatus is "new"' ); >+is( $sync_data->lastsync, '2014-03-31T12:35:14', 'lastsync is ok' ); >+is( $sync_data->hashed_pin, $hashed_pin, 'hashed_pin is ok' ); >+ >+=head1 SUBROUTINES THAT TALK TO SOAP >+ >+=head2 NLSearch >+ >+=cut >+ >+my $lite = Test::MockModule->new('SOAP::Lite'); >+ >+# Mock a successfull call to the "hent" method >+$lite->mock( >+ hent => sub { return SOAP::Deserializer->deserialize( hent_success() )->result; } >+); >+ok( my $res = NLSearch( '12345678910' ), 'successfull call to NLSearch' ); >+is( $res->{'antall_poster_returnert'}, 1, 'got 1 record' ); >+isa_ok( $res, "Resultat" ); >+isa_ok( $res->{'respons_poster'}, "LaanerListe" ); >+isa_ok( $res->{'respons_poster'}[0], "Laaner" ); >+ >+# Mock an unsuccessfull call to the "hent" method >+$lite->mock( >+ hent => sub { return SOAP::Deserializer->deserialize( hent_failure() )->result; } >+); >+ok( $res = NLSearch( '12345678910' ), 'call to NLSearch with an illegal argument' ); >+is( $res->{'antall_poster_returnert'}, 0, 'got 0 records' ); >+isa_ok( $res, "Resultat" ); >+like( $res->{'melding'}, qr/Ulovlig argument: hverken LNR eller FNR_HASH/, "got expected error message for an illegal identifier" ); >+ >+=head2 NLSync >+ >+=head3 New patron >+ >+=cut >+ >+my $borrower = Borrower->find({ 'borrowernumber' => 1 }); >+$lite->mock( >+ nyPost => sub { return SOAP::Deserializer->deserialize( nyPost_success() )->result; } >+); >+is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('syncstatus'), 'new', 'patron is new' ); >+ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync via patron ("nyPost")' ); >+is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('syncstatus'), 'synced', 'patron is synced' ); >+ >+# Now do the same test, but pass in a borrowernumber, not a Koha::Schema::Result::Borrower >+is ( BorrowerSync->find({ 'borrowernumber' => 4 })->get_column('syncstatus'), 'new', 'patron is new' ); >+ok ( $result = NLSync({ 'borrowernumber' => 4 }), 'successfull call to NLSync via borrowernumber ("nyPost")' ); >+is ( BorrowerSync->find({ 'borrowernumber' => 4 })->get_column('syncstatus'), 'synced', 'patron is synced' ); >+ >+=head3 Edited patron >+ >+=cut >+ >+ok ( $borrower = Borrower->find({ 'borrowernumber' => 2 }), 'find our "edited" mock patron' ); >+$lite->mock( >+ endre => sub { return SOAP::Deserializer->deserialize( endre_success() )->result; } >+); >+is ( BorrowerSync->find({ 'borrowernumber' => 2 })->get_column('syncstatus'), 'edited', 'patron is edited' ); >+ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync ("endre")' ); >+is ( BorrowerSync->find({ 'borrowernumber' => 2 })->get_column('syncstatus'), 'synced', 'patron is synced' ); >+ >+=head3 Deleted patron >+ >+=cut >+ >+ok ( $borrower = Borrower->find({ 'borrowernumber' => 3 }), 'find our "deleted" mock patron' ); >+$lite->mock( >+ slett => sub { return SOAP::Deserializer->deserialize( endre_success() )->result; } >+); >+is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'delete', 'patron is marked for deletion' ); >+ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync ("slett")' ); >+is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('sync'), 0, 'sync is now disabled' ); >+ >+=head2 NLGetChanged >+ >+=cut >+ >+# Mock a successfull call to the "soekEndret" method >+$lite->mock( >+ soekEndret => sub { return SOAP::Deserializer->deserialize( soekEndret_success() ); } >+); >+ok( $res = NLGetChanged(), 'successfull call to NLGetChanged - 2 results' ); >+is( $res->{'melding'}, 'OK', 'got "OK"' ); >+is( $res->{'antall_poster_returnert'}, 2, 'got 2 records' ); >+isa_ok( $res, "Resultat" ); >+isa_ok( $res->{'respons_poster'}, "LaanerListe" ); >+isa_ok( $res->{'respons_poster'}[0], "Laaner" ); >+ >+ >+# Mock a successfull call to the "soekEndret" method, but with zero new records >+$lite->mock( >+ soekEndret => sub { return SOAP::Deserializer->deserialize( soekEndret_zero_new() ); } >+); >+ok( $res = NLGetChanged(), 'successfull call to NLGetChanged - 0 results' ); >+is( $res->{'melding'}, 'ingen treff', 'got "ingen treff"' ); >+is( $res->{'antall_poster_returnert'}, 0, 'got 0 records' ); >+is( $res->{'antall_treff'}, 0, 'got 0 records' ); >+ >+done_testing(); >+ >+=head1 SAMPLE SOAP XML RESPONSES >+ >+These responses can be gathered by setting "outputxml()" to true on the SOAP >+client: >+ >+ my $client = SOAP::Lite >+ ->on_action( sub { return '""';}) >+ ->uri('http://lanekortet.no') >+ ->proxy('https://fl.lanekortet.no/laanekort/fl_test.php') >+ ->outputxml(1); >+ my $response = $client->slett( $x ); >+ say $response; >+ >+Pretty formatting can be achieved by piping the output from a test script >+through xmllint: >+ >+ perl my_test_script.pl > xmllint --format - >+ >+=cut >+ >+sub slett_success { >+ >+ return <<'ENDRESPONSE'; >+<?xml version="1.0" encoding="UTF-8"?> >+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> >+ <SOAP-ENV:Body> >+ <ns1:slettResponse> >+ <return xsi:type="ns1:Svar"> >+ <status xsi:type="xsd:boolean">true</status> >+ <melding xsi:type="xsd:string">Test Testersen (1973-08-11) er slettet fra registeret</melding> >+ <lnr xsi:type="xsd:string">N000106188</lnr> >+ <server_tid xsi:type="xsd:string">2014-06-02T16:51:58</server_tid> >+ </return> >+ </ns1:slettResponse> >+ </SOAP-ENV:Body> >+</SOAP-ENV:Envelope> >+ENDRESPONSE >+ >+} >+ >+sub endre_success { >+ >+ return <<'ENDRESPONSE'; >+<?xml version="1.0" encoding="UTF-8"?> >+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> >+ <SOAP-ENV:Body> >+ <ns1:endreResponse> >+ <return xsi:type="ns1:Svar"> >+ <status xsi:type="xsd:boolean">true</status> >+ <melding xsi:type="xsd:string">Oppdaterte felt: navn, p_adresse1, p_postnr, p_sted, p_land, fdato, fnr_hash, kjonn, pin, sist_endret, sist_endret_av</melding> >+ <lnr xsi:type="xsd:string">N000106188</lnr> >+ <server_tid xsi:type="xsd:string">2014-06-02T16:42:32</server_tid> >+ </return> >+ </ns1:endreResponse> >+ </SOAP-ENV:Body> >+</SOAP-ENV:Envelope> >+ENDRESPONSE >+ >+} >+ >+sub nyPost_success { >+ >+ return <<'ENDRESPONSE'; >+<?xml version="1.0" encoding="UTF-8"?> >+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> >+ <SOAP-ENV:Body> >+ <ns1:nyPostResponse> >+ <return xsi:type="ns1:Svar"> >+ <status xsi:type="xsd:boolean">true</status> >+ <melding xsi:type="xsd:string">Ny post er opprettet</melding> >+ <lnr xsi:type="xsd:string">N000106188</lnr> >+ <server_tid xsi:type="xsd:string">2014-06-02T14:10:09</server_tid> >+ </return> >+ </ns1:nyPostResponse> >+ </SOAP-ENV:Body> >+</SOAP-ENV:Envelope> >+ENDRESPONSE >+ >+} >+ >+sub soekEndret_success { >+ >+return <<'ENDRESPONSE'; >+<?xml version="1.0" encoding="UTF-8"?> >+<SOAP-ENV:Envelope >+ xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >+ xmlns:ns1="http://lanekortet.no" >+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" >+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >+ xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" >+ SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> >+ <SOAP-ENV:Body> >+ <ns1:soekEndretResponse> >+ <return xsi:type="ns1:Resultat"> >+ <status xsi:type="xsd:boolean">true</status> >+ <melding xsi:type="xsd:string">OK</melding> >+ <antall_treff xsi:type="xsd:int">2</antall_treff> >+ <antall_poster_returnert xsi:type="xsd:int">2</antall_poster_returnert> >+ <neste_indeks xsi:type="xsd:int">0</neste_indeks> >+ <respons_poster SOAP-ENC:arrayType="ns1:Laaner[2]" xsi:type="ns1:LaanerListe"> >+ <item xsi:type="ns1:Laaner"> >+ <lnr xsi:type="xsd:string">N000106186</lnr> >+ <navn xsi:type="xsd:string">Hansen, Hanne</navn> >+ <p_adresse1 xsi:type="xsd:string"/> >+ <p_adresse2 xsi:type="xsd:string"/> >+ <p_postnr xsi:type="xsd:string"/> >+ <p_sted xsi:type="xsd:string">BÃDÃ</p_sted> >+ <p_land xsi:type="xsd:string">no</p_land> >+ <p_sjekk xsi:type="xsd:string">0</p_sjekk> >+ <m_adresse1 xsi:type="xsd:string"/> >+ <m_adresse2 xsi:type="xsd:string"/> >+ <m_postnr xsi:type="xsd:string"/> >+ <m_sted xsi:type="xsd:string"/> >+ <m_land xsi:type="xsd:string"/> >+ <m_sjekk xsi:type="xsd:string">0</m_sjekk> >+ <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til> >+ <tlf_hjemme xsi:type="xsd:string"/> >+ <tlf_jobb xsi:type="xsd:string"/> >+ <tlf_mobil xsi:type="xsd:string"/> >+ <epost xsi:type="xsd:string"/> >+ <epost_sjekk xsi:type="xsd:string"/> >+ <prim_kontakt xsi:type="xsd:string"/> >+ <hjemmebibliotek xsi:type="xsd:string">5180401</hjemmebibliotek> >+ <fdato xsi:type="xsd:string">1994-04-08</fdato> >+ <fnr_hash xsi:type="xsd:string">11087395628</fnr_hash> >+ <kjonn xsi:type="xsd:string">F</kjonn> >+ <pin xsi:type="xsd:string">89308dfc85ee7a5826ae14e2d8efad1e</pin> >+ <passord xsi:type="xsd:string"/> >+ <feide xsi:type="xsd:string">0</feide> >+ <opprettet xsi:type="xsd:string">2014-04-28T15:20:38</opprettet> >+ <opprettet_av xsi:type="xsd:string">5180401</opprettet_av> >+ <sist_endret xsi:type="xsd:string">2014-04-28T15:20:38</sist_endret> >+ <sist_endret_av xsi:type="xsd:string">5180401</sist_endret_av> >+ <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato> >+ </item> >+ <item xsi:type="ns1:Laaner"> >+ <lnr xsi:type="xsd:string">N000106184</lnr> >+ <navn xsi:type="xsd:string">Enger, Magnus</navn> >+ <p_adresse1 xsi:type="xsd:string">Svarthammarveien 633333</p_adresse1> >+ <p_adresse2 xsi:type="xsd:string"/> >+ <p_postnr xsi:type="xsd:string">8015</p_postnr> >+ <p_sted xsi:type="xsd:string">Bodø</p_sted> >+ <p_land xsi:type="xsd:string">no</p_land> >+ <p_sjekk xsi:type="xsd:string">0</p_sjekk> >+ <m_adresse1 xsi:type="xsd:string"/> >+ <m_adresse2 xsi:type="xsd:string"/> >+ <m_postnr xsi:type="xsd:string"/> >+ <m_sted xsi:type="xsd:string"/> >+ <m_land xsi:type="xsd:string">no</m_land> >+ <m_sjekk xsi:type="xsd:string">0</m_sjekk> >+ <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til> >+ <tlf_hjemme xsi:type="xsd:string">95158548</tlf_hjemme> >+ <tlf_jobb xsi:type="xsd:string"/> >+ <tlf_mobil xsi:type="xsd:string"/> >+ <epost xsi:type="xsd:string">magnus@enger.priv.no</epost> >+ <epost_sjekk xsi:type="xsd:string"/> >+ <prim_kontakt xsi:type="xsd:string"/> >+ <hjemmebibliotek xsi:type="xsd:string">5180401</hjemmebibliotek> >+ <fdato xsi:type="xsd:string">1973-08-11</fdato> >+ <fnr_hash xsi:type="xsd:string">11087345795</fnr_hash> >+ <kjonn xsi:type="xsd:string">M</kjonn> >+ <pin xsi:type="xsd:string">a632c504b8c4fba3149115cb07e0796c</pin> >+ <passord xsi:type="xsd:string"/> >+ <feide xsi:type="xsd:string">0</feide> >+ <opprettet xsi:type="xsd:string">2014-04-28T14:52:02</opprettet> >+ <opprettet_av xsi:type="xsd:string">5180401</opprettet_av> >+ <sist_endret xsi:type="xsd:string">2014-05-13T11:01:33</sist_endret> >+ <sist_endret_av xsi:type="xsd:string">5180401</sist_endret_av> >+ <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato> >+ </item> >+ </respons_poster> >+ <server_tid xsi:type="xsd:string">2014-05-16T14:44:44</server_tid> >+ </return> >+ </ns1:soekEndretResponse> >+ </SOAP-ENV:Body> >+</SOAP-ENV:Envelope> >+ENDRESPONSE >+} >+ >+sub soekEndret_zero_new { >+ return <<'ENDRESPONSE'; >+<?xml version="1.0" encoding="UTF-8"?> >+ <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> >+ <SOAP-ENV:Body> >+ <ns1:soekEndretResponse> >+ <return xsi:type="ns1:Resultat"> >+ <status xsi:type="xsd:boolean">false</status> >+ <melding xsi:type="xsd:string">ingen treff</melding> >+ <antall_treff xsi:type="xsd:int">0</antall_treff> >+ <antall_poster_returnert xsi:type="xsd:int">0</antall_poster_returnert> >+ <neste_indeks xsi:type="xsd:int">0</neste_indeks> >+ <respons_poster SOAP-ENC:arrayType="ns1:Laaner[0]" xsi:type="ns1:LaanerListe"/> >+ <server_tid xsi:type="xsd:string">2014-05-20T13:02:02</server_tid> >+ </return> >+ </ns1:soekEndretResponse> >+ </SOAP-ENV:Body> >+ </SOAP-ENV:Envelope> >+ENDRESPONSE >+} >+ >+sub hent_failure { >+ return <<'ENDRESPONSE'; >+<?xml version="1.0" encoding="UTF-8"?> >+<SOAP-ENV:Envelope >+ xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >+ xmlns:ns1="http://lanekortet.no" >+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" >+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >+ xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" >+ SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> >+ <SOAP-ENV:Body> >+ <ns1:hentResponse> >+ <return xsi:type="ns1:Resultat"> >+ <status xsi:type="xsd:boolean">false</status> >+ <melding xsi:type="xsd:string">hent: Ulovlig argument: hverken LNR eller FNR_HASH</melding> >+ <antall_treff xsi:type="xsd:int">0</antall_treff> >+ <antall_poster_returnert xsi:type="xsd:int">0</antall_poster_returnert> >+ <neste_indeks xsi:type="xsd:int">0</neste_indeks> >+ <respons_poster SOAP-ENC:arrayType="ns1:Laaner[0]" xsi:type="ns1:LaanerListe"/> >+ <server_tid xsi:type="xsd:string">2014-05-15T10:56:24</server_tid> >+ </return> >+ </ns1:hentResponse> >+ </SOAP-ENV:Body> >+</SOAP-ENV:Envelope> >+ENDRESPONSE >+ >+} >+ >+sub hent_success { >+ >+return <<'ENDRESPONSE'; >+<?xml version="1.0" encoding="UTF-8"?> >+<SOAP-ENV:Envelope >+ xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >+ xmlns:ns1="http://lanekortet.no" >+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" >+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >+ xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" >+ SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> >+ <SOAP-ENV:Body> >+ <ns1:hentResponse> >+ <return xsi:type="ns1:Resultat"> >+ <status xsi:type="xsd:boolean">true</status> >+ <melding xsi:type="xsd:string">OK</melding> >+ <antall_treff xsi:type="xsd:int">1</antall_treff> >+ <antall_poster_returnert xsi:type="xsd:int">1</antall_poster_returnert> >+ <neste_indeks xsi:type="xsd:int">0</neste_indeks> >+ <respons_poster SOAP-ENC:arrayType="ns1:Laaner[1]" xsi:type="ns1:LaanerListe"> >+ <item xsi:type="ns1:Laaner"> >+ <lnr xsi:type="xsd:string">N000123456</lnr> >+ <navn xsi:type="xsd:string">Test, Testersen</navn> >+ <p_adresse1 xsi:type="xsd:string">Bibliotekveien 6</p_adresse1> >+ <p_adresse2 xsi:type="xsd:string"/> >+ <p_postnr xsi:type="xsd:string">1234</p_postnr> >+ <p_sted xsi:type="xsd:string">Lillevik</p_sted> >+ <p_land xsi:type="xsd:string">no</p_land> >+ <p_sjekk xsi:type="xsd:string">0</p_sjekk> >+ <m_adresse1 xsi:type="xsd:string"/> >+ <m_adresse2 xsi:type="xsd:string"/> >+ <m_postnr xsi:type="xsd:string"/> >+ <m_sted xsi:type="xsd:string"/> >+ <m_land xsi:type="xsd:string">no</m_land> >+ <m_sjekk xsi:type="xsd:string">0</m_sjekk> >+ <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til> >+ <tlf_hjemme xsi:type="xsd:string"/> >+ <tlf_jobb xsi:type="xsd:string"/> >+ <tlf_mobil xsi:type="xsd:string">12345678</tlf_mobil> >+ <epost xsi:type="xsd:string">test@example.com</epost> >+ <epost_sjekk xsi:type="xsd:string">0</epost_sjekk> >+ <prim_kontakt xsi:type="xsd:string"/> >+ <hjemmebibliotek xsi:type="xsd:string">2060000</hjemmebibliotek> >+ <fdato xsi:type="xsd:string">1964-05-22</fdato> >+ <fnr_hash xsi:type="xsd:string">22056412345</fnr_hash> >+ <kjonn xsi:type="xsd:string">F</kjonn> >+ <pin xsi:type="xsd:string">g345abc123dab567abc78900abc123ab</pin> >+ <passord xsi:type="xsd:string"/> >+ <feide xsi:type="xsd:string"/> >+ <opprettet xsi:type="xsd:string">2005-10-20</opprettet> >+ <opprettet_av xsi:type="xsd:string">2060000</opprettet_av> >+ <sist_endret xsi:type="xsd:string">2013-05-13T13:51:24</sist_endret> >+ <sist_endret_av xsi:type="xsd:string">2060000</sist_endret_av> >+ <gyldig_til xsi:type="xsd:string"/> >+ <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato> >+ </item> >+ </respons_poster> >+ <server_tid xsi:type="xsd:string">2014-01-07T14:43:18</server_tid> >+ </return> >+ </ns1:hentResponse> >+ </SOAP-ENV:Body> >+</SOAP-ENV:Envelope> >+ENDRESPONSE >+ >+} >-- >2.1.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 11401
:
30752
|
31710
|
31711
|
31815
|
31816
|
32546
|
32607
|
32888
|
32889
|
32890
|
32949
|
32950
|
32951
|
32952
|
33528
|
33570
|
33582