View | Details | Raw Unified | Return to bug 25078
Collapse All | Expand All

(-)a/C4/Installer.pm (-1 / +232 lines)
Lines 19-28 package C4::Installer; Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Try::Tiny;
22
use Encode qw( encode is_utf8 );
23
use Encode qw( encode is_utf8 );
23
use DBIx::RunSQL;
24
use DBIx::RunSQL;
24
use YAML::XS;
25
use YAML::XS;
25
use C4::Context;
26
use C4::Context;
27
use Koha::Schema;
26
use DBI;
28
use DBI;
27
use Koha;
29
use Koha;
28
30
Lines 30-36 use vars qw(@ISA @EXPORT); Link Here
30
BEGIN {
32
BEGIN {
31
    require Exporter;
33
    require Exporter;
32
    @ISA = qw( Exporter );
34
    @ISA = qw( Exporter );
33
    push @EXPORT, qw( primary_key_exists foreign_key_exists index_exists column_exists TableExists);
35
    push @EXPORT, qw( primary_key_exists foreign_key_exists index_exists column_exists TableExists TransformToNum CheckVersion sanitize_zero_date update get_db_entries );
34
};
36
};
35
37
36
=head1 NAME
38
=head1 NAME
Lines 683-688 sub TableExists { # Could be renamed table_exists for consistency Link Here
683
    return 0;
685
    return 0;
684
}
686
}
685
687
688
sub version_from_file {
689
    my $file = shift;
690
    return unless $file =~ m|(^\|/)(\d{2})(\d{2})(\d{2})(\d{3}).pl$|;
691
    return sprintf "%s.%s.%s.%s", $2, $3, $4, $5;
692
}
693
694
sub get_db_entries {
695
    my $db_revs_dir = C4::Context->config('intranetdir') . '/installer/data/mysql/db_revs';
696
    opendir my $dh, $db_revs_dir or die "Cannot open $db_revs_dir dir ($!)";
697
    my @files = sort grep { m|\.pl$| && ! m|skeleton\.pl$| } readdir $dh;
698
    my @need_update;
699
    for my $file ( @files ) {
700
        my $version = version_from_file( $file );
701
702
        unless ( $version ) {
703
            warn "Invalid db_rev found: " . $file;
704
            next
705
        }
706
707
        next unless CheckVersion( $version );
708
709
        push @need_update, sprintf( "%s/%s", $db_revs_dir, $file );
710
    }
711
    return \@need_update;
712
}
713
714
sub update {
715
    my ( $files, $params ) = @_;
716
717
    my $force = $params->{force} || 0;
718
719
    my $schema = Koha::Database->new->schema;
720
    my ( @done, @errors );
721
    for my $file ( @$files ) {
722
723
        my $db_rev = do $file;
724
725
        my $error;
726
727
        try {
728
            $schema->txn_do(
729
                sub {
730
                    $db_rev->{up}->();
731
                }
732
            );
733
        } catch {
734
            $error = $_;
735
        };
736
737
        my $db_entry = {
738
            bug_number  => $db_rev->{bug_number},
739
            description => ( ref $db_rev->{description} eq 'CODE' )
740
              ? $db_rev->{description}->()
741
              : $db_rev->{description},
742
            version     => version_from_file($file),
743
            time        => POSIX::strftime( "%H:%M:%S", localtime ),
744
        };
745
        $db_entry->{output} = output_version( { %$db_entry, done => !$error } );
746
747
        if ( $error ) {
748
            push @errors, { %$db_entry, error => $error };
749
            $force ? next : last ;
750
                # We stop the update if an error occurred!
751
        }
752
753
        SetVersion($db_entry->{version});
754
        push @done, $db_entry;
755
    }
756
    return { success => \@done, error => \@errors };
757
}
758
759
sub output_version {
760
    my ( $db_entry ) = @_;
761
762
    my $descriptions = $db_entry->{description};
763
    my $DBversion = $db_entry->{version};
764
    my $bug_number = $db_entry->{bug_number};
765
    my $time = $db_entry->{time};
766
    my $done = $db_entry->{done} ? "done" : "failed";
767
768
    unless ( ref($descriptions) ) {
769
        $descriptions = [ $descriptions ];
770
    }
771
772
    my $first = 1;
773
    my @output;
774
    for my $description ( @$descriptions ) {
775
        if ( @$descriptions > 1 ) {
776
            if ( $first ) {
777
                unless ( $bug_number ) {
778
                    push @output, sprintf "Upgrade to %s %s [%s]:", $DBversion, $done, $time;
779
                } else {
780
                    push @output, sprintf "Upgrade to %s %s [%s]: Bug %5s", $DBversion, $done, $time, $bug_number;
781
                }
782
            }
783
            push @output, sprintf "\t\t\t\t\t\t   - %s", $description;
784
        } else {
785
            unless ( $bug_number ) {
786
                push @output, sprintf "Upgrade to %s %s [%s]: %s", $DBversion, $done, $time, $description;
787
            } else {
788
                push @output, sprintf "Upgrade to %s %s [%s]: Bug %5s - %s", $DBversion, $done, $time, $bug_number, $description;
789
            }
790
        }
791
        $first = 0;
792
    }
793
    return \@output;
794
}
795
796
=head2 DropAllForeignKeys($table)
797
798
Drop all foreign keys of the table $table
799
800
=cut
801
802
sub DropAllForeignKeys {
803
    my ($table) = @_;
804
    # get the table description
805
    my $dbh = C4::Context->dbh;
806
    my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
807
    $sth->execute;
808
    my $vsc_structure = $sth->fetchrow;
809
    # split on CONSTRAINT keyword
810
    my @fks = split /CONSTRAINT /,$vsc_structure;
811
    # parse each entry
812
    foreach (@fks) {
813
        # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
814
        $_ = /(.*) FOREIGN KEY.*/;
815
        my $id = $1;
816
        if ($id) {
817
            # we have found 1 foreign, drop it
818
            $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
819
            $id="";
820
        }
821
    }
822
}
823
824
825
=head2 TransformToNum
826
827
Transform the Koha version from a 4 parts string
828
to a number, with just 1 .
829
830
=cut
831
832
sub TransformToNum {
833
    my $version = shift;
834
    # remove the 3 last . to have a Perl number
835
    $version =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
836
    # three X's at the end indicate that you are testing patch with dbrev
837
    # change it into 999
838
    # prevents error on a < comparison between strings (should be: lt)
839
    $version =~ s/XXX$/999/;
840
    return $version;
841
}
842
843
=head2 SetVersion
844
845
set the DBversion in the systempreferences
846
847
=cut
848
849
sub SetVersion {
850
    return if $_[0]=~ /XXX$/;
851
      #you are testing a patch with a db revision; do not change version
852
    my $kohaversion = TransformToNum($_[0]);
853
    my $dbh = C4::Context->dbh;
854
    if (C4::Context->preference('Version')) {
855
      my $finish=$dbh->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
856
      $finish->execute($kohaversion);
857
    } else {
858
      my $finish=$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')");
859
      $finish->execute($kohaversion);
860
    }
861
    C4::Context::clear_syspref_cache(); # invalidate cached preferences
862
}
863
864
sub NewVersion {
865
    # void FIXME replace me
866
}
867
868
=head2 CheckVersion
869
870
Check whether a given update should be run when passed the proposed version
871
number. The update will always be run if the proposed version is greater
872
than the current database version and less than or equal to the version in
873
kohaversion.pl. The update is also run if the version contains XXX, though
874
this behavior will be changed following the adoption of non-linear updates
875
as implemented in bug 7167.
876
877
=cut
878
879
sub CheckVersion {
880
    my ($proposed_version) = @_;
881
    my $version_number = TransformToNum($proposed_version);
882
883
    # The following line should be deleted when bug 7167 is pushed
884
    return 1 if ( $proposed_version =~ m/XXX/ );
885
886
    if ( C4::Context->preference("Version") < $version_number
887
        && $version_number <= TransformToNum( $Koha::VERSION ) )
888
    {
889
        return 1;
890
    }
891
892
    return 0;
893
}
894
895
sub sanitize_zero_date {
896
    my ( $table_name, $column_name ) = @_;
897
898
    my $dbh = C4::Context->dbh;
899
900
    my (undef, $datatype) = $dbh->selectrow_array(qq|
901
        SHOW COLUMNS FROM $table_name WHERE Field = ?|, undef, $column_name);
902
903
    if ( $datatype eq 'date' ) {
904
        $dbh->do(qq|
905
            UPDATE $table_name
906
            SET $column_name = NULL
907
            WHERE CAST($column_name AS CHAR(10)) = '0000-00-00';
908
        |);
909
    } else {
910
        $dbh->do(qq|
911
            UPDATE $table_name
912
            SET $column_name = NULL
913
            WHERE CAST($column_name AS CHAR(19)) = '0000-00-00 00:00:00';
914
        |);
915
    }
916
}
686
917
687
=head1 AUTHOR
918
=head1 AUTHOR
688
919
(-)a/installer/data/mysql/db_revs/210600000.pl (+8 lines)
Line 0 Link Here
1
use Modern::Perl;
2
use utf8;
3
4
{
5
    bug_number => undef,
6
    description => ["🎵 Run, rabbit run. 🎶", "Dig that hole, forget the sun,", "And when at last the work is done", "Don't sit down it's time to dig another one."],
7
    up => sub {},
8
}
(-)a/installer/data/mysql/db_revs/skeleton.pl (+13 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
{
4
    bug_number => "BUG_NUMBER",
5
    description => "A single line description",
6
    # description => ["Multi", "lines", "description"],
7
    # description => sub { return ["Your dynamic description"] },
8
    up => sub {
9
        my $dbh = C4::Context->dbh;
10
        # Do you stuffs here
11
        $dbh->do(q{});
12
    },
13
}
(-)a/installer/data/mysql/updatedatabase.pl (-155 / +16 lines)
Lines 34-39 use feature 'say'; Link Here
34
# CPAN modules
34
# CPAN modules
35
use DBI;
35
use DBI;
36
use Getopt::Long;
36
use Getopt::Long;
37
use Encode qw( encode_utf8 );
37
# Koha modules
38
# Koha modules
38
use C4::Context;
39
use C4::Context;
39
use C4::Installer;
40
use C4::Installer;
Lines 59-68 my ( Link Here
59
60
60
my $schema = Koha::Database->new()->schema();
61
my $schema = Koha::Database->new()->schema();
61
62
62
my $silent;
63
my ( $silent, $force );
63
GetOptions(
64
GetOptions(
64
    's' =>\$silent
65
    's'     => \$silent,
65
    );
66
    'force' => \$force,
67
);
66
my $dbh = C4::Context->dbh;
68
my $dbh = C4::Context->dbh;
67
$|=1; # flushes output
69
$|=1; # flushes output
68
70
Lines 24281-24289 if( CheckVersion( $DBversion ) ) { Link Here
24281
    NewVersion( $DBversion, "", "Koha 21.05.00 release" );
24283
    NewVersion( $DBversion, "", "Koha 21.05.00 release" );
24282
}
24284
}
24283
24285
24284
$DBversion = '21.06.00.000';
24286
unless ( $ENV{HTTP_HOST} ) { # Is that correct?
24285
if( CheckVersion( $DBversion ) ) {
24287
    my $files = C4::Installer::get_db_entries;
24286
    NewVersion( $DBversion, "", ["🎵 Run, rabbit run. 🎶", "Dig that hole, forget the sun,", "And when at last the work is done", "Don't sit down it's time to dig another one."] );
24288
    my $report = update( $files, { force => $force } );
24289
24290
    for my $s ( @{ $report->{success} } ) {
24291
        say Encode::encode_utf8(join "\n", @{$s->{output}});
24292
    }
24293
    for my $e ( @{ $report->{error} } ) {
24294
        say Encode::encode_utf8(join "\n", @{$e->{output}});
24295
        say Encode::encode_utf8("ERROR - " . $e->{error});
24296
    }
24287
}
24297
}
24288
24298
24289
$DBversion = '21.06.00.001';
24299
$DBversion = '21.06.00.001';
Lines 24391-24543 foreach my $file ( sort readdir $dirh ) { Link Here
24391
    }
24401
    }
