diff --git a/C4/Languages.pm b/C4/Languages.pm index e84aa1418a..61898c911f 100644 --- a/C4/Languages.pm +++ b/C4/Languages.pm @@ -28,6 +28,7 @@ use List::MoreUtils qw( any ); use C4::Context; use Koha::Caches; use Koha::Cache::Memory::Lite; +use Koha::Language; our (@ISA, @EXPORT_OK); BEGIN { @@ -111,12 +112,17 @@ Returns a reference to an array of hashes: =cut sub getTranslatedLanguages { - my ($interface, $theme, $current_language, $which) = @_; + my ($interface, $theme, $current_language) = @_; my @languages; - my @enabled_languages = - ( $interface && $interface eq 'intranet' ) - ? split ",", C4::Context->preference('StaffInterfaceLanguages') - : split ",", C4::Context->preference('OPACLanguages'); + + my @enabled_languages; + if ($interface) { + if ($interface eq 'intranet') { + @enabled_languages = split ",", C4::Context->preference('StaffInterfaceLanguages'); + } elsif ($interface eq 'opac') { + @enabled_languages = split ",", C4::Context->preference('OPACLanguages'); + } + } my $cache = Koha::Caches->get_instance; my $cache_key = "languages_${interface}_${theme}"; @@ -640,8 +646,26 @@ sub accept_language { =head2 getlanguage - Select a language based on the URL parameter 'language', a cookie, - syspref available languages & browser +Select a language based on: + +=over + +=item * the URL parameter 'language' (staff and opac interfaces only), + +=item * the 'KohaOpacLanguage' cookie (staff and opac interfaces only), + +=item * the language set by C +(interfaces other than staff and opac only). The REST API sets this to the +value of 'KohaOpacLanguage' cookie, + +=item * syspref StaffInterfaceLanguages (staff only) + +=item * syspref OpacLanguages (opac only), + +=item * HTTP header Accept-Language (more precisely, environment variable +HTTP_ACCEPT_LANGUAGE) + +=back =cut @@ -655,29 +679,36 @@ sub getlanguage { return $cached if $cached; } - $cgi //= CGI->new; my $interface = C4::Context->interface; - my $theme = C4::Context->preference( ( $interface eq 'opac' ) ? 'opacthemes' : 'template' ); + my $theme = ''; my $language; - - my $preference_to_check = - $interface eq 'intranet' ? 'StaffInterfaceLanguages' : 'OPACLanguages'; - # Get the available/valid languages list my @languages; - my $preference_value = C4::Context->preference($preference_to_check); - if ($preference_value) { - @languages = split /,/, $preference_value; - } - # Chose language from the URL - my $cgi_param_language = $cgi->param( 'language' ); - if ( defined $cgi_param_language && any { $_ eq $cgi_param_language } @languages) { - $language = $cgi_param_language; - } + if ($interface eq 'opac' || $interface eq 'intranet') { + $cgi //= CGI->new; + $theme = C4::Context->preference( ( $interface eq 'opac' ) ? 'opacthemes' : 'template' ); - # cookie - if (not $language and my $cgi_cookie_language = $cgi->cookie('KohaOpacLanguage') ) { - ( $language = $cgi_cookie_language ) =~ s/[^a-zA-Z_-]*//; # sanitize cookie + my $preference_to_check = + $interface eq 'intranet' ? 'StaffInterfaceLanguages' : 'OPACLanguages'; + # Get the available/valid languages list + my $preference_value = C4::Context->preference($preference_to_check); + if ($preference_value) { + @languages = split /,/, $preference_value; + } + + # Chose language from the URL + my $cgi_param_language = $cgi->param( 'language' ); + if ( defined $cgi_param_language && any { $_ eq $cgi_param_language } @languages) { + $language = $cgi_param_language; + } + + # cookie + if (not $language and my $cgi_cookie_language = $cgi->cookie('KohaOpacLanguage') ) { + ( $language = $cgi_cookie_language ) =~ s/[^a-zA-Z_-]*//; # sanitize cookie + } + } else { + @languages = map { $_->{rfc4646_subtag} } @{ getTranslatedLanguages($interface, $theme) }; + $language = Koha::Language->get_requested_language(); } # HTTP_ACCEPT_LANGUAGE diff --git a/Koha/App/Intranet.pm b/Koha/App/Intranet.pm index 558de8da1c..5628174fdc 100644 --- a/Koha/App/Intranet.pm +++ b/Koha/App/Intranet.pm @@ -39,6 +39,7 @@ sub startup { $self->plugin('RESTV1'); $self->plugin('CSRF'); + $self->plugin('Language'); $self->hook(before_dispatch => \&_before_dispatch); $self->hook(around_action => \&_around_action); diff --git a/Koha/App/Opac.pm b/Koha/App/Opac.pm index 1f722360e4..d41a704776 100644 --- a/Koha/App/Opac.pm +++ b/Koha/App/Opac.pm @@ -39,6 +39,7 @@ sub startup { $self->plugin('RESTV1'); $self->plugin('CSRF'); + $self->plugin('Language'); $self->hook(before_dispatch => \&_before_dispatch); $self->hook(around_action => \&_around_action); diff --git a/Koha/App/Plugin/Language.pm b/Koha/App/Plugin/Language.pm new file mode 100644 index 0000000000..c93e4fa95f --- /dev/null +++ b/Koha/App/Plugin/Language.pm @@ -0,0 +1,72 @@ +package Koha::App::Plugin::Language; + +# 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 3 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, see . + +=head1 NAME + +Koha::App::Plugin::Language + +=head1 SYNOPSIS + + $app->plugin('Language'); + +=head1 DESCRIPTION + +Sets the current language based on request (cookies, headers, ...) + +=cut + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Plugin'; + +use Koha::Language; + +=head1 METHODS + +=head2 register + +Called by Mojolicious when the plugin is loaded. + +Defines an `around_action` hook that will sets the current language based on +the current request. + +=cut + +sub register { + my ( $self, $app, $conf ) = @_; + + $app->hook( + around_action => sub { + my ( $next, $c, $action, $last ) = @_; + + # Make the value of Accept-Language header and KohaOpacLanguage + # cookie accessible to C4::Languages::getlanguage when not in a CGI + # context + + if ( my $accept_language = $c->req->headers->accept_language ) { + $ENV{HTTP_ACCEPT_LANGUAGE} = $accept_language; + } + if ( my $KohaOpacLanguage = $c->cookie('KohaOpacLanguage') ) { + Koha::Language->set_requested_language($KohaOpacLanguage); + } + + return $next->(); + } + ); +} + +1; diff --git a/Koha/Language.pm b/Koha/Language.pm new file mode 100644 index 0000000000..1dcb58237e --- /dev/null +++ b/Koha/Language.pm @@ -0,0 +1,46 @@ +package Koha::Language; + +=head1 NAME + +Koha::Language + +=head1 SYNOPSIS + + use Koha::Language; + + Koha::Language->set_requested_language('xx-XX'); + my $language = Koha::Language->get_requested_language(); + +=head1 DESCRIPTION + +This module is essentially a communication tool between the REST API and +C4::Languages::getlanguage so that getlanguage can be aware of the value of +KohaOpacLanguage cookie when not in CGI context. + +It can also be used in other contexts, like command line scripts for instance. + +=cut + +use Modern::Perl; + +use Koha::Cache::Memory::Lite; + +use constant REQUESTED_LANGUAGE_CACHE_KEY => 'requested_language'; + +sub set_requested_language { + my ($class, $language) = @_; + + my $cache = Koha::Cache::Memory::Lite->get_instance; + + $cache->set_in_cache(REQUESTED_LANGUAGE_CACHE_KEY, $language); +} + +sub get_requested_language { + my ($class) = @_; + + my $cache = Koha::Cache::Memory::Lite->get_instance; + + return $cache->get_from_cache(REQUESTED_LANGUAGE_CACHE_KEY); +} + +1; diff --git a/Koha/REST/V1.pm b/Koha/REST/V1.pm index 52bd15697e..1a3559bfdf 100644 --- a/Koha/REST/V1.pm +++ b/Koha/REST/V1.pm @@ -153,6 +153,7 @@ sub startup { $self->app->log->warn( "Warning: Failed to fetch oauth configuration: " . $_ ); }; + $self->plugin('Koha::App::Plugin::Language'); $self->plugin('Koha::REST::Plugin::Pagination'); $self->plugin('Koha::REST::Plugin::Query'); $self->plugin('Koha::REST::Plugin::Objects'); diff --git a/Koha/REST/V1/Auth.pm b/Koha/REST/V1/Auth.pm index c10e81cf8a..dd59def5f1 100644 --- a/Koha/REST/V1/Auth.pm +++ b/Koha/REST/V1/Auth.pm @@ -155,11 +155,6 @@ sub authenticate_api_request { $c->stash_embed( { spec => $spec } ); $c->stash_overrides(); - # FIXME: Remove once CGI is not used - my $accept_language = $c->req->headers->accept_language; - $ENV{HTTP_ACCEPT_LANGUAGE} = $accept_language - if $accept_language; - my $cookie_auth = 0; my $authorization = $spec->{'x-koha-authorization'}; diff --git a/t/Languages.t b/t/Languages.t index 14c5d2866d..4b13d02cc2 100755 --- a/t/Languages.t +++ b/t/Languages.t @@ -18,10 +18,11 @@ # with Koha; if not, see . use Modern::Perl; -use Test::More tests => 4; +use Test::More tests => 5; use Test::MockModule; use CGI qw ( -utf8 ); use Koha::Cache::Memory::Lite; +use Koha::Language; BEGIN { use_ok('C4::Languages', qw( getlanguage )); @@ -32,6 +33,7 @@ my @languages = (); # stores the list of active languages my $return_undef = 0; my $module_context = Test::MockModule->new('C4::Context'); +my $module_languages = Test::MockModule->new('C4::Languages'); $module_context->mock( preference => sub { @@ -46,6 +48,12 @@ $module_context->mock( }, ); +$module_languages->mock( + _get_language_dirs => sub { + return @languages + } +); + delete $ENV{HTTP_ACCEPT_LANGUAGE}; my $query = CGI->new(); @@ -59,3 +67,24 @@ is(C4::Languages::getlanguage($query), 'en', 'default to English if no language Koha::Cache::Memory::Lite->get_instance()->clear_from_cache('getlanguage'); $return_undef = 1; is(C4::Languages::getlanguage($query), 'en', 'default to English if no database'); + +subtest 'when interface is not intranet or opac' => sub { + plan tests => 3; + + my $cache = Koha::Cache::Memory::Lite->get_instance; + C4::Context->interface('api'); + + @languages = ('fr-FR', 'de-DE', 'en'); + + $ENV{HTTP_ACCEPT_LANGUAGE} = 'de-DE'; + $cache->clear_from_cache('getlanguage'); + is(C4::Languages::getlanguage(), 'de-DE', 'Accept-Language HTTP header is respected'); + + Koha::Language->set_requested_language('fr-FR'); + $cache->clear_from_cache('getlanguage'); + is(C4::Languages::getlanguage(), 'fr-FR', 'but language requested through Koha::Language is prefered'); + + @languages = (); + $cache->clear_from_cache('getlanguage'); + is(C4::Languages::getlanguage(), 'en', 'fallback to english when no language is available'); +}; diff --git a/t/db_dependent/api/v1/items.t b/t/db_dependent/api/v1/items.t index 2d5592d091..4dfcc2200c 100755 --- a/t/db_dependent/api/v1/items.t +++ b/t/db_dependent/api/v1/items.t @@ -32,6 +32,7 @@ use Mojo::JSON qw(encode_json); use C4::Auth; use Koha::Items; use Koha::Database; +use Koha::Language; my $schema = Koha::Database->new->schema; my $builder = t::lib::TestBuilder->new; @@ -256,7 +257,7 @@ subtest 'list_public() tests' => sub { subtest 'get() tests' => sub { - plan tests => 34; + plan tests => 37; $schema->storage->txn_begin; @@ -346,6 +347,13 @@ subtest 'get() tests' => sub { ->json_is( '/not_for_loan_status' => 0, 'not_for_loan_status is 0' ) ->json_is( '/effective_not_for_loan_status' => 3, 'effective_not_for_loan_status now picks up itemtype level - item-level_itypes:0' ); + my $module = Test::MockModule->new('C4::Languages'); + $module->mock( _get_language_dirs => sub { return 'en', 'de-DE' } ); + $schema->resultset('Localization')->create({ entity => 'itemtypes', code => $itype->itemtype, lang => 'de-DE', translation => 'translated' }); + $t->get_ok( "//$userid:$password@/api/v1/items/" . $item->itemnumber, { 'X-Koha-Embed' => '+strings', Cookie => 'KohaOpacLanguage=de-DE', 'Accept-Language' => 'en' } ) + ->status_is( 200, 'SWAGGER3.2.2' ) + ->json_is( '/_strings/item_type_id/str' => 'translated', 'translations in _strings use language set in KohaOpacLanguage cookie' ); + $schema->storage->txn_rollback; };