Bug 32612: (QA follow-up) Add BINMODE method to C4::SIP::Trapper
[koha-ffzg.git] / C4 / Installer.pm
index 203d175..ffab0ed 100644 (file)
@@ -20,19 +20,21 @@ package C4::Installer;
 use Modern::Perl;
 
 use Try::Tiny;
-use Encode qw( encode is_utf8 );
+use Encode qw( encode decode is_utf8 );
 use DBIx::RunSQL;
 use YAML::XS;
+use File::Slurp qw( read_file );
+use DBI;
+
 use C4::Context;
 use Koha::Schema;
-use DBI;
 use Koha;
 
 use vars qw(@ISA @EXPORT);
 BEGIN {
     require Exporter;
     @ISA = qw( Exporter );
-    push @EXPORT, qw( primary_key_exists unique_key_exists foreign_key_exists index_exists column_exists TableExists marc_framework_sql_list TransformToNum CheckVersion NewVersion sanitize_zero_date update get_db_entries );
+    push @EXPORT, qw( primary_key_exists unique_key_exists foreign_key_exists index_exists column_exists TableExists marc_framework_sql_list TransformToNum CheckVersion NewVersion SetVersion sanitize_zero_date update get_db_entries get_atomic_updates run_atomic_updates );
 };
 
 =head1 NAME
@@ -71,7 +73,7 @@ sub new {
     my $self = {};
 
     # get basic information from context
-    $self->{'dbname'}   = C4::Context->config("database");
+    $self->{'dbname'}   = C4::Context->config("database_test") || C4::Context->config("database");
     $self->{'dbms'}     = C4::Context->config("db_scheme") ? C4::Context->config("db_scheme") : "mysql";
     $self->{'hostname'} = C4::Context->config("hostname");
     $self->{'port'}     = C4::Context->config("port");
@@ -283,6 +285,7 @@ sub load_db_schema {
     my $datadir = C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}";
 
     # Disable checks before load
+    # NOTE: These lines are found in kohastructure itself, but DBIx::RunSQL ignores them!
     $self->{'dbh'}->do(q{SET NAMES utf8mb4});
     $self->{'dbh'}->do(q{SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0});
     $self->{'dbh'}->do(q{SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0});
@@ -362,13 +365,12 @@ sub load_sql_in_order {
     push @fnames, "$global_mandatory_dir/userflags.sql",
                   "$global_mandatory_dir/userpermissions.sql",
                   "$global_mandatory_dir/audio_alerts.sql",
-                  "$global_mandatory_dir/account_offset_types.sql",
                   "$global_mandatory_dir/account_credit_types.sql",
                   "$global_mandatory_dir/account_debit_types.sql",
                   ;
     my $localization_file = C4::Context->config('intranetdir') .
                             "/installer/data/$self->{dbms}/localization/$langchoice/custom.sql";
-    if ( $langchoice ne 'en' and -f $localization_file ) {
+    if ( -f $localization_file ) {
         push @fnames, $localization_file;
     }
     foreach my $file (@fnames) {
@@ -422,10 +424,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, UNIMARC or NORMARC.
+normalized to MARC21 or UNIMARC.
 
 FIXME: this method assumes that the MARC flavour will be either
-MARC21, UNIMARC or NORMARC.
+MARC21 or UNIMARC.
 
 =cut
 
@@ -437,10 +439,9 @@ 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, UNIMARC or NORMARC) used for character encoding','MARC21|UNIMARC|NORMARC','Choice');"
+          "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');"
         );
     $request->execute;
 }
@@ -632,12 +633,15 @@ sub get_file_path_from_name {
 sub primary_key_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 = 'PRIMARY' AND column_name = ?
-        |, undef, $key_name
-    );
+    my $sql = qq| SHOW INDEX FROM $table_name WHERE key_name='PRIMARY' |;
+    my $exists;
+    if( $key_name ){
+        $sql .= 'AND column_name = ? ' if $key_name;
+        ($exists) = $dbh->selectrow_array( $sql, undef, $key_name );
+    } else {
+        ($exists) = $dbh->selectrow_array( $sql, undef );
+    }
+
     return $exists;
 }
 
