@@ -, +, @@ 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). 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" 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" 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" whatever) plugin terms is suggested. terms is suggested. searching for exploded terms is NOT suggested. --- 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 --- a/Koha/SuggestionEngine.pm +++ a/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 = (); --- a/Koha/SuggestionEngine/Base.pm +++ a/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; --- a/Koha/SuggestionEngine/Plugin/ExplodedTerms.pm +++ a/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; +} --- a/admin/didyoumean.pl +++ a/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; --- a/installer/data/mysql/sysprefs.sql +++ a/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'); --- a/installer/data/mysql/updatedatabase.pl +++ a/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) --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ a/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%; --- a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc +++ a/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?
  • --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt +++ a/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.
    --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/didyoumean.tt +++ a/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' %] --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt +++ a/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' %]
    --- a/koha-tmpl/opac-tmpl/prog/en/modules/text/explodedterms.tt +++ a/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 -%] --- a/opac/opac-search.pl +++ a/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; --- a/opac/svc/suggestion +++ a/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 } ); --- a/t/SuggestionEngine_ExplodedTerms.t +++ a/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(); --