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

(-)a/C4/Installer.pm (-2 / +233 lines)
Lines 19-28 package C4::Installer; Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Encode;
22
use Try::Tiny;
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 marc_framework_sql_list);
35
    push @EXPORT, qw( primary_key_exists foreign_key_exists index_exists column_exists TableExists marc_framework_sql_list 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 24499-24651 foreach my $file ( sort readdir $dirh ) { Link Here
24499
    }
24509
    }
24500
}
24510
}
24501
24511
24502
=head1 FUNCTIONS
24503
24504
=head2 DropAllForeignKeys($table)
24505
24506
Drop all foreign keys of the table $table
24507
24508
=cut
24509
24510
sub DropAllForeignKeys {
24511
    my ($table) = @_;
24512
    # get the table description
24513
    my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
24514
    $sth->execute;
24515
    my $vsc_structure = $sth->fetchrow;
24516
    # split on CONSTRAINT keyword
24517
    my @fks = split /CONSTRAINT /,$vsc_structure;
24518
    # parse each entry
24519
    foreach (@fks) {
24520
        # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
24521
        $_ = /(.*) FOREIGN KEY.*/;
24522
        my $id = $1;
24523
        if ($id) {
24524
            # we have found 1 foreign, drop it
24525
            $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
24526
            $id="";
24527
        }
24528
    }
24529
}
24530
24531
24532
=head2 TransformToNum
24533
24534
Transform the Koha version from a 4 parts string
24535
to a number, with just 1 .
24536
24537
=cut
24538
24539
sub TransformToNum {
24540
    my $version = shift;
24541
    # remove the 3 last . to have a Perl number
24542
    $version =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
24543
    # three X's at the end indicate that you are testing patch with dbrev
24544
    # change it into 999
24545
    # prevents error on a < comparison between strings (should be: lt)
24546
    $version =~ s/XXX$/999/;
24547
    return $version;
24548
}
24549
24550
=head2 SetVersion
24551
24552
set the DBversion in the systempreferences
24553
24554
=cut
24555
24556
sub SetVersion {
24557
    return if $_[0]=~ /XXX$/;
24558
      #you are testing a patch with a db revision; do not change version
24559
    my $kohaversion = TransformToNum($_[0]);
24560
    if (C4::Context->preference('Version')) {
24561
      my $finish=$dbh->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
24562
      $finish->execute($kohaversion);
24563
    } else {
24564
      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')");
24565
      $finish->execute($kohaversion);
24566
    }
24567
    C4::Context::clear_syspref_cache(); # invalidate cached preferences
24568
}
24569
24570
sub NewVersion {
24571
    my ( $DBversion, $bug_number, $descriptions ) = @_;
24572
24573
    SetVersion($DBversion);
24574
24575
    unless ( ref($descriptions) ) {
24576
        $descriptions = [ $descriptions ];
24577
    }
24578
    my $first = 1;
24579
    my $time = POSIX::strftime("%H:%M:%S",localtime);
24580
    for my $description ( @$descriptions ) {
24581
        if ( @$descriptions > 1 ) {
24582
            if ( $first ) {
24583
                unless ( $bug_number ) {
24584
                    say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
24585
                } else {
24586
                    say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
24587
                }
24588
            } else {
24589
                say sprintf "\t\t\t\t\t\t   - %s", $description;
24590
            }
24591
        } else {
24592
            unless ( $bug_number ) {
24593
                say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
24594
            } else {
24595
                say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
24596
            }
24597
        }
24598
        $first = 0;
24599
    }
24600
}
24601
24602
=head2 CheckVersion
24603
24604
Check whether a given update should be run when passed the proposed version
24605
number. The update will always be run if the proposed version is greater
24606
than the current database version and less than or equal to the version in
24607
kohaversion.pl. The update is also run if the version contains XXX, though
24608
this behavior will be changed following the adoption of non-linear updates
24609
as implemented in bug 7167.
24610
24611
=cut
24612
24613
sub CheckVersion {
24614
    my ($proposed_version) = @_;
24615
    my $version_number = TransformToNum($proposed_version);
24616
24617
    # The following line should be deleted when bug 7167 is pushed
24618
    return 1 if ( $proposed_version =~ m/XXX/ );
24619
24620
    if ( C4::Context->preference("Version") < $version_number
24621
        && $version_number <= TransformToNum( $Koha::VERSION ) )
24622
    {
24623
        return 1;
24624
    }
24625
    else {
24626
        return 0;
24627
    }
24628
}
24629
24630
sub sanitize_zero_date {
24631
    my ( $table_name, $column_name ) = @_;
24632
24633
    my (undef, $datatype) = $dbh->selectrow_array(qq|
24634
        SHOW COLUMNS FROM $table_name WHERE Field = ?|, undef, $column_name);
24635
24636
    if ( $datatype eq 'date' ) {
24637
        $dbh->do(qq|
24638
            UPDATE $table_name
24639
            SET $column_name = NULL
24640
            WHERE CAST($column_name AS CHAR(10)) = '0000-00-00';
24641
        |);
24642
    } else {
24643
        $dbh->do(qq|
24644
            UPDATE $table_name
24645
            SET $column_name = NULL
24646
            WHERE CAST($column_name AS CHAR(19)) = '0000-00-00 00:00:00';
24647
        |);
24648
    }
24649
}
24650
24651
exit;
24512
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