3 # Copyright (C) 2008 LibLime
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #use warnings; FIXME - Bug 2505
25 use C4::Installer::PerlModules 1.000000;
35 my $installer = C4::Installer->new();
37 my $all_languages = getAllLanguages();
39 my $error = $installer->load_db_schema();
41 my $list = $installer->sql_file_list('en', 'marc21', { optional => 1, mandatory => 1 });
43 my ($fwk_language, $error_list) = $installer->load_sql_in_order($all_languages, @$list);
45 $installer->set_version_syspref();
47 $installer->set_marcflavour_syspref('MARC21');
49 $installer->set_indexing_engine(0);
59 my $installer = C4::Installer->new();
63 Creates a new installer.
72 # get basic information from context
73 $self->{'dbname'} = C4::Context->config("database");
74 $self->{'dbms'} = C4::Context->config("db_scheme") ? C4::Context->config("db_scheme") : "mysql";
75 $self->{'hostname'} = C4::Context->config("hostname");
76 $self->{'port'} = C4::Context->config("port");
77 $self->{'user'} = C4::Context->config("user");
78 $self->{'password'} = C4::Context->config("pass");
79 $self->{'dbh'} = DBI->connect("DBI:$self->{dbms}:dbname=$self->{dbname};host=$self->{hostname}" .
80 ( $self->{port} ? ";port=$self->{port}" : "" ),
81 $self->{'user'}, $self->{'password'});
82 $self->{'language'} = undef;
83 $self->{'marcflavour'} = undef;
84 $self->{'dbh'}->do('set NAMES "utf8"');
85 $self->{'dbh'}->{'mysql_enable_utf8'}=1;
91 =head2 marcflavour_list
95 my ($marcflavours) = $installer->marcflavour_list($lang);
99 Return a arrayref of the MARC flavour sets available for the
100 specified language C<$lang>. Returns 'undef' if a directory
101 for the language does not exist.
105 sub marcflavour_list {
109 my $dir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/$lang/marcflavour";
110 opendir(MYDIR, $dir) or return;
111 my @list = grep { !/^\.|CVS/ && -d "$dir/$_" } readdir(MYDIR);
116 =head2 marc_framework_sql_list
120 my ($defaulted_to_en, $list) = $installer->marc_framework_sql_list($lang, $marcflavour);
124 Returns in C<$list> a structure listing the filename, description, section,
125 and mandatory/optional status of MARC framework scripts available for C<$lang>
128 If the C<$defaulted_to_en> return value is true, no scripts are available
129 for language C<$lang> and the 'en' ones are returned.
133 sub marc_framework_sql_list {
136 my $marcflavour = shift;
138 my $defaulted_to_en = 0;
141 my $dir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/$lang/marcflavour/".lc($marcflavour);
142 unless (opendir( MYDIR, $dir )) {
144 warn "cannot open MARC frameworks directory $dir";
146 # if no translated MARC framework is available,
148 $dir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/en/marcflavour/".lc($marcflavour);
149 opendir(MYDIR, $dir) or warn "cannot open English MARC frameworks directory $dir";
150 $defaulted_to_en = 1;
153 my @listdir = sort grep { !/^\.|marcflavour/ && -d "$dir/$_" } readdir(MYDIR);
157 my $request = $self->{'dbh'}->prepare("SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'");
159 my ($frameworksloaded) = $request->fetchrow;
160 $frameworksloaded = '' unless defined $frameworksloaded; # avoid warning
161 my %frameworksloaded;
162 foreach ( split( /\|/, $frameworksloaded ) ) {
163 $frameworksloaded{$_} = 1;
166 foreach my $requirelevel (@listdir) {
167 opendir( MYDIR, "$dir/$requirelevel" );
168 my @listname = grep { !/^\./ && -f "$dir/$requirelevel/$_" && $_ =~ m/\.sql$/ } readdir(MYDIR);
173 my $name = substr( $_, 0, -4 );
174 open FILE, "<:utf8","$dir/$requirelevel/$name.txt";
176 $lines =~ s/\n|\r/<br \/>/g;
178 utf8::encode($lines) unless ( utf8::is_utf8($lines) );
179 my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
183 'fwkfile' => "$dir/$requirelevel/$_",
184 'fwkdescription' => $lines,
185 'checked' => ( ( $frameworksloaded{$_} || $mandatory ) ? 1 : 0 ),
186 'mandatory' => $mandatory,
190 sort { $a->{'fwkname'} cmp $b->{'fwkname'} } @frameworklist;
192 $cell{"frameworks"} = \@fwks;
193 $cell{"label"} = ucfirst($requirelevel);
194 $cell{"code"} = lc($requirelevel);
195 push @fwklist, \%cell;
198 return ($defaulted_to_en, \@fwklist);
201 =head2 sample_data_sql_list
205 my ($defaulted_to_en, $list) = $installer->sample_data_sql_list($lang);
209 Returns in C<$list> a structure listing the filename, description, section,
210 and mandatory/optional status of sample data scripts available for C<$lang>.
211 If the C<$defaulted_to_en> return value is true, no scripts are available
212 for language C<$lang> and the 'en' ones are returned.
216 sub sample_data_sql_list {
220 my $defaulted_to_en = 0;
223 my $dir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/$lang";
224 unless (opendir( MYDIR, $dir )) {
226 warn "cannot open sample data directory $dir";
228 # if no sample data is available,
230 $dir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/en";
231 opendir(MYDIR, $dir) or warn "cannot open English sample data directory $dir";
232 $defaulted_to_en = 1;
235 my @listdir = sort grep { !/^\.|marcflavour/ && -d "$dir/$_" } readdir(MYDIR);
239 my $request = $self->{'dbh'}->prepare("SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'");
241 my ($frameworksloaded) = $request->fetchrow;
242 $frameworksloaded = '' unless defined $frameworksloaded; # avoid warning
243 my %frameworksloaded;
244 foreach ( split( /\|/, $frameworksloaded ) ) {
245 $frameworksloaded{$_} = 1;
248 foreach my $requirelevel (@listdir) {
249 opendir( MYDIR, "$dir/$requirelevel" );
250 my @listname = grep { !/^\./ && -f "$dir/$requirelevel/$_" && $_ =~ m/\.sql$/ } readdir(MYDIR);
255 my $name = substr( $_, 0, -4 );
256 open FILE, "<:utf8","$dir/$requirelevel/$name.txt";
258 $lines =~ s/\n|\r/<br \/>/g;
260 utf8::encode($lines) unless ( utf8::is_utf8($lines) );
261 my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
265 'fwkfile' => "$dir/$requirelevel/$_",
266 'fwkdescription' => $lines,
267 'checked' => ( ( $frameworksloaded{$_} || $mandatory ) ? 1 : 0 ),
268 'mandatory' => $mandatory,
271 my @fwks = sort { $a->{'fwkname'} cmp $b->{'fwkname'} } @frameworklist;
273 $cell{"frameworks"} = \@fwks;
274 $cell{"label"} = ucfirst($requirelevel);
275 $cell{"code"} = lc($requirelevel);
276 push @levellist, \%cell;
279 return ($defaulted_to_en, \@levellist);
286 my $list = $installer->sql_file_list($lang, $marcflavour, $subset_wanted);
290 Returns an arrayref containing the filepaths of installer SQL scripts
291 available for laod. The C<$lang> and C<$marcflavour> arguments
292 specify the desired language and MARC flavour. while C<$subset_wanted>
293 is a hashref containing possible named parameters 'mandatory' and 'optional'.
300 my $marcflavour = shift;
301 my $subset_wanted = shift;
303 my ($marc_defaulted_to_en, $marc_sql) = $self->marc_framework_sql_list($lang, $marcflavour);
304 my ($sample_defaulted_to_en, $sample_sql) = $self->sample_data_sql_list($lang);
309 if ($subset_wanted->{'mandatory'}) {
310 push @sql_list, $_->{'fwkfile'} if $_->{'mandatory'};
312 if ($subset_wanted->{'optional'}) {
313 push @sql_list, $_->{'fwkfile'} unless $_->{'mandatory'};
315 } @{ $_->{'frameworks'} }
316 } (@$marc_sql, @$sample_sql);
321 =head2 load_db_schema
325 my $error = $installer->load_db_schema();
329 Loads the SQL script that creates Koha's tables and indexes. The
330 return value is a string containing error messages reported by the
338 my $datadir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}";
339 my $error = $self->load_sql("$datadir/kohastructure.sql");
344 =head2 load_sql_in_order
348 my ($fwk_language, $list) = $installer->load_sql_in_order($all_languages, @sql_list);
352 Given a list of SQL scripts supplied in C<@sql_list>, loads each of them
353 into the database and sets the FrameworksLoaded system preference to names
354 of the scripts that were loaded.
356 The SQL files are loaded in alphabetical order by filename (not including
357 directory path). This means that dependencies among the scripts are to
358 be resolved by carefully naming them, keeping in mind that the directory name
359 does *not* currently count.
361 FIXME: this is a rather delicate way of dealing with dependencies between
364 The return value C<$list> is an arrayref containing a hashref for each
365 "level" or directory containing SQL scripts; the hashref in turns contains
366 a list of hashrefs containing a list of each script load and any error
367 messages associated with the loading of each script.
369 FIXME: The C<$fwk_language> code probably doesn't belong and needs to be
370 moved to a different method.
374 sub load_sql_in_order {
376 my $all_languages = shift;
382 my @aa = split /\/|\\/, ($a);
383 my @bb = split /\/|\\/, ($b);
386 my $request = $self->{'dbh'}->prepare( "SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'" );
388 my ($systempreference) = $request->fetchrow;
389 $systempreference = '' unless defined $systempreference; # avoid warning
390 foreach my $file (@fnames) {
393 my $error = $self->load_sql($file);
394 my @file = split qr(\/|\\), $file;
395 $lang = $file[ scalar(@file) - 3 ] unless ($lang);
396 my $level = $file[ scalar(@file) - 2 ];
398 $systempreference .= "$file[scalar(@file)-1]|"
399 unless ( index( $systempreference, $file[ scalar(@file) - 1 ] ) >= 0 );
402 #Bulding here a hierarchy to display files by level.
403 push @{ $hashlevel{$level} },
404 { "fwkname" => $file[ scalar(@file) - 1 ], "error" => $error };
407 #systempreference contains an ending |
408 chop $systempreference;
410 map { push @list, { "level" => $_, "fwklist" => $hashlevel{$_} } } keys %hashlevel;
412 for my $each_language (@$all_languages) {
414 # warn "CODE".$each_language->{'language_code'};
415 # warn "LANG:".$lang;
416 if ( $lang eq $each_language->{'language_code'} ) {
417 $fwk_language = $each_language->{language_locale_name};
422 "UPDATE systempreferences set value=\"$systempreference\" where variable='FrameworksLoaded'"
425 unless ( $updateflag == 1 ) {
427 "INSERT INTO systempreferences (value, variable, explanation, type) VALUES (\"$systempreference\",'FrameworksLoaded','Frameworks loaded through webinstaller','choice')";
428 my $rq = $self->{'dbh'}->prepare($string);
431 return ($fwk_language, \@list);
434 =head2 set_marcflavour_syspref
438 $installer->set_marcflavour_syspref($marcflavour);
442 Set the 'marcflavour' system preference. The incoming
443 C<$marcflavour> references to a subdirectory of
444 installer/data/$dbms/$lang/marcflavour, and is
445 normalized to MARC21 or UNIMARC.
447 FIXME: this method assumes that the MARC flavour will be either
452 sub set_marcflavour_syspref {
454 my $marcflavour = shift;
456 # we can have some variants of marc flavour, by having different directories, like : unimarc_small and unimarc_full, for small and complete unimarc frameworks.
457 # marc_cleaned finds the marcflavour, without the variant.
458 my $marc_cleaned = 'MARC21';
459 $marc_cleaned = 'UNIMARC' if $marcflavour =~ /unimarc/i;
461 $self->{'dbh'}->prepare(
462 "INSERT IGNORE INTO `systempreferences` (variable,value,explanation,options,type) VALUES('marcflavour','$marc_cleaned','Define global MARC flavor (MARC21 or UNIMARC) used for character encoding','MARC21|UNIMARC','Choice');"
467 =head2 set_indexing_engine
471 $installer->set_indexing_engine($nozebra);
475 Sets system preferences related to the indexing
476 engine. The C<$nozebra> argument is a boolean;
477 if true, turn on NoZebra mode and turn off QueryFuzzy,
478 QueryWeightFields, and QueryStemming. If false, turn
479 off NoZebra mode (i.e., use the Zebra search engine).
483 sub set_indexing_engine {
488 $self->{'dbh'}->do("UPDATE systempreferences SET value=1 WHERE variable='NoZebra'");
489 $self->{'dbh'}->do("UPDATE systempreferences SET value=0 WHERE variable in ('QueryFuzzy','QueryWeightFields','QueryStemming')");
491 $self->{'dbh'}->do("UPDATE systempreferences SET value=0 WHERE variable='NoZebra'");
496 =head2 set_version_syspref
500 $installer->set_version_syspref();
504 Set or update the 'Version' system preference to the current
505 Koha software version.
509 sub set_version_syspref {
512 my $kohaversion=C4::Context::KOHAVERSION;
513 # remove the 3 last . to have a Perl number
514 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
515 if (C4::Context->preference('Version')) {
516 warn "UPDATE Version";
517 my $finish=$self->{'dbh'}->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
518 $finish->execute($kohaversion);
520 warn "INSERT Version";
521 my $finish=$self->{'dbh'}->prepare("INSERT into systempreferences (variable,value,explanation) values ('Version',?,'The Koha database version. WARNING: Do not change this value manually, it is maintained by the webinstaller')");
522 $finish->execute($kohaversion);
524 C4::Context->clear_syspref_cache();
531 my $error = $installer->load_sql($filename);
535 Runs a the specified SQL using the DB's command-line
536 SQL tool, and returns any strings sent to STDERR
537 by the command-line tool.
539 FIXME: there has been a long-standing desire to
540 replace this with an SQL loader that goes
541 through DBI; partly for portability issues
542 and partly to improve error handling.
544 FIXME: even using the command-line loader, some more
545 basic error handling should be added - deal
546 with missing files, e.g.
552 my $filename = shift;
554 my $datadir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}";
557 if ( $self->{dbms} eq 'mysql' ) {
559 . ( $self->{hostname} ? " -h $self->{hostname} " : "" )
560 . ( $self->{port} ? " -P $self->{port} " : "" )
561 . ( $self->{user} ? " -u $self->{user} " : "" )
562 . ( $self->{password} ? " -p'$self->{password}'" : "" )
563 . " $self->{dbname} ";
564 $error = qx($strcmd --default-character-set=utf8 <$filename 2>&1 1>/dev/null);
565 } elsif ( $self->{dbms} eq 'Pg' ) {
567 . ( $self->{hostname} ? " -h $self->{hostname} " : "" )
568 . ( $self->{port} ? " -p $self->{port} " : "" )
569 . ( $self->{user} ? " -U $self->{user} " : "" )
570 # . ( $self->{password} ? " -W $self->{password}" : "" ) # psql will NOT accept a password, but prompts...
571 . " $self->{dbname} "; # Therefore, be sure to run 'trust' on localhost in pg_hba.conf -fbcit
572 $error = qx($strcmd -f $filename 2>&1 1>/dev/null);
573 # Be sure to set 'client_min_messages = error' in postgresql.conf
574 # so that only true errors are returned to stderr or else the installer will
575 # report the import a failure although it really succeded -fbcit
577 # errors thrown while loading installer data should be logged
578 warn "C4::Installer::load_sql returned the following errors while attempting to load $filename:\n";
583 =head2 get_file_path_from_name
587 my $filename = $installer->get_file_path_from_name('script_name');
591 searches through the set of known SQL scripts and finds the fully
592 qualified path name for the script that mathches the input.
594 returns undef if no match was found.
599 sub get_file_path_from_name {
601 my $partialname = shift;
603 my $lang = 'en'; # FIXME: how do I know what language I want?
605 my ($defaulted_to_en, $list) = $self->sample_data_sql_list($lang);
606 # warn( Data::Dumper->Dump( [ $list ], [ 'list' ] ) );
609 foreach my $frameworklist ( @$list ) {
610 push @found, grep { $_->{'fwkfile'} =~ /$partialname$/ } @{$frameworklist->{'frameworks'}};
613 # warn( Data::Dumper->Dump( [ \@found ], [ 'found' ] ) );
614 if ( 0 == scalar @found ) {
616 } elsif ( 1 < scalar @found ) {
617 warn "multiple results found for $partialname";
620 return $found[0]->{'fwkfile'};
628 C4::Installer is a refactoring of logic originally from installer/installer.pl, which was
629 originally written by Henri-Damien Laurant.
631 Koha Developement team <info@koha.org>
633 Galen Charlton <galen.charlton@liblime.com>