Bug 11614: Untranslatable label_element_title in label management
[koha.git] / C4 / SIP / Sip / MsgType.pm
blob4bad2ea0ec89f854acea9a18701808d3a66be7fa
2 # Sip::MsgType.pm
4 # A Class for handing SIP messages
7 package Sip::MsgType;
9 use strict;
10 use warnings;
11 use Exporter;
12 use Sys::Syslog qw(syslog);
14 use Sip qw(:all);
15 use Sip::Constants qw(:all);
16 use Sip::Checksum qw(verify_cksum);
18 use Data::Dumper;
19 use CGI;
20 use C4::Auth qw(&check_api_auth);
22 use UNIVERSAL qw(can); # make sure this is *after* C4 modules.
24 use vars qw(@ISA $VERSION @EXPORT_OK);
26 BEGIN {
27 $VERSION = 3.07.00.049;
28 @ISA = qw(Exporter);
29 @EXPORT_OK = qw(handle);
32 # Predeclare handler subroutines
33 use subs qw(handle_patron_status handle_checkout handle_checkin
34 handle_block_patron handle_sc_status handle_request_acs_resend
35 handle_login handle_patron_info handle_end_patron_session
36 handle_fee_paid handle_item_information handle_item_status_update
37 handle_patron_enable handle_hold handle_renew handle_renew_all);
40 # For the most part, Version 2.00 of the protocol just adds new
41 # variable fields, but sometimes it changes the fixed header.
43 # In general, if there's no '2.00' protocol entry for a handler, that's
44 # because 2.00 didn't extend the 1.00 version of the protocol. This will
45 # be handled by the module initialization code following the declaration,
46 # which goes through the handlers table and creates a '2.00' entry that
47 # points to the same place as the '1.00' entry. If there's a 2.00 entry
48 # but no 1.00 entry, then that means that it's a completely new service
49 # in 2.00, so 1.00 shouldn't recognize it.
51 my %handlers = (
52 (PATRON_STATUS_REQ) => {
53 name => "Patron Status Request",
54 handler => \&handle_patron_status,
55 protocol => {
56 1 => {
57 template => "A3A18",
58 template_len => 21,
59 fields => [(FID_INST_ID), (FID_PATRON_ID),
60 (FID_TERMINAL_PWD), (FID_PATRON_PWD)],
64 (CHECKOUT) => {
65 name => "Checkout",
66 handler => \&handle_checkout,
67 protocol => {
68 1 => {
69 template => "CCA18A18",
70 template_len => 38,
71 fields => [(FID_INST_ID), (FID_PATRON_ID),
72 (FID_ITEM_ID), (FID_TERMINAL_PWD)],
74 2 => {
75 template => "CCA18A18",
76 template_len => 38,
77 fields => [(FID_INST_ID), (FID_PATRON_ID),
78 (FID_ITEM_ID), (FID_TERMINAL_PWD),
79 (FID_ITEM_PROPS), (FID_PATRON_PWD),
80 (FID_FEE_ACK), (FID_CANCEL)],
84 (CHECKIN) => {
85 name => "Checkin",
86 handler => \&handle_checkin,
87 protocol => {
88 1 => {
89 template => "CA18A18",
90 template_len => 37,
91 fields => [(FID_CURRENT_LOCN), (FID_INST_ID),
92 (FID_ITEM_ID), (FID_TERMINAL_PWD)],
94 2 => {
95 template => "CA18A18",
96 template_len => 37,
97 fields => [(FID_CURRENT_LOCN), (FID_INST_ID),
98 (FID_ITEM_ID), (FID_TERMINAL_PWD),
99 (FID_ITEM_PROPS), (FID_CANCEL)],
103 (BLOCK_PATRON) => {
104 name => "Block Patron",
105 handler => \&handle_block_patron,
106 protocol => {
107 1 => {
108 template => "CA18",
109 template_len => 19,
110 fields => [(FID_INST_ID), (FID_BLOCKED_CARD_MSG),
111 (FID_PATRON_ID), (FID_TERMINAL_PWD)],
115 (SC_STATUS) => {
116 name => "SC Status",
117 handler => \&handle_sc_status,
118 protocol => {
119 1 => {
120 template =>"CA3A4",
121 template_len => 8,
122 fields => [],
126 (REQUEST_ACS_RESEND) => {
127 name => "Request ACS Resend",
128 handler => \&handle_request_acs_resend,
129 protocol => {
130 1 => {
131 template => "",
132 template_len => 0,
133 fields => [],
137 (LOGIN) => {
138 name => "Login",
139 handler => \&handle_login,
140 protocol => {
141 2 => {
142 template => "A1A1",
143 template_len => 2,
144 fields => [(FID_LOGIN_UID), (FID_LOGIN_PWD),
145 (FID_LOCATION_CODE)],
149 (PATRON_INFO) => {
150 name => "Patron Info",
151 handler => \&handle_patron_info,
152 protocol => {
153 2 => {
154 template => "A3A18A10",
155 template_len => 31,
156 fields => [(FID_INST_ID), (FID_PATRON_ID),
157 (FID_TERMINAL_PWD), (FID_PATRON_PWD),
158 (FID_START_ITEM), (FID_END_ITEM)],
162 (END_PATRON_SESSION) => {
163 name => "End Patron Session",
164 handler => \&handle_end_patron_session,
165 protocol => {
166 2 => {
167 template => "A18",
168 template_len => 18,
169 fields => [(FID_INST_ID), (FID_PATRON_ID),
170 (FID_TERMINAL_PWD), (FID_PATRON_PWD)],
174 (FEE_PAID) => {
175 name => "Fee Paid",
176 handler => \&handle_fee_paid,
177 protocol => {
178 2 => {
179 template => "A18A2A2A3",
180 template_len => 25,
181 fields => [(FID_FEE_AMT), (FID_INST_ID),
182 (FID_PATRON_ID), (FID_TERMINAL_PWD),
183 (FID_PATRON_PWD), (FID_FEE_ID),
184 (FID_TRANSACTION_ID)],
188 (ITEM_INFORMATION) => {
189 name => "Item Information",
190 handler => \&handle_item_information,
191 protocol => {
192 2 => {
193 template => "A18",
194 template_len => 18,
195 fields => [(FID_INST_ID), (FID_ITEM_ID),
196 (FID_TERMINAL_PWD)],
200 (ITEM_STATUS_UPDATE) => {
201 name => "Item Status Update",
202 handler => \&handle_item_status_update,
203 protocol => {
204 2 => {
205 template => "A18",
206 template_len => 18,
207 fields => [(FID_INST_ID), (FID_PATRON_ID),
208 (FID_ITEM_ID), (FID_TERMINAL_PWD),
209 (FID_ITEM_PROPS)],
213 (PATRON_ENABLE) => {
214 name => "Patron Enable",
215 handler => \&handle_patron_enable,
216 protocol => {
217 2 => {
218 template => "A18",
219 template_len => 18,
220 fields => [(FID_INST_ID), (FID_PATRON_ID),
221 (FID_TERMINAL_PWD), (FID_PATRON_PWD)],
225 (HOLD) => {
226 name => "Hold",
227 handler => \&handle_hold,
228 protocol => {
229 2 => {
230 template => "AA18",
231 template_len => 19,
232 fields => [(FID_EXPIRATION), (FID_PICKUP_LOCN),
233 (FID_HOLD_TYPE), (FID_INST_ID),
234 (FID_PATRON_ID), (FID_PATRON_PWD),
235 (FID_ITEM_ID), (FID_TITLE_ID),
236 (FID_TERMINAL_PWD), (FID_FEE_ACK)],
240 (RENEW) => {
241 name => "Renew",
242 handler => \&handle_renew,
243 protocol => {
244 2 => {
245 template => "CCA18A18",
246 template_len => 38,
247 fields => [(FID_INST_ID), (FID_PATRON_ID),
248 (FID_PATRON_PWD), (FID_ITEM_ID),
249 (FID_TITLE_ID), (FID_TERMINAL_PWD),
250 (FID_ITEM_PROPS), (FID_FEE_ACK)],
254 (RENEW_ALL) => {
255 name => "Renew All",
256 handler => \&handle_renew_all,
257 protocol => {
258 2 => {
259 template => "A18",
260 template_len => 18,
261 fields => [(FID_INST_ID), (FID_PATRON_ID),
262 (FID_PATRON_PWD), (FID_TERMINAL_PWD),
263 (FID_FEE_ACK)],
270 # Now, initialize some of the missing bits of %handlers
272 foreach my $i (keys(%handlers)) {
273 if (!exists($handlers{$i}->{protocol}->{2})) {
274 $handlers{$i}->{protocol}->{2} = $handlers{$i}->{protocol}->{1};
278 sub new {
279 my ($class, $msg, $seqno) = @_;
280 my $self = {};
281 my $msgtag = substr($msg, 0, 2);
283 if ($msgtag eq LOGIN) {
284 # If the client is using the 2.00-style "Login" message
285 # to authenticate to the server, then we get the Login message
286 # _before_ the client has indicated that it supports 2.00, but
287 # it's using the 2.00 login process, so it must support 2.00.
288 $protocol_version = 2;
290 syslog("LOG_DEBUG", "Sip::MsgType::new('%s', '%s...', '%s'): seq.no '%s', protocol %s",
291 $class, substr($msg, 0, 10), $msgtag, $seqno, $protocol_version);
292 # warn "SIP PROTOCOL: $protocol_version";
293 if (!exists($handlers{$msgtag})) {
294 syslog("LOG_WARNING", "new Sip::MsgType: Skipping message of unknown type '%s' in '%s'",
295 $msgtag, $msg);
296 return;
297 } elsif (!exists($handlers{$msgtag}->{protocol}->{$protocol_version})) {
298 syslog("LOG_WARNING", "new Sip::MsgType: Skipping message '%s' unsupported by protocol rev. '%d'",
299 $msgtag, $protocol_version);
300 return;
303 bless $self, $class;
305 $self->{seqno} = $seqno;
306 $self->_initialize(substr($msg,2), $handlers{$msgtag});
308 return($self);
311 sub _initialize {
312 my ($self, $msg, $control_block) = @_;
313 my ($fs, $fn, $fe);
314 my $proto = $control_block->{protocol}->{$protocol_version};
316 $self->{name} = $control_block->{name};
317 $self->{handler} = $control_block->{handler};
319 $self->{fields} = {};
320 $self->{fixed_fields} = [];
322 chomp($msg); # These four are probably unnecessary now.
323 $msg =~ tr/\cM//d;
324 $msg =~ s/\^M$//;
325 chomp($msg);
327 foreach my $field (@{$proto->{fields}}) {
328 $self->{fields}->{$field} = undef;
331 syslog("LOG_DEBUG", "Sip::MsgType::_initialize('%s', '%s', '%s', '%s', ...)",
332 $self->{name}, $msg, $proto->{template}, $proto->{template_len});
334 $self->{fixed_fields} = [ unpack($proto->{template}, $msg) ]; # see http://perldoc.perl.org/5.8.8/functions/unpack.html
336 # Skip over the fixed fields and the split the rest of
337 # the message into fields based on the delimiter and parse them
338 foreach my $field (split(quotemeta($field_delimiter), substr($msg, $proto->{template_len}))) {
339 $fn = substr($field, 0, 2);
341 if (!exists($self->{fields}->{$fn})) {
342 syslog("LOG_WARNING", "Unsupported field '%s' in %s message '%s'",
343 $fn, $self->{name}, $msg);
344 } elsif (defined($self->{fields}->{$fn})) {
345 syslog("LOG_WARNING", "Duplicate field '%s' (previous value '%s') in %s message '%s'",
346 $fn, $self->{fields}->{$fn}, $self->{name}, $msg);
347 } else {
348 $self->{fields}->{$fn} = substr($field, 2);
352 return($self);
355 sub handle {
356 my ($msg, $server, $req) = @_;
357 my $config = $server->{config};
358 my $self;
361 # What's the field delimiter for variable length fields?
362 # This can't be based on the account, since we need to know
363 # the field delimiter to parse a SIP login message
365 if (defined($server->{config}->{delimiter})) {
366 $field_delimiter = $server->{config}->{delimiter};
369 # error detection is active if this is a REQUEST_ACS_RESEND
370 # message with a checksum, or if the message is long enough
371 # and the last nine characters begin with a sequence number
372 # field
373 if ($msg eq REQUEST_ACS_RESEND_CKSUM) {
374 # Special case
375 $error_detection = 1;
376 $self = new Sip::MsgType ((REQUEST_ACS_RESEND), 0);
377 } elsif((length($msg) > 11) && (substr($msg, -9, 2) eq "AY")) {
378 $error_detection = 1;
380 if (!verify_cksum($msg)) {
381 syslog("LOG_WARNING", "Checksum failed on message '%s'", $msg);
382 # REQUEST_SC_RESEND with error detection
383 $last_response = REQUEST_SC_RESEND_CKSUM;
384 print("$last_response\r");
385 return REQUEST_ACS_RESEND;
386 } else {
387 # Save the sequence number, then strip off the
388 # error detection data to process the message
389 $self = new Sip::MsgType (substr($msg, 0, -9), substr($msg, -7, 1));
391 } elsif ($error_detection) {
392 # We received a non-ED message when ED is supposed to be active.
393 # Warn about this problem, then process the message anyway.
394 syslog("LOG_WARNING",
395 "Received message without error detection: '%s'", $msg);
396 $error_detection = 0;
397 $self = new Sip::MsgType ($msg, 0);
398 } else {
399 $self = new Sip::MsgType ($msg, 0);
402 if ((substr($msg, 0, 2) ne REQUEST_ACS_RESEND) &&
403 $req && (substr($msg, 0, 2) ne $req)) {
404 return substr($msg, 0, 2);
406 unless ($self->{handler}) {
407 syslog("LOG_WARNING", "No handler defined for '%s'", $msg);
408 $last_response = REQUEST_SC_RESEND;
409 print("$last_response\r");
410 return REQUEST_ACS_RESEND;
412 return($self->{handler}->($self, $server)); # FIXME
413 # FIXME: Use of uninitialized value in subroutine entry
414 # Can't use string ("") as a subroutine ref while "strict refs" in use
418 ## Message Handlers
422 # Patron status messages are produced in response to both
423 # "Request Patron Status" and "Block Patron"
425 # Request Patron Status requires a patron password, but
426 # Block Patron doesn't (since the patron may never have
427 # provided one before attempting some illegal action).
429 # ASSUMPTION: If the patron password field is present in the
430 # message, then it must match, otherwise incomplete patron status
431 # information will be returned to the terminal.
433 sub build_patron_status {
434 my ($patron, $lang, $fields, $server)= @_;
436 my $patron_pwd = $fields->{(FID_PATRON_PWD)};
437 my $resp = (PATRON_STATUS_RESP);
439 if ($patron) {
440 $resp .= patron_status_string($patron);
441 $resp .= $lang . Sip::timestamp();
442 $resp .= add_field(FID_PERSONAL_NAME, $patron->name);
444 # while the patron ID we got from the SC is valid, let's
445 # use the one returned from the ILS, just in case...
446 $resp .= add_field(FID_PATRON_ID, $patron->id);
447 if ($protocol_version >= 2) {
448 $resp .= add_field(FID_VALID_PATRON, 'Y');
449 # Patron password is a required field.
450 $resp .= add_field(FID_VALID_PATRON_PWD, sipbool($patron->check_password($patron_pwd)));
451 $resp .= maybe_add(FID_CURRENCY, $patron->currency);
452 $resp .= maybe_add(FID_FEE_AMT, $patron->fee_amount);
455 $resp .= maybe_add( FID_SCREEN_MSG, $patron->screen_msg );
456 $resp .= maybe_add( FID_SCREEN_MSG, $patron->{branchcode} )
457 if ( $server->{account}->{send_patron_home_library_in_af} );
459 $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line);
460 } else {
461 # Invalid patron id. Report that the user has no privs.,
462 # no personal name, and is invalid (if we're using 2.00)
463 $resp .= 'YYYY' . (' ' x 10) . $lang . Sip::timestamp();
464 $resp .= add_field(FID_PERSONAL_NAME, '');
466 # the patron ID is invalid, but it's a required field, so
467 # just echo it back
468 $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)});
470 ($protocol_version >= 2) and
471 $resp .= add_field(FID_VALID_PATRON, 'N');
474 $resp .= add_field(FID_INST_ID, $fields->{(FID_INST_ID)});
475 return $resp;
478 sub handle_patron_status {
479 my ($self, $server) = @_;
480 warn "handle_patron_status server: " . Dumper(\$server);
481 my $ils = $server->{ils};
482 my $patron;
483 my $resp = (PATRON_STATUS_RESP);
484 my $account = $server->{account};
485 my ($lang, $date) = @{$self->{fixed_fields}};
486 my $fields = $self->{fields};
487 #warn Dumper($fields);
488 #warn FID_INST_ID;
489 #warn $fields->{(FID_INST_ID)};
490 $ils->check_inst_id($fields->{(FID_INST_ID)}, "handle_patron_status");
491 $patron = $ils->find_patron($fields->{(FID_PATRON_ID)});
492 $resp = build_patron_status($patron, $lang, $fields, $server );
493 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
494 return (PATRON_STATUS_REQ);
497 sub handle_checkout {
498 my ($self, $server) = @_;
499 my $account = $server->{account};
500 my $ils = $server->{ils};
501 my $inst = $ils->institution;
502 my ($sc_renewal_policy, $no_block, $trans_date, $nb_due_date);
503 my $fields;
504 my ($patron_id, $item_id, $status);
505 my ($item, $patron);
506 my $resp;
508 ($sc_renewal_policy, $no_block, $trans_date, $nb_due_date) =
509 @{$self->{fixed_fields}};
510 $fields = $self->{fields};
512 $patron_id = $fields->{(FID_PATRON_ID)};
513 $item_id = $fields->{(FID_ITEM_ID)};
516 if ($no_block eq 'Y') {
517 # Off-line transactions need to be recorded, but there's
518 # not a lot we can do about it
519 syslog("LOG_WARNING", "received no-block checkout from terminal '%s'",
520 $account->{id});
522 $status = $ils->checkout_no_block($patron_id, $item_id,
523 $sc_renewal_policy,
524 $trans_date, $nb_due_date);
525 } else {
526 # Does the transaction date really matter for items that are
527 # checkout out while the terminal is online? I'm guessing 'no'
528 $status = $ils->checkout($patron_id, $item_id, $sc_renewal_policy);
531 $item = $status->item;
532 $patron = $status->patron;
534 if ($status->ok) {
535 # Item successfully checked out
536 # Fixed fields
537 $resp = CHECKOUT_RESP . '1';
538 $resp .= sipbool($status->renew_ok);
539 if ($ils->supports('magnetic media')) {
540 $resp .= sipbool($item->magnetic_media);
541 } else {
542 $resp .= 'U';
544 # We never return the obsolete 'U' value for 'desensitize'
545 $resp .= sipbool($status->desensitize);
546 $resp .= Sip::timestamp;
548 # Now for the variable fields
549 $resp .= add_field(FID_INST_ID, $inst);
550 $resp .= add_field(FID_PATRON_ID, $patron_id);
551 $resp .= add_field(FID_ITEM_ID, $item_id);
552 $resp .= add_field(FID_TITLE_ID, $item->title_id);
553 if ($item->due_date) {
554 $resp .= add_field(FID_DUE_DATE, Sip::timestamp($item->due_date));
555 } else {
556 $resp .= add_field(FID_DUE_DATE, q{});
559 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
560 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
562 if ($protocol_version >= 2) {
563 if ($ils->supports('security inhibit')) {
564 $resp .= add_field(FID_SECURITY_INHIBIT,
565 $status->security_inhibit);
567 $resp .= maybe_add(FID_MEDIA_TYPE, $item->sip_media_type);
568 $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
570 # Financials
571 if ($status->fee_amount) {
572 $resp .= add_field(FID_FEE_AMT, $status->fee_amount);
573 $resp .= maybe_add(FID_CURRENCY, $status->sip_currency);
574 $resp .= maybe_add(FID_FEE_TYPE, $status->sip_fee_type);
575 $resp .= maybe_add(FID_TRANSACTION_ID,
576 $status->transaction_id);
580 } else {
581 # Checkout failed
582 # Checkout Response: not ok, no renewal, don't know mag. media,
583 # no desensitize
584 $resp = sprintf("120NUN%s", Sip::timestamp);
585 $resp .= add_field(FID_INST_ID, $inst);
586 $resp .= add_field(FID_PATRON_ID, $patron_id);
587 $resp .= add_field(FID_ITEM_ID, $item_id);
589 # If the item is valid, provide the title, otherwise
590 # leave it blank
591 $resp .= add_field(FID_TITLE_ID, $item ? $item->title_id : '');
592 # Due date is required. Since it didn't get checked out,
593 # it's not due, so leave the date blank
594 $resp .= add_field(FID_DUE_DATE, '');
596 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
597 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
599 if ($protocol_version >= 2) {
600 # Is the patron ID valid?
601 $resp .= add_field(FID_VALID_PATRON, sipbool($patron));
603 if ($patron && exists($fields->{FID_PATRON_PWD})) {
604 # Password provided, so we can tell if it was valid or not
605 $resp .= add_field(FID_VALID_PATRON_PWD,
606 sipbool($patron->check_password($fields->{(FID_PATRON_PWD)})));
611 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
612 return(CHECKOUT);
615 sub handle_checkin {
616 my ($self, $server) = @_;
617 my $account = $server->{account};
618 my $ils = $server->{ils};
619 my $my_branch = $ils->institution;
620 my ($current_loc, $inst_id, $item_id, $terminal_pwd, $item_props, $cancel);
621 my ($patron, $item, $status);
622 my $resp = CHECKIN_RESP;
623 my ($no_block, $trans_date, $return_date) = @{$self->{fixed_fields}};
624 my $fields = $self->{fields};
626 $current_loc = $fields->{(FID_CURRENT_LOCN)};
627 $inst_id = $fields->{(FID_INST_ID)};
628 $item_id = $fields->{(FID_ITEM_ID)};
629 $item_props = $fields->{(FID_ITEM_PROPS)};
630 $cancel = $fields->{(FID_CANCEL)};
631 if ($current_loc) {
632 $my_branch = $current_loc;# most scm do not set $current_loc
635 $ils->check_inst_id($inst_id, "handle_checkin");
637 if ($no_block eq 'Y') {
638 # Off-line transactions, ick.
639 syslog("LOG_WARNING", "received no-block checkin from terminal '%s'", $account->{id});
640 $status = $ils->checkin_no_block($item_id, $trans_date, $return_date, $item_props, $cancel);
641 } else {
642 $status = $ils->checkin($item_id, $trans_date, $return_date, $my_branch, $item_props, $cancel);
645 $patron = $status->patron;
646 $item = $status->item;
648 $resp .= $status->ok ? '1' : '0';
649 $resp .= $status->resensitize ? 'Y' : 'N';
650 if ($item && $ils->supports('magnetic media')) {
651 $resp .= sipbool($item->magnetic_media);
652 } else {
653 # item barcode is invalid or system doesn't support 'magnetic media' indicator
654 $resp .= 'U';
657 # apparently we can't trust the returns from Checkin yet (because C4::Circulation::AddReturn is faulty)
658 # So we reproduce the alert logic here.
659 if (not $status->alert) {
660 if ($item->destination_loc and $item->destination_loc ne $my_branch) {
661 $status->alert(1);
662 $status->alert_type('04'); # no hold, just send it
665 $resp .= $status->alert ? 'Y' : 'N';
666 $resp .= Sip::timestamp;
667 $resp .= add_field(FID_INST_ID, $inst_id);
668 $resp .= add_field(FID_ITEM_ID, $item_id);
670 if ($item) {
671 $resp .= add_field(FID_PERM_LOCN, $item->permanent_location);
672 $resp .= maybe_add(FID_TITLE_ID, $item->title_id);
675 if ($protocol_version >= 2) {
676 $resp .= maybe_add(FID_SORT_BIN, $status->sort_bin);
677 if ($patron) {
678 $resp .= add_field(FID_PATRON_ID, $patron->id);
680 if ($item) {
681 $resp .= maybe_add(FID_MEDIA_TYPE, $item->sip_media_type );
682 $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
683 $resp .= maybe_add(FID_COLLECTION_CODE, $item->collection_code );
684 $resp .= maybe_add(FID_CALL_NUMBER, $item->call_number );
685 $resp .= maybe_add(FID_DESTINATION_LOCATION, $item->destination_loc );
686 $resp .= maybe_add(FID_HOLD_PATRON_ID, $item->hold_patron_bcode );
687 $resp .= maybe_add(FID_HOLD_PATRON_NAME, $item->hold_patron_name );
688 if ($status->hold and $status->hold->{branchcode} ne $item->destination_loc) {
689 warn 'SIP hold mismatch: $status->hold->{branchcode}=' . $status->hold->{branchcode} . '; $item->destination_loc=' . $item->destination_loc;
690 # just me being paranoid.
695 $resp .= maybe_add(FID_ALERT_TYPE, $status->alert_type) if $status->alert;
696 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
697 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
699 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
701 return(CHECKIN);
704 sub handle_block_patron {
705 my ($self, $server) = @_;
706 my $account = $server->{account};
707 my $ils = $server->{ils};
708 my ($card_retained, $trans_date);
709 my ($inst_id, $blocked_card_msg, $patron_id, $terminal_pwd);
710 my ($fields,$resp,$patron);
712 ($card_retained, $trans_date) = @{$self->{fixed_fields}};
713 $fields = $self->{fields};
714 $inst_id = $fields->{(FID_INST_ID)};
715 $blocked_card_msg = $fields->{(FID_BLOCKED_CARD_MSG)};
716 $patron_id = $fields->{(FID_PATRON_ID)};
717 $terminal_pwd = $fields->{(FID_TERMINAL_PWD)};
719 # Terminal passwords are different from account login
720 # passwords, but I have no idea what to do with them. So,
721 # I'll just ignore them for now.
723 # FIXME ???
725 $ils->check_inst_id($inst_id, "block_patron");
726 $patron = $ils->find_patron($patron_id);
728 # The correct response for a "Block Patron" message is a
729 # "Patron Status Response", so use that handler to generate
730 # the message, but then return the correct code from here.
732 # Normally, the language is provided by the "Patron Status"
733 # fixed field, but since we're not responding to one of those
734 # we'll just say, "Unspecified", as per the spec. Let the
735 # terminal default to something that, one hopes, will be
736 # intelligible
737 if ($patron) {
738 # Valid patron id
739 $patron->block($card_retained, $blocked_card_msg);
742 $resp = build_patron_status( $patron, $patron->language, $fields, $server );
743 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
744 return(BLOCK_PATRON);
747 sub handle_sc_status {
748 my ($self, $server) = @_;
749 ($server) or warn "handle_sc_status error: no \$server argument received.";
750 my ($status, $print_width, $sc_protocol_version) = @{$self->{fixed_fields}};
751 my ($new_proto);
753 if ($sc_protocol_version =~ /^1\./) {
754 $new_proto = 1;
755 } elsif ($sc_protocol_version =~ /^2\./) {
756 $new_proto = 2;
757 } else {
758 syslog("LOG_WARNING", "Unrecognized protocol revision '%s', falling back to '1'", $sc_protocol_version);
759 $new_proto = 1;
762 if ($new_proto != $protocol_version) {
763 syslog("LOG_INFO", "Setting protocol level to $new_proto");
764 $protocol_version = $new_proto;
767 if ($status == SC_STATUS_PAPER) {
768 syslog("LOG_WARNING", "Self-Check unit '%s@%s' out of paper",
769 $self->{account}->{id}, $self->{account}->{institution});
770 } elsif ($status == SC_STATUS_SHUTDOWN) {
771 syslog("LOG_WARNING", "Self-Check unit '%s@%s' shutting down",
772 $self->{account}->{id}, $self->{account}->{institution});
775 $self->{account}->{print_width} = $print_width;
776 return (send_acs_status($self, $server) ? SC_STATUS : '');
779 sub handle_request_acs_resend {
780 my ($self, $server) = @_;
782 if (!$last_response) {
783 # We haven't sent anything yet, so respond with a
784 # REQUEST_SC_RESEND msg (p. 16)
785 $self->write_msg(REQUEST_SC_RESEND,undef,$server->{account}->{terminator},$server->{account}->{encoding});
786 } elsif ((length($last_response) < 9)
787 || substr($last_response, -9, 2) ne 'AY') {
788 # When resending a message, we aren't supposed to include
789 # a sequence number, even if the original had one (p. 4).
790 # If the last message didn't have a sequence number, then
791 # we can just send it.
792 print("$last_response\r"); # not write_msg?
793 } else {
794 # Cut out the sequence number and checksum, since the old
795 # checksum is wrong for the resent message.
796 my $rebuilt = substr($last_response, 0, -9);
797 $self->write_msg($rebuilt,undef,$server->{account}->{terminator},$server->{account}->{encoding});
800 return REQUEST_ACS_RESEND;
803 sub login_core {
804 my $server = shift or return;
805 my $uid = shift;
806 my $pwd = shift;
807 my $status = 1; # Assume it all works
808 if (!exists($server->{config}->{accounts}->{$uid})) {
809 syslog("LOG_WARNING", "MsgType::login_core: Unknown login '$uid'");
810 $status = 0;
811 } elsif ($server->{config}->{accounts}->{$uid}->{password} ne $pwd) {
812 syslog("LOG_WARNING", "MsgType::login_core: Invalid password for login '$uid'");
813 $status = 0;
814 } else {
815 # Store the active account someplace handy for everybody else to find.
816 $server->{account} = $server->{config}->{accounts}->{$uid};
817 my $inst = $server->{account}->{institution};
818 $server->{institution} = $server->{config}->{institutions}->{$inst};
819 $server->{policy} = $server->{institution}->{policy};
820 $server->{sip_username} = $uid;
821 $server->{sip_password} = $pwd;
823 my $auth_status = api_auth($uid,$pwd,$inst);
824 if (!$auth_status or $auth_status !~ /^ok$/i) {
825 syslog("LOG_WARNING", "api_auth failed for SIP terminal '%s' of '%s': %s",
826 $uid, $inst, ($auth_status||'unknown'));
827 $status = 0;
828 } else {
829 syslog("LOG_INFO", "Successful login/auth for '%s' of '%s'", $server->{account}->{id}, $inst);
831 # initialize connection to ILS
833 my $module = $server->{config}->{institutions}->{$inst}->{implementation};
834 syslog("LOG_DEBUG", 'login_core: ' . Dumper($module));
835 $module->use;
836 if ($@) {
837 syslog("LOG_ERR", "%s: Loading ILS implementation '%s' for institution '%s' failed",
838 $server->{service}, $module, $inst);
839 die("Failed to load ILS implementation '$module' for $inst");
842 # like ILS->new(), I think.
843 $server->{ils} = $module->new($server->{institution}, $server->{account});
844 if (!$server->{ils}) {
845 syslog("LOG_ERR", "%s: ILS connection to '%s' failed", $server->{service}, $inst);
846 die("Unable to connect to ILS '$inst'");
850 return $status;
853 sub handle_login {
854 my ($self, $server) = @_;
855 my ($uid_algorithm, $pwd_algorithm);
856 my ($uid, $pwd);
857 my $inst;
858 my $fields;
859 my $status = 1; # Assume it all works
861 $fields = $self->{fields};
862 ($uid_algorithm, $pwd_algorithm) = @{$self->{fixed_fields}};
864 $uid = $fields->{(FID_LOGIN_UID)}; # Terminal ID, not patron ID.
865 $pwd = $fields->{(FID_LOGIN_PWD)}; # Terminal PWD, not patron PWD.
867 if ($uid_algorithm || $pwd_algorithm) {
868 syslog("LOG_ERR", "LOGIN: Unsupported non-zero encryption method(s): uid = $uid_algorithm, pwd = $pwd_algorithm");
869 $status = 0;
871 else { $status = login_core($server,$uid,$pwd); }
873 $self->write_msg(LOGIN_RESP . $status,undef,$server->{account}->{terminator},$server->{account}->{encoding});
874 return $status ? LOGIN : '';
878 # Build the detailed summary information for the Patron
879 # Information Response message based on the first 'Y' that appears
880 # in the 'summary' field of the Patron Information reqest. The
881 # specification says that only one 'Y' can appear in that field,
882 # and we're going to believe it.
884 sub summary_info {
885 my ($ils, $patron, $summary, $start, $end) = @_;
886 my $resp = '';
887 my $summary_type;
889 # Map from offsets in the "summary" field of the Patron Information
890 # message to the corresponding field and handler
892 my @summary_map = (
893 { func => $patron->can( "hold_items"), fid => FID_HOLD_ITEMS },
894 { func => $patron->can("overdue_items"), fid => FID_OVERDUE_ITEMS },
895 { func => $patron->can("charged_items"), fid => FID_CHARGED_ITEMS },
896 { func => $patron->can( "fine_items"), fid => FID_FINE_ITEMS },
897 { func => $patron->can( "recall_items"), fid => FID_RECALL_ITEMS },
898 { func => $patron->can("unavail_holds"), fid => FID_UNAVAILABLE_HOLD_ITEMS },
901 if (($summary_type = index($summary, 'Y')) == -1) {
902 return ''; # No detailed information required
905 syslog("LOG_DEBUG", "Summary_info: index == '%d', field '%s'",
906 $summary_type, $summary_map[$summary_type]->{fid});
908 my $func = $summary_map[$summary_type]->{func};
909 my $fid = $summary_map[$summary_type]->{fid};
910 my $itemlist = &$func($patron, $start, $end);
912 syslog("LOG_DEBUG", "summary_info: list = (%s)", join(", ", @{$itemlist}));
913 foreach my $i (@{$itemlist}) {
914 $resp .= add_field($fid, $i->{barcode});
917 return $resp;
920 sub handle_patron_info {
921 my ($self, $server) = @_;
922 my $ils = $server->{ils};
923 my ($lang, $trans_date, $summary) = @{$self->{fixed_fields}};
924 my $fields = $self->{fields};
925 my ($inst_id, $patron_id, $terminal_pwd, $patron_pwd, $start, $end);
926 my ($resp, $patron, $count);
928 $inst_id = $fields->{(FID_INST_ID)};
929 $patron_id = $fields->{(FID_PATRON_ID)};
930 $terminal_pwd = $fields->{(FID_TERMINAL_PWD)};
931 $patron_pwd = $fields->{(FID_PATRON_PWD)};
932 $start = $fields->{(FID_START_ITEM)};
933 $end = $fields->{(FID_END_ITEM)};
935 $patron = $ils->find_patron($patron_id);
937 $resp = (PATRON_INFO_RESP);
938 if ($patron) {
939 $resp .= patron_status_string($patron);
940 $resp .= (defined($lang) and length($lang) ==3) ? $lang : $patron->language;
941 $resp .= Sip::timestamp();
943 $resp .= add_count('patron_info/hold_items',
944 scalar @{$patron->hold_items});
945 $resp .= add_count('patron_info/overdue_items',
946 scalar @{$patron->overdue_items});
947 $resp .= add_count('patron_info/charged_items',
948 scalar @{$patron->charged_items});
949 $resp .= add_count('patron_info/fine_items',
950 scalar @{$patron->fine_items});
951 $resp .= add_count('patron_info/recall_items',
952 scalar @{$patron->recall_items});
953 $resp .= add_count('patron_info/unavail_holds',
954 scalar @{$patron->unavail_holds});
956 $resp .= add_field(FID_INST_ID, ($ils->institution_id || 'SIP2'));
958 # while the patron ID we got from the SC is valid, let's
959 # use the one returned from the ILS, just in case...
960 $resp .= add_field(FID_PATRON_ID, $patron->id);
961 $resp .= add_field(FID_PERSONAL_NAME, $patron->name);
963 # TODO: add code for the fields
964 # hold items limit
965 # overdue items limit
966 # charged items limit
968 $resp .= add_field(FID_VALID_PATRON, 'Y');
969 if (defined($patron_pwd)) {
970 # If patron password was provided, report whether it was right or not.
971 $resp .= add_field(FID_VALID_PATRON_PWD,
972 sipbool($patron->check_password($patron_pwd)));
975 $resp .= maybe_add(FID_CURRENCY, $patron->currency);
976 $resp .= maybe_add(FID_FEE_AMT, $patron->fee_amount);
977 $resp .= add_field(FID_FEE_LMT, $patron->fee_limit);
979 # TODO: zero or more item details for 2.0 can go here:
980 # hold_items
981 # overdue_items
982 # charged_items
983 # fine_items
984 # recall_items
986 $resp .= summary_info($ils, $patron, $summary, $start, $end);
988 $resp .= maybe_add(FID_HOME_ADDR, $patron->address);
989 $resp .= maybe_add(FID_EMAIL, $patron->email_addr);
990 $resp .= maybe_add(FID_HOME_PHONE, $patron->home_phone);
992 # SIP 2.0 extensions used by Envisionware
993 # Other terminals will ignore unrecognized fields (unrecognized field identifiers)
994 $resp .= maybe_add(FID_PATRON_BIRTHDATE, $patron->birthdate);
995 $resp .= maybe_add(FID_PATRON_CLASS, $patron->ptype);
997 # Custom protocol extension to report patron internet privileges
998 $resp .= maybe_add(FID_INET_PROFILE, $patron->inet_privileges);
1000 $resp .= maybe_add( FID_SCREEN_MSG, $patron->screen_msg );
1001 $resp .= maybe_add( FID_SCREEN_MSG, $patron->{branchcode} )
1002 if ( $server->{account}->{send_patron_home_library_in_af} );
1004 $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line);
1005 } else {
1006 # Invalid patron ID:
1007 # no privileges, no items associated,
1008 # no personal name, and is invalid (if we're using 2.00)
1009 $resp .= 'YYYY' . (' ' x 10) . $lang . Sip::timestamp();
1010 $resp .= '0000' x 6;
1012 $resp .= add_field(FID_INST_ID, ($ils->institution_id || 'SIP2'));
1013 # patron ID is invalid, but field is required, so just echo it back
1014 $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)});
1015 $resp .= add_field(FID_PERSONAL_NAME, '');
1017 if ($protocol_version >= 2) {
1018 $resp .= add_field(FID_VALID_PATRON, 'N');
1022 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1023 return(PATRON_INFO);
1026 sub handle_end_patron_session {
1027 my ($self, $server) = @_;
1028 my $ils = $server->{ils};
1029 my $trans_date;
1030 my $fields = $self->{fields};
1031 my $resp = END_SESSION_RESP;
1032 my ($status, $screen_msg, $print_line);
1034 ($trans_date) = @{$self->{fixed_fields}};
1036 $ils->check_inst_id($fields->{(FID_INST_ID)}, 'handle_end_patron_session');
1038 ($status, $screen_msg, $print_line) = $ils->end_patron_session($fields->{(FID_PATRON_ID)});
1040 $resp .= $status ? 'Y' : 'N';
1041 $resp .= Sip::timestamp();
1043 $resp .= add_field(FID_INST_ID, $server->{ils}->institution);
1044 $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)});
1046 $resp .= maybe_add(FID_SCREEN_MSG, $screen_msg);
1047 $resp .= maybe_add(FID_PRINT_LINE, $print_line);
1049 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1051 return(END_PATRON_SESSION);
1054 sub handle_fee_paid {
1055 my ($self, $server) = @_;
1056 my $ils = $server->{ils};
1057 my ($trans_date, $fee_type, $pay_type, $currency) = @{ $self->{fixed_fields} };
1058 my $fields = $self->{fields};
1059 my ($fee_amt, $inst_id, $patron_id, $terminal_pwd, $patron_pwd);
1060 my ($fee_id, $trans_id);
1061 my $status;
1062 my $resp = FEE_PAID_RESP;
1064 $fee_amt = $fields->{(FID_FEE_AMT)};
1065 $inst_id = $fields->{(FID_INST_ID)};
1066 $patron_id = $fields->{(FID_PATRON_ID)};
1067 $patron_pwd = $fields->{(FID_PATRON_PWD)};
1068 $fee_id = $fields->{(FID_FEE_ID)};
1069 $trans_id = $fields->{(FID_TRANSACTION_ID)};
1071 $ils->check_inst_id($inst_id, "handle_fee_paid");
1073 $status = $ils->pay_fee($patron_id, $patron_pwd, $fee_amt, $fee_type,
1074 $pay_type, $fee_id, $trans_id, $currency);
1076 $resp .= ($status->ok ? 'Y' : 'N') . Sip::timestamp;
1077 $resp .= add_field(FID_INST_ID, $inst_id);
1078 $resp .= add_field(FID_PATRON_ID, $patron_id);
1079 $resp .= maybe_add(FID_TRANSACTION_ID, $status->transaction_id);
1080 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
1081 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
1083 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1085 return(FEE_PAID);
1088 sub handle_item_information {
1089 my ($self, $server) = @_;
1090 my $ils = $server->{ils};
1091 my $trans_date;
1092 my $fields = $self->{fields};
1093 my $resp = ITEM_INFO_RESP;
1094 my $item;
1095 my $i;
1097 ($trans_date) = @{$self->{fixed_fields}};
1099 $ils->check_inst_id($fields->{(FID_INST_ID)}, "handle_item_information");
1101 $item = $ils->find_item($fields->{(FID_ITEM_ID)});
1103 if (!defined($item)) {
1104 # Invalid Item ID
1105 # "Other" circ stat, "Other" security marker, "Unknown" fee type
1106 $resp .= "010101";
1107 $resp .= Sip::timestamp;
1108 # Just echo back the invalid item id
1109 $resp .= add_field(FID_ITEM_ID, $fields->{(FID_ITEM_ID)});
1110 # title id is required, but we don't have one
1111 $resp .= add_field(FID_TITLE_ID, '');
1112 } else {
1113 # Valid Item ID, send the good stuff
1114 $resp .= $item->sip_circulation_status;
1115 $resp .= $item->sip_security_marker;
1116 $resp .= $item->sip_fee_type;
1117 $resp .= Sip::timestamp;
1119 $resp .= add_field(FID_ITEM_ID, $item->id);
1120 $resp .= add_field(FID_TITLE_ID, $item->title_id);
1122 $resp .= maybe_add(FID_MEDIA_TYPE, $item->sip_media_type);
1123 $resp .= maybe_add(FID_PERM_LOCN, $item->permanent_location);
1124 $resp .= maybe_add(FID_CURRENT_LOCN, $item->current_location);
1125 $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
1127 if (($i = $item->fee) != 0) {
1128 $resp .= add_field(FID_CURRENCY, $item->fee_currency);
1129 $resp .= add_field(FID_FEE_AMT, $i);
1131 $resp .= maybe_add(FID_OWNER, $item->owner);
1133 if (($i = scalar @{$item->hold_queue}) > 0) {
1134 $resp .= add_field(FID_HOLD_QUEUE_LEN, $i);
1136 if ($item->due_date) {
1137 $resp .= add_field(FID_DUE_DATE, Sip::timestamp($item->due_date));
1139 if (($i = $item->recall_date) != 0) {
1140 $resp .= add_field(FID_RECALL_DATE, Sip::timestamp($i));
1142 if (($i = $item->hold_pickup_date) != 0) {
1143 $resp .= add_field(FID_HOLD_PICKUP_DATE, Sip::timestamp($i));
1146 $resp .= maybe_add(FID_SCREEN_MSG, $item->screen_msg);
1147 $resp .= maybe_add(FID_PRINT_LINE, $item->print_line);
1150 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1152 return(ITEM_INFORMATION);
1155 sub handle_item_status_update {
1156 my ($self, $server) = @_;
1157 my $ils = $server->{ils};
1158 my ($trans_date, $item_id, $terminal_pwd, $item_props);
1159 my $fields = $self->{fields};
1160 my $status;
1161 my $item;
1162 my $resp = ITEM_STATUS_UPDATE_RESP;
1164 ($trans_date) = @{$self->{fixed_fields}};
1166 $ils->check_inst_id($fields->{(FID_INST_ID)});
1168 $item_id = $fields->{(FID_ITEM_ID)};
1169 $item_props = $fields->{(FID_ITEM_PROPS)};
1171 if (!defined($item_id)) {
1172 syslog("LOG_WARNING",
1173 "handle_item_status: received message without Item ID field");
1174 } else {
1175 $item = $ils->find_item($item_id);
1178 if (!$item) {
1179 # Invalid Item ID
1180 $resp .= '0';
1181 $resp .= Sip::timestamp;
1182 $resp .= add_field(FID_ITEM_ID, $item_id);
1183 } else {
1184 # Valid Item ID
1186 $status = $item->status_update($item_props);
1188 $resp .= $status->ok ? '1' : '0';
1189 $resp .= Sip::timestamp;
1191 $resp .= add_field(FID_ITEM_ID, $item->id);
1192 $resp .= add_field(FID_TITLE_ID, $item->title_id);
1193 $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
1196 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
1197 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
1199 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1201 return(ITEM_STATUS_UPDATE);
1204 sub handle_patron_enable {
1205 my ($self, $server) = @_;
1206 my $ils = $server->{ils};
1207 my $fields = $self->{fields};
1208 my ($trans_date, $patron_id, $terminal_pwd, $patron_pwd);
1209 my ($status, $patron);
1210 my $resp = PATRON_ENABLE_RESP;
1212 ($trans_date) = @{$self->{fixed_fields}};
1213 $patron_id = $fields->{(FID_PATRON_ID)};
1214 $patron_pwd = $fields->{(FID_PATRON_PWD)};
1216 syslog("LOG_DEBUG", "handle_patron_enable: patron_id: '%s', patron_pwd: '%s'",
1217 $patron_id, $patron_pwd);
1219 $patron = $ils->find_patron($patron_id);
1221 if (!defined($patron)) {
1222 # Invalid patron ID
1223 $resp .= 'YYYY' . (' ' x 10) . '000' . Sip::timestamp();
1224 $resp .= add_field(FID_PATRON_ID, $patron_id);
1225 $resp .= add_field(FID_PERSONAL_NAME, '');
1226 $resp .= add_field(FID_VALID_PATRON, 'N');
1227 $resp .= add_field(FID_VALID_PATRON_PWD, 'N');
1228 } else {
1229 # valid patron
1230 if (!defined($patron_pwd) || $patron->check_password($patron_pwd)) {
1231 # Don't enable the patron if there was an invalid password
1232 $status = $patron->enable;
1234 $resp .= patron_status_string($patron);
1235 $resp .= $patron->language . Sip::timestamp();
1237 $resp .= add_field(FID_PATRON_ID, $patron->id);
1238 $resp .= add_field(FID_PERSONAL_NAME, $patron->name);
1239 if (defined($patron_pwd)) {
1240 $resp .= add_field(FID_VALID_PATRON_PWD,
1241 sipbool($patron->check_password($patron_pwd)));
1243 $resp .= add_field(FID_VALID_PATRON, 'Y');
1244 $resp .= maybe_add(FID_SCREEN_MSG, $patron->screen_msg);
1245 $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line);
1248 $resp .= add_field(FID_INST_ID, $ils->institution);
1250 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1252 return(PATRON_ENABLE);
1255 sub handle_hold {
1256 my ($self, $server) = @_;
1257 my $ils = $server->{ils};
1258 my ($hold_mode, $trans_date);
1259 my ($expiry_date, $pickup_locn, $hold_type, $patron_id, $patron_pwd);
1260 my ($item_id, $title_id, $fee_ack);
1261 my $fields = $self->{fields};
1262 my $status;
1263 my $resp = HOLD_RESP;
1265 ($hold_mode, $trans_date) = @{$self->{fixed_fields}};
1267 $ils->check_inst_id($fields->{(FID_INST_ID)}, "handle_hold");
1269 $patron_id = $fields->{(FID_PATRON_ID) };
1270 $expiry_date = $fields->{(FID_EXPIRATION) } || '';
1271 $pickup_locn = $fields->{(FID_PICKUP_LOCN)} || '';
1272 $hold_type = $fields->{(FID_HOLD_TYPE) } || '2'; # Any copy of title
1273 $patron_pwd = $fields->{(FID_PATRON_PWD) };
1274 $item_id = $fields->{(FID_ITEM_ID) } || '';
1275 $title_id = $fields->{(FID_TITLE_ID) } || '';
1276 $fee_ack = $fields->{(FID_FEE_ACK) } || 'N';
1278 if ($hold_mode eq '+') {
1279 $status = $ils->add_hold($patron_id, $patron_pwd, $item_id, $title_id,
1280 $expiry_date, $pickup_locn, $hold_type, $fee_ack);
1281 } elsif ($hold_mode eq '-') {
1282 $status = $ils->cancel_hold($patron_id, $patron_pwd, $item_id, $title_id);
1283 } elsif ($hold_mode eq '*') {
1284 $status = $ils->alter_hold($patron_id, $patron_pwd, $item_id, $title_id,
1285 $expiry_date, $pickup_locn, $hold_type, $fee_ack);
1286 } else {
1287 syslog("LOG_WARNING", "handle_hold: Unrecognized hold mode '%s' from terminal '%s'",
1288 $hold_mode, $server->{account}->{id});
1289 $status = $ils->Transaction::Hold; # new?
1290 $status->screen_msg("System error. Please contact library staff.");
1293 $resp .= $status->ok;
1294 $resp .= sipbool($status->item && $status->item->available($patron_id));
1295 $resp .= Sip::timestamp;
1297 if ($status->ok) {
1298 $resp .= add_field(FID_PATRON_ID, $status->patron->id);
1300 ($status->expiration_date) and
1301 $resp .= maybe_add(FID_EXPIRATION,
1302 Sip::timestamp($status->expiration_date));
1303 $resp .= maybe_add(FID_QUEUE_POS, $status->queue_position);
1304 $resp .= maybe_add(FID_PICKUP_LOCN, $status->pickup_location);
1305 $resp .= maybe_add(FID_ITEM_ID, $status->item->id);
1306 $resp .= maybe_add(FID_TITLE_ID, $status->item->title_id);
1307 } else {
1308 # Not ok. still need required fields
1309 $resp .= add_field(FID_PATRON_ID, $patron_id);
1312 $resp .= add_field(FID_INST_ID, $ils->institution);
1313 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
1314 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
1316 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1318 return(HOLD);
1321 sub handle_renew {
1322 my ($self, $server) = @_;
1323 my $ils = $server->{ils};
1324 my ($third_party, $no_block, $trans_date, $nb_due_date);
1325 my ($patron_id, $patron_pwd, $item_id, $title_id, $item_props, $fee_ack);
1326 my $fields = $self->{fields};
1327 my $status;
1328 my ($patron, $item);
1329 my $resp = RENEW_RESP;
1331 ($third_party, $no_block, $trans_date, $nb_due_date) =
1332 @{$self->{fixed_fields}};
1334 $ils->check_inst_id($fields->{(FID_INST_ID)}, "handle_renew");
1336 if ($no_block eq 'Y') {
1337 syslog("LOG_WARNING",
1338 "handle_renew: recieved 'no block' renewal from terminal '%s'",
1339 $server->{account}->{id});
1342 $patron_id = $fields->{(FID_PATRON_ID)};
1343 $patron_pwd = $fields->{(FID_PATRON_PWD)};
1344 $item_id = $fields->{(FID_ITEM_ID)};
1345 $title_id = $fields->{(FID_TITLE_ID)};
1346 $item_props = $fields->{(FID_ITEM_PROPS)};
1347 $fee_ack = $fields->{(FID_FEE_ACK)};
1349 $status = $ils->renew($patron_id, $patron_pwd, $item_id, $title_id,
1350 $no_block, $nb_due_date, $third_party,
1351 $item_props, $fee_ack);
1353 $patron = $status->patron;
1354 $item = $status->item;
1356 if ($status->renewal_ok) {
1357 $resp .= '1';
1358 $resp .= $status->renewal_ok ? 'Y' : 'N';
1359 if ($ils->supports('magnetic media')) {
1360 $resp .= sipbool($item->magnetic_media);
1361 } else {
1362 $resp .= 'U';
1364 $resp .= sipbool($status->desensitize);
1365 $resp .= Sip::timestamp;
1366 $resp .= add_field(FID_PATRON_ID, $patron->id);
1367 $resp .= add_field(FID_ITEM_ID, $item->id);
1368 $resp .= add_field(FID_TITLE_ID, $item->title_id);
1369 if ($item->due_date) {
1370 $resp .= add_field(FID_DUE_DATE, Sip::timestamp($item->due_date));
1371 } else {
1372 $resp .= add_field(FID_DUE_DATE, q{});
1374 if ($ils->supports('security inhibit')) {
1375 $resp .= add_field(FID_SECURITY_INHIBIT,
1376 $status->security_inhibit);
1378 $resp .= add_field(FID_MEDIA_TYPE, $item->sip_media_type);
1379 $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
1380 } else {
1381 # renew failed for some reason
1382 # not OK, renewal not OK, Unknown media type (why bother checking?)
1383 $resp .= '0NUN';
1384 $resp .= Sip::timestamp;
1385 # If we found the patron or the item, the return the ILS
1386 # information, otherwise echo back the infomation we received
1387 # from the terminal
1388 $resp .= add_field(FID_PATRON_ID, $patron ? $patron->id : $patron_id);
1389 $resp .= add_field(FID_ITEM_ID, $item ? $item->id : $item_id );
1390 $resp .= add_field(FID_TITLE_ID, $item ? $item->title_id : $title_id );
1391 $resp .= add_field(FID_DUE_DATE, '');
1394 if ($status->fee_amount) {
1395 $resp .= add_field(FID_FEE_AMT, $status->fee_amount);
1396 $resp .= maybe_add(FID_CURRENCY, $status->sip_currency);
1397 $resp .= maybe_add(FID_FEE_TYPE, $status->sip_fee_type);
1398 $resp .= maybe_add(FID_TRANSACTION_ID, $status->transaction_id);
1401 $resp .= add_field(FID_INST_ID, $ils->institution);
1402 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
1403 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
1405 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1407 return(RENEW);
1410 sub handle_renew_all {
1411 # my ($third_party, $no_block, $nb_due_date, $fee_ack, $patron);
1413 my ($self, $server) = @_;
1414 my $ils = $server->{ils};
1415 my ($trans_date, $patron_id, $patron_pwd, $terminal_pwd, $fee_ack);
1416 my $fields = $self->{fields};
1417 my $resp = RENEW_ALL_RESP;
1418 my $status;
1419 my (@renewed, @unrenewed);
1421 $ils->check_inst_id($fields->{(FID_INST_ID)}, "handle_renew_all");
1423 ($trans_date) = @{$self->{fixed_fields}};
1425 $patron_id = $fields->{(FID_PATRON_ID)};
1426 $patron_pwd = $fields->{(FID_PATRON_PWD)};
1427 $terminal_pwd = $fields->{(FID_TERMINAL_PWD)};
1428 $fee_ack = $fields->{(FID_FEE_ACK)};
1430 $status = $ils->renew_all($patron_id, $patron_pwd, $fee_ack);
1432 $resp .= $status->ok ? '1' : '0';
1434 if (!$status->ok) {
1435 $resp .= add_count("renew_all/renewed_count" , 0);
1436 $resp .= add_count("renew_all/unrenewed_count", 0);
1437 @renewed = ();
1438 @unrenewed = ();
1439 } else {
1440 @renewed = (@{$status->renewed});
1441 @unrenewed = (@{$status->unrenewed});
1442 $resp .= add_count("renew_all/renewed_count" , scalar @renewed );
1443 $resp .= add_count("renew_all/unrenewed_count", scalar @unrenewed);
1446 $resp .= Sip::timestamp;
1447 $resp .= add_field(FID_INST_ID, $ils->institution);
1449 $resp .= join('', map(add_field(FID_RENEWED_ITEMS , $_), @renewed ));
1450 $resp .= join('', map(add_field(FID_UNRENEWED_ITEMS, $_), @unrenewed));
1452 $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
1453 $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
1455 $self->write_msg($resp,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1457 return(RENEW_ALL);
1461 # send_acs_status($self, $server)
1463 # Send an ACS Status message, which is contains lots of little fields
1464 # of information gleaned from all sorts of places.
1467 my @message_type_names = (
1468 "patron status request",
1469 "checkout",
1470 "checkin",
1471 "block patron",
1472 "acs status",
1473 "request sc/acs resend",
1474 "login",
1475 "patron information",
1476 "end patron session",
1477 "fee paid",
1478 "item information",
1479 "item status update",
1480 "patron enable",
1481 "hold",
1482 "renew",
1483 "renew all",
1486 sub send_acs_status {
1487 my ($self, $server, $screen_msg, $print_line) = @_;
1488 my $msg = ACS_STATUS;
1489 ($server) or die "send_acs_status error: no \$server argument received";
1490 my $account = $server->{account} or die "send_acs_status error: no 'account' in \$server object:\n" . Dumper($server);
1491 my $policy = $server->{policy} or die "send_acs_status error: no 'policy' in \$server object:\n" . Dumper($server);
1492 my $ils = $server->{ils} or die "send_acs_status error: no 'ils' in \$server object:\n" . Dumper($server);
1493 my ($online_status, $checkin_ok, $checkout_ok, $ACS_renewal_policy);
1494 my ($status_update_ok, $offline_ok, $timeout, $retries);
1496 $online_status = 'Y';
1497 $checkout_ok = sipbool($ils->checkout_ok);
1498 $checkin_ok = sipbool($ils->checkin_ok);
1499 $ACS_renewal_policy = sipbool($policy->{renewal});
1500 $status_update_ok = sipbool($ils->status_update_ok);
1501 $offline_ok = sipbool($ils->offline_ok);
1502 $timeout = sprintf("%03d", $policy->{timeout});
1503 $retries = sprintf("%03d", $policy->{retries});
1505 if (length($timeout) != 3) {
1506 syslog("LOG_ERR", "handle_acs_status: timeout field wrong size: '%s'",
1507 $timeout);
1508 $timeout = '000';
1511 if (length($retries) != 3) {
1512 syslog("LOG_ERR", "handle_acs_status: retries field wrong size: '%s'",
1513 $retries);
1514 $retries = '000';
1517 $msg .= "$online_status$checkin_ok$checkout_ok$ACS_renewal_policy";
1518 $msg .= "$status_update_ok$offline_ok$timeout$retries";
1519 $msg .= Sip::timestamp();
1521 if ($protocol_version == 1) {
1522 $msg .= '1.00';
1523 } elsif ($protocol_version == 2) {
1524 $msg .= '2.00';
1525 } else {
1526 syslog("LOG_ERR",
1527 'Bad setting for $protocol_version, "%s" in send_acs_status',
1528 $protocol_version);
1529 $msg .= '1.00';
1532 # Institution ID
1533 $msg .= add_field(FID_INST_ID, $account->{institution});
1535 if ($protocol_version >= 2) {
1536 # Supported messages: we do it all
1537 my $supported_msgs = '';
1539 foreach my $msg_name (@message_type_names) {
1540 if ($msg_name eq 'request sc/acs resend') {
1541 $supported_msgs .= Sip::sipbool(1);
1542 } else {
1543 $supported_msgs .= Sip::sipbool($ils->supports($msg_name));
1546 if (length($supported_msgs) < 16) {
1547 syslog("LOG_ERR", 'send_acs_status: supported messages "%s" too short', $supported_msgs);
1549 $msg .= add_field(FID_SUPPORTED_MSGS, $supported_msgs);
1552 $msg .= maybe_add(FID_SCREEN_MSG, $screen_msg);
1554 if (defined($account->{print_width}) && defined($print_line)
1555 && $account->{print_width} < length($print_line)) {
1556 syslog("LOG_WARNING", "send_acs_status: print line '%s' too long. Truncating",
1557 $print_line);
1558 $print_line = substr($print_line, 0, $account->{print_width});
1561 $msg .= maybe_add(FID_PRINT_LINE, $print_line);
1563 # Do we want to tell the terminal its location?
1565 $self->write_msg($msg,undef,$server->{account}->{terminator},$server->{account}->{encoding});
1566 return 1;
1570 # build_patron_status: create the 14-char patron status
1571 # string for the Patron Status message
1573 sub patron_status_string {
1574 my $patron = shift;
1575 my $patron_status;
1577 syslog("LOG_DEBUG", "patron_status_string: %s charge_ok: %s", $patron->id, $patron->charge_ok);
1578 $patron_status = sprintf(
1579 '%s%s%s%s%s%s%s%s%s%s%s%s%s%s',
1580 denied($patron->charge_ok),
1581 denied($patron->renew_ok),
1582 denied($patron->recall_ok),
1583 denied($patron->hold_ok),
1584 boolspace($patron->card_lost),
1585 boolspace($patron->too_many_charged),
1586 boolspace($patron->too_many_overdue),
1587 boolspace($patron->too_many_renewal),
1588 boolspace($patron->too_many_claim_return),
1589 boolspace($patron->too_many_lost),
1590 boolspace($patron->excessive_fines),
1591 boolspace($patron->excessive_fees),
1592 boolspace($patron->recall_overdue),
1593 boolspace($patron->too_many_billed)
1595 return $patron_status;
1598 sub api_auth {
1599 my ($username,$password, $branch) = @_;
1600 $ENV{REMOTE_USER} = $username;
1601 my $query = CGI->new();
1602 $query->param(userid => $username);
1603 $query->param(password => $password);
1604 if ($branch) {
1605 $query->param(branch => $branch);
1607 my ($status, $cookie, $sessionID) = check_api_auth($query, {circulate=>1}, 'intranet');
1608 return $status;
1612 __END__