+sub GetFacets {
+
+ my $rs = shift;
+ my $facets;
+
+ my $indexing_mode = C4::Context->config('zebra_bib_index_mode') // 'dom';
+ my $use_zebra_facets = C4::Context->config('use_zebra_facets') // 0;
+
+ if ( $indexing_mode eq 'dom' &&
+ $use_zebra_facets ) {
+ $facets = _get_facets_from_zebra( $rs );
+ } else {
+ $facets = _get_facets_from_records( $rs );
+ }
+
+ return $facets;
+}
+
+sub _get_facets_from_records {
+
+ my $rs = shift;
+
+ my $facets_maxrecs = C4::Context->preference('maxRecordsForFacets') // 20;
+ my $facets_config = getFacets();
+ my $facets = {};
+ my $size = $rs->size();
+ my $jmax = $size > $facets_maxrecs
+ ? $facets_maxrecs
+ : $size;
+
+ for ( my $j = 0 ; $j < $jmax ; $j++ ) {
+
+ my $marc_record = new_record_from_zebra (
+ 'biblioserver',
+ $rs->record( $j )->raw()
+ );
+
+ if ( ! defined $marc_record ) {
+ warn "ERROR DECODING RECORD - $@: " .
+ $rs->record( $j )->raw();
+ next;
+ }
+
+ _get_facets_data_from_record( $marc_record, $facets_config, $facets );
+ }
+
+ return $facets;
+}
+
+=head2 _get_facets_data_from_record
+
+ C4::Search::_get_facets_data_from_record( $marc_record, $facets, $facets_counter );
+
+Internal function that extracts facets information from a MARC::Record object
+and populates $facets_counter for using in getRecords.
+
+$facets is expected to be filled with C4::Koha::getFacets output (i.e. the configured
+facets for Zebra).
+
+=cut
+
+sub _get_facets_data_from_record {
+
+ my ( $marc_record, $facets, $facets_counter ) = @_;
+
+ for my $facet (@$facets) {
+
+ my @used_datas = ();
+
+ foreach my $tag ( @{ $facet->{ tags } } ) {
+
+ # tag number is the first three digits
+ my $tag_num = substr( $tag, 0, 3 );
+ # subfields are the remainder
+ my $subfield_letters = substr( $tag, 3 );
+
+ my @fields = $marc_record->field( $tag_num );
+ foreach my $field (@fields) {
+ # If $field->indicator(1) eq 'z', it means it is a 'see from'
+ # field introduced because of IncludeSeeFromInSearches, so skip it
+ next if $field->indicator(1) eq 'z';
+
+ my $data = $field->as_string( $subfield_letters, $facet->{ sep } );
+
+ unless ( grep { /^\Q$data\E$/ } @used_datas ) {
+ push @used_datas, $data;
+ $facets_counter->{ $facet->{ idx } }->{ $data }++;
+ }
+ }
+ }
+ }
+}
+
+=head2 _get_facets_from_zebra
+
+ my $facets = _get_facets_from_zebra( $result_set )
+
+Retrieves facets for a specified result set. It loops through the facets defined
+in C4::Koha::getFacets and returns a hash with the following structure:
+
+ { facet_idx => {
+ facet_value => count
+ },
+ ...
+ }
+
+=cut
+
+sub _get_facets_from_zebra {
+
+ my $rs = shift;
+
+ # save current elementSetName
+ my $elementSetName = $rs->option( 'elementSetName' );
+
+ my $facets_loop = getFacets();
+ my $facets_data = {};
+ # loop through defined facets and fill the facets hashref
+ foreach my $facet ( @$facets_loop ) {
+
+ my $idx = $facet->{ idx };
+ my $sep = $facet->{ sep };
+ my $facet_values = _get_facet_from_result_set( $idx, $rs, $sep );
+ if ( $facet_values ) {
+ # we've actually got a result
+ $facets_data->{ $idx } = $facet_values;
+ }
+ }
+ # set elementSetName to its previous value to avoid side effects
+ $rs->option( elementSetName => $elementSetName );
+
+ return $facets_data;
+}
+
+=head2 _get_facet_from_result_set
+
+ my $facet_values =
+ C4::Search::_get_facet_from_result_set( $facet_idx, $result_set, $sep )
+
+Internal function that extracts facet information for a specific index ($facet_idx) and
+returns a hash containing facet values and count:
+
+ {
+ $facet_value => $count ,
+ ...
+ }
+
+Warning: this function has the side effect of changing the elementSetName for the result
+set. It is a helper function for the main loop, which takes care of backing it up for
+restoring.
+
+=cut
+
+sub _get_facet_from_result_set {
+
+ my $facet_idx = shift;
+ my $rs = shift;
+ my $sep = shift;
+
+ my $internal_sep = '<*>';
+ my $facetMaxCount = C4::Context->preference('FacetMaxCount') // 20;
+
+ return if ( ! defined $facet_idx || ! defined $rs );
+ # zebra's facet element, untokenized index
+ my $facet_element = 'zebra::facet::' . $facet_idx . ':0:' . $facetMaxCount;
+ # configure zebra results for retrieving the desired facet
+ $rs->option( elementSetName => $facet_element );
+ # get the facet record from result set
+ my $facet = $rs->record( 0 )->raw;
+ # if the facet has no restuls...
+ return if !defined $facet;
+ # TODO: benchmark DOM vs. SAX performance
+ my $facet_dom = XML::LibXML->load_xml(
+ string => ($facet)
+ );
+ my @terms = $facet_dom->getElementsByTagName('term');
+ return if ! @terms;
+
+ my $facets = {};
+ foreach my $term ( @terms ) {
+ my $facet_value = $term->textContent;
+ $facet_value =~ s/\Q$internal_sep\E/$sep/ if defined $sep;
+ $facets->{ $facet_value } = $term->getAttribute( 'occur' );
+ }
+
+ return $facets;
+}
+
+=head2 _get_facets_info
+
+ my $facets_info = C4::Search::_get_facets_info( $facets )
+
+Internal function that extracts facets information and properly builds
+the data structure needed to render facet labels.
+
+=cut
+
+sub _get_facets_info {
+
+ my $facets = shift;
+
+ my $facets_info = {};
+
+ for my $facet ( @$facets ) {
+ $facets_info->{ $facet->{ idx } }->{ label_value } = $facet->{ label };
+ $facets_info->{ $facet->{ idx } }->{ expanded } = $facet->{ expanded };
+ }
+
+ return $facets_info;
+}
+