]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/SIP.pm
User activity tracking: ingress, auth.verify, and login agent
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / SIP.pm
1 #
2 # ILS.pm: Test ILS interface module
3 #
4
5 package OpenILS::SIP;
6 use warnings; use strict;
7
8 use Sys::Syslog qw(syslog);
9 use Time::HiRes q/time/;
10
11 use OpenILS::SIP::Item;
12 use OpenILS::SIP::Patron;
13 use OpenILS::SIP::Transaction;
14 use OpenILS::SIP::Transaction::Checkout;
15 use OpenILS::SIP::Transaction::Checkin;
16 use OpenILS::SIP::Transaction::Renew;
17 use OpenILS::SIP::Transaction::FeePayment;
18
19 use OpenSRF::System;
20 use OpenSRF::AppSession;
21 use OpenILS::Utils::Fieldmapper;
22 use OpenSRF::Utils::SettingsClient;
23 use OpenILS::Application::AppUtils;
24 use OpenSRF::Utils qw/:datetime/;
25 use DateTime::Format::ISO8601;
26 use Encode;
27 use Unicode::Normalize;
28 my $U = 'OpenILS::Application::AppUtils';
29
30 my $editor;
31 my $config;
32 my $target_encoding;    # FIXME: this is configured at the institution level. 
33
34 use Digest::MD5 qw(md5_hex);
35
36 sub new {
37         my ($class, $institution, $login) = @_;
38         my $type = ref($class) || $class;
39         my $self = {};
40
41         $self->{login} = $login;
42
43         $config = $institution;
44         syslog("LOG_DEBUG", "OILS: new ILS '%s'", $institution->{id});
45         $self->{institution} = $institution;
46
47         my $bsconfig     = $institution->{implementation_config}->{bootstrap};
48         $target_encoding = $institution->{implementation_config}->{encoding} || 'ascii';
49
50         syslog('LOG_DEBUG', "OILS: loading bootstrap config: $bsconfig");
51
52         # ingress will persist throughout
53         OpenSRF::AppSession->ingress('sip2');
54         
55         local $/ = "\n";    # why?
56         OpenSRF::System->bootstrap_client(config_file => $bsconfig);
57         syslog('LOG_DEBUG', "OILS: bootstrap loaded..");
58
59         $self->{osrf_config} = OpenSRF::Utils::SettingsClient->new;
60
61         Fieldmapper->import($self->{osrf_config}->config_value('IDL'));
62
63         bless( $self, $type );
64
65         return undef unless 
66                 $self->login( $login->{id}, $login->{password} );
67
68         return $self;
69 }
70
71 sub fetch_session {
72     my $self = shift;
73
74         my $ses = $U->simplereq( 
75                 'open-ils.auth',
76                 'open-ils.auth.session.retrieve',  $self->{authtoken});
77
78     return undef if $U->event_code($ses); # auth timed out
79     return $self->{login_session} = $ses;
80 }
81
82 sub verify_session {
83         my $self = shift;
84
85     return 1 if $self->fetch_session;
86
87     syslog('LOG_INFO', "OILS: Logging back after session timeout as user ".$self->{login}->{id});
88     return $self->login( $self->{login}->{id}, $self->{login}->{password} );
89 }
90
91 sub editor {
92         return $editor = make_editor();
93 }
94
95 sub config {
96         return $config;
97 }
98
99 sub get_option_value {
100     my($self, $option) = @_;
101     my $ops = $config->{implementation_config}->{options}->{option};
102     $ops = [$ops] unless ref $ops eq 'ARRAY';
103     my @vals = grep { $_->{name} eq $option } @$ops;
104     return @vals ? $vals[0]->{value} : undef;
105 }
106
107
108 # Creates the global editor object
109 my $cstore_init = 1; # call init on first use
110 sub make_editor {
111     OpenILS::Utils::CStoreEditor::init() if $cstore_init;
112     $cstore_init = 0;
113         return OpenILS::Utils::CStoreEditor->new;
114 }
115
116 =head2 clean_text(scalar)
117
118 Evergreen uses the UTF8 encoding for everything from the database up. Perl
119 doesn't know this, however, so we have to convince it to treat our UTF8 strings
120 as UTF8 strings. This may enable OpenNCIP to correctly calculate the checksums
121 for UTF8 text for SIP clients that support such modern options.
122
123 The target encoding is set in the <encoding> element of the SIPServer.pm
124 configuration file.
125
126 =cut
127
128 sub clean_text {
129     my $text = shift || '';
130
131     # Convert our incoming UTF8 data into Perl's internal string format
132
133     # Also convert to Normalization Form D, as the ASCII, iso-8859-1,
134     # and latin-1 encodings (at least) require this to substitute
135     # characters rather than simply returning a string truncated
136     # after the first non-ASCII character
137     $text = NFD(decode_utf8($text));
138
139     if ($target_encoding eq 'ascii') {
140
141         # Try to maintain a reasonable version of the content by
142         # stripping diacritics from the text, given that the SIP client
143         # wants just plain ASCII. This is the base requirement according
144         # to the SIP2 specification.
145
146         # Stripping the combining characters converts ""béè♁ts"
147         # into "bee?ts" instead of "b???ts" - better, eh?
148         $text =~ s/\pM+//og;
149     }
150
151     # Characters that cannot be represented in the target encoding will
152     # generally be replaced with a question mark (?) character.
153     $text = encode($target_encoding, $text);
154
155     return $text;
156 }
157
158 my %org_sn_cache;
159 sub shortname_from_id {
160     my $id = shift or return;
161     return $id->shortname if ref $id;
162     return $org_sn_cache{$id} if $org_sn_cache{$id};
163     return $org_sn_cache{$id} = editor()->retrieve_actor_org_unit($id)->shortname;
164 }
165 sub patron_barcode_from_id {
166     my $id = shift or return;
167     return editor()->search_actor_card({ usr => $id, active => 't' })->[0]->barcode;
168 }
169
170 sub format_date {
171         my $class = shift;
172         my $date = shift;
173         my $type = shift || 'dob';
174
175         return "" unless $date;
176
177         $date = DateTime::Format::ISO8601->new->
178                 parse_datetime(OpenSRF::Utils::cleanse_ISO8601($date));
179         my @time = localtime($date->epoch);
180
181         my $year   = $time[5]+1900;
182         my $mon    = $time[4]+1;
183         my $day    = $time[3];
184         my $hour   = $time[2];
185         my $minute = $time[1];
186         my $second = $time[0];
187   
188         $date = sprintf("%04d%02d%02d", $year, $mon, $day);
189
190         # Due dates need hyphen separators and time of day as well
191         if ($type eq 'due') {
192                 $date = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year, $mon, $day, $hour, $minute, $second);
193         }
194
195         syslog('LOG_DEBUG', "OILS: formatted date [type=$type]: $date");
196         return $date;
197 }
198
199
200
201 sub login {
202         my( $self, $username, $password ) = @_;
203         syslog('LOG_DEBUG', "OILS: Logging in with username $username");
204
205         my $seed = $U->simplereq( 
206                 'open-ils.auth',
207                 'open-ils.auth.authenticate.init', $username );
208
209         my $response = $U->simplereq(
210                 'open-ils.auth', 
211                 'open-ils.auth.authenticate.complete', 
212                 {       
213                         username => $username, 
214                         password => md5_hex($seed . md5_hex($password)), 
215                         type     => 'opac',
216                 }
217         );
218
219         if( my $code = $U->event_code($response) ) {
220                 my $txt = $response->{textcode};
221                 syslog('LOG_WARNING', "OILS: Login failed for $username.  $txt:$code");
222                 return undef;
223         }
224
225         my $key = $response->{payload}->{authtoken};
226         syslog('LOG_INFO', "OILS: Login succeeded for $username : authkey = $key");
227
228     $self->fetch_session; # to cache the login
229
230         return $self->{authtoken} = $key;
231 }
232
233
234 sub find_patron {
235         my $self = shift;
236         return OpenILS::SIP::Patron->new(@_);
237 }
238
239
240 sub find_item {
241         my $self = shift;
242         return OpenILS::SIP::Item->new(@_);
243 }
244
245
246 sub institution {
247     my $self = shift;
248     return $self->{institution}->{id};  # consider making this return the whole institution
249 }
250
251 sub institution_id {
252     my $self = shift;
253     return $self->{institution}->{id};  # then use this for just the ID
254 }
255
256 sub supports {
257         my ($self, $op) = @_;
258         my ($i) = grep { $_->{name} eq $op }  
259                 @{$config->{implementation_config}->{supports}->{item}};
260         return to_bool($i->{value});
261 }
262
263 sub check_inst_id {
264     my ($self, $id, $whence) = @_;
265     if ($id ne $self->{institution}->{id}) {
266         syslog("LOG_WARNING", "OILS: %s: received institution '%s', expected '%s'", $whence, $id, $self->{institution}->{id});
267         # Just an FYI check, we don't expect the user to change location from that in SIPconfig.xml
268     }
269 }
270
271
272 sub to_bool {
273     my $bool = shift;
274     # If it's defined, and matches a true sort of string, or is
275     # a non-zero number, then it's true.
276     defined($bool) or return;                   # false
277     ($bool =~ /true|y|yes/i) and return 1;      # true
278     return ($bool =~ /^\d+$/ and $bool != 0);   # true for non-zero numbers, false otherwise
279 }
280
281 sub checkout_ok {
282         return to_bool($config->{policy}->{checkout});
283 }
284
285 sub checkin_ok {
286         return to_bool($config->{policy}->{checkin});
287 }
288
289 sub renew_ok {
290         return to_bool($config->{policy}->{renewal});
291 }
292
293 sub status_update_ok {
294         return to_bool($config->{policy}->{status_update});
295 }
296
297 sub offline_ok {
298         return to_bool($config->{policy}->{offline});
299 }
300
301
302
303 ##
304 ## Checkout(patron_id, item_id, sc_renew, fee_ack):
305 ##    patron_id & item_id are the identifiers send by the terminal
306 ##    sc_renew is the renewal policy configured on the terminal
307 ## returns a status opject that can be queried for the various bits
308 ## of information that the protocol (SIP or NCIP) needs to generate
309 ## the response.
310 ##    fee_ack is the fee_acknowledged field (BO) sent from the sc
311 ## when doing chargeable loans.
312 ##
313
314 sub checkout {
315         my ($self, $patron_id, $item_id, $sc_renew, $fee_ack) = @_;
316         # In order to allow renewals the selfcheck AND the config have to say they are allowed
317         $sc_renew = (chr($sc_renew) eq 'Y' && $self->renew_ok());
318
319         $self->verify_session;
320
321         syslog('LOG_DEBUG', "OILS: OpenILS::Checkout attempt: patron=$patron_id, item=$item_id");
322
323     my $xact   = OpenILS::SIP::Transaction::Checkout->new( authtoken => $self->{authtoken} );
324     my $patron = $self->find_patron($patron_id);
325     my $item   = $self->find_item($item_id);
326
327         $xact->patron($patron);
328         $xact->item($item);
329
330         if (!$patron) {
331                 $xact->screen_msg("Invalid Patron Barcode '$patron_id'");
332                 return $xact;
333         }
334
335         if (!$patron->charge_ok) {
336                 $xact->screen_msg("Patron Blocked");
337                 return $xact;
338         }
339
340         if( !$item ) {
341                 $xact->screen_msg("Invalid Item Barcode: '$item_id'");
342                 return $xact;
343         }
344
345         syslog('LOG_DEBUG', "OILS: OpenILS::Checkout data loaded OK, checking out...");
346
347         if ($item->{patron} && ($item->{patron} eq $patron_id)) {
348                 $xact->renew_ok(1); # So that accept/reject responses have the correct value later
349                 if($sc_renew) {
350                         syslog('LOG_INFO', "OILS: OpenILS::Checkout data loaded OK, doing renew...");
351                 } else {
352                         syslog('LOG_INFO', "OILS: OpenILS::Checkout appears to be renew, but renewal disallowed...");
353                         $xact->screen_msg("Renewals not permitted");
354                         $xact->ok(0);
355                         return $xact; # Don't attempt later
356         }
357         } elsif ($item->{patron} && ($item->{patron} ne $patron_id)) {
358                 # I can't deal with this right now
359                 # XXX check in then check out?
360                 $xact->screen_msg("Item checked out to another patron");
361                 $xact->ok(0);
362                 return $xact; # Don't wipe out the screen message later
363         } else {
364                 $sc_renew = 0;
365         } 
366
367         # Check for fee and $fee_ack. If there is a fee, and $fee_ack
368         # is 'Y', we proceed, otherwise we reject the checkout.
369         if ($item->fee > 0.0) {
370             $xact->fee_amount($item->fee);
371             $xact->sip_fee_type($item->sip_fee_type);
372             $xact->sip_currency($item->fee_currency);
373             if ($fee_ack && $fee_ack eq 'Y') {
374                 $xact->fee_ack(1);
375             } else {
376                 $xact->screen_msg('Fee required');
377                 $xact->ok(0);
378                 return $xact;
379             }
380         }
381
382         $xact->do_checkout($sc_renew);
383         $xact->desensitize(!$item->magnetic);
384
385         if( $xact->ok ) {
386                 #editor()->commit;
387                 syslog("LOG_DEBUG", "OILS: OpenILS::Checkout: " .
388                         "patron %s checkout %s succeeded", $patron_id, $item_id);
389         } else {
390                 #editor()->xact_rollback;
391                 syslog("LOG_DEBUG", "OILS: OpenILS::Checkout: " .
392                         "patron %s checkout %s FAILED, rolling back xact...", $patron_id, $item_id);
393         }
394
395         return $xact;
396 }
397
398
399 sub checkin {
400         my ($self, $item_id, $inst_id, $trans_date, $return_date,
401         $current_loc, $item_props, $cancel) = @_;
402
403     my $start_time = time();
404
405         $self->verify_session;
406
407         syslog('LOG_DEBUG', "OILS: OpenILS::Checkin of item=$item_id (to $inst_id)");
408         
409     my $xact = OpenILS::SIP::Transaction::Checkin->new(authtoken => $self->{authtoken});
410     my $item = OpenILS::SIP::Item->new($item_id);
411
412     unless ( $xact->item($item) ) {
413         $xact->ok(0);
414         # $circ->alert(1); $circ->alert_type(99);
415         $xact->screen_msg("Invalid Item Barcode: '$item_id'");
416         syslog('LOG_INFO', "OILS: Checkin failed.  " . $xact->screen_msg() );
417         return $xact;
418     }
419
420         $xact->do_checkin( $self, $inst_id, $trans_date, $return_date, $current_loc, $item_props );
421         
422         if ($xact->ok) {
423         $xact->patron($self->find_patron(usr => $xact->{circ_user_id}, slim_user => 1)) if $xact->{circ_user_id};
424         delete $item->{patron};
425         delete $item->{due_date};
426         syslog('LOG_INFO', "OILS: Checkin succeeded");
427     } else {
428         syslog('LOG_WARNING', "OILS: Checkin failed");
429     }
430
431     syslog('LOG_INFO', "OILS: SIP Checkin request took %0.3f seconds", (time() - $start_time));
432         return $xact;
433 }
434
435 ## If the ILS caches patron information, this lets it free it up.
436 ## Also, this could be used for centrally logging session duration.
437 ## We don't do anything with it.
438 sub end_patron_session {
439     my ($self, $patron_id) = @_;
440     return (1, 'Thank you!', '');
441 }
442
443
444 sub pay_fee {
445     my ($self, $patron_id, $patron_pwd, $fee_amt, $fee_type,
446         $pay_type, $fee_id, $trans_id, $currency) = @_;
447
448     $self->verify_session;
449
450     my $xact = OpenILS::SIP::Transaction::FeePayment->new(authtoken => $self->{authtoken});
451     my $patron = $self->find_patron($patron_id);
452
453     if (!$patron) {
454         $xact->screen_msg("Invalid Patron Barcode '$patron_id'");
455         $xact->ok(0);
456         return $xact;
457     }
458
459     $xact->patron($patron);
460     $xact->sip_currency($currency);
461     $xact->fee_amount($fee_amt);
462     $xact->sip_fee_type($fee_type);
463     $xact->transaction_id($trans_id);
464     $xact->fee_id($fee_id);
465     # We don't presently use these, but we might in the future.
466     $xact->patron_password($patron_pwd);
467     $xact->sip_payment_type($pay_type);
468
469     $xact->do_fee_payment();
470
471     return $xact;
472 }
473
474 #sub add_hold {
475 #    my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
476 #       $expiry_date, $pickup_location, $hold_type, $fee_ack) = @_;
477 #    my ($patron, $item);
478 #    my $hold;
479 #    my $trans;
480 #
481 #
482 #    $trans = new ILS::Transaction::Hold;
483 #
484 #    # BEGIN TRANSACTION
485 #    $patron = new ILS::Patron $patron_id;
486 #    if (!$patron
487 #       || (defined($patron_pwd) && !$patron->check_password($patron_pwd))) {
488 #       $trans->screen_msg("Invalid Patron.");
489 #
490 #       return $trans;
491 #    }
492 #
493 #    $item = new ILS::Item ($item_id || $title_id);
494 #    if (!$item) {
495 #       $trans->screen_msg("No such item.");
496 #
497 #       # END TRANSACTION (conditionally)
498 #       return $trans;
499 #    } elsif ($item->fee && ($fee_ack ne 'Y')) {
500 #       $trans->screen_msg = "Fee required to place hold.";
501 #
502 #       # END TRANSACTION (conditionally)
503 #       return $trans;
504 #    }
505 #
506 #    $hold = {
507 #       item_id         => $item->id,
508 #       patron_id       => $patron->id,
509 #       expiration_date => $expiry_date,
510 #       pickup_location => $pickup_location,
511 #       hold_type       => $hold_type,
512 #    };
513 #
514 #    $trans->ok(1);
515 #    $trans->patron($patron);
516 #    $trans->item($item);
517 #    $trans->pickup_location($pickup_location);
518 #
519 #    push(@{$item->hold_queue}, $hold);
520 #    push(@{$patron->{hold_items}}, $hold);
521 #
522 #
523 #    # END TRANSACTION
524 #    return $trans;
525 #}
526 #
527 #sub cancel_hold {
528 #    my ($self, $patron_id, $patron_pwd, $item_id, $title_id) = @_;
529 #    my ($patron, $item, $hold);
530 #    my $trans;
531 #
532 #    $trans = new ILS::Transaction::Hold;
533 #
534 #    # BEGIN TRANSACTION
535 #    $patron = new ILS::Patron $patron_id;
536 #    if (!$patron) {
537 #       $trans->screen_msg("Invalid patron barcode.");
538 #
539 #       return $trans;
540 #    } elsif (defined($patron_pwd) && !$patron->check_password($patron_pwd)) {
541 #       $trans->screen_msg('Invalid patron password.');
542 #
543 #       return $trans;
544 #    }
545 #
546 #    $item = new ILS::Item ($item_id || $title_id);
547 #    if (!$item) {
548 #       $trans->screen_msg("No such item.");
549 #
550 #       # END TRANSACTION (conditionally)
551 #       return $trans;
552 #    }
553 #
554 #    # Remove the hold from the patron's record first
555 #    $trans->ok($patron->drop_hold($item_id));
556 #
557 #    if (!$trans->ok) {
558 #       # We didn't find it on the patron record
559 #       $trans->screen_msg("No such hold on patron record.");
560 #
561 #       # END TRANSACTION (conditionally)
562 #       return $trans;
563 #    }
564 #
565 #    # Now, remove it from the item record.  If it was on the patron
566 #    # record but not on the item record, we'll treat that as success.
567 #    foreach my $i (0 .. scalar @{$item->hold_queue}) {
568 #       $hold = $item->hold_queue->[$i];
569 #
570 #       if ($hold->{patron_id} eq $patron->id) {
571 #           # found it: delete it.
572 #           splice @{$item->hold_queue}, $i, 1;
573 #           last;
574 #       }
575 #    }
576 #
577 #    $trans->screen_msg("Hold Cancelled.");
578 #    $trans->patron($patron);
579 #    $trans->item($item);
580 #
581 #    return $trans;
582 #}
583 #
584 #
585 ## The patron and item id's can't be altered, but the
586 ## date, location, and type can.
587 #sub alter_hold {
588 #    my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
589 #       $expiry_date, $pickup_location, $hold_type, $fee_ack) = @_;
590 #    my ($patron, $item);
591 #    my $hold;
592 #    my $trans;
593 #
594 #    $trans = new ILS::Transaction::Hold;
595 #
596 #    # BEGIN TRANSACTION
597 #    $patron = new ILS::Patron $patron_id;
598 #    if (!$patron) {
599 #       $trans->screen_msg("Invalid patron barcode.");
600 #
601 #       return $trans;
602 #    }
603 #
604 #    foreach my $i (0 .. scalar @{$patron->{hold_items}}) {
605 #       $hold = $patron->{hold_items}[$i];
606 #
607 #       if ($hold->{item_id} eq $item_id) {
608 #           # Found it.  So fix it.
609 #           $hold->{expiration_date} = $expiry_date if $expiry_date;
610 #           $hold->{pickup_location} = $pickup_location if $pickup_location;
611 #           $hold->{hold_type} = $hold_type if $hold_type;
612 #
613 #           $trans->ok(1);
614 #           $trans->screen_msg("Hold updated.");
615 #           $trans->patron($patron);
616 #           $trans->item(new ILS::Item $hold->{item_id});
617 #           last;
618 #       }
619 #    }
620 #
621 #    # The same hold structure is linked into both the patron's
622 #    # list of hold items and into the queue of outstanding holds
623 #    # for the item, so we don't need to search the hold queue for
624 #    # the item, since it's already been updated by the patron code.
625 #
626 #    if (!$trans->ok) {
627 #       $trans->screen_msg("No such outstanding hold.");
628 #    }
629 #
630 #    return $trans;
631 #}
632
633
634 sub renew {
635         my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
636                 $no_block, $nb_due_date, $third_party, $item_props, $fee_ack) = @_;
637
638         $self->verify_session;
639
640         my $trans = OpenILS::SIP::Transaction::Renew->new( authtoken => $self->{authtoken} );
641         $trans->patron($self->find_patron($patron_id));
642         $trans->item($self->find_item($item_id));
643
644         if(!$trans->patron) {
645                 $trans->screen_msg("Invalid patron barcode.");
646                 $trans->ok(0);
647                 return $trans;
648         }
649
650         if(!$trans->patron->renew_ok) {
651                 $trans->screen_msg("Renewals not allowed.");
652                 $trans->ok(0);
653                 return $trans;
654         }
655
656         if(!$trans->item) {
657                 if( $title_id ) {
658                         $trans->screen_msg("Title ID renewal not supported.  Use item barcode.");
659                 } else {
660                         $trans->screen_msg("Invalid item barcode.");
661                 }
662                 $trans->ok(0);
663                 return $trans;
664         }
665
666         if(!$trans->item->{patron} or 
667                         $trans->item->{patron} ne $patron_id) {
668                 $trans->screen_msg("Item not checked out to " . $trans->patron->name);
669                 $trans->ok(0);
670                 return $trans;
671         }
672
673         # Perform the renewal
674         $trans->do_renew();
675
676         $trans->desensitize(0); # It's already checked out
677         $trans->item->{due_date} = $nb_due_date if $no_block eq 'Y';
678         $trans->item->{sip_item_properties} = $item_props if $item_props;
679
680         return $trans;
681 }
682
683
684
685
686
687 #
688 #sub renew_all {
689 #    my ($self, $patron_id, $patron_pwd, $fee_ack) = @_;
690 #    my ($patron, $item_id);
691 #    my $trans;
692 #
693 #    $trans = new ILS::Transaction::RenewAll;
694 #
695 #    $trans->patron($patron = new ILS::Patron $patron_id);
696 #    if (defined $patron) {
697 #       syslog("LOG_DEBUG", "ILS::renew_all: patron '%s': renew_ok: %s",
698 #              $patron->name, $patron->renew_ok);
699 #    } else {
700 #       syslog("LOG_DEBUG", "ILS::renew_all: Invalid patron id: '%s'",
701 #              $patron_id);
702 #    }
703 #
704 #    if (!defined($patron)) {
705 #       $trans->screen_msg("Invalid patron barcode.");
706 #       return $trans;
707 #    } elsif (!$patron->renew_ok) {
708 #       $trans->screen_msg("Renewals not allowed.");
709 #       return $trans;
710 #    } elsif (defined($patron_pwd) && !$patron->check_password($patron_pwd)) {
711 #       $trans->screen_msg("Invalid patron password.");
712 #       return $trans;
713 #    }
714 #
715 #    foreach $item_id (@{$patron->{items}}) {
716 #       my $item = new ILS::Item $item_id;
717 #
718 #       if (!defined($item)) {
719 #           syslog("LOG_WARNING",
720 #                  "renew_all: Invalid item id associated with patron '%s'",
721 #                  $patron->id);
722 #           next;
723 #       }
724 #
725 #       if (@{$item->hold_queue}) {
726 #           # Can't renew if there are outstanding holds
727 #           push @{$trans->unrenewed}, $item_id;
728 #       } else {
729 #           $item->{due_date} = time + (14*24*60*60); # two weeks hence
730 #           push @{$trans->renewed}, $item_id;
731 #       }
732 #    }
733 #
734 #    $trans->ok(1);
735 #
736 #    return $trans;
737 #}
738
739 1;