24392
}
24402
}
24393
24403
24394
=head1 FUNCTIONS
24395
24396
=head2 DropAllForeignKeys($table)
24397
24398
Drop all foreign keys of the table $table
24399
24400
=cut
24401
24402
sub DropAllForeignKeys {
24403
    my ($table) = @_;
24404
    # get the table description
24405
    my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
24406
    $sth->execute;
24407
    my $vsc_structure = $sth->fetchrow;
24408
    # split on CONSTRAINT keyword
24409
    my @fks = split /CONSTRAINT /,$vsc_structure;
24410
    # parse each entry
24411
    foreach (@fks) {
24412
        # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
24413
        $_ = /(.*) FOREIGN KEY.*/;
24414
        my $id = $1;
24415
        if ($id) {
24416
            # we have found 1 foreign, drop it
24417
            $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
24418
            $id="";
24419
        }
24420
    }
24421
}
24422
24423
24424
=head2 TransformToNum
24425
24426
Transform the Koha version from a 4 parts string
24427
to a number, with just 1 .
24428
24429
=cut
24430
24431
sub TransformToNum {
24432
    my $version = shift;
24433
    # remove the 3 last . to have a Perl number
24434
    $version =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
24435
    # three X's at the end indicate that you are testing patch with dbrev
24436
    # change it into 999
24437
    # prevents error on a < comparison between strings (should be: lt)
24438
    $version =~ s/XXX$/999/;
24439
    return $version;
24440
}
24441
24442
=head2 SetVersion
24443
24444
set the DBversion in the systempreferences
24445
24446
=cut
24447
24448
sub SetVersion {
24449
    return if $_[0]=~ /XXX$/;
24450
      #you are testing a patch with a db revision; do not change version
24451
    my $kohaversion = TransformToNum($_[0]);
24452
    if (C4::Context->preference('Version')) {
24453
      my $finish=$dbh->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
24454
      $finish->execute($kohaversion);
24455
    } else {
24456
      my $finish=$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')");
24457
      $finish->execute($kohaversion);
24458
    }
24459
    C4::Context::clear_syspref_cache(); # invalidate cached preferences
24460
}
24461
24462
sub NewVersion {
24463
    my ( $DBversion, $bug_number, $descriptions ) = @_;
24464
24465
    SetVersion($DBversion);
24466
24467
    unless ( ref($descriptions) ) {
24468
        $descriptions = [ $descriptions ];
24469
    }
24470
    my $first = 1;
24471
    my $time = POSIX::strftime("%H:%M:%S",localtime);
24472
    for my $description ( @$descriptions ) {
24473
        if ( @$descriptions > 1 ) {
24474
            if ( $first ) {
24475
                unless ( $bug_number ) {
24476
                    say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
24477
                } else {
24478
                    say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
24479
                }
24480
            } else {
24481
                say sprintf "\t\t\t\t\t\t   - %s", $description;
24482
            }
24483
        } else {
24484
            unless ( $bug_number ) {
24485
                say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
24486
            } else {
24487
                say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
24488
            }
24489
        }
24490
        $first = 0;
24491
    }
24492
}
24493
24494
=head2 CheckVersion
24495
24496
Check whether a given update should be run when passed the proposed version
24497
number. The update will always be run if the proposed version is greater
24498
than the current database version and less than or equal to the version in
24499
kohaversion.pl. The update is also run if the version contains XXX, though
24500
this behavior will be changed following the adoption of non-linear updates
24501
as implemented in bug 7167.
24502
24503
=cut
24504
24505
sub CheckVersion {
24506
    my ($proposed_version) = @_;
24507
    my $version_number = TransformToNum($proposed_version);
24508
24509
    # The following line should be deleted when bug 7167 is pushed
24510
    return 1 if ( $proposed_version =~ m/XXX/ );
24511
24512
    if ( C4::Context->preference("Version") < $version_number
24513
        && $version_number <= TransformToNum( $Koha::VERSION ) )
24514
    {
24515
        return 1;
24516
    }
24517
    else {
24518
        return 0;
24519
    }
24520
}
24521
24522
sub sanitize_zero_date {
24523
    my ( $table_name, $column_name ) = @_;
24524
24525
    my (undef, $datatype) = $dbh->selectrow_array(qq|
24526
        SHOW COLUMNS FROM $table_name WHERE Field = ?|, undef, $column_name);
24527
24528
    if ( $datatype eq 'date' ) {
24529
        $dbh->do(qq|
24530
            UPDATE $table_name
24531
            SET $column_name = NULL
24532
            WHERE CAST($column_name AS CHAR(10)) = '0000-00-00';
24533
        |);
24534
    } else {
24535
        $dbh->do(qq|
24536
            UPDATE $table_name
24537
            SET $column_name = NULL
24538
            WHERE CAST($column_name AS CHAR(19)) = '0000-00-00 00:00:00';
24539
        |);
24540
    }
24541
}
24542
24543
exit;
24404
exit;
(-)a/installer/install.pl (-35 / +11 lines)
Lines 393-433 elsif ( $step && $step == 3 ) { Link Here
393
            chk_log( $logdir, "updatedatabase-error_$filename_suffix" )
393
            chk_log( $logdir, "updatedatabase-error_$filename_suffix" )
394
        );
