diff --git a/Koha/SuggestionEngine.pm b/Koha/SuggestionEngine.pm index 0d977c1e22..e3ee1c9d90 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 95c66225ba..5c52e2bbf5 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 0000000000..a380261add --- /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 0000000000..434d0e9a31 --- /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 ef2538c05f..ae2c4eed2a 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -376,10 +376,11 @@ 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 IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('AuthDisplayHierarchy','0','Display authority hierarchies','','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 eb45fb2440..b8b404765d 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -5768,7 +5768,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"); @@ -5951,7 +5950,6 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) { SetVersion ($DBversion); } - $DBversion = "3.09.00.054"; if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { $dbh->do("ALTER TABLE aqorders CHANGE COLUMN gst gstrate DECIMAL(6,4) DEFAULT NULL"); @@ -5982,6 +5980,14 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { SetVersion($DBversion); } +$DBversion ="3.09.00.058"; +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 20935ff139..9e414142f8 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -2434,6 +2434,42 @@ ul.ui-tabs-nav li { color: #990033; } +#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 72162d382d..fbda324b72 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 88fabe9ee7..8419f87c4c 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 0000000000..b534fed7c8 --- /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 e5238f7fb7..c76f162a9d 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 0000000000..6f394ad1cd --- /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 9aac79df62..bcdf671d81 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 7234ffdac2..9c08565a79 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 0000000000..d597aa1d64 --- /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();