@@ -, +, @@ you arrive at the "Update database" step Pending migrations: 20201002135449-create-executed-migrations-table.pl Click on "Update your database" Update report : Migration done: 20201002135449-create-executed-migrations-table.pl (Create table executed_migrations) Everything went okay. Update done. correct version of the manual exists and is not empty at the end of the install process --- C4/Auth.pm | 41 ++++------------- C4/Context.pm | 15 ++++++- C4/Installer.pm | 28 ------------ Koha.pm | 13 +++--- Koha/Manual.pm | 3 +- Koha/Migrations.pm | 44 +++++++++++++++++++ Koha/SharedContent.pm | 5 +-- Koha/Template/Plugin/Asset.pm | 2 +- Koha/Template/Plugin/Koha.pm | 3 +- installer/data/mysql/kohastructure.sql | 11 +++++ ...135449-create-executed-migrations-table.pl | 15 +++++++ installer/data/mysql/updatedatabase.pl | 27 ++++++++++++ installer/install.pl | 37 +++++----------- installer/onboarding.pl | 2 +- .../prog/en/modules/installer/step3.tt | 9 +++- opac/maintenance.pl | 12 ++--- plugins/plugins-home.pl | 6 ++- t/Context.t | 11 ----- t/Koha_Template_Plugin_Koha.t | 8 +--- .../Koha/REST/Plugin/PluginRoutes.t | 6 +-- 20 files changed, 164 insertions(+), 134 deletions(-) create mode 100644 Koha/Migrations.pm create mode 100644 installer/data/mysql/migrations/20201002135449-create-executed-migrations-table.pl --- a/C4/Auth.pm +++ a/C4/Auth.pm @@ -31,6 +31,7 @@ use C4::Context; use C4::Templates; # to get the template use C4::Languages; use C4::Search::History; +use C4::Installer; use Koha; use Koha::Logger; use Koha::Caches; @@ -51,6 +52,7 @@ use Net::CIDR; use C4::Log qw( logaction ); use Koha::CookieManager; use Koha::Auth::Permissions; +use Koha::Migrations; # use utf8; @@ -574,7 +576,6 @@ sub get_template_and_user { OPACPrivacy => C4::Context->preference("OPACPrivacy"), OPACFinesTab => C4::Context->preference("OPACFinesTab"), OpacTopissue => C4::Context->preference("OpacTopissue"), - 'Version' => C4::Context->preference('Version'), hidelostitems => C4::Context->preference("hidelostitems"), mylibraryfirst => ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv ) ? C4::Context->userenv->{'branch'} : '', opacbookbag => "" . C4::Context->preference("opacbookbag"), @@ -704,7 +705,6 @@ has authenticated. sub _version_check { my $type = shift; my $query = shift; - my $version; # If version syspref is unavailable, it means Koha is being installed, # and so we must redirect to OPAC maintenance page or to the WebInstaller @@ -714,7 +714,7 @@ sub _version_check { print $query->redirect("/cgi-bin/koha/maintenance.pl"); safe_exit; } - unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison + unless ( C4::Installer::TableExists('systempreferences') ) { if ( $type ne 'opac' ) { warn "Install required, redirecting to Installer"; print $query->redirect("/cgi-bin/koha/installer/install.pl"); @@ -725,17 +725,8 @@ sub _version_check { safe_exit; } - # check that database and koha version are the same - # there is no DB version, it's a fresh install, - # go to web installer - # there is a DB version, compare it to the code version - my $kohaversion = Koha::version(); - - # remove the 3 last . to have a Perl number - $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - Koha::Logger->get->debug("kohaversion : $kohaversion"); - if ( $version < $kohaversion ) { - my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion"; + if (Koha::Migrations->pending_migrations > 0) { + my $warning = "Database update needed, redirecting to %s"; if ( $type ne 'opac' ) { warn sprintf( $warning, 'Installer' ); print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure"); @@ -1519,15 +1510,7 @@ sub check_api_auth { my $flagsrequired = shift; my $timeout = _timeout_syspref(); - unless ( C4::Context->preference('Version') ) { - - # database has not been installed yet - return ( "maintenance", undef, undef ); - } - my $kohaversion = Koha::version(); - $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - if ( C4::Context->preference('Version') < $kohaversion ) { - + if ( Koha::Migrations->pending_migrations > 0 ) { # database in need of version update; assume that # no API should be called while databsae is in # this condition. @@ -1728,17 +1711,9 @@ sub check_cookie_auth { my $skip_version_check = $params->{skip_version_check}; # Only for checkauth unless ( $skip_version_check ) { - unless ( C4::Context->preference('Version') ) { - - # database has not been installed yet - return ( "maintenance", undef ); - } - my $kohaversion = Koha::version(); - $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - if ( C4::Context->preference('Version') < $kohaversion ) { - + if ( Koha::Migrations->pending_migrations > 0 ) { # database in need of version update; assume that - # no API should be called while databsae is in + # no API should be called while database is in # this condition. return ( "maintenance", undef ); } --- a/C4/Context.pm +++ a/C4/Context.pm @@ -314,6 +314,12 @@ sub preference { $var = lc $var; + if ($var eq 'version') { + my $version = Koha::version(); + $version =~ s/^(\d+)\.(\d+)\.(\d+)$/$1.$2$3/; + return $version; + } + if ($use_syspref_cache) { my $syspref_cache = Koha::Caches->get_instance('syspref'); my $cached_var = $syspref_cache->get_from_cache("syspref_$var"); @@ -856,7 +862,6 @@ Gets various version info, for core Koha packages, Currently called from carp ha sub get_versions { my ( %versions, $mysqlVersion ); $versions{kohaVersion} = Koha::version(); - $versions{kohaDbVersion} = C4::Context->preference('version'); $versions{osVersion} = join(" ", POSIX::uname()); $versions{perlVersion} = $]; @@ -1037,7 +1042,13 @@ This method returns a boolean representing the install status of the Koha instan sub needs_install { my ($self) = @_; - return ($self->preference('Version')) ? 0 : 1; + eval { + my $dbh = $self->dbh; + local $dbh->{PrintError} = 0; + local $dbh->{RaiseError} = 1; + $dbh->do('SELECT * FROM systempreferences WHERE 1 = 0'); + }; + return $@ ? 1 : 0; } =head3 psgi_env --- a/C4/Installer.pm +++ a/C4/Installer.pm @@ -50,7 +50,6 @@ C4::Installer 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'); =head1 DESCRIPTION @@ -448,33 +447,6 @@ sub set_marcflavour_syspref { $request->execute; } -=head2 set_version_syspref - - $installer->set_version_syspref(); - -Set or update the 'Version' system preference to the current -Koha software version. - -=cut - -sub set_version_syspref { - my $self = shift; - - my $kohaversion = Koha::version(); - # remove the 3 last . to have a Perl number - $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - if (C4::Context->preference('Version')) { - warn "UPDATE Version"; - my $finish=$self->{'dbh'}->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'"); - $finish->execute($kohaversion); - } else { - warn "INSERT Version"; - 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')"); - $finish->execute($kohaversion); - } - C4::Context->clear_syspref_cache(); -} - =head2 set_languages_syspref $installer->set_languages_syspref(); --- a/Koha.pm +++ a/Koha.pm @@ -22,14 +22,11 @@ use Modern::Perl; use vars qw{ $VERSION }; -#the kohaversion is divided in 4 parts : -# - #1 : the major number. 3 atm -# - #2 : the functional release. 00 atm -# - #3 : the subnumber, moves only on a public release -# - #4 : the developer version. The 4th number is the database subversion. -# used by developers when the database changes. updatedatabase take care of the changes itself -# and is automatically called by Auth.pm when needed. -$VERSION = "23.06.00.007"; +#the kohaversion is divided in 3 parts : +# - #1 : the year of release +# - #2 : the month of release (05 or 11 for stable releases, 06 or 12 for development branch) +# - #3 : the maintenance release number +$VERSION = "23.12.00"; sub version { return $VERSION; --- a/Koha/Manual.pm +++ a/Koha/Manual.pm @@ -3,9 +3,10 @@ package Koha::Manual; use Modern::Perl; use C4::Context; +use Koha; sub _get_help_version { - my $help_version = C4::Context->preference("Version"); + my $help_version = Koha::version(); if ( $help_version =~ m|^(\d+)\.(\d{2}).*$| ) { my $version = $1; my $major = $2; --- a/Koha/Migrations.pm +++ a/Koha/Migrations.pm @@ -0,0 +1,44 @@ +package Koha::Migrations; + +use Modern::Perl; + +use C4::Context; + +sub pending_migrations { + my ($self) = @_; + + my $dbh = C4::Context->dbh; + + my @pending_migrations; + my $executed_migrations = {}; + eval { + $executed_migrations = $dbh->selectall_hashref('SELECT * FROM executed_migrations', 'name'); + }; + + opendir(my $dh, $self->migrations_dir); + foreach my $file (sort readdir $dh) { + next if $file !~ /\.pl$/; + next if (exists ($executed_migrations->{$file})); + push @pending_migrations, $file; + } + closedir($dh); + + return @pending_migrations; +} + +# Only needed for installer +sub mark_all_migrations_as_executed { + my ($self) = @_; + + my $dbh = C4::Context->dbh; + + foreach my $migration ($self->pending_migrations) { + $dbh->do('INSERT INTO executed_migrations (name) VALUES (?)', {}, $migration); + } +} + +sub migrations_dir { + return C4::Context->config('intranetdir') . '/installer/data/mysql/migrations'; +} + +1; --- a/Koha/SharedContent.pm +++ a/Koha/SharedContent.pm @@ -25,6 +25,7 @@ use LWP::UserAgent; use Koha::Serials; use Koha::Reports; use C4::Context; +use Koha; =head1 NAME @@ -144,11 +145,9 @@ sub prepare_entity_data { $mana_email = C4::Context->preference('KohaAdminEmailAddress') if ( ( not defined($mana_email) ) or ( $mana_email eq '' ) ); - my %versions = C4::Context::get_versions(); - my $mana_info = { language => $lang, - kohaversion => $versions{'kohaVersion'}, + kohaversion => Koha::version(), exportemail => $mana_email }; --- a/Koha/Template/Plugin/Asset.pm +++ a/Koha/Template/Plugin/Asset.pm @@ -142,7 +142,7 @@ sub url { ); my $version = Koha::version; - $version =~ s/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/$1.$2$3$4/; + $version =~ s/([0-9]+)\.([0-9]+)\.([0-9]+)/$1.$2${3}000/; foreach my $dir (@dirs) { my $abspath = File::Spec->catfile($root, $dir, $filename); if (-e $abspath) { --- a/Koha/Template/Plugin/Koha.pm +++ a/Koha/Template/Plugin/Koha.pm @@ -73,14 +73,13 @@ sub CSVDelimiter { sub Version { my $version_string = Koha::version(); - my ( $major, $minor, $maintenance, $development ) = split( '\.', $version_string ); + my ( $major, $minor, $maintenance ) = split( '\.', $version_string ); return { major => $major, minor => $minor, release => $major . "." . $minor, maintenance => $major . "." . $minor . "." . $maintenance, - development => ( $development ne '000' ) ? $development : undef, }; } --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -3067,6 +3067,17 @@ CREATE TABLE `erm_user_roles` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Structure for table executed_migrations +-- + +DROP TABLE IF EXISTS `executed_migrations`; +CREATE TABLE `executed_migrations` ( + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `executed_at` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + -- -- Table structure for table `export_format` -- --- a/installer/data/mysql/migrations/20201002135449-create-executed-migrations-table.pl +++ a/installer/data/mysql/migrations/20201002135449-create-executed-migrations-table.pl @@ -0,0 +1,15 @@ +use Modern::Perl; + +{ + description => 'Create table executed_migrations', + up => sub { + my ($dbh) = @_; + $dbh->do(q{ + CREATE TABLE IF NOT EXISTS `executed_migrations` ( + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `executed_at` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`name`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + }); + }, +} --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -42,6 +42,7 @@ use Koha::Database; use Koha; use Koha::DateUtils qw( dt_from_string output_pref ); use Koha::Caches; +use Koha::Migrations; use MARC::Record; use MARC::File::XML ( BinaryEncoding => 'utf8' ); @@ -24316,4 +24317,30 @@ unless ( $ENV{HTTP_HOST} ) { # Is that correct? } +my $insert_migration_sth = $dbh->prepare('INSERT INTO executed_migrations (name) VALUES (?)'); + +my $migrations_dir = Koha::Migrations->migrations_dir; +foreach my $file (Koha::Migrations->pending_migrations) { + my $migration = do $migrations_dir . '/' . $file; + unless ($migration) { + if ($@) { + warn "Could not compile $file: $@"; + } elsif ($!) { + warn "Could not do $file: $!"; + } else { + warn "Could not run $file"; + } + next; + } + + eval { + $migration->{up}->($dbh); + $insert_migration_sth->execute($file); + say sprintf('Migration done: %s (%s)', $file, $migration->{description}); + }; + if ($@) { + die sprintf('Migration failed %s (%s): %s', $file, $migration->{description}, $@); + } +} + exit; --- a/installer/install.pl +++ a/installer/install.pl @@ -33,6 +33,7 @@ use C4::Installer::PerlModules; use Koha; use Koha::Caches; +use Koha::Migrations; my $query = CGI->new; my $step = $query->param('step'); @@ -238,7 +239,7 @@ elsif ( $step && $step == 3 ) { exit; } elsif ( $op && $op eq 'finish' ) { - $installer->set_version_syspref(); + Koha::Migrations->mark_all_migrations_as_executed(); my $langchoice = $query->param('fwklanguage'); $langchoice = $query->cookie('KohaOpacLanguage') unless ($langchoice); @@ -395,11 +396,10 @@ elsif ( $step && $step == 3 ) { my $now = POSIX::strftime( "%Y-%m-%dT%H:%M:%S", localtime() ); my $logdir = C4::Context->config('logdir'); - my $dbversion = C4::Context->preference('Version'); my $kohaversion = Koha::version; $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - my $filename_suffix = join '_', $now, $dbversion, $kohaversion; + my $filename_suffix = join '_', $now, $kohaversion; my ( $logfilepath, $logfilepath_errors ) = ( chk_log( $logdir, "updatedatabase_$filename_suffix" ), chk_log( $logdir, "updatedatabase-error_$filename_suffix" ) @@ -497,19 +497,14 @@ elsif ( $step && $step == 3 ) { # we have tables, propose to select files to upload or updatedatabase # $template->param( "count" => $count, "default" => 1 ); - # - # 1st part of step 3 : check if there is a databaseversion systempreference - # if there is, then we just need to upgrade - # if there is none, then we need to install the database - # - if ( C4::Context->preference('Version') ) { - my $dbversion = C4::Context->preference('Version'); - $dbversion =~ /(.*)\.(..)(..)(...)/; - $dbversion = "$1.$2.$3.$4"; + + # 1st part of step 3 : check if we need to install the database + # if not, then we just need to upgrade + unless ( C4::Context->needs_install() ) { $template->param( "upgrading" => 1, - "dbversion" => $dbversion, "kohaversion" => Koha::version(), + "pending_migrations" => [ Koha::Migrations->pending_migrations ], ); } } @@ -521,18 +516,10 @@ else { # using opendir + language Hash my $languages_loop = getTranslatedLanguages('intranet'); $template->param( installer_languages_loop => $languages_loop ); - if ($dbh) { - my $rq = - $dbh->prepare( - "SELECT * from systempreferences WHERE variable='Version'"); - if ( $rq->execute ) { - my ($version) = $rq->fetchrow; - if ($version) { - print $query->redirect( - "/cgi-bin/koha/installer/install.pl?step=3"); - exit; - } - } + if (!C4::Context->needs_install) { + print $query->redirect( + "/cgi-bin/koha/installer/install.pl?step=3"); + exit; } } output_html_with_http_headers $query, $cookie, $template->output; --- a/installer/onboarding.pl +++ a/installer/onboarding.pl @@ -34,7 +34,7 @@ use Koha::CirculationRules; #Setting variables my $input = CGI->new; -unless ( C4::Context->preference('Version') ) { +if ( C4::Context->needs_install ) { print $input->redirect("/cgi-bin/koha/installer/install.pl"); exit; } --- a/koha-tmpl/intranet-tmpl/prog/en/modules/installer/step3.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/installer/step3.tt @@ -314,7 +314,14 @@ [% IF ( default ) %] [% IF ( upgrading ) %]