@@ -718,41 +722,54 @@ sub get_db_entries {
     return \@need_update;
 }
 
+sub run_db_rev {
+    my ($file) = @_;
+
+    my $db_rev = do $file;
+
+    my $error;
+    my $out = '';
+    open my $outfh, '>', \$out;
+    try {
+        my $schema = Koha::Database->new->schema;
+        $schema->txn_do(
+            sub {
+                $db_rev->{up}->( { dbh => $schema->storage->dbh, out => $outfh } );
+            }
+        );
+    }
+    catch {
+        $error = $_;
+    };
+
+    close $outfh;
+    $out = decode( 'UTF-8', $out );
+
+    my $db_entry = {
+        filepath    => $file,
+        bug_number  => $db_rev->{bug_number},
+        description => $db_rev->{description},
+        exec_output => $out,
+        version     => scalar version_from_file($file),
+        time        => POSIX::strftime( "%H:%M:%S", localtime ),
+        error       => $error
+    };
+    $db_entry->{output} = generate_output_db_entry($db_entry, $out);
+    return $db_entry;
+}
+
 sub update {
     my ( $files, $params ) = @_;
 
     my $force = $params->{force} || 0;
 
-    my $schema = Koha::Database->new->schema;
     my ( @done, @errors );
     for my $file ( @$files ) {
 
-        my $db_rev = do $file;
-
-        my $error;
+        my $db_entry = run_db_rev($file);
 
-        my $out = '';
-        open my $outfh, '>', \$out;
-        try {
-            $schema->txn_do(
-                sub {
-                    $db_rev->{up}->({ dbh => $schema->storage->dbh, out => $outfh });
-                }
-            );
-        } catch {
-            $error = $_;
-        };
-
-        my $db_entry = {
-            bug_number  => $db_rev->{bug_number},
-            description => $db_rev->{description},
-            version     => version_from_file($file),
-            time        => POSIX::strftime( "%H:%M:%S", localtime ),
-        };
-        $db_entry->{output} = output_version( { %$db_entry, done => !$error, report => $out } );
-
-        if ( $error ) {
-            push @errors, { %$db_entry, error => $error };
+        if ( $db_entry->{error} ) {
+            push @errors, $db_entry;
             $force ? next : last ;
                 # We stop the update if an error occurred!
         }
@@ -763,37 +780,115 @@ sub update {
     return { success => \@done, error => \@errors };
 }
 
-sub output_version {
+sub generate_output_db_entry {
     my ( $db_entry ) = @_;
 
     my $description = $db_entry->{description};
-    my $report = $db_entry->{report};
-    my $DBversion = $db_entry->{version};
-    my $bug_number = $db_entry->{bug_number};
-    my $time = $db_entry->{time};
-    my $done = defined $db_entry->{done}
-                ? $db_entry->{done}
-                    ? " done"
-                    : " failed"
-                : ""; # For old versions, we don't know if we succeed or failed
+    my $output      = $db_entry->{output};
+    my $DBversion   = $db_entry->{version};
+    my $bug_number  = $db_entry->{bug_number};
+    my $time        = $db_entry->{time};
+    my $exec_output = $db_entry->{exec_output};
+    my $done        = defined $db_entry->{done}
+                       ? $db_entry->{done}
+                           ? " done"
+                           : " failed"
+                       : ""; # For old versions, we don't know if we succeed or failed
 
     my @output;
 
-    if ($bug_number) {
-        push @output, sprintf('Upgrade to %s %s [%s]: Bug %5s - %s', $DBversion, $done, $time, $bug_number, $description);
-    } else {
-        push @output, sprintf('Upgrade to %s %s [%s]: %s', $DBversion, $done, $time, $description);
+    if ( $DBversion ) {
+        if ($bug_number) {
+            push @output, sprintf('Upgrade to %s %s [%s]: Bug %5s - %s', $DBversion, $done, $time, $bug_number, $description);
+        } else {
+            push @output, sprintf('Upgrade to %s %s [%s]: %s', $DBversion, $done, $time, $description);
+        }
+    } else { # Atomic update
+        if ($bug_number) {
+            push @output, sprintf('DEV atomic update %s %s [%s]: Bug %5s - %s', $db_entry->{filepath}, $done, $time, $bug_number, $description);
+        } else { # Old atomic update syntax
+            push @output, sprintf('DEV atomic update %s %s [%s]', $db_entry->{filepath}, $done, $time);
+        }
     }
 
-    if ($report) {
-        foreach my $line (split /\n/, $report) {
-            push @output, sprintf "\t\t\t\t\t\t   - %s", $line;
+    if ($exec_output) {
+        foreach my $line (split /\n/, $exec_output) {
+            push @output, sprintf "\t%s", $line;
         }
     }
 
     return \@output;
 }
 
+sub get_atomic_updates {
+    my @atomic_upate_files;
+    # if there is anything in the atomicupdate, read and execute it.
+    my $update_dir = C4::Context->config('intranetdir') . '/installer/data/mysql/atomicupdate/';
+    opendir( my $dirh, $update_dir );
+    foreach my $file ( sort readdir $dirh ) {
+        next if $file !~ /\.(perl|pl)$/;  #skip other files
+        next if $file eq 'skeleton.perl' || $file eq 'skeleton.pl'; # skip the skeleton files
+
+        push @atomic_upate_files, $file;
+    }
+    return \@atomic_upate_files;
+}
+
+sub run_atomic_updates {
+    my ( $files ) = @_;
+
+    my $update_dir = C4::Context->config('intranetdir') . '/installer/data/mysql/atomicupdate/';
+    my ( @done, @errors );
+    for my $file ( @$files ) {
+        my $filepath = $update_dir . $file;
+
+        my $atomic_update;
+        if ( $file =~ m{\.perl$} ) {
+            my $code = read_file( $filepath );
+            my ( $out, $err ) = ('', '');
+            {
+                open my $oldout, ">&STDOUT";
+                close STDOUT;
+                open STDOUT,'>:encoding(utf8)', \$out;
+                my $DBversion = Koha::version; # We need $DBversion and $dbh for the eval
+                my $dbh = C4::Context->dbh;
+                eval $code; ## no critic (StringyEval)
+                $err = $@;
+                warn $err if $err;
+                close STDOUT;
+                open STDOUT, ">&", $oldout;
+            }
+
+            $atomic_update = {
+                filepath    => $filepath,
+                description => '',
+                version     => undef,
+                time        => POSIX::strftime( "%H:%M:%S", localtime ),
+            };
+
+
+            $atomic_update->{output} =
+              $out
+              ? [ split "\n", $out ]
+              : generate_output_db_entry($atomic_update); # There wad an error, we didn't reach NewVersion)
+
+            $atomic_update->{error} = $err if $err;
+        } elsif ( $file =~ m{\.pl$} ) {
+            $atomic_update = run_db_rev($filepath);
+        } else {
+            warn "Atomic update must be .perl or .pl ($file)";
+        }
+
+        if ( $atomic_update->{error} ) {
+            push @errors, $atomic_update;
+        } else {
+            push @done, $atomic_update;
+        }
+    }
+
+    return { success => \@done, error => \@errors };
+}
+
 =head2 DropAllForeignKeys($table)
 
 Drop all foreign keys of the table $table
@@ -869,13 +964,19 @@ sub NewVersion {
 
     SetVersion($DBversion);
 
-    unless ( ref($descriptions) ) {
-        $descriptions = [ $descriptions ];
+    my ( $description, $report );
+    if ( ref($descriptions) ) {
+        $description = shift @$descriptions;
+        $report      = join( "\n", @{$descriptions} );
+    }
+    else {
+        $description = $descriptions;
     }
 
-    my $output = output_version( {
+    my $output = generate_output_db_entry( {
             bug_number  => $bug_number,
-            description => $descriptions,
+            description => $description,
+            report      => $report,
             version     => $DBversion,
             time        => POSIX::strftime( "%H:%M:%S", localtime ),
     });