--- /dev/null
+package FulfILLment::AT::Reactor::BibRefresh;
+use base 'OpenILS::Application::Trigger::Reactor';
+use OpenSRF::Utils::Logger qw($logger);
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+
+use strict; use warnings;
+use Error qw/:try/;
+use OpenSRF::Utils::SettingsClient;
+
+my $U = 'OpenILS::Application::AppUtils';
+my $FF = OpenSRF::AppSession->create('fulfillment.laicore');
+
+sub handler {
+ my $self = shift;
+ my $env = shift;
+
+ update_bib($_) for @{$env->{target}};
+
+ return 1;
+}
+
+sub update_bib {
+ my $self = shift;
+ my $b = shift || $self;
+ my $e = new_editor();
+
+ my $owner = $b->owner;
+ my ($error, $new_bibs);
+
+ try {
+ $new_bibs = $FF->request( 'fulfillment.laicore.record_by_id', $owner, $b->remote_id)->gather(1);
+ } otherwise {
+ $error = 1;
+ };
+
+ unless ($error) {
+ if (@$new_bibs) {
+
+ my $bib = shift @$new_bibs;
+ (my $id = $bib->{id}) =~ s#^/resources/##;
+ $b->ischanged(1);
+ $b->cache_time('edit_time');
+ $b->marc($bib->{content});
+ $b->remote_id($id); # just in case
+
+ $e->xact_begin;
+ $e->update_biblio_record_entry($b) or return $e->die_event;
+ return $e->xact_commit;
+ }
+
+ return undef;
+ }
+
+ return 0;
+}
+
--- /dev/null
+package FulfILLment::AT::Reactor::ItemLoad;
+use base 'OpenILS::Application::Trigger::Reactor';
+use OpenSRF::Utils::Logger qw($logger);
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+
+use strict; use warnings;
+use Error qw/:try/;
+use OpenSRF::Utils::SettingsClient;
+
+my $U = 'OpenILS::Application::AppUtils';
+
+=comment
+$item_template = {
+
+ 'isbn_issn_code' => '',
+ 'call_number' => '',
+ 'edit_date' => '',
+ 'create_date' => '',
+ 'fingerprint' => '',
+ 'barcode' => '',
+ 'holdable' => 't',
+ 'call_number' => '',
+ 'agency_id' => '',
+ 'error' => 0,
+ 'error_message' => ''
+};
+=cut
+
+sub ByBib {
+ my $self = shift;
+ my $env = shift;
+
+ my $owner = $env->{target}->owner;
+ my $remote_bibid = $env->{target}->remote_id;
+ my $bibid = $env->{target}->id;
+
+ my $FF = OpenSRF::AppSession->create('fulfillment.laicore');
+
+ my ($error, $new_items);
+ try {
+ $new_items = $FF->request( 'fulfillment.laicore.items_by_record', $owner, $remote_bibid)->gather(1);
+ } otherwise {
+ $error = 1;
+ };
+
+ if ($error or !$new_items or !@$new_items) {
+ $FF->disconnect;
+ return 0;
+ }
+
+ my $e = new_editor();
+ for my $remote_cp (@$new_items) {
+ try {
+ $e->xact_begin;
+
+ $$remote_cp{call_number} ||= 'UNKNOWN';
+
+ $logger->info("Remote copy data: " . join(', ', map { "$_ => $$remote_cp{$_}" } keys %$remote_cp));
+
+ my $existing_cp = $e->search_asset_copy(
+ { source_lib => $owner, barcode => $$remote_cp{barcode} }
+ )->[0];
+
+ if (!$existing_cp) {
+ $existing_cp = Fieldmapper::asset::copy->new;
+ $existing_cp->isnew(1);
+ $existing_cp->creator(1);
+ $existing_cp->editor(1);
+ $existing_cp->loan_duration(2);
+ $existing_cp->fine_level(2);
+ $existing_cp->source_lib($owner);
+ $existing_cp->circ_lib($owner);
+ $existing_cp->barcode($$remote_cp{barcode});
+ }
+
+ $existing_cp->ischanged( 1 );
+ $existing_cp->remote_id( $remote_cp->{bib_id} );
+ $existing_cp->holdable( defined($remote_cp->{holdable}) ? $remote_cp->{holdable} : 1 );
+ my $due = $remote_cp->{due_date} || ''; # avoid warnings
+ $existing_cp->status( $due =~ /^\d+-\d+-\d+$/ ? 1 : 0 );
+
+
+ my $existing_cn = $e->search_asset_call_number(
+ { record => $bibid, owning_lib => $owner, label => $$remote_cp{call_number} }
+ )->[0];
+
+ if (!$existing_cn) {
+ $existing_cn = Fieldmapper::asset::call_number->new;
+ $existing_cn->isnew(1);
+ $existing_cn->creator(1);
+ $existing_cn->editor(1);
+ $existing_cn->label($$remote_cp{call_number});
+ $existing_cn->owning_lib($owner);
+ $existing_cn->record($bibid);
+
+ $existing_cn = $e->create_asset_call_number( $existing_cn );
+ }
+
+ $existing_cp->call_number( $existing_cn->id );
+
+ if ($existing_cp->isnew) {
+ $e->create_asset_copy( $existing_cp );
+ } else {
+ $e->update_asset_copy( $existing_cp );
+ }
+
+ $e->xact_commit;
+ } otherwise {
+ $e->xact_rollback;
+ };
+ }
+ $e->disconnect;
+ $FF->disconnect;
+
+ return 1;
+}
+
+1;
--- /dev/null
+package FulfILLment::AT::Reactor::ItemRefresh;
+use base 'OpenILS::Application::Trigger::Reactor';
+use OpenSRF::Utils::Logger qw($logger);
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+
+use strict; use warnings;
+use Error qw/:try/;
+use OpenSRF::Utils::SettingsClient;
+
+my $U = 'OpenILS::Application::AppUtils';
+
+
+sub ByItem {
+ my $self = shift;
+ my $env = shift;
+
+ update_item($_) for @{$env->{target}};
+
+ return 1;
+}
+
+sub ByBib {
+ my $self = shift;
+ my $env = shift;
+
+ my $e = new_editor();
+ for my $cn (@{ $e->search_asset_call_number( { record => $env->{target}->id } ) }) {
+ update_item($_) for @{ $e->search_asset_copy( { call_number => $cn->id } ) };
+ }
+
+ return 1;
+}
+
+sub update_item {
+ my $self = shift;
+ my $i = shift;
+
+ $i = $self if (!$i);
+
+ my $owner = $i->source_lib;
+ my $e = new_editor();
+ my $FF = OpenSRF::AppSession->create('fulfillment.laicore');
+
+ my ($error, $new_items);
+ try {
+ $new_items = $FF->request( 'fulfillment.laicore.item_by_barcode', $owner, $i->barcode)->gather(1);
+ } otherwise {
+ $error = 1;
+ };
+
+ unless ($error) {
+ if (@$new_items) {
+ my $item = shift @$new_items;
+
+ $i->ischanged(1);
+ $i->cache_time('now');
+ $i->remote_id($item->{bib_id});
+ $i->holdable($item->{holdable});
+ $i->status(
+ $item->{due_date} =~ /^\d+-\d+-\d+$/ ? 1 : 0
+ );
+
+ my $bib = $e->search_biblio_record_entry(
+ {remote_id => $item->{bib_id}, owner => $owner}
+ )->[0];
+
+ if ($bib) {
+ my $cn = $e->search_asset_call_number(
+ {label => $item->{call_number}, record => $bib->id}
+ )->[0];
+ $i->call_number($cn->id) if ($cn);
+ }
+
+ $e->xact_begin;
+ $e->update_asset_copy($i) or return $e->die_event;
+ return $e->xact_commit;
+ }
+
+ return undef;
+ }
+
+ return 0;
+}
+
+1;
--- /dev/null
+package FulfILLment::Application::LAICore;
+use strict; use warnings;
+use OpenILS::Application;
+use base qw/OpenILS::Application/;
+use OpenSRF::AppSession;
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Logger qw($logger);
+use OpenSRF::Utils::JSON;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use FulfILLment::LAIConnector;
+use FulfILLment::AT::Reactor::ItemLoad;
+
+sub killme_wrapper {
+
+ my $self = shift;
+ my $client = shift;
+ my $method = $self->{real_method};
+ die "killme_wrapper called without method\n" unless $method;
+
+ $logger->info("FF executing LAICore::$method()");
+
+ my @final = ();
+ eval {
+ local $SIG{ALRM} = sub { die 'killme' };
+ alarm($self->{killme});
+ my $this = bless($self,$self->{package});
+ @final = $this->$method($client,@_);
+ alarm(0);
+ };
+ alarm(0);
+
+ if ($@) {
+ $logger->error("Error executing $method : $@");
+ return undef;
+ }
+
+ $client->respond($_) for (@final);
+ return undef;
+}
+
+# TODO : If/when API calls return virtual Fieldmapper
+# objects (or arrays thereof), a wrapper to hash-ify
+# all outbound responses may be in order.
+
+sub register_method {
+ my $class = shift;
+ my %args = @_;
+
+ $class = ref($class) || $class;
+ $args{package} = $class;
+
+ if (exists($args{killme})) {
+ $args{real_method} = $args{method};
+ $args{method} = 'killme_wrapper';
+ return $class->SUPER::register_method( %args );
+ }
+
+ $class->SUPER::register_method( %args );
+}
+
+
+__PACKAGE__->register_method(
+ method => "get_connector_info",
+ api_name => "fulfillment.laicore.connector_info.retrieve",
+ signature => { params => [ {desc => 'Org Unit ID', type => 'number'} ] },
+ argc => 1,
+ api_level => 1
+);
+
+sub get_connector_info {
+ my ($self, $client, $ou) = @_;
+ return undef if ($ou !~ /^\d+$/);
+ return FulfILLment::LAIConnector->load($ou);
+}
+
+__PACKAGE__->register_method(
+ method => "items_by_barcode",
+ api_name => "fulfillment.laicore.item_by_barcode",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "items_by_barcode",
+ api_name => "fulfillment.laicore.item_by_barcode.batch",
+ killme => 120
+);
+
+sub items_by_barcode {
+ my ($self, $client, $ou, $ids) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+
+ return $connector->get_item_batch($ids)
+ if $self->api_name =~ /batch/;
+
+ return $connector->get_item($ids);
+}
+
+__PACKAGE__->register_method(
+ method => "items_by_record",
+ api_name => "fulfillment.laicore.items_by_record",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "items_by_record",
+ api_name => "fulfillment.laicore.items_by_record.batch",
+ killme => 120
+);
+sub items_by_record {
+ my ($self, $client, $ou, $ids) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+
+ return $connector->get_items_by_record_batch($ids)
+ if $self->api_name =~ /batch/;
+
+ return $connector->get_items_by_record($ids);
+}
+
+__PACKAGE__->register_method(
+ method => "records_by_item",
+ api_name => "fulfillment.laicore.record_by_item",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "items_by_record",
+ api_name => "fulfillment.laicore.record_by_item.batch",
+ killme => 120
+);
+sub records_by_item {
+ my ($self, $client, $ou, $ids) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+
+ return $connector->get_record_by_item_batch($ids)
+ if $self->api_name =~ /batch/;
+
+ return $connector->get_record_by_item($ids);
+}
+
+__PACKAGE__->register_method(
+ method => "get_holds",
+ api_name => "fulfillment.laicore.holds_by_item",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "get_holds",
+ api_name => "fulfillment.laicore.holds_by_record",
+ killme => 120
+);
+# target is copy_barcode or record_id
+sub get_holds {
+ my ($self, $client, $ou, $target, $user_barcode) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+
+ return $connector->get_item_holds($target, $user_barcode)
+ if $self->api_name =~ /by_item/;
+
+ return $connector->get_record_holds($target, $user_barcode);
+}
+
+__PACKAGE__->register_method(
+ method => "lender_holds",
+ api_name => "fulfillment.laicore.hold.lender.place",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "lender_holds",
+ api_name => "fulfillment.laicore.hold.lender.delete_earliest",
+ killme => 120
+);
+
+sub lender_holds {
+ my ($self, $client, $ou, $copy_barcode) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+ my $e = new_editor();
+
+ # for lending library holds, we use the configured hold user
+ my $user_barcode = $connector->{'user.hold'} || $connector->{'user'};
+
+ if (!$user_barcode) {
+ $logger->error(
+ "FF no hold recipient defined for ou=$ou copy=$copy_barcode");
+ return;
+ }
+
+ # TODO: proxy user pickup lib setting?
+ return $connector->place_lender_hold($copy_barcode, $user_barcode)
+ if $self->api_name =~ /place/;
+
+ return $connector->delete_lender_hold($copy_barcode, $user_barcode);
+}
+
+__PACKAGE__->register_method(
+ method => "create_borrower_copy",
+ api_name => "fulfillment.laicore.item.create_for_borrower",
+ killme => 120
+);
+sub create_borrower_copy {
+ my ($self, $client, $ou, $src_copy_id) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+ my $e = new_editor();
+
+ my $src_copy = $e->retrieve_asset_copy([
+ $src_copy_id,
+ { flesh => 3,
+ flesh_fields => {
+ acp => ['call_number'],
+ acn => ['record'],
+ bre => ['simple_record']
+ }
+ }
+ ]);
+
+ my $circ_lib = $e->retrieve_actor_org_unit($ou)->shortname;
+
+ return $connector->create_borrower_copy($src_copy, $circ_lib);
+}
+
+
+__PACKAGE__->register_method(
+ method => "borrower_holds",
+ api_name => "fulfillment.laicore.hold.borrower.place",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "borrower_holds",
+ api_name => "fulfillment.laicore.hold.borrower.delete_earliest",
+ killme => 120
+);
+# ---------------------------------------------------------------------------
+# Borrower Library Holds:
+# Create a hold against the temporary copy for the borrowing user
+# at the borrowing library.
+# ---------------------------------------------------------------------------
+sub borrower_holds {
+ my ($self, $client, $ou, $copy_barcode, $user_barcode) = @_;
+
+ # TODO: should be a pickup_lib here based on the pickup_lib
+ # of the FF hold
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+ my $e = new_editor();
+ my $pickup_lib = $e->retrieve_actor_org_unit($ou)->shortname;
+
+ return $connector->place_borrower_hold(
+ $copy_barcode, $user_barcode, $pickup_lib)
+ if $self->api_name =~ /place/;
+
+ return $connector->deletel_borrower_hold($copy_barcode, $user_barcode);
+}
+
+
+__PACKAGE__->register_method(
+ method => "circulation",
+ api_name => "fulfillment.laicore.circ.retrieve",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "circulation",
+ api_name => "fulfillment.laicore.circ.lender.checkout",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "circulation",
+ api_name => "fulfillment.laicore.circ.lender.checkin",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "circulation",
+ api_name => "fulfillment.laicore.circ.borrower.checkout",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "circulation",
+ api_name => "fulfillment.laicore.circ.borrower.checkin",
+ killme => 120
+);
+sub circulation {
+ my ($self, $client, $ou, $item_ident, $user_barcode) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+
+ if ($self->api_name =~ /lender/) {
+ # the circulation on the lender side is always checked
+ # out to the circ proxy user.
+
+ $user_barcode = $connector->{'user.circ'} || $connector->{'user'};
+ if (!$user_barcode) {
+ $logger->error("FF proxy circ user defined for $ou");
+ return;
+ }
+ }
+
+ if ($self->api_name =~ /checkout/) {
+
+ return $connector->checkout_lender($item_ident, $user_barcode)
+ if $self->api_name =~ /lender/;
+
+ return $connector->checkout_borrower($item_ident, $user_barcode);
+ }
+
+ if ($self->api_name =~ /checkin/) {
+
+ return $connector->checkin_lender($item_ident, $user_barcode)
+ if $self->api_name =~ /lender/;
+
+ return $connector->checkin_borrower($item_ident, $user_barcode);
+ }
+
+ return $connector->get_circulation($item_ident, $user_barcode);
+}
+
+__PACKAGE__->register_method(
+ method => "records_by_id",
+ api_name => "fulfillment.laicore.record_by_id",
+ killme => 120
+);
+__PACKAGE__->register_method(
+ method => "records_by_id",
+ api_name => "fulfillment.laicore.record_by_id.batch",
+ killme => 120
+);
+sub records_by_id {
+ my ($self, $client, $ou, $ids) = @_;
+
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+
+ return $connector->get_record_by_id_batch($ids)
+ if $self->api_name =~ /batch/;
+
+ return $connector->get_record_by_id($ids);
+}
+
+__PACKAGE__->register_method(
+ method => "lookup_user",
+ api_name => "fulfillment.laicore.lookup_user",
+ killme => 120
+);
+
+sub lookup_user {
+ my ($self, $client, $ou, $user_barcode, $user_pass) = @_;
+ my $connector = FulfILLment::LAIConnector->load($ou) or return;
+ return $connector->get_user($user_barcode, $user_pass);
+}
+
+__PACKAGE__->register_method(
+ method => "import_items_by_record",
+ api_name => "fulfillment.laicore.import_items_by_record",
+ killme => 120,
+ signature => q/
+ Import items from the remote site via remote record ID.
+ Returns true on success, false on failure.
+ /
+);
+sub import_items_by_record {
+ my ($self, $client, $ou, $record_id) = @_;
+ FulfILLment::LAIConnector->load($ou) or return;
+ my $e = new_editor();
+
+ # record_id param refers to the remote_id
+ my $rec = $e->search_biblio_record_entry(
+ {owner => $ou, remote_id => $record_id}
+ )->[0] or return;
+
+ return FulfILLment::AT::Reactor::ItemLoad->ByBib({target => $rec});
+}
+
+
+1;
+
--- /dev/null
+package FulfILLment::LAIConnector;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use FulfILLment::Util::Z3950;
+use FulfILLment::Util::SIP2Client;
+use FulfILLment::Util::NCIP;
+my $U = 'OpenILS::Application::AppUtils';
+
+# Determines the correct connector to load for the provided org
+# unit and returns a ref to a new instance of the connector.
+# This is the main sub called by external modules.
+sub load {
+ my ($class, $org_id) = @_;
+
+ # collect all of the FF org unit settings for this org.
+ my $settings = new_editor()->search_config_org_unit_setting_type(
+ {name => {'like' => 'ff.remote.connector%'}}
+ );
+
+ my $cdata = {
+ map {
+ $_->name => $U->ou_ancestor_setting($org_id, $_->name)
+ } @$settings
+ };
+
+ my %args = (org_id => $org_id, extra => {});
+
+ for my $key (keys %$cdata) {
+ my $setting = $cdata->{$key};
+ my $value = $setting->{value};
+ (my $newkey = $key) =~ s/^ff\.remote\.connector\.//;
+ if ($newkey =~ /^extra\./) {
+ $newkey =~ s/^extra\.//;
+ $args{extra}{$newkey} = $value;
+ } else {
+ $args{$newkey} = $value;
+ }
+ }
+
+ if (!$args{type}) {
+ $logger->error("No ILS type specifed for org unit $org_id");
+ return undef;
+ }
+
+ if ($args{disabled}) {
+ $logger->info("FF connector for ".$args{type}." disabled");
+ return;
+ }
+
+ return $class->_load_connector($args{type}, $args{version}, \%args);
+}
+
+# Returns a new LAIConnector for the specified type
+# and (optional) version. All additional args are
+# passed through to the connector constructor.
+sub _load_connector {
+ my ($class, $type, $version, $args) = @_;
+
+ my $module = "FulfILLment::LAIConnector::$type";
+ $module .= "::$version" if $version;
+
+ $logger->info("FF loading ILS module org=$$args{org_id} $module");
+
+ # note: do not wrap in eval {}. $module->use
+ # uses eval internally and nested evals clobber $@
+ $module->use;
+
+ if ($@) {
+ $logger->error("Unable to load $module : $@");
+ return undef;
+ }
+
+ my $connector;
+ eval { $connector = $module->new($args) };
+
+ if ($@) {
+ $logger->error("Unable to create $module object : $@");
+ return undef;
+ }
+
+ if (!$connector->init) {
+ $logger->error("Error initializing connector $module");
+ return undef;
+ }
+
+ return $connector;
+}
+
+sub new {
+ my ($class, $args) = @_;
+ $args ||= {};
+ return bless($args, $class);
+}
+
+sub z39_client {
+ my $self = shift;
+
+ if (!$self->{z39_client}) {
+
+ my $host = $self->{extra}{'z3950.host'} || $self->{host};
+ my $port = $self->{extra}{'z3950.port'};
+ my $database = $self->{extra}{'z3950.database'};
+
+ unless ($host and $port and $database) {
+ $logger->info("FF Z39 not configured for $self, skipping...");
+ return;
+ }
+
+ $self->{z39_client} =
+ FulfILLment::Util::Z3950->new(
+ $host, $port, $database,
+ $self->{extra}{'z3950.username'},
+ $self->{extra}{'z3950.password'}
+ );
+ }
+
+ return $self->{z39_client};
+}
+
+sub sip_client {
+ my $self = shift;
+
+ if (!$self->{sip_client}) {
+
+ my $host = $self->{extra}{'sip2.host'} || $self->{host};
+ my $port = $self->{extra}{'sip2.port'};
+
+ unless ($host and $port) {
+ $logger->info("FF SIP not configured for $self, skipping...");
+ return;
+ }
+
+ $self->{sip_client} =
+ FulfILLment::Util::SIP2Client->new(
+ $host,
+ $self->{extra}{'sip2.username'},
+ $self->{extra}{'sip2.password'},
+ $port,
+ $self->{extra}{'sip2.protocol'}, # undef == SOCK_STREAM
+ $self->{extra}{'sip2.institution'}
+ );
+ }
+
+ return $self->{sip_client};
+}
+
+sub ncip_client {
+ my $self = shift;
+
+ if (!$self->{ncip_client}) {
+ my $host = $self->{extra}{'ncip.host'} || $self->{host};
+ my $port = $self->{extra}{'ncip.port'};
+
+ unless ($host and $port) {
+ $logger->info("FF NCIP not configured for $self, skipping...");
+ return;
+ }
+
+ $self->{ncip_client} = FulfILLment::Util::NCIP->new(
+ protocol => $self->{extra}->{'ncip.protocol'},
+ host => $host,
+ port => $port,
+ path => $self->{extra}->{'ncip.path'},
+ template_paths => ['/openils/var/ncip/v1'], # TODO
+ ils_agency_name => $self->{extra}->{'ncip.ils_agency.name'},
+ ils_agency_uri => $self->{extra}->{'ncip.ils_agency.uri'},
+ ff_agency_name => 'FulfILLment',
+ ff_agency_uri => 'http://fulfillment-ill.org/ncip/schemes/agency.scm'
+ );
+ }
+
+ return $self->{ncip_client};
+}
+
+
+# override with connector-specific initialization as needed
+# return true on success, false on failure
+sub init {
+ my $self = shift;
+ return 1;
+}
+
+# commonly accessed data
+
+# retursn the connector type (ff.remote.connector.type)
+sub type {
+ my $self = shift;
+ return $self->{type};
+}
+# returns the connector ILS version string (ff.remote.connector.version)
+sub version {
+ my $self = shift;
+ return $self->{version};
+}
+# returns the actor.org_unit.id for our current context org unit
+sub org_id {
+ my $self = shift;
+ return $self->{org_id};
+}
+# returns the actor.org_unit.shortname value for our current context org unit
+sub org_code {
+ my $self = shift;
+ return $self->{org_code} ? $self->{org_code} :
+ $self->{org_code} =
+ new_editor()->retrieve_actor_org_unit($self->org_id)->shortname;
+}
+
+
+# ----------------------------------------------------------------------------
+# Below are methods responsible for communicating with remote ILSes. In some
+# cases, default implementations are provided. This is only done when the
+# implementation could reasonably by used by multiple connectors and only when
+# using SIP2 or Z39.50 as the communication layer.
+#
+# Connectors should override each method as needed.
+# ----------------------------------------------------------------------------
+
+# returns one item
+# Default implementation uses SIP2
+sub get_item {
+ my ($self, $copy_barcode) = @_;
+
+ my $item =
+ $self->sip_client->item_information_request($copy_barcode)
+ or return;
+
+ $item->{barcode} = $item->{item_identifier};
+ $item->{status} = $item->{circulation_status};
+ $item->{location_code} = $item->{permanent_location};
+
+ return $item;
+}
+
+# returns a list of items
+sub get_item_batch {
+ my ($self, $item_idents) = @_;
+ return [map {$self->get_item($_)} @$item_idents];
+}
+
+# returns a list of items
+sub get_items_by_record {
+ my ($self, $record_id) = @_;
+ return [];
+}
+
+# returns a list of items
+sub get_items_by_record_batch {
+ my ($self, $record_ids) = @_;
+ return [map {$self->get_items_by_record($_)} @$record_ids];
+}
+
+# returns one record
+sub get_record_by_id {
+ my ($self, $rec_id) = @_;
+ return;
+}
+
+# returns one record
+sub get_record_by_id_batch {
+ my ($self, $rec_ids) = @_;
+ return [];
+}
+
+# returns one record
+sub get_record_by_item {
+ my ($self, $item_ident) = @_;
+ return [];
+}
+
+# returns a list of records
+sub get_record_by_item_batch {
+ my ($self, $item_idents) = @_;
+ return [];
+}
+
+# returns 1 user.
+# Default implementation uses SIP2
+sub get_user {
+ my ($self, $user_barcode, $user_pass) = @_;
+
+ my $user = $self->sip_client->lookup_user({
+ patron_id => $user_barcode,
+ patron_pass => $user_pass
+ });
+
+ return unless $user;
+
+ $user->{user_barcode} = $user->{patron_identifier};
+ $user->{loaned_items} = $user->{charged_items};
+ $user->{loaned_items_count} = $user->{charged_items_count};
+ $user->{loaned_items_limit} = $user->{charged_items_limit};
+ $user->{lang_pref} = $user->{language};
+ $user->{phone} = $user->{home_phone_number};
+
+ $user->{billing_address} = $user->{home_address};
+ $user->{mailing_address} = $user->{home_address};
+
+ return $user;
+}
+
+# returns a list of holds
+sub get_item_holds {
+ my ($self, $item_ident) = @_;
+ return [];
+}
+
+# TODO: docs
+sub place_borrower_hold {
+ my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_;
+}
+
+# TODO: docs
+sub delete_borrower_hold {
+ my ($self, $item_barcode, $user_barcode) = @_;
+}
+
+# TODO: docs
+sub place_lender_hold {
+ my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_;
+}
+
+# TODO: docs
+sub delete_lender_hold {
+ my ($self, $item_barcode, $user_barcode) = @_;
+}
+
+
+# ---------------------------------------------------------------------------
+# Provide a default hold placement via SIP
+# ---------------------------------------------------------------------------
+sub place_hold_via_sip {
+ my $self = shift;
+ my $bib_id = shift || '';
+ my $copy_barcode = shift || '';
+ my $user_barcode = shift || '';
+ my $pickup_lib = shift || '';
+ my $expire_date = shift;
+ my $hold_type = shift;
+
+ if (!$hold_type) {
+ # if no hold type is provided, assume passing
+ # a barcode implies a copy-level hold
+
+ # 2 == bib hold
+ # 3 == copy hold
+ $hold_type = $copy_barcode ? 3 : 2;
+ }
+
+ if (!$expire_date) {
+ $expire_date = # interval should be a setting?
+ DateTime->now->add({months => 6})->strftime("%Y%m%d 000000");
+ }
+
+ if (!$pickup_lib) {
+ # use the home org unit of the requesting user
+ # as the pickup lib if none is provided
+ my $user = $self->flesh_user($user_barcode);
+ $pickup_lib = $user->home_ou->shortname if $user;
+ }
+
+ $logger->warn("FF has no pickup lib for $user_barcode") if !$pickup_lib;
+
+ $logger->info("FF placing hold copy=$copy_barcode; ".
+ "pickup_lib=$pickup_lib; bib=$bib_id; ".
+ "user=$user_barcode; expire=$expire_date");
+
+ my $resp = $self->sip_client->place_hold($user_barcode, undef,
+ $expire_date, $pickup_lib, $hold_type, $copy_barcode, $bib_id);
+
+ return undef unless $resp;
+
+ my $blob = $self->translate_sip_hold($resp);
+ $blob->{bibID} = $bib_id;
+ $blob->{hold_type} = $bib_id ? 'T' : 'C';
+
+ return undef if $blob->{error};
+ return $blob;
+}
+
+sub translate_sip_hold {
+ my ($self, $sip_msg) = @_;
+
+ my $fields = $sip_msg->{fields};
+ my $fixed_fields = $sip_msg->{fixed_fields};
+
+ # TODO: verify returned format is sane
+
+ return {
+ error => !$fixed_fields->[0],
+ error_message => $fields->{AF},
+ success_message => $fields->{AF},
+ expire_time => $fields->{BW},
+ expires => $fields->{BW},
+ placed_on => $fixed_fields->[2],
+ request_time => $fixed_fields->[2],
+ status => $fixed_fields->[1] eq 'Y' ? 'Available' : 'Pending',
+ title => $fields->{AJ},
+ barcode => $fields->{AB},
+ itemid => $fields->{AB},
+ pickup_lib => $fields->{BS}
+ };
+}
+
+# given a copy barcode, this will return the copy whose source lib
+# matches my org unit fleshed with its call number and bib record
+sub flesh_copy {
+ my ($self, $copy_barcode) = @_;
+
+ # find the FF copy so we can get the copy's record_id
+ my $copy = new_editor()->search_asset_copy([
+ { barcode => $copy_barcode, source_lib => $self->org_id},
+ { flesh => 2,
+ flesh_fields => {
+ acp => ['call_number'],
+ acn => ['record']
+ }
+ }
+ ])->[0];
+
+ return $copy ? $copy : undef;
+}
+
+
+# given a user barcode, this will return the use whose home lib
+# is at or below my org unit, fleshed with home_ou
+sub flesh_user {
+ my ($self, $user_barcode) = @_;
+
+ my $cards = new_editor()->search_actor_card([
+ { barcode => $user_barcode,
+ org => $U->get_org_descendants($self->org_id)
+ }, {
+ flesh => 2,
+ flesh_fields => {ac => ['usr'], au => ['home_ou']}
+ }
+ ]);
+
+ return @$cards ? $cards->[0]->usr : undef;
+}
+
+
+
+
+# returns one hold
+# pickup_lib is the library code (org_unit.shortname)
+sub place_item_hold {
+ my ($self, $item_ident, $user_barcode, $pickup_lib) = @_;
+ return;
+}
+
+# returns one hold
+# pickup_lib is the library code (org_unit.shortname)
+sub place_record_hold {
+ my ($self, $rec_id, $user_barcode, $pickup_lib) = @_;
+ return;
+}
+
+# returns one hold
+# pickup_lib is the library code (org_unit.shortname)
+sub delete_item_hold {
+ my ($self, $item_ident, $user_barcode, $pickup_lib) = @_;
+ return;
+}
+
+# returns one hold
+# pickup_lib is the library code (org_unit.shortname)
+sub delete_record_hold {
+ my ($self, $rec_id, $user_barcode, $pickup_lib) = @_;
+ return;
+}
+
+# returns a list of holds
+sub get_record_holds {
+ my ($self, $rec_id) = @_;
+ return [];
+}
+
+# ---------------------------------------------------------------------------
+# Allow connectors to provide lender vs. borrower checkout and checkin
+# handling. Call the stock checkout/checkin methods by default.
+# ---------------------------------------------------------------------------
+sub checkout_lender {
+ my $self = shift;
+ return $self->checkout(@_);
+}
+sub checkout_borrower {
+ my $self = shift;
+ return $self->checkout(@_);
+}
+sub checkin_lender {
+ my $self = shift;
+ return $self->checkin(@_);
+}
+sub checkin_borrower {
+ my $self = shift;
+ return $self->checkin(@_);
+}
+
+# ---------------------------------------------------------------------------
+# Provide default checkout and checkin routines via SIP.
+# Override with connector-specific behavior as needed.
+# ---------------------------------------------------------------------------
+sub checkout {
+ my ($self, $item_barcode, $user_barcode) = @_;
+ return unless $self->sip_client;
+ my $resp = $self->sip_client->checkout($user_barcode, undef, $item_barcode);
+ return $self->sip_client->sip_msg_to_circ($resp, 'checkout');
+}
+
+sub checkin {
+ my ($self, $item_barcode, $user_barcode) = @_;
+ return unless $self->sip_client;
+ my $resp = $self->sip_client->checkin($user_barcode, undef, $item_barcode);
+ return $self->sip_client->sip_msg_to_circ($resp);
+}
+
+
+# ---------------------------------------------------------------------------
+
+# returns one circulation
+sub get_circulation {
+ my ($self, $item_ident, $user_barcode) = @_;
+ return;
+}
+
+# ---------------------------------------------------------------------------
+# Reference copy is the asset.copy hold target for the lender hold,
+# fleshed with ->call_number->record. The borrower copy is a temporary /
+# dummy copy created at the borrowing library for the purposes of
+# hold placement and circulation at the borrowing library.
+# ---------------------------------------------------------------------------
+sub create_borrower_copy {
+ my ($self, $reference_copy, $circ_lib_code) = @_;
+}
+
+# --------------------------------------------------
+# -- these method have no corresponding API calls --
+# -- their purpose is unclear --
+
+sub item_get_page {
+ # TODO: params?
+ return [];
+}
+sub item_get_range {
+ # TODO: params?
+ return [];
+}
+sub resource_get_page {
+ # TODO: params
+ return [];
+}
+sub resource_get_range {
+ # TODO: params
+ return [];
+}
+sub resource_get_actor_relation {
+ # TODO: params
+ return [];
+}
+sub resource_get_total_pages {
+ # TODO: params
+ return [];
+}
+sub resource_get_on_date {
+ # TODO: params
+ return [];
+}
+sub resource_get_after_date {
+ # TODO: params
+ return [];
+}
+sub resource_get_before_date {
+ # TODO: params
+ return [];
+}
+sub actor_get_range {
+ # TODO: params
+ return [];
+}
+sub actor_get_page {
+ # TODO: params
+ return [];
+}
+sub actor_get_total_pages {
+ # TODO: params
+ return [];
+}
+sub actor_list_holds {
+ my ($self, $user_barcode) = @_;
+ return [];
+}
+sub get_results_per_page {
+ # TODO: params
+ return [];
+}
+sub get_host_ills {
+ # TODO: params
+ return;
+}
+sub item_get_total_pages {
+ # TODO: params?
+ return [];
+}
+sub item_get_all {
+ # TODO: params?
+ return [];
+}
+
+1;
--- /dev/null
+package FulfILLment::LAIConnector::Aleph;
+use base FulfILLment::LAIConnector;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+1;
--- /dev/null
+package FulfILLment::LAIConnector::Evergreen;
+use base FulfILLment::LAIConnector;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+use Digest::MD5 qw(md5_hex);
+use LWP::UserAgent;
+use HTTP::Request;
+use JSON::XS;
+use Data::Dumper;
+
+my $json = JSON::XS->new;
+$json->allow_nonref(1);
+
+my $ua = LWP::UserAgent->new;
+$ua->agent("FulfILLment/1.0");
+
+sub gateway {
+ my ($self, $service, $method, @args) = @_;
+
+ my $url = sprintf(
+ 'https://%s/osrf-gateway-v1?service=%s&method=%s',
+ $self->{host}, $service, $method
+ );
+ $url .= '¶m=' . $json->encode($_) for (@args);
+
+ $logger->debug("FF Evergreen gateway request => $url");
+
+ my $req = HTTP::Request->new('GET' => $url);
+ my $res = $ua->request($req);
+
+ if (!$res->is_success) {
+ $logger->error(
+ "FF Evergreen gateway request error [HTTP ".$res->code."] $url");
+ return undef;
+ }
+
+ my $value = decode_json($res->content);
+ return $$value{payload} if $$value{status} == 200;
+}
+
+# --------------------------------------------------------------------
+# Login
+# TODO: support authtoken re-use (and graceful recovery) for
+# faster batches of actions
+# Always assumes barcode-based login.
+# --------------------------------------------------------------------
+sub login {
+ my $self = shift;
+ my $username = shift || $self->{user};
+ my $password = shift || $self->{passwd};
+ my $type = shift;
+
+ my $json = $self->gateway(
+ 'open-ils.fulfillment',
+ 'fulfillment.connector.login',
+ undef, $password, $type, $username
+ );
+
+ my $auth = $$json[0];
+ $logger->info("EG: login failed for $username") unless $auth;
+ return $self->{authtoken} = $auth;
+}
+
+sub get_user {
+ my $self = shift;
+ my $user_barcode = shift;
+ my $user_password = shift;
+
+ my $resp = $self->gateway(
+ 'open-ils.fulfillment',
+ 'fulfillment.connector.verify_user_by_barcode',
+ $user_barcode, $user_password
+ );
+
+ # TODO: we always assume barcode logins in FF, but it would be
+ # nice if the EG connector could safey fall-through to username
+ # logins. Care must be taken to prevent multiple accounts, one
+ # for the barcode and one for the username.
+
+ unless ($resp and $resp->[0]) {
+ $logger->info("EG: unable to verify user $user_barcode");
+ return [];
+ }
+
+ my $data = $resp->[0];
+
+ $logger->info("Evergreen retreived user " . Dumper($data));
+
+ $data->{surname} = $data->{family_name};
+ $data->{user_id} = $data->{id};
+ $data->{given_name} = $data->{first_given_name};
+ $data->{exp_date} = $data->{expire_date};
+ $data->{user_barcode} = ref($data->{card}) ?
+ $data->{card}->{barcode} : $user_barcode;
+
+ return $data;
+}
+
+sub get_items_by_record {
+ my ($self, $record_id) = @_;
+
+ my $auth = $self->login or return [];
+
+ my $resp = $self->gateway(
+ 'open-ils.fulfillment',
+ 'fulfillment.connector.copy_tree',
+ $auth, $record_id
+ );
+
+ my $cns = $resp->[0];
+ my @copies = map {@{$_->{copies}}} @$cns;
+ $_->{bib_id} = $_->{record_id} = $record_id for @copies;
+ return \@copies;
+}
+
+sub get_record_by_id {
+ my ($self, $record_id) = @_;
+
+ my $url = sprintf(
+ "http://%s/opac/extras/supercat/retrieve/marcxml/record/%s",
+ $self->{host}, $record_id
+ );
+
+ $logger->info("FF EG get_record_by_id() => $url");
+
+ my $req = HTTP::Request->new("GET" => $url);
+ my $res = $ua->request($req);
+
+ if (!$res->is_success) {
+ $logger->error(
+ "FF Evergreen gateway request error [HTTP ".$res->code."] $url");
+ return undef;
+ }
+
+ return {
+ marc => $res->content,
+ error => 0,
+ id => $record_id
+ };
+}
+
+sub get_item {
+ my ($self, $barcode) = @_;
+
+ my $auth = $self->login;
+
+ # TODO add fields as needed
+ my %fields;
+ for my $field (qw/
+ id circ_lib barcode location status holdable circulate/) {
+ $fields{$field} = {path => $field, display => 1};
+ }
+
+ $fields{call_number} = {path => 'call_number.label', display => 1};
+ $fields{record_id} = {path => 'call_number.record', display => 1};
+
+ my $resp = $self->gateway(
+ 'open-ils.fielder',
+ 'open-ils.fielder.flattened_search',
+ $auth, 'acp', \%fields, {barcode => $barcode}
+ );
+
+ my $copy = $resp->[0];
+ $copy->{bib_id} = $copy->{record_id};
+
+ return $resp->[0];
+}
+
+sub get_record_holds {
+ my ($self, $record_id) = @_;
+ my $auth = $self->login;
+
+ # TODO: xmlrpc is dead
+ #my $resp = $self->request(
+ #'open-ils.circ',
+ #'open-ils.circ.holds.retrieve_all_from_title',
+ #$key,
+ #$bibID,
+ #)->value;
+}
+
+sub place_lender_hold {
+ my $self = shift;
+ return $self->place_borrower_hold(@_);
+}
+
+sub place_borrower_hold {
+ my ($self, $copy_barcode, $user_barcode, $pickup_lib) = @_;
+
+ my $auth = $self->login or return;
+
+ my $resp = $self->gateway(
+ 'open-ils.fulfillment',
+ 'fulfillment.connector.create_hold',
+ $auth, $copy_barcode, $user_barcode
+ );
+
+
+ $logger->debug("FF Evergreen item hold for copy=$copy_barcode ".
+ "user=$user_barcode resulted in ".Dumper($resp));
+
+ # NOTE: fulfillment.connector.create_hold only returns
+ # the hold ID and not the hold object. is that enough?
+ return $$resp[0] if $resp and @$resp;
+ return;
+}
+
+sub delete_lender_hold {
+ my $self = shift;
+ return $self->delete_borrower_hold(@_);
+}
+
+sub delete_borrower_hold {
+ my ($self, $copy_barcode, $user_barcode) = @_;
+
+ my $auth = $self->login or return;
+
+ my $resp = $self->gateway(
+ 'open-ils.fulfillment',
+ 'fulfillment.connector.cancel_oldest_hold',
+ $auth, $copy_barcode
+ );
+
+ # NOTE: fulfillment.connector.cancel_oldest_hold only
+ # returns success or failure. is that enough?
+ return $resp and @$resp and $$resp[0];
+}
+
+sub create_borrower_copy {
+ my ($self, $ref_copy, $ou_code) = @_;
+
+ my $auth = $self->login or return;
+
+ my $resp = $self->gateway(
+ 'open-ils.fulfillment',
+ 'fulfillment.connector.create_borrower_copy',
+ $auth, $ou_code, $ref_copy->barcode, {
+ title => $ref_copy->call_number->record->simple_record->title,
+ author => $ref_copy->call_number->record->simple_record->author,
+ isbn => $ref_copy->call_number->record->simple_record->isbn,
+ }
+ );
+
+ my $copy = $resp->[0] if $resp;
+
+ unless ($copy) {
+ $logger->error(
+ "FF unable to create borrower copy for ".$ref_copy->barcode);
+ return;
+ }
+
+ $logger->debug("FF created borrower copy " . Dumper($copy));
+
+ return $copy;
+}
+
+# borrower checkout uses a precat copy
+sub checkout_borrower {
+ my ($self, $copy_barcode, $user_barcode) = @_;
+
+ my $args = {
+ copy_barcode => $copy_barcode,
+ patron_barcode => $user_barcode,
+ request_precat => 1
+ };
+
+ return $self->_perform_checkout($args);
+}
+
+# to date, lender checkout requires no special handling
+sub checkout_lender {
+ my ($self, $copy_barcode, $user_barcode) = @_;
+
+ my $args = {
+ copy_barcode => $copy_barcode,
+ patron_barcode => $user_barcode
+ };
+
+ return $self->_perform_checkout($args);
+}
+
+# ---------------------------------------------------------------------------
+# attempts a checkout.
+# if the checkout fails with a COPY_IN_TRANSIT event, abort the transit and
+# attempt the checkout again.
+# ---------------------------------------------------------------------------
+sub _perform_checkout {
+ my ($self, $args) = @_;
+ my $auth = $self->login or return;
+ my $copy_barcode = $args->{copy_barcode};
+
+ my $resp = $self->_send_checkout($auth, $args) or return;
+
+ if ($resp->{textcode} eq 'COPY_IN_TRANSIT') {
+ # checkout of in-transit copy attempted. We really want this
+ # copy, so let's abort the transit, then try again.
+
+ $logger->info("FF EG attempting to abort ".
+ "transit on $copy_barcode for checkout");
+
+ my $resp2 = $self->gateway(
+ 'open-ils.circ',
+ 'open-ils.circ.transit.abort',
+ $auth, {barcode => $copy_barcode}
+ );
+
+ if ($resp2 and $resp2->[0] eq '1') {
+ $logger->info(
+ "FF EG successfully aborted transit for $copy_barcode");
+
+ # re-do the checkout
+ $resp = $self->_send_checkout($auth, $args);
+
+ } else {
+ $logger->warn("FF EG unable to abort transit on checkout");
+ return;
+ }
+
+ }
+
+ return $resp;
+}
+
+sub _send_checkout {
+ my ($self, $auth, $args) = @_;
+
+ my $resp = $self->gateway(
+ 'open-ils.circ',
+ 'open-ils.circ.checkout.full.override',
+ $auth, $args
+ );
+
+ if ($resp) {
+ # gateway returns an array
+ if ($resp = $resp->[0]) {
+ # circ may return an array of events; use the first.
+ $resp = $resp->[0] if ref($resp) eq 'ARRAY';
+ $logger->info("FF EG checkout returned event ".$resp->{textcode});
+ return $resp;
+ }
+ }
+
+ $logger->error("FF EG checkout failed to return a response");
+ return;
+}
+
+
+sub checkin {
+ my ($self, $copy_barcode, $user_barcode) = @_;
+ my $auth = $self->login or return;
+
+ # we want to check the item in at the
+ # correct location or it will go into transit.
+ my $fields = {
+ id => {path => 'id', display => 1},
+ shortname => {path => 'shortname', display => 1}
+ };
+
+ my $resp = $self->gateway(
+ 'open-ils.fielder',
+ 'open-ils.fielder.flattened_search',
+ $auth, 'aou', $fields, {shortname => $self->org_code}
+ );
+
+ my $checkin_args = {copy_barcode => $copy_barcode};
+
+ if ($resp and $resp->[0]) {
+ $logger->debug("FF EG found org ".Dumper($resp));
+ $checkin_args->{circ_lib} = $resp->[0]->{id};
+ } else {
+ $logger->warn("FF EG unable to locate org unit ".
+ $self->org_code.", passing no circ_lib on checkin");
+ }
+
+ $resp = $self->gateway(
+ 'open-ils.circ',
+ 'open-ils.circ.checkin.override',
+ $auth, $checkin_args
+ );
+
+ if ($resp) {
+ # gateway returns an array
+ if ($resp = $resp->[0]) {
+ # circ may return an array of events; use the first.
+ $resp = $resp->[0] if ref($resp) eq 'ARRAY';
+ $logger->info("FF EG checkin returned event ".$resp->{textcode});
+ return $resp;
+ }
+ }
+
+ $logger->error("FF EG checkin failed to return a response");
+ return;
+}
+
+
+1;
--- /dev/null
+#
+#===============================================================================
+#
+# FILE: FF-evergreen-app.pm
+#
+# DESCRIPTION:
+#
+# FILES: ---
+# BUGS: ---
+# NOTES: ---
+# AUTHOR: Michael Davadrian Smith (), msmith@esilibrary.com, Mike Rylander, miker@esilibrary.com
+# COMPANY: Equinox Software
+# VERSION: 1.0
+# CREATED: 01/29/2013 03:27:04 PM
+# REVISION: ---
+#===============================================================================
+
+use strict;
+use warnings;
+
+package OpenILS::Application::FulfILLment_EGAPP;
+
+use OpenILS::Application;
+use base qw/OpenILS::Application/;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Logger qw($logger);
+
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Const qw/:const/;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use Digest::MD5 qw(md5_hex);
+
+sub login {
+ my( $self, $client, $username, $password, $type, $barcode ) = @_;
+
+ $type |= "staff";
+
+ my $seed = $U->simplereq(
+ 'open-ils.auth',
+ 'open-ils.auth.authenticate.init',
+ $username || $barcode
+ );
+
+ $logger->info("No auth seed. Couldn't talk to the auth server") unless $seed;
+
+ my $args = {
+ username => $username,
+ agent => 'fulfillment',
+ password => md5_hex($seed . md5_hex($password)),
+ type => $type
+ };
+
+ if ($barcode) {
+ delete $args->{username};
+ $args->{barcode} = $barcode;
+ }
+
+ my $response = $U->simplereq(
+ 'open-ils.auth',
+ 'open-ils.auth.authenticate.complete',
+ $args
+ );
+
+ $logger->info("No auth response returned on login.") unless $response;
+
+ my $authtime = $response->{payload}->{authtime};
+ my $authtoken = $response->{payload}->{authtoken};
+
+ my $ident = $username || $barcode;
+ $logger->info("Login failed for user $ident!") unless $authtoken;
+
+ return $authtoken || '';
+}
+
+__PACKAGE__->register_method(
+ method => "login",
+ api_name => "fulfillment.connector.login",
+ signature => {
+ desc => "Authenticate the requesting user",
+ params => [
+ { name => 'username', type => 'string' },
+ { name => 'passwd', type => 'string' },
+ { name => 'type', type => 'string' }
+ ],
+ 'return' => {
+ desc => 'Returns an authentication token on success, or an empty string on failure'
+ }
+ }
+);
+
+sub lookup_user {
+ my ($self, $client, $authtoken, $keys, $value) = @_;
+
+ $keys = [$keys] if (!ref($keys));
+
+ ($authtoken) = $self
+ ->method_lookup( 'fulfillment.connector.login' )
+ ->run(@$authtoken)
+ if (ref $authtoken);
+
+ my $e = new_editor(authtoken => $authtoken);
+
+ return undef unless $e->checkauth;
+
+ for my $k ( @$keys ) {
+ my $users = $e->search_actor_user([
+ { $k => $value },
+ {flesh => 1, flesh_fields => {au => ['card']}}
+ ]);
+
+ if ($users->[0]) {
+
+ # user's are allowed to retrieve their own accounts
+ # regardless of proxy permissions
+ return recursive_hash($users->[0])
+ if $users->[0]->id eq $e->requestor->id;
+
+ # all other user retrievals require proxy user permissions
+ return undef unless $e->allowed('fulfillment.proxy_user');
+ return recursive_hash($users->[0]);
+ }
+ }
+
+ return undef;
+}
+
+__PACKAGE__->register_method(
+ method => "lookup_user",
+ api_name => "fulfillment.connector.lookup_user",
+ signature => {
+ desc => "Retrieve a user hash",
+ params => [
+ { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' },
+ { name => 'keys', type => 'string', 'One or more fields against which to attempt matching the retrieval value, such as "id" or "usrname"' },
+ { name => 'lookup_value', type => 'string' }
+ ],
+ 'return' => {
+ desc => 'Returns a user hash on success, or nothing on failure'
+ }
+ }
+);
+
+sub verify_user_by_barcode {
+ my ($self, $client, $user_barcode, $user_password) = @_;
+
+ my ($authtoken) = $self
+ ->method_lookup( 'fulfillment.connector.login' )
+ ->run(undef, $user_password, 'temp', $user_barcode);
+
+ return undef unless $authtoken;
+
+ my $user = $U->simplereq(
+ 'open-ils.auth',
+ 'open-ils.auth.session.retrieve',
+ $authtoken
+ );
+
+ return recursive_hash($user);
+}
+
+__PACKAGE__->register_method(
+ method => "verify_user_by_barcode",
+ api_name => "fulfillment.connector.verify_user_by_barcode",
+ signature => {
+ desc => q/Given a user barcode and password, returns the
+ user hash if the barcode+password combo is valid/,
+ params => [
+ {name => 'barcode', type => 'string', desc => 'User barcode'},
+ {name => 'password', type => 'string', desc => 'User password'}
+ ],
+ 'return' => {
+ desc => 'Returns a user hash on success, or nothing on failure'
+ }
+ }
+);
+
+
+sub lookup_holds {
+ my ($self, $client, $authtoken, $uid) = @_;
+
+ ($authtoken) = $self
+ ->method_lookup( 'fulfillment.connector.login' )
+ ->run(@$authtoken)
+ if (ref $authtoken);
+
+ my $e = new_editor(authtoken => $authtoken);
+
+ return undef unless $e->checkauth;
+ return undef unless $e->allowed('fulfillment.proxy_user');
+
+ $uid ||= $e->requestor;
+
+ my $holds = $e->search_action_hold_request([
+ { usr => $uid, capture_time => undef, cancel_time => undef },
+ { order_by => { ahr => 'request_time' } }
+ ]);
+
+ return recursive_hash($holds);
+}
+
+__PACKAGE__->register_method(
+ method => "lookup_holds",
+ api_name => "fulfillment.connector.lookup_holds",
+ signature => {
+ desc => "Retrieve a list of open holds for a user",
+ params => [
+ { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' }
+ ],
+ 'return' => {
+ desc => 'Returns an array of hold hashes on success, or nothing on failure'
+ }
+ }
+);
+
+sub copy_detail {
+ my ($self, $client, $authtoken, $barcode ) = @_;
+
+ ($authtoken) = $self
+ ->method_lookup( 'fulfillment.connector.login' )
+ ->run(@$authtoken)
+ if (ref $authtoken);
+
+ my $e = new_editor(authtoken => $authtoken);
+ return undef unless $e->checkauth;
+ return undef unless $e->allowed('fulfillment.proxy_user');
+
+ my $tree = $U->simplereq('open-ils.circ', 'open-ils.circ.copy_details.retrieve.barcode', $authtoken, $barcode);
+
+ return recursive_hash($tree);
+}
+
+__PACKAGE__->register_method(
+ method => "copy_detail",
+ api_name => "fulfillment.connector.copy_detail",
+ signature => {
+ desc => "Fetch a copy tree by bib id, optionally org-scoped",
+ params => [
+ { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' },
+ { name => 'barcode', type => 'string', desc => 'Copy barcode' },
+ ],
+ 'return' => {
+ desc => 'Returns a fleshed copy on success, or nothing on failure'
+ }
+ }
+);
+
+sub copy_tree {
+ my ($self, $client, $authtoken, $bib, @orgs ) = @_;
+
+ ($authtoken) = $self
+ ->method_lookup( 'fulfillment.connector.login' )
+ ->run(@$authtoken)
+ if (ref $authtoken);
+
+ my $e = new_editor(authtoken => $authtoken);
+ return undef unless $e->checkauth;
+ return undef unless $e->allowed('fulfillment.proxy_user');
+
+ my $tree;
+ if (@orgs) {
+ $tree = $U->simplereq('open-ils.cat', 'open-ils.cat.asset.copy_tree.retrieve', $authtoken, $bib, @orgs);
+ } else {
+ $tree = $U->simplereq('open-ils.cat', 'open-ils.cat.asset.copy_tree.global.retrieve', $authtoken, $bib);
+ }
+
+ return recursive_hash($tree);
+}
+
+__PACKAGE__->register_method(
+ method => "copy_tree",
+ api_name => "fulfillment.connector.copy_tree",
+ signature => {
+ desc => "Fetch a copy tree by bib id, optionally org-scoped",
+ params => [
+ { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' },
+ { name => 'bib_id', type => 'string', desc => 'Bib id to fetch copies from' },
+ { name => 'org', type => 'string', desc => 'Org id for copy scoping; repeatable' },
+ ],
+ 'return' => {
+ desc => 'Returns a CN-CP tree on success, or nothing on failure'
+ }
+ }
+);
+
+sub recursive_hash {
+ my $obj = shift;
+
+ if (ref($obj)) {
+ if (ref($obj) =~ /Fieldmapper/) {
+ $obj = $obj->to_bare_hash;
+ $$obj{$_} = recursive_hash($$obj{$_}) for (keys %$obj);
+ } elsif (ref($obj) =~ /ARRAY/) {
+ $obj = [ map { recursive_hash($_) } @$obj ];
+ } else {
+ $$obj{$_} = recursive_hash($$obj{$_}) for (keys %$obj);
+ }
+ }
+
+ return $obj;
+}
+
+
+sub create_hold {
+ my ($self, $client, $authtoken, $copy_bc, $patron_bc) = @_;
+
+ ($authtoken) = $self
+ ->method_lookup( 'fulfillment.connector.login' )
+ ->run(@$authtoken)
+ if (ref $authtoken);
+
+ my $e = new_editor(authtoken => $authtoken);
+ return undef unless $e->checkauth;
+ return undef unless $e->allowed('fulfillment.proxy_user');
+
+ my $patron = $e->requestor->id;
+
+ if ($patron_bc) {
+ my $p = $e->search_actor_card({barcode => $patron_bc})->[0];
+ $patron = $p->id if $p;
+ }
+
+ my $copy = $e->search_asset_copy({barcode => $copy_bc, deleted => 'f'})->[0];
+ return undef unless ($copy);
+
+ my $hold = new Fieldmapper::action::hold_request;
+ $hold->usr($patron);
+ $hold->target($copy->id);
+ $hold->hold_type('C');
+ $hold->pickup_lib($copy->circ_lib);
+
+ my $resp = $U->simplereq('open-ils.circ', 'open-ils.circ.holds.create.override', $authtoken, $hold);
+
+ return undef if (ref $resp);
+ return $resp;
+}
+
+__PACKAGE__->register_method(
+ method => "create_hold",
+ api_name => "fulfillment.connector.create_hold",
+ signature => {
+ desc => "Create a new hold",
+ params => [
+ { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' },
+ { name => 'copy_bc', type => 'string', desc => 'Copy barcode on which to place a hold' },
+ { name => 'patron_bc', type => 'string', desc => 'Patron barcode as which to place a hold, if different from calling user' },
+ ],
+ 'return' => {
+ desc => 'Returns a hold id on success, or nothing on failure'
+ }
+ }
+);
+
+sub cancel_proxy_hold {
+ my ($self, $client, $authtoken, $copy_bc) = @_;
+
+ ($authtoken) = $self
+ ->method_lookup( 'fulfillment.connector.login' )
+ ->run(@$authtoken)
+ if (ref $authtoken);
+
+ my $e = new_editor(authtoken => $authtoken);
+ return undef unless $e->checkauth;
+ return undef unless $e->allowed('fulfillment.proxy_user');
+
+ my ($holds) = $self
+ ->method_lookup( 'fulfillment.connector.lookup_holds' )
+ ->run($authtoken);
+
+ my $copy = $e->search_asset_copy({barcode => $copy_bc, deleted => 'f'})->[0];
+ return undef unless ($copy);
+
+ $holds = [ grep { $_->{target} == $copy->id } @$holds ];
+
+ my $resp = $U->simplereq('open-ils.circ', 'open-ils.circ.hold.cancel', $authtoken, $holds->[-1]->{id}) if (@$holds);
+
+ return undef if (ref $resp);
+ return $resp;
+}
+
+__PACKAGE__->register_method(
+ method => "cancel_proxy_hold",
+ api_name => "fulfillment.connector.cancel_oldest_hold",
+ signature => {
+ desc => "Retrieve a list of open holds for a user",
+ params => [
+ { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' },
+ { name => 'copy_bc', type => 'string', desc => 'Copy barcode against which to cancel the oldest hold' },
+ ],
+ 'return' => {
+ desc => 'Returns 1 on success, or nothing on failure'
+ }
+ }
+);
+
+
+__PACKAGE__->register_method(
+ method => "create_borrower_copy",
+ api_name => "fulfillment.connector.create_borrower_copy",
+ signature => {
+ desc => "Creates a pre-cat copy for borrower holds/circs",
+ params => [
+ { name => 'authtoken',
+ type => 'string',
+ desc => q/Either a valid auth token OR an arrayref
+ containing a username and password to log in as/
+ },
+ { name => 'ou_code',
+ type => 'string',
+ desc => 'org_unit shortname to use as the copy circ lib'
+ },
+ { name => 'barcode',
+ type => 'string',
+ desc => q/copy barcode. Note, if a barcode collision
+ occurs the barcode of the final copy may be different/,
+ },
+ { name => 'args',
+ type => 'hash',
+ desc => 'Hash of optional extra information: title, author, isbn'
+ }
+ ],
+ 'return' => {
+ desc => 'Copy object (hash) or nothing on failure'
+ }
+ }
+);
+
+sub create_borrower_copy {
+ my ($self, $client, $auth, $ou_code, $barcode, $args) = @_;
+ $args ||= {};
+
+ ($auth) = $self
+ ->method_lookup('fulfillment.connector.login')
+ ->run(@$auth) if ref $auth;
+
+ my $e = new_editor(authtoken => $auth, xact => 1);
+
+ my $e_copy = $e->search_asset_copy(
+ {deleted => 'f', barcode => $barcode});
+
+ # copy with the requested barcode already exists.
+ # Add a prefix to the barcode.
+ # TODO: make the prefix a setting
+ # TODO: maybe all such copies should be given a prefix for consistency
+ $barcode = "FF$barcode" if @$e_copy;
+
+ my $circ_lib = $e->search_actor_org_unit({shortname => $ou_code});
+ if (!@$circ_lib) {
+ $logger->error("Unable to locate org unit '$ou_code'");
+ $e->rollback;
+ return;
+ }
+
+ my $copy = Fieldmapper::asset::copy->new;
+ $copy->barcode($barcode);
+ $copy->circ_lib($circ_lib->[0]->id);
+ $copy->creator($e->requestor->id);
+ $copy->editor($e->requestor->id);
+
+ $copy->call_number(OILS_PRECAT_CALL_NUMBER);
+ $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
+ $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
+
+ # if the caller provided any additional metadata on the
+ # item we're creating, capture it in the dummy fields
+ $copy->dummy_title($args->{title} || "");
+ $copy->dummy_author($args->{author} || "");
+ $copy->dummy_isbn($args->{isbn} || "");
+
+ unless ($e->create_asset_copy($copy)) {
+ $logger->error("error creating FF precat copy");
+ $e->rollback;
+ return;
+ }
+
+ # fetch from DB to ensure updated values (dates, etc.)
+ $copy = $e->retrieve_asset_copy($copy->id);
+ $e->commit;
+ return recursive_hash($copy);
+}
+
+
+
+1;
+
--- /dev/null
+package FulfILLment::LAIConnector::Horizon;
+use base FulfILLment::LAIConnector;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::Fieldmapper;
+use FulfILLment::Util::Z3950;
+use FulfILLment::Util::SIP2Client;
+use XML::LibXML;
+use Data::Dumper;
+use Encode;
+use Unicode::Normalize;
+use DateTime;
+use MARC::Record;
+use MARC::File::XML ( BinaryEncoding => 'utf8' );
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+sub get_item {
+ my ($self, $item_ident) = @_;
+
+ my $sip_item = $self->sip_client->item_information_request($item_ident);
+ return unless $sip_item;
+
+ my $item = $sip_item;
+ $item->{barcode} = $sip_item->{item_identifier};
+ $item->{status} = $sip_item->{circulation_status};
+ $item->{location_code} = $sip_item->{permanent_location};
+
+ return $item;
+}
+
+sub get_item_batch {
+ my ($self, $item_barcodes) = @_;
+ return [map {$self->get_item($_)} @$item_barcodes];
+}
+
+sub get_record_by_id {
+ my ($self, $record_id) = @_;
+
+ my $bib = {};
+ my $attr = $self->{args}{extra}{'z3950.search_attr'};
+ my $xml = $self->z39_client->get_record_by_id($record_id, $attr);
+
+ # TODO: clean this up
+ if ($xml =~ /something went wrong/) {
+ $bib->{'error'} = 1;
+ $bib->{'error_message'} = $xml;
+ } else {
+ $bib->{'marc'} = $xml;
+ $bib->{'id'} = $record_id;
+ }
+
+ return $bib;
+}
+
+
+
+=comment Format for holdings via Z39.50
+
+<holdings>
+ <holding>
+ <localLocation>LIB NAME</localLocation>
+ <shelvingLocation>Juvenile Fiction</shelvingLocation>
+ <callNumber>J ROWLING</callNumber>
+ <circulations>
+ <circulation>
+ <availableNow value="1"/>
+ <restrictions>LIB NAME</restrictions>
+ <itemId>1234567890</itemId>
+ <renewable value="1"/>
+ <onHold value="0"/>
+ <temporaryLocation>Checked In</temporaryLocation>
+ </circulation>
+ </circulations>
+ </holding>
+ ...
+=cut
+
+sub get_items_by_record {
+ my ($self, $record_id) = @_;
+
+ my $attr = $self->{args}{extra}{'z3950.search_attr'};
+ my $xml = $self->z39_client->get_record_by_id($record_id, $attr, undef, 'opac', 1);
+
+ # entityize()
+ $xml = decode_utf8($xml);
+ NFC($xml);
+ $xml =~ s/&(?!\S+;)/&/gso;
+ $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
+
+ # strip control chars, etc.
+ # silence 'uninitialized value in substitution iterator'
+ no warnings;
+ $xml =~ s/([\x{0000}-\x{001F}])//sgoe;
+ use warnings;
+
+ my $doc = XML::LibXML->new->parse_string($xml);
+
+ my %map = (
+ circ_lib => 'localLocation',
+ location => 'shelvingLocation',
+ call_number => 'callNumber',
+ barcode => 'itemId',
+ status => 'temporaryLocation'
+ );
+
+ my @copies;
+ for my $node ($doc->findnodes('//holding')) {
+ my $copy = {};
+
+ for my $key (keys %map) {
+ my $fnode = $node->getElementsByTagName($map{$key})->[0];
+ $copy->{$key} = $fnode->textContent if $fnode;
+ }
+
+ push(@copies, $copy);
+ }
+
+ return \@copies;
+}
+
+sub get_user {
+ my ($self, $user_barcode, $user_pass) = @_;
+
+ # fetch the user using the default implementation
+ my $user = $self->SUPER::get_user($user_barcode, $user_pass);
+ return unless $user;
+
+ # munge the names...
+ # personal_name is delivered => SURNAME, GIVEN NAME
+ my @names = split(',', $user->{personal_name} || $user->{full_name});
+ $user->{full_name} = $user->{personal_name};
+ $user->{given_name} = $names[1];
+ $user->{surname} = $names[0];
+
+ return $user;
+}
+
+# copy hold
+sub place_borrower_hold {
+ my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_;
+ return $self->place_hold_via_sip(
+ undef, $item_barcode, $user_barcode, $pickup_lib);
+}
+
+# bib hold
+sub place_lender_hold {
+ my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_;
+
+ my $copy = $self->flesh_copy($item_barcode) or return;
+
+ return $self->place_hold_via_sip(
+ $copy->call_number->record->id,
+ undef, # item_barcode
+ $user_barcode,
+ $pickup_lib
+ );
+}
+
+sub delete_borrower_hold {
+ my ($self, $item_barcode, $user_barcode) = @_;
+
+ my $resp = $self->sip_client->delete_hold(
+ $user_barcode,
+ undef, undef, undef, undef,
+ $item_barcode
+ );
+
+ return $resp ? $self->translate_sip_hold($resp) : undef;
+}
+
+sub delete_lender_hold {
+ my ($self, $item_barcode, $user_barcode) = @_;
+ my $copy = $self->flesh_copy($item_barcode) or return;
+
+ my $resp = $self->sip_client->delete_hold(
+ $user_barcode,
+ undef, undef, undef, undef, undef,
+ $copy->call_number->record->id
+ );
+
+ return $resp ? $self->translate_sip_hold($resp) : undef;
+}
+
+sub delete_item_hold {
+ my ($self, $item_ident, $user_barcode) = @_;
+
+ my $resp = $self->sip_client->delete_hold(
+ $user_barcode, undef, undef, undef, undef, $item_ident);
+
+ return unless $resp;
+ return $self->translate_sip_hold($resp);
+}
+
+sub delete_record_hold {
+ my ($self, $record_id, $user_barcode) = @_;
+
+ my $resp = $self->sip_client->delete_hold(
+ $user_barcode, undef, undef, undef, undef, undef, $record_id);
+
+ return [] unless $resp;
+
+ my $blob = $self->translate_sip_hold($resp);
+ $blob->{bibID} = $record_id;
+ return $blob;
+}
+
+
+# ---------------------------------------------------------
+# BELOW NEEDS RE-TESTING
+# ---------------------------------------------------------
+
+sub get_user_holds {
+ my $self = shift;
+ my $user_barcode = shift;
+ my $user_pass = shift;
+ my @holds;
+
+ # TODO: requiring user_pass may be problematic...
+
+ # Horizon provides available holds and unavailable holds in
+ # separate lists, which requires multiple patron info requests.
+ # FF does not differentiate, though, so collect them all
+ # into one patron holds list.
+
+ # available holds
+ my $user = $self->sip_client->lookup_user({
+ patron_id => $user_barcode,
+ patron_pass => $user_pass
+ });
+
+ # TODO: fix the Available/Pending statuses?
+
+ if ($user) {
+ $logger->debug("User hold items = @{$user->{hold_items}}");
+ push(@holds, translate_patron_info_hold($_, 'Available'))
+ for @{$user->{hold_items}};
+ }
+
+ # unavailable holds
+ $user = $self->sip_client->lookup_user({
+ patron_id => $user_barcode,
+ patron_pass => $user_pass,
+ enable_summary_pos => 5
+ });
+
+ if ($user) {
+ $logger->debug("User pending hold items = @{$user->{hold_items}}");
+ push(@holds, translate_patron_info_hold($_, 'Pending'))
+ for @{$user->{hold_items}};
+ }
+
+ return \@holds;
+}
+
+sub translate_patron_info_hold {
+ my ($txt, $status) = @_;
+
+ # Horizon SIP2 patron info hold format
+ # |CDSOFFTESTB12 CENT 06/05/13 $0.00 b SO FF Test Book 1|
+ # |CDSOFFTESTB22 CENT 06/05/13 $0.00 b SO FF Test Book 2|
+
+ my ($barcode, undef, $xact_start, undef, undef, $title) = split(' ', $txt);
+
+ return {
+ placed_on => $xact_start,
+ status => $status,
+ title => $title,
+ barcode => $barcode,
+ itemid => $barcode
+ };
+}
+
+sub create_borrower_copy {
+ my ($self, $ref_copy, $ou_code) = @_;
+
+ return unless $self->ncip_client;
+
+ my $simple_rec = $ref_copy->call_number->record->simple_record;
+
+ my ($doc, @errs) = $self->ncip_client->request(
+ 'CreateItem',
+ item => {
+ barcode => $ref_copy->barcode,
+ call_number => $ref_copy->call_number->label,
+ title => $simple_rec->title,
+ author => $simple_rec->author,
+ owning_lib => $ou_code
+ }
+ );
+
+
+ @errs = ('See Error Log') unless @errs or $doc;
+
+ if (@errs) {
+ $logger->error(
+ "FF unable to create borrower copy ".
+ $ref_copy->barcode." : @errs");
+ return;
+ }
+
+ my $barcode = $doc->findnodes(
+ '//CreateItemResponse/UniqueItemId/ItemIdentifierValue'
+ )->string_value;
+
+ if (!$barcode) {
+ $logger->error("FF unable to create borrower copy : ".$doc->toString);
+ return;
+ }
+
+ $logger->info("FF created borrower copy $barcode");
+
+ return {barcode => $barcode};
+}
+
+
+1;
--- /dev/null
+package FulfILLment::LAIConnector::III;
+use base FulfILLment::LAIConnector;
+use strict; use warnings;
+use Net::Telnet;
+use Net::OpenSSH;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+# connect to SSH / Millenium terminal app
+sub ssh_connect {
+ my $self = shift;
+
+ my $host = $self->{'ssh.host'} || $self->{'host.item'} || $self->{host};
+ my $user = $self->{'ssh.user'} || $self->{'user.item'} || $self->{user};
+ my $pass = $self->{'ssh.passwd'} || $self->{'passwd.item'} || $self->{passwd};
+
+ # creating a pty spawns a child process make sure
+ # we're not picking up any existing sigchld handlers
+ $SIG{CHLD} = 'DEFAULT';
+
+ $logger->info("FF III SSH connecting to $user\@$host");
+
+ my $ssh = Net::OpenSSH->new(
+ $host, user => $user, password => $pass);
+
+ if ($ssh->error) {
+ $logger->error("FF III SSH connect error " . $ssh->error);
+ return;
+ }
+
+ my ($fh, $pid) = $ssh->open2pty();
+ my $term = Net::Telnet->new(Fhopen => $fh);
+
+ # keep these around for later cleanup and to ensure $ssh stays in scope
+ $self->{ssh_parent} = $ssh;
+ $self->{ssh_pty} = $fh;
+ $self->{ssh_pid} = $pid;
+ $self->{ssh_term} = $term;
+
+ return 1 if $term->waitfor(-match => '/SEARCH/', -errmode => "return");
+
+ $logger->error("FF III never received SSH menu prompt");
+ $self->ssh_disconnect;
+ return;
+}
+
+sub ssh_disconnect {
+ my $self = shift;
+ return unless $self->{ssh_parent};
+
+ $logger->debug("FF III SSH disconnecting");
+
+ $self->{ssh_pty}->close if $self->{ssh_pty};
+ $self->{ssh_term}->close if $self->{ssh_term};
+ $self->{ssh_term} = undef;
+ $self->{ssh_parent} = undef;
+
+ # required to avoid <defunct> SSH processes
+ if (my $pid = $self->{ssh_pid}) { # assignment
+ $logger->debug("FF III SSH waiting on child $pid");
+ waitpid($pid, 0);
+ }
+}
+
+# send command to SSH term and wait for a response
+sub send_wait {
+ my ($self, $send, $wait, $timeout) = @_;
+
+ if ($send) {
+ $logger->debug("FF III sending SSH command '$send'");
+ $self->{ssh_term}->print($send);
+ }
+
+ my @response;
+
+ if ($wait) {
+ $logger->debug("FF III SSH waiting for '$wait'...");
+
+ @response = $self->{ssh_term}->waitfor(
+ -match => "/$wait/",
+ -errmode => 'return',
+ -timeout => $timeout || 10
+ );
+
+ if (@response) {
+ my $txt = join('', @response);
+ $txt =~ s/[[:cntrl:]]//mg;
+ $logger->debug("FF III SSH wait received text: $txt");
+ warn "==\n$send ==> \n$txt\n";
+
+ } else {
+ $logger->warn(
+ "FF III SSH timed out waiting for '$wait' :".
+ $self->{ssh_term}->errmsg);
+ }
+ }
+
+ return @response;
+}
+
+
+sub get_user {
+ my ($self, $user_barcode, $user_pass) = @_;
+
+ return $self->SUPER::get_user($user_barcode, $user_pass)
+ if $self->sip_client;
+
+ # no SIP, use SSH instead..
+ $self->ssh_connect or return;
+
+ my $user;
+ eval { $user = $self->get_user_guts($user_barcode, $user_pass) };
+ $logger->error("FF III error getting user $user_barcode : $@");
+
+ $self->ssh_disconnect;
+ return $user;
+}
+
+sub get_items_by_record {
+ my ($self, $record_id) = @_;
+ $self->ssh_connect or return;
+
+ my @items;
+ eval { @items = $self->get_items_by_record_guts($record_id) };
+ $logger->error("FF III get_items_by_record() died : $@") if $@;
+
+ $self->ssh_disconnect;
+ return @items ? \@items : [];
+}
+
+# TODO: test with 2011
+sub get_record_by_id {
+ my ($self, $rec_id) = @_;
+
+ if ($self->z39_client) {
+ $logger->info("FF III fetching record from Z39: $rec_id");
+ chop($rec_id); # z39 does not want the final checksum char.
+ return { marc => $self->z39_client->get_record_by_id($rec_id) };
+ } else {
+ $logger->info("FF III fetching record from SSH: $rec_id");
+ return $self->get_record_by_id_ssh($rec_id);
+ }
+}
+
+1;
--- /dev/null
+package FulfILLment::LAIConnector::III::2009B_1_2;
+use base FulfILLment::LAIConnector::III;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+use MARC::Record;
+use MARC::Batch;
+use MARC::File::XML (BinaryEncoding => 'utf8');
+use LWP::UserAgent;
+use HTTP::Request;
+use WWW::Mechanize;
+use XML::Simple;
+use Sip;
+use FulfILLment::Util::Z3950;
+use FulfILLment::Util::SIP2Client;
+use Data::Dumper;
+
+my $ua = LWP::UserAgent->new;
+$ua->agent("FulfILLment/1.0");
+
+sub get_items_by_record_guts {
+ my ($self, $record_id) = @_;
+ my @items;
+
+ $self->send_wait('S', 'prominently') or return;
+ $self->send_wait('B', 'RECORD NO') or return;
+ $self->send_wait('R', 'Type Record') or return;
+ $self->send_wait($record_id, 'Record SUMMARY') or return;
+
+ my ($prematch, $match) = $self->send_wait('S',
+ 'To see a particular|Copy Type:\w+') or return;
+
+ if ($match =~ /Copy Type/) {
+ # single-copy bib jumps right to copy details.
+
+ # we need the Copy Type from the match as well,
+ # so push it back into the main text
+ $prematch .= $match;
+
+ # TODO: see why parse_item_screen() works fine for
+ # single copies in 2011_1_3 but not here. there's
+ # an opportunity for more consolidation w/i the superclass
+ my @barcodes = ($prematch =~ /BARCODE\s+(\d+)/g);
+ my @location_code = ($prematch =~ /LOCATION:\s(\w+)\s+/);
+ $prematch =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi;
+ $prematch =~ s/\e\[k//gi;
+ $prematch =~ s/[[:cntrl:]]//g;
+ my @fstring = ($prematch =~ /TITLE(.*)IMPRINT/);
+ my @fingerprint = split(/IMPRINT|\s{2,}/,$fstring[0]);
+ my $item_fields = $self->parse_item_screen($prematch, $barcodes[0]);
+
+ unless ($item_fields and $barcodes[0]) {
+ $logger->warn(
+ "FF III unable to parse single-item screen for $record_id");
+ return;
+ }
+
+ my $item = {
+ fingerprint => $fingerprint[1],
+ call_number => $item_fields->{call_number},
+ due_date => $item_fields->{due_date},
+ barcode => $barcodes[0],
+ holdable => "t",
+ location_code => $location_code[0],
+ error => 0,
+ error_message => '',
+ item_id => $item_fields->{item_id},
+ };
+
+ push(@items, $item);
+
+ } else {
+ # multi-copy bib
+
+ my @item_indexes = ($prematch =~ /ITEM\s+(\d+)\s>/g);
+
+ for my $index (@item_indexes) {
+ my @response = $self->send_wait($index, 'Record SUMMARY') or last;
+ my $screen = $response[0];
+
+ my @barcodes = ($screen =~ /BARCODE\s+(\d+)/g);
+
+ $logger->debug("FF III item screen contains ".
+ length($screen)." characters");
+
+ my $item;
+ $logger->debug("FF III parsing item screen for entry $index");
+ $item = $self->parse_item_screen($screen, $barcodes[0]);
+
+ unless ($item and $item->{barcode}) {
+ $logger->warn(
+ "FF III unable to parse item screen for $record_id");
+ last;
+ }
+
+ push(@items, $item);
+
+ # return to the summary screen
+ $self->send_wait('S', 'To see a particular') or last;
+ }
+ }
+
+ return @items;
+}
+
+sub get_user_guts {
+ my ($self, $user_barcode, $user_pass) = @_;
+
+ $self->send_wait('S', 'prominently') or return;
+ $self->send_wait('P', 'RECORDS') or return;
+ $self->send_wait('B', 'BARCODE') or return;
+
+ my ($txt, $match) = $self->send_wait(
+ $user_barcode,
+ 'Record|BARCODE not found'
+ ) or return;
+
+ if ($match =~ /BARCODE not found/) {
+ $logger->info("FF III user '$user_barcode' not found");
+ return;
+ }
+
+ $txt =~ s/\[\d+;\d+(;\d+)?[Hm]//g;
+ $txt =~ s/^\d\d\d\s+.+//g;
+ $txt =~ s/\x1b//g;
+ $txt =~ s/\[0m//g;
+ $txt =~ s/\[0xF\]//g;
+ $txt =~ s/[[:cntrl:]]//g;
+
+ my $user = {
+ exp_date => qr/EXP DATE:\s(\d+\-\d+\-\d+)/,
+ user_id => qr/PIN\s+([A-Za-z0-9]+)INPUT/,
+ notice_pref => qr/NOTICE PREF:\s(.*)TOT/,
+ lang_pref => qr/LANG PREF:\s+(\w+)/,
+ mblock => qr/MBLOCK:\s+([A-Za-z0-9\-])\s+/,
+ patron_agency => qr/PAT AGENCY:\s+(\d+)\s+/,
+ overdue_items_count => qr/HLODUES:\s+(\d+)/,
+ notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/,
+ home_ou => qr/HOME LIBR:\s+(\w+)/,
+ total_renewals => qr/TOT RENWAL:\s+(\d+)/,
+ email_address => qr/EMAIL ADDR\s+([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,3})/,
+ claims_returned_count => qr/CL RTRND:\s(\d+)/,
+ loaned_items_count => qr/CUR CHKOUT:\s(\d+)/,
+ money_owed => qr/MONEY OWED:\s(\$\d+\.\d+)/,
+ overdue_penalty => qr/OD PENALTY:\s(\d+)/,
+ institution_code => qr/HOME LIBR:\s+(\w+)/,
+ block_until => qr/BLK UNTIL:\s(\d+\-\d+\-\d+)/,
+ full_name => qr/PATRN NAME\s+(.*)ADDRESS/,
+ street_address => qr/ADDRESS\s+(.*)SSN/,
+ ssn => qr/SSN #\s+(\d+)P BARCODE/,
+ photocopy_count => qr/PIUSE:\s+(\d+)/,
+ patron_code1 => qr/PCODE1:\s+(\d+)/,
+ patron_code2 => qr/PCODE2:\s+(\d+)/,
+ patron_code3 => qr/PCODE3:\s+(\d+)/,
+ patron_code4 => qr/PCODE4:\s+(\d+)/,
+ notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/,
+ ill_request => qr/ILL REQUES:\s+(\d+)/,
+ last_circ_date => qr/CIRCACTIV:\s(\d+\-\d+\-\d+)/,
+ patron_type => qr/P TYPE:\s(\d+)/,
+ census => qr/CENSUS:\s(\d+)\s+/,
+ total_checkouts => qr/TOT CHKOUT:\s(\d+)/,
+ checkouts => qr/CUR CHKOUT:\s+(\d+)/,
+ phone => qr/TELEPHONE\s+(\d\d\d\-\d\d\d\-\d\d\d\d)/,
+ input_by => qr/INPUT BY\s+([A-Za-z\/]+)/,
+ birth_date => qr/BIRTH DAT:(\d\d\-\d\d-\d\d)/,
+ cur_item_A => qr/CUR ITEMA:\s(\d+)/,
+ cur_item_B => qr/CUR ITEMB:\s(\d+)/,
+ cur_item_C => qr/CUR ITEMC:\s(\d+)/,
+ cur_item_D => qr/CUR ITEMD:\s(\d+)/,
+ error => 0,
+ };
+
+ for my $key (keys %$user) {
+ my ($val) = ($txt =~ $user->{$key});
+ $user->{$key} = $val;
+ }
+
+ my %prefs = (
+ z => 'email',
+ t => 'phone',
+ p => 'secondary phone'
+ );
+
+ $user->{notice_pref} = $prefs{$user->{notice_pref}};
+ $user->{barcode} = $user_barcode;
+
+ return $user;
+}
+
+
+sub get_item {
+ my ($self, $item_barcode) = @_;
+
+ $self->ssh_connect or return;
+
+ $self->send_wait('S', 'prominently') or return;
+ $self->send_wait('I', 'SEARCHING RECORDS') or return;
+ $self->send_wait('B', 'BARCODE') or return; # NOTE: 2011 uses 'D'
+ my ($screen) = $self->send_wait($item_barcode, 'NEW Search') or return;
+
+ $self->ssh_disconnect;
+
+ if ($screen =~ /not found|PATRON Information/g) {
+ $logger->info("FF III unable to locate item $item_barcode");
+ return;
+ }
+
+ my $item = $self->parse_item_screen($screen, $item_barcode);
+
+ $logger->error("FF III error parsing item screen for $item_barcode")
+ unless $item;
+
+ return $item;
+}
+
+# ---------------------------------------------------------------------------
+# Everything below here needs modification and testing
+# ---------------------------------------------------------------------------
+
+sub get_item_fields{
+ my $self=$_[0];
+ my $itemData=$_[1];
+ #print Dumper $itemData;
+ $itemData =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi;
+ $itemData =~ s/\e\[k//gi;
+ $itemData =~ s/[[:cntrl:]]//g;
+ $itemData =~ s/VOLUME/VOLUME:/g;
+ $itemData =~ s/CALL #/CALL #:/g;
+ $itemData =~ s/R > Browse Nearby EntriesI > Show similar ITEMSN >//g;
+ $itemData =~ s/R > RETURN to BrowsingZ > Show Items Nearby on ShelfF > FORWARD browseI > Show similar ITEMSN >//g;
+ $itemData =~ s/U > Show BIBLIOGRAPHIC RecordZ > Show Items Nearby on Shelf//g;
+ $itemData =~ s/\+ > ADDITIONAL options1-2,N,A,Z,I,U,T,E,\+\).*ITEM Information//g;
+ $itemData =~ s/I > Show similar ITEMSA > ANOTHER Search by RECORD #//g;
+ $itemData =~ s/U > Show similar BIBLIOGRAPHIC RecordS > Record SUMMARY//g;
+ $itemData =~ s/U > Show BIBLIOGRAPHIC RecordS > Record SUMMARY//g;
+ $itemData =~ s/T > Display MARC RecordE > Mark item for//g;
+ $itemData =~ s/U > Show BIBLIOGRAPHIC Record//g;
+ $itemData =~ s/NEW Search//g;
+ $itemData =~ s/N,A,S,Z,I,U,T,E\)//g;
+ $itemData =~ s/N >//g;
+
+ if(my @l = ($itemData =~ m/(\d+)BARCODE/g)){
+ $itemData =~ s/VOLUME:/VOLUME: $l[0]/g;
+ }
+
+
+ $itemData =~ s/(\d+)BARCODE/BARCODE:/g;
+ #$itemData =~ s/1BARCODE/BARCODE/g;
+ my @fields = grep {defined and not /^\s*$/} split /(\s{2,})|(- -)/, $itemData;
+ my $i=0;
+ my @newfields = [];
+
+ foreach (@fields){
+ #$_.=':' if $_ eq 'VOLUME';
+ $_.=':' if $_ eq 'BARCODE';
+
+ if((/^[ \-]+$/ or $newfields[$#newfields] eq 'BARCODE:') and @newfields){
+ $newfields[$#newfields] .=$_;
+ }else{
+ push @newfields, $_;
+ }
+
+ $i++;
+ }
+
+ return \@newfields;
+}
+
+
+
+
+
+
+
+sub get_item_call_and_bib_number{
+ my $self = $_[0];
+ my $item_data = $_[1];
+ #print Dumper $item_data;
+ $item_data =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi;
+ $item_data =~ s/\e\[k//gi;
+ $item_data =~ s/[[:cntrl:]]//g;
+
+ #get Call Number
+ my @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s+(.*?)AUTHOR/);
+
+ if(not $c[0]){
+ @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s(.*?)TITLE/);
+ }
+
+ #get Barcode
+ my @b = ($item_data =~ /BARCODE:.*(B.*)\s+BIBLIOGRAPHIC Information/);
+
+ if($b[0]){
+ $b[0] =~ s/\s//g;
+ }
+
+ #get title
+ my @f = split("TITLE",$item_data) ;
+ my @fingerprint = ($f[0] =~ /\s+(.*)\s{5,}/);
+
+ my @out = ($b[0],$c[0],$fingerprint[0]);
+ #print Dumper @out;
+ return \@out;
+
+}
+
+
+
+
+
+sub parse_item_screen{
+ my $self = $_[0];
+ my $screen = $_[1];
+ my $barcode = $_[2];
+ my $jhash={};
+ my @nvp;
+ my $label;
+ my $value;
+ my @screen_split;
+ my $rs = $screen;
+ my @record_number = ($rs =~ /(I\d.*)\s+ITEM Information/g);
+ my $e = "ESC[7;2H";
+ $e = ord($e);
+
+ $record_number[0] =~ s/\s+//g;
+ push @screen_split, split(/ITEM Information/,$screen);
+ #print Dumper @screen_split;
+ my $fields = $self->get_item_fields($screen_split[1]);
+ my $call_and_bib = $self->get_item_call_and_bib_number($screen_split[0]);
+
+ unless ($call_and_bib->[1]) {
+ # in multi-copy records, the call number is embedded in the
+ # copy data and has no predictable terminating string.
+ # capture it all and chop it off at the first occurence
+ # of 2 consecutive spaces
+ my $scr = $screen_split[1];
+ $scr =~ s/[[:cntrl:]]//mg;
+ my @c = ($scr =~ /CALL #\s+(.*)/);
+ $c[0] =~ s/(.*?)\s{2}.*/$1/mg if $c[0];
+ $call_and_bib->[1] = $c[0] || 'UNKNOWN';
+ }
+
+ # remove pesky trailing junk
+ $call_and_bib->[1] =~ s/^\s+//g;
+ $call_and_bib->[1] =~ s/\s+$//g;
+
+ $logger->warn("FF III unble to find callnumber")
+ unless $call_and_bib->[1];
+
+ #print Dumper $fields;
+
+ foreach(@$fields){
+ @nvp=split(":",$_);
+ $label=$nvp[0];
+ $value=$nvp[1];
+ if($label eq "DUE DATE"){
+ $label="due_date";
+ }elsif($label eq "BARCODE"){
+ $label="barcode";
+ }elsif($label eq "STATUS"){
+ $label="holdable";
+ }elsif($label eq "LOCATION"){
+ $label="location";
+ }
+
+ if($label eq "holdable" and $value eq " -"){
+ $value="t";
+ }elsif($label eq "holdable" and $value eq "e"){
+ $value="t";
+ }elsif($label eq "holdable" ){
+ $value="f";
+ }
+
+ $jhash->{$label}=$value;
+ }
+
+ #print Dumper $call_and_bib;
+ if($jhash->{due_date}){
+ if($jhash->{'due_date'} eq "- -"){
+ $jhash->{'due_date'} = '';
+ }
+ }
+ $jhash->{'call_number'} = $call_and_bib->[1];
+ $jhash->{'bib_id'}=$call_and_bib->[0];
+ $jhash->{'item_id'} = $record_number[0];
+ $jhash->{'barcode'} = $barcode;
+ $jhash->{'error_message'} = '';
+ $jhash->{'fingerprint'} = '';
+ return $jhash;
+}
+
+
+
+sub get_item_by_call_number{
+ my $self = $_[0];
+ my $ssh = $self->initialize;
+ my $item_id = $_[1];
+ my $call_number_type = $_[2];
+ my @out;
+ my @entries; #If there are multiple entries for an item, the entries are stored here.
+ #print "preparing to search the catalog\n";
+ $ssh->print("S");
+ $ssh->waitfor(-match => '/prominently\?/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ #print "ok\n";
+ #print "selecting option to search for items\n";
+ $ssh->print("I");
+ $ssh->waitfor(-match => '/SEARCHING RECORDS/',
+ -errmode => "return")or die "search failed;", $ssh->lastline;
+ #print "ok\n";
+ #select attribute to search by, i.e title, barcode etc...
+ $ssh->print("C");
+ $ssh->waitfor(-match => '/CALL NUMBER SEARCHES/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+
+ if(lc($call_number_type) eq "dewey"){
+
+ $ssh->print("D");
+ $ssh->waitfor(-match => '/DEWEY CALL NO :/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+
+ }elsif(lc($call_number_type) eq "lc"){
+ $ssh->print("C");
+ $ssh->waitfor(-match => '/LC CALL NO :/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+
+ }elsif(lc($call_number_type) eq "local"){
+ $ssh->print("L");
+ $ssh->waitfor(-match => '/LOCAL CALL NO :/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ }
+
+ $ssh->print($item_id);
+ push @out,$ssh->waitfor(-match => '/NEW Search/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ #print "Done.\n";
+
+ if(my @num_entries = ($out[0] =~ /(\d+)\sentries found/)){
+ #my $i = 0;
+ #$i++;
+ $ssh->print(1);
+ push @out,$ssh->waitfor(-match => '/NEW Search/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ #print "Done.\n";
+ }
+
+ $self->ssh_disconnect;
+ my @items;
+ push @items,$self->parse_item_screen($out[2]);
+ return \@items;
+}
+
+
+#Under construction
+
+sub get_item_by_bib_number_z3950{
+ my $self = $_[0];
+ my $bibID = $_[1];
+ my $marc = $self->get_bib_records_by_record_number_z3950($bibID);
+ my $batch = MARC::Batch->new('XML', $marc );
+ while (my $m = $batch->next ){
+ print $m->subfield(650,"a"),"\n";
+
+ }
+ #my $record = $batch->next();
+ #print Dumper $record;
+
+}
+
+
+
+
+
+# Method: get_bib_records_by_record_num
+# Params:
+# idList => list of record numbers
+
+#BIBLIOGRAPHIC RECORDS
+#Notes: Section contains methods to retrieve and parse bibliographic records
+#==================================================================================
+
+
+sub get_bib_records_by_record_number{
+ my $self = $_[0];
+ my $ssh = $self->initialize;
+ my $id = $_[1];
+ my $json=JSON::XS->new();
+ my $count=0;
+ my @out;
+ my @screen;
+ my @marc;
+ my @bib = ();
+ my $jhash={};
+
+ eval{
+ if(ref($ssh) eq "ARRAY"){
+ return $ssh;
+ }
+
+ #select SEARCH the catalog
+ #print "getBibRecords\n";
+ #print "preparing to search the catalog\n";
+ $ssh->print("S");
+
+ $ssh->waitfor(-match => '/prominently\?/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+
+ #print "ok\n";
+ #print "selecting option to search for bibliographic records\n";
+ #select item record option
+ $ssh->print("B");
+ $ssh->waitfor(-match => '/SEARCHING RECORDS/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+
+ #print "ok\n";
+ #print "selecting option to search by record number\n";
+ #select attribute to search by, i.e title, barcode etc...
+ $ssh->print("R"); #search by record number
+ #print "ok\n";
+ #print "searching for id=$id\n";
+ #search for id
+ $ssh->print($id);
+ my $first = 1;
+ #print "searching more of the document\n";
+ my @bid;
+
+ my $get_more = sub {
+ my @temp_screen = ();
+ my @lines;
+ my $last = 0;
+ my $get_more = shift;
+
+ if($first == 1){
+ $ssh->print("T");
+ $ssh->waitfor(-match => '/BIBLIOGRAPHIC Information/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+ #-errmode => "return") or die "search failed;", $ssh->lastline;
+ $ssh->print("M");
+ #$first = 0;
+ }
+
+ $ssh->print("M");
+ $ssh->print("T");
+ #delete the following two lines
+
+ push @temp_screen ,$ssh->waitfor(-match => '/Regular Display/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+ #-errmode => "return") or die "search failed;", $ssh->lastline;
+ #print Dumper $temp_screen[0];
+
+ @bid = ($temp_screen[0] =~ /([Bb].*)\s+BIBLIOGRAPHIC Information/);
+ if($temp_screen[0] =~ /COUNTRY:/g and $first == 0 ){
+ #print Dumper $temp_screen[0];
+ #print "Reached end of record... I think\n";
+ return 1;
+ }
+ push @lines,split(/\[\d+;\d+(;\d+)?[Hm]/,$temp_screen[0]);
+
+ foreach (@lines){
+ if($_){
+ $_ =~ s/^(\s+)//g;
+ if($_ =~/^\d\d\d\s+.+/g){
+ $_ =~ s/\x1b//g;
+ $_ =~ s/\[0m//g;
+ $_ =~ s/\[0xF\]//g;
+ $_ =~ s/[[:cntrl:]]//g;
+ push @marc,$_;
+ #print Dumper $_;
+ }
+ }
+ }
+ #print Dumper @lines;
+
+ $first = 0;
+ #print Dumper @marc;
+ $ssh->print("M");
+ $ssh->print("T");
+ @temp_screen = ();
+ $get_more->($get_more);
+ };
+
+
+ $get_more->($get_more);
+
+ my @id = ($bid[0]) ? ($bid[0] =~ /([Bb][0-9]+)\s+/) : "" ;
+ #print Dumper @marc;
+
+ #print "bid = ".$id[0]."\n";
+ #print "ok\n";
+ #push @out, $self->parse_bib_records( $self->grab_bib_screen($ssh,$id), $id );
+ #print "logging out\n";
+ $ssh->print("N");
+ $ssh->print("Q");
+ $ssh->print("Q");
+ $ssh->print("Q");
+ $ssh->print("X");
+ $ssh->waitfor(-match => '/closed/',
+ #-errmode => "return")or die "log out failed;", $ssh->lastline;
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+
+ #print "logged out\n";
+ my $rec = breaker2marc(\@marc);
+ $rec->insert_fields_ordered(
+ MARC::Field->new(
+ '907',
+ ' ',
+ ' ',
+ 'a' => $id[0]
+ )
+ ) if (!$rec->subfield( '907' => 'a' ));
+
+ my $x = $rec->as_xml_record;
+ $x =~ s/^<\?.+?\?>.//sm;
+ #my $jhash={};
+ $jhash->{'marc'}=$x;
+ #$bid =~ s/\[/$bid/g;
+ $jhash->{'id'}=$id[0];
+ $jhash->{'format'}="marcxml";
+ $jhash->{error} = 0;
+ $jhash->{error_message} = '';
+ #print "xml = $x\n";
+ #my @bib = ();
+ push @bib,$jhash;
+ #warn Dumper \@bib;
+ return \@bib;
+
+ 1;
+ }or do {
+ $jhash->{error} = 1;
+ $jhash->{error_message} = $@;
+ push @bib,$jhash;
+ return \@bib;
+ }
+
+}
+
+
+
+
+sub get_range_of_records{
+ my $self = $_[0];
+ my $list = $_[1];
+ my $dir = `pwd`;
+ my $file = "/openils/lib/perl5/FulfILLment/WWW/LAIConnector/conf/III_2009B_1_2/marc/marc_dump.mrc";
+ open FILE, ">$file" or die &!;
+
+ foreach my $r (@$list){
+ my $record = $self->get_bib_records_by_record_number($r);
+
+ print FILE $record->[0]->{marc};
+
+ }
+
+ close FILE;
+ my @out;
+ my $jhash;
+ push @out,$jhash;
+ return \@out;
+
+}
+
+
+
+
+
+
+
+sub breaker2marc {
+ my $lines = shift;
+ my $delim = quotemeta(shift() || '|');
+ my $rec = new MARC::Record;
+ for my $line (@$lines) {
+
+ chomp($line);
+
+ if ($line =~ /^=?(\d{3})\s{2}(.)(.)\s(.+)$/) {
+
+ my ($tag, $i1, $i2, $rest) = ($1, $2, $3, $4);
+ if ($tag < 10) {
+ $rec->insert_fields_ordered( MARC::Field->new( $tag => $rest ) );
+
+ } else {
+
+ my @subfield_data = split $delim, $rest;
+ if ($subfield_data[0]) {
+ $subfield_data[0] = 'a' . $subfield_data[0];
+ } else {
+ shift @subfield_data;
+ }
+
+ my @subfields;
+ for my $sfd (@subfield_data) {
+ if ($sfd =~ /^(.)(.+)$/) {
+ push @subfields, $1, $2;
+ }
+ }
+
+ $rec->insert_fields_ordered(
+ MARC::Field->new(
+ $tag,
+ $i1,
+ $i2,
+ @subfields
+ )
+ ) if @subfields;
+ }
+ }
+ }
+
+ return $rec;
+}
+
+
+#END BIBLIOGRAPHIC RECORDS
+#=====================================================================================
+
+
+
+
+
+#Places hold on a III server through the web interface
+
+
+
+sub placeHold{
+ #use WWW::Curl::Easy;
+ my $self = shift;
+ my $host = $self->{host};
+ my $port = $self->{port};
+ my $name = $self->{login};
+ my $user_barcode = $self->{password};
+ my $bib_id = shift;
+
+ if(length($bib_id) > 8){
+ chop($bib_id);
+ }
+
+ my $patron_sys_num = shift;
+ my $url = "http://$host/search~SO?/.$bib_id/.$bib_id/1%2C1%2C1%CB/request~$bib_id?name=$name&code=$user_barcode";
+ my @out;
+ my $response_body;
+ my $mech = WWW::Mechanize->new();
+ my $error_msg;
+ my $response;
+ my @radioInputs;
+ my $content;
+ my $hold = {};
+
+ eval{
+ $mech->post($url);
+ $mech->form_name('patform');
+ $mech->field('name',$name);
+ $mech->field('code',$user_barcode);
+ $response = $mech->submit();
+ $mech->submit();
+ $content = $mech->content;
+ };
+
+ if($@){
+ $hold->{error} = 1;
+ $hold->{error_message} = "check username and password , error : $@";
+ push @out,$hold;
+ return \@out;
+ }
+
+ my @title = ($content =~ /<p>Requesting <strong>(.+)<\/strong><br \/><p>/);
+ my @err_response_msg = ($content =~ /<font color="red" size="\+2">(.+)<\/font>/g);
+ my @success_response_msg = ($content =~ /Your request for .* was successful./g);
+ my @delivered_to = ($content =~ /Your request will be delivered to .* when it is available./);
+
+ if($content =~ /No Such Record/){
+ $hold->{error} = 1;
+ $hold->{error_message} = "A hold could not be placed on $bib_id, no such record\n";
+ push @out,$hold;
+ return \@out;
+ }
+
+
+ if($content =~ /Request denied/){
+ $err_response_msg[0] =~ s/<strong>//;
+ $err_response_msg[0] =~ s/<\/strong>//;
+ $hold->{error} = 1;
+ $hold->{error_message} = $err_response_msg[0];
+ $hold->{title} = $title[0];
+ push @out,$hold;
+ return \@out;
+ }elsif($content =~ /Your request for .* was successful./g){
+ $success_response_msg[0] =~ s/<strong>//;
+ $success_response_msg[0] =~ s/<\/strong>//;
+ $hold->{error} = 0;
+ $hold->{success_message} = $success_response_msg[0];
+ $hold->{title} = $title[0];
+ push @out,$hold;
+ return \@out;
+ }
+
+ @title = ($content =~ /class="bibInfoLabel">Title<\/td>\n<td class="bibInfoData">\n<strong>(.*)<\/strong>/g);
+
+ if($title[0]){
+ $hold->{error} = 0;
+ $hold->{success_message} = "Your request for $title[0] was successful.";
+ $hold->{title} = $title[0];
+ push @out,$hold;
+ return \@out;
+ }
+}
+
+
+
+
+sub parse_hold_response{
+ my $self = $_[0];
+ my $txt = $_[1];
+ my @resp = split("END SEARCH WIDGET -->",$txt);
+ my $success = ($resp[1] =~ /denied/) ? "false" : "true";
+ if(!defined($resp[1])){$success = "false"}
+ my $msg = $resp[1];
+ $msg =~ s/<p>//g;
+ $msg =~ s/<\/p>//g;
+ $msg =~ s/<br \/>//g;
+ $msg =~ s/ //g;
+ $msg =~ s/<!-End the bottom logo table-->//g;
+ $msg =~ s/<\/body>//g;
+ $msg =~ s/<\/html>//g;
+ $msg =~ s/<\/strong\>//g;
+ $msg =~ s/<strong>//g;
+ $msg =~ s/\./\. /g;
+ $msg =~ s/\n//g;
+ $msg =~ s/<font color="red"\s+size="\+2">/\. /g;
+ $msg =~ s/<\/font>//g;
+ my $data = {};
+ $data->{'success'} = $success;
+ $data->{'message'} = $msg;
+ my $out = [];
+ $out->[0] = $data;
+ return $out;
+}
+
+
+
+sub list_holds{
+ my $self = $_[0];
+ my $base_url = $self->{host};
+ $base_url = "https://".$base_url;
+ my $port = $self->{port};
+ my $username = $_[1];
+ my $user_barcode = $_[2];
+ my $patron_sys_num = $_[3];
+ my $action = $_[4] || "list_holds";
+ my $response_body;
+ my $request_parameters;
+ $request_parameters = "name=$username&code=$user_barcode";
+ my $get_string = "/patroninfo~SO/$patron_sys_num/holds";
+ my $url = $base_url.$get_string."?".$request_parameters;
+ my $out;
+ my $mech = WWW::Mechanize->new();
+ $mech->post($url);
+
+
+ if($mech->success){
+ $out = $self->parse_hold_list($mech->content,$action);
+ }else{
+ $out = [{error => 1, error_message => 'There was a problem with the request' }];
+ }
+
+ if( not defined($out->[0]) ){
+ $out = [{error => 0, error_message => 'No holds were found for this user' }];
+ }
+
+ #print Dumper $out;
+ return $out;
+
+}
+
+
+
+sub list_holds_by_bib{
+ my $self = shift;
+ my $host = shift;
+ my $port = shift;
+ my $login = shift;
+ my $user_barcode = shift;
+ my $bibID = shift;
+ my $patron_sys_num = shift;
+ my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num);
+ my @out;
+
+ foreach(@$holds){
+ if($_->{'bibid'} eq $bibID){
+ push @out,$_;
+ }
+ }
+
+ return \@out;
+}
+
+
+
+sub list_holds_by_item{
+ my $self = shift;
+ my $host = shift;
+ my $port = shift;
+ my $login = shift;
+ my $user_barcode = shift;
+ my $itemID = shift;
+ my $patron_sys_num = shift;
+ my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num);
+ my @out;
+
+ foreach(@$holds){
+ if($_->{'itemid'} eq $itemID){
+ push @out,$_;
+ }
+ }
+
+ return \@out;
+}
+
+
+
+
+
+
+
+sub delete_hold{
+
+ my $self = shift;
+ my $host = $self->{host};
+ my $port = $self->{port};
+ my $userid = $self->{login};
+ $userid =~ s/\s/%20/g;
+ my $user_barcode = $self->{password};
+ my $search_id = shift;
+ my $id_type = shift;
+ my $patron_sys_num = shift;
+ my $response_body;
+ my $holds = $self->list_holds($userid,$user_barcode,$patron_sys_num);
+ my $itemid;
+ my $linkid;
+ my $num_holds_before = @$holds;
+ my $num_holds_after;
+
+ if($id_type eq "bib"){
+
+ if(length($search_id) > 8){
+ chop($search_id);
+ }
+
+ foreach(@$holds){
+ #print "bibid = $_->{bibid} search_id = $search_id\n";
+ if($_->{'bibid'} eq $search_id){
+ #print "item id = ".$_->{'itemid'}."\n";
+ $itemid = $_->{'itemid'};
+ $linkid = $_->{'linkid'};
+ }
+ }
+ }elsif($id_type eq "item"){
+ foreach(@$holds){
+ if($_->{'itemid'} eq $search_id){
+ #print "item id = ".$_->{'itemid'}."\n";
+ $itemid = $_->{'itemid'};
+ $linkid = $_->{'linkid'};
+ }
+ }
+ }
+
+ $itemid = (defined($itemid)) ? $itemid : "";
+ $linkid = (defined($linkid)) ? $linkid : "";
+ my $url = "http://$host/patroninfo~SO/$patron_sys_num/holds?name=$userid&code=$user_barcode&$linkid=on¤tsortorder=current_pickup&updateholdssome=YES&loc$itemid=";
+ my @out;
+ my $msg = {};
+ my $mech = WWW::Mechanize->new();
+ $response_body = $mech->post($url);
+
+ if($mech->success){
+ my $nha = $self->list_holds($userid,$user_barcode,$patron_sys_num);
+ $num_holds_after = @$nha;
+ #check to see whether the user was successfully authenticated
+ my $auth = $self->loggedIn($response_body);
+
+ if($auth ne "t"){
+ $msg->{"error"} = 1;
+ $msg->{"error_message"} = "The user $userid could not be authenticated";
+ push @out, $msg;
+ return \@out;
+ }
+
+ if($num_holds_before == $num_holds_after){
+ $msg->{"error"} = 1;
+ $msg->{"error_message"} = "The hold for $search_id either does not exist or could not be deleted";
+ push @out, $msg;
+ return \@out;
+ }elsif($num_holds_before > $num_holds_after){
+ $msg->{"error"} = 0;
+ $msg->{"success_message"} = "The hold for $search_id has been deleted";
+ push @out, $msg;
+ return \@out;
+ }
+
+ }else{
+ $msg->{"error"} = 1;
+ $msg->{"error_message"} = "An error occured: code $mech->status";
+ push @out, $msg;
+ return \@out;
+ }
+}
+
+
+
+
+sub parse_hold_list{
+ my $self = $_[0];
+ my $in = $_[1];
+ my $action = $_[2];
+ $in =~ s/\n//g;
+ my @holds;
+ my @holdEntries = split(/(<tr class="patFuncEntry".*?<\/tr>)/ ,$in);
+ my $user = {};
+ shift @holdEntries;
+ pop @holdEntries;
+ my @userSurname = ($in =~ /<h4>Patron Record for<\/h4><strong>(\w+),.*<\/strong><br \/>/);
+ my @userGivenName = ($in =~ /<h4>Patron Record for<\/h4><strong>(\w+),.*<\/strong><br \/>/);
+ my @expDate = ($in =~ /EXP DATE:(\d\d\-\d\d\-\d\d\d\d)<br \/>/);
+
+
+ $user->{surname} = $userSurname[0];
+ $user->{given_name} = $userGivenName[0];
+ $user->{exp_date} = $expDate[0];
+
+ if($action eq "lookup_user"){
+ push @holds,$user;
+ }elsif($action eq "list_holds"){
+
+ foreach(@holdEntries){
+ my $hold = {};
+ my @bibid = ($_ =~ /record=(.*)~/);
+ my @pickup = ($_ =~ /"patFuncPickup">(\w+)<\/td>/);
+ my @title = ($_ =~ /record=.*>\s(.*)<\/a>/);
+ my @status = ($_ =~ /"patFuncStatus">\s(\w+)\s<\/td>/);
+ my @itemid = ($_ =~ /id="cancel(\w+)x/);
+ my @linkid = ($_ =~ /id="(cancel.+x\d+)"\s+\/>/);
+
+ $hold->{'bibid'} = $bibid[0] if(defined $bibid[0]);
+ $hold->{'pickup'} = $pickup[0] if(defined $pickup[0]);
+ $hold->{'title'} = $title[0] if(defined $title[0]);
+ $hold->{title} =~ s/<.+>.*<\/.+>//g if(defined $hold->{title});
+ $hold->{'status'} = $status[0] if(defined $status[0]);
+ $hold->{'itemid'} = $itemid[0] if(defined $status[0]);
+ $hold->{'linkid'} = $linkid[0] if(defined $status[0]);
+ push @holds, $hold if (defined $hold->{'bibid'});
+ }
+
+ if($user->{surname}){
+ $user->{error} = 0;
+ $user->{error_meessage} = '';
+ }else{
+ $user->{error} = 1;
+ $user->{error_message} = 'supplied user could not be looked up';
+ }
+ }
+
+ return \@holds;
+}
+
+
+sub checkout{
+ my $self = $_[0];
+ my $user_barcode = $_[1];
+ my $ssh = $self->initialize;
+ my @temp_screen;
+ my @screenData;
+ my @lines;
+ my $circ;
+
+ eval{
+ $ssh->print("S");
+ $ssh->waitfor(-match => '/prominently\?/',
+ -errmode => "return") or die "Search failed. Could not retrieve user ", $user_barcode;
+ $ssh->print("P");
+ $ssh->waitfor(-match => '/RECORDS/',
+ -errmode => "return") or die "Search failed. Could not retrieve user ", $user_barcode;
+
+ $ssh->print("D");
+ $ssh->waitfor(-match => '/BARCODE/',
+ -errmode => "return") or die "Search failed. Could not retrieve user ", $user_barcode;
+
+ $ssh->print($user_barcode);
+
+ }or do {
+ $circ = {error => 1, error_message => $@};
+
+ };
+
+
+}
+
+
+
+sub checkin{
+ my $self = $_[0];
+ my $user_barcode = $_[1];
+ my $ssh = $self->initialize;
+ my @temp_screen;
+ my @screenData;
+ my @lines;
+ my $circ;
+
+ eval{
+ $ssh->print("S");
+
+
+ }or do{
+
+
+ }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+sub loggedIn{
+ my $self = $_[0];
+ my $response = $_[1];
+ if($response =~ /Please enter the following information/g){
+ return "f";
+ }
+
+ return "t";
+}
+
+
+
+
+
+1;
+
+
--- /dev/null
+package FulfILLment::LAIConnector::III::2011_1_3;
+use base FulfILLment::LAIConnector::III;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+use MARC::Record;
+use MARC::Batch;
+use MARC::File::XML (BinaryEncoding => 'utf8');
+use LWP::UserAgent;
+use HTTP::Request;
+use WWW::Mechanize;
+use XML::Simple;
+use Data::Dumper;
+
+my $ua = LWP::UserAgent->new;
+$ua->agent("FulfILLment/1.0");
+
+sub get_record_by_id_ssh {
+ my ($self, $record_id) = @_;
+
+ $self->ssh_connect or return;
+ $self->send_wait('S', 'Choose one') or return;
+ $self->send_wait('B', 'Choose one') or return;
+ $self->send_wait('R', 'Type Record') or return;
+ $self->send_wait("$record_id", 'Choose one') or return;
+ $self->send_wait('T', 'BIBLIOGRAPHIC Information') or return;
+ $self->send_wait('M');
+ my ($pre, $post) = $self->send_wait('T', 'Regular Display') or return;
+ $self->ssh_disconnect or return;
+
+ my @lines = split(/\[\d+;\d+(;\d+)?[Hm]/, $pre);
+
+ my @marc;
+ foreach (@lines){
+ next unless $_;
+ s/^(\s+)//g;
+ next unless /^\d{3}/;
+ s/\x1b//g;
+ s/\[0m//g;
+ s/\[0xF\]//g;
+ s/[[:cntrl:]]//g;
+ push @marc,$_;
+ }
+
+ my $rec = breaker2marc(\@marc);
+ $rec->insert_fields_ordered(
+ MARC::Field->new('907', ' ', ' ', a => $record_id)
+ ) unless $rec->subfield('907' => 'a');
+
+ my $x = $rec->as_xml_record;
+ $x =~ s/^<\?.+?\?>.//sm;
+ return {marc => $x};
+}
+
+sub get_items_by_record_guts {
+ my ($self, $record_id) = @_;
+ my @items;
+
+ $self->send_wait('S', 'prominently') or return;
+ $self->send_wait('B', 'SEARCHING RECORDS') or return;
+ $self->send_wait('R', 'RECORD') or return;
+ $self->send_wait($record_id, 'Choose one') or return;
+
+ my ($prematch, $match) = $self->send_wait(
+ 'S', 'To see a particular|BARCODE\s*[^\s]+') or return;
+
+ if ($match =~ /BARCODE/) {
+ # single-copy bib jumps right to copy details.
+
+
+ # we need the Copy Type from the match as well,
+ # so push it back into the main text
+ $prematch .= $match;
+ my $item = $self->parse_item_screen($prematch);
+
+ unless ($item and $item->{barcode}) {
+ $logger->warn(
+ "FF III unable to parse single-item screen for $record_id");
+ return;
+ }
+
+ push(@items, $item);
+
+ } else {
+ # multi-copy bib
+
+ my @item_indexes = ($prematch =~ /ITEM\s+(\d+)\s>/g);
+
+ for my $index (@item_indexes) {
+ my @response = $self->send_wait($index, 'Record SUMMARY') or last;
+ my $screen = $response[0];
+
+ $logger->debug("FF III item screen contains ".
+ length($screen)." characters");
+
+ my $item;
+ $logger->debug("FF III parsing item screen for entry $index");
+ $item = $self->parse_item_screen($screen);
+
+ unless ($item and $item->{barcode}) {
+ $logger->warn(
+ "FF III unable to parse item screen for $record_id");
+ last;
+ }
+
+ push(@items, $item);
+
+ # return to the summary screen
+ $self->send_wait('S', 'To see a particular') or last;
+ }
+ }
+
+ return @items;
+}
+
+sub get_user_guts {
+ my ($self, $user_barcode, $user_pass) = @_;
+
+ $self->send_wait('S', 'prominently') or return;
+ $self->send_wait('P', 'RECORDS') or return;
+ $self->send_wait('D', 'BARCODE') or return; # 2009 uses 'B'
+
+ my ($txt, $match) = $self->send_wait(
+ $user_barcode,
+ 'Record|BARCODE not found'
+ ) or return;
+
+ if ($match =~ /BARCODE not found/) {
+ $logger->info("FF III user '$user_barcode' not found");
+ return;
+ }
+
+ $txt =~ s/\[\d+;\d+(;\d+)?[Hm]//g;
+ $txt =~ s/^\d\d\d\s+.+//g;
+ $txt =~ s/\x1b//g;
+ $txt =~ s/\[0m//g;
+ $txt =~ s/\[0xF\]//g;
+ $txt =~ s/[[:cntrl:]]//g;
+
+ my $user = {
+ exp_date => qr/EXP DATE:\s(\d+\-\d+\-\d+)/,
+ user_id => qr/PIN\s+([A-Za-z0-9]+)INPUT/,
+ notice_pref => qr/NOTICE PREF:\s(.*)TOT/,
+ lang_pref => qr/LANG PREF:\s+(\w+)/,
+ mblock => qr/MBLOCK:\s+([A-Za-z0-9\-])\s+/,
+ patron_agency => qr/PAT AGENCY:\s+(\d+)\s+/,
+ overdue_items_count => qr/HLODUES:\s+(\d+)/,
+ notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/,
+ home_ou => qr/HOME LIBR:\s+(\w+)/,
+ total_renewals => qr/TOT RENWAL:\s+(\d+)/,
+ email_address => qr/EMAIL ADDR\s+([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,3})/,
+ claims_returned_count => qr/CL RTRND:\s(\d+)/,
+ loaned_items_count => qr/CUR CHKOUT:\s(\d+)/,
+ money_owed => qr/MONEY OWED:\s(\$\d+\.\d+)/,
+ overdue_penalty => qr/OD PENALTY:\s(\d+)/,
+ institution_code => qr/HOME LIBR:\s+(\w+)/,
+ block_until => qr/BLK UNTIL:\s(\d+\-\d+\-\d+)/,
+ full_name => qr/PATRN NAME\s+(.*)ADDRESS/,
+ street_address => qr/ADDRESS\s+(.*)SSN/,
+ ssn => qr/SSN #\s+(\d+)P BARCODE/,
+ photocopy_count => qr/PIUSE:\s+(\d+)/,
+ patron_code1 => qr/PCODE1:\s+(\d+)/,
+ patron_code2 => qr/PCODE2:\s+(\d+)/,
+ patron_code3 => qr/PCODE3:\s+(\d+)/,
+ patron_code4 => qr/PCODE4:\s+(\d+)/,
+ notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/,
+ ill_request => qr/ILL REQUES:\s+(\d+)/,
+ last_circ_date => qr/CIRCACTIV:\s(\d+\-\d+\-\d+)/,
+ patron_type => qr/P TYPE:\s(\d+)/,
+ census => qr/CENSUS:\s(\d+)\s+/,
+ total_checkouts => qr/TOT CHKOUT:\s(\d+)/,
+ checkouts => qr/CUR CHKOUT:\s+(\d+)/,
+ phone => qr/TELEPHONE\s+(\d\d\d\-\d\d\d\-\d\d\d\d)/,
+ input_by => qr/INPUT BY\s+([A-Za-z\/]+)/,
+ birth_date => qr/BIRTH DAT:(\d\d\-\d\d-\d\d)/,
+ cur_item_A => qr/CUR ITEMA:\s(\d+)/,
+ cur_item_B => qr/CUR ITEMB:\s(\d+)/,
+ cur_item_C => qr/CUR ITEMC:\s(\d+)/,
+ cur_item_D => qr/CUR ITEMD:\s(\d+)/,
+ error => 0,
+ };
+
+ for my $key (keys %$user) {
+ my ($val) = ($txt =~ $user->{$key});
+ $user->{$key} = $val;
+ }
+
+ my %prefs = (
+ z => 'email',
+ t => 'phone',
+ p => 'secondary phone'
+ );
+
+ $user->{notice_pref} = $prefs{$user->{notice_pref}};
+ $user->{barcode} = $user_barcode;
+
+ return $user;
+}
+
+# not needed if SIP is used.
+sub get_item_via_ssh {
+ my ($self, $item_barcode) = @_;
+
+ $self->ssh_connect or return;
+
+ $self->send_wait('S', 'prominently') or return;
+ $self->send_wait('I', 'SEARCHING RECORDS') or return;
+ $self->send_wait('D', 'BARCODE') or return; # NOTE: 2009 uses 'B'
+ my ($screen) = $self->send_wait($item_barcode, 'NEW Search') or return;
+
+ $self->ssh_disconnect;
+
+ if ($screen =~ /not found|PATRON Information/g) {
+ $logger->info("FF III unable to locate item $item_barcode");
+ return;
+ }
+
+ my $item = $self->parse_item_screen($screen, $item_barcode);
+
+ $logger->error("FF III error parsing item screen for $item_barcode")
+ unless $item;
+
+ return $item;
+}
+
+
+sub place_record_hold {
+ my ($self, $record_id, $user_barcode) = @_;
+
+ my $host = $self->{host};
+ my $password = $self->{'passwd.hold'} || $self->{passwd};
+
+ # $self->{port} usually == 80; need a separate SSL port config
+ my $port = 443;
+
+ # XXX do we need an org setting for default lender hold pickup location?
+ # TODO: web form pads the pickup lib values. add padding via sprintf to match.
+ my $pickup_lib = 'pla ';
+
+ chop($record_id); # strip check digit
+ $record_id = substr($record_id, 1); # strip the initial '.'
+
+ my $url = "https://$host/search~S1?/.$record_id/".
+ ".$record_id/1%2C1%2C1%2CB/request~$record_id";
+
+ $logger->info("FF III title hold URL: $url");
+
+ my $mech = WWW::Mechanize->new();
+ my $content;
+
+ eval {
+ $mech->post($url);
+ $mech->form_name('patform');
+ $mech->set_fields('pin', $password, 'code', $user_barcode);
+ $mech->select('locx00', $pickup_lib);
+ my $response = $mech->submit();
+ $content = $mech->content if $response;
+ };
+
+ $logger->info($content); # XXX
+
+ if ($@ or !$content){
+ my $msg = $@ || 'no content';
+ $logger->info("FF III error placing title hold on $record_id : $msg");
+ return {error => 1, error_message => $@};
+ }
+
+ my @title = ($content =~ /<p>Requesting <strong>(.+)<\/strong><br \/><p>/);
+ my @err_response_msg = ($content =~ /<font color="red" size="\+2">(.+)<\/font>/g);
+ my @success_response_msg = ($content =~ /Your request for .* was successful./g);
+ my @delivered_to = ($content =~ /Your request will be delivered to .* when it is available./);
+
+ if($content =~ /No Such Record/){
+ $logger->info("FF III no such record $record_id in title hold");
+ return {
+ error => 1,
+ error_message => # TODO: i18n?
+ "A hold could not be placed on $record_id, no such record"
+ }
+ }
+
+ if ($content =~ /Request denied/){
+ $logger->info("FF III title hold for $record_id denied");
+ $err_response_msg[0] =~ s/<strong>//;
+ $err_response_msg[0] =~ s/<\/strong>//;
+
+ return {
+ error => 1,
+ error_message => $err_response_msg[0],
+ title => $title[0]
+ };
+
+ } elsif ($content =~ /Your request for .* was successful./g){
+ $success_response_msg[0] =~ s/<strong>//;
+ $success_response_msg[0] =~ s/<\/strong>//;
+
+ return {
+ error => 0,
+ success_message => $success_response_msg[0],
+ title => $title[0]
+ };
+ }
+
+ @title = ($content =~
+ /class="bibInfoLabel">Title<\/td>\n<td class="bibInfoData">\n<strong>(.*)<\/strong>/g);
+
+ if ($title[0]){
+ return {
+ error => 0,
+ success_message => "Your request for $title[0] was successful.",
+ title => $title[0]
+ };
+ }
+}
+
+
+# ---------------------------------------------------------------------------
+# Everything below here needs modification and testing
+# ---------------------------------------------------------------------------
+
+
+sub get_item_fields{
+ my $self=$_[0];
+ my $itemData=$_[1];
+ return [] unless $itemData;
+ $logger->debug("FF III get_item_fields parsing " .length($itemData)." characters");
+ #print Dumper $itemData;
+ $itemData =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi;
+ $itemData =~ s/\e\[k//gi;
+ $itemData =~ s/[[:cntrl:]]//g;
+ $itemData =~ s/VOLUME/VOLUME:/g;
+ $itemData =~ s/CALL #/CALL #:/g;
+ $itemData =~ s/R > Browse Nearby EntriesI > Show similar ITEMSN >//g;
+ $itemData =~ s/R > RETURN to BrowsingZ > Show Items Nearby on ShelfF > FORWARD browseI > Show similar ITEMSN >//g;
+ $itemData =~ s/U > Show BIBLIOGRAPHIC RecordZ > Show Items Nearby on Shelf//g;
+ $itemData =~ s/\+ > ADDITIONAL options1-2,N,A,Z,I,U,T,E,\+\).*ITEM Information//g;
+ $itemData =~ s/I > Show similar ITEMSA > ANOTHER Search by RECORD #//g;
+ $itemData =~ s/U > Show similar BIBLIOGRAPHIC RecordS > Record SUMMARY//g;
+ $itemData =~ s/U > Show BIBLIOGRAPHIC RecordS > Record SUMMARY//g;
+ $itemData =~ s/T > Display MARC RecordE > Mark item for//g;
+ $itemData =~ s/U > Show BIBLIOGRAPHIC Record//g;
+ $itemData =~ s/NEW Search//g;
+ $itemData =~ s/N,A,S,Z,I,U,T,E\)//g;
+ $itemData =~ s/N >//g;
+
+ if(my @l = ($itemData =~ m/(\d+)BARCODE/g)){
+ $itemData =~ s/VOLUME:/VOLUME: $l[0]/g;
+ }
+
+ $itemData =~ s/(\d+)BARCODE/BARCODE:/g;
+ #$itemData =~ s/1BARCODE/BARCODE/g;
+ my @fields = grep {defined and not /^\s*$/} split /(\s{2,})|(- -)/, $itemData;
+ #my $i=0;
+ my @newfields = [];
+
+ foreach (@fields){
+ #$_.=':' if $_ eq 'VOLUME';
+ $_.=':' if $_ eq 'BARCODE';
+
+ if((/^[ \-]+$/ or $newfields[$#newfields] eq 'BARCODE:') and @newfields){
+ $newfields[$#newfields] .=$_;
+ }else{
+ unless(ref($_) eq 'ARRAY'){
+ push @newfields, $_;
+ }
+ }
+
+ #$i++;
+ }
+
+ return \@newfields;
+}
+
+
+
+
+
+
+
+sub get_item_call_and_bib_number {
+ $logger->debug("In get_item_call_and_bib_number");
+ my $self = $_[0];
+ my $item_data = $_[1];
+ $item_data =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi;
+ $item_data =~ s/\e\[k//gi;
+ $item_data =~ s/[[:cntrl:]]//g;
+
+ my @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s+(.*?)AUTHOR/);
+
+ if(not defined($c[0])){
+ @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s(.*?)TITLE/);
+ }
+
+ if (!$c[0]) {
+ # in multi-copy records, the call number is embedded in the
+ # copy data and has no predictable terminating string.
+ # capture it all and chop it off at the first occurence
+ # of 2 consecutive spaces
+
+ @c = ($item_data =~ /CALL #\s+(.*)/);
+ if ($c[0]) {
+ $c[0] =~ s/(.*?)\s{2}.*/$1/mg;
+ $c[0] =~ s/\s+$//;
+ } else {
+ $logger->warn("FF III unable to parse callnumber");
+ $c[0] = 'UNKNOWN';
+ }
+ }
+
+ #get Barcode
+ my @b = ($item_data =~ /BARCODE:.*(B.*)\s+BIBLIOGRAPHIC Information/);
+
+ if($b[0]){
+ $b[0] =~ s/\s//g;
+ }else{
+ @b = ($item_data =~ /BARCODE\s+([^\s]+)/);
+ }
+
+ #get title
+ my @f = split("TITLE",$item_data) ;
+ my @fingerprint = ($f[0] =~ /\s+(.*)\s{5,}/);
+ my @out;
+
+ if(not defined $fingerprint[0]){
+ @fingerprint = ($item_data =~ /TITLE\s+(.*)\s{5,}/);
+ }
+
+ @out = ($b[0],$c[0],$fingerprint[0]);
+ $logger->debug("Exiting get_item_call_and_bib_number");
+ return \@out;
+}
+
+
+
+
+
+sub parse_item_screen{
+ $logger->debug("In parse_item_screen");
+ my $self = $_[0];
+ my $screen = $_[1];
+ my $jhash={};
+ my @nvp;
+ my $label;
+ my $value;
+ my @screen_split;
+ my $rs = $screen;
+ my @record_number = ($rs =~ /(I\d.*)\s+ITEM Information/g);
+ my $e = "ESC[7;2H";
+ $e = ord($e);
+ $record_number[0] =~ s/\s+//g;
+ push @screen_split, split(/ITEM Information/,$screen);
+ #print Dumper @screen_split;
+ my $fields = $self->get_item_fields($screen_split[1]);
+
+
+ my $call_and_bib = $self->get_item_call_and_bib_number($screen_split[1]);
+
+ my $barcode = (defined($_[2])) ? $_[2] : $call_and_bib->[0];
+ my @fingerprint = ($screen =~ /TITLE\s+([^.!?\s][^.!?]*)\s+/);
+ $logger->debug("setting fields to their FulfILLment equivalents");
+
+ foreach(@$fields){
+ @nvp=split(":",$_);
+ $label=$nvp[0];
+ $value=$nvp[1];
+ if($label eq "DUE DATE"){
+ $label="due_date";
+ }elsif($label eq "BARCODE"){
+ $label="barcode";
+ }elsif($label eq "STATUS"){
+ $label="holdable";
+ }elsif($label eq "LOCATION"){
+ $label="location";
+ }elsif($label eq "PRICE"){
+ $label="price";
+ }elsif($label eq "COPY #"){
+ $label = "copy_number";
+ }elsif($label eq "I TYPE"){
+ $label = "item_type";
+ }elsif($label eq "IMESSAGE"){
+ $label = "note";
+ }elsif($label eq "AGENCY"){
+ $label = "agency";
+ }elsif($label eq "IN LOC"){
+ $label = "in_location";
+ }elsif($label eq "LOU"){
+ $label = "last_checkout_date";
+ }elsif($label eq "ICODE1"){
+ $label = "item_code1";
+ }elsif($label eq "ICODE2"){
+ $label = "item_code2";
+ }elsif($label eq "ICODE3"){
+ $label = "item_code3";
+ }elsif($label eq "IUSE1"){
+ $label = "item_use1";
+ }elsif($label eq "IUSE2"){
+ $label = "item_use2";
+ }elsif($label eq "IUSE3"){
+ $label = "item_use3";
+ }elsif($label eq "OPACMSG"){
+ $label = "opac_msg";
+ }elsif($label eq "OUT LOC"){
+ $label = "out_location";
+ }elsif($label eq "# RENEWALS"){
+ $label = "num_renewals";
+ }elsif($label eq "# OVERDUE"){
+ $label = "num_overdue";
+ }elsif($label eq "LOANRULE"){
+ $label = "loanrule";
+ }elsif($label eq "LYRCIRC"){
+ $label = "last_year_circ_stats";
+ }
+
+ if($label eq "holdable" and $value eq " -"){
+ $value="t";
+ }elsif($label eq "holdable" and $value eq "e"){
+ $value="t";
+ }elsif($label eq "holdable" ){
+ $value="f";
+ }
+
+ $jhash->{$label}=$value;
+ }
+
+ #print Dumper $call_and_bib;
+ if($jhash->{due_date}){
+ if($jhash->{'due_date'} eq "- -"){
+ $jhash->{'due_date'} = '';
+ }
+ }
+
+ # avoid leading/trailing spaces in cn
+ $call_and_bib->[1] =~ s/^\s+//g;
+ $call_and_bib->[1] =~ s/\s+$//g;
+
+ $jhash->{'call_number'} = $call_and_bib->[1];
+ #$jhash->{'bib_id'}=$call_and_bib->[0];
+ $jhash->{'item_id'} = $record_number[0];
+ $jhash->{'barcode'} = $barcode;
+ $jhash->{'error_message'} = '';
+ #$jhash->{'fingerprint'} = $fingerprint[0];
+ $jhash->{'fingerprint'} = '';
+ #print Dumper $jhash;
+ $logger->debug("Exiting parse_item_screen");
+ #print Dumper $jhash;
+ return $jhash;
+}
+
+
+sub get_item_by_call_number{
+ my $self = $_[0];
+ my $ssh = $self->initialize;
+ my $item_id = $_[1];
+ my $call_number_type = $_[2];
+ my @out;
+ my @entries; #If there are multiple entries for an item, the entries are stored here.
+ #print "preparing to search the catalog\n";
+ $ssh->print("S");
+ $ssh->waitfor(-match => '/prominently\?/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ #print "ok\n";
+ #print "selecting option to search for items\n";
+ $ssh->print("I");
+ $ssh->waitfor(-match => '/SEARCHING RECORDS/',
+ -errmode => "return")or die "search failed;", $ssh->lastline;
+ #print "ok\n";
+ #select attribute to search by, i.e title, barcode etc...
+ $ssh->print("C");
+ $ssh->waitfor(-match => '/CALL NUMBER SEARCHES/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+
+ if(lc($call_number_type) eq "dewey"){
+
+ $ssh->print("D");
+ $ssh->waitfor(-match => '/DEWEY CALL NO :/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+
+ }elsif(lc($call_number_type) eq "lc"){
+ $ssh->print("C");
+ $ssh->waitfor(-match => '/LC CALL NO :/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+
+ }elsif(lc($call_number_type) eq "local"){
+ $ssh->print("L");
+ $ssh->waitfor(-match => '/LOCAL CALL NO :/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ }
+
+ $ssh->print($item_id);
+ push @out,$ssh->waitfor(-match => '/NEW Search/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ #print "Done.\n";
+
+ if(my @num_entries = ($out[0] =~ /(\d+)\sentries found/)){
+ #my $i = 0;
+ #$i++;
+ $ssh->print(1);
+ push @out,$ssh->waitfor(-match => '/NEW Search/',
+ -errmode => "return") or die "search failed;", $ssh->lastline;
+ #print "Done.\n";
+ }
+
+ $self->ssh_disconnect;
+ my @items;
+ push @items,$self->parse_item_screen($out[2]);
+ return \@items;
+}
+
+
+#Under construction
+
+sub get_item_by_bib_number_z3950{
+ my $self = $_[0];
+ my $bibID = $_[1];
+ my $marc = $self->get_bib_records_by_record_number_z3950($bibID);
+ my $batch = MARC::Batch->new('XML', $marc );
+ while (my $m = $batch->next ){
+ print $m->subfield(650,"a"),"\n";
+
+ }
+ #my $record = $batch->next();
+ #print Dumper $record;
+
+}
+
+
+
+
+
+# Method: get_bib_records_by_record_num
+# Params:
+# idList => list of record numbers
+
+#BIBLIOGRAPHIC RECORDS
+#Notes: Section contains methods to retrieve and parse bibliographic records
+#==================================================================================
+
+
+sub get_bib_records_by_record_number{
+ my $self = $_[0];
+ my $ssh = $self->initialize;
+ my $id = $_[1];
+ my $count=0;
+ my @out;
+ my @screen;
+ my @marc;
+ my @bib = ();
+ my $jhash={};
+
+ eval{
+ if(ref($ssh) eq "ARRAY"){
+ return $ssh;
+ }
+
+ #select SEARCH the catalog
+ #print "getBibRecords\n";
+ #print "preparing to search the catalog\n";
+ $ssh->print("S");
+
+ $ssh->waitfor(-match => '/prominently\?/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+
+ #print "ok\n";
+ #print "selecting option to search for bibliographic records\n";
+ #select item record option
+ $ssh->print("B");
+ $ssh->waitfor(-match => '/SEARCHING RECORDS/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+
+ #print "ok\n";
+ #print "selecting option to search by record number\n";
+ #select attribute to search by, i.e title, barcode etc...
+ $ssh->print("R"); #search by record number
+ #print "ok\n";
+ #print "searching for id=$id\n";
+ #search for id
+ $ssh->print($id);
+ my $first = 1;
+ #print "searching more of the document\n";
+ my @bid;
+
+ my $get_more = sub {
+ my @temp_screen = ();
+ my @lines;
+ my $last = 0;
+ my $get_more = shift;
+
+ if($first == 1){
+ $ssh->print("T");
+ $ssh->waitfor(-match => '/BIBLIOGRAPHIC Information/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+ #-errmode => "return") or die "search failed;", $ssh->lastline;
+ $ssh->print("M");
+ #$first = 0;
+ }
+
+ $ssh->print("M");
+ $ssh->print("T");
+ #delete the following two lines
+
+ push @temp_screen ,$ssh->waitfor(-match => '/Regular Display/',
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+ #-errmode => "return") or die "search failed;", $ssh->lastline;
+ #print Dumper $temp_screen[0];
+
+ @bid = ($temp_screen[0] =~ /([Bb].*)\s+BIBLIOGRAPHIC Information/);
+ if($temp_screen[0] =~ /COUNTRY:/g and $first == 0 ){
+ #print Dumper $temp_screen[0];
+ #print "Reached end of record... I think\n";
+ return 1;
+ }
+ push @lines,split(/\[\d+;\d+(;\d+)?[Hm]/,$temp_screen[0]);
+
+ foreach (@lines){
+ if($_){
+ $_ =~ s/^(\s+)//g;
+ if($_ =~/^\d\d\d\s+.+/g){
+ $_ =~ s/\x1b//g;
+ $_ =~ s/\[0m//g;
+ $_ =~ s/\[0xF\]//g;
+ $_ =~ s/[[:cntrl:]]//g;
+ push @marc,$_;
+ #print Dumper $_;
+ }
+ }
+ }
+ #print Dumper @lines;
+
+ $first = 0;
+ #print Dumper @marc;
+ $ssh->print("M");
+ $ssh->print("T");
+ @temp_screen = ();
+ $get_more->($get_more);
+ };
+
+
+ $get_more->($get_more);
+
+ my @id = ($bid[0]) ? ($bid[0] =~ /([Bb][0-9]+)\s+/) : "" ;
+ #print Dumper @marc;
+
+ #print "bid = ".$id[0]."\n";
+ #print "ok\n";
+ #push @out, $self->parse_bib_records( $self->grab_bib_screen($ssh,$id), $id );
+ #print "logging out\n";
+ $ssh->print("N");
+ $ssh->print("Q");
+ $ssh->print("Q");
+ $ssh->print("X");
+ $ssh->waitfor(-match => '/closed/',
+ #-errmode => "return")or die "log out failed;", $ssh->lastline;
+ -errmode => "return") or die "Search failed. Could not retrieve ", $id;
+
+ #print "logged out\n";
+ my $rec = breaker2marc(\@marc);
+ $rec->insert_fields_ordered(
+ MARC::Field->new(
+ '907',
+ ' ',
+ ' ',
+ 'a' => $id[0]
+ )
+ ) if (!$rec->subfield( '907' => 'a' ));
+
+ my $x = $rec->as_xml_record;
+ $x =~ s/^<\?.+?\?>.//sm;
+ #my $jhash={};
+ $jhash->{'marc'}=$x;
+ #$bid =~ s/\[/$bid/g;
+ $jhash->{'id'}=$id[0];
+ $jhash->{'format'}="marcxml";
+ $jhash->{error} = 0;
+ $jhash->{error_message} = '';
+ #print "xml = $x\n";
+ #my @bib = ();
+ push @bib,$jhash;
+ #warn Dumper \@bib;
+ return \@bib;
+
+ 1;
+ }or do {
+ $jhash->{error} = 1;
+ $jhash->{error_message} = $@;
+ push @bib,$jhash;
+ return \@bib;
+ }
+
+}
+
+
+
+
+sub get_range_of_records{
+ my $self = $_[0];
+ my $list = $_[1];
+ my $dir = `pwd`;
+ my $file = "/openils/lib/perl5/FulfILLment/WWW/LAIConnector/conf/III_2009B_1_2/marc/marc_dump.mrc";
+ open FILE, ">$file" or die &!;
+
+ foreach my $r (@$list){
+ my $record = $self->get_bib_records_by_record_number($r);
+
+ print FILE $record->[0]->{marc};
+
+ }
+
+ close FILE;
+ my @out;
+ my $jhash;
+ push @out,$jhash;
+ return \@out;
+
+}
+
+
+
+
+
+
+
+sub breaker2marc {
+ my $lines = shift;
+ my $delim = quotemeta(shift() || '|');
+ my $rec = new MARC::Record;
+ for my $line (@$lines) {
+
+ chomp($line);
+
+ if ($line =~ /^=?(\d{3})\s{2}(.)(.)\s(.+)$/) {
+
+ my ($tag, $i1, $i2, $rest) = ($1, $2, $3, $4);
+ if ($tag < 10) {
+ $rec->insert_fields_ordered( MARC::Field->new( $tag => $rest ) );
+
+ } else {
+
+ my @subfield_data = split $delim, $rest;
+ if ($subfield_data[0]) {
+ $subfield_data[0] = 'a' . $subfield_data[0];
+ } else {
+ shift @subfield_data;
+ }
+
+ my @subfields;
+ for my $sfd (@subfield_data) {
+ if ($sfd =~ /^(.)(.+)$/) {
+ push @subfields, $1, $2;
+ }
+ }
+
+ $rec->insert_fields_ordered(
+ MARC::Field->new(
+ $tag,
+ $i1,
+ $i2,
+ @subfields
+ )
+ ) if @subfields;
+ }
+ }
+ }
+
+ return $rec;
+}
+
+
+#END BIBLIOGRAPHIC RECORDS
+#=====================================================================================
+
+
+
+
+
+#Places hold on a III server through the web interface
+
+
+
+sub parse_hold_response{
+ my $self = $_[0];
+ my $txt = $_[1];
+ my @resp = split("END SEARCH WIDGET -->",$txt);
+ my $success = ($resp[1] =~ /denied/) ? "false" : "true";
+ if(!defined($resp[1])){$success = "false"}
+ my $msg = $resp[1];
+ $msg =~ s/<p>//g;
+ $msg =~ s/<\/p>//g;
+ $msg =~ s/<br \/>//g;
+ $msg =~ s/ //g;
+ $msg =~ s/<!-End the bottom logo table-->//g;
+ $msg =~ s/<\/body>//g;
+ $msg =~ s/<\/html>//g;
+ $msg =~ s/<\/strong\>//g;
+ $msg =~ s/<strong>//g;
+ $msg =~ s/\./\. /g;
+ $msg =~ s/\n//g;
+ $msg =~ s/<font color="red"\s+size="\+2">/\. /g;
+ $msg =~ s/<\/font>//g;
+ my $data = {};
+ $data->{'success'} = $success;
+ $data->{'message'} = $msg;
+ my $out = [];
+ $out->[0] = $data;
+ return $out;
+}
+
+
+
+sub list_holds{
+ $logger->debug("In list_holds");
+ my $self = shift;
+ my $host = $self->{host};
+ my $port = $self->{port};
+ my $user_barcode = shift;
+ my $passwd = shift;
+ my $patron_sys_num = shift;
+ my $action = shift || "list_holds";
+ my $response;
+ my $content;
+ $logger->debug("params are host=$host\n port=$port\n user_barcode=$user_barcode\n passwd=$passwd\n patron_sys_number=$patron_sys_num");
+ my $url = "https://$host:$port/patroninfo~S0/$patron_sys_num/holds/?name=$user_barcode&code=$passwd";
+ my $out;
+ my $mech = WWW::Mechanize->new();
+
+ eval{
+ $mech->post($url);
+ $mech->form_name('patform');
+ $mech->set_fields('pin',$passwd,
+ 'code',$user_barcode
+
+ );
+
+ $response = $mech->submit();
+ $content = $mech->content;
+ };
+
+ if($@){
+ my $hold;
+ my @out;
+ $hold->{error} = 1;
+ $hold->{error_message} = "There was an error looking up holds for the user $user_barcode : $@";
+ push @out,$hold;
+ $logger->debug("Exiting list_holds");
+ return \@out;
+ }
+
+
+ $logger->debug("Exiting list_holds");
+ return $self->parse_hold_list($content,"list_holds");
+
+}
+
+
+
+sub list_holds_by_bib{
+ my $self = shift;
+ my $host = shift;
+ my $port = shift;
+ my $login = shift;
+ my $user_barcode = shift;
+ my $bibID = shift;
+ my $patron_sys_num = shift;
+ my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num);
+ my @out;
+
+ foreach(@$holds){
+ if($_->{'bibid'} eq $bibID){
+ push @out,$_;
+ }
+ }
+
+ return \@out;
+}
+
+
+
+sub list_holds_by_item{
+ my $self = shift;
+ my $host = shift;
+ my $port = shift;
+ my $login = shift;
+ my $user_barcode = shift;
+ my $itemID = shift;
+ my $patron_sys_num = shift;
+ my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num);
+ my @out;
+
+ foreach(@$holds){
+ if($_->{'itemid'} eq $itemID){
+ push @out,$_;
+ }
+ }
+
+ return \@out;
+}
+
+
+sub delete_hold{
+ $logger->debug("In delete_hold");
+ my $self = shift;
+ my $host = $self->{host};
+ my $port = $self->{port};
+ my $userid = $self->{login};
+ $userid =~ s/\s/%20/g;
+ my $search_id = shift;
+ my $id_type = shift;
+ my $patron_sys_num = shift;
+ my $user_barcode = shift;
+
+ $logger->debug("fields are host=$host\n port=$port \n userid=$userid \n search_id=$search_id \n id_type=$id_type \n patron_sys_num=$patron_sys_num \n user_barcode=$user_barcode\n");
+
+ my $response_body;
+ my $holds = $self->list_holds($userid,$user_barcode,$patron_sys_num);
+ my $itemid;
+ my $linkid;
+ my $num_holds_before = @$holds;
+ my $num_holds_after;
+
+ if($id_type eq "bib"){
+
+ if(length($search_id) > 8){
+ chop($search_id);
+ }
+
+ foreach(@$holds){
+ #print "bibid = $_->{bibid} search_id = $search_id\n";
+ if($_->{'bibid'} eq $search_id){
+ #print "item id = ".$_->{'itemid'}."\n";
+ $itemid = $_->{'itemid'};
+ $linkid = $_->{'linkid'};
+ }
+ }
+ }elsif($id_type eq "item"){
+ foreach(@$holds){
+ if($_->{'itemid'} eq $search_id){
+ #print "item id = ".$_->{'itemid'}."\n";
+ $itemid = $_->{'itemid'};
+ $linkid = $_->{'linkid'};
+ }
+ }
+ }
+
+ $itemid = (defined($itemid)) ? $itemid : "";
+ $linkid = (defined($linkid)) ? $linkid : "";
+ my $url = "https://$host:$port/patroninfo~SO/$patron_sys_num/holds?name=$userid&code=$user_barcode&$linkid=on¤tsortorder=current_pickup&updateholdssome=YES&loc$itemid=";
+ my @out;
+ my $msg = {};
+ my $mech = WWW::Mechanize->new();
+ $response_body = $mech->post($url);
+
+ if($mech->success){
+ my $nha = $self->list_holds($userid,$user_barcode,$patron_sys_num);
+ $num_holds_after = @$nha;
+ #check to see whether the user was successfully authenticated
+ my $auth = $self->loggedIn($response_body);
+
+ if($auth ne "t"){
+ $msg->{"error"} = 1;
+ $msg->{"error_message"} = "The user $userid could not be authenticated";
+ push @out, $msg;
+ return \@out;
+ }
+
+ if($num_holds_before == $num_holds_after){
+ $msg->{"error"} = 1;
+ $msg->{"error_message"} = "The hold for $search_id either does not exist or could not be deleted";
+ push @out, $msg;
+ return \@out;
+ }elsif($num_holds_before > $num_holds_after){
+ $msg->{"error"} = 0;
+ $msg->{"success_message"} = "The hold for $search_id has been deleted";
+ push @out, $msg;
+ return \@out;
+ }
+
+ }else{
+ $msg->{"error"} = 1;
+ $msg->{"error_message"} = "An error occured: code $mech->status";
+ push @out, $msg;
+ return \@out;
+ }
+}
+
+
+
+
+sub parse_hold_list{
+ my $self = shift;
+ my $in = shift;
+ my $action = shift;
+ $in =~ s/\n//g;
+ my @holds;
+ my @holdEntries = split(/(<tr class="patFuncEntry".*?<\/tr>)/ ,$in);
+ my $user = {};
+ shift @holdEntries;
+ pop @holdEntries;
+ my @userSurname = ($in =~ /<h4>Patron Record for<\/h4><strong>(\w+),.*<\/strong><br \/>/);
+ my @userGivenName = ($in =~ /<h4>Patron Record for<\/h4><strong>(\w+),.*<\/strong><br \/>/);
+ my @expDate = ($in =~ /EXP DATE:(\d\d\-\d\d\-\d\d\d\d)<br \/>/);
+
+
+ $user->{surname} = $userSurname[0];
+ $user->{given_name} = $userGivenName[0];
+ $user->{exp_date} = $expDate[0];
+
+ if($action eq "lookup_user"){
+ push @holds,$user;
+ }elsif($action eq "list_holds"){
+
+ foreach(@holdEntries){
+ my $hold = {};
+ my @bibid = ($_ =~ /record=(.*)~/);
+ my @pickup = ($_ =~ /"patFuncPickup">(\w+)<\/td>/);
+ my @title = ($_ =~ /record=.*>\s(.*)<\/a>/);
+ my @status = ($_ =~ /"patFuncStatus">\s(\w+)\s<\/td>/);
+ my @itemid = ($_ =~ /id="cancel(\w+)x/);
+ my @linkid = ($_ =~ /id="(cancel.+x\d+)"\s+\/>/);
+
+ $hold->{'bibid'} = $bibid[0] if(defined $bibid[0]);
+ $hold->{'pickup'} = $pickup[0] if(defined $pickup[0]);
+ $hold->{'title'} = $title[0] if(defined $title[0]);
+ $hold->{title} =~ s/<.+>.*<\/.+>//g if(defined $hold->{title});
+ $hold->{'status'} = $status[0] if(defined $status[0]);
+ $hold->{'itemid'} = $itemid[0] if(defined $status[0]);
+ $hold->{'linkid'} = $linkid[0] if(defined $status[0]);
+ push @holds, $hold if (defined $hold->{'bibid'});
+ }
+
+ if($user->{surname}){
+ $user->{error} = 0;
+ $user->{error_meessage} = '';
+ }else{
+ $user->{error} = 1;
+ $user->{error_message} = 'supplied user could not be looked up';
+ }
+ }
+
+ return \@holds;
+}
+
+
+sub loggedIn{
+ my $self = $_[0];
+ my $response = $_[1];
+ if($response =~ /Please enter the following information/g){
+ return "f";
+ }
+
+ return "t";
+}
+
+
+
+
+
+1;
--- /dev/null
+package FulfILLment::LAIConnector::Koha;
+use base FulfILLment::LAIConnector;
+use strict; use warnings;
+use XML::LibXML;
+use LWP::UserAgent;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+# TODO: for holds
+use DateTime;
+my $U = 'OpenILS::Application::AppUtils';
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+
+# special thanks to Koha => misc/migration_tools/koha-svc.pl
+sub svc_login {
+ my $self = shift;
+ return $self->{svc_agent} if $self->{svc_agent};
+
+ my $username = $self->{extra}->{'svc.user'} || $self->{user};
+ my $password = $self->{extra}->{'svc.password'} || $self->{passwd};
+
+ # TODO: https (setting)
+ my $url = sprintf(
+ "http://%s/cgi-bin/koha/svc",
+ $self->{extra}->{'svc.host'} || $self->{host}
+ );
+
+ my $ua = LWP::UserAgent->new();
+ $ua->cookie_jar({});
+
+ $logger->info("FF Koha logging in at $url/authentication");
+
+ my $resp = $ua->post(
+ "$url/authentication",
+ {userid => $username, password => $password}
+ );
+
+ if (!$resp->is_success) {
+ $logger->error("FF Koha svc login failed " . $resp->status_line);
+ return;
+ }
+
+ $self->{svc_url} = $url;
+ $self->{svc_agent} = $ua;
+
+ return 1;
+}
+
+sub escape_xml {
+ my $str = shift;
+ $str =~ s/&/&/sog;
+ $str =~ s/</</sog;
+ $str =~ s/>/>/sog;
+ return $str;
+}
+
+# sends a MARCXML stub record w/ a single embedded copy
+sub create_borrower_copy {
+ my ($self, $ref_copy, $circ_lib_code) = @_;
+ return unless $self->svc_login;
+
+ my $marc = <<XML;
+<record
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
+ xmlns="http://www.loc.gov/MARC21/slim">
+ <datafield tag="100" ind1="1" ind2=" ">
+ <subfield code="a">AUTHOR</subfield>
+ </datafield>
+ <datafield tag="245" ind1="1" ind2="0">
+ <subfield code="a">TITLE</subfield>
+ </datafield>
+ <datafield tag="952" ind1=" " ind2=" ">
+ <subfield code="p">BARCODE</subfield>
+ <subfield code="o">CALLNUMBER</subfield>
+ <subfield code="a">LOCATION</subfield>
+ </datafield>
+</record>
+XML
+
+ my $title = escape_xml($ref_copy->call_number->record->simple_record->title);
+ my $author = escape_xml($ref_copy->call_number->record->simple_record->author);
+ my $barcode = escape_xml($ref_copy->barcode); # TODO: setting for leading org id
+ my $callnumber = escape_xml($ref_copy->call_number->label);
+
+ $marc =~ s/TITLE/$title/g;
+ $marc =~ s/AUTHOR/$author/g;
+ $marc =~ s/BARCODE/$barcode/g;
+ $marc =~ s/CALLNUMBER/$callnumber/g;
+ $marc =~ s/LOCATION/$circ_lib_code/g;
+
+ $logger->info("FF Koha borrower rec/copy: $marc");
+
+ my $resp = $self->{svc_agent}->post(
+ $self->{svc_url} . "/new_bib?items=1",
+ {POSTDATA => $marc}
+ # note: passing Content => $marc fails
+ );
+
+ if (!$resp->is_success) {
+ $logger->error("FF Koha create_borrower_copy " . $resp->status_line);
+ return;
+ }
+
+ $logger->info($resp->decoded_content);
+
+ my $resp_xml = XML::LibXML->new->parse_string($resp->decoded_content);
+ $logger->info($resp_xml);
+ $logger->info($resp_xml->toString);
+
+ my $error = $resp_xml->getElementsByTagName('error')->string_value;
+ my $marcxml = $resp_xml->getElementsByTagName('record')->shift;
+
+ return {
+ error => $error,
+ barcode => $error ? '' : $barcode, # return bc on success
+ title => $title,
+ author => $author,
+ location => $circ_lib_code,
+ call_number => $callnumber,
+ remote_id => $resp_xml->getElementsByTagName('biblionumber')->string_value,
+ status => $resp_xml->getElementsByTagName('status')->string_value,
+ marcxml => $marcxml ? $marcxml->toString : ''
+ };
+}
+
+sub get_record_by_id {
+ my ($self, $record_id, $with_items) = @_;
+ return unless $self->svc_login;
+
+ $with_items = '?items=1' if $with_items;
+
+ my $url = $self->{svc_url}."/bib/$record_id$with_items";
+ my $resp = $self->{svc_agent}->get($url);
+
+ if (!$resp->is_success) {
+ $logger->error("FF Koha record_by_id failed " . $resp->status_line);
+ return;
+ }
+
+ return $resp->decoded_content
+}
+
+# NOTE: unused, but kept for reference
+sub get_record_by_id_z3950 {
+ my ($self, $record_id) = @_;
+
+ my $attr = $self->{args}{extra}{'z3950.search_attr'};
+
+ # Koha returns holdings by default, which is useful
+ # for get_items_by_record (below).
+
+ my $xml = $self->z39_client->get_record_by_id(
+ $record_id, $attr, undef, 'xml', 1) or return;
+
+ return {marc => $xml, id => $record_id};
+}
+
+sub get_items_by_record {
+ my ($self, $record_id) = @_;
+
+ my $rec = $self->get_record_by_id($record_id, 1) or return [];
+
+ # when calling get_record_by_id_z3950
+ # my $doc = XML::LibXML->new->parse_string($rec->{marc}) or return [];
+
+ my $doc = XML::LibXML->new->parse_string($rec) or return [];
+
+ # marc code to copy field map
+ my %map = (
+ o => 'call_number',
+ p => 'barcode',
+ a => 'location_code'
+ );
+
+ my @items;
+ for my $node ($doc->findnodes('//*[@tag="952"]')) {
+
+ my $item = {bib_id => $record_id};
+
+ for my $key (keys %map) {
+ my $val = $node->findnodes("./*[\@code='$key']")->string_value;
+ next unless $val;
+ $val =~ s/^\s+|\s+$//g; # cleanup
+ $item->{$map{$key}} = $val;
+ }
+
+ push (@items, $item);
+ }
+
+ return \@items;
+}
+
+# NOTE: initial code review suggests Koha only supports bib-level
+# holds via SIP, but they are created via copy barcode (not bib id).
+# Needs more research
+
+sub place_borrower_hold {
+ my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_;
+
+ # NOTE: i believe koha ignores (but requires) the hold type
+ my $hold = $self->place_hold_via_sip(
+ undef, $item_barcode, $user_barcode, $pickup_lib, 3)
+ or return;
+
+ $hold->{hold_type} = 'T';
+ return $hold;
+}
+
+sub place_lender_hold {
+ my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_;
+
+ # NOTE: i believe koha ignores (but requires) the hold type
+ my $hold = $self->place_hold_via_sip(
+ undef, $item_barcode, $user_barcode, $pickup_lib, 2)
+ or return;
+
+ $hold->{hold_type} = 'T';
+ return $hold;
+}
+
+sub delete_borrower_hold {
+ my ($self, $item_barcode, $user_barcode) = @_;
+
+ # TODO: find the hold in the FF db to determine the pickup_lib
+ # for now, assume pickup lib matches the user's home lib
+ my $user = $self->flesh_user($user_barcode);
+ my $pickup_lib = $user->home_ou->shortname if $user;
+
+ my $resp = $self->sip_client->delete_hold(
+ $user_barcode, undef, undef,
+ $pickup_lib, 3, $item_barcode)
+ or return;
+
+ return unless $resp;
+ return $self->translate_sip_hold($resp);
+}
+
+sub delete_lender_hold {
+ my ($self, $item_barcode, $user_barcode) = @_;
+
+ my $user = $self->flesh_user($user_barcode);
+ my $pickup_lib = $user->home_ou->shortname if $user;
+
+ my $resp = $self->sip_client->delete_hold(
+ $user_barcode, undef, undef,
+ $pickup_lib, 2, $item_barcode)
+ or return;
+
+ return unless $resp;
+ return $self->translate_sip_hold($resp);
+}
+
+
+1;
--- /dev/null
+package FulfILLment::LAIConnector::Polaris;
+use base FulfILLment::LAIConnector;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+1;
--- /dev/null
+package FulfILLment::LAIConnector::Symphony;
+use base FulfILLment::LAIConnector;
+use strict; use warnings;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+1;
--- /dev/null
+package FulfILLment::Util::NCIP;
+use strict;
+use warnings;
+use IO::Socket;
+use Data::Dumper;
+use XML::LibXML;
+use LWP::UserAgent;
+use HTTP::Request;
+use Template;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+my $ua = LWP::UserAgent->new;
+$ua->agent("FulfILLment/1.0");
+$ua->default_header('Content-Type' => 'application/xml; charset="utf-8"');
+
+sub new {
+ my ($class, %args) = @_;
+ return bless(\%args, $class);
+}
+
+sub request {
+ my ($self, $type, %params) = @_;
+
+ $logger->info("FF NCIP sending message $type");
+
+ my $xml = $self->compile_xml($type, %params);
+ return unless $xml;
+
+ my $proto = $self->{protocol} || '';
+ my $resp_xml;
+ if ($proto =~ /http/i) {
+ $resp_xml = $self->send_via_http($xml);
+ } elsif ($proto =~ /tcp/i) {
+ $resp_xml = $self->send_via_tcp($xml);
+ } else {
+ $logger->error("FF Invalid NCIP protocol '$proto'");
+ return;
+ }
+
+ my $doc = $self->parse_xml($resp_xml) or return;
+ return ($doc, $self->extract_ncip_errors($doc));
+}
+
+# parses/verifies XML and returns an XML doc
+sub parse_xml {
+ my ($self, $xml) = @_;
+
+ my $parser = XML::LibXML->new;
+ $parser->keep_blanks(0);
+
+ my $doc;
+ eval { $doc = $parser->parse_string($xml) };
+
+ if (!$doc) {
+ $logger->error("FF invalid XML for NCIP message $@ : $xml");
+ return;
+ }
+
+ my $log_xml = $doc->toString;
+ $log_xml =~ s/\n/ /g;
+ $logger->debug("FF NCIP XML : $log_xml");
+
+ return $doc;
+}
+
+
+# extract all //Problem/* text values
+sub extract_ncip_errors {
+ my ($self, $doc) = @_;
+ my @errors;
+ my $prob_xpath = '//Problem//Value';
+ push(@errors, $_->textContent) for $doc->findnodes($prob_xpath);
+ return @errors;
+}
+
+# sends the xml template for the requested message type
+# through TT to generate the final XML message.
+sub compile_xml {
+ my ($self, $type, %params) = @_;
+
+ # insert the agency info into the template environment
+ $params{ff_agency_name} = $self->{ff_agency_name};
+ $params{ff_agency_uri} = $self->{ff_agency_uri};
+ $params{ils_agency_name} = $self->{ils_agency_name};
+ $params{ils_agency_uri} = $self->{ils_agency_uri};
+
+ my $template = "$type.tt2";
+
+ my $tt = Template->new({
+ ENCODING => 'utf-8',
+ INCLUDE_PATH => $self->{template_paths}
+ });
+
+ my $xml = '';
+ if ($tt->process($template, \%params, \$xml)) {
+
+ my $doc = $self->parse_xml($xml) or return;
+ return $doc->toString;
+
+ } else {
+ $logger->error("FF NCIP XML template error : ".$tt->error);
+ return;
+ }
+}
+
+sub send_via_http {
+ my ($self, $xml) = @_;
+
+ my $url = sprintf(
+ '%s://%s:%s%s',
+ $self->{protocol},
+ $self->{host},
+ $self->{port},
+ $self->{path}
+ );
+
+ $logger->debug("FF NCIP url = $url");
+
+ my $r = HTTP::Request->new('POST', $url);
+ $r->content($xml);
+ my $resp = $ua->request($r);
+
+ return $resp->decoded_content if $resp->is_success;
+
+ $logger->error("FF NCIP HTTP(S) Error : " . $resp->status_line);
+ return;
+}
+
+sub send_via_tcp {
+ my ($self, $xml) = @_;
+
+ my $sock = IO::Socket::INET->new(
+ PeerAddr => $self->{host},
+ PeerPort => $self->{port},
+ Proto => 'tcp',
+ Timeout => 10,
+ );
+
+ if (!$sock) {
+ $logger->error("FF NCIP TCP connection error $!");
+ return;
+ }
+
+ $sock->send($xml);
+ my $resp_xml = <$sock>;
+
+ $sock->close or $logger->warn("FF error closing socket $!");
+ return $resp_xml;
+}
+
+1;
--- /dev/null
+#
+#===============================================================================
+#
+# FILE: SIP2client.pm
+#
+# DESCRIPTION:
+#
+# FILES: ---
+# BUGS: ---
+# NOTES: ---
+# AUTHOR: Michael Davadrian Smith (), msmith@esilibrary.com
+# COMPANY: Equinox Software
+# VERSION: 1.0
+# CREATED: 05/14/2012 03:27:10 PM
+# REVISION: ---
+#===============================================================================
+
+package FulfILLment::Util::SIP2Client;
+
+use strict;
+use warnings;
+use Data::Dumper;
+use IO::Socket::INET;
+use Encode;
+use Sip qw(:all);
+use Sip::Checksum qw(checksum);
+use Sip::Constants qw(:all);
+use Sip::MsgType;
+use OpenSRF::Utils::Logger qw/:logger/;
+
+$Sip::protocol_version = 2;
+$Sip::error_detection = 1;
+$/ = "\r";
+
+sub new {
+ my ($type) = $_[0];
+ my $self = {};
+ $self->{host} = $_[1];
+ $self->{login_username} = $_[2];
+ $self->{login_passwd} = $_[3];
+ $self->{port} = $_[4];
+ $self->{protocol} = $_[5];
+ $self->{location} = $_[6];
+ bless($self,$type);
+}
+
+sub socket{
+ my $self = shift;
+
+ if ($self->{socket}) {
+ # logout may cause disconnect
+ return $self->{socket} if $self->{socket}->connected;
+
+ # probably excessive, but can't hurt to clean up
+ $self->{socket}->shutdown(2);
+ $self->{socket}->close;
+ }
+
+ $logger->debug("FF creating SIP socket to ".$self->{host});
+
+ $self->{socket} = IO::Socket::INET->new(
+ PeerAddr => $self->{host},
+ Proto => $self->{protocol},
+ PeerPort => $self->{port}
+ ) or die "Cannot connect to host $self->{host} $@";
+
+ return $self->{socket};
+}
+
+sub sendMsg{
+ my $self = $_[0];
+ my $msg = $_[1];
+ my $seqno = $_[2] || 1;
+ my $resp;
+ my %fields;
+ my $sock = $self->socket;
+ $sock->autoflush(1);
+ $logger->info("FF SIP request => $msg");
+ write_msg({seqno => $seqno},$msg,$sock);
+ $resp = <$sock>;
+ # SIP Msg hates leading spaces especially
+ $resp =~ s/^\s+|\s+$//mg;
+ $logger->info("FF SIP response => $resp");
+ return $resp;
+}
+
+sub login{
+ my $self = $_[0];
+ return $self->{login_resp} if ($self->{logged_in});
+ my $userid = $self->{login_username};
+ my $userpasswd = $self->{login_passwd};
+ my $locationCode = $self->{location};
+
+ # some SIP servers do not require login
+ return $self->{logged_in} = 1 unless $userid and $userpasswd;
+
+ my $msg = "93 CN$userid|CO$userpasswd|";
+ $self->{login_resp} = $self->sendMsg($msg);
+ my $u = Sip::MsgType->new($self->{login_resp},0);
+ my ($ok) = @{$u->{fixed_fields}};
+ $self->{logged_in} = ($ok eq 'Y');
+ return $ok;
+
+}
+
+sub logout{
+ my $self = $_[0];
+ $self->{login_resp} = $self->{logged_in} = undef;
+ if ($self->{socket}) {
+ $self->{socket}->shutdown(2);
+ $self->{socket}->close;
+ $self->{socket} = undef;
+ }
+}
+
+# args:
+# patron_id
+# patron_pass - optional
+# start_index - optional
+# end_index - optional
+sub lookup_user {
+ my $self = shift;
+ my $args = shift;
+
+ $args->{enable_summary_pos} = 0 # backwards compat
+ unless $args->{enable_summary_pos};
+
+ my $msg = '63001'; # message 63, language english (001)
+ $msg .= Sip::timestamp();
+
+ # summary field is 10 slots, all spaces, one Y allowed.
+ $msg .= $args->{enable_summary_pos} == $_ ? 'Y' : ' ' for (0..9);
+
+ $msg .= add_field(FID_INST_ID, $self->{location});
+ $msg .= add_field(FID_PATRON_ID, $args->{patron_id});
+
+ # optional fields
+ $msg .= maybe_add(FID_TERMINAL_PWD, $self->{login_passwd});
+ $msg .= maybe_add(FID_PATRON_PWD, $args->{patron_pass});
+ $msg .= maybe_add(FID_START_ITEM, $args->{start_index});
+ $msg .= maybe_add(FID_END_ITEM, $args->{end_index});
+
+ $self->login;
+
+ my $resp = $self->sendMsg($msg, 5);
+ my $u = Sip::MsgType->new($resp, 0, 1);
+ my $out = $self->lookup_user_handler($u);
+
+ $self->logout;
+ return $out;
+}
+
+
+# deprecated, start using lookup_user() instead
+sub lookupUser{
+ my $self = $_[0];
+ my $terminalPwd = $self->{login_passwd};
+ my $location = $self->{location}; #institution id
+ my $patronId = $_[1];
+ my $patronPasswd = $_[2];
+ my $start_item = $_[3] || 1;
+ my $end_item = $_[4] || 10;
+ my $msg = '63001'; #sets message id to 63 and the language to english, 001
+ $msg .= Sip::timestamp()."Y";
+ $msg .= ' ' x 9; #Adds an empty 10 spaces for the summary field
+ $msg .= add_field(FID_INST_ID,$location);
+ $msg .= add_field(FID_PATRON_ID,$patronId);
+ $msg .= maybe_add(FID_TERMINAL_PWD,$terminalPwd);
+ $msg .= maybe_add(FID_PATRON_PWD,$patronPasswd);
+ $msg .= maybe_add(FID_START_ITEM,$start_item);
+ $msg .= maybe_add(FID_END_ITEM,$end_item);
+ $self->login;
+ my $resp = $self->sendMsg($msg,5);
+ #$self->logout;
+ my $u = Sip::MsgType->new($resp,0);
+ my $out = $self->lookup_user_handler($u);
+ $self->logout;
+ return $out;
+}
+
+sub lookup_user_handler{
+ my $self = shift;
+ my $data = shift;
+
+ if (!$data) {
+ $logger->error("FF SIP lookup_user returned no response");
+ return;
+ }
+
+ my $fields = $data->{fields};
+ my $fixed_fields = $data->{fixed_fields};
+ my @wholename = split(',',$fields->{AE});
+ my $surname = $wholename[0];
+ my $given_name = $wholename[1];
+ my $valid_patron = $fields->{BL};
+ my $error = 0;
+ my $error_message = "";
+ $fields->{AF} ||= '';
+
+ if($valid_patron eq "N" || $fields->{AF} eq "User not found"){
+ $error = 1;
+ $error_message = "User is not valid";
+ }
+
+ my $out = {
+ #user_id => $fields->{AA},
+ patron_status => $fixed_fields->[0],
+ langauge => $fixed_fields->[1],
+ transaction_date => $fixed_fields->[2],
+ hold_items_count => $fixed_fields->[3],
+ overdue_items_count => $data->{fixed_fields}->[4],
+ charged_items_count => $data->{fixed_fields}->[5],
+ fine_items_count => $data->{fixed_fields}->[6],
+ recall_items_count => $data->{fixed_fields}->[7],
+ unavailable_holds_count => $data->{fixed_fields}->[8],
+ institution_id => $fields->{AO},
+ patron_identifier => $fields->{AA},
+ personal_name => $fields->{AE},
+ holds_items_limit => $fields->{BZ},
+ overdue_items_limit => $fields->{CA},
+ charged_items_limit => $fields->{CB},
+ valid_patron => $fields->{BL},
+ valid_patron_password => $fields->{CQ},
+ currency_type => $fields->{BH},
+ fee_amount => $fields->{BV},
+ fee_limit => $fields->{CC},
+ hold_items => $fields->{AS},
+ overdue_items => $fields->{AT},
+ charged_items => $fields->{AU},
+ fine_items => $fields->{AV},
+ recall_items => $fields->{BU},
+ unavailable_hold_items => $fields->{CD},
+ home_address => $fields->{BD},
+ email_address => $fields->{BE},
+ home_phone_number => $fields->{BF},
+ screen_message => $fields->{AF},
+ print_line => $fields->{AG},
+ };
+
+ return $out;
+}
+
+sub checkout{
+ my $self = $_[0];
+ my $terminalPwd = $self->{login_passwd};
+ my $location = $self->{location}; #institution id
+ my $patron_id = $_[1];
+ my $patron_passwd =$_[2];
+ my $item_id = $_[3];
+ my $fee_ack = $_[4] || "N"; #value should be Y or N
+ my $cancel = $_[5] || "N"; #value should be Y or N
+ my $due_date_epoch = $_[6];
+ my $msg = '11NN'; #sets message id to 11, no blocking, no autorenew
+ $msg .= Sip::timestamp();
+ $msg .= Sip::timestamp($due_date_epoch);
+ $msg .= maybe_add(FID_INST_ID,$location);
+ $msg .= add_field(FID_PATRON_ID,$patron_id);
+ $msg .= add_field(FID_ITEM_ID,$item_id);
+ $msg .= add_field(FID_TERMINAL_PWD,$terminalPwd);
+ $msg .= maybe_add(FID_PATRON_PWD,$patron_passwd);
+ $msg .= maybe_add(FID_FEE_ACK,$fee_ack);
+ $msg .= maybe_add(FID_CANCEL,$cancel);
+ $self->login;
+ my $resp = $self->sendMsg($msg,5);
+ my $co = Sip::MsgType->new($resp,0);
+ $self->logout;
+ return $co;
+
+}
+
+sub checkin{
+ my $self = $_[0];
+ my $terminalPwd = $self->{login_passwd};
+ my $location = $self->{location}; #institution id
+ my $patron_id = $_[1];
+ my $patron_passwd =$_[2];
+ my $item_id = $_[3];
+ my $item_properties = $_[4];
+ my $cancel = $_[5] || "N"; #value should be Y or N
+ my $fee_ack = $_[6] || "N"; #value should be Y or N
+ my $msg = '09N'; #sets message id to 09, no blocking
+ $msg .= Sip::timestamp();
+ $msg .= Sip::timestamp();
+ $msg .= add_field(FID_CURRENT_LOCN,$location);
+ $msg .= add_field(FID_INST_ID,$location);
+ $msg .= add_field(FID_PATRON_ID,$patron_id);
+ $msg .= add_field(FID_ITEM_ID,$item_id);
+ $msg .= add_field(FID_TERMINAL_PWD,$terminalPwd);
+ $msg .= add_field(FID_ITEM_PROPS,$item_properties);
+ $msg .= add_field(FID_CANCEL,$cancel);
+ $msg .= add_field(FID_FEE_ACK,$fee_ack);
+ $self->login;
+ my $resp = $self->sendMsg($msg,5);
+ my $ci = Sip::MsgType->new($resp,0);
+ $self->logout;
+ return $ci;
+
+}
+
+# translates a SIP checkout or checkin response to a human-friendlier hash
+sub sip_msg_to_circ {
+ my ($self, $msg, $type) = @_;
+ $type |= '';
+
+ my $fields = $msg->{fields};
+ my $fixed_fields = $msg->{fixed_fields};
+
+ $logger->debug("FF mapping SIP to circ for " . Dumper($msg));
+
+ my $circ = {
+ error => !$fixed_fields->[0],
+ magnetic_media => $fixed_fields->[2], # Y N U
+ transaction_date => $fixed_fields->[4],
+ institution_id => $fields->{AO},
+ patron_id => $fields->{AA},
+ item_id => $fields->{AB},
+ due_date => $fields->{AH},
+ fee_type => $fields->{BT},
+ security_inhibit => $fields->{BI},
+ currency_type => $fields->{BH},
+ fee_amount => $fields->{BV},
+ media_type => $fields->{BV},
+ item_properties => $fields->{CH},
+ transaction_id => $fields->{BK},
+ screen_message => $fields->{AF},
+ print_line => $fields->{AG},
+ permanent_location => $fields->{AQ}
+ #title =>
+ #call_number =>
+ #price =>
+ };
+
+ if ($type eq 'checkout') {
+ $circ->{renewal_ok} = $fixed_fields->[1];
+ $circ->{desensitize} = $fixed_fields->[3];
+ } else {
+ $circ->{resensitize} = $fixed_fields->[1];
+ $circ->{alert} = $fixed_fields->[3];
+ }
+
+ return $circ;
+}
+
+
+
+sub item_information_request{
+ my ($self,$item_id) = @_;
+ $Sip::protocol_version = 2;
+ my $msg = "17".Sip::timestamp();
+ $msg .= add_field(FID_INST_ID,$self->{location});
+ $msg .= add_field(FID_ITEM_ID,$item_id);
+ $msg .= add_field(FID_TERMINAL_PWD,$self->{login_passwd});
+ $self->login;
+ my $resp = $self->sendMsg($msg,6);
+ my $u = Sip::MsgType->new($resp,0);
+ $self->logout;
+ return unless $u;
+ my $out = $self->item_information_request_handler($u);
+ return $out;
+}
+
+sub item_information_request_handler{
+ my $self = $_[0];
+ my $response = $_[1];
+ my $fixed_fields = $response->{fixed_fields};
+ my $fields = $response->{fields};
+
+ my $out = {
+ circulation_status => $fixed_fields->[0],
+ security_marker => $fixed_fields->[1],
+ fee_type => $fixed_fields->[2],
+ transaction_date => $fixed_fields->[3],
+ hold_queue_length => $fields->{CF},
+ due_date => $fields->{AH},
+ recall_date => $fields->{CJ},
+ hold_pickup_date => $fields->{CM},
+ item_identifier => $fields->{AB},
+ title_identifier => $fields->{AJ},
+ owner => $fields->{BG},
+ currency_type => $fields->{BH},
+ fee_amount => $fields->{BV},
+ media_type => $fields->{CK},
+ permanent_location => $fields->{AQ},
+ current_location => $fields->{AP},
+ item_properties => $fields->{CH},
+ screen_message => $fields->{AF},
+ print_line => $fields->{AG},
+ };
+
+ return $out;
+}
+
+
+sub build_hold_msg{
+ my ($self,$patron_id, $patron_pwd ,$holdMode,
+ $expiration_date, $pickup_location, $hold_type,
+ $item_id, $title_id, $fee_acknowledged
+ ) = @_;
+
+ my $location = $self->{location};
+ my $msg = "15";
+ my $terminal_pwd = $self->{login_passwd} ;
+
+ if($holdMode eq "add"){
+ $holdMode = "+";
+ }elsif($holdMode eq "delete"){
+ $holdMode = "-";
+ }elsif($holdMode eq "change"){
+ $holdMode = "*";
+ }
+
+ $msg .= $holdMode . Sip::timestamp();
+ $msg .= maybe_add(FID_EXPIRATION,$expiration_date);
+ $msg .= maybe_add(FID_PICKUP_LOCN,$pickup_location);
+ $msg .= maybe_add(FID_HOLD_TYPE,$hold_type);
+ $msg .= maybe_add(FID_INST_ID,$location);
+ $msg .= add_field(FID_PATRON_ID,$patron_id);
+ $msg .= maybe_add(FID_PATRON_PWD,$patron_pwd);
+ $msg .= maybe_add(FID_ITEM_ID,$item_id);
+ $msg .= maybe_add(FID_TITLE_ID,$title_id);
+ $msg .= maybe_add(FID_TERMINAL_PWD,$terminal_pwd);
+ #$msg .= maybe_add(FID_FEE_ACK,$fee_acknowledged); #This field did not work when tested with the Sirsi-dynex implementation
+
+ return $msg;
+
+}
+
+sub place_hold{
+
+ my ($self,$patron_id, $patron_pwd ,$expiration_date, $pickup_location, $hold_type,
+ $item_id, $title_id, $fee_acknowledged
+ ) = @_;
+
+ my $hold_mode = "add";
+ my $msg = $self->build_hold_msg($patron_id, $patron_pwd,$hold_mode,
+ $expiration_date ,$pickup_location,$hold_type,$item_id,$title_id,$fee_acknowledged);
+ $self->login;
+ my $resp = $self->sendMsg($msg,5);
+ my $u = Sip::MsgType->new($resp,0);
+ $self->logout;
+ return $u;
+}
+
+sub delete_hold{
+ my ($self,$patron_id, $patron_pwd ,$expiration_date, $pickup_location, $hold_type,
+ $item_id, $title_id, $fee_acknowledged
+ ) = @_;
+
+ my $hold_mode = "delete";
+ my $msg = $self->build_hold_msg($patron_id, $patron_pwd,$hold_mode,
+ $expiration_date ,$pickup_location,$hold_type,$item_id,$title_id,$fee_acknowledged);
+ $self->login;
+ my $resp = $self->sendMsg($msg,5);
+ my $u = Sip::MsgType->new($resp,0);
+ return $u;
+ $self->logout;
+}
+
+1;
--- /dev/null
+#
+#===============================================================================
+#
+# FILE: z39.50.pm
+#
+# DESCRIPTION:
+#
+# FILES: ---
+# BUGS: ---
+# NOTES: ---
+# AUTHOR: Michael Davadrian Smith (), msmith@esilibrary.com
+# COMPANY: Equinox Software
+# VERSION: 1.0
+# CREATED: 12/12/2011 01:12:32 PM
+# REVISION: ---
+#===============================================================================
+
+package FulfILLment::Util::Z3950;
+use strict;
+use warnings;
+use Data::Dumper;
+use JSON::XS;
+use ZOOM;
+use MARC::Record;
+use MARC::File::XML ( BinaryEncoding => 'utf8' );
+use MARC::Charset;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+MARC::Charset->ignore_errors(1);
+
+sub new{
+ my($type) = shift;
+ my $host = shift;
+ #$host =~ s/http:\/\///;
+ my $port = shift;
+ my $database = shift;
+ my $login= shift;
+ my $password= shift;
+ my ($self)={
+ 'host'=>$host,
+ 'login' => $login,
+ 'password' => $password,
+ 'database' => $database,
+ 'port' => $port,
+ 'out'=>'',#will hold output of query
+ };
+
+ bless($self,$type);
+
+}
+
+
+sub breaker2marc {
+ my $lines = shift;
+ my $delim = quotemeta(shift() || '$');
+
+ my $rec = new MARC::Record;
+
+ my $first = 1;
+ for my $line (@$lines) {
+
+ chomp($line);
+
+ if ($first) {
+ if ($line =~ /^\d/) {
+ $rec->leader($line);
+ $first--;
+ }
+ } elsif ($line =~ /^=?(\d{3}) (.)(.) (.+)$/) {
+
+ my ($tag, $i1, $i2, $rest) = ($1, $2, $3, $4);
+
+ if ($tag < 10) {
+ $rec->insert_fields_ordered( MARC::Field->new( $tag => $rest ) );
+
+ } else {
+
+ my @subfield_data = split $delim, $rest;
+ if ($subfield_data[0]) {
+ $subfield_data[0] = 'a ' . $subfield_data[0];
+ } else {
+ shift @subfield_data;
+ }
+
+ my @subfields;
+ for my $sfd (@subfield_data) {
+ if ($sfd =~ /^(.) (.+)$/) {
+ push @subfields, $1, $2;
+ }
+ }
+
+ $rec->insert_fields_ordered(
+ MARC::Field->new(
+ $tag,
+ $i1,
+ $i2,
+ @subfields
+ )
+ ) if @subfields;
+ }
+ }
+ }
+
+ return $rec;
+}
+
+
+sub get_record_by_id {
+ my $self = shift;
+ my $recid = shift;
+ my $attr = shift || '12';
+ my $asxml = shift || 1;
+ my $syntax = shift || 'usmarc';
+ my $return_raw = shift || 0;
+
+ my $conn = new ZOOM::Connection(
+ $self->{'host'},
+ $self->{'port'},
+ databaseName => $self->{'database'},
+ preferredRecordSyntax => $syntax
+ );
+
+ my $query = "\@attr 1=$attr \"$recid\"";
+
+ $logger->info(sprintf(
+ "FF Z3950 sending query to %s/%s => %s",
+ $self->{host}, $self->{database}, $query));
+
+ my $rs = $conn->search_pqf($query);
+
+ if ($conn->errcode() != 0) {
+ $logger->error("Z39 bib-by-id failed: " . $conn->errmsg());
+ return;
+ }
+
+ $logger->info("Z39 bib-by-id returned ".$rs->size." hit(s)");
+
+ return unless $rs->size;
+
+ return $rs->record(0)->raw if $return_raw;
+
+ # warning: render() is highly subjective and may
+ # not behave as expected on all Z servers and formats
+ my $m = $rs->record(0)->render();
+
+ my $rec = breaker2marc([ split /\n/, $m ]);
+
+ my $x = $rec->as_xml_record;
+ $x =~ s/^<\?.+?\?>.//sm;
+
+ my @out;
+ $conn->destroy();
+ if($asxml == 1){
+ return $x;
+ }elsif($asxml == 0){
+ return $m;
+ }
+}
+
+
+
+sub getBibByTitle{
+ my $self = shift;
+ my $title = shift;
+ my $attr = "4";
+ my $asxml = shift || 1;
+
+ my $conn = new ZOOM::Connection(
+ $self->{'host'},
+ $self->{'port'},
+ databaseName => $self->{'database'},
+ preferredRecordSyntax => "usmarc"
+ );
+
+ my $rs = $conn->search_pqf("\@attr 1=$attr \"$title\"");
+
+ if ($conn->errcode() != 0) {
+ die("something went wrong: " . $conn->errmsg())
+ }
+
+ my $m = $rs->record(0)->render();
+ my $rec = breaker2marc([ split /\n/, $m ]);
+ my $x = $rec->as_xml_record;
+ $x =~ s/^<\?.+?\?>.//sm;
+
+ my @out;
+ $conn->destroy();
+ if($asxml == 1){
+ return $x;
+ }elsif($asxml == 0){
+ return $m;
+ }
+}
+
+
+
+
+
+sub queryServer{
+ my $self = shift;
+ my $query = shift;
+ my $asxml = shift || 1;
+ my $conn = new ZOOM::Connection(
+ $self->{host},
+ $self->{port},
+ databaseName => $self->{database},
+ preferredRecordSyntax => "usmarc"
+ );
+ my $rs = $conn->search_pqf($query);
+
+ if($conn->errcode() != 0){
+ die("something went wrong: ".$conn->errmsg());
+ }
+
+ my $m = $rs->record(0)->render();
+ my $rec = breaker2marc([ split /\n/, $m ]);
+ my $x = $rec->as_xml_record;
+ $x =~ s/^<\?.+?\?>.//sm;
+ my @out;
+ $conn->destroy();
+ if($asxml == 1){
+ return $x;
+ }elsif($asxml == 0){
+ return $m;
+ }
+
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1;
--- /dev/null
+package FulfILLment::WWW::FastImport;
+use strict;
+use warnings;
+use bytes;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND FORBIDDEN :log);
+use APR::Const -compile => qw(:error SUCCESS);
+use APR::Table;
+
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Data::Dumper;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::AppSession;
+use OpenSRF::MultiSession;
+use OpenSRF::Utils::JSON;
+use XML::LibXML;
+
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+use MARC::Batch;
+use MARC::File::XML ( BinaryEncoding => 'utf-8' );
+use MARC::Charset;
+use Getopt::Long;
+use Unicode::Normalize;
+use Encode;
+
+use UNIVERSAL::require;
+
+MARC::Charset->ignore_errors(1);
+
+our @formats = qw/USMARC UNIMARC XML BRE/;
+my $MAX_FILE_SIZE = 1073741824; # 1GB
+my $FILE_READ_SIZE = 4096;
+
+# set the bootstrap config and template include directory when
+# this module is loaded
+my $bootstrap;
+
+sub import {
+ my $self = shift;
+ $bootstrap = shift;
+}
+
+
+sub child_init {
+ OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+}
+
+sub handler {
+ my $r = shift;
+ my $cgi = new CGI;
+
+ my $auth = $cgi->param('ses') || $cgi->cookie('ses');
+
+ unless(verify_login($auth)) {
+ $logger->error("authentication failed on vandelay record import: $auth");
+ return Apache2::Const::FORBIDDEN;
+ }
+
+ my $fh = $cgi->param('loadFile');
+ my $x;
+ my $mtype = (sysread($fh,$x,1) && $x =~ /^\D/o) ? 'XML' : 'USMARC';
+
+ sysseek($fh,0,0);
+
+ $r->content_type('html');
+ print '<div>';
+
+ my $conf = OpenSRF::Utils::SettingsClient->new;
+ my $parallel = $conf->config_value(
+ apps => 'fulfillment.www.fast-import' => app_settings => 'parallel'
+ ) || 1;
+
+ my $owner = $cgi->param('uploadLocation');
+
+ my $multi = OpenSRF::MultiSession->new(
+ app => 'open-ils.cstore',
+ cap => $parallel,
+ api_level => 1
+ );
+
+ my $batch = new MARC::Batch ($mtype, $fh);
+ $batch->strict_off;
+
+ my $count = 0;
+ my $rec = -1;
+ while (try { $rec = $batch->next } otherwise { $rec = -1 }) {
+ $count++;
+ warn "record $count\n";
+ if ($rec == -1) {
+ print "<div>Processing of record $count in set $fh failed. Skipping this record</div>";
+ next;
+ }
+
+ try {
+ # Avoid an over-eager MARC::File::XML that may try to convert
+ # our record from MARC8 to UTF8 and break because the record
+ # is obviously already UTF8
+ my $ldr = $rec->leader();
+ if (($mtype eq 'XML') && (substr($ldr, 9, 1) ne 'a')) {
+ print "<div style='color: orange;'>MARCXML record LDR/09 was not 'a'; record leader may be corrupt</div>";
+ substr($ldr,9,1,'a');
+ $rec->leader($ldr);
+ }
+
+ (my $xml = $rec->as_xml_record()) =~ s/\n//sog;
+ $xml =~ s/^<\?xml.+\?\s*>//go;
+ $xml =~ s/>\s+</></go;
+ $xml =~ s/\p{Cc}//go;
+
+ $xml = entityize($xml);
+ $xml =~ s/[\x00-\x1f]//go;
+
+ $multi->request(
+ 'open-ils.cstore.json_query',
+ { from => [ 'biblio.fast_import', $owner, $xml ] }
+ );
+
+ } catch Error with {
+ my $error = shift;
+ print "<div style='color: red;'>Encountered a bad record during fast import: $error</div>";
+ };
+
+ }
+
+ print "<div>Completed processing of $count records from $fh</div>";
+ $multi->session_wait(1);
+ $multi->disconnect;
+
+ print '</div>';
+
+ return Apache2::Const::OK;
+}
+
+# xml-escape non-ascii characters
+sub entityize {
+ my($string, $form) = @_;
+ $form ||= "";
+
+ # If we're going to convert non-ASCII characters to XML entities,
+ # we had better be dealing with a UTF8 string to begin with
+ $string = decode_utf8($string);
+
+ if ($form eq 'D') {
+ $string = NFD($string);
+ } else {
+ $string = NFC($string);
+ }
+
+ # Convert raw ampersands to entities
+ $string =~ s/&(?!\S+;)/&/gso;
+
+ # Convert Unicode characters to entities
+ $string =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
+
+ return $string;
+}
+
+sub verify_login {
+ my $auth_token = shift;
+ return undef unless $auth_token;
+
+ my $user = OpenSRF::AppSession
+ ->create("open-ils.auth")
+ ->request( "open-ils.auth.session.retrieve", $auth_token )
+ ->gather(1);
+
+ if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
+ return undef;
+ }
+
+ return $user if ref($user);
+ return undef;
+}
+
+1;
+
--- /dev/null
+dojo.require("fieldmapper.AutoIDL");
+dojo.require("dijit.layout.ContentPane");
+dojo.require("dijit.layout.TabContainer");
+dojo.require("dijit.form.TextBox");
+dojo.require("openils.widget.AutoGrid");
+dojo.require("openils.User");
+dojo.require("openils.PermaCrud");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+
+function objSize (obj) {
+ var size = 0, key;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) size++;
+ }
+ return size;
+}
+
+
+var user = new openils.User ({authcookie:'ses'});
+openils.User.authtoken = user.authtoken;
+var pcrud = new openils.PermaCrud ({authtoken:user.authtoken});
+var cgi = new CGI ();
+
+var cache = {acp:{}, au:{}, ac:{}, acn:{}, circ:{}, atc:{}};
+var firstrun = true;
+
+function resetLocation(loc,which) {
+
+ if (!dojo.isObject(which)) which = {};
+ var hasSome = objSize(which) > 0;
+
+ if (!firstrun) toMeGrid.resetStore();
+ if (!hasSome || which.tmg) toMeGrid.loadAll(
+ {order_by:{"circ":"xact_start"}},
+ {circ_lib : loc, checkin_time : null}
+ );
+
+ if (!firstrun) fromMeGrid.resetStore();
+ if (!hasSome || which.fmg) fromMeGrid.loadAll(
+ {order_by:{"circ":"xact_start"}},
+ { checkin_time : null,
+ target_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: { circ_lib : loc, id : { '=' : {'+circ' : 'target_copy'} } }
+ }
+ }
+ }
+ );
+
+
+ if(!firstrun) nonHoldTransitsFromMeGrid.resetStore();
+ if (!hasSome || which.nhtfmg) nonHoldTransitsFromMeGrid.loadAll(
+ {order_by:{"atc":"source_send_time DESC"}},
+ {source:loc, dest_recv_time:null,
+ id:{"not in":{
+ select : {'ahtc' : ['id']},
+ from : 'ahtc',
+ where :{source:loc, dest_recv_time:null}
+ }}
+ }
+ );
+
+
+ if(!firstrun) nonHoldTransitsToMeGrid.resetStore();
+ if (!hasSome || which.nhttmg) nonHoldTransitsToMeGrid.loadAll(
+ {order_by:{"atc":"source_send_time DESC"}},
+ {dest:loc, dest_recv_time:null,
+ id:{"not in":{
+ select : {'ahtc' : ['id']},
+ from : 'ahtc',
+ where :{dest:loc, dest_recv_time:null}
+ }}
+ }
+ );
+
+
+
+
+
+ firstrun = false;
+}
+
+
+function processCopyBarcodeAction (bc, resetPart) {
+ var c = pcrud.search(
+ "circ",
+ { checkin_time : null,
+ circ_lib : circLocation.getValue(),
+ target_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : { '<>' : circLocation.getValue() },
+ barcode : bc,
+ id : { '=' : {'+circ' : 'target_copy'} }
+ }
+ }
+ }
+ }
+ )[0];
+
+ var stat = {
+ barcode: bc, // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ patron: null, // patron FM object from circ.usr()
+ card: null, // item with the barcode we were passed
+ circ: null, // circ FM object
+ transit: null, // circ FM object
+ action: null, // Check In, Recall
+ };
+
+ if (c) { // circ found, it's leaving
+ checkinAction(c.id(),resetPart);
+
+ } else { // going out ... maybe
+ c = pcrud.search(
+ "circ",
+ { checkin_time : null,
+ circ_lib : { '<>' : circLocation.getValue() },
+ target_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : circLocation.getValue(),
+ barcode : bc,
+ id : { '=' : {'+circ' : 'target_copy'} }
+ }
+ }
+ }
+ }
+ )[0];
+
+ if (c) { // found a remote circ
+ recallAction(c.id(),{x:false});
+
+ } else { // return transit, perhaps?
+ stat.transit = pcrud.search(
+ "atc",
+ { target_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : circLocation.getValue(),
+ barcode : bc,
+ id : { '=' : {'+atc' : 'target_copy'} }
+ }
+ }
+ },
+ dest: circLocation.getValue(),
+ dest_recv_time: null
+ }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}}
+ )[0]; // grab any transit
+
+ if (stat.transit) {
+ if (!cache.acp[stat.transit.target_copy()]) cache.acp[stat.transit.target_copy()] = pcrud.retrieve("acp", stat.transit.target_copy());
+ stat.copy = cache.acp[stat.transit.target_copy()];
+ if (stat.copy) stat.barcode = stat.copy.barcode();
+
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.checkin.override'],
+ [user.authtoken, {noop: 1, force: 1, barcode: bc}]
+ );
+
+ stat.action = 'Incoming';
+ stat.direction = 'Item Received';
+ } else {
+ stat.action = 'None';
+ stat.direction = 'Unknown';
+ }
+
+ return renderScanStatusRow(stat);
+
+ }
+ }
+
+}
+
+function truncate_date (ts) { return ts.substring(0,10) }
+
+var stat_list = [];
+
+function renderScanStatusRow(stat) {
+ stat_list.push(stat);
+ var stat_pos = stat_list.length - 1;
+
+ var row = dojo.clone( dojo.query('.statusTemplate')[0] );
+ row.setAttribute('stat_pos', stat_pos);
+
+ dojo.query('.action', row)[0].innerHTML = stat.action;
+
+ if (stat.card) dojo.query('.patron', row)[0].innerHTML = stat.card.barcode();
+
+ if (stat.circ || stat.transit) {
+ dojo.query('.receipt', row)[0].setAttribute('href','javascript:printCircStatDetail(' + stat_pos + ')');
+ }
+
+ if (stat.copy) {
+ dojo.query('.item', row)[0].innerHTML = formatCopyBarcode(stat.copy);
+ } else {
+ dojo.query('.item', row)[0].innerHTML = stat.barcode;
+ }
+
+ if (stat.transit) {
+ if (stat.transit.dest() == circLocation.getValue()) {
+ dojo.query('.location', row)[0].innerHTML = formatCirclib(
+ fieldmapper.aou.findOrgUnit(stat.transit.source())
+ );
+ } else {
+ dojo.query('.location', row)[0].innerHTML = formatCirclib(
+ fieldmapper.aou.findOrgUnit(stat.transit.dest())
+ );
+ }
+ }
+
+ dojo.byId('statusTarget').appendChild(row);
+}
+
+function printAllCircStatDetail() {
+ for (var i = 0; i < stat_list.length; i++) {
+ printCircStatDetail(i);
+ }
+}
+
+function printCircStatDetail(stat_pos) {
+ var stat = stat_list[stat_pos];
+ var template = fieldmapper.standardRequest(
+ ['open-ils.actor','open-ils.actor.web_action_print_template.fetch'],
+ [circLocation.getValue(), 'circ', stat.action, stat.direction ]
+ );
+
+ var f_list = [ 'copy', 'patron', 'card', 'circ', 'transit' ];
+
+ var stat_copy = { action : stat.action, barcode: stat.barcode, direction: stat.direction };
+ for (var i = 0; i < f_list.length; i++) {
+ if (stat[f_list[i]]) stat_copy[f_list[i]] = stat[f_list[i]].toHash(true, [], true)
+ }
+
+ var w = window.open();
+ w.document.write( dojo.string.substitute( template.template(), stat_copy, function (v) { if (!v) return ''; return v; } ) );
+ w.document.close();
+ w.print();
+ w.close();
+}
+
+function fetchCopy(rowIndex, item) {
+ if (!item) return null;
+
+ var acp_id = this.grid.store.getValue(item, "target_copy");
+ if (!cache.acp[acp_id])
+ cache.acp[acp_id] = pcrud.retrieve("acp", this.grid.store.getValue(item, "target_copy"));
+
+ return cache.acp[acp_id]
+}
+
+function formatBarcode(value) {
+ if (!value) return ""; // XXX
+ return value.barcode();
+}
+
+function fetchSource(rowIndex, item) {
+ if (!item) return null;
+
+ return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "source"));
+}
+
+function fetchDest(rowIndex, item) {
+ if (!item) return null;
+
+ return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "dest"));
+}
+
+function fetchCirclib(rowIndex, item) {
+ if (!item) return null;
+
+ return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "circ_lib"));
+}
+
+function formatCirclib(value) {
+ if (!value) return ""; // XXX
+ return value.name();
+}
+
+function fetchCirc(rowIndex, item) {
+ if (!item) return null;
+ return item;
+}
+
+function fetchTransit(rowIndex, item) {
+ if (!item) return null;
+ return item;
+}
+
+function formatIncomingTransitActions(item) {
+ if (!item) return "";
+
+ var c_id = this.grid.store.getValue(item, "id");
+
+ var link_text = '<span style="white-space:nowrap;">' +
+ '<a href="javascript:checkinAction(' + c_id + ',{nhttmg:true});">Receive Item</a>' +
+ '</span>';
+
+ return link_text;
+}
+
+function formatOutgoingTransitActions(item) { return ""; }
+
+function formatLocalActions(item) {
+ if (!item) return ""; // XXX
+ var circ_id = this.grid.store.getValue(item, "id");
+ return '<span style="white-space:nowrap;">' +
+ '<a href="javascript:checkinAction(' + circ_id + ',{tmg:true});">Check In</a>' +
+ '<br/>' +
+ '<a href="javascript:renewAction(' + circ_id + ',{tmg:true});">Renew</a>' +
+ '<br/>' +
+ '<a href="javascript:lostAction(' + circ_id + ',{tmg:true});">Mark Lost</a>' +
+ '<br/>' +
+ '<a href="javascript:CRAction(' + circ_id + ',{tmg:true});">Mark Claims Returned</a>' +
+ '</span>';
+}
+
+function formatRemoteActions(item) {
+ if (!item) return ""; // XXX
+ return '<span style="white-space:nowrap;">' +
+ '<a href="javascript:recallAction(' + this.grid.store.getValue(item, "id") + ',{fmg:true});">Recall</a>' +
+ '</span>';
+}
+
+function formatCopyBarcode(value) {
+ if (!value) return ""; // XXX
+ if (!cache.acn[value.call_number()])
+ cache.acn[value.call_number()] = pcrud.retrieve("acn", value.call_number());
+ var cn = cache.acn[value.call_number()];
+ return '<a target="_blank" href=/opac/skin/bt/xml/rdetail.xml?r=' + cn.record() + '>' + value.barcode() + '</a>';
+}
+
+function checkinAction(circid, resetPart) {
+ if (!cache.circ[circid])
+ cache.circ[circid] = pcrud.retrieve("circ", circid);
+
+ var c = cache.circ[circid];
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.checkin.override'],
+ [user.authtoken, {force: 1, copy_id: c.target_copy(), patron: c.usr()}]
+ );
+ resetLocation(circLocation.getValue(),resetPart);
+
+ var stat = {
+ barcode: null, // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ patron: null, // patron FM object from circ.usr()
+ card: null, // item with the barcode we were passed
+ circ: null, // circ FM object
+ transit: null, // circ FM object
+ action: 'Checked In', // Check In, Recall
+ };
+
+ c = pcrud.retrieve("circ", c.id()); // refetch the circ
+ cache.circ[c.id()] = c;
+ stat.circ = c;
+
+ if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy
+ stat.copy = cache.acp[c.target_copy()];
+ stat.barcode = stat.copy.barcode(); // barcode we would have been passed
+
+ if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy
+ stat.patron = cache.au[c.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card
+ stat.card = cache.ac[stat.patron.card()];
+
+ stat.transit = pcrud.search(
+ "atc",
+ { target_copy: stat.copy.id(),
+ source: circLocation.getValue(),
+ dest_recv_time: null
+ }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}}
+ )[0]; // grab any transit
+
+ if (stat.transit) cache.atc[stat.transit.id()] = stat.transit;
+
+ return renderScanStatusRow(stat);
+}
+
+function renewAction(circid,resetPart) {
+ if (!resetPart) resetPart = {tmg:true};
+ if (!cache.circ[circid])
+ cache.circ[circid] = pcrud.retrieve("circ", circid);
+
+ var c = cache.circ[circid];
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.renew.override'],
+ [user.authtoken, {copy_id: c.target_copy(), patron: c.usr()}]
+ );
+ resetLocation(circLocation.getValue(),resetPart);
+
+ var stat = {
+ barcode: null, // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ patron: null, // patron FM object from circ.usr()
+ card: null, // item with the barcode we were passed
+ circ: null, // circ FM object
+ transit: null, // circ FM object
+ action: 'Item Renewed', // Check In, Recall
+ };
+
+ var new_c = pcrud.search( // Fetch the new circ
+ "circ",
+ { checkin_time : null,
+ circ_lib : circLocation.getValue(),
+ target_copy : c.target_copy()
+ }
+ )[0];
+
+ if (!new_c) {
+ stat.action = 'Item Renewal Failed';
+ } else if (c.id() != new_c.id()) {
+ c = new_c;
+ cache.circ[c.id()] = c;
+ stat.circ = c;
+
+ } else if (c.id() == new_c.id()) {
+ c = pcrud.retrieve("circ", c.id()); // refetch the circ
+ cache.circ[c.id()] = c;
+ stat.circ = c;
+ stat.action = 'Item Renewal Failed';
+ }
+
+ if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy
+ stat.copy = cache.acp[c.target_copy()];
+ stat.barcode = stat.copy.barcode(); // barcode we would have been passed
+
+ if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy
+ stat.patron = cache.au[c.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card
+ stat.card = cache.ac[stat.patron.card()];
+
+ return renderScanStatusRow(stat);
+}
+
+function lostAction(circid,resetPart) {
+ if (!resetPart) resetPart = {tmg:true};
+ if (!cache.circ[circid])
+ cache.circ[circid] = pcrud.retrieve("circ", circid);
+
+ var c = cache.circ[circid];
+
+ if (!cache.acp[c.target_copy()])
+ cache.acp[c.target_copy()] = pcrud.retrieve("acp", c.target_copy());
+
+ var cp = cache.acp[c.target_copy()];
+
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.circulation.set_lost'],
+ [user.authtoken, {barcode: cp.barcode()}]
+ );
+ resetLocation(circLocation.getValue(),resetPart);
+
+ var stat = {
+ barcode: null, // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ patron: null, // patron FM object from circ.usr()
+ card: null, // item with the barcode we were passed
+ circ: null, // circ FM object
+ transit: null, // circ FM object
+ action: 'Item Marked Lost', // Check In, Recall
+ };
+
+ c = pcrud.retrieve("circ", c.id()); // refetch the circ
+ cache.circ[c.id()] = c;
+ stat.circ = c;
+
+ if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy
+ stat.copy = cache.acp[c.target_copy()];
+ stat.barcode = stat.copy.barcode(); // barcode we would have been passed
+
+ if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy
+ stat.patron = cache.au[c.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card
+ stat.card = cache.ac[stat.patron.card()];
+
+ return renderScanStatusRow(stat);
+}
+
+function CRAction(circid,resetPart) {
+ if (!resetPart) resetPart = {tmg:true};
+ if (!cache.circ[circid])
+ cache.circ[circid] = pcrud.retrieve("circ", circid);
+
+ var c = cache.circ[circid];
+
+ if (!cache.acp[c.target_copy()])
+ cache.acp[c.target_copy()] = pcrud.retrieve("acp", c.target_copy());
+
+ var cp = cache.acp[c.target_copy()];
+
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.circulation.set_claims_returned.override'],
+ [user.authtoken, {barcode: cp.barcode(), use_due_date: 1}]
+ );
+ resetLocation(circLocation.getValue(),resetPart);
+
+ var stat = {
+ barcode: null, // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ patron: null, // patron FM object from circ.usr()
+ card: null, // item with the barcode we were passed
+ circ: null, // circ FM object
+ transit: null, // circ FM object
+ action: 'Item Marked Claims-Returned', // Check In, Recall
+ };
+
+ c = pcrud.retrieve("circ", c.id()); // refetch the circ
+ cache.circ[c.id()] = c;
+ stat.circ = c;
+
+ if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy
+ stat.copy = cache.acp[c.target_copy()];
+ stat.barcode = stat.copy.barcode(); // barcode we would have been passed
+
+ if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy
+ stat.patron = cache.au[c.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card
+ stat.card = cache.ac[stat.patron.card()];
+
+ return renderScanStatusRow(stat);
+}
+
+function recallAction(circid,resetPart) {
+ if (!resetPart) resetPart = {fmg:true};
+ alert('recalling ' + circid);
+ resetLocation(circLocation.getValue(),resetPart);
+
+ var stat = {
+ barcode: null, // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ card: null, // item with the barcode we were passed
+ patron: null, // patron FM object from circ.usr()
+ circ: null, // circ FM object
+ transit: null, // circ FM object
+ action: 'Item Recalled', // Check In, Recall
+ };
+
+ c = pcrud.retrieve("circ", c.id()); // refetch the circ
+ cache.circ[c.id()] = c;
+ stat.circ = c;
+
+ if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy
+ stat.copy = cache.acp[c.target_copy()];
+ stat.barcode = stat.copy.barcode(); // barcode we would have been passed
+
+ if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy
+ stat.patron = cache.au[c.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card
+ stat.card = cache.ac[stat.patron.card()];
+
+ stat.transit = pcrud.search(
+ "atc",
+ { target_copy: stat.copy.id(),
+ source: circLocation.getValue(),
+ dest_recv_time: null
+ }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}}
+ )[0]; // grab any transit
+
+ if (stat.transit) cache.atc[stat.transit.id()] = stat.transit;
+
+ return renderScanStatusRow(stat);
+}
+
+function fetchCard(rowIndex, item) {
+ if (!item) return null;
+
+ var auid = this.grid.store.getValue(item, "usr");
+ if (!cache.au[auid])
+ cache.au[auid] = pcrud.retrieve("au", auid);
+
+ var acid = cache.au[auid].card();
+ if (!cache.ac[acid])
+ cache.ac[acid] = pcrud.retrieve("ac", acid);
+
+ return cache.ac[acid]
+}
+
+dojo.addOnLoad(function(){
+ user.buildPermOrgSelector(
+ 'STAFF_LOGIN',
+ circLocation,
+ cgi.param('l') || user.user.home_ou()
+ );
+});
+
+
--- /dev/null
+dojo.require("fieldmapper.AutoIDL");
+dojo.require("dijit.layout.ContentPane");
+dojo.require("dijit.layout.TabContainer");
+dojo.require("dijit.form.TextBox");
+dojo.require("openils.widget.AutoGrid");
+dojo.require("openils.User");
+dojo.require("openils.PermaCrud");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+
+function objSize (x) {
+ var size = 0, key;
+ for (key in x) {
+ if (x.hasOwnProperty(key)) size++;
+ }
+ return size;
+}
+
+var user = new openils.User ({authcookie:'ses'});
+openils.User.authtoken = user.authtoken;
+
+var pcrud = new openils.PermaCrud ({authtoken:user.authtoken});
+var cgi = new CGI ();
+
+var cache = { acp : {}, acn : {}, ahr : {}, au : {}, ac : {}, ahtc_by_hold : {}, atc : {}, circ : {} };
+var firstrun = true;
+
+function resetLocation(loc, which) {
+
+ if (!dojo.isObject(which)) which = {};
+
+ var hasSome = objSize(which) > 0;
+
+ if (!firstrun) toMeGrid.resetStore();
+ if (!hasSome || which.tmg) toMeGrid.loadAll(
+ {order_by:{"ahr": "request_time DESC"}},
+ {request_lib : loc, fulfillment_time : null, cancel_time : null}
+ );
+
+
+ if (!firstrun) fromMeGrid.resetStore();
+ if (!hasSome || which.fmg) fromMeGrid.loadAll(
+ {order_by:{"ahr": "request_time DESC"}},
+ { fulfillment_time : null,
+ cancel_time : null,
+ current_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: { circ_lib : loc, id : { '=' : {'+ahr' : 'current_copy'} } }
+ }
+ }
+ }
+ );
+
+
+
+ if (!firstrun) holdTransitFromMeGrid.resetStore();
+ if (!hasSome || which.htfmg) holdTransitFromMeGrid.loadAll(
+ {order_by:{"ahtc":"source_send_time DESC"}},
+ {source:loc, dest_recv_time:null}
+ );
+
+
+ if(!firstrun) holdTransitToMeGrid.resetStore();
+ if (!hasSome || which.httmg) holdTransitToMeGrid.loadAll(
+ {order_by:{"ahtc":"source_send_time DESC"}},
+ {dest:loc, dest_recv_time:null}
+ );
+
+ firstrun = false;
+}
+
+function processCopyBarcodeAction (bc) {
+ console.log("processing barcode " + bc);
+
+ var h = pcrud.search(
+ "ahr",
+ { fulfillment_time : null,
+ cancel_time : null,
+ frozen : 'f',
+ request_lib : holdLocation.getValue(),
+ current_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : { '<>' : holdLocation.getValue() },
+ barcode : bc,
+ id : { '=' : {'+ahr' : 'current_copy'} }
+ }
+ }
+ }
+ }
+ )[0];
+
+ var stat = {
+ barcode: bc, // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ hold: h, // hold FM object
+ patron: null, // patron FM object
+ card: null, // card FM object
+ transit: null, // transit FM object
+ action: null, // Receive, Check Out, Capture
+ direction: null, // Incoming, Outgoing
+ };
+
+ if (h) { // hold coming here
+ stat.direction = 'Incoming';
+
+ if (h.shelf_time()) { // captured, ready for circ
+ stat.action = 'Check Out';
+ checkoutAction(h.id(),stat, null, 'ill-foreign-checkout');
+ } else { // receiving, check it in to capture
+ stat.action = 'Check In';
+ checkinAction(h.id(),stat, null, 'ill-foreign-receive');
+ }
+
+ } else { // going out ... maybe
+ h = pcrud.search(
+ "ahr",
+ { fulfillment_time : null,
+ cancel_time : null,
+ frozen : 'f',
+ request_lib : { '<>' : holdLocation.getValue() },
+ current_copy : {
+ in : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : holdLocation.getValue(),
+ barcode : bc,
+ id : { '=' : {'+ahr' : 'current_copy'} }
+ }
+ }
+ }
+ }
+ )[0];
+
+ if (h) { // yep ... going out
+ stat.direction = 'Outgoing';
+
+ if (!h.capture_time()) { // time to capture it
+ stat.action = 'Capture';
+ checkinAction(h.id(),stat,null,'ill-home-capture');
+ } else { // else, already captured, just grab the transit
+ stat.hold = h;
+ cache.ahr[stat.hold.id()] = stat.hold;
+
+ if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy());
+ stat.copy = cache.acp[stat.hold.current_copy()];
+ if (stat.copy) stat.barcode = stat.copy.barcode();
+
+ if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr());
+ stat.patron = cache.au[stat.hold.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card());
+ stat.card = cache.ac[stat.patron.card()];
+
+ // Grab the transit again, with stuff fleshed for printing
+ stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0];
+
+ return renderScanStatusRow(stat);
+ }
+
+
+ } else { // return transit, perhaps?
+ stat.transit = pcrud.search(
+ "atc",
+ { target_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : holdLocation.getValue(),
+ barcode : bc,
+ id : { '=' : {'+atc' : 'target_copy'} }
+ }
+ }
+ },
+ dest: holdLocation.getValue(),
+ dest_recv_time: null
+ }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}}
+ )[0]; // grab any transit
+
+ if (stat.transit) { // coming home
+ if (!cache.acp[stat.transit.target_copy()])
+ cache.acp[stat.transit.target_copy()] =
+ pcrud.retrieve("acp", stat.transit.target_copy());
+
+ stat.copy = cache.acp[stat.transit.target_copy()];
+ if (stat.copy) stat.barcode = stat.copy.barcode();
+
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.checkin.override'],
+ [user.authtoken, {noop: 1, force: 1, barcode: bc, ff_action: 'transit-home-receive'}]
+ );
+
+ stat.direction = 'Incoming';
+ stat.action = 'Check In';
+ } else { // maybe it's checked out here
+ var c = pcrud.search(
+ "circ",
+ { checkin_time : null,
+ circ_lib : holdLocation.getValue(),
+ target_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : { '<>' : holdLocation.getValue() },
+ barcode : bc,
+ id : { '=' : {'+circ' : 'target_copy'} }
+ }
+ }
+ }
+ }
+ )[0];
+
+ if (c) {
+ stat.action = 'Check In';
+ stat.direction = 'Outgoing';
+ return checkinILLAction(c, stat);
+
+ } else {
+
+ stat.direction = 'Unknown';
+
+ // maybe it's already checked in here and transiting home
+ // TODO
+ // all the commented-out stuff below needs re-thinking
+ // with borrowing tmp copy barcode in mind
+
+ /*
+ stat.transit = pcrud.search(
+ "atc",
+ { target_copy : {
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : holdLocation.getValue(),
+ barcode : bc,
+ id : { '=' : {'+atc' : 'target_copy'} }
+ }
+ }
+ },
+ dest: holdLocation.getValue(),
+ dest_recv_time: null
+ }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}}
+ )[0]; // grab any transit
+
+
+ // this copy means nothing to us, but show it in the UI so the user
+ // has some conformation it was entered.
+
+
+
+ // find the real copy if we can
+ for (var id in cache) {
+ if (cache[id].barcode() == stat.barcode) {
+ stat.copy = cache[id];
+ }
+ }
+ if (!stat.copy) {
+ stat.copy = pcrud.search(
+ 'acp', {
+ barcode : stat.barcode,
+ deleted : 'f'
+ }
+ )[0];
+ if (stat.copy)
+ cache.acp[stat.copy.id()] = stat.copy;
+ }
+
+ if (stat.copy) {
+ // find the last transit this copy took part in
+ // to provide some context to the user.
+ stat.transit = pcrud.search("atc",
+ {target_copy : stat.copy.id()},
+ { flesh: 2,
+ flesh_fields: {aou:['ill_address'], atc: ['source','dest']},
+ order_by : {atc : "source_send_time DESC"},
+ limit : 1
+ }
+ )[0];
+
+ if (stat.transit && !stat.transit.dest_recv_time &&
+ stat.dest.id() != holdLocation.getValue() ) {
+
+ // copy has already been checked in at the
+ // borrowing library and wants to get back home.
+ stat.direction = 'Outgoing';
+
+ // report on the most recent circulation
+ stat.circ = pcrud.search(
+ "circ",
+ { circ_lib : holdLocation.getValue(),
+ target_copy :
+ "in" : {
+ select: { acp : ['id'] },
+ from : 'acp',
+ where: {
+ deleted : 'f',
+ circ_lib : { '<>' : holdLocation.getValue() },
+ barcode : bc,
+ id : { '=' : {'+circ' : 'target_copy'} }
+ }
+ }
+ }
+ }
+ )[0];
+
+ }
+
+ }
+ */
+
+ }
+ }
+
+ return renderScanStatusRow(stat);
+ }
+ }
+}
+
+function checkinILLAction(c, stat) {
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.checkin.override'],
+ [user.authtoken, {force: 1, copy_id: c.target_copy(), patron: c.usr(), ff_action: 'ill-foreign-checkin'}]
+ );
+
+ c = pcrud.retrieve("circ", c.id()); // refetch the circ
+ cache.circ[c.id()] = c;
+ stat.circ = c;
+
+ if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy
+ stat.copy = cache.acp[c.target_copy()];
+ stat.barcode = stat.copy.barcode(); // barcode we would have been passed
+
+ if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy
+ stat.patron = cache.au[c.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card
+ stat.card = cache.ac[stat.patron.card()];
+
+ stat.transit = pcrud.search(
+ "atc",
+ { target_copy: stat.copy.id(),
+ source: holdLocation.getValue(),
+ dest_recv_time: null
+ }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}}
+ )[0]; // grab any transit
+
+ if (stat.transit) cache.atc[stat.transit.id()] = stat.transit;
+
+ return renderScanStatusRow(stat);
+}
+
+function truncate_date (ts) { return ts.substring(0,10) }
+
+var stat_list = [];
+
+function renderScanStatusRow(stat) {
+ stat_list.push(stat);
+ var stat_pos = stat_list.length - 1;
+
+ var row = dojo.clone( dojo.query('.statusTemplate')[0] );
+ row.setAttribute('stat_pos', stat_pos);
+
+ dojo.query('.action', row)[0].innerHTML = stat.action; // TODO i18n
+ dojo.query('.direction', row)[0].innerHTML = stat.direction; // TODO i18n
+
+ if (stat.card) dojo.query('.patron', row)[0].innerHTML = stat.card.barcode();
+
+ if (stat.circ) {
+ dojo.query('.receipt', row)[0].setAttribute('href','javascript:printCircStatDetail(' + stat_pos + ')');
+ } else if (stat.hold || stat.transit) {
+ dojo.query('.receipt', row)[0].setAttribute('href','javascript:printHoldStatDetail(' + stat_pos + ')');
+ }
+
+ if (stat.copy) {
+ dojo.query('.item', row)[0].innerHTML = formatCopyBarcode(stat.copy);
+ } else {
+ dojo.query('.item', row)[0].innerHTML = stat.barcode;
+ }
+
+ if (stat.transit) {
+ if (stat.transit.dest().id() == holdLocation.getValue()) {
+ dojo.query('.location', row)[0].innerHTML = formatCirclib( stat.transit.source() );
+ } else {
+ dojo.query('.location', row)[0].innerHTML = formatCirclib( stat.transit.dest() );
+ }
+ }
+
+ dojo.byId('statusTarget').appendChild(row);
+}
+
+
+function printAllHoldStatDetail() {
+ for (var i = 0; i < stat_list.length; i++) {
+ console.log('stat list: ' + js2JSON(stat_list));
+ if (stat_list[i].circ) printCircStatDetail(i);
+ else printHoldStatDetail(i);
+ }
+}
+
+function printHoldStatDetail(stat_pos) {
+ var stat = stat_list[stat_pos];
+ var template = fieldmapper.standardRequest(
+ ['open-ils.actor','open-ils.actor.web_action_print_template.fetch'],
+ [holdLocation.getValue(), 'hold', stat.action, stat.direction ]
+ );
+
+ var f_list = [ 'copy', 'patron', 'card', 'hold', 'transit' ];
+
+ var stat_copy = { action : stat.action, barcode: stat.barcode, direction: stat.direction };
+ for (var i = 0; i < f_list.length; i++) {
+ if (stat[f_list[i]]) stat_copy[f_list[i]] = stat[f_list[i]].toHash(true, [], true)
+ }
+
+ var w = window.open();
+ w.document.write( dojo.string.substitute( template.template(), stat_copy, function (v) { if (!v) return ''; return v; } ) );
+ w.document.close();
+ w.print();
+ w.close();
+}
+
+function printCircStatDetail(stat_pos) {
+ var stat = stat_list[stat_pos];
+ var template = fieldmapper.standardRequest(
+ ['open-ils.actor','open-ils.actor.web_action_print_template.fetch'],
+ [holdLocation.getValue(), 'circ', stat.action, stat.direction ]
+ );
+
+ var f_list = [ 'copy', 'patron', 'card', 'circ', 'transit' ];
+
+ var stat_copy = { action : stat.action, barcode: stat.barcode, direction: stat.direction };
+ for (var i = 0; i < f_list.length; i++) {
+ if (stat[f_list[i]]) stat_copy[f_list[i]] = stat[f_list[i]].toHash(true, [], true)
+ }
+
+ var w = window.open();
+ w.document.write( dojo.string.substitute( template.template(), stat_copy, function (v) { if (!v) return ''; return v; } ) );
+ w.document.close();
+ w.print();
+ w.close();
+}
+
+function fetchCopy(rowIndex, item) {
+ if (!item) return null;
+
+ var acp_id = this.grid.store.getValue(item, "current_copy");
+ if (!cache.acp[acp_id]) cache.acp[acp_id] = pcrud.retrieve("acp", this.grid.store.getValue(item, "current_copy"));
+ return cache.acp[acp_id];
+}
+
+function fetchTargetCopy(rowIndex, item) {
+ if (!item) return null;
+
+ var acp_id = this.grid.store.getValue(item, "target_copy");
+ if (!cache.acp[acp_id]) cache.acp[acp_id] = pcrud.retrieve("acp", this.grid.store.getValue(item, "target_copy"));
+ return cache.acp[acp_id];
+}
+
+function formatBarcode(value) {
+ if (!value) return ""; // XXX
+ return value.barcode();
+}
+
+function fetchSource(rowIndex, item) {
+ if (!item) return null;
+
+ return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "source"));
+}
+
+function fetchDest(rowIndex, item) {
+ if (!item) return null;
+
+ return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "dest"));
+}
+
+function fetchCirclib(rowIndex, item) {
+ if (!item) return null;
+
+ return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "pickup_lib"));
+}
+
+function formatCirclib(value) {
+ if (!value) return ""; // XXX
+ return value.name();
+}
+
+function fetchCard(rowIndex, item) {
+ if (!item) return null;
+
+ var u_id = this.grid.store.getValue(item, "usr");
+ if (!cache.au[u_id]) cache.au[u_id] = pcrud.retrieve("au", u_id);
+
+ if (!cache.ac[cache.au[u_id].card()]) cache.ac[cache.au[u_id].card()] = pcrud.retrieve("ac", cache.au[u_id].card());
+
+ return cache.ac[cache.au[u_id].card()];
+}
+
+function fetchHold(rowIndex, item) {
+ if (!item) return null;
+ return item;
+}
+
+function fetchTransit(rowIndex, item) {
+ if (!item) return null;
+ return item;
+}
+
+function formatLocalActions(item) {
+ if (!item) return ""; // XXX
+
+ var h_id = this.grid.store.getValue(item, "id");
+
+ var f_text = '<a href="javascript:freezeAction(' + h_id + ',\'t\',{tmg:true});">Freeze Request</a><br/>';
+ if (this.grid.store.getValue(item, "frozen") == 't')
+ f_text = '<a href="javascript:freezeAction(' + h_id + ',\'f\',{tmg:true});">Thaw Request</a><br/>';
+
+ var cc_text = '<a href="javascript:rejectAction(' + h_id + ',{tmg:true});">Target Request</a><br/>';
+ if (this.grid.store.getValue(item, "current_copy")) cc_text = '';
+
+ var cap_text = '<a href="javascript:checkinAction(' + h_id + ',{direction:\'Incoming\',action:\'Check In\'},{tmg:true},\'ill-foreign-receive\');">Receive Item</a><br/>';
+ if (this.grid.store.getValue(item, "shelf_time")) cap_text = '<a href="javascript:checkoutAction(' + h_id + ',{direction:\'Incoming\',action:\'Check Out\'},{tmg:true},\'ill-foreign-checkout\');">Circulate Item</a><br/>';
+
+ var link_text = '<span style="white-space:nowrap;">' +
+ cap_text + f_text + cc_text +
+ '<a href="javascript:cancelAction(' + h_id + ',{tmg:true});">Cancel Request</a>' +
+ '</span>';
+ return link_text;
+}
+
+function formatRemoteActions(item) {
+ if (!item) return ""; // XXX
+
+ var h_id = this.grid.store.getValue(item, "id");
+
+ var cap_text = '<a href="javascript:checkinAction(' + h_id + ',{direction:\'Outgoing\',action:\'Capture\'},{fmg:true},\'ill-home-capture\');">Capture Item</a><br/>';
+ if (this.grid.store.getValue(item, "capture_time")) cap_text = '';
+
+ var link_text = '<span style="white-space:nowrap;">' +
+ cap_text +
+ '<a href="javascript:rejectAction(' + h_id + ',{fmg:true});">Reject Request</a>' +
+ '</span>';
+ return link_text;
+}
+
+function formatIncomingTransitActions(item) {
+ if (!item) return ""; // XXX
+
+ var h_id = this.grid.store.getValue(item, "hold");
+
+ var link_text = '<span style="white-space:nowrap;">' +
+ '<a href="javascript:checkinAction(' + h_id + ',{direction:\'Incoming\',action:\'Check In\'},{httmg:true},\'transit-home-receive\');">Receive Item</a><br/>' +
+ '<a href="javascript:cancelAction(' + h_id + ',{httmg:true});">Cancel Request</a>' +
+ '</span>';
+ return link_text;
+}
+
+function formatOutgoingTransitActions(item) {
+ if (!item) return ""; // XXX
+
+ var h_id = this.grid.store.getValue(item, "hold");
+
+ var link_text = '<span style="white-space:nowrap;">' +
+ '<a href="javascript:cancelAction(' + h_id + ',{htfmg:true});">Cancel Request</a>' +
+ '</span>';
+ return link_text;
+}
+
+function formatCopyBarcode(value) {
+ if (!value) return ""; // XXX
+ if (!cache.acn[value.call_number()]) cache.acn[value.call_number()] = pcrud.retrieve("acn", value.call_number());
+ return '<a target="_blank" href=/opac/skin/bt/xml/rdetail.xml?r=' + cache.acn[value.call_number()].record() + '>' + value.barcode() + '</a>';
+}
+
+function checkinAction(holdid, stat, resetPart, ff_a) {
+ if (!resetPart) resetPart = {tmg : true};
+ var hold = pcrud.retrieve("ahr", holdid);
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.checkin.override'],
+ [user.authtoken, {force: 1, copy_id: hold.current_copy(), patron: hold.usr(), ff_action: ff_a}]
+ );
+ resetLocation(holdLocation.getValue(), resetPart);
+
+ stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold
+ cache.ahr[stat.hold.id()] = stat.hold;
+
+ if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy());
+ stat.copy = cache.acp[stat.hold.current_copy()];
+
+ if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr());
+ stat.patron = cache.au[stat.hold.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card());
+ stat.card = cache.ac[stat.patron.card()];
+
+ // Grab the transit again, with stuff fleshed for printing
+ stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0];
+
+ return renderScanStatusRow(stat);
+
+}
+
+function checkoutAction(holdid, stat, resetPart, ff_a) {
+ if (!resetPart) resetPart = {tmg : true};
+ var hold = pcrud.retrieve("ahr", holdid);
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.checkout.full'],
+ [user.authtoken, {copy_id: hold.current_copy(), patron: hold.usr(), ff_action: ff_a}]
+ );
+ resetLocation(holdLocation.getValue(), resetPart);
+
+ stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold
+ cache.ahr[stat.hold.id()] = stat.hold;
+
+ var c = pcrud.search(
+ "circ",
+ { checkin_time : null,
+ circ_lib : holdLocation.getValue(),
+ target_copy : hold.current_copy()
+ }
+ )[0];
+
+ cache.circ[c.id()] = c;
+ stat.circ = cache.circ[c.id()];
+
+ if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy());
+ stat.copy = cache.acp[stat.hold.current_copy()];
+
+ if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr());
+ stat.patron = cache.au[stat.hold.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card());
+ stat.card = cache.ac[stat.patron.card()];
+
+ // Grab the transit again, with stuff fleshed for printing
+ stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0];
+
+ return renderScanStatusRow(stat);
+
+}
+
+function cancelAction(holdid, resetPart) {
+ if (!resetPart) resetPart = {tmg : true};
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.hold.cancel'],
+ [user.authtoken, holdid]
+ );
+ resetLocation(holdLocation.getValue(), resetPart);
+
+ var stat = {
+ barcode: '', // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ hold: null, // hold FM object
+ patron: null, // patron FM object
+ card: null, // card FM object
+ transit: null, // transit FM object
+ action: 'Cancel', // Receive, Check Out, Capture
+ direction: 'Incoming', // Incoming, Outgoing
+ };
+
+ stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold
+ cache.ahr[stat.hold.id()] = stat.hold;
+
+ if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy());
+ stat.copy = cache.acp[stat.hold.current_copy()];
+ if (stat.copy) stat.barcode = stat.copy.barcode();
+
+ if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr());
+ stat.patron = cache.au[stat.hold.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card());
+ stat.card = cache.ac[stat.patron.card()];
+
+ // Grab the transit again, with stuff fleshed for printing
+ stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0];
+
+ return renderScanStatusRow(stat);
+
+}
+
+function freezeAction(holdid, setting, resetPart) {
+ if (!resetPart) resetPart = {tmg : true};
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.hold.update'],
+ [user.authtoken, null, { id : holdid, frozen : setting }]
+ );
+ resetLocation(holdLocation.getValue(), resetPart);
+
+ var stat = {
+ barcode: '', // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ hold: null, // hold FM object
+ patron: null, // patron FM object
+ card: null, // card FM object
+ transit: null, // transit FM object
+ action: setting == 't' ? 'Freeze' : 'Thaw', // Receive, Check Out, Capture
+ direction: 'Incoming', // Incoming, Outgoing
+ };
+
+ stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold
+ cache.ahr[stat.hold.id()] = stat.hold;
+
+ if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy());
+ stat.copy = cache.acp[stat.hold.current_copy()];
+ if (stat.copy) stat.barcode = stat.copy.barcode();
+
+ if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr());
+ stat.patron = cache.au[stat.hold.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card());
+ stat.card = cache.ac[stat.patron.card()];
+
+ // Grab the transit again, with stuff fleshed for printing
+ stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0];
+
+ return renderScanStatusRow(stat);
+
+}
+
+function rejectAction(holdid, resetPart) {
+ if (!resetPart) resetPart = {fmg : true};
+ fieldmapper.standardRequest(
+ ['open-ils.circ','open-ils.circ.hold.reset'],
+ [user.authtoken, holdid]
+ );
+ resetLocation(holdLocation.getValue(), resetPart);
+
+ var stat = {
+ barcode: '', // barcode we were passed
+ copy: null, // item with the barcode we were passed
+ hold: null, // hold FM object
+ patron: null, // patron FM object
+ card: null, // card FM object
+ transit: null, // transit FM object
+ action: 'Reject', // Receive, Check Out, Capture
+ direction: 'Outgoing', // Incoming, Outgoing
+ };
+
+ stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold
+ cache.ahr[stat.hold.id()] = stat.hold;
+
+ if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy());
+ stat.copy = cache.acp[stat.hold.current_copy()];
+ if (stat.copy) stat.barcode = stat.copy.barcode();
+
+ if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr());
+ stat.patron = cache.au[stat.hold.usr()];
+
+ if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card());
+ stat.card = cache.ac[stat.patron.card()];
+
+ // Grab the transit again, with stuff fleshed for printing
+ stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0];
+
+ return renderScanStatusRow(stat);
+
+}
+
+dojo.addOnLoad(function(){
+ user.buildPermOrgSelector(
+ 'STAFF_LOGIN',
+ holdLocation,
+ cgi.param('l') || user.user.home_ou()
+ );
+});
+
--- /dev/null
+dojo.require("fieldmapper.AutoIDL");
+dojo.require("dijit.layout.ContentPane");
+dojo.require("dijit.layout.TabContainer");
+dojo.require("openils.widget.AutoGrid");
+dojo.require("openils.User");
+dojo.require("openils.PermaCrud");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+dojo.require("dojo.io.iframe");
+
+var user = new openils.User ({authcookie:'ses'});
+openils.User.authtoken = user.authtoken;
+var pcrud = new openils.PermaCrud ({authtoken:user.authtoken});
+var cgi = new CGI ();
+
+function wireForm(){
+ var canvas=dojo.byId("response");
+ dojo.connect(dojo.byId('localMarcForm'),"onsubmit",function(event){
+ dojo.stopEvent(event);
+
+ dojo.byId("response").innerHTML=" Sending data, please wait...";
+ dojo.io.iframe.send({
+ form: "localMarcForm",
+ handleAs: "html",
+ load: function(data){ canvas.innerHTML = data.innerHTML },
+ error: function(error){ canvas.innerHTML = error.innerHTML }
+ });
+
+ });
+}
+
+dojo.addOnLoad(function(){
+ user.buildPermOrgSelector(
+ 'STAFF_LOGIN',
+ loc,
+ cgi.param('l') || user.user.home_ou()
+ );
+
+ wireForm();
+});
+
+
--- /dev/null
+<?php\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ \r
+ Version 0.0.7a 2001-04-01 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+\r
+require("debug.php");\r
+\r
+/***************************** base class ********************************************/\r
+/** NB: all GD call's is here **/\r
+\r
+/* Styles */\r
+\r
+/* Global */\r
+define("BCS_BORDER" , 1);\r
+define("BCS_TRANSPARENT" , 2);\r
+define("BCS_ALIGN_CENTER" , 4);\r
+define("BCS_ALIGN_LEFT" , 8);\r
+define("BCS_ALIGN_RIGHT" , 16);\r
+define("BCS_IMAGE_JPEG" , 32);\r
+define("BCS_IMAGE_PNG" , 64);\r
+define("BCS_DRAW_TEXT" , 128);\r
+define("BCS_STRETCH_TEXT" , 256);\r
+define("BCS_REVERSE_COLOR" , 512);\r
+/* For the I25 Only */\r
+define("BCS_I25_DRAW_CHECK" , 2048);\r
+\r
+/* Default values */\r
+\r
+/* Global */\r
+define("BCD_DEFAULT_BACKGROUND_COLOR", 0xFFFFFF);\r
+define("BCD_DEFAULT_FOREGROUND_COLOR", 0x000000);\r
+define("BCD_DEFAULT_STYLE" , BCS_BORDER | BCS_ALIGN_CENTER | BCS_IMAGE_PNG);\r
+define("BCD_DEFAULT_WIDTH" , 460);\r
+define("BCD_DEFAULT_HEIGHT" , 120);\r
+define("BCD_DEFAULT_FONT" , 5);\r
+define("BCD_DEFAULT_XRES" , 2);\r
+/* Margins */\r
+define("BCD_DEFAULT_MAR_Y1" , 10);\r
+define("BCD_DEFAULT_MAR_Y2" , 10);\r
+define("BCD_DEFAULT_TEXT_OFFSET" , 2);\r
+/* For the I25 Only */\r
+define("BCD_I25_NARROW_BAR" , 1);\r
+define("BCD_I25_WIDE_BAR" , 2);\r
+\r
+/* For the C39 Only */\r
+define("BCD_C39_NARROW_BAR" , 1);\r
+define("BCD_C39_WIDE_BAR" , 2);\r
+\r
+/* For Code 128 */\r
+define("BCD_C128_BAR_1" , 1);\r
+define("BCD_C128_BAR_2" , 2);\r
+define("BCD_C128_BAR_3" , 3);\r
+define("BCD_C128_BAR_4" , 4);\r
+\r
+ class BarcodeObject {\r
+ \r
+ var $mWidth, $mHeight, $mStyle, $mBgcolor, $mBrush;\r
+ var $mImg, $mFont;\r
+ var $mError;\r
+ \r
+ function BarcodeObject ($Width = BCD_DEFAULT_Width, $Height = BCD_DEFAULT_HEIGHT, $Style = BCD_DEFAULT_STYLE) {\r
+ $this->mWidth = $Width; \r
+ $this->mHeight = $Height;\r
+ $this->mStyle = $Style; \r
+ $this->mFont = BCD_DEFAULT_FONT;\r
+ $this->mImg = ImageCreate($this->mWidth, $this->mHeight);\r
+ $dbColor = $this->mStyle & BCS_REVERSE_COLOR ? BCD_DEFAULT_FOREGROUND_COLOR : BCD_DEFAULT_BACKGROUND_COLOR;\r
+ $dfColor = $this->mStyle & BCS_REVERSE_COLOR ? BCD_DEFAULT_BACKGROUND_COLOR : BCD_DEFAULT_FOREGROUND_COLOR;\r
+ $this->mBgcolor = ImageColorAllocate($this->mImg, ($dbColor & 0xFF0000) >> 16, \r
+ ($dbColor & 0x00FF00) >> 8 , $dbColor & 0x0000FF); \r
+ $this->mBrush = ImageColorAllocate($this->mImg, ($dfColor & 0xFF0000) >> 16, \r
+ ($dfColor & 0x00FF00) >> 8 , $dfColor & 0x0000FF);\r
+ if (!($this->mStyle & BCS_TRANSPARENT)) { \r
+ ImageFill($this->mImg, $this->mWidth, $this->mHeight, $this->mBgcolor); \r
+ }\r
+ __TRACE__("OBJECT CONSTRUCTION: ".$this->mWidth." ".$this->mHeight." ".$this->mStyle);\r
+ }\r
+ \r
+ function DrawObject ($xres) {\r
+ /* there is not implementation neded, is simply the asbsract function. */\r
+ __TRACE__("OBJECT DRAW: NEED VIRTUAL FUNCTION IMPLEMENTATION");\r
+ return false;\r
+ }\r
+ \r
+ function DrawBorder () {\r
+ ImageRectangle($this->mImg, 0, 0, $this->mWidth-1, $this->mHeight-1, $this->mBrush);\r
+ __TRACE__("DRAWING BORDER");\r
+ }\r
+ \r
+ function DrawChar ($Font, $xPos, $yPos, $Char) {\r
+ ImageString($this->mImg,$Font,$xPos,$yPos,$Char,$this->mBrush);\r
+ }\r
+ \r
+ function DrawText ($Font, $xPos, $yPos, $Char) {\r
+ ImageString($this->mImg,$Font,$xPos,$yPos,$Char,$this->mBrush);\r
+ } \r
+ \r
+ function DrawSingleBar($xPos, $yPos, $xSize, $ySize) {\r
+ if ($xPos>=0 && $xPos<=$this->mWidth && ($xPos+$xSize)<=$this->mWidth &&\r
+ $yPos>=0 && $yPos<=$this->mHeight && ($yPos+$ySize)<=$this->mHeight) {\r
+ for ($i=0;$i<$xSize;$i++) {\r
+ ImageLine($this->mImg, $xPos+$i, $yPos, $xPos+$i, $yPos+$ySize, $this->mBrush);\r
+ }\r
+ return true;\r
+ }\r
+ __DEBUG__("DrawSingleBar: Out of range");\r
+ return false; \r
+ } \r
+ \r
+ function GetError() { \r
+ return $this->mError;\r
+ } \r
+ \r
+ function GetFontHeight($font) {\r
+ return ImageFontHeight($font);\r
+ } \r
+ \r
+ function GetFontWidth($font) {\r
+ return ImageFontWidth($font);\r
+ } \r
+ \r
+ function SetFont($font) {\r
+ $this->mFont = $font;\r
+ } \r
+ \r
+ function GetStyle () {\r
+ return $this->mStyle;\r
+ } \r
+ \r
+ function SetStyle ($Style) {\r
+ __TRACE__("CHANGING STYLE");\r
+ $this->mStyle = $Style;\r
+ } \r
+ \r
+ function FlushObject () {\r
+ if (($this->mStyle & BCS_BORDER)) {\r
+ $this->DrawBorder();\r
+ } \r
+ if ($this->mStyle & BCS_IMAGE_PNG) {\r
+ Header("Content-Type: image/png");\r
+ ImagePng($this->mImg);\r
+ } else if ($this->mStyle & BCS_IMAGE_JPEG) {\r
+ Header("Content-Type: image/jpeg");\r
+ ImageJpeg($this->mImg);\r
+ } else __DEBUG__("FlushObject: No output type");\r
+ } \r
+ \r
+ function DestroyObject () {\r
+ ImageDestroy($obj->mImg);\r
+ }\r
+ }\r
+?>\r
--- /dev/null
+<?php\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ \r
+ Version 0.0.7a 2001-04-01 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+ \r
+ /* \r
+ Render for Code 128-A \r
+ Code 128-A is a continuous, multilevel and include all upper case alphanumeric characters and ASCII control characters .\r
+ */\r
+ \r
+ \r
+ class C128AObject extends BarcodeObject {\r
+ var $mCharSet, $mChars;\r
+ function C128AObject($Width, $Height, $Style, $Value)\r
+ {\r
+ $this->BarcodeObject($Width, $Height, $Style);\r
+ $this->mValue = $Value;\r
+ $this->mChars = " !\"#$%&'()*+´-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";\r
+ $this->mCharSet = array\r
+ (\r
+ "212222", /* 00 */\r
+ "222122", /* 01 */\r
+ "222221", /* 02 */\r
+ "121223", /* 03 */\r
+ "121322", /* 04 */\r
+ "131222", /* 05 */\r
+ "122213", /* 06 */\r
+ "122312", /* 07 */\r
+ "132212", /* 08 */\r
+ "221213", /* 09 */\r
+ "221312", /* 10 */\r
+ "231212", /* 11 */\r
+ "112232", /* 12 */\r
+ "122132", /* 13 */\r
+ "122231", /* 14 */\r
+ "113222", /* 15 */\r
+ "123122", /* 16 */\r
+ "123221", /* 17 */\r
+ "223211", /* 18 */\r
+ "221132", /* 19 */\r
+ "221231", /* 20 */\r
+ "213212", /* 21 */\r
+ "223112", /* 22 */\r
+ "312131", /* 23 */\r
+ "311222", /* 24 */\r
+ "321122", /* 25 */\r
+ "321221", /* 26 */\r
+ "312212", /* 27 */\r
+ "322112", /* 28 */\r
+ "322211", /* 29 */\r
+ "212123", /* 30 */\r
+ "212321", /* 31 */\r
+ "232121", /* 32 */\r
+ "111323", /* 33 */\r
+ "131123", /* 34 */\r
+ "131321", /* 35 */\r
+ "112313", /* 36 */\r
+ "132113", /* 37 */\r
+ "132311", /* 38 */\r
+ "211313", /* 39 */\r
+ "231113", /* 40 */\r
+ "231311", /* 41 */\r
+ "112133", /* 42 */\r
+ "112331", /* 43 */\r
+ "132131", /* 44 */\r
+ "113123", /* 45 */\r
+ "113321", /* 46 */\r
+ "133121", /* 47 */\r
+ "313121", /* 48 */\r
+ "211331", /* 49 */\r
+ "231131", /* 50 */\r
+ "213113", /* 51 */\r
+ "213311", /* 52 */\r
+ "213131", /* 53 */\r
+ "311123", /* 54 */\r
+ "311321", /* 55 */\r
+ "331121", /* 56 */\r
+ "312113", /* 57 */\r
+ "312311", /* 58 */\r
+ "332111", /* 59 */\r
+ "314111", /* 60 */\r
+ "221411", /* 61 */\r
+ "431111", /* 62 */\r
+ "111224", /* 63 */\r
+ "111422", /* 64 */\r
+ "121124", /* 65 */\r
+ "121421", /* 66 */\r
+ "141122", /* 67 */\r
+ "141221", /* 68 */\r
+ "112214", /* 69 */\r
+ "112412", /* 70 */\r
+ "122114", /* 71 */\r
+ "122411", /* 72 */\r
+ "142112", /* 73 */\r
+ "142211", /* 74 */\r
+ "241211", /* 75 */\r
+ "221114", /* 76 */\r
+ "413111", /* 77 */\r
+ "241112", /* 78 */\r
+ "134111", /* 79 */\r
+ "111242", /* 80 */\r
+ "121142", /* 81 */\r
+ "121241", /* 82 */\r
+ "114212", /* 83 */\r
+ "124112", /* 84 */\r
+ "124211", /* 85 */\r
+ "411212", /* 86 */\r
+ "421112", /* 87 */\r
+ "421211", /* 88 */\r
+ "212141", /* 89 */\r
+ "214121", /* 90 */\r
+ "412121", /* 91 */\r
+ "111143", /* 92 */\r
+ "111341", /* 93 */\r
+ "131141", /* 94 */\r
+ "114113", /* 95 */\r
+ "114311", /* 96 */\r
+ "411113", /* 97 */\r
+ "411311", /* 98 */\r
+ "113141", /* 99 */\r
+ "114131", /* 100 */\r
+ "311141", /* 101 */\r
+ "411131" /* 102 */\r
+ );\r
+ }\r
+ \r
+ function GetCharIndex ($char) {\r
+ for ($i=0;$i<64;$i++) {\r
+ if ($this->mChars[$i] == $char)\r
+ return $i;\r
+ }\r
+ return -1;\r
+ }\r
+ \r
+ function GetBarSize ($xres, $char) { \r
+ switch ($char)\r
+ {\r
+ case '1':\r
+ $cVal = BCD_C128_BAR_1;\r
+ break;\r
+ case '2':\r
+ $cVal = BCD_C128_BAR_2;\r
+ break;\r
+ case '3':\r
+ $cVal = BCD_C128_BAR_3;\r
+ break;\r
+ case '4':\r
+ $cVal = BCD_C128_BAR_4;\r
+ break;\r
+ default:\r
+ $cVal = 0;\r
+ }\r
+ return $cVal * $xres;\r
+ }\r
+\r
+ \r
+ function GetSize($xres) {\r
+ $len = strlen($this->mValue);\r
+ \r
+ if ($len == 0) {\r
+ $this->mError = "Null value";\r
+ __DEBUG__("GetRealSize: null barcode value");\r
+ return false;\r
+ }\r
+ $ret = 0;\r
+ for ($i=0;$i<$len;$i++) {\r
+ if (($id = $this->GetCharIndex($this->mValue[$i])) == -1) {\r
+ $this->mError = "C128A not include the char '".$this->mValue[$i]."'";\r
+ return false;\r
+ } else {\r
+ $cset = $this->mCharSet[$id];\r
+ $ret += $this->GetBarSize($xres, $cset[0]);\r
+ $ret += $this->GetBarSize($xres, $cset[1]);\r
+ $ret += $this->GetBarSize($xres, $cset[2]);\r
+ $ret += $this->GetBarSize($xres, $cset[3]);\r
+ $ret += $this->GetBarSize($xres, $cset[4]);\r
+ $ret += $this->GetBarSize($xres, $cset[5]);\r
+ }\r
+ } \r
+ \r
+ /* length of Check character */\r
+ $cset = $this->GetCheckCharValue();\r
+ for ($i=0;$i<6;$i++) {\r
+ $CheckSize += $this->GetBarSize($cset[$i], $xres);\r
+ } \r
+ $StartSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres;\r
+ $StopSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + 2*BCD_C128_BAR_3*$xres;\r
+ return $StartSize + $ret + $CheckSize + $StopSize;\r
+ }\r
+ \r
+ function GetCheckCharValue()\r
+ {\r
+ $len = strlen($this->mValue);\r
+ $sum = 103; // 'A' type;\r
+ for ($i=0;$i<$len;$i++) {\r
+ $sum += $this->GetCharIndex($this->mValue[$i]) * ($i+1);\r
+ }\r
+ $check = $sum % 103;\r
+ return $this->mCharSet[$check];\r
+ }\r
+\r
+ function DrawStart($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Start code is '211412' */\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('4', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawStop($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Stop code is '2331112' */\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $DrawPos += $this->GetBarSize('3', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('3', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawCheckChar($DrawPos, $yPos, $ySize, $xres)\r
+ {\r
+ $cset = $this->GetCheckCharValue();\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[0], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[1], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[2], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[3], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[4], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[5], $xres); \r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawObject ($xres)\r
+ {\r
+ $len = strlen($this->mValue); \r
+ if (($size = $this->GetSize($xres))==0) {\r
+ __DEBUG__("GetSize: failed");\r
+ return false;\r
+ } \r
+ \r
+ if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2);\r
+ else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size;\r
+ else $sPos = 0; \r
+ \r
+ /* Total height of bar code -Bars only- */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont);\r
+ else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2;\r
+ \r
+ /* Draw text */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) {\r
+ if ($this->mStyle & BCS_STRETCH_TEXT) {\r
+ for ($i=0;$i<$len;$i++) {\r
+ $this->DrawChar($this->mFont, $sPos+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres)+($size/$len)*$i,\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]);\r
+ } \r
+ } else {/* Center */\r
+ $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue);\r
+ $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres),\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue);\r
+ } \r
+ } \r
+ \r
+ $cPos = 0; \r
+ $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); \r
+ do { \r
+ $c = $this->GetCharIndex($this->mValue[$cPos]);\r
+ $cset = $this->mCharSet[$c]; \r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[0], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[1], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[2], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[3], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[4], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[5], $xres);\r
+ $cPos++; \r
+ } while ($cPos<$len);\r
+ $DrawPos = $this->DrawCheckChar($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres);\r
+ $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres);\r
+ return true;\r
+ }\r
+ }\r
+?>\r
--- /dev/null
+<?php\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ \r
+ Version 0.0.7a 2001-04-01 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+ \r
+ /* \r
+ Render for Code 128-B \r
+ Code 128-B is a continuous, multilevel and full ASCII code .\r
+ */\r
+ \r
+ \r
+ class C128BObject extends BarcodeObject {\r
+ var $mCharSet, $mChars;\r
+ function C128BObject($Width, $Height, $Style, $Value)\r
+ {\r
+ $this->BarcodeObject($Width, $Height, $Style);\r
+ $this->mValue = $Value;\r
+ $this->mChars = " !\"#$%&'()*+´-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{ }~";\r
+ $this->mCharSet = array\r
+ (\r
+ "212222", /* 00 */\r
+ "222122", /* 01 */\r
+ "222221", /* 02 */\r
+ "121223", /* 03 */\r
+ "121322", /* 04 */\r
+ "131222", /* 05 */\r
+ "122213", /* 06 */\r
+ "122312", /* 07 */\r
+ "132212", /* 08 */\r
+ "221213", /* 09 */\r
+ "221312", /* 10 */\r
+ "231212", /* 11 */\r
+ "112232", /* 12 */\r
+ "122132", /* 13 */\r
+ "122231", /* 14 */\r
+ "113222", /* 15 */\r
+ "123122", /* 16 */\r
+ "123221", /* 17 */\r
+ "223211", /* 18 */\r
+ "221132", /* 19 */\r
+ "221231", /* 20 */\r
+ "213212", /* 21 */\r
+ "223112", /* 22 */\r
+ "312131", /* 23 */\r
+ "311222", /* 24 */\r
+ "321122", /* 25 */\r
+ "321221", /* 26 */\r
+ "312212", /* 27 */\r
+ "322112", /* 28 */\r
+ "322211", /* 29 */\r
+ "212123", /* 30 */\r
+ "212321", /* 31 */\r
+ "232121", /* 32 */\r
+ "111323", /* 33 */\r
+ "131123", /* 34 */\r
+ "131321", /* 35 */\r
+ "112313", /* 36 */\r
+ "132113", /* 37 */\r
+ "132311", /* 38 */\r
+ "211313", /* 39 */\r
+ "231113", /* 40 */\r
+ "231311", /* 41 */\r
+ "112133", /* 42 */\r
+ "112331", /* 43 */\r
+ "132131", /* 44 */\r
+ "113123", /* 45 */\r
+ "113321", /* 46 */\r
+ "133121", /* 47 */\r
+ "313121", /* 48 */\r
+ "211331", /* 49 */\r
+ "231131", /* 50 */\r
+ "213113", /* 51 */\r
+ "213311", /* 52 */\r
+ "213131", /* 53 */\r
+ "311123", /* 54 */\r
+ "311321", /* 55 */\r
+ "331121", /* 56 */\r
+ "312113", /* 57 */\r
+ "312311", /* 58 */\r
+ "332111", /* 59 */\r
+ "314111", /* 60 */\r
+ "221411", /* 61 */\r
+ "431111", /* 62 */\r
+ "111224", /* 63 */\r
+ "111422", /* 64 */\r
+ "121124", /* 65 */\r
+ "121421", /* 66 */\r
+ "141122", /* 67 */\r
+ "141221", /* 68 */\r
+ "112214", /* 69 */\r
+ "112412", /* 70 */\r
+ "122114", /* 71 */\r
+ "122411", /* 72 */\r
+ "142112", /* 73 */\r
+ "142211", /* 74 */\r
+ "241211", /* 75 */\r
+ "221114", /* 76 */\r
+ "413111", /* 77 */\r
+ "241112", /* 78 */\r
+ "134111", /* 79 */\r
+ "111242", /* 80 */\r
+ "121142", /* 81 */\r
+ "121241", /* 82 */\r
+ "114212", /* 83 */\r
+ "124112", /* 84 */\r
+ "124211", /* 85 */\r
+ "411212", /* 86 */\r
+ "421112", /* 87 */\r
+ "421211", /* 88 */\r
+ "212141", /* 89 */\r
+ "214121", /* 90 */\r
+ "412121", /* 91 */\r
+ "111143", /* 92 */\r
+ "111341", /* 93 */\r
+ "131141", /* 94 */\r
+ "114113", /* 95 */\r
+ "114311", /* 96 */\r
+ "411113", /* 97 */\r
+ "411311", /* 98 */\r
+ "113141", /* 99 */\r
+ "114131", /* 100 */\r
+ "311141", /* 101 */\r
+ "411131" /* 102 */\r
+ );\r
+ }\r
+ \r
+ function GetCharIndex ($char) {\r
+ for ($i=0;$i<95;$i++) {\r
+ if ($this->mChars[$i] == $char)\r
+ return $i;\r
+ }\r
+ return -1;\r
+ }\r
+ \r
+ function GetBarSize ($xres, $char) { \r
+ switch ($char)\r
+ {\r
+ case '1':\r
+ $cVal = BCD_C128_BAR_1;\r
+ break;\r
+ case '2':\r
+ $cVal = BCD_C128_BAR_2;\r
+ break;\r
+ case '3':\r
+ $cVal = BCD_C128_BAR_3;\r
+ break;\r
+ case '4':\r
+ $cVal = BCD_C128_BAR_4;\r
+ break;\r
+ default:\r
+ $cVal = 0;\r
+ }\r
+ return $cVal * $xres;\r
+ }\r
+\r
+ \r
+ function GetSize($xres) {\r
+ $len = strlen($this->mValue);\r
+ \r
+ if ($len == 0) {\r
+ $this->mError = "Null value";\r
+ __DEBUG__("GetRealSize: null barcode value");\r
+ return false;\r
+ }\r
+ $ret = 0;\r
+ for ($i=0;$i<$len;$i++) {\r
+ if (($id = $this->GetCharIndex($this->mValue[$i])) == -1) {\r
+ $this->mError = "C128B not include the char '".$this->mValue[$i]."'";\r
+ return false;\r
+ } else {\r
+ $cset = $this->mCharSet[$id];\r
+ $ret += $this->GetBarSize($xres, $cset[0]);\r
+ $ret += $this->GetBarSize($xres, $cset[1]);\r
+ $ret += $this->GetBarSize($xres, $cset[2]);\r
+ $ret += $this->GetBarSize($xres, $cset[3]);\r
+ $ret += $this->GetBarSize($xres, $cset[4]);\r
+ $ret += $this->GetBarSize($xres, $cset[5]);\r
+ }\r
+ } \r
+ /* length of Check character */\r
+ $cset = $this->GetCheckCharValue();\r
+ for ($i=0;$i<6;$i++) {\r
+ $CheckSize += $this->GetBarSize($cset[$i], $xres);\r
+ }\r
+ \r
+ $StartSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres;\r
+ $StopSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + 2*BCD_C128_BAR_3*$xres;\r
+ \r
+ return $StartSize + $ret + $CheckSize + $StopSize;\r
+ }\r
+ \r
+ function GetCheckCharValue()\r
+ {\r
+ $len = strlen($this->mValue);\r
+ $sum = 104; // 'B' type;\r
+ for ($i=0;$i<$len;$i++) {\r
+ $sum += $this->GetCharIndex($this->mValue[$i]) * ($i+1);\r
+ }\r
+ $check = $sum % 103;\r
+ return $this->mCharSet[$check];\r
+ }\r
+ \r
+ function DrawStart($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Start code is '211214' */\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('4', $xres);\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawStop($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Stop code is '2331112' */\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $DrawPos += $this->GetBarSize('3', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('3', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawCheckChar($DrawPos, $yPos, $ySize, $xres)\r
+ {\r
+ $cset = $this->GetCheckCharValue();\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[0], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[1], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[2], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[3], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[4], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[5], $xres); \r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawObject ($xres)\r
+ {\r
+ $len = strlen($this->mValue); \r
+ if (($size = $this->GetSize($xres))==0) {\r
+ __DEBUG__("GetSize: failed");\r
+ return false;\r
+ } \r
+ \r
+ if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2);\r
+ else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size;\r
+ else $sPos = 0; \r
+ \r
+ /* Total height of bar code -Bars only- */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont);\r
+ else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2;\r
+ \r
+ /* Draw text */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) {\r
+ if ($this->mStyle & BCS_STRETCH_TEXT) {\r
+ for ($i=0;$i<$len;$i++) {\r
+ $this->DrawChar($this->mFont, $sPos+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres)+($size/$len)*$i,\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]);\r
+ } \r
+ } else {/* Center */\r
+ $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue);\r
+ $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres),\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue);\r
+ } \r
+ } \r
+ \r
+ $cPos = 0; \r
+ $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); \r
+ do { \r
+ $c = $this->GetCharIndex($this->mValue[$cPos]);\r
+ $cset = $this->mCharSet[$c]; \r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[0], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[1], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[2], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[3], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[4], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[5], $xres);\r
+ $cPos++; \r
+ } while ($cPos<$len);\r
+ $DrawPos = $this->DrawCheckChar($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres);\r
+ $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres);\r
+ return true;\r
+ }\r
+ }\r
+?>\r
--- /dev/null
+<?php\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ -- Written on 2001-08-03 by Sam Michaels\r
+ to add Code 128-C support.\r
+ swampgas@swampgas.org\r
+ \r
+ Version 0.0.7a 2001-08-03 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+ \r
+ /* \r
+ Render for Code 128-C\r
+ Code 128-C is numeric only and provides the most efficiency.\r
+ */\r
+ \r
+ \r
+ class C128CObject extends BarcodeObject {\r
+ var $mCharSet, $mChars;\r
+ function C128CObject($Width, $Height, $Style, $Value)\r
+ {\r
+ $this->BarcodeObject($Width, $Height, $Style);\r
+ $this->mValue = $Value;\r
+ $this->mChars = array\r
+ (\r
+ "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",\r
+ "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",\r
+ "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",\r
+ "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",\r
+ "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",\r
+ "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",\r
+ "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",\r
+ "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",\r
+ "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",\r
+ "90", "91", "92", "93", "94", "95", "96", "97", "98", "99",\r
+ );\r
+ $this->mCharSet = array\r
+ (\r
+ "212222", /* 00 */\r
+ "222122", /* 01 */\r
+ "222221", /* 02 */\r
+ "121223", /* 03 */\r
+ "121322", /* 04 */\r
+ "131222", /* 05 */\r
+ "122213", /* 06 */\r
+ "122312", /* 07 */\r
+ "132212", /* 08 */\r
+ "221213", /* 09 */\r
+ "221312", /* 10 */\r
+ "231212", /* 11 */\r
+ "112232", /* 12 */\r
+ "122132", /* 13 */\r
+ "122231", /* 14 */\r
+ "113222", /* 15 */\r
+ "123122", /* 16 */\r
+ "123221", /* 17 */\r
+ "223211", /* 18 */\r
+ "221132", /* 19 */\r
+ "221231", /* 20 */\r
+ "213212", /* 21 */\r
+ "223112", /* 22 */\r
+ "312131", /* 23 */\r
+ "311222", /* 24 */\r
+ "321122", /* 25 */\r
+ "321221", /* 26 */\r
+ "312212", /* 27 */\r
+ "322112", /* 28 */\r
+ "322211", /* 29 */\r
+ "212123", /* 30 */\r
+ "212321", /* 31 */\r
+ "232121", /* 32 */\r
+ "111323", /* 33 */\r
+ "131123", /* 34 */\r
+ "131321", /* 35 */\r
+ "112313", /* 36 */\r
+ "132113", /* 37 */\r
+ "132311", /* 38 */\r
+ "211313", /* 39 */\r
+ "231113", /* 40 */\r
+ "231311", /* 41 */\r
+ "112133", /* 42 */\r
+ "112331", /* 43 */\r
+ "132131", /* 44 */\r
+ "113123", /* 45 */\r
+ "113321", /* 46 */\r
+ "133121", /* 47 */\r
+ "313121", /* 48 */\r
+ "211331", /* 49 */\r
+ "231131", /* 50 */\r
+ "213113", /* 51 */\r
+ "213311", /* 52 */\r
+ "213131", /* 53 */\r
+ "311123", /* 54 */\r
+ "311321", /* 55 */\r
+ "331121", /* 56 */\r
+ "312113", /* 57 */\r
+ "312311", /* 58 */\r
+ "332111", /* 59 */\r
+ "314111", /* 60 */\r
+ "221411", /* 61 */\r
+ "431111", /* 62 */\r
+ "111224", /* 63 */\r
+ "111422", /* 64 */\r
+ "121124", /* 65 */\r
+ "121421", /* 66 */\r
+ "141122", /* 67 */\r
+ "141221", /* 68 */\r
+ "112214", /* 69 */\r
+ "112412", /* 70 */\r
+ "122114", /* 71 */\r
+ "122411", /* 72 */\r
+ "142112", /* 73 */\r
+ "142211", /* 74 */\r
+ "241211", /* 75 */\r
+ "221114", /* 76 */\r
+ "413111", /* 77 */\r
+ "241112", /* 78 */\r
+ "134111", /* 79 */\r
+ "111242", /* 80 */\r
+ "121142", /* 81 */\r
+ "121241", /* 82 */\r
+ "114212", /* 83 */\r
+ "124112", /* 84 */\r
+ "124211", /* 85 */\r
+ "411212", /* 86 */\r
+ "421112", /* 87 */\r
+ "421211", /* 88 */\r
+ "212141", /* 89 */\r
+ "214121", /* 90 */\r
+ "412121", /* 91 */\r
+ "111143", /* 92 */\r
+ "111341", /* 93 */\r
+ "131141", /* 94 */\r
+ "114113", /* 95 */\r
+ "114311", /* 96 */\r
+ "411113", /* 97 */\r
+ "411311", /* 98 */\r
+ "113141", /* 99 */\r
+ );\r
+ }\r
+ \r
+ function GetCharIndex ($char) {\r
+ for ($i=0;$i<100;$i++) {\r
+ if ($this->mChars[$i] == $char)\r
+ return $i;\r
+ }\r
+ return -1;\r
+ }\r
+ \r
+ function GetBarSize ($xres, $char) { \r
+ switch ($char)\r
+ {\r
+ case '1':\r
+ $cVal = BCD_C128_BAR_1;\r
+ break;\r
+ case '2':\r
+ $cVal = BCD_C128_BAR_2;\r
+ break;\r
+ case '3':\r
+ $cVal = BCD_C128_BAR_3;\r
+ break;\r
+ case '4':\r
+ $cVal = BCD_C128_BAR_4;\r
+ break;\r
+ default:\r
+ $cVal = 0;\r
+ }\r
+ return $cVal * $xres;\r
+ }\r
+\r
+ \r
+ function GetSize($xres) {\r
+ $len = strlen($this->mValue);\r
+ \r
+ if ($len == 0) {\r
+ $this->mError = "Null value";\r
+ __DEBUG__("GetRealSize: null barcode value");\r
+ return false;\r
+ }\r
+ $ret = 0;\r
+\r
+ for ($i=0;$i<$len;$i++) {\r
+ if ((ord($this->mValue[$i])<48) || (ord($this->mValue[$i])>57)) {\r
+ $this->mError = "Code-128C is numeric only";\r
+ return false;\r
+ }\r
+ }\r
+\r
+ if (($len%2) != 0) {\r
+ $this->mError = "The length of barcode value must be even. You must pad the number with zeros.";\r
+ __DEBUG__("GetSize: failed C128-C requiremente");\r
+ return false;\r
+ } \r
+\r
+ for ($i=0;$i<$len;$i+=2) {\r
+ $id = $this->GetCharIndex($this->mValue[$i].$this->mValue[$i+1]);\r
+ $cset = $this->mCharSet[$id];\r
+ $ret += $this->GetBarSize($xres, $cset[0]);\r
+ $ret += $this->GetBarSize($xres, $cset[1]);\r
+ $ret += $this->GetBarSize($xres, $cset[2]);\r
+ $ret += $this->GetBarSize($xres, $cset[3]);\r
+ $ret += $this->GetBarSize($xres, $cset[4]);\r
+ $ret += $this->GetBarSize($xres, $cset[5]);\r
+ } \r
+ /* length of Check character */\r
+ $cset = $this->GetCheckCharValue();\r
+ for ($i=0;$i<6;$i++) {\r
+ $CheckSize += $this->GetBarSize($cset[$i], $xres);\r
+ }\r
+ \r
+ $StartSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres;\r
+ $StopSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + 2*BCD_C128_BAR_3*$xres;\r
+ return $StartSize + $ret + $CheckSize + $StopSize;\r
+ }\r
+ \r
+ function GetCheckCharValue()\r
+ {\r
+ $len = strlen($this->mValue);\r
+ $sum = 105; // 'C' type;\r
+ $m = 0;\r
+ for ($i=0;$i<$len;$i+=2) {\r
+ $m++;\r
+ $sum += $this->GetCharIndex($this->mValue[$i].$this->mValue[$i+1]) * $m;\r
+ }\r
+ $check = $sum % 103;\r
+ return $this->mCharSet[$check];\r
+ }\r
+ \r
+ function DrawStart($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Start code is '211232' */\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('3', $xres);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawStop($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Stop code is '2331112' */\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ $DrawPos += $this->GetBarSize('3', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('3', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $DrawPos += $this->GetBarSize('1', $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize('2', $xres);\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawCheckChar($DrawPos, $yPos, $ySize, $xres)\r
+ {\r
+ $cset = $this->GetCheckCharValue();\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[0], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[1], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[2], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[3], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ySize);\r
+ $DrawPos += $this->GetBarSize($cset[4], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[5], $xres); \r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawObject ($xres)\r
+ {\r
+ $len = strlen($this->mValue); \r
+ if (($size = $this->GetSize($xres))==0) {\r
+ __DEBUG__("GetSize: failed");\r
+ return false;\r
+ } \r
+ \r
+ if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2);\r
+ else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size;\r
+ else $sPos = 0; \r
+ \r
+ /* Total height of bar code -Bars only- */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont);\r
+ else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2;\r
+ \r
+ /* Draw text */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) {\r
+ if ($this->mStyle & BCS_STRETCH_TEXT) {\r
+ for ($i=0;$i<$len;$i++) {\r
+ $this->DrawChar($this->mFont, $sPos+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres)+($size/$len)*$i,\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]);\r
+ } \r
+ } else {/* Center */\r
+ $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue);\r
+ $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres),\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue);\r
+ } \r
+ } \r
+ \r
+ $cPos = 0; \r
+ $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); \r
+ do { \r
+ $c = $this->GetCharIndex($this->mValue[$cPos].$this->mValue[$cPos+1]);\r
+ $cset = $this->mCharSet[$c]; \r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[0], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[1], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[2], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[3], $xres);\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ysize);\r
+ $DrawPos += $this->GetBarSize($cset[4], $xres);\r
+ $DrawPos += $this->GetBarSize($cset[5], $xres);\r
+ $cPos += 2; \r
+ } while ($cPos<$len);\r
+ $DrawPos = $this->DrawCheckChar($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres);\r
+ $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres);\r
+ return true;\r
+ }\r
+ }\r
+?>\r
--- /dev/null
+<?php\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ \r
+ Version 0.0.7a 2001-04-01 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+ \r
+ /* \r
+ Render for Code 39 \r
+ Code 39 is an alphanumeric bar code that can encode decimal number, case alphabet and some special symbols.\r
+ */\r
+ \r
+ \r
+ class C39Object extends BarcodeObject {\r
+ var $mCharSet, $mChars;\r
+ function C39Object($Width, $Height, $Style, $Value)\r
+ {\r
+ $this->BarcodeObject($Width, $Height, $Style);\r
+ $this->mValue = $Value;\r
+ $this->mChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";\r
+ $this->mCharSet = array\r
+ (\r
+ /* 0 */ "000110100",\r
+ /* 1 */ "100100001",\r
+ /* 2 */ "001100001",\r
+ /* 3 */ "101100000",\r
+ /* 4 */ "000110001",\r
+ /* 5 */ "100110000",\r
+ /* 6 */ "001110000",\r
+ /* 7 */ "000100101",\r
+ /* 8 */ "100100100",\r
+ /* 9 */ "001100100",\r
+ /* A */ "100001001",\r
+ /* B */ "001001001",\r
+ /* C */ "101001000",\r
+ /* D */ "000011001",\r
+ /* E */ "100011000",\r
+ /* F */ "001011000",\r
+ /* G */ "000001101",\r
+ /* H */ "100001100",\r
+ /* I */ "001001100",\r
+ /* J */ "000011100",\r
+ /* K */ "100000011",\r
+ /* L */ "001000011",\r
+ /* M */ "101000010",\r
+ /* N */ "000010011",\r
+ /* O */ "100010010",\r
+ /* P */ "001010010",\r
+ /* Q */ "000000111",\r
+ /* R */ "100000110",\r
+ /* S */ "001000110",\r
+ /* T */ "000010110",\r
+ /* U */ "110000001",\r
+ /* V */ "011000001",\r
+ /* W */ "111000000",\r
+ /* X */ "010010001",\r
+ /* Y */ "110010000",\r
+ /* Z */ "011010000",\r
+ /* - */ "010000101",\r
+ /* . */ "110000100",\r
+ /* SP */ "011000100",\r
+ /* * */ "010010100",\r
+ /* $ */ "010101000",\r
+ /* / */ "010100010",\r
+ /* + */ "010001010",\r
+ /* % */ "000101010" \r
+ );\r
+ }\r
+ \r
+ function GetCharIndex ($char)\r
+ {\r
+ for ($i=0;$i<44;$i++) {\r
+ if ($this->mChars[$i] == $char)\r
+ return $i;\r
+ }\r
+ return -1;\r
+ }\r
+ \r
+ function GetSize($xres)\r
+ {\r
+ $len = strlen($this->mValue);\r
+ \r
+ if ($len == 0) {\r
+ $this->mError = "Null value";\r
+ __DEBUG__("GetRealSize: null barcode value");\r
+ return false;\r
+ }\r
+ \r
+ for ($i=0;$i<$len;$i++) {\r
+ if ($this->GetCharIndex($this->mValue[$i]) == -1 || $this->mValue[$i] == '*') {\r
+ /* The asterisk is only used as a start and stop code */\r
+ $this->mError = "C39 not include the char '".$this->mValue[$i]."'";\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ /* Start, Stop is 010010100 == '*' */\r
+ $StartSize = BCD_C39_NARROW_BAR * $xres * 6 + BCD_C39_WIDE_BAR * $xres * 3;\r
+ $StopSize = BCD_C39_NARROW_BAR * $xres * 6 + BCD_C39_WIDE_BAR * $xres * 3;\r
+ $CharSize = BCD_C39_NARROW_BAR * $xres * 6 + BCD_C39_WIDE_BAR * $xres * 3; /* Same for all chars */\r
+ \r
+ return $CharSize * $len + $StarSize + $StopSize + /* Space between chars */ BCD_C39_NARROW_BAR * $xres * ($len-1);\r
+ }\r
+ \r
+ function DrawStart($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Start code is '*' */\r
+ $narrow = BCD_C39_NARROW_BAR * $xres;\r
+ $wide = BCD_C39_WIDE_BAR * $xres;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize);\r
+ $DrawPos += $narrow;\r
+ $DrawPos += $wide;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize);\r
+ $DrawPos += $narrow;\r
+ $DrawPos += $narrow;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize);\r
+ $DrawPos += $wide;\r
+ $DrawPos += $narrow;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize);\r
+ $DrawPos += $wide;\r
+ $DrawPos += $narrow;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $narrow, $ySize);\r
+ $DrawPos += $narrow;\r
+ $DrawPos += $narrow; /* Space between chars */\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawStop($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Stop code is '*' */\r
+ $narrow = BCD_C39_NARROW_BAR * $xres;\r
+ $wide = BCD_C39_WIDE_BAR * $xres;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize);\r
+ $DrawPos += $narrow;\r
+ $DrawPos += $wide;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize);\r
+ $DrawPos += $narrow;\r
+ $DrawPos += $narrow;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize);\r
+ $DrawPos += $wide;\r
+ $DrawPos += $narrow;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize);\r
+ $DrawPos += $wide;\r
+ $DrawPos += $narrow;\r
+ $this->DrawSingleBar($DrawPos, $yPos, $narrow, $ySize);\r
+ $DrawPos += $narrow;\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawObject ($xres)\r
+ {\r
+ $len = strlen($this->mValue);\r
+ \r
+ $narrow = BCD_C39_NARROW_BAR * $xres;\r
+ $wide = BCD_C39_WIDE_BAR * $xres; \r
+ \r
+ if (($size = $this->GetSize($xres))==0) {\r
+ __DEBUG__("GetSize: failed");\r
+ return false;\r
+ } \r
+ \r
+ $cPos = 0;\r
+ if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2);\r
+ else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size;\r
+ else $sPos = 0; \r
+ \r
+ /* Total height of bar code -Bars only- */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont);\r
+ else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2;\r
+ \r
+ /* Draw text */ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) {\r
+ if ($this->mStyle & BCS_STRETCH_TEXT) {\r
+ for ($i=0;$i<$len;$i++) {\r
+ $this->DrawChar($this->mFont, $sPos+($narrow*6+$wide*3)+($size/$len)*$i,\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]);\r
+ } \r
+ } else {/* Center */\r
+ $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue);\r
+ $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+($narrow*6+$wide*3),\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue);\r
+ } \r
+ } \r
+ \r
+ $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); \r
+ do { \r
+ $c = $this->GetCharIndex($this->mValue[$cPos]);\r
+ $cset = $this->mCharSet[$c]; \r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[0] == '0') ? $narrow : $wide , $ysize);\r
+ $DrawPos += ($cset[0] == '0') ? $narrow : $wide;\r
+ $DrawPos += ($cset[1] == '0') ? $narrow : $wide;\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[2] == '0') ? $narrow : $wide , $ysize);\r
+ $DrawPos += ($cset[2] == '0') ? $narrow : $wide;\r
+ $DrawPos += ($cset[3] == '0') ? $narrow : $wide;\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[4] == '0') ? $narrow : $wide , $ysize);\r
+ $DrawPos += ($cset[4] == '0') ? $narrow : $wide;\r
+ $DrawPos += ($cset[5] == '0') ? $narrow : $wide;\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[6] == '0') ? $narrow : $wide , $ysize);\r
+ $DrawPos += ($cset[6] == '0') ? $narrow : $wide;\r
+ $DrawPos += ($cset[7] == '0') ? $narrow : $wide;\r
+ $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[8] == '0') ? $narrow : $wide , $ysize);\r
+ $DrawPos += ($cset[8] == '0') ? $narrow : $wide;\r
+ $DrawPos += $narrow; /* Space between chars */\r
+ $cPos++; \r
+ } while ($cPos<$len);\r
+ $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres);\r
+ return true;\r
+ }\r
+ }\r
+?>\r
--- /dev/null
+<?\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ \r
+ Version 0.0.7a 2001-04-01 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+\r
+define(__DEBUG_HOST__, "localhost");\r
+define(__DEBUG_PORT__, "9999");\r
+\r
+define(__TRACE_HOST__, "localhost");\r
+define(__TRACE_PORT__, "9999");\r
+\r
+define(__TIMEOUT__, 3);\r
+ \r
+function __TRACE__ ($str) {\r
+if (__TRACE_ENABLED__) {\r
+ $errno = 0;\r
+ $errstr = "no error";\r
+ \r
+ $fp = @fsockopen(__TRACE_HOST__, __TRACE_PORT__, &$errno, &$errstr, __TIMEOUT__);\r
+ \r
+ if ($fp)\r
+ {\r
+ @fputs($fp, $str);\r
+ @fclose($fp);\r
+ }\r
+ }\r
+} \r
+ \r
+function __DEBUG__ ($str) {\r
+if (__DEBUG_ENABLED__) {\r
+ $errno = 0;\r
+ $errstr = "no error";\r
+ \r
+ $fp = @fsockopen(__DEBUG_HOST__, __DEGUB_PORT__, &$errno, &$errstr, __TIMEOUT__);\r
+ \r
+ if ($fp)\r
+ {\r
+ @fputs($fp, $str);\r
+ @fclose($fp);\r
+ } \r
+ }\r
+}\r
--- /dev/null
+<html>\r
+<head>\r
+ <title>Barcode Download</title>\r
+</head>\r
+<body bgcolor="#FFFFCC">\r
+<table align='center'>\r
+ <tr>\r
+ <td><a href="home.php"><img src="home.png" border="0"></a></td>\r
+ <td><a href="sample.php"><img src="sample.png" border="0"></a></td>\r
+ <td><img src="download.png" border="1"></td>\r
+ </tr>\r
+</table>\r
+<br><br>\r
+<table align="center" width="640">\r
+ <tr><td> This is alpha release, report any problem to <a href="mailto:barcode@mribti.com">barcode@mribti.com</a></td></tr>\r
+ <tr><td><br></td></tr>\r
+ <tr><td align="left">Main site</td></tr>\r
+ <tr><td align="left"><a href="barcode-0.0.8a.tar.gz">barcode-0.0.8a.tar.gz(45kb)</a></td></tr>\r
+ <tr><td align="left"><a href="barcode-0.0.8a.zip">barcode-0.0.8a.zip(47kb)</a></td></tr>\r
+ <tr><td align="left">Mirror site (fast)</td></tr>\r
+ <tr><td align="left"><a href="http://www.aramsoft.com/barcode/barcode-0.0.8a.tar.gz">barcode-0.0.8a.tar.gz(45kb)</a></td></tr>\r
+ <tr><td align="left"><a href="http://www.aramsoft.com/barcode/barcode-0.0.8a.zip">barcode-0.0.8a.zip(47kb)</a></td></tr>\r
+ <tr><td><br></td></tr>\r
+</table>\r
+<br><br>\r
+<table align="center" width="640">\r
+ <tr>\r
+ <td colspan="3" align="center">CHANGES LOG</td>\r
+ </tr>\r
+ <tr>\r
+ <td colspan="3"><br></td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-03-25</td>\r
+ <td width="20%" align="left">v0.0.1a</td>\r
+ <td width="60%">Initial release.</td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-03-26</td>\r
+ <td width="20%" align="left">v0.0.2a</td>\r
+ <td width="60%">Error checking has been added, and there are minor bugfixes</td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-03-26</td>\r
+ <td width="20%" align="left">v0.0.3a</td>\r
+ <td width="60%">Add Code 39 support</td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-03-27</td>\r
+ <td width="20%" align="left">v0.0.4a</td>\r
+ <td width="60%">Minor feature enhancements, new output styles. </td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-03-28</td>\r
+ <td width="20%" align="left">v0.0.5a</td>\r
+ <td width="60%">Add font control, minor bugfixes. </td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-03-29</td>\r
+ <td width="20%" align="left">v0.0.6a</td>\r
+ <td width="60%">Bugfix in Code 39 render, thanks to Henry Bland. </td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-04-01</td>\r
+ <td width="20%" align="left">v0.0.7a</td>\r
+ <td width="60%">Add support for Code 128-A and Code 128-B</td>\r
+ </tr>\r
+ <tr>\r
+ <td width="20%" align="left">2001-08-03</td>\r
+ <td width="20%" align="left">v0.0.8a</td>\r
+ <td width="60%">Now support Code 128-C, thanks to Sam Michaels. </td>\r
+ </tr>\r
+</table>\r
+</body>\r
+</html>\r
--- /dev/null
+<html>\r
+<head>\r
+ <title>Barcode home page</title>\r
+</head>\r
+<body bgcolor="#FFFFCC">\r
+<table align='center'>\r
+ <tr>\r
+ <td><img src="home.png" border="1"></td>\r
+ <td><a href="sample.php"><img src="sample.png" border="0"></a></td>\r
+ <td><a href="download.php"><img src="download.png" border="0"></a></td>\r
+ </tr>\r
+</table>\r
+<br><br>\r
+<table align="center" width="640">\r
+ <tr><td> Barcode is a small implementation of a barcode rendering class using the <a target="_blank" href="http://www.php.net">PHP</a> language and <a target="_blank" href="http://www.boutell.com/gd/">GD graphics library</a>.</td></tr>\r
+ <tr><td><br></td></tr>\r
+ <tr><td><br></td></tr>\r
+ <tr><td>For any question, please send an email to <a href="mailto:barcode@mribti.com">barcode@mribti.com</a></td></tr>\r
+</table>\r
+<br><br>\r
+<table align="center">\r
+<tr>\r
+ <td align="center"><A href="http://sourceforge.net"><IMG src="http://sourceforge.net/sflogo.php?group_id=25228" width="88" height="31" border="0" alt="SourceForge Logo"></A></td>
+</tr>\r
+<tr>\r
+ <td></td>\r
+</tr>\r
+</table>\r
+</body>\r
+</html>
--- /dev/null
+<?php\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ \r
+ Version 0.0.7a 2001-04-01 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+ \r
+ /* \r
+ render for Interleaved 2 of 5 \r
+ Interleaved 2 of 5 is a numeric only bar code with a optional check number.\r
+ */\r
+ \r
+ class I25Object extends BarcodeObject {\r
+ var $mCharSet;\r
+ function I25Object($Width, $Height, $Style, $Value)\r
+ {\r
+ $this->BarcodeObject($Width, $Height, $Style);\r
+ $this->mValue = $Value;\r
+ $this->mCharSet = array\r
+ ( \r
+ /* 0 */ "00110",\r
+ /* 1 */ "10001",\r
+ /* 2 */ "01001",\r
+ /* 3 */ "11000",\r
+ /* 4 */ "00101",\r
+ /* 5 */ "10100",\r
+ /* 6 */ "01100",\r
+ /* 7 */ "00011",\r
+ /* 8 */ "10010",\r
+ /* 9 */ "01010" \r
+ );\r
+ }\r
+ \r
+ function GetSize($xres)\r
+ {\r
+ $len = strlen($this->mValue);\r
+ \r
+ if ($len == 0) {\r
+ $this->mError = "Null value";\r
+ __DEBUG__("GetRealSize: null barcode value");\r
+ return false;\r
+ }\r
+ \r
+ for ($i=0;$i<$len;$i++) {\r
+ if ((ord($this->mValue[$i])<48) || (ord($this->mValue[$i])>57)) {\r
+ $this->mError = "I25 is numeric only";\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ if (($len%2) != 0) {\r
+ $this->mError = "The length of barcode value must be even";\r
+ __DEBUG__("GetSize: failed I25 requiremente");\r
+ return false;\r
+ } \r
+ $StartSize = BCD_I25_NARROW_BAR * 4 * $xres;\r
+ $StopSize = BCD_I25_WIDE_BAR * $xres + 2 * BCD_I25_NARROW_BAR * $xres;\r
+ $cPos = 0;\r
+ $sPos = 0;\r
+ do { \r
+ $c1 = $this->mValue[$cPos];\r
+ $c2 = $this->mValue[$cPos+1];\r
+ $cset1 = $this->mCharSet[$c1];\r
+ $cset2 = $this->mCharSet[$c2];\r
+ \r
+ for ($i=0;$i<5;$i++) {\r
+ $type1 = ($cset1[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres);\r
+ $type2 = ($cset2[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres);\r
+ $sPos += ($type1 + $type2);\r
+ }\r
+ $cPos+=2;\r
+ } while ($cPos<$len);\r
+ \r
+ return $sPos + $StarSize + $StopSize;\r
+ }\r
+ \r
+ function DrawStart($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Start code is "0000" */\r
+ $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_NARROW_BAR * $xres , $ySize);\r
+ $DrawPos += BCD_I25_NARROW_BAR * $xres;\r
+ $DrawPos += BCD_I25_NARROW_BAR * $xres;\r
+ $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_NARROW_BAR * $xres , $ySize);\r
+ $DrawPos += BCD_I25_NARROW_BAR * $xres;\r
+ $DrawPos += BCD_I25_NARROW_BAR * $xres;\r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawStop($DrawPos, $yPos, $ySize, $xres)\r
+ { /* Stop code is "100" */\r
+ $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_WIDE_BAR * $xres , $ySize);\r
+ $DrawPos += BCD_I25_WIDE_BAR * $xres;\r
+ $DrawPos += BCD_I25_NARROW_BAR * $xres;\r
+ $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_NARROW_BAR * $xres , $ySize);\r
+ $DrawPos += BCD_I25_NARROW_BAR * $xres; \r
+ return $DrawPos;\r
+ }\r
+ \r
+ function DrawObject ($xres)\r
+ {\r
+ $len = strlen($this->mValue);\r
+ \r
+ if (($size = $this->GetSize($xres))==0) {\r
+ __DEBUG__("GetSize: failed");\r
+ return false;\r
+ } \r
+ \r
+ $cPos = 0;\r
+ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont);\r
+ else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2;\r
+ \r
+ if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2);\r
+ else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size;\r
+ else $sPos = 0;\r
+ \r
+ if ($this->mStyle & BCS_DRAW_TEXT) {\r
+ if ($this->mStyle & BCS_STRETCH_TEXT) {\r
+ /* Stretch */\r
+ for ($i=0;$i<$len;$i++) {\r
+ $this->DrawChar($this->mFont, $sPos+BCD_I25_NARROW_BAR*4*$xres+($size/$len)*$i, \r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET , $this->mValue[$i]);\r
+ } \r
+ }else {/* Center */\r
+ $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue);\r
+ $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(BCD_I25_NARROW_BAR*4*$xres),\r
+ $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue);\r
+ } \r
+ } \r
+ \r
+ $sPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1, $ysize, $xres); \r
+ do { \r
+ $c1 = $this->mValue[$cPos];\r
+ $c2 = $this->mValue[$cPos+1];\r
+ $cset1 = $this->mCharSet[$c1];\r
+ $cset2 = $this->mCharSet[$c2];\r
+ \r
+ for ($i=0;$i<5;$i++) {\r
+ $type1 = ($cset1[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres);\r
+ $type2 = ($cset2[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres);\r
+ $this->DrawSingleBar($sPos, BCD_DEFAULT_MAR_Y1, $type1 , $ysize);\r
+ $sPos += ($type1 + $type2);\r
+ } \r
+ $cPos+=2;\r
+ } while ($cPos<$len);\r
+ $sPos = $this->DrawStop($sPos, BCD_DEFAULT_MAR_Y1, $ysize, $xres);\r
+ return true;\r
+ }\r
+ }\r
+?>\r
--- /dev/null
+<?php\r
+/*\r
+Barcode Render Class for PHP using the GD graphics library \r
+Copyright (C) 2001 Karim Mribti\r
+ \r
+ Version 0.0.7a 2001-04-01 \r
+ \r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+ \r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+General Public License for more details.\r
+ \r
+You should have received a copy of the GNU General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ \r
+Copy of GNU General Public License at: http://www.gnu.org/copyleft/gpl.txt\r
+ \r
+Source code home page: http://www.mribti.com/barcode/\r
+Contact author at: barcode@mribti.com\r
+*/\r
+ \r
+ define (__TRACE_ENABLED__, false);\r
+ define (__DEBUG_ENABLED__, false);\r
+ \r
+ require("barcode.php"); \r
+ require("i25object.php");\r
+ require("c39object.php");\r
+ require("c128aobject.php");\r
+ require("c128bobject.php");\r
+ require("c128cobject.php");\r
+ \r
+ if (!isset($style)) $style = BCD_DEFAULT_STYLE;\r
+ if (!isset($width)) $width = BCD_DEFAULT_WIDTH;\r
+ if (!isset($height)) $height = BCD_DEFAULT_HEIGHT;\r
+ if (!isset($xres)) $xres = BCD_DEFAULT_XRES;\r
+ if (!isset($font)) $font = BCD_DEFAULT_FONT;\r
+ \r
+ switch ($type)\r
+ {\r
+ case "I25":\r
+ $obj = new I25Object($width, $height, $style, $code);\r
+ break;\r
+ case "C39":\r
+ $obj = new C39Object($width, $height, $style, $code);\r
+ break;\r
+ case "C128A":\r
+ $obj = new C128AObject($width, $height, $style, $code);\r
+ break;\r
+ case "C128B":\r
+ $obj = new C128BObject($width, $height, $style, $code);\r
+ break;\r
+ case "C128C":\r
+ $obj = new C128CObject($width, $height, $style, $code);\r
+ break;\r
+ default:\r
+ echo "Need bar code type ex. C39";\r
+ $obj = false;\r
+ }\r
+ \r
+ if ($obj) {\r
+ $obj->SetFont($font); \r
+ $obj->DrawObject($xres);\r
+ $obj->FlushObject();\r
+ $obj->DestroyObject();\r
+ unset($obj); /* clean */\r
+ }\r
+?>\r
--- /dev/null
+<?\r
+include("home.php");\r
+?>
\ No newline at end of file
--- /dev/null
+<html>\r
+<head>\r
+ <title>Barcode Sample</title>\r
+</head>\r
+<body bgcolor="#FFFFCC">\r
+<table align='center'>\r
+ <tr>\r
+ <td><a href="home.php"><img src="home.png" border="0"></a></td>\r
+ <td><img src="sample.png" border="1"></td>\r
+ <td><a href="download.php"><img src="download.png" border="0"></a></td>\r
+ </tr>\r
+</table>\r
+<br><br>\r
+<? \r
+ define (__TRACE_ENABLED__, false);\r
+ define (__DEBUG_ENABLED__, false);\r
+ \r
+ require("barcode.php"); \r
+ require("i25object.php");\r
+ require("c39object.php");\r
+ require("c128aobject.php");\r
+ require("c128bobject.php");\r
+ require("c128cobject.php");\r
+ \r
+/* Default value */\r
+if (!isset($output)) $output = "png"; \r
+if (!isset($barcode)) $barcode = "0123456789";\r
+if (!isset($type)) $type = "I25";\r
+if (!isset($width)) $width = "460";\r
+if (!isset($height)) $height = "120";\r
+if (!isset($xres)) $xres = "2";\r
+if (!isset($font)) $font = "5";\r
+/*********************************/ \r
+ \r
+if (isset($barcode) && strlen($barcode)>0) { \r
+ $style = BCS_ALIGN_CENTER; \r
+ $style |= ($output == "png" ) ? BCS_IMAGE_PNG : 0; \r
+ $style |= ($output == "jpeg") ? BCS_IMAGE_JPEG : 0; \r
+ $style |= ($border == "on" ) ? BCS_BORDER : 0; \r
+ $style |= ($drawtext== "on" ) ? BCS_DRAW_TEXT : 0; \r
+ $style |= ($stretchtext== "on" ) ? BCS_STRETCH_TEXT : 0; \r
+ $style |= ($negative== "on" ) ? BCS_REVERSE_COLOR : 0; \r
+ \r
+ switch ($type)\r
+ {\r
+ case "I25":\r
+ $obj = new I25Object(250, 120, $style, $barcode);\r
+ break;\r
+ case "C39":\r
+ $obj = new C39Object(250, 120, $style, $barcode);\r
+ break;\r
+ case "C128A":\r
+ $obj = new C128AObject(250, 120, $style, $barcode);\r
+ break;\r
+ case "C128B":\r
+ $obj = new C128BObject(250, 120, $style, $barcode);\r
+ break;\r
+ case "C128C":\r
+ $obj = new C128CObject(250, 120, $style, $barcode);\r
+ break;\r
+ default:\r
+ $obj = false;\r
+ }\r
+ if ($obj) {\r
+ if ($obj->DrawObject($xres)) {\r
+ echo "<table align='center'><tr><td><img src='./image.php?code=".$barcode."&style=".$style."&type=".$type."&width=".$width."&height=".$height."&xres=".$xres."&font=".$font."'></td></tr></table>";\r
+ } else echo "<table align='center'><tr><td><font color='#FF0000'>".($obj->GetError())."</font></td></tr></table>";\r
+ }\r
+}\r
+?>\r
+<br>\r
+<form method="post" action="sample.php">\r
+<table align="center" border="1" cellpadding="1" cellspacing="1">\r
+ <tr>\r
+ <td bgcolor="#EFEFEF"><b>Type</b></td>\r
+ <td><select name="type" style="WIDTH: 260px" size="1">\r
+ <option value="I25" <?=($type=="I25" ? "selected" : " ")?>>Interleaved 2 of 5\r
+ <option value="C39" <?=($type=="C39" ? "selected" : " ")?>>Code 39\r
+ <option value="C128A" <?=($type=="C128A" ? "selected" : " ")?>>Code 128-A\r
+ <option value="C128B" <?=($type=="C128B" ? "selected" : " ")?>>Code 128-B\r
+ <option value="C128C" <?=($type=="C128C" ? "selected" : " ")?>>Code 128-C</select></td>\r
+ </tr>\r
+ <tr>\r
+ <td bgcolor="#EFEFEF"><b>Output</b></td>\r
+ <td><select name="output" style="WIDTH: 260px" size="1">\r
+ <option value="png" <?=($output=="png" ? "selected" : " ")?>>Portable Network Graphics (PNG)\r
+ <option value="jpeg" <?=($output=="jpeg" ? "selected" : " ")?>>Joint Photographic Experts Group(JPEG)</select></td>\r
+ </tr>\r
+ <tr>\r
+ <td rowspan="4" bgcolor="#EFEFEF"><b>Styles</b></td>\r
+ <td rowspan="1"><input type="Checkbox" name="border" <?=($border=="on" ? "CHECKED" : " ")?>>Draw border</td>\r
+ </tr>\r
+ <tr>\r
+ <td><input type="Checkbox" name="drawtext" <?=($drawtext=="on" ? "CHECKED" : " ")?>>Draw value text</td>\r
+ </tr>\r
+ <tr>\r
+ <td><input type="Checkbox" name="stretchtext" <?=($stretchtext=="on" ? "CHECKED" : " ")?>>Stretch text</td>\r
+ </tr>\r
+ <tr>\r
+ <td><input type="Checkbox" name="negative" <?=($negative=="on" ? "CHECKED" : " ")?>>Negative (White on black)</td>\r
+ </tr>\r
+ <tr>\r
+ <td rowspan="2" bgcolor="#EFEFEF"><b>Size</b></td>\r
+ <td rowspan="1">Width: <input type="text" size="6" maxlength="3" name="width" value="<?=$width?>"></td>\r
+ </tr>\r
+ <tr>\r
+ <td>Height: <input type="text" size="6" maxlength="3" name="height" value="<?=$height?>"></td>\r
+ </tr>\r
+ <tr>\r
+ <td bgcolor="#EFEFEF"><b>Xres</b></td>\r
+ <td>\r
+ <input type="Radio" name="xres" value="1" <?=($xres=="1" ? "CHECKED" : " ")?>>1 \r
+ <input type="Radio" name="xres" value="2" <?=($xres=="2" ? "CHECKED" : " ")?>>2 \r
+ <input type="Radio" name="xres" value="3" <?=($xres=="3" ? "CHECKED" : " ")?>>3 \r
+ </td>\r
+ </tr>\r
+ <tr>\r
+ <td bgcolor="#EFEFEF"><b>Text Font</b></td>\r
+ <td>\r
+ <input type="Radio" name="font" value="1" <?=($font=="1" ? "CHECKED" : " ")?>>1 \r
+ <input type="Radio" name="font" value="2" <?=($font=="2" ? "CHECKED" : " ")?>>2 \r
+ <input type="Radio" name="font" value="3" <?=($font=="3" ? "CHECKED" : " ")?>>3 \r
+ <input type="Radio" name="font" value="4" <?=($font=="4" ? "CHECKED" : " ")?>>4 \r
+ <input type="Radio" name="font" value="5" <?=($font=="5" ? "CHECKED" : " ")?>>5 \r
+ </td>\r
+ </tr>\r
+ <tr>\r
+ <td bgcolor="#EFEFEF"><b>Value</b></td>\r
+ <td><input type="Text" size="24" name="barcode" style="WIDTH: 260px" value="<?=$barcode?>"></td>\r
+ </tr>\r
+ <tr>\r
+ </tr>\r
+ <tr>\r
+ <td colspan="2" align="center"><input type="Submit" name="Submit" value="Show"></td>\r
+ </tr>\r
+</table>\r
+</form>\r
+</body>\r
+</html>\r
--- /dev/null
+<html>
+<head>
+ <style type="text/css">
+ @import '/js/dojo/dojo/resources/dojo.css';
+ @import '/js/dojo/dijit/themes/tundra/tundra.css';
+ @import '/js/dojo/dojox/grid/resources/Grid.css';
+
+ #mainhead{
+ height:120px;
+ background-color:#1d57aa;
+ }
+
+ #wrap{
+ width:950px;
+ margin-left: auto;
+ margin-right: auto;
+ border:1px solid #8396d3;
+ min-height:750px;
+ background-color:white;
+ margin-top:0px;
+
+ }
+
+ .mainNav{
+ text-decoration:none;
+ color:#8396d3;
+ padding-right:1em;
+ }
+
+ .thispage{
+ color:white;
+ }
+
+ a.mainNav:hover{
+ color:white;
+ text-decoration:none;
+ }
+
+ #subhead{
+ background-color:#00396a;
+ padding-left:30px;
+ height:30px;
+ line-height:30px;
+ font-size:1em;
+ }
+
+ </style>
+
+ <script language='javascript' type="text/javascript">
+
+ var djConfig = {
+ AutoIDL: ['aou','aout','pgt','ahr','au','ac','acp','acn','ahtc','atc'],
+ parseOnLoad: true,
+ isDebug: false
+ }, lang, bidi;
+
+ </script>
+
+ <script type="text/javascript" src='/opac/common/js/CGI.js'></script>
+ <script type="text/javascript" src='/js/dojo/dojo/dojo.js'></script>
+ <script type="text/javascript" src='/js/dojo/dojo/openils_dojo.js'></script>
+ <script type="text/javascript" src='/js/dojo/opensrf/opensrf.js'></script>
+ <script type="text/javascript" src='/js/dojo/opensrf/md5.js'></script>
+ <script type='text/javascript' src='../js/circ_tracker.js'></script>
+
+</head>
+<body class="tundra" style="background-color:#b7bbc3;">
+
+ <div id="wrap">
+ <div id="mainhead">
+ <a href="http://fulfillment-ill.org" target="_blank"><img src="/images/FulfillmentHomePageBanner.png" border="0" alt="Open Source Integrated Interlibrary Lending System" /></a>
+ </div>
+ <div id="subhead">
+ <a href="hold_tracker.xml" class="mainNav">Manage ILL Requests</a>
+ <a href="circ_tracker.xml" class="thispage mainNav">Manage ILL Transactions</a>
+ <a href="record_mgmt.xml" class="mainNav">Bibliographic Record Management</a>
+ </div>
+
+ <div style="height: 20px;"/>
+
+ <span style="margin-left: 10px;">Choose a location</span>
+ <div dojoType='openils.widget.OrgUnitFilteringSelect'
+ onChange="resetLocation(this.getValue())"
+ jsId='circLocation'
+ searchAttr='name'
+ labelAttr='name'>
+ </div>
+ <br/>
+ <br/>
+ <br/>
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client" style='min-height:600px; height:80%'>
+ <div dojoType="dijit.layout.TabContainer">
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="Single Scan" style="height:100%"
+ selected='true'>
+ <script type='dojo/connect' event='onShow'>itemScanBox.focus()</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <h1>Check In or Recall ILLs</h1>
+ <br/>
+ Scan Item: <input type="text" dojoType="dijit.form.TextBox" jsId="itemScanBox" trim="true" id="itemScanBox">
+ <script type='dojo/connect' event='startup'>
+ dojo.connect(
+ itemScanBox,
+ 'onKeyDown',
+ function (e) {
+ if(e.keyCode != dojo.keys.ENTER) return;
+ processCopyBarcodeAction(itemScanBox.getValue(),{x:false});
+ itemScanBox.setValue('');
+ }
+ );
+ </script>
+ </input>
+ <br/>
+ <br/>
+ <br/>
+
+ <table class="dojoxGridRowTable dojoxGridHeader dojoxMasterGridHeader" style="border-collapse:collapse; width:100%;">
+ <caption style="text-align:right; caption-side:bottom;"><a href="javascript:printAllCircStatDetail()">Print ALL</a></caption>
+ <tbody id="statusTarget">
+ <tr style="height:2em;">
+ <th style="font-weight:bold;" class="action dojoxGridCell">Action</th>
+ <th style="font-weight:bold;" class="item dojoxGridCell">Item</th>
+ <th style="font-weight:bold;" class="patron dojoxGridCell">Patron</th>
+ <th style="font-weight:bold;" class="location dojoxGridCell">Location</th>
+ <th style="font-weight:bold;" class="receipt dojoxGridCell">Print...</th>
+ </tr>
+ </tbody>
+ <tbody style="display:none; visiblility:hidden;">
+ <tr class="statusTemplate">
+ <td class="action dojoxGridCell"></td>
+ <td class="item dojoxGridCell"></td>
+ <td class="patron dojoxGridCell"></td>
+ <td class="location dojoxGridCell"></td>
+ <td class="dojoxGridCell">
+ <a class="receipt">ILL Receipt</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ </div>
+ </div>
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="ILLs To My patrons" style="height:100%">
+ <script type='dojo/connect' event='onShow'>resetLocation(circLocation.getValue(),{tmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="toMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['id', 'target_copy', 'xact_start','due_date', 'usr','duration','renewal_remaining']"
+ suppressFields="['xact_finish','unrecovered','circ_lib','circ_staff','checkin_staff','checkin_lib','stop_fines_time','checkin_time','phone_renewal','desk_renewal','opac_renewal','duration_rule','recurring_fine_rule','max_fine_rule','stop_fines','workstation','checkin_workstation','checkin_scan_time','parent_circ','create_time','fine_interval','max_fine','recurring_fine']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='circ'
+ showPaginator='false'
+ showSequenceFields="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <th field="target_copy" get="fetchCopy" formatter="formatCopyBarcode"></th>
+ <th field="usr" get="fetchCard" formatter="formatBarcode"></th>
+ <th name="Actions" field="id" get="fetchCirc" formatter="formatLocalActions"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="ILLs To Other locations" style="height:100%">
+ <script type='dojo/connect' event='onShow'>resetLocation(circLocation.getValue(),{fmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="fromMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['id', 'target_copy', 'xact_start','due_date', 'circ_lib','duration','renewal_remaining']"
+ suppressFields="['xact_finish','unrecovered','usr','circ_staff','checkin_staff','checkin_lib','stop_fines_time','checkin_time','phone_renewal','desk_renewal','opac_renewal','duration_rule','recurring_fine_rule','max_fine_rule','stop_fines','workstation','checkin_workstation','checkin_scan_time','parent_circ','create_time','fine_interval','max_fine','recurring_fine']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='circ'
+ showPaginator='false'
+ showSequenceFields="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <th field="target_copy" get="fetchCopy" formatter="formatCopyBarcode"></th>
+ <th field="circ_lib" get="fetchCirclib" formatter="formatCirclib"></th>
+ <th name="Actions" field="id" get="fetchCirc" formatter="formatRemoteActions"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="Return Transits To Me" style="height:100%">
+ <script type='dojo/connect' event='onShow'>resetLocation(circLocation.getValue(),{nhttmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="nonHoldTransitsToMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['id','target_copy', 'source', 'source_send_time']"
+ suppressFields="['persistant_transfer','dest','prev_hop','copy_status','prev_dest','dest_recv_time']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='atc'
+ showSequenceFields="true"
+ showLoadFilter="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <th name="Actions" field="id" get="fetchTransit" formatter="formatIncomingTransitActions"></th>
+ <th field="target_copy" get="fetchCopy" formatter="formatCopyBarcode"></th>
+ <th field="source" get="fetchSource" formatter="formatCirclib"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="Return Transits To Other Locations" style="height:100%">
+ <script type='dojo/connect' event='onShow'>resetLocation(circLocation.getValue(),{nhtfmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="nonHoldTransitsFromMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['target_copy', 'dest', 'source_send_time']"
+ suppressFields="['id','persistant_transfer','source','prev_hop','copy_status','prev_dest','dest_recv_time']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='atc'
+ showSequenceFields="true"
+ showLoadFilter="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <!-- <th name="Actions" field="id" get="fetchTransit" formatter="formatOutgoingTransitActions"></th> -->
+ <th field="target_copy" get="fetchCopy" formatter="formatCopyBarcode"></th>
+ <th field="dest" get="fetchDest" formatter="formatCirclib"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+
+ </div>
+ </div>
+
+ </div>
+ <br/>
+ <br/>
+ <a href="javascript:dojo.cookie('ses', null, {path:'/',expires:-1});location.href=location.href;">Log Out</a>
+</body>
+</html>
--- /dev/null
+<html>
+<head>
+ <style type="text/css">
+ @import '/js/dojo/dojo/resources/dojo.css';
+ @import '/js/dojo/dijit/themes/tundra/tundra.css';
+ @import '/js/dojo/dojox/grid/resources/Grid.css';
+
+ #mainhead{
+ height:120px;
+ background-color:#1d57aa;
+ }
+
+ #wrap{
+ width:950px;
+ margin-left: auto;
+ margin-right: auto;
+ border:1px solid #8396d3;
+ min-height:750px;
+ background-color:white;
+ margin-top:0px;
+
+ }
+
+ .mainNav{
+ text-decoration:none;
+ color:#8396d3;
+ padding-right:1em;
+ }
+
+ .thispage{
+ color:white;
+ }
+
+ a.mainNav:hover{
+ color:white;
+ text-decoration:none;
+ }
+
+ #subhead{
+ background-color:#00396a;
+ padding-left:30px;
+ height:30px;
+ line-height:30px;
+ font-size:1em;
+ }
+
+ </style>
+
+ <script language='javascript' type="text/javascript">
+
+ var djConfig = {
+ AutoIDL: ['aou','aout','pgt','ahr','au','ac','acp','acn','ahtc','atc'],
+ parseOnLoad: true,
+ isDebug: false
+ }, lang, bidi;
+
+ </script>
+
+ <script type="text/javascript" src='/opac/common/js/CGI.js'></script>
+ <script type="text/javascript" src='/js/dojo/dojo/dojo.js'></script>
+ <script type="text/javascript" src='/js/dojo/dojo/openils_dojo.js'></script>
+ <script type="text/javascript" src='/js/dojo/opensrf/opensrf.js'></script>
+ <script type="text/javascript" src='/js/dojo/opensrf/md5.js'></script>
+ <script type='text/javascript' src='../js/hold_tracker.js'></script>
+
+</head>
+<body class="tundra" style="background-color:#b7bbc3;">
+
+ <div id="wrap">
+ <div id="mainhead">
+ <a href="http://fulfillment-ill.org" target="_blank"><img src="/images/FulfillmentHomePageBanner.png" border="0" alt="Open Source Integrated Interlibrary Lending System" /></a>
+ </div>
+ <div id="subhead">
+ <a href="hold_tracker.xml" class="thispage mainNav">Manage ILL Requests</a>
+ <a href="circ_tracker.xml" class="mainNav">Manage ILL Transactions</a>
+ <a href="record_mgmt.xml" class="mainNav">Bibliographic Record Management</a>
+ </div>
+
+ <div style="height: 20px;"/>
+
+ <span style="margin-left: 10px;">Choose a location</span>
+ <div dojoType='openils.widget.OrgUnitFilteringSelect'
+ onChange="resetLocation(this.getValue())"
+ jsId='holdLocation'
+ searchAttr='name'
+ labelAttr='name'>
+ </div>
+ <br/>
+ <br/>
+ <br/>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client" style='min-height:600px; height:80%'>
+ <div dojoType="dijit.layout.TabContainer" style="height:100%">
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="Single Scan"
+ selected='true' style="height:100%">
+ <script type='dojo/connect' event='onShow'>itemScanBox.focus()</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <h1>Capture, Receive or Circulate ILL Requests</h1>
+ <br/>
+ Scan Item: <input type="text" dojoType="dijit.form.TextBox" jsId="itemScanBox" trim="true" id="itemScanBox">
+ <script type='dojo/connect' event='startup'>
+ dojo.connect(
+ itemScanBox,
+ 'onKeyDown',
+ function (e) {
+ if(e.keyCode != dojo.keys.ENTER) return;
+ processCopyBarcodeAction(itemScanBox.getValue());
+ itemScanBox.setValue('');
+ }
+ );
+ </script>
+ </input>
+ <br/>
+ <br/>
+ <br/>
+
+ <table class="dojoxGridRowTable dojoxGridHeader dojoxMasterGridHeader" style="border-collapse:collapse; width:100%;">
+ <caption style="text-align:right; caption-side:bottom;"><a href="javascript:printAllHoldStatDetail()">Print ALL</a></caption>
+ <tbody id="statusTarget">
+ <tr style="height:2em;">
+ <th style="font-weight:bold;" class="direction dojoxGridCell">Direction</th>
+ <th style="font-weight:bold;" class="action dojoxGridCell">Action</th>
+ <th style="font-weight:bold;" class="item dojoxGridCell">Item</th>
+ <th style="font-weight:bold;" class="patron dojoxGridCell">Patron</th>
+ <th style="font-weight:bold;" class="location dojoxGridCell">Location</th>
+ <th style="font-weight:bold;" class="receipt dojoxGridCell">Print...</th>
+ </tr>
+ </tbody>
+ <tbody style="display:none; visiblility:hidden;">
+ <tr class="statusTemplate">
+ <td class="direction dojoxGridCell"></td>
+ <td class="action dojoxGridCell"></td>
+ <td class="item dojoxGridCell"></td>
+ <td class="patron dojoxGridCell"></td>
+ <td class="location dojoxGridCell"></td>
+ <td class="dojoxGridCell">
+ <a class="receipt">ILL Request Receipt</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ </div>
+ </div>
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="ILL Requests By My Patrons" style="height:100%">
+ <script type='dojo/connect' event='onShow'>resetLocation(holdLocation.getValue(),{tmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="toMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['id', 'request_time', 'capture_time', 'usr','frozen','thaw_date']"
+ suppressFields="['cancel_cause','cancel_note','cancel_time','fulfillment_staff','fulfillment_lib','request_lib','selection_ou','selection_depth','holdable_formats','phone_notify','email_notify','shelf_time','shelf_expire_time','fulfillment_time','return_time','checkin_time','target','cut_in_line','expire_time','prev_check_time','mint_condition','hold_type','requestor','pickup_lib']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='ahr'
+ showSequenceFields="true"
+ showLoadFilter="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <th field="current_copy" get="fetchCopy" formatter="formatCopyBarcode"></th>
+ <th field="usr" get="fetchCard" formatter="formatBarcode"></th>
+ <th field="requestor" get="fetchCard" formatter="formatBarcode"></th>
+ <th name="Actions" field="id" get="fetchHold" formatter="formatLocalActions"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="ILL Requests From Other Locations" style="height:100%">
+ <script type='dojo/connect' event='onShow'>resetLocation(holdLocation.getValue(),{fmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="fromMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['id', 'request_time', 'capture_time', 'pickup_lib','frozen','thaw_date']"
+ suppressFields="['cancel_cause','cancel_note','cancel_time','fulfillment_staff','fulfillment_lib','request_lib','selection_ou','selection_depth','holdable_formats','phone_notify','email_notify','shelf_time','shelf_expire_time','fulfillment_time','return_time','checkin_time','target','cut_in_line','expire_time','prev_check_time','mint_condition','hold_type','requestor','usr']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='ahr'
+ showSequenceFields="true"
+ showLoadFilter="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <th field="pickup_lib" get="fetchCirclib" formatter="formatCirclib"></th>
+ <th field="requestor" get="fetchCard" formatter="formatBarcode"></th>
+ <th field="usr" get="fetchCard" formatter="formatBarcode"></th>
+ <th name="Actions" field="id" get="fetchHold" formatter="formatRemoteActions"></th>
+ <th field="current_copy" get="fetchCopy" formatter="formatCopyBarcode"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="ILL Transits From Me" style="height:100%">
+ <script type='dojo/connect' event='onShow'>resetLocation(holdLocation.getValue(),{htfmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="holdTransitFromMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['id','target_copy', 'dest', 'source_send_time']"
+ suppressFields="['persistant_transfer','source','prev_hop','copy_status','prev_dest','hold', 'dest_recv_time']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='ahtc'
+ showSequenceFields="true"
+ showLoadFilter="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <th name="Actions" field="id" get="fetchTransit" formatter="formatOutgoingTransitActions"></th>
+ <th field="target_copy" get="fetchTargetCopy" formatter="formatCopyBarcode"></th>
+ <th field="dest" get="fetchDest" formatter="formatCirclib"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="ILL Transits From Other Locations">
+ <script type='dojo/connect' event='onShow'>resetLocation(holdLocation.getValue(),{httmg:true})</script>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <table jsId="holdTransitToMeGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['id','target_copy', 'source', 'source_send_time']"
+ suppressFields="['persistant_transfer','dest','prev_hop','copy_status','prev_dest','hold', 'dest_recv_time']"
+ query="{id: '*'}"
+ defaultCellWidth='"auto"'
+ fmClass='ahtc'
+ showSequenceFields="true"
+ showLoadFilter="true"
+ editOnEnter='false'>
+ <thead>
+ <tr>
+ <th name="Actions" field="id" get="fetchTransit" formatter="formatIncomingTransitActions"></th>
+ <th field="target_copy" get="fetchTargetCopy" formatter="formatCopyBarcode"></th>
+ <th field="source" get="fetchSource" formatter="formatCirclib"></th>
+ </tr>
+ </thead>
+ </table>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ </div>
+ <br/>
+ <br/>
+ <a href="javascript:dojo.cookie('ses', null, {path:'/',expires:-1});location.href=location.href;">Log Out</a>
+</body>
+</html>
--- /dev/null
+<html>
+<head>
+ <style type="text/css">
+
+ @import '/js/dojo/dojo/resources/dojo.css';
+ @import '/js/dojo/dijit/themes/tundra/tundra.css';
+ @import '/js/dojo/dojox/grid/resources/Grid.css';
+
+ #mainhead{
+ height:120px;
+ background-color:#1d57aa;
+ }
+
+ #wrap{
+ width:950px;
+ margin-left: auto;
+ margin-right: auto;
+ border:1px solid #8396d3;
+ min-height:750px;
+ background-color:white;
+ margin-top:0px;
+
+ }
+
+ .mainNav{
+ text-decoration:none;
+ color:#8396d3;
+ padding-right:1em;
+ }
+
+ .thispage{
+ color:white;
+ }
+
+ a.mainNav:hover{
+ color:white;
+ text-decoration:none;
+ }
+
+ #subhead{
+ background-color:#00396a;
+ padding-left:30px;
+ height:30px;
+ line-height:30px;
+ font-size:1em;
+ }
+
+ </style>
+
+ <script language='javascript' type="text/javascript">
+
+ var djConfig = {
+ AutoIDL: ['aou','aout','pgt','ahr','au','ac','acp','acn'],
+ parseOnLoad: true,
+ isDebug: false
+ }, lang, bidi;
+
+ </script>
+
+ <script type="text/javascript" src='/opac/common/js/CGI.js'></script>
+ <script type="text/javascript" src='/js/dojo/dojo/dojo.js'></script>
+ <script type="text/javascript" src='/js/dojo/dojo/openils_dojo.js'></script>
+ <script type="text/javascript" src='/js/dojo/opensrf/opensrf.js'></script>
+ <script type="text/javascript" src='/js/dojo/opensrf/md5.js'></script>
+ <script type='text/javascript' src='../js/record_mgmt.js'></script>
+
+</head>
+<body class="tundra" style="background-color:#b7bbc3;">
+
+ <div id="wrap">
+ <div id="mainhead">
+ <a href="http://fulfillment-ill.org" target="_blank"><img src="/images/FulfillmentHomePageBanner.png" border="0" alt="Open Source Integrated Interlibrary Lending System" /></a>
+ </div>
+ <div id="subhead">
+ <a href="hold_tracker.xml" class="mainNav">Manage ILL Requests</a>
+ <a href="circ_tracker.xml" class="mainNav">Manage ILL Transactions</a>
+ <a href="record_mgmt.xml" class="thispage mainNav">Bibliographic Record Management</a>
+ </div>
+
+ <div style="height: 20px;"/>
+
+ <span style="margin-left: 10px;">Choose a location</span>
+ <select dojoType='openils.widget.OrgUnitFilteringSelect'
+ jsId='loc'
+ name='loc'
+ searchAttr='name'
+ labelAttr='name'
+ >
+ </select>
+ <br/>
+ <br/>
+ <br/>
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client" style='height:80%'>
+ <div dojoType="dijit.layout.TabContainer">
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="Ingest batch record file"
+ selected='true'>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+
+ <form action="/staff/postBibs/" method="POST" enctype="multipart/form-data" name="localMarcForm" id="localMarcForm">
+ <label for="loadFile">Load File:</label>
+ <input type="file" name="loadFile" id="loadFile" />
+ <input type="hidden" name="uploadLocation" id="uploadLocation" />
+ <br/>
+ <br/>
+ <table><tr><td>Status:</td><td><div id="response"></div></td></tr></table>
+ <div class="dojo-FormUploadProgress"
+ background="#333"
+ color="#ccc"
+ ready="upload:progress.ready"
+ connecting="upload:progress.connecting"
+ attr="ready connecting"/>
+ <br/>
+ <input type="submit" onclick="dojo.attr('uploadLocation', 'value', loc.getValue()); return true" value="Submit" />
+ </form>
+
+ </div>
+ </div>
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane'
+ title="Purge bibliographic records"
+ selected='true'>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+
+ <button dojoType='dijit.form.Button'>
+ <span>Purge all current bibliographic records</span>
+ <script type='dojo/connect' event='onClick'>
+ if (confirm('This will completely remove all bibliographic data for the currently selected location!')) {
+ var success = fieldmapper.standardRequest(
+ ['open-ils.cat','open-ils.cat.biblio.record.purge_by_owner'],
+ [user.authtoken, loc.getValue()]
+ );
+ }
+ </script>
+ </button>
+
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ </div>
+ <br/>
+ <br/>
+ <a href="javascript:dojo.cookie('ses', null, {path:'/',expires:-1});location.href=location.href;">Log Out</a>
+</body>
+</html>
+