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
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 use Encode
qw( encode is_utf8 );
25 use C4
::Installer
::PerlModules
;
36 my $installer = C4::Installer->new();
37 my $all_languages = getAllLanguages();
38 my $error = $installer->load_db_schema();
40 #fill $list with list of sql files
41 my ($fwk_language, $error_list) = $installer->load_sql_in_order($all_languages, @$list);
42 $installer->set_version_syspref();
43 $installer->set_marcflavour_syspref('MARC21');
53 my $installer = C4::Installer->new();
55 Creates a new installer.
64 # get basic information from context
65 $self->{'dbname'} = C4
::Context
->config("database");
66 $self->{'dbms'} = C4
::Context
->config("db_scheme") ? C4
::Context
->config("db_scheme") : "mysql";
67 $self->{'hostname'} = C4
::Context
->config("hostname");
68 $self->{'port'} = C4
::Context
->config("port");
69 $self->{'user'} = C4
::Context
->config("user");
70 $self->{'password'} = C4
::Context
->config("pass");
71 $self->{'dbh'} = DBI
->connect("DBI:$self->{dbms}:dbname=$self->{dbname};host=$self->{hostname}" .
72 ( $self->{port
} ?
";port=$self->{port}" : "" ),
73 $self->{'user'}, $self->{'password'});
74 $self->{'language'} = undef;
75 $self->{'marcflavour'} = undef;
76 $self->{'dbh'}->do('set NAMES "utf8"');
77 $self->{'dbh'}->{'mysql_enable_utf8'}=1;
83 =head2 marc_framework_sql_list
85 my ($defaulted_to_en, $list) =
86 $installer->marc_framework_sql_list($lang, $marcflavour);
88 Returns in C<$list> a structure listing the filename, description, section,
89 and mandatory/optional status of MARC framework scripts available for C<$lang>
92 If the C<$defaulted_to_en> return value is true, no scripts are available
93 for language C<$lang> and the 'en' ones are returned.
97 sub marc_framework_sql_list
{
100 my $marcflavour = shift;
102 my $defaulted_to_en = 0;
105 my $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/$lang/marcflavour/".lc($marcflavour);
106 unless (opendir( MYDIR
, $dir )) {
108 warn "cannot open MARC frameworks directory $dir";
110 # if no translated MARC framework is available,
112 $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/en/marcflavour/".lc($marcflavour);
113 opendir(MYDIR
, $dir) or warn "cannot open English MARC frameworks directory $dir";
114 $defaulted_to_en = 1;
117 my @listdir = sort grep { !/^\.|marcflavour/ && -d
"$dir/$_" } readdir(MYDIR
);
121 my $request = $self->{'dbh'}->prepare("SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'");
123 my ($frameworksloaded) = $request->fetchrow;
124 $frameworksloaded = '' unless defined $frameworksloaded; # avoid warning
125 my %frameworksloaded;
126 foreach ( split( /\|/, $frameworksloaded ) ) {
127 $frameworksloaded{$_} = 1;
130 foreach my $requirelevel (@listdir) {
131 opendir( MYDIR
, "$dir/$requirelevel" );
132 my @listname = grep { !/^\./ && -f
"$dir/$requirelevel/$_" && $_ =~ m/\.sql$/ } readdir(MYDIR
);
137 my $name = substr( $_, 0, -4 );
138 open my $fh, "<:encoding(UTF-8)", "$dir/$requirelevel/$name.txt";
140 $lines =~ s/\n|\r/<br \/>/g
;
141 $lines = Encode
::encode
('UTF-8', $lines) unless ( Encode
::is_utf8
($lines) );
142 my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
146 'fwkfile' => "$dir/$requirelevel/$_",
147 'fwkdescription' => $lines,
148 'checked' => ( ( $frameworksloaded{$_} || $mandatory ) ?
1 : 0 ),
149 'mandatory' => $mandatory,
153 sort { $a->{'fwkname'} cmp $b->{'fwkname'} } @frameworklist;
155 $cell{"frameworks"} = \
@fwks;
156 $cell{"label"} = ucfirst($requirelevel);
157 $cell{"code"} = lc($requirelevel);
158 push @fwklist, \
%cell;
161 return ($defaulted_to_en, \
@fwklist);
164 =head2 sample_data_sql_list
166 my ($defaulted_to_en, $list) = $installer->sample_data_sql_list($lang);
168 Returns in C<$list> a structure listing the filename, description, section,
169 and mandatory/optional status of sample data scripts available for C<$lang>.
170 If the C<$defaulted_to_en> return value is true, no scripts are available
171 for language C<$lang> and the 'en' ones are returned.
175 sub sample_data_sql_list
{
179 my $defaulted_to_en = 0;
182 my $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/$lang";
183 unless (opendir( MYDIR
, $dir )) {
185 warn "cannot open sample data directory $dir";
187 # if no sample data is available,
189 $dir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/en";
190 opendir(MYDIR
, $dir) or warn "cannot open English sample data directory $dir";
191 $defaulted_to_en = 1;
194 my @listdir = sort grep { !/^\.|marcflavour/ && -d
"$dir/$_" } readdir(MYDIR
);
198 my $request = $self->{'dbh'}->prepare("SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'");
200 my ($frameworksloaded) = $request->fetchrow;
201 $frameworksloaded = '' unless defined $frameworksloaded; # avoid warning
202 my %frameworksloaded;
203 foreach ( split( /\|/, $frameworksloaded ) ) {
204 $frameworksloaded{$_} = 1;
207 foreach my $requirelevel (@listdir) {
208 opendir( MYDIR
, "$dir/$requirelevel" );
209 my @listname = grep { !/^\./ && -f
"$dir/$requirelevel/$_" && $_ =~ m/\.sql$/ } readdir(MYDIR
);
214 my $name = substr( $_, 0, -4 );
215 open my $fh , "<:encoding(UTF-8)", "$dir/$requirelevel/$name.txt";
217 $lines =~ s/\n|\r/<br \/>/g
;
218 $lines = Encode
::encode
('UTF-8', $lines) unless ( Encode
::is_utf8
($lines) );
219 my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
223 'fwkfile' => "$dir/$requirelevel/$_",
224 'fwkdescription' => $lines,
225 'checked' => ( ( $frameworksloaded{$_} || $mandatory ) ?
1 : 0 ),
226 'mandatory' => $mandatory,
229 my @fwks = sort { $a->{'fwkname'} cmp $b->{'fwkname'} } @frameworklist;
231 $cell{"frameworks"} = \
@fwks;
232 $cell{"label"} = ucfirst($requirelevel);
233 $cell{"code"} = lc($requirelevel);
234 push @levellist, \
%cell;
237 return ($defaulted_to_en, \
@levellist);
240 =head2 load_db_schema
242 my $error = $installer->load_db_schema();
244 Loads the SQL script that creates Koha's tables and indexes. The
245 return value is a string containing error messages reported by the
253 my $datadir = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}";
254 my $error = $self->load_sql("$datadir/kohastructure.sql");
259 =head2 load_sql_in_order
261 my ($fwk_language, $list) = $installer->load_sql_in_order($all_languages, @sql_list);
263 Given a list of SQL scripts supplied in C<@sql_list>, loads each of them
264 into the database and sets the FrameworksLoaded system preference to names
265 of the scripts that were loaded.
267 The SQL files are loaded in alphabetical order by filename (not including
268 directory path). This means that dependencies among the scripts are to
269 be resolved by carefully naming them, keeping in mind that the directory name
270 does *not* currently count.
272 B<FIXME:> this is a rather delicate way of dealing with dependencies between
275 The return value C<$list> is an arrayref containing a hashref for each
276 "level" or directory containing SQL scripts; the hashref in turns contains
277 a list of hashrefs containing a list of each script load and any error
278 messages associated with the loading of each script.
280 B<FIXME:> The C<$fwk_language> code probably doesn't belong and needs to be
281 moved to a different method.
285 sub load_sql_in_order
{
287 my $all_languages = shift;
293 my @aa = split /\/|\\/, ($a);
294 my @bb = split /\/|\\/, ($b);
297 my $request = $self->{'dbh'}->prepare( "SELECT value FROM systempreferences WHERE variable='FrameworksLoaded'" );
299 my ($systempreference) = $request->fetchrow;
300 $systempreference = '' unless defined $systempreference; # avoid warning
301 # Make sure subtag_registry.sql is loaded second
302 my $subtag_registry = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/mandatory/subtag_registry.sql";
303 unshift(@fnames, $subtag_registry);
304 # Make sure authorised value categories are loaded at the beginning
305 my $av_cat = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/mandatory/auth_val_cat.sql";
306 unshift(@fnames, $av_cat);
307 # Make sure the global sysprefs.sql file is loaded first
308 my $globalsysprefs = C4
::Context
->config('intranetdir') . "/installer/data/$self->{dbms}/sysprefs.sql";
309 unshift(@fnames, $globalsysprefs);
310 push @fnames, C4
::Context
->config('intranetdir') . "/installer/data/mysql/userflags.sql";
311 push @fnames, C4
::Context
->config('intranetdir') . "/installer/data/mysql/userpermissions.sql";
312 push @fnames, C4
::Context
->config('intranetdir') . "/installer/data/mysql/audio_alerts.sql";
313 push @fnames, C4
::Context
->config('intranetdir') . "/installer/data/mysql/mandatory/refund_lost_item_fee_rules.sql";
314 foreach my $file (@fnames) {
317 my $error = $self->load_sql($file);
318 my @file = split qr
(\
/|\\), $file;
319 $lang = $file[ scalar(@file) - 3 ] unless ($lang);
320 my $level = $file[ scalar(@file) - 2 ];
322 $systempreference .= "$file[scalar(@file)-1]|"
323 unless ( index( $systempreference, $file[ scalar(@file) - 1 ] ) >= 0 );
326 #Bulding here a hierarchy to display files by level.
327 push @
{ $hashlevel{$level} },
328 { "fwkname" => $file[ scalar(@file) - 1 ], "error" => $error };
331 #systempreference contains an ending |
332 chop $systempreference;
334 map { push @list, { "level" => $_, "fwklist" => $hashlevel{$_} } } keys %hashlevel;
336 for my $each_language (@
$all_languages) {
338 # warn "CODE".$each_language->{'language_code'};
339 # warn "LANG:".$lang;
340 if ( $lang eq $each_language->{'language_code'} ) {
341 $fwk_language = $each_language->{language_locale_name
};
346 "UPDATE systempreferences set value=\"$systempreference\" where variable='FrameworksLoaded'"
349 unless ( $updateflag == 1 ) {
351 "INSERT INTO systempreferences (value, variable, explanation, type) VALUES (\"$systempreference\",'FrameworksLoaded','Frameworks loaded through webinstaller','choice')";
352 my $rq = $self->{'dbh'}->prepare($string);
355 return ($fwk_language, \
@list);
358 =head2 set_marcflavour_syspref
360 $installer->set_marcflavour_syspref($marcflavour);
362 Set the 'marcflavour' system preference. The incoming
363 C<$marcflavour> references to a subdirectory of
364 installer/data/$dbms/$lang/marcflavour, and is
365 normalized to MARC21, UNIMARC or NORMARC.
367 FIXME: this method assumes that the MARC flavour will be either
368 MARC21, UNIMARC or NORMARC.
372 sub set_marcflavour_syspref
{
374 my $marcflavour = shift;
376 # we can have some variants of marc flavour, by having different directories, like : unimarc_small and unimarc_full, for small and complete unimarc frameworks.
377 # marc_cleaned finds the marcflavour, without the variant.
378 my $marc_cleaned = 'MARC21';
379 $marc_cleaned = 'UNIMARC' if $marcflavour =~ /unimarc/i;
380 $marc_cleaned = 'NORMARC' if $marcflavour =~ /normarc/i;
382 $self->{'dbh'}->prepare(
383 "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');"
388 =head2 set_version_syspref
390 $installer->set_version_syspref();
392 Set or update the 'Version' system preference to the current
393 Koha software version.
397 sub set_version_syspref
{
400 my $kohaversion = Koha
::version
();
401 # remove the 3 last . to have a Perl number
402 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
403 if (C4
::Context
->preference('Version')) {
404 warn "UPDATE Version";
405 my $finish=$self->{'dbh'}->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
406 $finish->execute($kohaversion);
408 warn "INSERT Version";
409 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')");
410 $finish->execute($kohaversion);
412 C4
::Context
->clear_syspref_cache();
417 my $error = $installer->load_sql($filename);
419 Runs a the specified SQL file using a sql loader DBIx::RunSQL
420 Returns any strings sent to STDERR
422 # FIXME This should be improved: sometimes the caller and load_sql warn the same
429 my $filename = shift;
432 my $dbh = $self->{ dbh
};
437 open STDERR
, ">>", \
$dup_stderr;
440 DBIx
::RunSQL
->run_sql_file(
446 # errors thrown while loading installer data should be logged
448 warn "C4::Installer::load_sql returned the following errors while attempting to load $filename:\n";
449 $error = $dup_stderr;
456 =head2 get_file_path_from_name
458 my $filename = $installer->get_file_path_from_name('script_name');
460 searches through the set of known SQL scripts and finds the fully
461 qualified path name for the script that mathches the input.
463 returns undef if no match was found.
468 sub get_file_path_from_name
{
470 my $partialname = shift;
472 my $lang = 'en'; # FIXME: how do I know what language I want?
474 my ($defaulted_to_en, $list) = $self->sample_data_sql_list($lang);
475 # warn( Data::Dumper->Dump( [ $list ], [ 'list' ] ) );
478 foreach my $frameworklist ( @
$list ) {
479 push @found, grep { $_->{'fwkfile'} =~ /$partialname$/ } @
{$frameworklist->{'frameworks'}};
482 # warn( Data::Dumper->Dump( [ \@found ], [ 'found' ] ) );
483 if ( 0 == scalar @found ) {
485 } elsif ( 1 < scalar @found ) {
486 warn "multiple results found for $partialname";
489 return $found[0]->{'fwkfile'};
497 C4::Installer is a refactoring of logic originally from installer/installer.pl, which was
498 originally written by Henri-Damien Laurant.
500 Koha Development Team <http://koha-community.org/>
502 Galen Charlton <galen.charlton@liblime.com>