Bug 22330: (QA follow-up) Remove duplicate use lines, combine and sort remaning lines
[koha.git] / C4 / Installer.pm
index a7eb1f6..a1c4714 100644 (file)
@@ -4,25 +4,34 @@ package C4::Installer;
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
-use strict;
-#use warnings; FIXME - Bug 2505
+use Modern::Perl;
 
-our $VERSION = 3.00;
+use Encode qw( encode is_utf8 );
+use DBIx::RunSQL;
 use C4::Context;
-use C4::Installer::PerlModules 1.000000;
+use C4::Installer::PerlModules;
+use DBI;
+use Koha;
+
+use vars qw(@ISA @EXPORT);
+BEGIN {
+    require Exporter;
+    @ISA = qw( Exporter );
+    push @EXPORT, qw( foreign_key_exists index_exists column_exists );
+};
 
 =head1 NAME
 
@@ -34,11 +43,11 @@ C4::Installer
  my $installer = C4::Installer->new();
  my $all_languages = getAllLanguages();
  my $error = $installer->load_db_schema();
- my $list = $installer->sql_file_list('en', 'marc21', { optional => 1, mandatory => 1 });
+ my $list;
+ #fill $list with list of sql files
  my ($fwk_language, $error_list) = $installer->load_sql_in_order($all_languages, @$list);
  $installer->set_version_syspref();
  $installer->set_marcflavour_syspref('MARC21');
- $installer->set_indexing_engine(0);
 
 =head1 DESCRIPTION
 
