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
23 our $VERSION = 3.07.00.049;
25 use C4
::Installer
::PerlModules
;
34 my $installer = C4::Installer->new();
35 my $all_languages = getAllLanguages();
36 my $error = $installer->load_db_schema();
37 my $list = $installer->sql_file_list('en', 'marc21', { optional => 1, mandatory => 1 });
38 my ($fwk_language, $error_list) = $installer->load_sql_in_order($all_languages, @$list);
39 $installer->set_version_syspref();
40 $installer->set_marcflavour_syspref('MARC21');
50 my $installer = C4::Installer->new();
52 Creates a new installer.
61 # get basic information from context
62 $self->{'dbname'} = C4
::Context
->config("database");
63 $self->{'dbms'} = C4
::Context
->config("db_scheme") ? C4
::Context
->config("db_scheme") : "mysql";
64 $self->{'hostname'} = C4
::Context
->config("hostname");
65 $self->{'port'} = C4
::Context
->config("port");
66 $self->{'user'} = C4
::Context
->config("user");
67 $self->{'password'} = C4
::Context
->config("pass");
68 $self->{'dbh'} = DBI
->connect("DBI:$self->{dbms}:dbname=$self->{dbname};host=$self->{hostname}" .
69 ( $self->{port
} ?
";port=$self->{port}" : "" ),
70 $self->{'user'}, $self->{'password'});
71 $self->{'language'} = undef;
72 $self->{'marcflavour'} = undef;
73 $self->{'dbh'}->do('set NAMES "utf8"');
74 $self->{'dbh'}->{'mysql_enable_utf8'}=1;
80 =head2 marcflavour_list
82 my ($marcflavours) = $installer->marcflavour_list($lang);
84 Return a arrayref of the MARC flavour sets available for the
85 specified language C<$lang>. Returns 'undef' if a directory
86 for the language does not exist.
90 sub marcflavour_list
{
94 my $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/$lang/marcflavour";
95 opendir(MYDIR
, $dir) or return;
96 my @list = grep { !/^\.|CVS/ && -d
"$dir/$_" } readdir(MYDIR
);
101 =head2 marc_framework_sql_list
103 my ($defaulted_to_en, $list) =
104 $installer->marc_framework_sql_list($lang, $marcflavour);
106 Returns in C<$list> a structure listing the filename, description, section,
107 and mandatory/optional status of MARC framework scripts available for C<$lang>
110 If the C<$defaulted_to_en> return value is true, no scripts are available
111 for language C<$lang> and the 'en' ones are returned.
115 sub marc_framework_sql_list
{
118 my $marcflavour = shift;
120 my $defaulted_to_en = 0;
123 my $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/$lang/marcflavour/".lc($marcflavour);
124 unless (opendir( MYDIR
, $dir )) {
126 warn "cannot open MARC frameworks directory $dir";
128 # if no translated MARC framework is available,
130 $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/en/marcflavour/".lc($marcflavour);
131 opendir(MYDIR
, $dir) or warn "cannot open English MARC frameworks directory $dir";
132 $defaulted_to_en = 1;
135 my @listdir = sort grep { !/^\.|marcflavour/ && -d
"$dir/$_" } readdir(MYDIR
);
139 my $request = $self->{'dbh'}->prepare("SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'");
141 my ($frameworksloaded) = $request->fetchrow;
142 $frameworksloaded = '' unless defined $frameworksloaded; # avoid warning
143 my %frameworksloaded;
144 foreach ( split( /\|/, $frameworksloaded ) ) {
145 $frameworksloaded{$_} = 1;
148 foreach my $requirelevel (@listdir) {
149 opendir( MYDIR
, "$dir/$requirelevel" );
150 my @listname = grep { !/^\./ && -f
"$dir/$requirelevel/$_" && $_ =~ m/\.sql$/ } readdir(MYDIR
);
155 my $name = substr( $_, 0, -4 );
156 open my $fh, "<:encoding(UTF-8)", "$dir/$requirelevel/$name.txt";
158 $lines =~ s/\n|\r/<br \/>/g
;
160 utf8
::encode
($lines) unless ( utf8
::is_utf8
($lines) );
161 my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
165 'fwkfile' => "$dir/$requirelevel/$_",
166 'fwkdescription' => $lines,
167 'checked' => ( ( $frameworksloaded{$_} || $mandatory ) ?
1 : 0 ),
168 'mandatory' => $mandatory,
172 sort { $a->{'fwkname'} cmp $b->{'fwkname'} } @frameworklist;
174 $cell{"frameworks"} = \
@fwks;
175 $cell{"label"} = ucfirst($requirelevel);
176 $cell{"code"} = lc($requirelevel);
177 push @fwklist, \
%cell;
180 return ($defaulted_to_en, \
@fwklist);
183 =head2 sample_data_sql_list
185 my ($defaulted_to_en, $list) = $installer->sample_data_sql_list($lang);
187 Returns in C<$list> a structure listing the filename, description, section,
188 and mandatory/optional status of sample data scripts available for C<$lang>.
189 If the C<$defaulted_to_en> return value is true, no scripts are available
190 for language C<$lang> and the 'en' ones are returned.
194 sub sample_data_sql_list
{
198 my $defaulted_to_en = 0;
201 my $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/$lang";
202 unless (opendir( MYDIR
, $dir )) {
204 warn "cannot open sample data directory $dir";
206 # if no sample data is available,
208 $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/en";
209 opendir(MYDIR
, $dir) or warn "cannot open English sample data directory $dir";
210 $defaulted_to_en = 1;
213 my @listdir = sort grep { !/^\.|marcflavour/ && -d
"$dir/$_" } readdir(MYDIR
);
217 my $request = $self->{'dbh'}->prepare("SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'");
219 my ($frameworksloaded) = $request->fetchrow;
220 $frameworksloaded = '' unless defined $frameworksloaded; # avoid warning
221 my %frameworksloaded;
222 foreach ( split( /\|/, $frameworksloaded ) ) {
223 $frameworksloaded{$_} = 1;
226 foreach my $requirelevel (@listdir) {
227 opendir( MYDIR
, "$dir/$requirelevel" );
228 my @listname = grep { !/^\./ && -f
"$dir/$requirelevel/$_" && $_ =~ m/\.sql$/ } readdir(MYDIR
);
233 my $name = substr( $_, 0, -4 );
234 open my $fh , "<:encoding(UTF-8)", "$dir/$requirelevel/$name.txt";
236 $lines =~ s/\n|\r/<br \/>/g
;
238 utf8
::encode
($lines) unless ( utf8
::is_utf8
($lines) );
239 my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
243 'fwkfile' => "$dir/$requirelevel/$_",
244 'fwkdescription' => $lines,
245 'checked' => ( ( $frameworksloaded{$_} || $mandatory ) ?
1 : 0 ),
246 'mandatory' => $mandatory,
249 my @fwks = sort { $a->{'fwkname'} cmp $b->{'fwkname'} } @frameworklist;
251 $cell{"frameworks"} = \
@fwks;
252 $cell{"label"} = ucfirst($requirelevel);
253 $cell{"code"} = lc($requirelevel);
254 push @levellist, \
%cell;
257 return ($defaulted_to_en, \
@levellist);
262 my $list = $installer->sql_file_list($lang, $marcflavour, $subset_wanted);
264 Returns an arrayref containing the filepaths of installer SQL scripts
265 available for laod. The C<$lang> and C<$marcflavour> arguments
266 specify the desired language and MARC flavour. while C<$subset_wanted>
267 is a hashref containing possible named parameters 'mandatory' and 'optional'.
274 my $marcflavour = shift;
275 my $subset_wanted = shift;
277 my ($marc_defaulted_to_en, $marc_sql) = $self->marc_framework_sql_list($lang, $marcflavour);
278 my ($sample_defaulted_to_en, $sample_sql) = $self->sample_data_sql_list($lang);
283 if ($subset_wanted->{'mandatory'}) {
284 push @sql_list, $_->{'fwkfile'} if $_->{'mandatory'};
286 if ($subset_wanted->{'optional'}) {
287 push @sql_list, $_->{'fwkfile'} unless $_->{'mandatory'};
289 } @
{ $_->{'frameworks'} }
290 } (@
$marc_sql, @
$sample_sql);
295 =head2 load_db_schema
297 my $error = $installer->load_db_schema();
299 Loads the SQL script that creates Koha's tables and indexes. The
300 return value is a string containing error messages reported by the
308 my $datadir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}";
309 my $error = $self->load_sql("$datadir/kohastructure.sql");
314 =head2 load_sql_in_order
316 my ($fwk_language, $list) = $installer->load_sql_in_order($all_languages, @sql_list);
318 Given a list of SQL scripts supplied in C<@sql_list>, loads each of them
319 into the database and sets the FrameworksLoaded system preference to names
320 of the scripts that were loaded.
322 The SQL files are loaded in alphabetical order by filename (not including
323 directory path). This means that dependencies among the scripts are to
324 be resolved by carefully naming them, keeping in mind that the directory name
325 does *not* currently count.
327 B<FIXME:> this is a rather delicate way of dealing with dependencies between
330 The return value C<$list> is an arrayref containing a hashref for each
331 "level" or directory containing SQL scripts; the hashref in turns contains
332 a list of hashrefs containing a list of each script load and any error
333 messages associated with the loading of each script.
335 B<FIXME:> The C<$fwk_language> code probably doesn't belong and needs to be
336 moved to a different method.
340 sub load_sql_in_order
{
342 my $all_languages = shift;
348 my @aa = split /\/|\\/, ($a);
349 my @bb = split /\/|\\/, ($b);
352 my $request = $self->{'dbh'}->prepare( "SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'" );
354 my ($systempreference) = $request->fetchrow;
355 $systempreference = '' unless defined $systempreference; # avoid warning
356 # Make sure the global sysprefs.sql file is loaded first
357 my $globalsysprefs = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/sysprefs.sql";
358 unshift(@fnames, $globalsysprefs);
359 foreach my $file (@fnames) {
362 my $error = $self->load_sql($file);
363 my @file = split qr
(\
/|\\), $file;
364 $lang = $file[ scalar(@file) - 3 ] unless ($lang);
365 my $level = $file[ scalar(@file) - 2 ];
367 $systempreference .= "$file[scalar(@file)-1]|"
368 unless ( index( $systempreference, $file[ scalar(@file) - 1 ] ) >= 0 );
371 #Bulding here a hierarchy to display files by level.
372 push @
{ $hashlevel{$level} },
373 { "fwkname" => $file[ scalar(@file) - 1 ], "error" => $error };
376 #systempreference contains an ending |
377 chop $systempreference;
379 map { push @list, { "level" => $_, "fwklist" => $hashlevel{$_} } } keys %hashlevel;
381 for my $each_language (@
$all_languages) {
383 # warn "CODE".$each_language->{'language_code'};
384 # warn "LANG:".$lang;
385 if ( $lang eq $each_language->{'language_code'} ) {
386 $fwk_language = $each_language->{language_locale_name
};
391 "UPDATE systempreferences set value=\"$systempreference\" where variable='FrameworksLoaded'"
394 unless ( $updateflag == 1 ) {
396 "INSERT INTO systempreferences (value, variable, explanation, type) VALUES (\"$systempreference\",'FrameworksLoaded','Frameworks loaded through webinstaller','choice')";
397 my $rq = $self->{'dbh'}->prepare($string);
400 return ($fwk_language, \
@list);
403 =head2 set_marcflavour_syspref
405 $installer->set_marcflavour_syspref($marcflavour);
407 Set the 'marcflavour' system preference. The incoming
408 C<$marcflavour> references to a subdirectory of
409 installer/data/$dbms/$lang/marcflavour, and is
410 normalized to MARC21, UNIMARC or NORMARC.
412 FIXME: this method assumes that the MARC flavour will be either
413 MARC21, UNIMARC or NORMARC.
417 sub set_marcflavour_syspref
{
419 my $marcflavour = shift;
421 # we can have some variants of marc flavour, by having different directories, like : unimarc_small and unimarc_full, for small and complete unimarc frameworks.
422 # marc_cleaned finds the marcflavour, without the variant.
423 my $marc_cleaned = 'MARC21';
424 $marc_cleaned = 'UNIMARC' if $marcflavour =~ /unimarc/i;
425 $marc_cleaned = 'NORMARC' if $marcflavour =~ /normarc/i;
427 $self->{'dbh'}->prepare(
428 "INSERT IGNORE INTO `systempreferences` (variable,value,explanation,options,type) VALUES('marcflavour','$marc_cleaned','Define global MARC flavor (MARC21, UNIMARC or NORMARC) used for character encoding','MARC21|UNIMARC|NORMARC','Choice');"
433 =head2 set_version_syspref
435 $installer->set_version_syspref();
437 Set or update the 'Version' system preference to the current
438 Koha software version.
442 sub set_version_syspref
{
445 my $kohaversion=C4
::Context
::KOHAVERSION
;
446 # remove the 3 last . to have a Perl number
447 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
448 if (C4
::Context
->preference('Version')) {
449 warn "UPDATE Version";
450 my $finish=$self->{'dbh'}->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
451 $finish->execute($kohaversion);
453 warn "INSERT Version";
454 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')");
455 $finish->execute($kohaversion);
457 C4
::Context
->clear_syspref_cache();
462 my $error = $installer->load_sql($filename);
464 Runs a the specified SQL using the DB's command-line
465 SQL tool, and returns any strings sent to STDERR
466 by the command-line tool.
468 B<FIXME:> there has been a long-standing desire to
469 replace this with an SQL loader that goes
470 through DBI; partly for portability issues
471 and partly to improve error handling.
473 B<FIXME:> even using the command-line loader, some more
474 basic error handling should be added - deal
475 with missing files, e.g.
481 my $filename = shift;
483 my $datadir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}";
487 if ( $self->{dbms
} eq 'mysql' ) {
488 $cmd = qx(which mysql
2>/dev/null
|| whereis mysql
2>/dev/null
);
490 $cmd = $1 if ($cmd && $cmd =~ /^(.+?)[\r\n]+$/);
491 $cmd = 'mysql' if (!$cmd || !-x
$cmd);
493 . ( $self->{hostname
} ?
" -h $self->{hostname} " : "" )
494 . ( $self->{port
} ?
" -P $self->{port} " : "" )
495 . ( $self->{user
} ?
" -u $self->{user} " : "" )
496 . ( $self->{password
} ?
" -p'$self->{password}'" : "" )
497 . " $self->{dbname} ";
498 $error = qx($strcmd --default-character
-set
=utf8
<$filename 2>&1 1>/dev/null
);
499 } elsif ( $self->{dbms
} eq 'Pg' ) {
500 $cmd = qx(which psql
2>/dev/null
|| whereis psql
2>/dev/null
);
502 $cmd = $1 if ($cmd && $cmd =~ /^(.+?)[\r\n]+$/);
503 $cmd = 'psql' if (!$cmd || !-x
$cmd);
505 . ( $self->{hostname
} ?
" -h $self->{hostname} " : "" )
506 . ( $self->{port
} ?
" -p $self->{port} " : "" )
507 . ( $self->{user
} ?
" -U $self->{user} " : "" )
508 # . ( $self->{password} ? " -W $self->{password}" : "" ) # psql will NOT accept a password, but prompts...
509 . " $self->{dbname} "; # Therefore, be sure to run 'trust' on localhost in pg_hba.conf -fbcit
510 $error = qx($strcmd -f
$filename 2>&1 1>/dev/null
);
511 # Be sure to set 'client_min_messages = error' in postgresql.conf
512 # so that only true errors are returned to stderr or else the installer will
513 # report the import a failure although it really succeded -fbcit
515 # errors thrown while loading installer data should be logged
517 warn "C4::Installer::load_sql returned the following errors while attempting to load $filename:\n";
523 =head2 get_file_path_from_name
525 my $filename = $installer->get_file_path_from_name('script_name');
527 searches through the set of known SQL scripts and finds the fully
528 qualified path name for the script that mathches the input.
530 returns undef if no match was found.
535 sub get_file_path_from_name
{
537 my $partialname = shift;
539 my $lang = 'en'; # FIXME: how do I know what language I want?
541 my ($defaulted_to_en, $list) = $self->sample_data_sql_list($lang);
542 # warn( Data::Dumper->Dump( [ $list ], [ 'list' ] ) );
545 foreach my $frameworklist ( @
$list ) {
546 push @found, grep { $_->{'fwkfile'} =~ /$partialname$/ } @
{$frameworklist->{'frameworks'}};
549 # warn( Data::Dumper->Dump( [ \@found ], [ 'found' ] ) );
550 if ( 0 == scalar @found ) {
552 } elsif ( 1 < scalar @found ) {
553 warn "multiple results found for $partialname";
556 return $found[0]->{'fwkfile'};
564 C4::Installer is a refactoring of logic originally from installer/installer.pl, which was
565 originally written by Henri-Damien Laurant.
567 Koha Development Team <http://koha-community.org/>
569 Galen Charlton <galen.charlton@liblime.com>