From 0c14563a0d0a100f9a12d1e6915f46abc2e16698 Mon Sep 17 00:00:00 2001 From: Jared Camins-Esakov Date: Fri, 14 Sep 2012 09:08:10 -0400 Subject: [PATCH] Bug 8726: ExplodedTerms suggestion plugin (functionality) When working with hierarchical subject headings, it is sometimes helpful to do a search for all records with a specific subject, plus broader/narrower/related subjects. This patch adds a suggestion plugin for these "exploded" subject searches to Koha. Note that this patch depends on both bug 8211 AND bug 8209. To test (NOTE: this test plan covers both 8211 and 8726): 1) Make sure you have a bunch of hierarchical subjects. I created geographical subjects for "Arizona," "United States," and "Phoenix," and linked them together using 551s, and made sure I had a half dozen records linking to each (but not all to all three). 2) Do a search for su-br:Arizona (or choose "Subject and broader terms" on the advanced search screen with "more options" displayed), and check that you get the records with the subject "Arizona" and the records with the subject "United States" 3) Do a search for su-na:Arizona (or choose "Subject and narrower terms" on the advanced search screen with "more options" displayed), and check that you get the records with the subject "Arizona" and the records with the subject "Phoenix" 4) Do a search for su-rl:Arizona (or choose "Subject and related terms" on the advanced search screen with "more options" displayed), and check that you get the records with the subject "Arizona," the records with the subject "United States," and the records with the subject "Phoenix" 5) Ensure that other searches still work (keyword, subject, ccl, whatever) 6) Use "Did you mean?" page in admin section to enable ExplodedTerms plugin 7) Do a keyword search on the OPAC, confirm that searching for exploded terms is suggested. 8) Do a subject search on the OPAC, confirm that searching for exploded terms is suggested. 9) Do a non-keyword, non-subject search on the OPAC, confirm that searching for exploded terms is NOT suggested. 10) Disable ExplodedTerms plugin and enable AuthorityFile plugin. 11) Do search on OPAC, confirm suggestions are made from authority file. 12) Sign off Signed-off-by: wajasu Signed-off-by: Jared Camins-Esakov Split into two patches. This patch includes only the functionality. --- Koha/SuggestionEngine.pm | 7 +- Koha/SuggestionEngine/Base.pm | 44 ++++++++- Koha/SuggestionEngine/Plugin/ExplodedTerms.pm | 87 ++++++++++++++++ admin/didyoumean.pl | 37 +++++++ installer/data/mysql/sysprefs.sql | 3 +- installer/data/mysql/updatedatabase.pl | 10 ++- .../intranet-tmpl/prog/en/css/staff-global.css | 36 +++++++ .../intranet-tmpl/prog/en/includes/admin-menu.inc | 1 + .../prog/en/modules/admin/admin-home.tt | 2 + .../prog/en/modules/admin/didyoumean.tt | 104 ++++++++++++++++++++ .../opac-tmpl/prog/en/modules/opac-results.tt | 2 +- .../prog/en/modules/text/explodedterms.tt | 8 ++ opac/opac-search.pl | 2 +- opac/svc/suggestion | 12 ++- t/SuggestionEngine_ExplodedTerms.t | 31 ++++++ 15 files changed, 376 insertions(+), 10 deletions(-) create mode 100644 Koha/SuggestionEngine/Plugin/ExplodedTerms.pm create mode 100755 admin/didyoumean.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/didyoumean.tt create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/text/explodedterms.tt create mode 100755 t/SuggestionEngine_ExplodedTerms.t diff --git a/Koha/SuggestionEngine.pm b/Koha/SuggestionEngine.pm index 0d977c1..e3ee1c9 100644 --- a/Koha/SuggestionEngine.pm +++ b/Koha/SuggestionEngine.pm @@ -87,7 +87,7 @@ sub new { my $options = $param->{options} || ''; my @plugins = (); - foreach my $plugin ( $param->{plugins} ) { + foreach my $plugin ( @{$param->{plugins}} ) { next unless $plugin; my $plugin_module = $plugin =~ m/:/ @@ -141,15 +141,18 @@ sub get_suggestions { my %suggestions; + my $index = scalar @{ $self->plugins }; + foreach my $pluginobj ( @{ $self->plugins } ) { next unless $pluginobj; my $pluginres = $pluginobj->get_suggestions($param); foreach my $suggestion (@$pluginres) { $suggestions{ $suggestion->{'search'} }->{'relevance'} += - $suggestion->{'relevance'}; + $suggestion->{'relevance'} * $index; $suggestions{ $suggestion->{'search'} }->{'label'} |= $suggestion->{'label'}; } + $index--; } my @results = (); diff --git a/Koha/SuggestionEngine/Base.pm b/Koha/SuggestionEngine/Base.pm index 95c6622..5c52e2b 100644 --- a/Koha/SuggestionEngine/Base.pm +++ b/Koha/SuggestionEngine/Base.pm @@ -60,8 +60,6 @@ use base qw(Class::Accessor); __PACKAGE__->mk_ro_accessors(qw( name version )); __PACKAGE__->mk_accessors(qw( params )); -our $NAME = 'Base'; -our $VERSION = '1.0'; =head2 new @@ -125,4 +123,46 @@ sub get_suggestions { return; } +=head2 NAME + + my $name = $plugin->NAME; + +Getter function for plugin names. + +=cut + +sub NAME { + my $self = shift; + my $package = ref $self || $self; + return eval '$' . $package . '::NAME'; +} + +=head2 VERSION + + my $version = $plugin->VERSION; + +Getter function for plugin versions. + +=cut + +sub VERSION { + my $self = shift; + my $package = ref $self || $self; + return eval '$' . $package . '::VERSION'; +} + +=head2 DESCRIPTION + + my $description = $plugin->DESCRIPTION; + +Getter function for plugin descriptions. + +=cut + +sub DESCRIPTION { + my $self = shift; + my $package = ref $self || $self; + return eval '$' . $package . '::DESCRIPTION'; +} + 1; diff --git a/Koha/SuggestionEngine/Plugin/ExplodedTerms.pm b/Koha/SuggestionEngine/Plugin/ExplodedTerms.pm new file mode 100644 index 0000000..a380261 --- /dev/null +++ b/Koha/SuggestionEngine/Plugin/ExplodedTerms.pm @@ -0,0 +1,87 @@ +package Koha::SuggestionEngine::Plugin::ExplodedTerms; + +# Copyright 2012 C & P Bibliography Services +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=head1 NAME + +Koha::SuggestionEngine::Plugin::ExplodedTerms - suggest searches for broader/narrower/related subjects + +=head1 SYNOPSIS + + +=head1 DESCRIPTION + +Plugin to suggest expanding the search by adding broader/narrower/related +subjects to subject searches. + +=cut + +use strict; +use warnings; +use Carp; +use C4::Templates qw(gettemplate); # This is necessary for translatability + +use base qw(Koha::SuggestionEngine::Base); +our $NAME = 'ExplodedTerms'; +our $VERSION = '1.0'; + +=head2 get_suggestions + + my $suggestions = $plugin->get_suggestions(\%param); + +Return suggestions for the specified search that add broader/narrower/related +terms to the search. + +=cut + +sub get_suggestions { + my $self = shift; + my $param = shift; + + my $search = $param->{'search'}; + + return if ( $search =~ m/^(ccl=|cql=|pqf=)/ ); + $search =~ s/(su|su-br|su-na|su-rl)[:=](\w*)/OP!$2/g; + return if ( $search =~ m/\w+[:=]\w+/ ); + + my @indexes = ( + 'su-na', + 'su-br', + 'su-rl' + ); + my $cgi = new CGI; + my $template = C4::Templates::gettemplate('text/explodedterms.tt', 'opac', $cgi); + my @results; + foreach my $index (@indexes) { + my $thissearch = $search; + $thissearch = "$index=$thissearch" + unless ( $thissearch =~ s/OP!/$index=/g ); + $template->{VARS}->{index} = $index; + my $label = pack("U0a*", $template->output); #FIXME: C4::Templates is + # returning incorrectly-marked UTF-8. This fixes the problem, but is + # an annoying workaround. + push @results, + { + 'search' => $thissearch, + relevance => 100, + # FIXME: it'd be nice to have some empirical measure of + # "relevance" in this case, but we don't. + label => $label + }; + } return \@results; +} diff --git a/admin/didyoumean.pl b/admin/didyoumean.pl new file mode 100755 index 0000000..434d0e9 --- /dev/null +++ b/admin/didyoumean.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use CGI; +use C4::Context; +use C4::Auth; +use C4::Output; +use Koha::SuggestionEngine; +use Module::Load::Conditional qw(can_load); +use JSON; + +my $input = new CGI; + +my ($template, $loggedinuser, $cookie) + = get_template_and_user({template_name => "admin/didyoumean.tt", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => {parameters => 'parameters_remaining_permissions'}, + debug => 1, + }); + +my $opacplugins = from_json(C4::Context->preference('OPACdidyoumean') || '[]'); + +my $intraplugins = from_json(C4::Context->preference('INTRAdidyoumean') || '[]'); + +my @pluginlist = Koha::SuggestionEngine::AvailablePlugins(); +foreach my $plugin (@pluginlist) { + next if $plugin eq 'Koha::SuggestionEngine::Plugin::Null'; + next unless (can_load( modules => { "$plugin" => undef } )); + push @$opacplugins, { name => $plugin->NAME } unless grep { $_->{name} eq $plugin->NAME } @$opacplugins; + push @$intraplugins, { name => $plugin->NAME } unless grep { $_->{name} eq $plugin->NAME } @$intraplugins; +} +$template->{VARS}->{OPACpluginlist} = $opacplugins; +$template->{VARS}->{INTRApluginlist} = $intraplugins; +output_html_with_http_headers $input, $cookie, $template->output; diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 7b33ce6..2deb3d6 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -376,9 +376,10 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES(' INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('SubfieldsToUseWhenPrefill','','Define a list of subfields to use when prefilling items (separated by space)','','Free'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('AgeRestrictionMarker','','Markers for age restriction indication, e.g. FSK|PEGI|Age|',NULL,'free'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('AgeRestrictionOverride',0,'Allow staff to check out an item with age restriction.',NULL,'YesNo'); -INSERT INTO systempreferences (variable,value,explanation,type) VALUES('DidYouMeanFromAuthorities','0','Suggest searches based on authority file.','YesNo'); INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('IncludeSeeFromInSearches','0','','Include see-from references in searches.','YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OPACMobileUserCSS','','Include the following CSS for the mobile view on all pages in the OPAC:',NULL,'free'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacMainUserBlockMobile','','Show the following HTML in its own column on the main page of the OPAC (mobile version):',NULL,'free'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacShowLibrariesPulldownMobile','1','Show the libraries pulldown on the mobile version of the OPAC.',NULL,'YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacShowFiltersPulldownMobile','1','Show the search filters pulldown on the mobile version of the OPAC.',NULL,'YesNo'); +INSERT INTO systempreferences (variable,value,explanation,type) VALUES('OPACdidyoumean',NULL,'Did you mean? configuration for the OPAC. Do not change, as this is controlled by /cgi-bin/koha/admin/didyoumean.pl.','Free'); +INSERT INTO systempreferences (variable,value,explanation,type) VALUES('INTRAdidyoumean',NULL,'Did you mean? configuration for the Intranet. Do not change, as this is controlled by /cgi-bin/koha/admin/didyoumean.pl.','Free'); diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index 949d56b..0659315 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -5769,7 +5769,6 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { SetVersion($DBversion); } - $DBversion = '3.09.00.044'; if (C4::Context->preference("Version") < TransformToNum($DBversion)) { $dbh->do("ALTER TABLE statistics ADD COLUMN ccode VARCHAR ( 10 ) NULL AFTER associatedborrower"); @@ -5953,6 +5952,15 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) { } +$DBversion ="3.09.00.XXX"; +if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { + $dbh->do("INSERT INTO systempreferences (variable,value,explanation,type) VALUES('OPACdidyoumean',NULL,'Did you mean? configuration for the OPAC. Do not change, as this is controlled by /cgi-bin/koha/admin/didyoumean.pl.','Free');"); + $dbh->do("INSERT INTO systempreferences (variable,value,explanation,type) VALUES('INTRAdidyoumean',NULL,'Did you mean? configuration for the Intranet. Do not change, as this is controlled by /cgi-bin/koha/admin/didyoumean.pl.','Free');"); + print "Upgrade to $DBversion done (Add Did You Mean? configuration)\n"; + SetVersion($DBversion); +} + + =head1 FUNCTIONS =head2 TableExists($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css index 9753f4f..8acdbf5 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -2424,6 +2424,42 @@ ul.ui-tabs-nav li { display: inline; } +#didyoumeanopac, #didyoumeanintranet { + float: left; + width: 260px; +} + +#didyoumeanlegend { + float: right; +} + +.pluginlist { + padding-bottom: 10px; +} +.plugin { + margin: 0 1em 1em 0; +} +.pluginname { + margin: 0.3em; + padding-bottom: 4px; + padding-left: 0.2em; + background-color: #E6F0F2; +} +.pluginname .ui-icon { + float: right; +} +.plugindesc { + padding: 0.4em; +} +.ui-sortable-placeholder { + border: 1px dotted black; + visibility: visible !important; + height: 80px !important; +} +.ui-sortable-placeholder * { + visibility: hidden; +} + /* jQuery UI Datepicker */ .ui-datepicker table { width: 100%; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc index 72162d3..fbda324 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc @@ -62,6 +62,7 @@ [% IF ( NoZebra ) %]
  • Stop words
  • [% END %]
  • Z39.50 client targets
  • +
  • Did you mean?
  • diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt index 88fabe9..8419f87 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt @@ -107,6 +107,8 @@
    Printers (UNIX paths).
    -->
    Z39.50 client targets
    Define which servers to query for MARC data in the integrated Z39.50 client.
    +
    Did you mean?
    +
    Choose which plugins to use to suggest searches to patrons and staff.
    diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/didyoumean.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/didyoumean.tt new file mode 100644 index 0000000..b534fed --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/didyoumean.tt @@ -0,0 +1,104 @@ +[% BLOCK pluginlist %] +
    +[% FOREACH plugin IN plugins %] +
    +
    + [% IF plugin.enabled %][% ELSE %][% END %] +
    +
    + [% SWITCH plugin.name %] + [% CASE 'AuthorityFile' %] + Suggest authorities which are relevant to the term the user searched for. + [% CASE 'ExplodedTerms' %] + Suggest that patrons expand their searches to include + broader/narrower/related terms. + [% END %] +
    +
    +[% END %] +
    +[% END %] +[% INCLUDE 'doc-head-open.inc' %] +Koha › Administration › Did you mean? +[% INCLUDE 'doc-head-close.inc' %] + + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + +
    + +
    +
    +
    +

    Did you mean?

    + +
    + Please put the Did you mean? plugins in order by significance, from + most significant to least significant, and check the box to enable those + plugins that you want to use. +
    +
    +
    + OPAC + [% PROCESS pluginlist plugins=OPACpluginlist %] +
    +
    + Intranet + [% PROCESS pluginlist plugins=INTRApluginlist %] +
    +
    Cancel
    +
    + +
    +
    +
    +[% INCLUDE 'admin-menu.inc' %] +
    +
    +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt index e5238f7..c76f162 100644 --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt @@ -285,7 +285,7 @@ $(document).ready(function(){
    - [% IF ( DidYouMeanFromAuthorities ) %] + [% IF ( DidYouMean ) %]
    Not what you expected? Check for suggestions
    [% END %] [% INCLUDE 'page-numbers.inc' %]
    diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/text/explodedterms.tt b/koha-tmpl/opac-tmpl/prog/en/modules/text/explodedterms.tt new file mode 100644 index 0000000..6f394ad --- /dev/null +++ b/koha-tmpl/opac-tmpl/prog/en/modules/text/explodedterms.tt @@ -0,0 +1,8 @@ +[%- SWITCH index -%] +[%- CASE 'su-na' -%] +Search also for narrower subjects +[%- CASE 'su-br' -%] +Search also for broader subjects +[%- CASE 'su-rl' -%] +Search also for related subjects +[%- END -%] diff --git a/opac/opac-search.pl b/opac/opac-search.pl index 9aac79d..bcdf671 100755 --- a/opac/opac-search.pl +++ b/opac/opac-search.pl @@ -823,7 +823,7 @@ if (C4::Context->preference('GoogleIndicTransliteration')) { $template->param('GoogleIndicTransliteration' => 1); } -$template->{VARS}->{DidYouMeanFromAuthorities} = C4::Context->preference('DidYouMeanFromAuthorities'); +$template->{VARS}->{DidYouMean} = C4::Context->preference('OPACdidyoumean') =~ m/enable/; $template->param( borrowernumber => $borrowernumber); output_with_http_headers $cgi, $cookie, $template->output, $content_type; diff --git a/opac/svc/suggestion b/opac/svc/suggestion index 7234ffd..9c08565 100755 --- a/opac/svc/suggestion +++ b/opac/svc/suggestion @@ -80,12 +80,20 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( } ); -unless ( C4::Context->preference('DidYouMeanFromAuthorities') ) { +my @plugins = (); + +my $pluginsconfig = from_json(C4::Context->preference('OPACdidyoumean') || '[]'); + +foreach my $plugin (@$pluginsconfig) { + push @plugins, $plugin->{name} if ($plugin->{enabled}); +} + +unless ( @plugins ) { print $query->header; exit; } -my $suggestor = Koha::SuggestionEngine->new( { plugins => ('AuthorityFile') } ); +my $suggestor = Koha::SuggestionEngine->new( { plugins => \@plugins } ); my $suggestions = $suggestor->get_suggestions( { search => $search, count => $count } ); diff --git a/t/SuggestionEngine_ExplodedTerms.t b/t/SuggestionEngine_ExplodedTerms.t new file mode 100755 index 0000000..d597aa1 --- /dev/null +++ b/t/SuggestionEngine_ExplodedTerms.t @@ -0,0 +1,31 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + use_ok('Koha::SuggestionEngine'); +} + +my $suggestor = Koha::SuggestionEngine->new( { plugins => ( 'ExplodedTerms' ) } ); +is(ref($suggestor), 'Koha::SuggestionEngine', 'Created suggestion engine'); + +my $result = $suggestor->get_suggestions({search => 'Cookery'}); + +ok((grep { $_->{'search'} eq 'su-na=Cookery' } @$result) && (grep { $_->{'search'} eq 'su-br=Cookery' } @$result) && (grep { $_->{'search'} eq 'su-rl=Cookery' } @$result), "Suggested correct alternatives for keyword search 'Cookery'"); + +$result = $suggestor->get_suggestions({search => 'su:Cookery'}); + +ok((grep { $_->{'search'} eq 'su-na=Cookery' } @$result) && (grep { $_->{'search'} eq 'su-br=Cookery' } @$result) && (grep { $_->{'search'} eq 'su-rl=Cookery' } @$result), "Suggested correct alternatives for subject search 'Cookery'"); + +$result = $suggestor->get_suggestions({search => 'nt:Cookery'}); + +is(scalar @$result, 0, "No suggestions for fielded search"); + +$result = $suggestor->get_suggestions({search => 'ccl=su:Cookery'}); + +is(scalar @$result, 0, "No suggestions for CCL search"); + +done_testing(); -- 1.7.2.5