#!/usr/bin/perl # # Copyright 2009 Jesse Weaver and the Koha Dev Team # # 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 . use Modern::Perl; use CGI qw ( -utf8 ); use C4::Auth qw( get_template_and_user ); use C4::Context; use C4::Koha qw( getallthemes ); use C4::Languages qw( getTranslatedLanguages ); use C4::ClassSource qw( GetClassSources GetClassSource ); use C4::Output qw( output_html_with_http_headers output_and_exit_if_error ); use C4::Templates; use Koha::Acquisition::Currencies; use Koha::AuthorisedValues; use Koha::Config::SysPrefs; use Koha::Database::Columns; use Koha::ILL::Request::Config; use IO::File; use YAML::XS; use Encode; use List::MoreUtils qw( any uniq ); sub GetTab { my ( $input, $tab ) = @_; my $tab_template = C4::Templates::gettemplate( 'admin/preferences/' . $tab . '.pref', 'intranet', $input ); my $active_currency = Koha::Acquisition::Currencies->get_active; my $local_currency; if ($active_currency) { $local_currency = $active_currency->currency; } $tab_template->param( local_currency => $local_currency, # currency code is used, because we do not know how a given currency is formatted. ); return YAML::XS::Load( Encode::encode_utf8( $tab_template->output() ) ); } sub _get_chunk { my ( $value, %options ) = @_; my $name = $options{'pref'}; my $chunk = { name => $name, value => $value, type => $options{'type'} || 'input', class => $options{'class'} }; if ( $options{'syntax'} ) { $chunk->{'syntax'} = $options{'syntax'}; } if ( $options{'type'} ) { if ( $options{'type'} eq 'modalselect' ) { $chunk->{'source'} = $options{'source'}; $chunk->{'exclusions'} = $options{'exclusions'} // ""; $chunk->{'inclusions'} = $options{'inclusions'} // ""; $chunk->{'required'} = $options{'required'} // ""; $chunk->{'type'} = 'modalselect'; } elsif ( $options{'type'} eq 'modaljs' ) { $chunk->{'type'} = 'modaljs'; $chunk->{'initiator'} = $options{'initiator'}; $chunk->{'processor'} = $options{'processor'}; } } if ( $options{'class'} && $options{'class'} eq 'password' ) { $chunk->{'input_type'} = 'password'; } elsif ( $options{'class'} && $options{'class'} eq 'email' ) { $chunk->{'input_type'} = 'email'; } elsif ( $options{'class'} && $options{'class'} eq 'date' ) { $chunk->{'dateinput'} = 1; } elsif ( $options{'type'} && $options{'type'} eq 'ill-backends' ) { my @priority_enabled_backends = split ",", C4::Context->preference('AutoILLBackendPriority'); my @sys_pref_backends = map( { name => $_, enabled => 1 }, @priority_enabled_backends ); my $installed_backends = Koha::ILL::Request::Config->new->installed_backends; foreach my $installed_backend ( @{$installed_backends} ) { if ( not grep { $installed_backend eq $_->{name} } @sys_pref_backends ) { my $backend = Koha::ILL::Request->new->load_backend($installed_backend); push( @sys_pref_backends, { name => $installed_backend, enabled => 0, !$backend->_backend_capability('provides_backend_availability_check') ? ( unable => 1 ) : () } ); } } $chunk->{'ill_backends'} = \@sys_pref_backends; $chunk->{'type'} = 'ill_backends'; } elsif ( $options{'type'} && ( $options{'type'} eq 'opac-languages' || $options{'type'} eq 'staff-languages' ) ) { my $current_languages = { map { +$_, 1 } split( /\s*,\s*/, $value ) }; my $theme; my $interface; if ( $options{'type'} eq 'opac-languages' ) { # this is the OPAC $interface = 'opac'; $theme = C4::Context->preference('opacthemes'); } else { # this is the staff interface $interface = 'intranet'; $theme = C4::Context->preference('template'); } $chunk->{'languages'} = getTranslatedLanguages( $interface, $theme ); $chunk->{'type'} = 'languages'; } elsif ( $options{'type'} && $options{'type'} eq 'locale-list' ) { # Dynamic locale detection for facet sorting using Koha::I18N require Koha::I18N; my $locales = Koha::I18N::available_locales(); foreach my $locale (@$locales) { if ( $locale->{value} && $value && $locale->{value} eq $value ) { $locale->{selected} = 1; } } $chunk->{'CHOICES'} = $locales; $chunk->{'type'} = 'select'; } elsif ( $options{'choices'} ) { my $add_blank; if ( $options{'choices'} && ref( $options{'choices'} ) eq '' ) { if ( $options{'choices'} eq 'class-sources' ) { my $sources = GetClassSources(); $options{'choices'} = { map { $_ => $sources->{$_}->{'description'} } keys %$sources }; } elsif ( $options{'choices'} eq 'opac-templates' ) { $options{'choices'} = { map { $_ => $_ } getallthemes('opac') }; } elsif ( $options{'choices'} eq 'staff-templates' ) { $options{'choices'} = { map { $_ => $_ } getallthemes('intranet') }; } elsif ( $options{choices} eq 'patron-categories' ) { $options{choices} = { map { $_->categorycode => $_->description } Koha::Patron::Categories->search->as_list }; $add_blank = 1; } elsif ( $options{'choices'} eq 'authval' ) { if ( $options{'source'} ) { $options{'choices'} = { map { $_->authorised_value => $_->lib } Koha::AuthorisedValues->search( { category => $options{'source'} } )->as_list }; $add_blank = 1; } } else { die 'Unrecognized source of preference values: ' . $options{'choices'}; } } $value ||= 0; $chunk->{'type'} = ( $options{class} && $options{class} eq 'multiple' ) ? 'multiple' : 'select'; my @values; @values = split /,/, $value if defined($value); $chunk->{'CHOICES'} = [ sort { $a->{'text'} cmp $b->{'text'} } map { my $c = $_; { text => $options{'choices'}->{$c}, value => $c, selected => ( grep { $_ eq $c || ( $c eq '' && ( $value eq '0' || !$value ) ) } @values ) ? 1 : 0, } } keys %{ $options{'choices'} } ]; # Add a first blank value if needed unshift @{ $chunk->{CHOICES} }, { text => '', value => '', } if $add_blank && $chunk->{type} eq 'select'; } elsif ( $options{'multiple'} ) { my @values; @values = split /,/, $value if defined($value); $chunk->{type} = 'multiple'; $chunk->{CHOICES} = [ sort { $a->{'text'} cmp $b->{'text'} } map { my $option_value = $_; { text => $options{multiple}->{$option_value}, value => $option_value, selected => ( grep { $_ eq $option_value } @values ) ? 1 : 0, } } keys %{ $options{multiple} } ]; } elsif ( $options{'multiple_sortable'} ) { if ( ref( $options{'multiple_sortable'} ) eq '' ) { if ( $options{'multiple_sortable'} eq 'authval' && $options{'source'} ) { $options{'multiple_sortable'} = { map { $_->authorised_value => $_->lib } Koha::AuthorisedValues->search( { category => $options{'source'} } )->as_list }; } else { die 'Unrecognized source for multiple_sortable: ' . $options{'multiple_sortable'}; } } my @values; @values = split /,/, $value if defined($value); $chunk->{type} = 'multiple_sortable'; my @options = sort keys %{ $options{multiple_sortable} }; $chunk->{CHOICES} = [ map { my $option_value = $_; { text => $options{multiple_sortable}->{$option_value}, value => $option_value, selected => ( grep { $_ eq $option_value } @values ) ? 1 : 0, } } uniq( @values, @options ) ]; } $chunk->{ 'type_' . $chunk->{'type'} } = 1; return $chunk; } sub TransformPrefsToHTML { my ( $data, $searchfield ) = @_; my @lines; my $dbh = C4::Context->dbh; my $title = ( keys(%$data) )[0]; my $tab = $data->{$title}; $tab = { '' => $tab } if ( ref($tab) eq 'ARRAY' ); my @override_syspref_names; if ( exists( $ENV{OVERRIDE_SYSPREF_NAMES} ) && defined( $ENV{OVERRIDE_SYSPREF_NAMES} ) ) { @override_syspref_names = split /,/, $ENV{OVERRIDE_SYSPREF_NAMES}; } foreach my $group ( sort keys %$tab ) { if ($group) { push @lines, { is_group_title => 1, title => $group }; } foreach my $line ( @{ $tab->{$group} } ) { my @chunks; my @names; my @warnings; foreach my $piece (@$line) { if ( ref($piece) eq 'HASH' ) { my $name = $piece->{'pref'}; if ($name) { my $row = $dbh->selectrow_hashref( "SELECT value, type FROM systempreferences WHERE variable = ?", {}, $name ); my $value; if ( ( !defined($row) || ( !defined( $row->{'value'} ) && $row->{'type'} ne 'YesNo' ) ) && defined( $piece->{'default'} ) ) { $value = $piece->{'default'}; } else { $value = $row->{'value'}; } my $chunk = _get_chunk( $value, %$piece ); # No highlighting of inputs yet, but would be useful $chunk->{'highlighted'} = 1 if ( $searchfield && $name =~ /^$searchfield$/i ); if ( $name eq 'Pseudonymization' && !C4::Context->config('bcrypt_settings') ) { push @warnings, 'bcrypt_config_not_set'; $chunk->{disabled} = 1 unless $value; # Let disable if enabled } push @chunks, $chunk; my $name_entry = { name => $name }; if ($searchfield) { if ( $name =~ /^$searchfield$/i ) { $name_entry->{'jumped'} = 1; } elsif ( $name =~ /$searchfield/i ) { $name_entry->{'highlighted'} = 1; } } $name_entry->{'overridden'} = 1 if ( any { $name eq $_ } @override_syspref_names ); push @names, $name_entry; } else { push @chunks, $piece; } } else { if ($piece) { my $version = Koha::version(); my ( $major, $minor, $maintenance, $development ) = split( '\.', $version ); if ( $minor % 2 ) { $piece =~ s|__VERSION__|${major}_${minor}|g; } else { $piece =~ s|__VERSION__|main|g; } } push @chunks, { type_text => 1, contents => $piece }; } } push @lines, { CHUNKS => \@chunks, NAMES => \@names, WARNINGS => \@warnings, is_group_title => 0 }; } } return $title, \@lines; } sub _get_pref_files { my ( $input, $open_files ) = @_; my ( $htdocs, $theme, $lang, undef ) = C4::Templates::_get_template_file( 'admin/preferences/admin.pref', 'intranet', $input ); my %results; foreach my $file ( glob("$htdocs/$theme/$lang/modules/admin/preferences/*.pref") ) { my ($tab) = ( $file =~ /([a-z0-9_-]+)\.pref$/ ); $results{$tab} = $open_files ? IO::File->new( $file, 'r' ) : ''; } return %results; } sub SearchPrefs { my ( $input, $searchfield ) = @_; my @tabs; my %tab_files = _get_pref_files($input); our @terms = split( /\s+/, $searchfield ); foreach my $tab_name ( sort keys %tab_files ) { # FIXME Hum? # Force list context to remove 'uninitialized value in goto' warn coming from YAML::Syck; note that the other GetTab call is in list context too. The actual cause however is the null value for the pref OpacRenewalBranch in opac.pref my ($data) = GetTab( $input, $tab_name ); my $title = ( keys(%$data) )[0]; my $tab = $data->{$title}; $tab = { '' => $tab } if ( ref($tab) eq 'ARRAY' ); my $matched_groups; while ( my ( $group_title, $contents ) = each %$tab ) { if ( matches( $group_title, \@terms ) ) { $matched_groups->{$group_title} = $contents; next; } my @new_contents; foreach my $line (@$contents) { my $matched; foreach my $piece (@$line) { if ( ref($piece) eq 'HASH' ) { if ( !$piece->{'pref'} ) { next; } if ( matches( $piece->{'pref'}, \@terms ) ) { $matched = 1; } elsif ( ref( $piece->{'choices'} ) eq 'HASH' && grep( { $_ && matches( $_, \@terms ) } values( %{ $piece->{'choices'} } ) ) ) { $matched = 1; } } elsif ( matches( $piece, \@terms ) ) { $matched = 1; } last if ($matched); } push @new_contents, $line if ($matched); } $matched_groups->{$group_title} = \@new_contents if (@new_contents); } if ($matched_groups) { my ( $title, $LINES ) = TransformPrefsToHTML( { $title => $matched_groups }, $searchfield ); push @tabs, { tab => $tab, tab_title => $title, LINES => $LINES, tab_id => $tab_name }; } } return @tabs; } sub matches { my ( $text, $terms ) = @_; if ($text) { return !grep( { my $re = eval { qr|$_|i }; $re = qr|\Q$_\E| if $@; $text !~ m|$re|; } @$terms ); } } my $dbh = C4::Context->dbh; our $input = CGI->new; my ( $template, $borrowernumber, $cookie ) = get_template_and_user( { template_name => "admin/preferences.tt", query => $input, type => "intranet", flagsrequired => { parameters => 'manage_sysprefs' }, } ); my $op = $input->param('op') || ''; my $tab = $input->param('tab'); $tab ||= 'accounting'; # Ideally this should be "local-use" but preferences.pl # does not presently support local use preferences my $highlighted; if ( $op eq 'cud-save' ) { output_and_exit_if_error( $input, $cookie, $template, { check => 'csrf_token' } ); foreach my $param ( $input->param() ) { my ($pref) = ( $param =~ /pref_(.*)/ ); next if ( !defined($pref) ); my $value = join( ',', $input->param($param) ); C4::Context->set_preference( $pref, $value ); } print $input->redirect( '/cgi-bin/koha/admin/preferences.pl?tab=' . $tab ); exit; } my @TABS; if ( $op eq 'search' ) { my $searchfield = $input->param('searchfield'); $searchfield =~ s/\p{IsC}//g; $searchfield =~ s/\s+/ /; $searchfield =~ s/^\s+//; $searchfield =~ s/\s+$//; $template->param( searchfield => $searchfield ); @TABS = SearchPrefs( $input, $searchfield ); foreach my $tabh (@TABS) { $template->param( $tabh->{'tab'} => 1 ); } if (@TABS) { $tab = ''; # No need to load a particular tab, as we found results $template->param( search_jumped => 1 ) if ( $TABS[0]->{'search_jumped'} ); } else { my $local_use = Koha::Config::SysPrefs->search( { variable => { like => "%$searchfield%" } } )->count; if ($local_use) { print $input->redirect("/cgi-bin/koha/admin/systempreferences.pl"); exit; } $template->param( search_not_found => 1, ); } } if ($tab) { my ( $tab_title, $LINES ) = TransformPrefsToHTML( GetTab( $input, $tab ), $highlighted ); push @TABS, { tab_title => $tab_title, LINES => $LINES, tab_id => $tab }; $template->param( $tab => 1, tab => $tab, ); } $template->param( TABS => \@TABS, db_columns => Koha::Database::Columns->columns, ); output_html_with_http_headers $input, $cookie, $template->output;