Bug 13758: Move the Koha version from kohaversion.pl
[koha.git] / installer / install.pl
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use diagnostics;
6
7 use C4::InstallAuth;
8 use CGI qw ( -utf8 );
9 use POSIX qw(strftime);
10
11 use C4::Context;
12 use C4::Output;
13 use C4::Templates;
14 use C4::Languages qw(getAllLanguages getTranslatedLanguages);
15 use C4::Installer;
16
17 use Koha;
18
19 my $query = new CGI;
20 my $step  = $query->param('step');
21
22 my $language = $query->param('language');
23 my ( $template, $loggedinuser, $cookie );
24
25 my $all_languages = getAllLanguages();
26
27 if ( defined($language) ) {
28     C4::Templates::setlanguagecookie( $query, $language, "install.pl?step=1" );
29 }
30 ( $template, $loggedinuser, $cookie ) = get_template_and_user(
31     {
32         template_name => "installer/step" . ( $step ? $step : 1 ) . ".tt",
33         query         => $query,
34         type          => "intranet",
35         authnotrequired => 0,
36         debug           => 1,
37     }
38 );
39
40 my $installer = C4::Installer->new();
41 my %info;
42 $info{'dbname'} = C4::Context->config("database");
43 $info{'dbms'} =
44   (   C4::Context->config("db_scheme")
45     ? C4::Context->config("db_scheme")
46     : "mysql" );
47 $info{'hostname'} = C4::Context->config("hostname");
48 $info{'port'}     = C4::Context->config("port");
49 $info{'user'}     = C4::Context->config("user");
50 $info{'password'} = C4::Context->config("pass");
51 my $dbh = DBI->connect(
52     "DBI:$info{dbms}:dbname=$info{dbname};host=$info{hostname}"
53       . ( $info{port} ? ";port=$info{port}" : "" ),
54     $info{'user'}, $info{'password'}
55 );
56
57 if ( $step && $step == 1 ) {
58     #First Step
59     #Checking ALL perl Modules and services needed are installed.
60     #Whenever there is an error, adding a report to the page
61     $template->param( language => 1 );
62     $template->param( 'checkmodule' => 1 ); # we start with the assumption that there are no problems and set this to 0 if there are
63
64     unless ( $] >= 5.010000 ) {    # Bug 7375
65         $template->param( problems => 1, perlversion => 1, checkmodule => 0 );
66     }
67
68     my $perl_modules = C4::Installer::PerlModules->new;
69     $perl_modules->version_info;
70
71     my $modules = $perl_modules->get_attr('missing_pm');
72     if (scalar(@$modules)) {
73         my @components = ();
74         my $checkmodule = 1;
75         foreach (@$modules) {
76             my ($module, $stats) = each %$_;
77             $checkmodule = 0 if $stats->{'required'};
78             push(
79                 @components,
80                 {
81                     name    => $module,
82                     version => $stats->{'min_ver'},
83                     require => $stats->{'required'},
84                     usage   => $stats->{'usage'},
85                 }
86             );
87         }
88         @components = sort {$a->{'name'} cmp $b->{'name'}} @components;
89         $template->param( missing_modules => \@components, checkmodule => $checkmodule );
90     }
91 }
92 elsif ( $step && $step == 2 ) {
93 #
94 #STEP 2 Check Database connection and access
95 #
96     $template->param(%info);
97     my $checkdb = $query->param("checkdb");
98     $template->param( 'dbconnection' => $checkdb );
99     if ($checkdb) {
100         if ($dbh) {
101
102             # Can connect to the mysql
103             $template->param( "checkdatabaseaccess" => 1 );
104             if ( $info{dbms} eq "mysql" ) {
105
106                 #Check if database created
107                 my $rv = $dbh->do("SHOW DATABASES LIKE \'$info{dbname}\'");
108                 if ( $rv == 1 ) {
109                     $template->param( 'checkdatabasecreated' => 1 );
110                 }
111
112                 #Check if user have all necessary grants on this database.
113                 my $rq =
114                   $dbh->prepare(
115                     "SHOW GRANTS FOR \'$info{user}\'\@'$info{hostname}'");
116                 $rq->execute;
117                 my $grantaccess;
118                 while ( my ($line) = $rq->fetchrow ) {
119                     my $dbname = $info{dbname};
120                     if ( $line =~ m/^GRANT (.*?) ON `$dbname`\.\*/ || index( $line, '*.*' ) > 0 ) {
121                         $grantaccess = 1
122                           if (
123                             index( $line, 'ALL PRIVILEGES' ) > 0
124                             || (   ( index( $line, 'SELECT' ) > 0 )
125                                 && ( index( $line, 'INSERT' ) > 0 )
126                                 && ( index( $line, 'UPDATE' ) > 0 )
127                                 && ( index( $line, 'DELETE' ) > 0 )
128                                 && ( index( $line, 'CREATE' ) > 0 )
129                                 && ( index( $line, 'DROP' ) > 0 ) )
130                           );
131                     }
132                 }
133                 unless ($grantaccess) {
134                     $rq =
135                       $dbh->prepare("SHOW GRANTS FOR \'$info{user}\'\@'\%'");
136                     $rq->execute;
137                     while ( my ($line) = $rq->fetchrow ) {
138                         my $dbname = $info{dbname};
139                         if ( $line =~ m/$dbname/ || index( $line, '*.*' ) > 0 )
140                         {
141                             $grantaccess = 1
142                               if (
143                                 index( $line, 'ALL PRIVILEGES' ) > 0
144                                 || (   ( index( $line, 'SELECT' ) > 0 )
145                                     && ( index( $line, 'INSERT' ) > 0 )
146                                     && ( index( $line, 'UPDATE' ) > 0 )
147                                     && ( index( $line, 'DELETE' ) > 0 )
148                                     && ( index( $line, 'CREATE' ) > 0 )
149                                     && ( index( $line, 'DROP' ) > 0 ) )
150                               );
151                         }
152                     }
153                 }
154                 $template->param( "checkgrantaccess" => $grantaccess );
155             }   # End mysql connect check...
156
157             elsif ( $info{dbms} eq "Pg" ) {
158                 # Check if database has been created...
159                 my $rv = $dbh->do( "SELECT * FROM pg_catalog.pg_database WHERE datname = \'$info{dbname}\';" );
160                 if ( $rv == 1 ) {
161                         $template->param( 'checkdatabasecreated' => 1 );
162                 }
163
164                 # Check if user has all necessary grants on this database...
165                 my $rq = $dbh->do( "SELECT u.usesuper
166                                     FROM pg_catalog.pg_user as u
167                                     WHERE u.usename = \'$info{user}\';" );
168                 if ( $rq == 1 ) {
169                         $template->param( "checkgrantaccess" => 1 );
170                 }
171             }   # End Pg connect check...
172         }
173         else {
174             $template->param( "error" => DBI::err, "message" => DBI::errstr );
175         }
176     }
177 }
178 elsif ( $step && $step == 3 ) {
179 #
180 #
181 # STEP 3 : database setup
182 #
183 #
184     my $op = $query->param('op');
185     if ( $op && $op eq 'finished' ) {
186         #
187         # we have finished, just redirect to mainpage.
188         #
189         print $query->redirect("/cgi-bin/koha/mainpage.pl");
190         exit;
191     }
192     elsif ( $op && $op eq 'finish' ) {
193         $installer->set_version_syspref();
194
195         # Installation is finished.
196         # We just deny anybody access to install
197         # And we redirect people to mainpage.
198         # The installer will have to relogin since we do not pass cookie to redirection.
199         $template->param( "$op" => 1 );
200     }
201     elsif ( $op && $op eq 'addframeworks' ) {
202     #
203     # 1ST install, 3rd sub-step : insert the SQL files the user has selected
204     #
205
206         my ($fwk_language, $list) = $installer->load_sql_in_order($all_languages, $query->param('framework'));
207         $template->param(
208             "fwklanguage" => $fwk_language,
209             "list"        => $list
210         );
211         $template->param( "$op" => 1 );
212     }
213     elsif ( $op && $op eq 'selectframeworks' ) {
214         #
215         #
216         # 1ST install, 2nd sub-step : show the user the sql datas he can insert in the database.
217         #
218         #
219         # (note that the term "selectframeworks is not correct. The user can select various files, not only frameworks)
220
221         #Framework Selection
222         #sql data for import are supposed to be located in installer/data/<language>/<level>
223         # Where <language> is en|fr or any international abbreviation (provided language hash is updated... This will be a problem with internationlisation.)
224         # Where <level> is a category of requirement : required, recommended optional
225         # level should contain :
226         #   SQL File for import With a readable name.
227         #   txt File that explains what this SQL File is meant for.
228         # Could be VERY useful to have A Big file for a kind of library.
229         # But could also be useful to have some Authorised values data set prepared here.
230         # Framework Selection is achieved through checking boxes.
231         my $langchoice = $query->param('fwklanguage');
232         $langchoice = $query->cookie('KohaOpacLanguage') unless ($langchoice);
233         $langchoice =~ s/[^a-zA-Z_-]*//g;
234         my $marcflavour = $query->param('marcflavour');
235         if ($marcflavour){
236             $installer->set_marcflavour_syspref($marcflavour);
237         };
238         $marcflavour = C4::Context->preference('marcflavour') unless ($marcflavour);
239         #Insert into database the selected marcflavour
240         undef $/;
241         my ($marc_defaulted_to_en, $fwklist) = $installer->marc_framework_sql_list($langchoice, $marcflavour);
242         $template->param('en_marc_frameworks' => $marc_defaulted_to_en);
243         $template->param( "frameworksloop" => $fwklist );
244         $template->param( "marcflavour" => ucfirst($marcflavour));
245
246         my ($sample_defaulted_to_en, $levellist) = $installer->sample_data_sql_list($langchoice);
247         $template->param( "en_sample_data" => $sample_defaulted_to_en);
248         $template->param( "levelloop" => $levellist );
249         $template->param( "$op"       => 1 );
250     }
251     elsif ( $op && $op eq 'choosemarc' ) {
252         #
253         #
254         # 1ST install, 2nd sub-step : show the user the marcflavour available.
255         #
256         #
257
258         #Choose Marc Flavour
259         #sql data are supposed to be located in installer/data/<dbms>/<language>/marcflavour/marcflavourname
260         # Where <dbms> is database type according to DBD syntax
261         # Where <language> is en|fr or any international abbreviation (provided language hash is updated... This will be a problem with internationlisation.)
262         # Where <level> is a category of requirement : required, recommended optional
263         # level should contain :
264         #   SQL File for import With a readable name.
265         #   txt File taht explains what this SQL File is meant for.
266         # Could be VERY useful to have A Big file for a kind of library.
267         # But could also be useful to have some Authorised values data set prepared here.
268         # Marcflavour Selection is achieved through radiobuttons.
269         my $langchoice = $query->param('fwklanguage');
270         $langchoice = $query->cookie('KohaOpacLanguage') unless ($langchoice);
271         $langchoice =~ s/[^a-zA-Z_-]*//g;
272         my $dir =
273           C4::Context->config('intranetdir') . "/installer/data/$info{dbms}/$langchoice/marcflavour";
274         unless (opendir( MYDIR, $dir )) {
275             if ($langchoice eq 'en') {
276                 warn "cannot open MARC frameworks directory $dir";
277             } else {
278                 # if no translated MARC framework is available,
279                 # default to English
280                 $dir = C4::Context->config('intranetdir') . "/installer/data/$info{dbms}/en/marcflavour";
281                 opendir(MYDIR, $dir) or warn "cannot open English MARC frameworks directory $dir";
282             }
283         }
284         my @listdir = grep { !/^\./ && -d "$dir/$_" } readdir(MYDIR);
285         closedir MYDIR;
286         my $marcflavour=C4::Context->preference("marcflavour");
287         my @flavourlist;
288         foreach my $marc (@listdir) {
289             my %cell=(
290             "label"=> ucfirst($marc),
291             "code"=>uc($marc),
292             "checked"=> defined($marcflavour) ? uc($marc) eq $marcflavour : 0);
293 #             $cell{"description"}= do { local $/ = undef; open INPUT "<$dir/$marc.txt"||"";<INPUT> };
294             push @flavourlist, \%cell;
295         }
296         $template->param( "flavourloop" => \@flavourlist );
297         $template->param( "$op"       => 1 );
298     }
299     elsif ( $op && $op eq 'importdatastructure' ) {
300         #
301         #
302         # 1st install, 1st "sub-step" : import kohastructure
303         #
304         #
305         my $error = $installer->load_db_schema();
306         $template->param(
307             "error" => $error,
308             "$op"   => 1,
309         );
310     }
311     elsif ( $op && $op eq 'updatestructure' ) {
312         #
313         # Not 1st install, the only sub-step : update database
314         #
315         #Do updatedatabase And report
316
317     if ( ! defined $ENV{PERL5LIB} ) {
318         my $find = "C4/Context.pm";
319         my $path = $INC{$find};
320         $path =~ s/\Q$find\E//;
321         $ENV{PERL5LIB} = "$path:$path/installer";
322         warn "# plack? inserted PERL5LIB $ENV{PERL5LIB}\n";
323     }
324
325         my $now = POSIX::strftime( "%Y-%m-%dT%H:%M:%S", localtime() );
326         my $logdir = C4::Context->config('logdir');
327         my $dbversion = C4::Context->preference('Version');
328         my $kohaversion = C4::Context->KOHAVERSION;
329         $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
330
331         my $filename_suffix = join '_', $now, $dbversion, $kohaversion;
332         my ( $logfilepath, $logfilepath_errors ) = ( chk_log($logdir, "updatedatabase_$filename_suffix"), chk_log($logdir, "updatedatabase-error_$filename_suffix") );
333
334         my $cmd = C4::Context->config("intranetdir") . "/installer/data/$info{dbms}/updatedatabase.pl >> $logfilepath 2>> $logfilepath_errors";
335
336         system($cmd );
337
338         my $fh;
339         open( $fh, "<", $logfilepath ) or die "Cannot open log file $logfilepath: $!";
340         my @report = <$fh>;
341         close $fh;
342         if (@report) {
343             $template->param( update_report => [ map { { line => $_ } } split( /\n/, join( '', @report ) ) ] );
344             $template->param( has_update_succeeds => 1 );
345         } else {
346             eval{ `rm $logfilepath` };
347         }
348         open( $fh, "<", $logfilepath_errors ) or die "Cannot open log file $logfilepath_errors: $!";
349         @report = <$fh>;
350         close $fh;
351         if (@report) {
352             $template->param( update_errors => [ map { { line => $_ } } split( /\n/, join( '', @report ) ) ] );
353             $template->param( has_update_errors => 1 );
354             warn "The following errors were returned while attempting to run the updatedatabase.pl script:\n";
355             foreach my $line (@report) { warn "$line\n"; }
356         } else {
357             eval{ `rm $logfilepath_errors` };
358         }
359         $template->param( $op => 1 );
360     }
361     else {
362         #
363         # check whether it's a 1st install or an update
364         #
365         #Check if there are enough tables.
366         # Paul has cleaned up tables so reduced the count
367         #I put it there because it implied a data import if condition was not satisfied.
368         my $dbh = DBI->connect(
369                 "DBI:$info{dbms}:dbname=$info{dbname};host=$info{hostname}"
370                 . ( $info{port} ? ";port=$info{port}" : "" ),
371                 $info{'user'}, $info{'password'}
372         );
373         my $rq;
374         if ( $info{dbms} eq 'mysql' ) { $rq = $dbh->prepare( "SHOW TABLES" ); }
375         elsif ( $info{dbms} eq 'Pg' ) { $rq = $dbh->prepare( "SELECT *
376                                                                 FROM information_schema.tables
377                                                                 WHERE table_schema='public' and table_type='BASE TABLE';" ); }
378         $rq->execute;
379         my $data = $rq->fetchall_arrayref( {} );
380         my $count = scalar(@$data);
381         #
382         # we don't have tables, propose DB import
383         #
384         if ( $count < 70 ) {
385             $template->param( "count" => $count, "proposeimport" => 1 );
386         }
387         else {
388             #
389             # we have tables, propose to select files to upload or updatedatabase
390             #
391             $template->param( "count" => $count, "default" => 1 );
392             #
393             # 1st part of step 3 : check if there is a databaseversion systempreference
394             # if there is, then we just need to upgrade
395             # if there is none, then we need to install the database
396             #
397             if (C4::Context->preference('Version')) {
398                 my $dbversion = C4::Context->preference('Version');
399                 $dbversion =~ /(.*)\.(..)(..)(...)/;
400                 $dbversion = "$1.$2.$3.$4";
401                 $template->param("upgrading" => 1,
402                                 "dbversion" => $dbversion,
403                                 "kohaversion" => Koha::version(),
404                                 );
405             }
406         }
407     }
408 }
409 else {
410
411     # LANGUAGE SELECTION page by default
412     # using opendir + language Hash
413     my $languages_loop = getTranslatedLanguages('intranet');
414     $template->param( installer_languages_loop => $languages_loop );
415     if ($dbh) {
416         my $rq =
417           $dbh->prepare(
418             "SELECT * from systempreferences WHERE variable='Version'");
419         if ( $rq->execute ) {
420             my ($version) = $rq->fetchrow;
421             if ($version) {
422                 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
423                 exit;
424             }
425         }
426     }
427 }
428 output_html_with_http_headers $query, $cookie, $template->output;
429
430 sub chk_log { #returns a logfile in $dir or - if that failed - in temp dir
431     my ($dir, $name) = @_;
432     my $fn=$dir.'/'.$name.'.log';
433     if( ! open my $fh, '>', $fn ) {
434         $name.= '_XXXX';
435         require File::Temp;
436         ($fh, $fn)= File::Temp::tempfile( $name, TMPDIR => 1, SUFFIX => '.log');
437         #if this should not work, let croak take over
438     }
439     return $fn;
440 }