@@ -66,8 +75,17 @@ sub new {
     $self->{'port'}     = C4::Context->config("port");
     $self->{'user'}     = C4::Context->config("user");
     $self->{'password'} = C4::Context->config("pass");
+    $self->{'tls'} = C4::Context->config("tls");
+    if( $self->{'tls'} && $self->{'tls'} eq 'yes' ) {
+        $self->{'ca'} = C4::Context->config('ca');
+        $self->{'cert'} = C4::Context->config('cert');
+        $self->{'key'} = C4::Context->config('key');
+        $self->{'tlsoptions'} = ";mysql_ssl=1;mysql_ssl_client_key=".$self->{key}.";mysql_ssl_client_cert=".$self->{cert}.";mysql_ssl_ca_file=".$self->{ca};
+        $self->{'tlscmdline'} =  " --ssl-cert ". $self->{cert} . " --ssl-key " . $self->{key} . " --ssl-ca ".$self->{ca}." "
+    }
     $self->{'dbh'} = DBI->connect("DBI:$self->{dbms}:dbname=$self->{dbname};host=$self->{hostname}" .
-                                  ( $self->{port} ? ";port=$self->{port}" : "" ),
+                                  ( $self->{port} ? ";port=$self->{port}" : "" ).
+                                  ( $self->{tlsoptions} ? $self->{tlsoptions} : ""),
                                   $self->{'user'}, $self->{'password'});
     $self->{'language'} = undef;
     $self->{'marcflavour'} = undef;
@@ -78,27 +96,6 @@ sub new {
     return $self;
 }
 
-=head2 marcflavour_list
-
-  my ($marcflavours) = $installer->marcflavour_list($lang);
-
-Return a arrayref of the MARC flavour sets available for the
-specified language C<$lang>.  Returns 'undef' if a directory
-for the language does not exist.
-
-=cut
-
-sub marcflavour_list {
-    my $self = shift;
-    my $lang = shift;
-
-    my $dir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/$lang/marcflavour";
-    opendir(MYDIR, $dir) or return;
-    my @list = grep { !/^\.|CVS/ && -d "$dir/$_" } readdir(MYDIR);
-    closedir MYDIR;
-    return \@list;
-}
-
 =head2 marc_framework_sql_list
 
   my ($defaulted_to_en, $list) = 
@@ -154,17 +151,16 @@ sub marc_framework_sql_list {
         my @frameworklist;
         map {
             my $name = substr( $_, 0, -4 );
-            open FILE, "<:utf8","$dir/$requirelevel/$name.txt";
-            my $lines = <FILE>;
-            $lines =~ s/\n|\r/<br \/>/g;
-            use utf8;
-            utf8::encode($lines) unless ( utf8::is_utf8($lines) );
+            open my $fh, "<:encoding(UTF-8)", "$dir/$requirelevel/$name.txt";
+            my $line = <$fh>;
+            $line = Encode::encode('UTF-8', $line) unless ( Encode::is_utf8($line) );
+            my @lines = split /\n/, $line;
             my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
             push @frameworklist,
               {
                 'fwkname'        => $name,
                 'fwkfile'        => "$dir/$requirelevel/$_",
-                'fwkdescription' => $lines,
+                'fwkdescription' => \@lines,
                 'checked'        => ( ( $frameworksloaded{$_} || $mandatory ) ? 1 : 0 ),
                 'mandatory'      => $mandatory,
               };
@@ -232,17 +228,16 @@ sub sample_data_sql_list {
         my @frameworklist;
         map {
             my $name = substr( $_, 0, -4 );
-            open FILE, "<:utf8","$dir/$requirelevel/$name.txt";
-            my $lines = <FILE>;
-            $lines =~ s/\n|\r/<br \/>/g;
-            use utf8;
-            utf8::encode($lines) unless ( utf8::is_utf8($lines) );
+            open my $fh , "<:encoding(UTF-8)", "$dir/$requirelevel/$name.txt";
+            my $line = <$fh>;
+            $line = Encode::encode('UTF-8', $line) unless ( Encode::is_utf8($line) );
+            my @lines = split /\n/, $line;
             my $mandatory = ($requirelevel =~ /(mandatory|requi|oblig|necess)/i);
             push @frameworklist,
               {
                 'fwkname'        => $name,
                 'fwkfile'        => "$dir/$requirelevel/$_",
-                'fwkdescription' => $lines,
+                'fwkdescription' => \@lines,
                 'checked'        => ( ( $frameworksloaded{$_} || $mandatory ) ? 1 : 0 ),
                 'mandatory'      => $mandatory,
               };
@@ -258,41 +253,6 @@ sub sample_data_sql_list {
     return ($defaulted_to_en, \@levellist);
 }
 
-=head2 sql_file_list
-
-  my $list = $installer->sql_file_list($lang, $marcflavour, $subset_wanted);
-
-Returns an arrayref containing the filepaths of installer SQL scripts
-available for laod.  The C<$lang> and C<$marcflavour> arguments
-specify the desired language and MARC flavour. while C<$subset_wanted>
-is a hashref containing possible named parameters 'mandatory' and 'optional'.
-
-=cut
-
-sub sql_file_list {
-    my $self = shift;
-    my $lang = shift;
-    my $marcflavour = shift;
-    my $subset_wanted = shift;
-
-    my ($marc_defaulted_to_en, $marc_sql) = $self->marc_framework_sql_list($lang, $marcflavour);
-    my ($sample_defaulted_to_en, $sample_sql) = $self->sample_data_sql_list($lang);
-
-    my @sql_list = ();
-    map {
-        map {
-            if ($subset_wanted->{'mandatory'}) {
-                push @sql_list, $_->{'fwkfile'} if $_->{'mandatory'};
-            }
-            if ($subset_wanted->{'optional'}) {
-                push @sql_list, $_->{'fwkfile'} unless $_->{'mandatory'};
-            }
-        } @{ $_->{'frameworks'} }
-    } (@$marc_sql, @$sample_sql);
-
-    return \@sql_list
-}
-
 =head2 load_db_schema
 
   my $error = $installer->load_db_schema();
@@ -354,9 +314,19 @@ sub load_sql_in_order {
     $request->execute;
     my ($systempreference) = $request->fetchrow;
     $systempreference = '' unless defined $systempreference; # avoid warning
+    # Make sure subtag_registry.sql is loaded second
+    my $subtag_registry = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/mandatory/subtag_registry.sql";
+    unshift(@fnames, $subtag_registry);
+    # Make sure authorised value categories are loaded at the beginning
+    my $av_cat = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/mandatory/auth_val_cat.sql";
+    unshift(@fnames, $av_cat);
     # Make sure the global sysprefs.sql file is loaded first
     my $globalsysprefs = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/sysprefs.sql";
     unshift(@fnames, $globalsysprefs);
+    push @fnames, C4::Context->config('intranetdir') . "/installer/data/mysql/userflags.sql";
+    push @fnames, C4::Context->config('intranetdir') . "/installer/data/mysql/userpermissions.sql";
+    push @fnames, C4::Context->config('intranetdir') . "/installer/data/mysql/audio_alerts.sql";
+    push @fnames, C4::Context->config('intranetdir') . "/installer/data/mysql/account_offset_types.sql";
     foreach my $file (@fnames) {
         #      warn $file;
         undef $/;
@@ -408,10 +378,10 @@ sub load_sql_in_order {
 Set the 'marcflavour' system preference.  The incoming
 C<$marcflavour> references to a subdirectory of
 installer/data/$dbms/$lang/marcflavour, and is
-normalized to MARC21 or UNIMARC.
+normalized to MARC21, UNIMARC or NORMARC.
 
 FIXME: this method assumes that the MARC flavour will be either
-MARC21 or UNIMARC.
+MARC21, UNIMARC or NORMARC.
 
 =cut
 
@@ -423,38 +393,14 @@ sub set_marcflavour_syspref {
     # marc_cleaned finds the marcflavour, without the variant.
     my $marc_cleaned = 'MARC21';
     $marc_cleaned = 'UNIMARC' if $marcflavour =~ /unimarc/i;
+    $marc_cleaned = 'NORMARC' if $marcflavour =~ /normarc/i;
     my $request =
         $self->{'dbh'}->prepare(
-          "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');"
+          "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');"
         );
     $request->execute;
 }
 
-=head2 set_indexing_engine
-
-  $installer->set_indexing_engine($nozebra);
-
-Sets system preferences related to the indexing
-engine.  The C<$nozebra> argument is a boolean;
-if true, turn on NoZebra mode and turn off QueryFuzzy,
-QueryWeightFields, and QueryStemming.  If false, turn
-off NoZebra mode (i.e., use the Zebra search engine).
-
-=cut
-
-sub set_indexing_engine {
-    my $self = shift;
-    my $nozebra = shift;
-
-    if ($nozebra) {
-        $self->{'dbh'}->do("UPDATE systempreferences SET value=1 WHERE variable='NoZebra'");
-        $self->{'dbh'}->do("UPDATE systempreferences SET value=0 WHERE variable in ('QueryFuzzy','QueryWeightFields','QueryStemming')");
-    } else {
-        $self->{'dbh'}->do("UPDATE systempreferences SET value=0 WHERE variable='NoZebra'");
-    }
-
-}
-
 =head2 set_version_syspref
 
   $installer->set_version_syspref();
@@ -467,7 +413,7 @@ Koha software version.
 sub set_version_syspref {
     my $self = shift;
 
-    my $kohaversion=C4::Context::KOHAVERSION;
+    my $kohaversion = Koha::version();
     # remove the 3 last . to have a Perl number
     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
     if (C4::Context->preference('Version')) {
@@ -486,53 +432,39 @@ sub set_version_syspref {
 
   my $error = $installer->load_sql($filename);
 
-Runs a the specified SQL using the DB's command-line
-SQL tool, and returns any strings sent to STDERR
-by the command-line tool.
-
-B<FIXME:> there has been a long-standing desire to
-replace this with an SQL loader that goes
-through DBI; partly for portability issues
-and partly to improve error handling.
+Runs a the specified SQL file using a sql loader DBIx::RunSQL
+Returns any strings sent to STDERR
 
-B<FIXME:> even using the command-line loader, some more
-basic error handling should be added - deal
-with missing files, e.g.
+# FIXME This should be improved: sometimes the caller and load_sql warn the same
+error.
 
 =cut
 
 sub load_sql {
     my $self = shift;
     my $filename = shift;
-
-    my $datadir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}";
     my $error;
-    my $strcmd;
-    if ( $self->{dbms} eq 'mysql' ) {
-        $strcmd = "mysql "
-            . ( $self->{hostname} ? " -h $self->{hostname} " : "" )
-            . ( $self->{port}     ? " -P $self->{port} "     : "" )
-            . ( $self->{user}     ? " -u $self->{user} "     : "" )
-            . ( $self->{password} ? " -p'$self->{password}'"   : "" )
-            . " $self->{dbname} ";
-        $error = qx($strcmd --default-character-set=utf8 <$filename 2>&1 1>/dev/null);
-    } elsif ( $self->{dbms} eq 'Pg' ) {
-        $strcmd = "psql "
-            . ( $self->{hostname} ? " -h $self->{hostname} " : "" )
-            . ( $self->{port}     ? " -p $self->{port} "     : "" )
-            . ( $self->{user}     ? " -U $self->{user} "     : "" )
-#            . ( $self->{password} ? " -W $self->{password}"   : "" )       # psql will NOT accept a password, but prompts...
-            . " $self->{dbname} ";                        # Therefore, be sure to run 'trust' on localhost in pg_hba.conf -fbcit
-        $error = qx($strcmd -f $filename 2>&1 1>/dev/null);
-        # Be sure to set 'client_min_messages = error' in postgresql.conf
-        # so that only true errors are returned to stderr or else the installer will
-        # report the import a failure although it really succeded -fbcit
-    }
-#   errors thrown while loading installer data should be logged
-    if($error) {
-      warn "C4::Installer::load_sql returned the following errors while attempting to load $filename:\n";
-      warn "$error";
+
+    my $dbh = $self->{ dbh };
+
+    my $dup_stderr;
+    do {
+        local *STDERR;
+        open STDERR, ">>", \$dup_stderr;
+
+        eval {
+            DBIx::RunSQL->run_sql_file(
+                dbh     => $dbh,
+                sql     => $filename,
+            );
+        };
+    };
+    #   errors thrown while loading installer data should be logged
+    if( $dup_stderr ) {
+        warn "C4::Installer::load_sql returned the following errors while attempting to load $filename:\n";
+        $error = $dup_stderr;
     }
+
     return $error;
 }
 
@@ -574,6 +506,36 @@ sub get_file_path_from_name {
 
 }
 
+sub foreign_key_exists {
+    my ( $table_name, $constraint_name ) = @_;
+    my $dbh = C4::Context->dbh;
+    my (undef, $infos) = $dbh->selectrow_array(qq|SHOW CREATE TABLE $table_name|);
+    return $infos =~ m|CONSTRAINT `$constraint_name` FOREIGN KEY|;
+}
+
+sub index_exists {
+    my ( $table_name, $key_name ) = @_;
+    my $dbh = C4::Context->dbh;
+    my ($exists) = $dbh->selectrow_array(
+        qq|
+        SHOW INDEX FROM $table_name
+        WHERE key_name = ?
+        |, undef, $key_name
+    );
+    return $exists;
+}
+
+sub column_exists {
+    my ( $table_name, $column_name ) = @_;
+    my $dbh = C4::Context->dbh;
+    my ($exists) = $dbh->selectrow_array(
+        qq|
+        SHOW COLUMNS FROM $table_name
+        WHERE Field = ?
+        |, undef, $column_name
+    );
+    return $exists;
+}
 
 =head1 AUTHOR