API, TPAC backend, and UI bits for TPAC metarecord searching and holds.
* Group Formats and Editions options in advanced search / searchbar
* MR holds placement form, allowing selected formats and languages
* MR holds targeting updated to work w/ new holdable formats composite
definitions
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Dan Wells <dbw2@calvin.edu>
elsif ($$params{'hold_type'} eq 'P') { $target_field = 'partid'; }
else { return undef; }
+ my $formats_map = delete $$params{holdable_formats_map};
+
foreach (@$target_list) {
$$params{$target_field} = $_;
+
+ # copy the requested formats from the target->formats map
+ # into the top-level formats attr for each hold
+ $$params{holdable_formats} = $formats_map->{$_};
+
my $res;
($res) = $self->method_lookup(
'open-ils.circ.title_hold.is_possible')->run($auth, $params, $override ? $oargs : {});
} elsif( $hold_type eq OILS_HOLD_TYPE_METARECORD ) {
- my $maps = $e->search_metabib_metarecord_source_map({metarecord=>$mrid});
- my @recs = map { $_->source } @$maps;
+ my ($recs) = __PACKAGE__->method_lookup('open-ils.circ.holds.metarecord.filterd_records')->run($mrid, $holdable_formats);
my @status = ();
- for my $rec (@recs) {
+ for my $rec (@$recs) {
@status = _check_title_hold_is_possible(
$rec, $depth, $request_lib, $patron, $e->requestor, $pickup_lib, $selection_ou, $holdable_formats, $oargs
);
# else { Unrecognized hold_type ! } # FIXME: return error? or 0?
}
+sub MR_filter_records {
+ return $U->storagereq('open-ils.storage.metarecord.filtered_records.atomic', $_[2], $_[3]);
+}
+__PACKAGE__->register_method(
+ method => 'MR_filter_records',
+ api_name => 'open-ils.circ.holds.metarecord.filterd_records',
+);
+
+
my %prox_cache;
sub create_ranged_org_filter {
my($e, $selection_ou, $depth) = @_;
sub _check_title_hold_is_possible {
my( $titleid, $depth, $request_lib, $patron, $requestor, $pickup_lib, $selection_ou, $holdable_formats, $oargs ) = @_;
-
- my ($types, $formats, $lang);
- if (defined($holdable_formats)) {
- ($types, $formats, $lang) = split '-', $holdable_formats;
- }
+ # $holdable_formats is now unused. We pre-filter the MR's records.
my $e = new_editor();
my %org_filter = create_ranged_org_filter($e, $selection_ou, $depth);
acn => {
field => 'id',
fkey => 'call_number',
- 'join' => {
- bre => {
- field => 'id',
- filter => { id => $titleid },
- fkey => 'record'
- },
- mrd => {
- field => 'record',
- fkey => 'record',
- filter => {
- record => $titleid,
- ( $types ? (item_type => [split '', $types]) : () ),
- ( $formats ? (item_form => [split '', $formats]) : () ),
- ( $lang ? (item_lang => $lang) : () )
- }
- }
- }
+ filter => { record => $titleid }
},
acpl => { field => 'id', filter => { holdable => 't'}, fkey => 'location' },
ccs => { field => 'id', filter => { holdable => 't'}, fkey => 'status' },
return undef;
}
+
+__PACKAGE__->register_method(
+ method => 'mr_hold_filter_attrs',
+ api_name => 'open-ils.circ.mmr.holds.filters',
+ authoritative => 1,
+ stream => 1,
+ signature => {
+ desc => q/
+ Returns the set of available formats and languages for the
+ constituent records of the provided metarcord.
+ If an array of hold IDs is also provided, information about
+ each is returned as well. This information includes:
+ 1. a slightly easier to read version of holdable_formats
+ 2. attributes describing the set of format icons included
+ in the set of desired, constituent records.
+ /,
+ params => [
+ {desc => 'Metarecord ID', type => 'number'},
+ {desc => 'Hold ID List', type => 'array'},
+ ],
+ return => {
+ desc => q/
+ Stream of objects. The first will have a 'metarecord' key
+ containing non-hold-specific metarecord information, subsequent
+ responses will contain a 'hold' key containing hold-specific
+ information
+ /,
+ type => 'object'
+ }
+ }
+);
+
+sub mr_hold_filter_attrs {
+ my ($self, $client, $mr_id, $hold_ids) = @_;
+ my $e = new_editor();
+
+
+ my $mr = $e->retrieve_metabib_metarecord($mr_id) or return $e->event;
+ my $bre_ids = $e->json_query({
+ select => {mmrsm => ['source']},
+ from => 'mmrsm',
+ where => {'+mmrsm' => {metarecord => $mr_id}}
+ });
+ $bre_ids = [map {$_->{source}} @$bre_ids];
+
+ my $item_lang_attr = 'item_lang'; # configurable?
+ my $format_attr = $e->retrieve_config_global_flag(
+ 'opac.metarecord.holds.format_attr')->value;
+
+ # helper sub for fetching ccvms for a batch of record IDs
+ sub get_batch_ccvms {
+ my ($e, $attr, $bre_ids) = @_;
+ return [] unless $bre_ids and @$bre_ids;
+ my $vals = $e->search_metabib_record_attr_flat({
+ attr => $attr,
+ id => $bre_ids
+ });
+ return [] unless @$vals;
+ return $e->search_config_coded_value_map({
+ ctype => $attr,
+ code => [map {$_->value} @$vals]
+ });
+ }
+
+ my $langs = get_batch_ccvms($e, $item_lang_attr, $bre_ids);
+ my $formats = get_batch_ccvms($e, $format_attr, $bre_ids);
+
+ $client->respond({
+ metarecord => {
+ id => $mr_id,
+ formats => $formats,
+ langs => $langs
+ }
+ });
+
+ return unless $hold_ids;
+ my $icon_attr = $e->retrieve_config_global_flag('opac.icon_attr');
+ $icon_attr = $icon_attr ? $icon_attr->value : '';
+
+ for my $hold_id (@$hold_ids) {
+ my $hold = $e->retrieve_action_hold_request($hold_id)
+ or return $e->event;
+
+ next unless $hold->hold_type eq 'M';
+
+ my $resp = {
+ hold => {
+ id => $hold_id,
+ formats => [],
+ langs => []
+ }
+ };
+
+ # collect the ccvm's for the selected formats / language (
+ # (i.e. the holdable formats) on the MR.
+ # this assumes a two-key structure for format / language,
+ # though assumption is made about the keys themselves.
+ my $hformats = OpenSRF::Utils::JSON->JSON2perl($hold->holdable_formats);
+ my $lang_vals = [];
+ my $format_vals = [];
+ for my $val (values %$hformats) {
+ # val is either a single ccvm or an array of them
+ $val = [$val] unless ref $val eq 'ARRAY';
+ for my $node (@$val) {
+ push (@$lang_vals, $node->{_val})
+ if $node->{_attr} eq $item_lang_attr;
+ push (@$format_vals, $node->{_val})
+ if $node->{_attr} eq $format_attr;
+ }
+ }
+
+ # fetch the ccvm's for consistency with the {metarecord} blob
+ $resp->{hold}{formats} = $e->search_config_coded_value_map({
+ ctype => $format_attr, code => $format_vals});
+ $resp->{hold}{langs} = $e->search_config_coded_value_map({
+ ctype => $item_lang_attr, code => $lang_vals});
+
+ # find all of the bib records within this metarcord whose
+ # format / language match the holdable formats on the hold
+ my ($bre_ids) = $self->method_lookup(
+ 'open-ils.circ.holds.metarecord.filterd_records')->run(
+ $hold->target, $hold->holdable_formats);
+
+ # now find all of the 'icon' attributes for the records
+ $resp->{hold}{icons} = get_batch_ccvms($e, $icon_attr, $bre_ids);
+ $client->respond($resp);
+ }
+
+ return;
+}
+
1;
);
+sub MR_records_matching_format {
+ my $self = shift;
+ my $client = shift;
+ my $MR = shift;
+ my $filter = shift;
+
+ # find filters for MR holds
+ my $mr_filter;
+ if (defined($filter)) {
+ ($mr_filter) = @{action::hold_request->db_Main->selectcol_arrayref(
+ 'SELECT metabib.compile_composite_attr(?)',
+ {},
+ $filter
+ )};
+ }
+
+ my $records = [metabib::metarecord->retrieve($MR)->source_records];
+
+ if (!$mr_filter) {
+ $client->respond( $_->id ) for @$records;
+ } else {
+ for my $r ( map { isTrue($_->deleted) ? () : ($_->id) } @$records ) {
+ $client->respond($r) if
+ @{action::hold_request->db_Main->selectcol_arrayref(
+ 'SELECT source FROM metabib.record_attr_vector_list WHERE source = ? AND vlist @@ ?',
+ {},
+ $r,
+ $mr_filter
+ )};
+ }
+ }
+ return; # discard final l-val
+}
+__PACKAGE__->register_method(
+ api_name => 'open-ils.storage.metarecord.filtered_records',
+ api_level => 1,
+ stream => 1,
+ argc => 2,
+ method => 'MR_records_matching_format',
+);
+
sub new_hold_copy_targeter {
my $self = shift;
my $all_copies = [];
- # find filters for MR holds
- my ($types, $formats, $lang);
- if (defined($hold->holdable_formats)) {
- ($types, $formats, $lang) = split '-', $hold->holdable_formats;
- }
-
# find all the potential copies
if ($hold->hold_type eq 'M') {
- my $records = [
- map {
- isTrue($_->deleted) ? () : ($_->id)
- } metabib::metarecord->retrieve($hold->target)->source_records
- ];
- if(@$records > 0) {
- for my $r ( map
- {$_->record}
- metabib::record_descriptor
- ->search(
- record => $records,
- ( $types ? (item_type => [split '', $types]) : () ),
- ( $formats ? (item_form => [split '', $formats]) : () ),
- ( $lang ? (item_lang => $lang) : () ),
- )
- ) {
- my ($rtree) = $self
- ->method_lookup( 'open-ils.storage.biblio.record_entry.ranged_tree')
- ->run( $r->id, $hold->selection_ou, $hold->selection_depth );
-
- for my $cn ( @{ $rtree->call_numbers } ) {
- push @$all_copies,
- asset::copy->search_where(
- { id => [map {$_->id} @{ $cn->copies }],
- deleted => 'f' }
- ) if ($cn && @{ $cn->copies });
- }
+ for my $r_id (
+ $self->method_lookup(
+ 'open-ils.storage.metarecord.filtered_records'
+ )->run( $hold->target, $hold->holdable_formats )
+ ) {
+ my ($rtree) = $self
+ ->method_lookup( 'open-ils.storage.biblio.record_entry.ranged_tree')
+ ->run( $r_id, $hold->selection_ou, $hold->selection_depth );
+
+ for my $cn ( @{ $rtree->call_numbers } ) {
+ push @$all_copies,
+ asset::copy->search_where(
+ { id => [map {$_->id} @{ $cn->copies }],
+ deleted => 'f' }
+ ) if ($cn && @{ $cn->copies });
}
}
} elsif ($hold->hold_type eq 'T') {
$self->volume_hold_capture($hold,$cn_list) if (ref $cn_list and @$cn_list);
}
-sub metarecord_hold_capture {
- my $self = shift;
- my $hold = shift;
-
- my $titles;
- try {
- $titles = [ metabib::metarecord_source_map->search( metarecord => $hold->target) ];
-
- } catch Error with {
- my $e = shift;
- die "Could not retrieve initial title list:\n\n$e\n";
- };
-
- try {
- my @recs = map {$_->record} metabib::record_descriptor->search( record => $titles, item_type => [split '', $hold->holdable_formats] );
-
- $titles = [ biblio::record_entry->search( id => \@recs ) ];
-
- } catch Error with {
- my $e = shift;
- die "Could not retrieve format-pruned title list:\n\n$e\n";
- };
-
-
- $cache{titles}{$_->id} = $_ for (@$titles);
- $self->title_hold_capture($hold,$titles) if (ref $titles and @$titles);
-}
1;
if(@collected) {
while(my $blob = pop(@collected)) {
- my (undef, @data) = $self->get_records_and_facets(
- [$blob->{hold}->{bre_id}], undef, {flesh => '{mra}'}
- );
+ my @data;
+
+ # in the holds edit UI, we need to know what formats and
+ # languages the user selected for this hold, plus what
+ # formats/langs are available on the MR as a whole.
+ if ($blob->{hold}{hold}->hold_type eq 'M') {
+ my $hold = $blob->{hold}->{hold};
+
+ # for MR, fetch the combined MR unapi blob
+ (undef, @data) = $self->get_records_and_facets(
+ [$hold->target], undef, {flesh => '{mra}', metarecord => 1});
+
+ my $filter_data = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.mmr.holds.filters.authoritative.atomic',
+ $hold->target, [$hold->id]
+ );
+
+ $blob->{metarecord_filters} =
+ $filter_data->[0]->{metarecord};
+ $blob->{metarecord_selected_filters} =
+ $filter_data->[1]->{hold};
+ } else {
+
+ (undef, @data) = $self->get_records_and_facets(
+ [$blob->{hold}->{bre_id}], undef, {flesh => '{mra}'}
+ );
+ }
+
$blob->{marc_xml} = $data[0]->{marc_xml};
push(@holds, $blob);
}
m:^(\d{2})/(\d{2})/(\d{4})$:;
$val->{$field} = "$3-$1-$2";
}
+
+ $val->{holdable_formats} = # no-op for non-MR holds
+ $self->compile_holdable_formats(undef, $_);
+
$val;
} @hold_ids;
};
my $type_dispatch = {
+ M => sub {
+ # target metarecords
+ my $mrecs = $e->batch_retrieve_metabib_metarecord([
+ \@targets,
+ {flesh => 1, flesh_fields => {mmr => ['master_record']}}],
+ {substream => 1}
+ );
+
+ for my $id (@targets) {
+ my ($mr) = grep {$_->id eq $id} @$mrecs;
+
+ my $filter_data = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.mmr.holds.filters.authoritative', $mr->id);
+
+ my $holdable_formats =
+ $self->compile_holdable_formats($mr->id);
+
+ push(@hold_data, $data_filler->({
+ target => $mr,
+ record => $mr->master_record,
+ holdable_formats => $holdable_formats,
+ metarecord_filters => $filter_data->{metarecord}
+ }));
+ }
+ },
T => sub {
- my $recs = $e->batch_retrieve_biblio_record_entry(\@targets, {substream => 1});
+ my $recs = $e->batch_retrieve_biblio_record_entry(
+ [\@targets, {flesh => 1, flesh_fields => {bre => ['metarecord']}}],
+ {substream => 1}
+ );
for my $id (@targets) { # force back into the correct order
my ($rec) = grep {$_->id eq $id} @$recs;
if(@create_targets) {
+ # holdable formats may be different for each MR hold.
+ # map each set to the ID of the target.
+ my $holdable_formats = {};
+ if ($hold_type eq 'M') {
+ $holdable_formats->{$_->{target_id}} =
+ $_->{holdable_formats} for @hold_data;
+ }
+
my $bses = OpenSRF::AppSession->create('open-ils.circ');
my $breq = $bses->request(
$method,
$e->authtoken,
- $data_filler->({ patronid => $usr,
+ $data_filler->({
+ patronid => $usr,
pickup_lib => $pickup_lib,
- hold_type => $hold_type
+ hold_type => $hold_type,
+ holdable_formats_map => $holdable_formats
}),
\@create_targets
);
}
}
+# pull the selected formats and languages for metarecord holds
+# from the CGI params and map them into the JSON holdable
+# formats...er, format.
+# if no metarecord is provided, we'll pull it from the target
+# of the provided hold.
+sub compile_holdable_formats {
+ my ($self, $mr_id, $hold_id) = @_;
+ my $e = $self->editor;
+ my $cgi = $self->cgi;
+
+ # exit early if not needed
+ return "" unless
+ grep /metarecord_formats_|metarecord_langs_/,
+ $cgi->param;
+
+ # CGI params are based on the MR id, since during hold placement
+ # we have no old ID. During hold edit, map the hold ID back to
+ # the metarecod target.
+ $mr_id =
+ $e->retrieve_action_hold_request($hold_id)->target
+ unless $mr_id;
+
+ my $format_attr = $self->ctx->{get_cgf}->(
+ 'opac.metarecord.holds.format_attr');
+
+ if (!$format_attr) {
+ $logger->error("Missing config.global_flag: ".
+ "opac.metarecord.holds.format_attr!");
+ return "";
+ }
+
+ $format_attr = $format_attr->value;
+
+ # during hold placement or edit submission, the user selects
+ # which of the available formats/langs are acceptable.
+ # Capture those here as the holdable_formats for the MR hold.
+ my @selected_formats = $cgi->param("metarecord_formats_$mr_id");
+ my @selected_langs = $cgi->param("metarecord_langs_$mr_id");
+
+ # map the selected attrs into the JSON holdable_formats structure
+ my $blob = {};
+ if (@selected_formats) {
+ $blob->{0} = [
+ map { {_attr => $format_attr, _val => $_} }
+ @selected_formats
+ ];
+ }
+ if (@selected_langs) {
+ $blob->{1} = [
+ map { {_attr => 'item_lang', _val => $_} }
+ @selected_langs
+ ];
+ }
+
+ return OpenSRF::Utils::JSON->perl2JSON($blob);
+}
+
sub fetch_user_circs {
my $self = shift;
my $flesh = shift; # flesh bib data, etc.
foreach ($cgi->param('modifier')) {
# The unless bit is to avoid stacking modifiers.
- $query = ('#' . $_ . ' ' . $query) unless $query =~ qr/\#\Q$_/;
+ $query = ('#' . $_ . ' ' . $query) unless
+ $query =~ qr/\#\Q$_/ or $_ eq 'metabib';
}
# filters
my $ctx = $self->ctx;
my $e = $self->editor;
+ # 1. param->metarecord : view constituent bib records for a metarecord
+ # 2. param->modifier=metabib : perform a metarecord search
+ my $metarecord = $ctx->{metarecord} = $cgi->param('metarecord');
+ my @mods = $cgi->param('modifier');
+ my $is_meta = (@mods and grep {$_ eq 'metabib'} @mods and !$metarecord);
+ my $id_key = $is_meta ? 'mmr_id' : 'bre_id';
+
# find the last record in the set, then redirect
my $find_last = $cgi->param('find_last');
$ctx->{search_ou} = $self->_get_search_lib();
$ctx->{pref_ou} = $self->_get_pref_lib() || $ctx->{search_ou};
my $offset = $page * $limit;
- my $metarecord = $cgi->param('metarecord');
my $results;
my $tag_circs = $self->tag_circed_items;
$self->timelog("Got search parameters");
$query = "$_ $query" for @facets;
- $logger->activity("EGWeb: [search] $query");
+ my $ltag = $is_meta ? '[mmr search]' : '[bre search]';
+ $logger->activity("EGWeb: $ltag $query");
try {
my $method = 'open-ils.search.biblio.multiclass.query';
$method .= '.staff' if $ctx->{is_staff};
+ $method =~ s/biblio/metabib/ if $is_meta;
my $ses = OpenSRF::AppSession->create('open-ils.search');
my $rec_id = pop @$rec_ids;
$cgi->delete('find_last');
my $url = $cgi->url(-full => 1, -path => 1, -query => 1);
+ # TODO: metarecord => /rresults?metarecord=$mmr_id
$url =~ s|/results|/record/$rec_id|;
return $self->generic_redirect($url);
}
$self->load_rresults_bookbag_item_notes($rec_ids) if $ctx->{bookbag};
+ my $fetch_recs = $rec_ids;
+
+ my $metarecord_master;
+ if ($metarecord) {
+ # when listing the contents of a metarecord, be sure to fetch
+ # the lead record for summary display. Adding the ID to
+ # $fetch_recs lets us grab the record (if necessary) w/o it
+ # unintentially becoming a member of the result set.
+ my $mr = $e->retrieve_metabib_metarecord($metarecord);
+ push(@$fetch_recs, $mr->master_record)
+ unless grep {$_ eq $mr->master_record} @$fetch_recs;
+ $metarecord_master = $mr->master_record;
+ }
+
$self->timelog("Calling get_records_and_facets()");
my ($facets, @data) = $self->get_records_and_facets(
- $rec_ids, $results->{facet_key},
+ $fetch_recs, $results->{facet_key},
{
flesh => '{holdings_xml,mra,acp,acnp,acns,bmp}',
site => $site,
+ metarecord => $is_meta,
depth => $depth,
pref_lib => $ctx->{pref_ou},
}
$self->timelog("Returned from get_records_and_facets()");
if ($page == 0) {
+ # TODO: handle metarecords
my $stat = $self->check_1hit_redirect($rec_ids);
return $stat if $stat;
}
# shove recs into context in search results order
for my $rec_id (@$rec_ids) {
- push(
- @{$ctx->{records}},
- grep { $_->{id} == $rec_id } @data
- );
+ my ($rec) = grep { $_->{$id_key} == $rec_id } @data;
+ push(@{$ctx->{records}}, $rec);
+
+ $ctx->{metarecord_master} = $rec
+ if $metarecord_master and $metarecord_master eq $rec_id;
+
+ # MR's with multiple constituent records will have a
+ # null value in position 2 of the result set.
+ my ($res_rec) = grep { $_->[0] == $rec_id} @{$results->{ids}};
+ $rec->{mr_has_multi} = !$res_rec->[2];
}
if ($tag_circs) {
for my $rec (@{$ctx->{records}}) {
- my ($res_rec) = grep { $_->[0] == $rec->{id} } @{$results->{ids}};
+ my ($res_rec) = grep { $_->[0] == $rec->{$id_key} } @{$results->{ids}};
# index 1 in the per-record result array is a boolean which
# indicates whether the record in question is in the users
# accessible circ history list
- $rec->{user_circulated} = 1 if $res_rec->[1];
+ my $index = $is_meta ? 3 : 1;
+ $rec->{user_circulated} = 1 if $res_rec->[$index];
}
}
$unapi_args->{depth} ||= $self->ctx->{aou_tree}->()->ou_type->depth;
$unapi_args->{flesh_depth} ||= 5;
+ my $is_meta = delete $unapi_args->{metarecord};
+ my $unapi_type = $is_meta ? 'unapi.mmr' : 'unapi.bre';
+
$unapi_cache ||= OpenSRF::Utils::Cache->new('global');
my $unapi_cache_key_suffix = join(
'_',
+ $is_meta || 0,
$unapi_args->{site},
$unapi_args->{depth},
$unapi_args->{flesh_depth},
$outer_self->timelog("get_records_and_facets(): got response content");
# Protect against requests for non-existent records
- return unless $data->{'unapi.bre'};
+ return unless $data->{$unapi_type};
- my $xml = XML::LibXML->new->parse_string($data->{'unapi.bre'})->documentElement;
+ my $xml = XML::LibXML->new->parse_string($data->{$unapi_type})->documentElement;
$outer_self->timelog("get_records_and_facets(): parsed xml");
# Protect against legacy invalid MARCXML that might not have a 901c
my $bre_id;
+ my $mmr_id;
my $bre_id_nodes = $xml->find('*[@tag="901"]/*[@code="c"]');
if ($bre_id_nodes) {
$bre_id = $bre_id_nodes->[0]->textContent;
} else {
$logger->warn("Missing 901 subfield 'c' in " . $xml->toString());
}
- $tmp_data{$bre_id} = {id => $bre_id, marc_xml => $xml};
- if ($bre_id) {
+ if ($is_meta) {
+ # extract metarecord ID from mmr.unapi tag
+ for my $node ($xml->getElementsByTagName('abbr')) {
+ my $title = $node->getAttribute('title');
+ ($mmr_id = $title) =~
+ s/tag:open-ils.org:U2\@mmr\/(\d+)\/.*/$1/g;
+ last if $mmr_id;
+ }
+ }
+
+ my $rec_id = $mmr_id ? $mmr_id : $bre_id;
+ $tmp_data{$rec_id} = {
+ id => $rec_id,
+ bre_id => $bre_id,
+ mmr_id => $mmr_id,
+ marc_xml => $xml
+ };
+
+ if ($rec_id) {
# Let other backends grab our data now that we're done.
- my $key = 'TPAC_unapi_cache_'.$bre_id.'_'.$unapi_cache_key_suffix;
+ my $key = 'TPAC_unapi_cache_'.$rec_id.'_'.$unapi_cache_key_suffix;
my $cache_data = $unapi_cache->get_cache($key);
if ($$cache_data{running}) {
- $unapi_cache->put_cache($key, { id => $bre_id, marc_xml => $data->{'unapi.bre'} }, 10);
+ $unapi_cache->put_cache($key, {
+ bre_id => $bre_id,
+ mmr_id => $mmr_id,
+ id => $rec_id,
+ marc_xml => $data->{$unapi_type}
+ }, 10);
}
}
-
$outer_self->timelog("get_records_and_facets(): end of success handler");
}
);
- $self->timelog("get_records_and_facets(): about to call unapi.bre via json_query (rec_ids has " . scalar(@$rec_ids));
+ $self->timelog("get_records_and_facets(): about to call ".
+ "$unapi_type via json_query (rec_ids has " . scalar(@$rec_ids));
my @loop_recs = @$rec_ids;
my %rec_timeout;
$tmp_data{$unapi_data->{id}} = $unapi_data;
} else { # we're the first or we timed out. success_handler will populate the real value
$unapi_cache->put_cache($unapi_cache_key, { running => $$ }, 10);
+
+ my $sdepth = $unapi_args->{flesh_depth};
+ my $slimit = "acn=>$sdepth,acp=>$sdepth";
+ $slimit .= ",bre=>$sdepth" if $is_meta;
+ my $flesh = $unapi_args->{flesh} || '';
+
+ # tag the record with the MR id
+ $flesh =~ s/}$/,mmr.unapi}/g if $is_meta;
+
$ses->request(
'open-ils.cstore.json_query',
{from => [
- 'unapi.bre', $bid, 'marcxml','record',
- $unapi_args->{flesh},
+ $unapi_type, $bid, 'marcxml','record', $flesh,
$unapi_args->{site},
$unapi_args->{depth},
- 'acn=>' . $unapi_args->{flesh_depth} . ',acp=>' . $unapi_args->{flesh_depth},
+ $slimit,
undef, undef, $unapi_args->{pref_lib}
]}
);
}
-
}
parse_accept_lang($r->headers_in->get('Accept-Language'));
# set the editor default locale for each page load
- OpenSRF::AppSession->default_locale(parse_eg_locale($ctx->{locale}));
+ my $ses_locale = parse_eg_locale($ctx->{locale});
+ OpenSRF::AppSession->default_locale($ses_locale);
+ # give templates access to the en-US style locale
+ $ctx->{eg_locale} = $ses_locale;
my $mprefix = $ctx->{media_prefix};
if($mprefix and $mprefix !~ /^http/ and $mprefix !~ /^\//) {
div.adv_search_available {
margin-top: 1em;
}
+
#myopac_loading {
width:100%;
text-align:center;
</td>
<td>
<div class="format_icon">
- [% IF attrs.format_icon %]
- <img title="[% attrs.format_label | html %]" alt="[% attrs.format_label | html %]" src="[% attrs.format_icon %]" />
+ [% IF attrs.all_formats.size %]
+ [% FOR format IN attrs.all_formats %]
+ <img title="[% format.label | html %]" alt="[% format.label | html %]" src="[% format.icon %]" />
+ [% END %]
[% END %]
</div>
</td>
PROCESS "opac/parts/misc_util.tt2";
PROCESS "opac/parts/hold_status.tt2";
PROCESS "opac/parts/org_selector.tt2";
+ PROCESS "opac/parts/metarecord_hold_filters.tt2";
WRAPPER "opac/parts/myopac/base.tt2";
myopac_page = "holds"; # in this case, just for tab coloring.
</td>
</tr>
[% END %]
+
+ <tr><td colspan='2'>
+ [% IF hold.metarecord_filters.formats.size OR
+ hold.metarecord_filters.langs.size > 1;
+ PROCESS metarecord_hold_filters_selector
+ hold_data=hold; END %]
+ </td></tr>
+
<tr>
<td colspan="2" class="hold-editor-controls">
<a href="[% ctx.opac_root %]/myopac/holds"><button
CASE "sort_selector";
INCLUDE "opac/parts/filtersort.tt2"
value=CGI.param('sort') class='results_header_sel';
+ %]
- CASE "copy_location" %]
+ [% IF NOT metarecords.disabled %]
+ <br/><!-- <br> may seem redundant, but it allows the
+ <input> (below) to drop down inline w/ its label -->
+ <div class="adv_search_available">
+ <input type='checkbox' name="modifier" value="metabib"
+ [%- CGI.param('modifier').grep('metabib').size ?
+ ' checked="checked"' : '' %]
+ id='opac.result.ismetabib' />
+ <label for='opac.result.ismetabib'>
+ [% l("Group Formats and Editions") %]</label>
+ </div>
+ [% END %]
+
+ [% CASE "copy_location" %]
<select id="adv_copy_location_selector"
aria-label="[% l('Select Shelving Location') %]"
name="fi:locations" size="3" multiple="multiple">
#
# ctx.maintenance_message = "The system will not be available February 29, 2104.";
+
+##############################################################################
+# Metarecords configuration
+# metarecords.disabled = 1; # disable all metarecord access points
+##############################################################################
+
+
%]
--- /dev/null
+[%#
+Draws the format multi-select and the language multi-select for
+limiting the set of desired records for a given metarecord.
+%]
+
+<style>
+ /* TODO: MOVE ME */
+ .metarecord_filters {
+ padding: 5px;
+ margin-top: 5px;
+ border-bottom: 1px solid #333;
+ border-top: 1px solid #333;
+ }
+ .metarecord_filter_container {
+ float : left;
+ margin-right: 10px;
+ }
+ .metarecord_filter_container select {
+ padding: 2px;
+ width: 13em; /* consistent w/ adv search selectors */
+ }
+ .metarecord_filter_header {
+ padding-bottom: 5px;
+ }
+</style>
+
+[% BLOCK metarecord_hold_filters_selector;
+ # in edit mode, pull the target from the existing hold
+ target_id = hold_data.target.id || hold_data.hold.hold.target;
+
+ selected_formats = {};
+ selected_langs = {};
+ FOR fmt IN hold_data.metarecord_selected_filters.formats;
+ code = fmt.code;
+ selected_formats.$code = fmt;
+ END;
+ FOR lang IN hold_data.metarecord_selected_filters.langs;
+ code = lang.code;
+ selected_langs.$code = lang;
+ END;
+%]
+
+<div class="metarecord_filters">
+ <div class="metarecord_filter_container">
+ <div class="metarecord_filter_header">
+ <div>[% l('Select your desired format(s).') %]</div>
+ </div>
+ <select multiple='multiple'
+ name="metarecord_formats_[% target_id %]">
+ [% FOR ccvm IN
+ hold_data.metarecord_filters.formats.sort('search_label') %]
+ <option value="[% ccvm.code %]"[%- code = ccvm.code;
+ IF selected_formats.$code %] selected='selected'[% END -%]>
+ [% ccvm.search_label | html %]
+ </option>
+ [% END %]
+ </select>
+ </div>
+ [% IF hold_data.metarecord_filters.langs.size;
+ my_lang = ctx.get_i18n_l(ctx.eg_locale).marc_code;
+ %]
+ <div class="metarecord_filter_container">
+ <div class="metarecord_filter_header">
+ [% l('Select your desired language(s)') %]
+ </div>
+ <select multiple='multiple'
+ name="metarecord_langs_[% target_id %]">
+ [% FOR lang_ccvm IN hold_data.metarecord_filters.langs.sort('value') %]
+ [% selected = 0;
+ code = lang_ccvm.code;
+ IF selected_langs.size;
+ # user has already selected their preferred language(s)
+ SET selected = 1 IF selected_langs.$code;
+ ELSE;
+ # no prefered language selected, default to current locale
+ SET selected = 1 IF code == my_lang;
+ END;
+ %]
+ <option value="[% lang_ccvm.code %]"[%-
+ IF selected %] selected='selected'[%- END %]>
+ [% lang_ccvm.value | html %]
+ </option>
+ [% END %]
+ </select>
+ </div>
+ [% END %]
+ <div class="clear-both"> </div>
+</div>
+[% END # metarecord_hold_filters_selector %]
END;
END;
- # Get CCVM labels
BLOCK get_ccvm_label;
- IF !ctx.ccvm_cache.$id;
- fetch_ccvm = ctx.search_ccvm('id', id);
- IF fetch_ccvm;
- ctx.ccvm_cache.$id = fetch_ccvm.0;
- END;
- END;
- IF search_label and ctx.ccvm_cache.$id.search_label;
- ctx.ccvm_cache.$id.search_label;
+ ccvm = ctx.get_ccvm(id); # caches internally
+ IF search_label and ccvm.search_label;
+ ccvm.search_label;
ELSE;
- ctx.ccvm_cache.$id.value;
+ ccvm.$id.value;
END;
END;
# "mattype" == "custom marc format specifier"
icon_style = ctx.get_cgf('opac.icon_attr').value || 'item_type';
- node = xml.findnodes(
- '//*[local-name()="attributes"]/*[local-name()="field"][@name="' _ icon_style _ '"]');
- IF node AND node.textContent;
- type = node.textContent;
- args.format_label = PROCESS get_ccvm_label id=node.getAttribute('cvmid') search_label=1;
- IF !args.format_label;
- args.format_label = node.getAttribute('coded-value');
+ formats_xpath = '//*[local-name()="attributes"]/*[local-name()="field"][@name="' _ icon_style _ '"]';
+
+ args.all_formats = [];
+ FOR node IN xml.findnodes(formats_xpath);
+ IF node AND node.textContent;
+ type = node.textContent;
+ label = PROCESS get_ccvm_label id=node.getAttribute('cvmid') search_label=1;
+ itemtype = schema_typemap.$type || 'CreativeWork';
+ icon = ctx.media_prefix _ '/images/format_icons/' _ icon_style _ '/' _ type _ '.png';
+ # collect all formats for metarecord support
+ args.all_formats.push({label => label, icon => icon, itemtype => itemtype});
+ IF !args.format_label;
+ # use the first format as the default
+ args.format_label = label;
+ args.schema.itemtype = itemtype;
+ args.format_icon = icon;
+ END;
END;
- args.schema.itemtype = schema_typemap.$type || 'CreativeWork';
- args.format_icon = ctx.media_prefix _ '/images/format_icons/' _ icon_style _ '/' _ type _ '.png';
- LAST;
END;
args.bibid = [];
[% PROCESS "opac/parts/misc_util.tt2";
PROCESS "opac/parts/hold_error_messages.tt2";
+ PROCESS "opac/parts/metarecord_hold_filters.tt2";
%]
<div id='holds_box' class='canvas' style='margin-top: 6px;'>
<input type='hidden' name='part' value=''/>
[% END %]
[% END %]
+ [% IF NOT metarecords.disabled %]
+ [% IF CGI.param('hold_type') == 'T' AND hdata.record.metarecord %]
+ <a href="[% mkurl('', {hold_type => 'M', hold_target => hdata.record.metarecord.id}) %]">
+ [% l('Advanced Hold Options') %]</a>
+ [% END %]
+ [% IF hdata.metarecord_filters.formats.size OR # should this be size > 1
+ hdata.metarecord_filters.langs.size > 1;
+ PROCESS metarecord_hold_filters_selector hold_data=hdata;
+ END;
+ END %]
</td>
</tr>
[% END %]
IF CGI.param('detail_record_view');
attrs.title = attrs.title_extended;
END;
+ # note: rec.id refers to the record identifier, regardless
+ # of the type of record. i.e. rec.id = mmr_id ? mmr_id : bre_id
+ IF rec.mmr_id;
+ IF rec.mr_has_multi;
+ # metarecords link to record list page
+ record_url = mkurl(ctx.opac_root _ '/results',
+ {metarecord => rec.mmr_id}, ['page']);
+ ELSE;
+ # for MR, bre_id refers to the master and in
+ # this case, only, record
+ record_url = mkurl(ctx.opac_root _ '/record/' _ rec.bre_id);
+ END;
+ hold_type = 'M';
+ ELSE;
+ record_url = mkurl(ctx.opac_root _ '/record/' _ rec.bre_id);
+ hold_type = 'T';
+ END;
-%]
<tr class="result_table_row">
<td class="results_row_count" name="results_row_count">[%
result_count; result_count = result_count + 1
%].</td>
<td class='result_table_pic_header'>
- <a href="[% mkurl(ctx.opac_root _ '/record/' _ rec.id) %]"><img alt="[% l('Image of item') %]"
+ <a href="[% record_url %]"><img alt="[% l('Image of item') %]"
name='item_jacket' class='result_table_pic' width="55"
- src='[% ctx.media_prefix %]/opac/extras/ac/jacket/small/r/[% rec.id | uri %]' /></a><br />
+ src='[% ctx.media_prefix %]/opac/extras/ac/jacket/small/r/[% rec.bre_id | uri %]' /></a><br />
</td>
<td class='result_table_title_cell' name='result_table_title_cell'>
<div class="result_metadata">
- <abbr class="unapi-id" title='tag:[% ctx.hostname %],[% date.format(date.now, '%Y') %]:biblio-record_entry/[% rec.id %]'></abbr>
+ [% IF rec.mmr_id %]
+ <abbr class="unapi-id"
+ title='tag:[% ctx.hostname %],[% date.format(date.now, '%Y') %]:metabib-metarecord/[% rec.mmr_id %]'>
+ </abbr>
+ [% ELSE %]
+ <abbr class="unapi-id"
+ title='tag:[% ctx.hostname %],[% date.format(date.now, '%Y') %]:biblio-record_entry/[% rec.bre_id %]'>
+ </abbr>
+ [% END %]
<a class='record_title search_link' name='record_[% rec.id %]'
- href="[% mkurl(ctx.opac_root _ '/record/' _ rec.id) %]"
+ href="[% record_url %]"
[% html_text_attr('title', l('Display record details for "[_1]"', attrs.title)) %]
>[% attrs.title | html %]</a>
[%-
</div>
<div class='result_table_title_cell'>
[%- IF attrs.format_label; %]
- <img title="[% attrs.format_label | html %]" alt="[% attrs.format_label | html %]" src="[% attrs.format_icon %]" /> [% attrs.format_label; %]
+ [% FOR format IN attrs.all_formats %]
+ <img title="[% format.label | html %]"
+ alt="[% format.label | html %]"
+ src="[% format.icon %]" />
+ [% format.label | html %]
+ [% END %]
[%- END %]
[%- UNLESS CGI.param('detail_record_view')
OR (show_more_details.default == 'true'
%]
<div class="results_aux_utils place_hold"><a
href="[% mkurl(ctx.opac_root _ '/place_hold',
- {hold_target => rec.id, hold_type => 'T', hold_source_page => mkurl()}, ['query']) %]"
+ {hold_target => rec.id, hold_type => hold_type,
+ hold_source_page => mkurl()}, ['query']) %]"
[% html_text_attr('title', l('Place Hold on [_1]', attrs.title)) %]
class="no-dec"><img
src="[% ctx.media_prefix %]/images/green_check.png"
[% INCLUDE "opac/parts/searchbar.tt2" took_care_of_form=1 %]
<h3 class="sr-only">[% l('Additional search filters and navigation') %]</h3>
<div class="almost-content-wrapper">
- <div id="results_header_bar">
+
+ [%# hide the header bar when displaying metarecord constituents
+ instead of skipping it altogether to allow the search form
+ variables to propagate %]
+ [% IF ctx.metarecord;
+ mr_attrs = {marc_xml => ctx.metarecord_master.marc_xml};
+ PROCESS get_marc_attrs args=mr_attrs %]
+ <div class="results_header_lbl">
+ [% l('Viewing Results for Grouped Record: [_1]',
+ mr_attrs.title) | html %]
+ </div>
+ [% END %]
+ <div class="results_header_bar[%- IF ctx.metarecord %] hidden[% END -%]">
<div id="results_header_inner">
<div class="results_header_btns">
<a href="[% mkurl(ctx.opac_root _ '/home', {$loc_name => loc_value}, 1) %]">[% l('Another Search') %]</a>
<label class="results_header_lbl" for="limit_to_available">
<input type="checkbox" id="limit_to_available" name="modifier" value="available"
- onchange="limit_to_avail_onchange(this, true)"
+ onchange="search_modifier_onchange('available', this, true)"
[% CGI.param('modifier').grep('available').size ? ' checked="checked"' : '' %] />
[% l('Limit to available items') %]
</label>
+ [% IF NOT metarecords.disabled %]
+ <label class="results_header_lbl">
+ <input type="checkbox" name="modifier" value="metabib"
+ onchange="search_modifier_onchange('metabib', this, true)"
+ [% CGI.param('modifier').grep('metabib').size ? ' checked="checked"' : '' %] />
+ [% l('Group Formats and Editions') %]
+ </label>
+ [% END %]
[% IF CGI.param('detail_record_view') %]
<input type="hidden" name="detail_record_view" value="1" />
[% END %]
}
}
-function limit_to_avail_onchange(checkbox, submitOnChange) {
+function search_modifier_onchange(type, checkbox, submitOnChange) {
if (checkbox.form._adv && !checkbox.checked) {
var search_box = $('search_box');
- search_box.value = search_box.value.replace(/#available ?/g, "");
+ var reg = new RegExp('#' + type + ' ?', 'g');
+ search_box.value = search_box.value.replace(reg, "");
}
if (submitOnChange) {