Web installer › Update database

-

We are upgrading from Koha [% dbversion | html %] to [% kohaversion | html %]

+

+ Pending migrations: +

+

Update your database

[% ELSE %]

Web installer › Install basic configuration settings

--- a/opac/maintenance.pl +++ a/opac/maintenance.pl @@ -19,22 +19,18 @@ use Modern::Perl; use CGI qw ( -utf8 ); use C4::Auth; +use C4::Context; use C4::Output qw( output_html_with_http_headers ); use C4::Templates qw/gettemplate/; use Koha; +use Koha::Migrations; my $query = CGI->new; my $template = C4::Templates::gettemplate( 'maintenance.tt', 'opac', $query ); -my $koha_db_version = C4::Context->preference('Version'); -my $kohaversion = Koha::version(); -# Strip dots from version -$kohaversion =~ s/\.//g if defined $kohaversion; -$koha_db_version =~ s/\.//g if defined $koha_db_version; - -if ( !defined $koha_db_version || # DB not populated - $kohaversion > $koha_db_version || # Update needed +if ( C4::Context->needs_install() || # DB not populated + Koha::Migrations->pending_migrations > 0 || # Update needed C4::Context->preference('OpacMaintenance') ) { # Maintenance mode enabled output_html_with_http_headers $query, '', $template->output; } --- a/plugins/plugins-home.pl +++ a/plugins/plugins-home.pl @@ -45,8 +45,12 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( if ($plugins_enabled) { + my $koha_version = Koha::version(); + if ($koha_version =~ /^(\d+)\.(\d+)\.(\d+)/) { + $koha_version = "$1.$2$3"; + } $template->param( - koha_version => C4::Context->preference("Version"), + koha_version => $koha_version, method => $method, ); --- a/t/Context.t +++ a/t/Context.t @@ -58,17 +58,6 @@ subtest 'yaml_preference() tests' => sub { $context->unmock( 'preference' ); }; -subtest 'needs_install() tests' => sub { - - plan tests => 2; - - t::lib::Mocks::mock_preference( 'Version', '3.0.0' ); - is( C4::Context->needs_install, 0, 'Preference is defined, no need to install' ); - - t::lib::Mocks::mock_preference( 'Version', undef ); # the behaviour when ->preference fails to fetch - is( C4::Context->needs_install, 1, "->preference(Version) is not defined, need to install" ); -}; - subtest 'csv_delimiter() tests' => sub { plan tests => 4; --- a/t/Koha_Template_Plugin_Koha.t +++ a/t/Koha_Template_Plugin_Koha.t @@ -48,15 +48,13 @@ subtest "Koha::Template::Plugin::Koha::Version tests" => sub { $major = $rnd->randregex('\d'); $minor = $rnd->randregex('\d\d'); $maintenance = $rnd->randregex('\d\d'); - $development = $rnd->randregex('\d\d\d'); - my $version = "$major.$minor.$maintenance.$development"; + my $version = "$major.$minor.$maintenance"; my $res = Koha::Template::Plugin::Koha::Version( $version ); is_deeply( $res, { major => $major, minor => $minor, release => $major . "." . $minor, maintenance => $major . "." . $minor . "." . $maintenance, - development => $development }, "Correct development version"); @@ -64,15 +62,13 @@ subtest "Koha::Template::Plugin::Koha::Version tests" => sub { $major = $rnd->randregex('\d'); $minor = $rnd->randregex('\d\d'); $maintenance = $rnd->randregex('\d\d'); - $development = "000"; - $version = "$major.$minor.$maintenance.$development"; + $version = "$major.$minor.$maintenance"; $res = Koha::Template::Plugin::Koha::Version( $version ); is_deeply( $res, { major => $major, minor => $minor, release => $major . "." . $minor, maintenance => $major . "." . $minor . "." . $maintenance, - development => undef }, "Correct maintenance version"); }; --- a/t/db_dependent/Koha/REST/Plugin/PluginRoutes.t +++ a/t/db_dependent/Koha/REST/Plugin/PluginRoutes.t @@ -218,8 +218,8 @@ subtest 'needs_install use case tests' => sub { my $plugins = Koha::Plugins->new; $plugins->InstallPlugins; - # mock Version before initializing the API class - t::lib::Mocks::mock_preference('Version', undef); + my $c4_context = Test::MockModule->new('C4::Context'); + $c4_context->mock('needs_install', sub { 1 }); # initialize Koha::REST::V1 after mocking my $t = Test::Mojo->new('Koha::REST::V1'); @@ -232,7 +232,7 @@ subtest 'needs_install use case tests' => sub { 'Plugin enabled, route not defined as C4::Context->needs_install is true' ); - t::lib::Mocks::mock_preference('Version', '3.0.0'); + $c4_context->mock('needs_install', sub { 0 }); $schema->resultset('PluginData')->delete; $plugins->InstallPlugins; --