Adding KOHAVERSION management.
[wip/koha-chris_n.git] / installer / install.pl
1 #!/usr/bin/perl -w # please develop with -w
2
3 #use diagnostics;
4
5 # use Install;
6 use InstallAuth;
7 use C4::Context;
8 use C4::Output;
9 use C4::Languages;
10
11 use strict;    # please develop with the strict pragma
12
13 use CGI;
14
15 my $query = new CGI;
16 my $step  = $query->param('step');
17
18 my $language = $query->param('language');
19 my ( $template, $loggedinuser, $cookie );
20
21 my $all_languages = getAllLanguages();
22
23 if ( defined($language) ) {
24     setlanguagecookie( $query, $language, "install.pl?step=1" );
25 }
26 ( $template, $loggedinuser, $cookie ) = get_template_and_user(
27     {
28         template_name => "installer/step" . ( $step ? $step : 1 ) . ".tmpl",
29         query         => $query,
30         type          => "intranet",
31         authnotrequired => 0,
32         debug           => 1,
33     }
34 );
35
36 my %info;
37 $info{'dbname'} = C4::Context->config("database");
38 $info{'dbms'} =
39   (   C4::Context->config("db_scheme")
40     ? C4::Context->config("db_scheme")
41     : "mysql" );
42 $info{'hostname'} = C4::Context->config("hostname");
43 ( $info{'hostname'}, $info{'port'} ) = ( $1, $2 )
44   if $info{'hostname'} =~ /([^:]*):([0-9]+)/;
45 $info{'user'}     = C4::Context->config("user");
46 $info{'password'} = C4::Context->config("pass");
47 my $dbh = DBI->connect(
48     "DBI:$info{dbms}:$info{dbname}:$info{hostname}"
49       . ( $info{port} ? ":$info{port}" : "" ),
50     $info{'user'}, $info{'password'}
51 );
52
53 if ( $step && $step == 1 ) {
54     #First Step
55     #Checking ALL perl Modules and services needed are installed.
56     #Whenever there is an error, adding a report to the page
57     # I suppose here that Apache user can access /usr/bin/
58     # If mysql or zebra are in some fancy directory not in PATH
59     # Performing a disk search.
60     $template->param( language => 1 );
61     my $problem;
62
63     unless ( $] >= 5.006001 ) {    # Bug 179
64         $template->param( "problems" => 1, "perlversion" => 1 );
65         $problem = 1;
66     }
67     unless ( -x "/usr/bin/perl" ) {
68         my $realperl = `which perl`;
69         $realperl = `find / -name perl` unless ($realperl);
70         chomp $realperl;
71         $template->param( "problems" => 1, 'perllocation' => 1 )
72           unless ($realperl);
73         $problem = 1 unless ($realperl);
74     }
75     unless ( -x "/usr/local/bin/mysql" ) {
76         my $mysql = `which mysql`;
77         $mysql = `find / -name mysql` unless ($mysql);
78         chomp $mysql;
79         $template->param( "problems" => 1, 'mysql' => 1 ) unless ($mysql);
80         $problem = 1 unless ($mysql);
81     }
82     unless ( -x "/usr/local/bin/zebraidx" || -x "/usr/local/bin/zebraidx-2.0" )
83     {
84         my $zebra = `which zebraidx`;
85         $zebra = `which zebraidx-2.0`       unless ($zebra);
86         $zebra = `find / -name "zebraidx*"` unless ($zebra);
87         chomp $zebra;
88         $template->param( "problems" => 1, 'zebra' => 1 ) unless ($zebra);
89         $problem = 1 unless ($zebra);
90     }
91     unless ( -x "/usr/local/bin/zebrasrv" || -x "/usr/local/bin/zebrasrv-2.0" )
92     {
93         my $zebra = `which zebrasrv`;
94         $zebra = `which zebrasrv-2.0`       unless ($zebra);
95         $zebra = `find / -name "zebrasrv*"` unless ($zebra);
96         chomp $zebra;
97         $template->param( "problems" => 1, 'zebra' => 1 ) unless ($zebra);
98         $problem = 1 unless ($zebra);
99     }
100     unless ( -x "/usr/local/bin/yaz-client" ) {
101         my $yaz = `which yaz-client`;
102         $yaz = `find / -name "yaz-client*"` unless ($yaz);
103         chomp $yaz;
104         $template->param( "problems" => 1, 'yaz' => 1 ) unless ($yaz);
105         $problem = 1 unless ($yaz);
106     }
107
108     # We could here use a special find
109     my @missing = ();
110     unless ( eval { require ZOOM } ) {
111         push @missing, { name => "ZOOM" };
112     }
113     unless ( eval { require LWP::Simple } ) {
114         push @missing, { name => "LWP::Simple" };
115     }
116     unless ( eval { require XML::Simple } ) {
117         push @missing, { name => "XML::Simple" };
118     }
119     unless ( eval { require MARC::File::XML } ) {
120         push @missing, { name => "MARC::File::XML" };
121     }
122     unless ( eval { require MARC::File::USMARC } ) {
123         push @missing, { name => "MARC::File::USMARC" };
124     }
125     unless ( eval { require DBI } ) {
126         push @missing, { name => "DBI" };
127     }
128     unless ( eval { require Date::Manip } ) {
129         push @missing, { name => "Date::Manip" };
130     }
131     unless ( eval { require DBD::mysql } ) {
132         push @missing, { name => "DBD::mysql" };
133     }
134     unless ( eval { require HTML::Template } ) {
135         push @missing, { name => "HTML::Template::Pro" };
136     }
137     unless ( eval { require HTML::Template } ) {
138         push @missing, { name => "Date::Calc" };
139     }
140     unless ( eval { require Digest::MD5 } ) {
141         push @missing, { name => "Digest::MD5" };
142     }
143     unless ( eval { require MARC::Record } ) {
144         push @missing, { name => "MARC::Record" };
145     }
146     unless ( eval { require Mail::Sendmail } ) {
147         push @missing, { name => "Mail::Sendmail", usagemail => 1 };
148     }
149     unless ( eval { require List::MoreUtils } ) {
150         push @missing, { name => "List::MoreUtils" };
151     }
152     unless ( eval { require XML::RSS } ) {
153         push @missing, { name => "XML::RSS" };
154     }
155
156 # The following modules are not mandatory, depends on how the library want to use Koha
157     unless ( eval { require PDF::API2 } ) {
158         if ( $#missing >= 0 ) {   # only when $#missing >= 0 so this isn't fatal
159             push @missing, { name => "PDF::API2", usagebarcode => 1 };
160         }
161     }
162     unless ( eval { require GD::Barcorde } ) {
163         if ( $#missing >= 0 ) {   # only when $#missing >= 0 so this isn't fatal
164             push @missing,
165               { name => "GD::Barcode", usagebarcode => 1, usagespine => 1 };
166         }
167     }
168     unless ( eval { require Data::Random } ) {
169         if ( $#missing >= 0 ) {   # only when $#missing >= 0 so this isn't fatal
170             push @missing, { name => "Data::Random", usagebarcode => 1 };
171         }
172     }
173     unless ( eval { require PDF::Reuse::Barcode } ) {
174         if ( $#missing >= 0 ) {   # only when $#missing >= 0 so this isn't fatal
175             push @missing, { name => "PDF::Reuse::Barcode", usagebarcode => 1 };
176         }
177     }
178     unless ( eval { require PDF::Report } ) {
179         if ( $#missing >= 0 ) {   # only when $#missing >= 0 so this isn't fatal
180             push @missing, { name => "PDF::Report", usagebarcode => 1 };
181         }
182     }
183     unless ( eval { require GD::Barcode::UPCE } ) {
184         if ( $#missing >= 0 ) {   # only when $#missing >= 0 so this isn't fatal
185             push @missing, { name => "GD::Barcode::UPCE", usagepine => 1 };
186         }
187     }
188     unless ( eval { require Net::LDAP } ) {
189         if ( $#missing >= 0 ) {   # only when $#missing >= 0 so this isn't fatal
190             push @missing, { name => "Net::LDAP", usageLDAP => 1 };
191         }
192     }
193
194     $template->param( missings => \@missing ) if ( scalar(@missing) > 0 );
195     $template->param( 'checkmodule' => 1 )
196       unless ( scalar(@missing) && $problem );
197
198 }
199 elsif ( $step && $step == 2 ) {
200 #
201 #STEP 2 Check Database conn~ection and access
202 #
203     $template->param(%info);
204     my $checkmysql = $query->param("checkmysql");
205     $template->param( 'mysqlconnection' => $checkmysql );
206     if ($checkmysql) {
207         if ($dbh) {
208
209             # Can connect to the mysql
210             $template->param( "checkdatabaseaccess" => 1 );
211             if ( $info{dbms} eq "mysql" ) {
212
213                 #Check if database created
214                 my $rv = $dbh->do("SHOW DATABASES LIKE \'$info{dbname}\'");
215                 if ( $rv == 1 ) {
216                     $template->param( 'checkdatabasecreated' => 1 );
217                 }
218
219                 #Check if user have all necessary grants on this database.
220                 my $rq =
221                   $dbh->prepare(
222                     "SHOW GRANTS FOR \'$info{user}\'\@'$info{hostname}'");
223                 $rq->execute;
224                 my $grantaccess;
225                 while ( my ($line) = $rq->fetchrow ) {
226                     my $dbname = $info{dbname};
227                     if ( $line =~ m/$dbname/ || index( $line, '*.*' ) > 0 ) {
228                         $grantaccess = 1
229                           if (
230                             index( $line, 'ALL PRIVILEGES' ) > 0
231                             || (   ( index( $line, 'SELECT' ) > 0 )
232                                 && ( index( $line, 'INSERT' ) > 0 )
233                                 && ( index( $line, 'UPDATE' ) > 0 )
234                                 && ( index( $line, 'DELETE' ) > 0 )
235                                 && ( index( $line, 'CREATE' ) > 0 )
236                                 && ( index( $line, 'DROP' ) > 0 ) )
237                           );
238                     }
239                 }
240                 unless ($grantaccess) {
241                     $rq =
242                       $dbh->prepare("SHOW GRANTS FOR \'$info{user}\'\@'\%'");
243                     $rq->execute;
244                     while ( my ($line) = $rq->fetchrow ) {
245                         my $dbname = $info{dbname};
246                         if ( $line =~ m/$dbname/ || index( $line, '*.*' ) > 0 )
247                         {
248                             $grantaccess = 1
249                               if (
250                                 index( $line, 'ALL PRIVILEGES' ) > 0
251                                 || (   ( index( $line, 'SELECT' ) > 0 )
252                                     && ( index( $line, 'INSERT' ) > 0 )
253                                     && ( index( $line, 'UPDATE' ) > 0 )
254                                     && ( index( $line, 'DELETE' ) > 0 )
255                                     && ( index( $line, 'CREATE' ) > 0 )
256                                     && ( index( $line, 'DROP' ) > 0 ) )
257                               );
258                         }
259                     }
260                 }
261                 $template->param( "checkgrantaccess" => $grantaccess );
262             }
263         }
264         else {
265             $template->param( "error" => DBI::err, "message" => DBI::errstr );
266         }
267     }
268 }
269 elsif ( $step && $step == 3 ) {
270 #
271 #
272 # STEP 3 : database setup
273 #
274
275     my $op = $query->param('op');
276     if ( $op && $op eq 'finish' ) {
277     my $kohaversion=C4::Context::KOHAVERSION;
278     # remove the 3 last . to have a Perl number
279     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
280     if (C4::Context->preference('Version')) {
281         warn "UPDATE Version";
282       my $finish=$dbh->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
283       $finish->execute($kohaversion);
284     } else {
285         warn "INSERT Version";
286       my $finish=$dbh->prepare("INSERT into systempreferences (variable,value,explanation) values ('Version',?,'The Koha database version. Don t change this value manually, it s holded by the webinstaller')");
287       $finish->execute($kohaversion);
288     }
289
290   # Installation is finished.
291   # We just deny anybody acess to install
292   # And we redirect people to mainpage.
293   # The installer wil have to relogin since we donot pass cookie to redirection.
294         $template->param( "$op" => 1 );
295     }
296     elsif ( $op && $op eq 'finished' ) {
297     #
298     #
299     # we have finished, just redirect to mainpage.
300     #
301     #
302         print $query->redirect("/cgi-bin/koha/mainpage.pl");
303         exit 1;
304     }
305     elsif ( $op && $op eq 'addframeworks' ) {
306     #
307     # 1ST install : insert the SQL files the user has selected
308     #
309
310         #Framework importing and reports
311         my $lang;
312         my %hashlevel;
313
314        # sort by filename -> prepend with numbers to specify order of insertion.
315         my @fnames = sort {
316             my @aa = split /\/|\\/, ($a);
317             my @bb = split /\/|\\/, ($b);
318             $aa[-1] lt $bb[-1]
319         } $query->param('framework');
320         $dbh->do('SET FOREIGN_KEY_CHECKS=0');
321         my $request =
322           $dbh->prepare(
323 "SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'"
324           );
325         $request->execute;
326         my ($systempreference) = $request->fetchrow;
327         foreach my $file (@fnames) {
328
329             #      warn $file;
330             undef $/;
331             my $strcmd = "mysql "
332               . ( $info{hostname} ? " -h $info{hostname} " : "" )
333               . ( $info{port}     ? " -P $info{port} "     : "" )
334               . ( $info{user}     ? " -u $info{user} "     : "" )
335               . ( $info{password} ? " -p$info{password}"   : "" )
336               . " $info{dbname} ";
337             my $error = qx($strcmd < $file 2>&1);
338             my @file = split qr(\/|\\), $file;
339             $lang = $file[ scalar(@file) - 3 ] unless ($lang);
340             my $level = $file[ scalar(@file) - 2 ];
341             unless ($error) {
342                 $systempreference .= "$file[scalar(@file)-1]|"
343                   unless (
344                     index( $systempreference, $file[ scalar(@file) - 1 ] ) >=
345                     0 );
346             }
347
348             #Bulding here a hierarchy to display files by level.
349             push @{ $hashlevel{$level} },
350               { "fwkname" => $file[ scalar(@file) - 1 ], "error" => $error };
351         }
352
353         #systempreference contains an ending |
354         chop $systempreference;
355         my @list;
356         map { push @list, { "level" => $_, "fwklist" => $hashlevel{$_} } }
357           keys %hashlevel;
358         my $fwk_language;
359         for my $each_language (@$all_languages) {
360
361             #           warn "CODE".$each_language->{'language_code'};
362             #           warn "LANG:".$lang;
363             if ( $lang eq $each_language->{'language_code'} ) {
364                 $fwk_language = $each_language->{language_locale_name};
365             }
366         }
367         my $updateflag =
368           $dbh->do(
369 "UPDATE systempreferences set value=\"$systempreference\" where variable='FrameworksLoaded'"
370           );
371         unless ( $updateflag == 1 ) {
372             my $string =
373 "INSERT INTO systempreferences (value, variable, explanation, type) VALUES (\"$systempreference\",'FrameworksLoaded','Frameworks loaded through webinstaller','choice')";
374             my $rq = $dbh->prepare($string);
375             $rq->execute;
376         }
377         $template->param(
378             "fwklanguage" => $fwk_language,
379             "list"        => \@list
380         );
381         $template->param( "$op" => 1 );
382         $dbh->do('SET FOREIGN_KEY_CHECKS=1');
383     }
384     elsif ( $op && $op eq 'selectframeworks' ) {
385 #
386 #
387 # 1ST install : show the user the sql datas he can insert in the database.
388 #
389 #
390 # (note that the term "selectframeworks is not correct. The user can select various files, not only frameworks)
391
392 #Framework Selection
393 #sql data for import are supposed to be located in misc/sql-datas/<language>/<level>
394 # Where <language> is en|fr or any international abbreviation (provided language hash is updated... This will be a problem with internationlisation.)
395 # Where <level> is a category of requirement : required, recommended optional
396 # level should contain :
397 #   SQL File for import With a readable name.
398 #   txt File taht explains what this SQL File is meant for.
399 # Could be VERY useful to have A Big file for a kind of library.
400 # But could also be useful to have some Authorised values data set prepared here.
401 # Framework Selection is achieved through checking boxes.
402         my $langchoice = $query->param('fwklanguage');
403         $langchoice = $query->cookie('KohaOpacLanguage') unless ($langchoice);
404         my $dir = C4::Context->config('intranetdir') . "/misc/sql-datas/";
405         opendir( MYDIR, $dir );
406         my @listdir = grep { !/^\.|CVS/ && -d "$dir/$_" } readdir(MYDIR);
407         closedir MYDIR;
408         my $frmwklangs = getFrameworkLanguages();
409         my @languages;
410         map {
411             push @languages,
412               {
413                 'dirname'             => $_->{'language_code'},
414                 'languagedescription' => $_->{'language_name'},
415                 'checked' => ( $_->{'language_code'} eq $langchoice )
416               }
417               if ( $_->{'language_code'} );
418         } @$frmwklangs;
419         $template->param( "languagelist" => \@languages );
420         undef $/;
421         $dir =
422           C4::Context->config('intranetdir') . "/misc/sql-datas/$langchoice";
423         opendir( MYDIR, $dir ) || warn "no open $dir";
424         @listdir = grep { !/^\.|CVS/ && -d "$dir/$_" } readdir(MYDIR);
425         closedir MYDIR;
426         my @levellist;
427         my $request =
428           $dbh->prepare(
429 "SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'"
430           );
431         $request->execute;
432         my ($frameworksloaded) = $request->fetchrow;
433         my %frameworksloaded;
434
435         foreach ( split( /\|/, $frameworksloaded ) ) {
436             $frameworksloaded{$_} = 1;
437         }
438         foreach my $requirelevel (@listdir) {
439             $dir =
440               C4::Context->config('intranetdir')
441               . "/misc/sql-datas/$langchoice/$requirelevel";
442             opendir( MYDIR, $dir );
443             my @listname =
444               grep { !/^\.|CVS/ && -f "$dir/$_" && $_ =~ m/\.sql$/ }
445               readdir(MYDIR);
446             closedir MYDIR;
447             my %cell;
448             my @frameworklist;
449             map {
450                 my $name = substr( $_, 0, -4 );
451                 open FILE, "< $dir/$name.txt";
452                 my $lines = <FILE>;
453                 $lines =~ s/\n|\r/<br \/>/g;
454                 use utf8;
455                 utf8::encode($lines) unless ( utf8::is_utf8($lines) );
456                 push @frameworklist,
457                   {
458                     'fwkname'        => $name,
459                     'fwkfile'        => "$dir/$_",
460                     'fwkdescription' => $lines,
461                     'checked'        => (
462                         (
463                             $frameworksloaded{$_}
464                               || ( $requirelevel =~
465                                 /(mandatory|requi|oblig|necess)/i )
466                         ) ? 1 : 0
467                     )
468                   };
469             } @listname;
470             my @fwks =
471               sort { $a->{'fwkname'} lt $b->{'fwkname'} } @frameworklist;
472
473   #       $cell{"mandatory"}=($requirelevel=~/(mandatory|requi|oblig|necess)/i);
474             $cell{"frameworks"} = \@fwks;
475             $cell{"label"}      = ucfirst($requirelevel);
476             $cell{"code"}       = lc($requirelevel);
477             push @levellist, \%cell;
478         }
479         $template->param( "levelloop" => \@levellist );
480         $template->param( "$op"       => 1 );
481     }
482     elsif ( $op && $op eq 'updatestructure' ) {
483
484         #Do updatedatabase And report
485         my $execstring =
486           C4::Context->config("intranetdir") . "/updater/updatedatabase";
487         undef $/;
488         my $string = qx|$execstring 2>&1|;
489         if ($string) {
490             $string =~ s/\n|\r/<br \/>/g;
491             $string =~
492 s/(DBD::mysql.*? failed: .*? line [0-9]*.|=================.*?====================)/<font color=red>$1<\/font>/g;
493             $template->param( "updatereport" => $string );
494         }
495         $template->param( $op => 1 );
496     }
497     elsif ( $op && $op eq 'importdatastructure' ) {
498     #
499     #
500     # UPDATE (not 1st install) run updatedatabase
501     #
502     #
503
504         #Import data structure and show errors if any
505         #Uses DBI to read the file [MJR 2007-07-01]
506         my $dbh = DBI->connect(
507             "DBI:$info{dbms}:$info{dbname}:$info{hostname}"
508               . ( $info{port} ? ":$info{port}" : "" ),
509             $info{'user'}, $info{'password'}
510         );
511         open( INPUT, "<kohastructure.sql" );
512         my $file = do { local $/ = undef; <INPUT> };
513         my @commands = split( /;/, $file );
514         pop @commands;
515         map { $dbh->do($_) } @commands;
516         close(INPUT);
517         $template->param(
518             "error" => $dbh->errstr,
519             "$op"   => 1,
520         );
521         $dbh->disconnect;
522     }
523     else {
524
525 #Check if there are enough tables.
526 # Paul has cleaned up tables so reduced the count
527 #I put it there because it implied a data import if condition was not satisfied.
528         my $dbh = DBI->connect(
529             "DBI:$info{dbms}:$info{dbname}:$info{hostname}"
530               . ( $info{port} ? ":$info{port}" : "" ),
531             $info{'user'}, $info{'password'}
532         );
533         my $rq = $dbh->prepare( "SHOW TABLES FROM " . $info{'dbname'} );
534         $rq->execute;
535         my $data = $rq->fetchall_arrayref( {} );
536         my $count = scalar(@$data);
537         #
538         # we don't have tables, propose DB import
539         #
540         if ( $count < 70 ) {
541             $template->param( "count" => $count, "proposeimport" => 1 );
542         }
543         else {
544             #
545             # we have tables, propose to select files to upload or updatedatabase
546             #
547             $template->param( "count" => $count, "default" => 1 );
548             #
549             # 1st part of step 3 : check if there is a databaseversion systempreference
550             # if there is, then we just need to upgrade
551             # if there is none, then we need to install the database
552             #
553             my $dbversion = C4::Context->preference('Version');
554             $dbversion =~ /(.*)\.(..)(..)(...)/;
555             $dbversion = "$1.$2.$3.$4";
556             if (C4::Context->preference('Version')) {
557                 $template->param("upgrading" => 1,
558                                 "dbversion" => $dbversion,
559                                 "kohaversion" => C4::Context->KOHAVERSION,
560                                 );
561             }
562         }
563
564         $dbh->disconnect;
565     }
566 }
567 else {
568
569     # LANGUAGE SELECTION page by default
570     # using opendir + language Hash
571
572     my $langavail = getTranslatedLanguages();
573
574     my @languages;
575     foreach (@$langavail) {
576         push @languages,
577           {
578             'value'       => $_->{'language_code'},
579             'description' => $_->{'language_name'}
580           }
581           if ( $_->{'language_code'} );
582     }
583     $template->param( languages => \@languages );
584     if ($dbh) {
585         my $rq =
586           $dbh->prepare(
587             "SELECT * from systempreferences WHERE variable='Version'");
588         if ( $rq->execute ) {
589             my ($version) = $rq->fetchrow;
590             if ($version) {
591                 $query->redirect("install.pl?step=3");
592             }
593         }
594     }
595 }
596 output_html_with_http_headers $query, $cookie, $template->output;