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