Bug 18674: TZ error handling
[koha-equinox.git] / about.pl
1 #!/usr/bin/perl
2
3 # Copyright Pat Eyler 2003
4 # Copyright Biblibre 2006
5 # Parts Copyright Liblime 2008
6 # Parts Copyright Chris Nighswonger 2010
7 #
8 # This file is part of Koha.
9 #
10 # Koha is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # Koha is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22
23 use Modern::Perl;
24
25 use CGI qw ( -utf8 );
26 use DateTime::TimeZone;
27 use List::MoreUtils qw/ any /;
28 use LWP::Simple;
29 use XML::Simple;
30 use Config;
31 use Search::Elasticsearch;
32 use Try::Tiny;
33
34 use C4::Output;
35 use C4::Auth;
36 use C4::Context;
37 use C4::Installer;
38
39 use Koha;
40 use Koha::Acquisition::Currencies;
41 use Koha::Patron::Categories;
42 use Koha::Patrons;
43 use Koha::Caches;
44 use Koha::Config::SysPrefs;
45 use Koha::Illrequest::Config;
46 use Koha::SearchEngine::Elasticsearch;
47
48 use C4::Members::Statistics;
49
50
51 #use Smart::Comments '####';
52
53 my $query = new CGI;
54 my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
55     {
56         template_name   => "about.tt",
57         query           => $query,
58         type            => "intranet",
59         authnotrequired => 0,
60         flagsrequired   => { catalogue => 1 },
61         debug           => 1,
62     }
63 );
64
65 my $config_timezone = C4::Context->config('timezone');
66 my $config_invalid  = !DateTime::TimeZone->is_valid_name( $config_timezone );
67 my $env_timezone    = $ENV{TZ};
68 my $env_invalid     = !DateTime::TimeZone->is_valid_name( $env_timezone );
69 my $actual_bad_tz_fallback = 0;
70
71 if ( $config_timezone ne '' &&
72      $config_invalid ) {
73     # Bad config
74     $actual_bad_tz_fallback = 1;
75 }
76 elsif ( $config_timezone eq '' &&
77         $env_timezone    ne '' &&
78         $env_invalid ) {
79     # No config, but bad ENV{TZ}
80     $actual_bad_tz_fallback = 1;
81 }
82
83 my $time_zone = {
84     actual                 => C4::Context->timezone(),
85     actual_bad_tz_fallback => $actual_bad_tz_fallback,
86     config                 => $config_timezone,
87     config_invalid         => $config_invalid,
88     environment            => $env_timezone,
89     environment_invalid    => $env_invalid
90 };
91 $template->param( 'time_zone' => $time_zone );
92
93 my $perl_path = $^X;
94 if ($^O ne 'VMS') {
95     $perl_path .= $Config{_exe} unless $perl_path =~ m/$Config{_exe}$/i;
96 }
97
98 my $zebraVersion = `zebraidx -V`;
99
100 # Check running PSGI env
101 if ( any { /(^psgi\.|^plack\.)/i } keys %ENV ) {
102     $template->param(
103         is_psgi => 1,
104         psgi_server => ($ENV{ PLACK_ENV }) ? "Plack ($ENV{PLACK_ENV})" :
105                        ($ENV{ MOD_PERL })  ? "mod_perl ($ENV{MOD_PERL})" :
106                                              'Unknown'
107     );
108 }
109
110 # Memcached configuration
111 my $memcached_servers   = $ENV{MEMCACHED_SERVERS} || C4::Context->config('memcached_servers');
112 my $memcached_namespace = $ENV{MEMCACHED_NAMESPACE} || C4::Context->config('memcached_namespace') // 'koha';
113
114 my $cache = Koha::Caches->get_instance;
115 my $effective_caching_method = ref($cache->cache);
116 # Memcached may have been running when plack has been initialized but could have been stopped since
117 # FIXME What are the consequences of that??
118 my $is_memcached_still_active = $cache->set_in_cache('test_for_about_page', "just a simple value");
119
120 my $where_is_memcached_config = 'nowhere';
121 if ( $ENV{MEMCACHED_SERVERS} and C4::Context->config('memcached_servers') ) {
122     $where_is_memcached_config = 'both';
123 } elsif ( $ENV{MEMCACHED_SERVERS} and not C4::Context->config('memcached_servers') ) {
124     $where_is_memcached_config = 'ENV_only';
125 } elsif ( C4::Context->config('memcached_servers') ) {
126     $where_is_memcached_config = 'config_only';
127 }
128
129 $template->param(
130     effective_caching_method => $effective_caching_method,
131     memcached_servers   => $memcached_servers,
132     memcached_namespace => $memcached_namespace,
133     is_memcached_still_active => $is_memcached_still_active,
134     where_is_memcached_config => $where_is_memcached_config,
135     memcached_running   => Koha::Caches->get_instance->memcached_cache,
136 );
137
138 # Additional system information for warnings
139
140 my $warnStatisticsFieldsError;
141 my $prefStatisticsFields = C4::Context->preference('StatisticsFields');
142 if ($prefStatisticsFields) {
143     $warnStatisticsFieldsError = $prefStatisticsFields
144         unless ( $prefStatisticsFields eq C4::Members::Statistics->get_fields() );
145 }
146
147 my $prefAutoCreateAuthorities = C4::Context->preference('AutoCreateAuthorities');
148 my $prefBiblioAddsAuthorities = C4::Context->preference('BiblioAddsAuthorities');
149 my $warnPrefBiblioAddsAuthorities = ( $prefAutoCreateAuthorities && ( !$prefBiblioAddsAuthorities) );
150
151 my $prefEasyAnalyticalRecords  = C4::Context->preference('EasyAnalyticalRecords');
152 my $prefUseControlNumber  = C4::Context->preference('UseControlNumber');
153 my $warnPrefEasyAnalyticalRecords  = ( $prefEasyAnalyticalRecords  && $prefUseControlNumber );
154 my $warnPrefAnonymousPatron = (
155     C4::Context->preference('OPACPrivacy')
156         and not C4::Context->preference('AnonymousPatron')
157 );
158
159 my $anonymous_patron = Koha::Patrons->find( C4::Context->preference('AnonymousPatron') );
160 my $warnPrefAnonymousPatron_PatronDoesNotExist = ( not $anonymous_patron and Koha::Patrons->search({ privacy => 2 })->count );
161
162 my $errZebraConnection = C4::Context->Zconn("biblioserver",0)->errcode();
163
164 my $warnIsRootUser   = (! $loggedinuser);
165
166 my $warnNoActiveCurrency = (! defined Koha::Acquisition::Currencies->get_active);
167
168 my @xml_config_warnings;
169
170 my $context = new C4::Context;
171
172 if ( ! defined C4::Context->config('zebra_bib_index_mode') ) {
173     push @xml_config_warnings, {
174         error => 'zebra_bib_index_mode_warn'
175     };
176     if ($context->{'server'}->{'biblioserver'}->{'config'} !~ /zebra-biblios-dom.cfg/) {
177         push @xml_config_warnings, {
178             error => 'zebra_bib_mode_seems_grs1'
179         };
180     }
181     else {
182         push @xml_config_warnings, {
183             error => 'zebra_bib_mode_seems_dom'
184         };
185     }
186 } else {
187     push @xml_config_warnings, { error => 'zebra_bib_grs_warn' }
188         if C4::Context->config('zebra_bib_index_mode') eq 'grs1';
189 }
190
191 if ( (C4::Context->config('zebra_bib_index_mode') eq 'dom') &&
192      ($context->{'server'}->{'biblioserver'}->{'config'} !~ /zebra-biblios-dom.cfg/) ) {
193
194     push @xml_config_warnings, {
195         error => 'zebra_bib_index_mode_mismatch_warn'
196     };
197 }
198
199 if ( (C4::Context->config('zebra_bib_index_mode') eq 'grs1') &&
200      ($context->{'server'}->{'biblioserver'}->{'config'} =~ /zebra-biblios-dom.cfg/) ) {
201
202     push @xml_config_warnings, {
203         error => 'zebra_bib_index_mode_mismatch_warn'
204     };
205 }
206
207 if ( ! defined C4::Context->config('zebra_auth_index_mode') ) {
208     push @xml_config_warnings, {
209         error => 'zebra_auth_index_mode_warn'
210     };
211     if ($context->{'server'}->{'authorityserver'}->{'config'} !~ /zebra-authorities-dom.cfg/) {
212         push @xml_config_warnings, {
213             error => 'zebra_auth_mode_seems_grs1'
214         };
215     }
216     else {
217         push @xml_config_warnings, {
218             error => 'zebra_auth_mode_seems_dom'
219         };
220     }
221 } else {
222     push @xml_config_warnings, { error => 'zebra_auth_grs_warn' }
223         if C4::Context->config('zebra_auth_index_mode') eq 'grs1';
224 }
225
226 if ( (C4::Context->config('zebra_auth_index_mode') eq 'dom') && ($context->{'server'}->{'authorityserver'}->{'config'} !~ /zebra-authorities-dom.cfg/) ) {
227     push @xml_config_warnings, {
228         error => 'zebra_auth_index_mode_mismatch_warn'
229     };
230 }
231
232 if ( (C4::Context->config('zebra_auth_index_mode') eq 'grs1') && ($context->{'server'}->{'authorityserver'}->{'config'} =~ /zebra-authorities-dom.cfg/) ) {
233     push @xml_config_warnings, {
234         error => 'zebra_auth_index_mode_mismatch_warn'
235     };
236 }
237
238 if ( ! defined C4::Context->config('log4perl_conf') ) {
239     push @xml_config_warnings, {
240         error => 'log4perl_entry_missing'
241     }
242 }
243
244 if ( ! defined C4::Context->config('upload_path') ) {
245     if ( Koha::Config::SysPrefs->find('OPACBaseURL')->value ) {
246         # OPACBaseURL seems to be set
247         push @xml_config_warnings, {
248             error => 'uploadpath_entry_missing'
249         }
250     } else {
251         push @xml_config_warnings, {
252             error => 'uploadpath_and_opacbaseurl_entry_missing'
253         }
254     }
255 }
256
257 # Test QueryParser configuration sanity
258 if ( C4::Context->preference( 'UseQueryParser' ) ) {
259     # Get the QueryParser configuration file name
260     my $queryparser_file          = C4::Context->config( 'queryparser_config' );
261     my $queryparser_fallback_file = '/etc/koha/searchengine/queryparser.yaml';
262     # Check QueryParser is functional
263     my $QParser = C4::Context->queryparser();
264     my $queryparser_error = {};
265     if ( ! defined $QParser || ref($QParser) ne 'Koha::QueryParser::Driver::PQF' ) {
266         # Error initializing the QueryParser object
267         # Get the used queryparser.yaml file path to report the user
268         $queryparser_error->{ fallback } = ( defined $queryparser_file ) ? 0 : 1;
269         $queryparser_error->{ file }     = ( defined $queryparser_file )
270                                                 ? $queryparser_file
271                                                 : $queryparser_fallback_file;
272         # Report error data to the template
273         $template->param( QueryParserError => $queryparser_error );
274     } else {
275         # Check for an absent queryparser_config entry in koha-conf.xml
276         if ( ! defined $queryparser_file ) {
277             # Not an error but a warning for the missing entry in koha-conf-xml
278             push @xml_config_warnings, {
279                     error => 'queryparser_entry_missing',
280                     file  => $queryparser_fallback_file
281             };
282         }
283     }
284 }
285
286 # Test Zebra facets configuration
287 if ( !defined C4::Context->config('use_zebra_facets') ) {
288     push @xml_config_warnings, { error => 'use_zebra_facets_entry_missing' };
289 } else {
290     if ( C4::Context->config('use_zebra_facets') &&
291          C4::Context->config('zebra_bib_index_mode') ) {
292         # use_zebra_facets works with DOM
293         push @xml_config_warnings, {
294             error => 'use_zebra_facets_needs_dom'
295         } if C4::Context->config('zebra_bib_index_mode') ne 'dom' ;
296     }
297 }
298
299 # ILL module checks
300 if ( C4::Context->preference('ILLModule') ) {
301     my $warnILLConfiguration = 0;
302     my $ill_config_from_file = C4::Context->config("interlibrary_loans");
303     my $ill_config = Koha::Illrequest::Config->new;
304
305     my $available_ill_backends =
306       ( scalar @{ $ill_config->available_backends } > 0 );
307
308     # Check backends
309     if ( !$available_ill_backends ) {
310         $template->param( no_ill_backends => 1 );
311         $warnILLConfiguration = 1;
312     }
313
314     # Check partner_code
315     if ( !Koha::Patron::Categories->find($ill_config->partner_code) ) {
316         $template->param( ill_partner_code_doesnt_exist => $ill_config->partner_code );
317         $warnILLConfiguration = 1;
318     }
319
320     if ( !$ill_config_from_file->{partner_code} ) {
321         # partner code not defined
322         $template->param( ill_partner_code_not_defined => 1 );
323         $warnILLConfiguration = 1;
324     }
325
326     $template->param( warnILLConfiguration => $warnILLConfiguration );
327 }
328
329 if ( C4::Context->preference('SearchEngine') eq 'Elasticsearch' ) {
330     # Check ES configuration health and runtime status
331
332     my $es_status;
333     my $es_config_error;
334     my $es_running = 1;
335
336     my $es_conf;
337     try {
338         $es_conf = Koha::SearchEngine::Elasticsearch::_read_configuration();
339     }
340     catch {
341         if ( ref($_) eq 'Koha::Exceptions::Config::MissingEntry' ) {
342             $template->param( elasticsearch_fatal_config_error => $_->message );
343             $es_config_error = 1;
344         }
345     };
346     if ( !$es_config_error ) {
347
348         my $biblios_index_name     = $es_conf->{index_name} . "_" . $Koha::SearchEngine::BIBLIOS_INDEX;
349         my $authorities_index_name = $es_conf->{index_name} . "_" . $Koha::SearchEngine::AUTHORITIES_INDEX;
350
351         my @indexes = ($biblios_index_name, $authorities_index_name);
352         # TODO: When new indexes get added, we could have other ways to
353         #       fetch the list of available indexes (e.g. plugins, etc)
354         $es_status->{nodes} = $es_conf->{nodes};
355         my $es = Search::Elasticsearch->new({ nodes => $es_conf->{nodes} });
356
357         foreach my $index ( @indexes ) {
358             my $count;
359             try {
360                 $count = $es->indices->stats( index => $index )
361                       ->{_all}{primaries}{docs}{count};
362             }
363             catch {
364                 if ( ref($_) eq 'Search::Elasticsearch::Error::Missing' ) {
365                     push @{ $es_status->{errors} }, "Index not found ($index)";
366                     $count = -1;
367                 }
368                 elsif ( ref($_) eq 'Search::Elasticsearch::Error::NoNodes' ) {
369                     $es_running = 0;
370                 }
371                 else {
372                     # TODO: when time comes, we will cover more use cases
373                     die $_;
374                 }
375             };
376
377             push @{ $es_status->{indexes} },
378               {
379                 index_name => $index,
380                 count      => $count
381               };
382         }
383         $es_status->{running} = $es_running;
384
385         $template->param( elasticsearch_status => $es_status );
386     }
387 }
388
389 # Sco Patron should not contain any other perms than circulate => self_checkout
390 if (  C4::Context->preference('WebBasedSelfCheck')
391       and C4::Context->preference('AutoSelfCheckAllowed')
392 ) {
393     my $userid = C4::Context->preference('AutoSelfCheckID');
394     my $all_permissions = C4::Auth::get_user_subpermissions( $userid );
395     my ( $has_self_checkout_perm, $has_other_permissions );
396     while ( my ( $module, $permissions ) = each %$all_permissions ) {
397         if ( $module eq 'self_check' ) {
398             while ( my ( $permission, $flag ) = each %$permissions ) {
399                 if ( $permission eq 'self_checkout_module' ) {
400                     $has_self_checkout_perm = 1;
401                 } else {
402                     $has_other_permissions = 1;
403                 }
404             }
405         } else {
406             $has_other_permissions = 1;
407         }
408     }
409     $template->param(
410         AutoSelfCheckPatronDoesNotHaveSelfCheckPerm => not ( $has_self_checkout_perm ),
411         AutoSelfCheckPatronHasTooManyPerm => $has_other_permissions,
412     );
413 }
414
415 {
416     my $dbh       = C4::Context->dbh;
417     my $patrons = $dbh->selectall_arrayref(
418         q|select b.borrowernumber from borrowers b join deletedborrowers db on b.borrowernumber=db.borrowernumber|,
419         { Slice => {} }
420     );
421     my $biblios = $dbh->selectall_arrayref(
422         q|select b.biblionumber from biblio b join deletedbiblio db on b.biblionumber=db.biblionumber|,
423         { Slice => {} }
424     );
425     my $items = $dbh->selectall_arrayref(
426         q|select i.itemnumber from items i join deleteditems di on i.itemnumber=di.itemnumber|,
427         { Slice => {} }
428     );
429     my $checkouts = $dbh->selectall_arrayref(
430         q|select i.issue_id from issues i join old_issues oi on i.issue_id=oi.issue_id|,
431         { Slice => {} }
432     );
433     my $holds = $dbh->selectall_arrayref(
434         q|select r.reserve_id from reserves r join old_reserves o on r.reserve_id=o.reserve_id|,
435         { Slice => {} }
436     );
437     if ( @$patrons or @$biblios or @$items or @$checkouts or @$holds ) {
438         $template->param(
439             has_ai_issues => 1,
440             ai_patrons    => $patrons,
441             ai_biblios    => $biblios,
442             ai_items      => $items,
443             ai_checkouts  => $checkouts,
444             ai_holds      => $holds,
445         );
446     }
447 }
448 my %versions = C4::Context::get_versions();
449
450 $template->param(
451     kohaVersion   => $versions{'kohaVersion'},
452     osVersion     => $versions{'osVersion'},
453     perlPath      => $perl_path,
454     perlVersion   => $versions{'perlVersion'},
455     perlIncPath   => [ map { perlinc => $_ }, @INC ],
456     mysqlVersion  => $versions{'mysqlVersion'},
457     apacheVersion => $versions{'apacheVersion'},
458     zebraVersion  => $zebraVersion,
459     prefBiblioAddsAuthorities => $prefBiblioAddsAuthorities,
460     prefAutoCreateAuthorities => $prefAutoCreateAuthorities,
461     warnPrefBiblioAddsAuthorities => $warnPrefBiblioAddsAuthorities,
462     warnPrefEasyAnalyticalRecords  => $warnPrefEasyAnalyticalRecords,
463     warnPrefAnonymousPatron => $warnPrefAnonymousPatron,
464     warnPrefAnonymousPatron_PatronDoesNotExist => $warnPrefAnonymousPatron_PatronDoesNotExist,
465     errZebraConnection => $errZebraConnection,
466     warnIsRootUser => $warnIsRootUser,
467     warnNoActiveCurrency => $warnNoActiveCurrency,
468     warnNoTemplateCaching => ( C4::Context->config('template_cache_dir') ? 0 : 1 ),
469     xml_config_warnings => \@xml_config_warnings,
470     warnStatisticsFieldsError => $warnStatisticsFieldsError,
471 );
472
473 my @components = ();
474
475 my $perl_modules = C4::Installer::PerlModules->new;
476 $perl_modules->versions_info;
477
478 my @pm_types = qw(missing_pm upgrade_pm current_pm);
479
480 foreach my $pm_type(@pm_types) {
481     my $modules = $perl_modules->get_attr($pm_type);
482     foreach (@$modules) {
483         my ($module, $stats) = each %$_;
484         push(
485             @components,
486             {
487                 name    => $module,
488                 version => $stats->{'cur_ver'},
489                 missing => ($pm_type eq 'missing_pm' ? 1 : 0),
490                 upgrade => ($pm_type eq 'upgrade_pm' ? 1 : 0),
491                 current => ($pm_type eq 'current_pm' ? 1 : 0),
492                 require => $stats->{'required'},
493                 reqversion => $stats->{'min_ver'},
494             }
495         );
496     }
497 }
498
499 @components = sort {$a->{'name'} cmp $b->{'name'}} @components;
500
501 my $counter=0;
502 my $row = [];
503 my $table = [];
504 foreach (@components) {
505     push (@$row, $_);
506     unless (++$counter % 4) {
507         push (@$table, {row => $row});
508         $row = [];
509     }
510 }
511 # Processing the last line (if there are any modules left)
512 if (scalar(@$row) > 0) {
513     # Extending $row to the table size
514     $$row[3] = '';
515     # Pushing the last line
516     push (@$table, {row => $row});
517 }
518 ## ## $table
519
520 $template->param( table => $table );
521
522
523 ## ------------------------------------------
524 ## Koha time line code
525
526 #get file location
527 my $docdir;
528 if ( defined C4::Context->config('docdir') ) {
529     $docdir = C4::Context->config('docdir');
530 } else {
531     # if no <docdir> is defined in koha-conf.xml, use the default location
532     # this is a work-around to stop breakage on upgraded Kohas, bug 8911
533     $docdir = C4::Context->config('intranetdir') . '/docs';
534 }
535
536 if ( open( my $file, "<:encoding(UTF-8)", "$docdir" . "/history.txt" ) ) {
537
538     my $i = 0;
539
540     my @rows2 = ();
541     my $row2  = [];
542
543     my @lines = <$file>;
544     close($file);
545
546     shift @lines; #remove header row
547
548     foreach (@lines) {
549         my ( $epoch, $date, $desc, $tag ) = split(/\t/);
550         if(!$desc && $date=~ /(?<=\d{4})\s+/) {
551             ($date, $desc)= ($`, $');
552         }
553         push(
554             @rows2,
555             {
556                 date => $date,
557                 desc => $desc,
558             }
559         );
560     }
561
562     my $table2 = [];
563     #foreach my $row2 (@rows2) {
564     foreach  (@rows2) {
565         push (@$row2, $_);
566         push( @$table2, { row2 => $row2 } );
567         $row2 = [];
568     }
569
570     $template->param( table2 => $table2 );
571 } else {
572     $template->param( timeline_read_error => 1 );
573 }
574
575 output_html_with_http_headers $query, $cookie, $template->output;