394
        );
395
395
396
        my $cmd = C4::Context->config("intranetdir")
396
        my $updatedatabase_path = C4::Context->config("intranetdir")
397
          . "/installer/data/$info{dbms}/updatedatabase.pl >> $logfilepath 2>> $logfilepath_errors";
397
          . "/installer/data/$info{dbms}/updatedatabase.pl";
398
398
399
        system($cmd );
399
        my $db_entries = get_db_entries();
400
400
        my $report = update( $db_entries );
401
        my $fh;
401
402
        open( $fh, "<:encoding(utf-8)", $logfilepath )
402
        # FIXME restore log to logfilepath and logfilepath_errors
403
          or die "Cannot open log file $logfilepath: $!";
403
404
        my @report = <$fh>;
404
        $template->param( success => $report->{success}, error => $report->{error} );
405
        close $fh;
405
406
        if (@report) {
406
        #warn "The following errors were returned while attempting to run the updatedatabase.pl script:\n"; #FIXME restore this
407
            $template->param( update_report =>
408
                  [ map { { line => $_ =~ s/\t/&emsp;&emsp;/gr } } split( /\n/, join( '', @report ) ) ]
409
            );
410
            $template->param( has_update_succeeds => 1 );
411
        }
412
        else {
413
            eval { `rm $logfilepath` };
414
        }
415
        open( $fh, "<:encoding(utf-8)", $logfilepath_errors )
416
          or die "Cannot open log file $logfilepath_errors: $!";
417
        @report = <$fh>;
418
        close $fh;
419
        if (@report) {
420
            $template->param( update_errors =>
421
                  [ map { { line => $_ } } split( /\n/, join( '', @report ) ) ]
422
            );
423
            $template->param( has_update_errors => 1 );
424
            warn
425
"The following errors were returned while attempting to run the updatedatabase.pl script:\n";
426
            foreach my $line (@report) { warn "$line\n"; }
427
        }
428
        else {
429
            eval { `rm $logfilepath_errors` };
430
        }
431
        $template->param( $op => 1 );
407
        $template->param( $op => 1 );
432
    }
408
    }
433
    else {
409
    else {
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/installer/step3.tt (-15 / +26 lines)
Lines 244-274 Link Here
244
244
245
                [% IF ( updatestructure ) %]
245
                [% IF ( updatestructure ) %]
246
                    <h2>Updating database structure</h2>
246
                    <h2>Updating database structure</h2>
247
                    [% IF ( has_update_succeeds ) %]
247
                    [% IF success %]
248
                        <p>Update report :</p>
248
                        <p>Update report:</p>
249
                        <ul>
249
                        <ul>
250
                            [% FOREACH l IN update_report %]
250
                            [% FOR s IN success %]
251
                                [% SET line = l.line %]
251
                                [% FOR o IN s.output %]
252
                                [% IF line.match('^Upgrade to') %]
252
                                    <li>[% o | html %]</li>
253
                                    <li>[% line | $raw %]</li>
253
                                    [% IF s.output.size > 1 %]
254
                                [% ELSE %]
254
                                        [% IF loop.first %]<ul>[% ELSIF loop.last %]</ul>[% END %]
255
                                    [% line | $raw %]<br/>
255
                                    [% END %]
256
                                [% END %]
256
                                [% END %]
257
                            [% END %]
257
                            [% END %]
258
                        </ul>
258
                        </ul>
259
                    [% END %]
259
                    [% END %]
260
                    [% IF ( has_update_errors ) %]
260
                    [% IF error %]
261
                        <p>Update errors :</p>
261
                        <p>Update error :</p>
262
                        <ul>
262
                        <ul>
263
                            [% FOREACH update_error IN update_errors %]
263
                            [% FOR e IN error %]
264
                                <li class="update_error">[% update_error.line | html %]</li>
264
                                [% FOR o IN e.output %]
265
                                    <li class="update_error">
266
                                        [% o | html %]
267
                                        <br/>
268
                                        ERROR: [% e.error | html %]
269
270
                                        [% IF e.output.size > 1 %]
271
                                            [% IF loop.first %]<ul>[% ELSIF loop.last %]</ul>[% END %]
272
                                        [% END %]
273
                                    </li>
274
                                [% END %]
265
                            [% END %]
275
                            [% END %]
266
                        </ul>
276
                        </ul>
267
                    [% END %]
277
                    [% END %]
268
                    [% UNLESS ( has_update_errors ) %]
278
                    [% UNLESS error %]
269
                        <p>Everything went okay. Update done.</p>
279
                        <p>Everything went okay. Update done.</p>
280
                        <p><a href="install.pl?step=3&amp;op=finished" class="btn btn-primary">Continue to log in to Koha</a></p>
281
                    [% ELSE %]
282
                        <p><a href="install.pl?step=3&amp;op=updatestructure" class="btn btn-primary">Try again</a></p>
270
                    [% END %]
283
                    [% END %]
271
                    <p><a href="install.pl?step=3&amp;op=finished" class="btn btn-primary">Continue to log in to Koha</a></p>
272
               [% END # / IF updatestructure %]
284
               [% END # / IF updatestructure %]
273
            </div> <!-- / #installer-step3 -->
285
            </div> <!-- / #installer-step3 -->
274
        </div> <!-- / .row -->
286
        </div> <!-- / .row -->
275
- 

Return to bug 25078