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

(-)a/C4/Auth.pm (-71 / +16 lines)
Lines 547-596 has authenticated. Link Here
547
547
548
=cut
548
=cut
549
549
550
sub _version_check {
551
    my $type = shift;
552
    my $query = shift;
553
    my $version;
554
    # If Version syspref is unavailable, it means Koha is beeing installed,
555
    # and so we must redirect to OPAC maintenance page or to the WebInstaller
556
	# also, if OpacMaintenance is ON, OPAC should redirect to maintenance
557
	if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
558
        warn "OPAC Install required, redirecting to maintenance";
559
        print $query->redirect("/cgi-bin/koha/maintenance.pl");
560
        safe_exit;
561
    }
562
    unless ( $version = C4::Context->preference('Version') ) {    # assignment, not comparison
563
        if ( $type ne 'opac' ) {
564
            warn "Install required, redirecting to Installer";
565
            print $query->redirect("/cgi-bin/koha/installer/install.pl");
566
        } else {
567
            warn "OPAC Install required, redirecting to maintenance";
568
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
569
        }
570
        safe_exit;
571
    }
572
573
    # check that database and koha version are the same
574
    # there is no DB version, it's a fresh install,
575
    # go to web installer
576
    # there is a DB version, compare it to the code version
577
    my $kohaversion=C4::Context::KOHAVERSION;
578
    # remove the 3 last . to have a Perl number
579
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
580
    $debug and print STDERR "kohaversion : $kohaversion\n";
581
    if ($version < $kohaversion){
582
        my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
583
        if ($type ne 'opac'){
584
            warn sprintf($warning, 'Installer');
585
            print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
586
        } else {
587
            warn sprintf("OPAC: " . $warning, 'maintenance');
588
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
589
        }
590
        safe_exit;
591
    }
592
}
593
594
sub _session_log {
550
sub _session_log {
595
    (@_) or return 0;
551
    (@_) or return 0;
596
    open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
552
    open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
Lines 618-625 sub checkauth { Link Here
618
574
619
    my $dbh     = C4::Context->dbh;
575
    my $dbh     = C4::Context->dbh;
620
    my $timeout = _timeout_syspref();
576
    my $timeout = _timeout_syspref();
577
    # days
578
    if ($timeout =~ /(\d+)[dD]/) {
579
        $timeout = $1 * 86400;
580
    };
581
    $timeout = 600 unless $timeout;
582
    # check we have a Version. Otherwise => go to installer
583
    unless ( C4::Context->preference('Version') ) {    # assignment, not comparison
584
        if ( $type ne 'opac' ) {
585
            $debug && warn "Install required, redirecting to Installer";
586
            print $query->redirect("/cgi-bin/koha/installer/install.pl");
587
        } else {
588
            $debug && warn "OPAC Install required, redirecting to maintenance";
589
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
590
        }
591
        safe_exit;
592
    }
621
593
622
    _version_check($type,$query);
623
    # state variables
594
    # state variables
624
    my $loggedin = 0;
595
    my $loggedin = 0;
625
    my %info;
596
    my %info;
Lines 1066-1084 sub check_api_auth { Link Here
1066
    my $dbh     = C4::Context->dbh;
1037
    my $dbh     = C4::Context->dbh;
1067
    my $timeout = _timeout_syspref();
1038
    my $timeout = _timeout_syspref();
1068
1039
1069
    unless (C4::Context->preference('Version')) {
1070
        # database has not been installed yet
1071
        return ("maintenance", undef, undef);
1072
    }
1073
    my $kohaversion=C4::Context::KOHAVERSION;
1074
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1075
    if (C4::Context->preference('Version') < $kohaversion) {
1076
        # database in need of version update; assume that
1077
        # no API should be called while databsae is in
1078
        # this condition.
1079
        return ("maintenance", undef, undef);
1080
    }
1081
1082
    # FIXME -- most of what follows is a copy-and-paste
1040
    # FIXME -- most of what follows is a copy-and-paste
1083
    # of code from checkauth.  There is an obvious need
1041
    # of code from checkauth.  There is an obvious need
1084
    # for refactoring to separate the various parts of
1042
    # for refactoring to separate the various parts of
Lines 1298-1316 sub check_cookie_auth { Link Here
1298
    my $dbh     = C4::Context->dbh;
1256
    my $dbh     = C4::Context->dbh;
1299
    my $timeout = _timeout_syspref();
1257
    my $timeout = _timeout_syspref();
1300
1258
1301
    unless (C4::Context->preference('Version')) {
1302
        # database has not been installed yet
1303
        return ("maintenance", undef);
1304
    }
1305
    my $kohaversion=C4::Context::KOHAVERSION;
1306
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1307
    if (C4::Context->preference('Version') < $kohaversion) {
1308
        # database in need of version update; assume that
1309
        # no API should be called while databsae is in
1310
        # this condition.
1311
        return ("maintenance", undef);
1312
    }
1313
1314
    # FIXME -- most of what follows is a copy-and-paste
1259
    # FIXME -- most of what follows is a copy-and-paste
1315
    # of code from checkauth.  There is an obvious need
1260
    # of code from checkauth.  There is an obvious need
1316
    # for refactoring to separate the various parts of
1261
    # for refactoring to separate the various parts of
(-)a/C4/Installer.pm (-1 / +10 lines)
Lines 23-28 use strict; Link Here
23
our $VERSION = 3.07.00.049;
23
our $VERSION = 3.07.00.049;
24
use C4::Context;
24
use C4::Context;
25
use C4::Installer::PerlModules;
25
use C4::Installer::PerlModules;
26
use C4::Update::Database;
26
27
27
=head1 NAME
28
=head1 NAME
28
29
Lines 466-472 Koha software version. Link Here
466
467
467
sub set_version_syspref {
468
sub set_version_syspref {
468
    my $self = shift;
469
    my $self = shift;
469
470
    # get all updatedatabase, and mark them as passed, as it's a fresh install
471
    my $versions = C4::Update::Database::list_versions_availables();
472
    for my $v ( @$versions ) {
473
        my $queries;
474
        $queries->{queries} = ["initial setup"];
475
        $queries->{comments} = ["initial setup"];
476
        C4::Update::Database::set_infos($v,$queries,undef,undef);
477
    }
478
    # mark the "old" 3.6 version number
470
    my $kohaversion=C4::Context::KOHAVERSION;
479
    my $kohaversion=C4::Context::KOHAVERSION;
471
    # remove the 3 last . to have a Perl number
480
    # remove the 3 last . to have a Perl number
472
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
481
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
(-)a/C4/Update/Database.pm (+545 lines)
Line 0 Link Here
1
package C4::Update::Database;
2
3
# Copyright 2011 BibLibre
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use C4::Context;
23
24
use File::Basename;
25
use File::Find::Rule;
26
use Digest::MD5;
27
use List::MoreUtils qw/uniq/;
28
use YAML;
29
30
=head1 NAME
31
32
C4::Update::Database.pm
33
34
=head1 SYNOPSIS
35
36
  use C4::Update::Database;
37
38
  This package is used by admin/updatedatabase.pl, to manage DB updates
39
40
=head1 FUNCTIONS
41
42
=cut
43
44
my $VERSIONS_PATH = C4::Context->config('intranetdir') . '/installer/data/mysql/versions';
45
46
my $version;
47
my $list;
48
49
my $dbh = C4::Context->dbh;
50
51
=head2
52
53
  my $file = get_filepath($version);
54
  this sub will return the full path of a given DB update number
55
56
=cut
57
58
sub get_filepath {
59
    my ( $version ) = @_;
60
    my @files = File::Find::Rule->file->name( "$version.sql", "$version.pl" ) ->in( ( $VERSIONS_PATH ) );
61
62
    if ( scalar @files != 1 ) {
63
        die "This version ($version) returned has ".scalar @files." corresponding, need only 1";
64
    }
65
66
    return $files[0];
67
}
68
69
=head2 get_md5
70
71
  my $md5 = get_md5($filepath)
72
  returns the md5sum of the selected file.
73
  This is used to check consistency of updates
74
75
=cut
76
sub get_md5 {
77
    my ( $filepath ) = @_;
78
    open(FILE, $filepath);
79
80
    my $ctx = Digest::MD5->new;
81
    $ctx->addfile(*FILE);
82
    my $md5 = $ctx->hexdigest;
83
    close(FILE);
84
    return $md5;
85
}
86
87
=head2 execute_version
88
89
  $result = execute_version($version_number);
90
  Execute an update.
91
  This sub will detect if the number is made through a .pl or a .sql, and behave accordingly
92
  if there is more than 1 file with the same number, an error will be issued
93
  if you try to execute a version_number that has already be executed, then it will also issue an error
94
  the sub return an result hash, with the version number and the result
95
96
=cut
97
98
sub execute_version {
99
    my ( $version ) = @_;
100
    my $report;
101
102
    my $filepath;
103
    eval {
104
        $filepath = get_filepath $version;
105
    };
106
    if ( $@ ) {
107
        return { $version => $@ };
108
    }
109
110
    my @file_infos = fileparse( $filepath, qr/\.[^.]*/ );
111
    my $extension = $file_infos[2];
112
    my $filename = $version . $extension;
113
114
    my $md5 = get_md5 $filepath;
115
    my $r = md5_already_exists( $md5 );
116
    if ( scalar @$r ) {
117
        my $p = @$r[0];
118
        $report->{$version} = {
119
            error => "ALREADY_EXISTS",
120
            filepath => $filepath,
121
            old_version => @$r[0]->{version},
122
            md5 => @$r[0]->{md5},
123
        };
124
        return $report;
125
    }
126
127
    my $queries;
128
    given ( $extension ) {
129
        when ( /.sql/ ) {
130
            $queries = get_queries ( $filepath );
131
        }
132
        when ( /.pl/ ) {
133
            my $versions_dir = C4::Context->intranetdir . '/installer/data/mysql/versions/';
134
            my $version_file = $versions_dir . $filename;
135
            if ( do $version_file ) {
136
                $queries = _get_queries();
137
            } else {
138
                $report->{$version} = {
139
                    error => "LOAD_FUNCTIONS_FAILED",
140
                    filename => $filename,
141
                };
142
            }
143
        }
144
        default {
145
            $report->{$version} = {
146
                error => "BAD_EXTENSION",
147
                extension => $extension,
148
            };
149
        }
150
    }
151
152
    return $report
153
        if ( defined $report->{$version} );
154
155
    my $errors;
156
    for my $query ( @{$queries->{queries}} ) {
157
        eval {
158
            check_coherency( $query );
159
        };
160
        if ( $@ ) {
161
            push @$errors, $@
162
        }
163
    }
164
165
    if ( $errors ) {
166
        $_ =~ s/at [^ ]* line \d*\.$// for @$errors;
167
        $report->{$version} = $errors;
168
        return $report;
169
    }
170
171
    $errors = execute ( $queries );
172
    $report->{$version} = scalar( @$errors ) ? $errors : "OK";
173
    set_infos ( $version, $queries, $errors, $md5 );
174
    return $report;
175
}
176
177
=head2 list_versions_availables
178
179
  my @versions = list_versions_availables;
180
  return an array with all version available
181
  This list is retrieved from the directory defined in the etc/update/database/config.yaml, versions_dir parameter
182
183
=cut
184
185
sub list_versions_availables {
186
    my @versions;
187
188
    my @files = File::Find::Rule->file->name( "*.sql", "*.pl" ) ->in( ( $VERSIONS_PATH ) );
189
190
    for my $f ( @files ) {
191
        my @file_infos = fileparse( $f, qr/\.[^.]*/ );
192
        push @versions, $file_infos[0];
193
    }
194
    @versions = uniq @versions;
195
    return \@versions;
196
}
197
198
=head2 list_versions_already_knows
199
200
  my @versions = list_versions_availables;
201
  return an array with all version that have already been applied
202
  This sub check first that the updatedb tables exist and create them if needed
203
204
=cut
205
206
sub list_versions_already_knows {
207
    # 1st check if tables exist, otherwise create them
208
        $dbh->do(qq{
209
                CREATE TABLE IF NOT EXISTS `updatedb_error` ( `version` varchar(32) DEFAULT NULL, `error` text ) ENGINE=InnoDB CHARSET=utf8;
210
        });
211
            $dbh->do(qq{
212
            CREATE TABLE  IF NOT EXISTS `updatedb_query` ( `version` varchar(32) DEFAULT NULL, `query` text ) ENGINE=InnoDB CHARSET=utf8;
213
        });
214
        $dbh->do(qq{
215
            CREATE TABLE  IF NOT EXISTS `updatedb_report` ( `version` text, `md5` varchar(50) DEFAULT NULL, `comment` text, `status` int(1) DEFAULT NULL ) ENGINE=InnoDB CHARSET=utf8;
216
        });
217
218
    my $query = qq/ SELECT version, comment, status FROM updatedb_report ORDER BY version/;
219
    my $sth = $dbh->prepare( $query );
220
    $sth->execute;
221
    my $versions = $sth->fetchall_arrayref( {} );
222
    map {
223
        my $version = $_;
224
        my @comments = defined $_->{comment} ? split '\\\n', $_->{comment} : "";
225
        push @{ $version->{comments} }, { comment => $_ } for @comments;
226
        delete $version->{comment};
227
    } @$versions;
228
    $sth->finish;
229
    for my $version ( @$versions ) {
230
        $query = qq/ SELECT query FROM updatedb_query WHERE version = ? ORDER BY version/;
231
        $sth = $dbh->prepare( $query );
232
        $sth->execute( $version->{version} );
233
        $version->{queries} = $sth->fetchall_arrayref( {} );
234
        $sth->finish;
235
        $query = qq/ SELECT error FROM updatedb_error WHERE version = ? ORDER BY version/;
236
        $sth = $dbh->prepare( $query );
237
        $sth->execute( $version->{version} );
238
        $version->{errors} = $sth->fetchall_arrayref( {} );
239
        $sth->finish;
240
    }
241
    return $versions;
242
}
243
244
=head2 execute
245
246
  my @errors = $execute(\@queries);
247
  This sub will execute queries coming from an execute_version based on a .sql file
248
249
=cut
250
251
sub execute {
252
    my ( $queries ) = @_;
253
    my @errors;
254
    for my $query ( @{$queries->{queries}} ) {
255
        eval {
256
            $dbh->do( $query );
257
        };
258
        push @errors, get_error();
259
    }
260
    return \@errors;
261
}
262
263
=head2 get_tables_name
264
265
  my $tables = get_tables_name;
266
  return an array with all Koha mySQL table names
267
268
=cut
269
270
sub get_tables_name {
271
    my $sth = $dbh->prepare("SHOW TABLES");
272
    $sth->execute();
273
    my @tables;
274
    while ( my ( $table ) = $sth->fetchrow_array ) {
275
        push @tables, $table;
276
    }
277
    return \@tables;
278
}
279
my $tables;
280
281
=head2 check_coherency
282
283
  my $errors = check_coherency($query);
284
  This sub will try to check if a SQL query is useless or no.
285
  for queries that are CREATE TABLE, it will check if the table already exists
286
  for queries that are ALTER TABLE, it will search if the modification has already been made
287
  for queries that are INSERT, it will search if the insert has already been made if it's a syspref or a permission
288
289
  Those test cover 90% of the updatedatabases cases. That will help finding duplicate or inconsistencies
290
291
=cut
292
293
sub check_coherency {
294
    my ( $query ) = @_;
295
    $tables = get_tables_name() if not $tables;
296
297
    given ( $query ) {
298
        when ( /CREATE TABLE(?:.*?)? `?(\w+)`?/ ) {
299
            my $table_name = $1;
300
            if ( grep { /$table_name/ } @$tables ) {
301
                die "COHERENCY: Table $table_name already exists";
302
            }
303
        }
304
305
        when ( /ALTER TABLE *`?(\w+)`? *ADD *(?:COLUMN)? `?(\w+)`?/ ) {
306
            my $table_name = $1;
307
            my $column_name = $2;
308
            next if $column_name =~ /(UNIQUE|CONSTRAINT|INDEX|KEY|FOREIGN)/;
309
            if ( not grep { /$table_name/ } @$tables ) {
310
                return "COHERENCY: Table $table_name does not exist";
311
            } else {
312
                my $sth = $dbh->prepare( "DESC $table_name $column_name" );
313
                my $rv = $sth->execute;
314
                if ( $rv > 0 ) {
315
                    die "COHERENCY: Field $table_name.$column_name already exists";
316
                }
317
            }
318
        }
319
320
        when ( /INSERT INTO `?(\w+)`?.*?VALUES *\((.*?)\)/ ) {
321
            my $table_name = $1;
322
            my @values = split /,/, $2;
323
            s/^ *'// foreach @values;
324
            s/' *$// foreach @values;
325
            given ( $table_name ) {
326
                when ( /systempreferences/ ) {
327
                    my $syspref = $values[0];
328
                    my $sth = $dbh->prepare( "SELECT COUNT(*) FROM systempreferences WHERE variable = ?" );
329
                    $sth->execute( $syspref );
330
                    if ( ( my $count = $sth->fetchrow_array ) > 0 ) {
331
                        die "COHERENCY: Syspref $syspref already exists";
332
                    }
333
                }
334
335
                when ( /permissions/){
336
                    my $module_bit = $values[0];
337
                    my $code = $values[1];
338
                    my $sth = $dbh->prepare( "SELECT COUNT(*) FROM permissions WHERE module_bit = ? AND code = ?" );
339
                    $sth->execute($module_bit, $code);
340
                    if ( ( my $count = $sth->fetchrow_array ) > 0 ) {
341
                        die "COHERENCY: Permission $code already exists";
342
                    }
343
                }
344
            }
345
        }
346
    }
347
    return 1;
348
}
349
350
=head2 get_error
351
352
  my $errors = get_error()
353
  This sub will return any mySQL error that occured during an update
354
355
=cut
356
357
sub get_error {
358
    my @errors = $dbh->selectrow_array(qq{SHOW ERRORS}); # Get errors
359
    my @warnings = $dbh->selectrow_array(qq{SHOW WARNINGS}); # Get warnings
360
    if ( @errors ) { # Catch specifics errors
361
        return qq{$errors[0] : $errors[1] => $errors[2]};
362
    } elsif ( @warnings ) {
363
        return qq{$warnings[0] : $warnings[1] => $warnings[2]}
364
            if $warnings[0] ne 'Note';
365
    }
366
    return;
367
}
368
369
=head2
370
371
  my $result = get_queries($filepath);
372
  this sub will return a hashref with 2 entries:
373
    $result->{queries} is an array with all queries to execute
374
    $result->{comments} is an array with all comments in the .sql file
375
376
=cut
377
378
sub get_queries {
379
    my ( $filepath ) = @_;
380
    open my $fh, "<", $filepath;
381
    my @queries;
382
    my @comments;
383
    my $old_delimiter = $/;
384
    while ( <$fh> ) {
385
        my $line = $_;
386
        chomp $line;
387
        $line =~ s/^\s*//;
388
        if ( $line =~ s/^--(.*)$// ) {
389
            push @comments, $1;
390
            next;
391
        }
392
        if ( $line =~ /^delimiter (.*)$/i ) {
393
            $/ = $1;
394
            next;
395
        }
396
        $line =~ s#$/##;
397
        push @queries, $line if not $line =~ /^\s*$/; # Push if query is not empty
398
    }
399
    $/ = $old_delimiter;
400
    close $fh;
401
402
    return { queries => \@queries, comments => \@comments };
403
}
404
405
=head2 md5_already_exists
406
407
  my $result = md5_already_exists($md5);
408
  check if the md5 of an update has already been applied on the database.
409
  If yes, it will return a hash with the version related to this md5
410
411
=cut
412
413
sub md5_already_exists {
414
    my ( $md5 ) = @_;
415
    my $query = qq/SELECT version, md5 FROM updatedb_report WHERE md5 = ?/;
416
    my $sth = $dbh->prepare( $query );
417
    $sth->execute( $md5 );
418
    my @r;
419
    while ( my ( $version, $md5 ) = $sth->fetchrow ) {
420
        push @r, { version => $version, md5 => $md5 };
421
    }
422
    $sth->finish;
423
    return \@r;
424
}
425
426
=head2 set_infos
427
428
  set_info($version,$queries, $error, $md5);
429
  this sub will insert into the updatedb tables what has been made on the database (queries, errors, result)
430
431
=cut
432
sub set_infos {
433
    my ( $version, $queries, $errors, $md5 ) = @_;
434
    #SetVersion($DBversion) if not -s $errors;
435
    for my $query ( @{ $queries->{queries} } ) {
436
        my $sth = $dbh->prepare("INSERT INTO updatedb_query(version, query) VALUES (?, ?)");
437
        $sth->execute( $version, $query );
438
        $sth->finish;
439
    }
440
    for my $error ( @$errors ) {
441
        my $sth = $dbh->prepare("INSERT INTO updatedb_error(version, error) VALUES (?, ?)");
442
        $sth->execute( $version, $error );
443
    }
444
    my $sth = $dbh->prepare("INSERT INTO updatedb_report(version, md5, comment, status) VALUES (?, ?, ?, ?)");
445
    $sth->execute(
446
        $version,
447
        $md5,
448
        join ('\n', @{ $queries->{comments} }),
449
        ( @$errors > 0 ) ? 0 : 1
450
    );
451
}
452
453
=head2 mark_as_ok
454
455
  mark_as_ok($version);
456
  this sub will force to mark as "OK" an update that has failed
457
  once this has been made, the status will look as "forced OK", and appear in green like versions that have been applied without any problem
458
459
=cut
460
sub mark_as_ok {
461
    my ( $version ) = @_;
462
    my $sth = $dbh->prepare( "UPDATE updatedb_report SET status = 2 WHERE version=?" );
463
    my $affected = $sth->execute( $version );
464
    if ( $affected < 1 ) {
465
        # For "Coherency"
466
        my $filepath = get_filepath $version;
467
        my $queries = get_queries $filepath;
468
        my $errors;
469
        for my $query ( @{$queries->{queries}} ) {
470
            eval {
471
                check_coherency( $query );
472
            };
473
            if ( $@ ) {
474
                push @$errors, $@
475
            }
476
        }
477
478
        $_ =~ s/at [^ ]* line \d*\.$// for @$errors;
479
        my $md5 = get_md5 $filepath;
480
        set_infos $version, $queries, $errors, $md5;
481
482
        $sth->execute( $version );
483
    }
484
    $sth->finish;
485
}
486
487
=head2 is_uptodate
488
  is_uptodate();
489
  return 1 if the database is up to date else 0.
490
  The database is up to date if all versions are excecuted.
491
=cut
492
sub is_uptodate {
493
    my $versions_availables = C4::Update::Database::list_versions_availables;
494
    my $versions = C4::Update::Database::list_versions_already_knows;
495
    for my $v ( @$versions_availables ) {
496
        if ( not grep { $v eq $$_{version} } @$versions ) {
497
            return 0;
498
        }
499
    }
500
    return 1;
501
}
502
503
=head2 TransformToNum
504
505
  Transform the Koha version from a 4 parts string
506
  to a number, with just 1 . (ie: it's a number)
507
508
=cut
509
sub TransformToNum {
510
    my $version = shift;
511
512
    # remove the 3 last . to have a Perl number
513
    $version =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
514
    $version =~ s/Bug(\d+)/$1/;
515
    return $version;
516
}
517
518
sub SetVersion {
519
    my $kohaversion = TransformToNum(shift);
520
    if ( C4::Context->preference('Version') ) {
521
        my $finish = $dbh->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
522
        $finish->execute($kohaversion);
523
    } else {
524
        my $finish = $dbh->prepare(
525
"INSERT IGNORE INTO systempreferences (variable,value,explanation) values ('Version',?,'The Koha database version. WARNING: Do not change this value manually, it is maintained by the webinstaller')"
526
        );
527
        $finish->execute($kohaversion);
528
    }
529
}
530
531
=head2 TableExists($table)
532
533
=cut
534
sub TableExists {
535
    my $table = shift;
536
    eval {
537
                local $dbh->{PrintError} = 0;
538
                local $dbh->{RaiseError} = 1;
539
                $dbh->do(qq{SELECT * FROM $table WHERE 1 = 0 });
540
            };
541
    return 1 unless $@;
542
    return 0;
543
}
544
545
1;
(-)a/about.pl (-1 / +41 lines)
Lines 32-37 use C4::Output; Link Here
32
use C4::Auth;
32
use C4::Auth;
33
use C4::Context;
33
use C4::Context;
34
use C4::Installer;
34
use C4::Installer;
35
use C4::Update::Database;
35
36
36
#use Smart::Comments '####';
37
#use Smart::Comments '####';
37
38
Lines 47-53 my ( $template, $loggedinuser, $cookie ) = get_template_and_user( Link Here
47
    }
48
    }
48
);
49
);
49
50
50
my $kohaVersion   = C4::Context::KOHAVERSION;
51
my $kohaVersion   = C4::Context->preference("Version");
52
# restore ., for display consistency
53
$kohaVersion =~ /(.)\.(..)(..)(...)/;
54
# transform digits to Perl number, to display 3.6.1.2 instead of 3.06.01.002
55
$kohaVersion = ($1+0).".".($2+0).".".($3+0).".".($4+0);
56
57
my $dbrev_applied=""; # the list of database revisions
58
59
# the $kohaVersion is duplicated since 3.7: the 3.6 (that uses the old mechanism) and the 3.7 (new mechanism).
60
# Both versions reflects how the database has been upgraded
61
my $already_knows = C4::Update::Database::list_versions_already_knows();
62
# $last_known contains the previous DBrev applied number (all . removed). It's used to have a . instead of a number in case of continuous updates
63
my $last_known=0;
64
# $last_known_sep contains the previous DBrev applied with the separator (used for display)
65
my $last_known_sep="";
66
for my $v ( @$already_knows ) {
67
    my $current = $v->{version};
68
    $current =~s/\.//g;
69
    # if the current number is the previous one +1, then just add a ., for a better display N.........N+10, for example
70
    # (instead of N / N+1 / N+2 / ...)
71
    if ($current==$last_known+1) {
72
        $dbrev_applied.=".";
73
    } else { # we're not N+1, start a new range
74
        # if version don't end by a ., no need to add the current loop number
75
        # this avoid having N...N (in case of an isolated BDrev number)
76
        if ($last_known & $dbrev_applied =~ /\.$/) {
77
            $dbrev_applied .= "...".$last_known_sep;
78
        }
79
        # start a new range
80
        $dbrev_applied .= " ".$v->{version};
81
    }
82
    $last_known= $current;
83
    $last_known_sep=$v->{version};
84
}
85
# add the last DB rev number, we don't want to end with "..."
86
if ($dbrev_applied =~ /\.$/) {
87
    $dbrev_applied .= "...".$last_known_sep;
88
}
89
51
my $osVersion     = `uname -a`;
90
my $osVersion     = `uname -a`;
52
my $perl_path = $^X;
91
my $perl_path = $^X;
53
if ($^O ne 'VMS') {
92
if ($^O ne 'VMS') {
Lines 76-81 my $warnIsRootUser = (! $loggedinuser); Link Here
76
115
77
$template->param(
116
$template->param(
78
    kohaVersion   => $kohaVersion,
117
    kohaVersion   => $kohaVersion,
118
    dbrev_applied => $dbrev_applied,
79
    osVersion     => $osVersion,
119
    osVersion     => $osVersion,
80
    perlPath      => $perl_path,
120
    perlPath      => $perl_path,
81
    perlVersion   => $perlVersion,
121
    perlVersion   => $perlVersion,
(-)a/admin/ajax-updatedb-getinfos.pl (+61 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2011 BibLibre
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
=head1 NAME
21
22
ajax-updatedb-getinfos.pl
23
24
=head1 DESCRIPTION
25
this script returns comments for a updatedatabase version
26
27
=back
28
29
=cut
30
31
use Modern::Perl;
32
use CGI;
33
use C4::Update::Database;
34
35
my $input = new CGI;
36
my $version = $input->param('version');
37
38
binmode STDOUT, ":utf8";
39
print $input->header(-type => 'text/plain', -charset => 'UTF-8');
40
my $filepath;
41
my $queries;
42
eval {
43
    $filepath = C4::Update::Database::get_filepath( $version );
44
    $queries = C4::Update::Database::get_queries( $filepath );
45
};
46
if ( $@ ){
47
    print $@;
48
    exit;
49
}
50
51
if ( @{ $$queries{comments} } ) {
52
    print "Comments : <br/>" . join ( "<br/>", @{ $$queries{comments} } );
53
} else {
54
    print "No comment <br/>";
55
}
56
57
if ( @{ $$queries{queries} } ) {
58
    print "<br/><br/>Queries : <br/>" . join ( "<br/>", @{ $$queries{queries} } );
59
} else {
60
    print "<br/>No queries";
61
}
(-)a/admin/updatedatabase.pl (+103 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 2 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
use strict;
20
use CGI;
21
use C4::Auth;
22
use C4::Output;
23
use C4::Update::Database;
24
25
my $query = new CGI;
26
my $op = $query->param('op') || 'list';
27
28
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
29
    {   template_name   => "admin/updatedatabase.tmpl",
30
        query           => $query,
31
        type            => "intranet",
32
        authnotrequired => 0,
33
        flagsrequired   => { parameters => 1 },
34
    }
35
);
36
37
if ( $op eq 'update' ) {
38
    my @versions = $query->param('version');
39
    @versions = sort {
40
        C4::Update::Database::TransformToNum( $a ) <=> C4::Update::Database::TransformToNum( $b )
41
    } @versions;
42
43
    my @reports;
44
    for my $version ( @versions ) {
45
        push @reports, C4::Update::Database::execute_version $version;
46
    }
47
48
    my @report_loop = map {
49
        my ( $v, $r ) = each %$_;
50
        my @errors = ref ( $r ) eq 'ARRAY'
51
            ?
52
                map {
53
                    { error => $_ }
54
                } @$r
55
            :
56
                { error => $r };
57
        {
58
            version => $v,
59
            report  => \@errors,
60
            coherency => ( ref ( $r ) eq 'ARRAY'
61
                ? @$r[0] =~ /COHERENCY/ ? 1 : 0
62
                : $r =~ /COHERENCY/ ? 1 : 0 )
63
        }
64
    } @reports;
65
    $template->param( report_loop => \@report_loop );
66
67
    $op = 'list';
68
}
69
70
if ( $op eq 'mark_as_ok' ) {
71
    my @versions = $query->param('version');
72
    C4::Update::Database::mark_as_ok $_ for @versions;
73
    $op = 'list';
74
}
75
76
if ( $op eq 'list' ) {
77
    my $versions_availables = C4::Update::Database::list_versions_availables;
78
    my $versions = C4::Update::Database::list_versions_already_knows;
79
80
    for my $v ( @$versions_availables ) {
81
        if ( not grep { $v eq $$_{version} } @$versions ) {
82
            push @$versions, {
83
                version => $v,
84
                available => 1
85
            };
86
        }
87
    }
88
    my @sorted = sort {
89
        C4::Update::Database::TransformToNum( $$a{version} ) <=> C4::Update::Database::TransformToNum( $$b{version} )
90
    } @$versions;
91
92
    my @availables = grep { defined $$_{available} and $$_{available} == 1 } @sorted;
93
    my @v_availables = map { {version => $$_{version}} } @availables;
94
95
    $template->param(
96
        dev_mode => $ENV{DEBUG},
97
        versions => \@sorted,
98
        nb_availables => scalar @availables,
99
        availables => [ map { {version => $$_{version}} } @availables ],
100
    );
101
}
102
103
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/installer/data/mysql/kohastructure.sql (+21 lines)
Lines 2082-2087 CREATE TABLE `tags_index` ( -- a weighted list of all tags and where they are us Link Here
2082
        REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE
2082
        REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE
2083
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2083
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2084
2084
2085
2086
--
2087
-- Table structure for database updates
2088
--
2089
CREATE TABLE`updatedb_error` (
2090
    `version` varchar(32) DEFAULT NULL,
2091
    `error` text
2092
) ENGINE=InnoDB CHARSET=utf8;
2093
2094
CREATE TABLE `updatedb_query` (
2095
    `version` varchar(32) DEFAULT NULL,
2096
    `query` text
2097
) ENGINE=InnoDB CHARSET=utf8;
2098
2099
CREATE TABLE `updatedb_report` (
2100
    `version` text,
2101
    `md5` varchar(50) DEFAULT NULL,
2102
    `comment` text,
2103
    `status` int(1) DEFAULT NULL
2104
) ENGINE=InnoDB CHARSET=utf8;
2105
2085
--
2106
--
2086
-- Table structure for table `userflags`
2107
-- Table structure for table `userflags`
2087
--
2108
--
(-)a/installer/data/mysql/update.pl (+28 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
use C4::Context;
4
use C4::Update::Database;
5
use Getopt::Long;
6
7
8
my $version;
9
my $list;
10
11
GetOptions(
12
    'm:s' => \$version,
13
    'l'   => \$list,
14
);
15
16
if ( $version ) {
17
    my $report = C4::Update::Database::execute_version($version);
18
}
19
20
if ( $list ) {
21
    my $versions = C4::Update::Database::list_versions_availables();
22
    say "Versions availables:";
23
    say "\t- $_" for @$versions;
24
    $versions = C4::Update::Database::list_versions_already_knows();
25
    say "Versions already knows:";
26
    say "\t- $$_{version}" for @$versions;
27
28
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css (+7 lines)
Lines 2139-2144 a.localimage img { Link Here
2139
}
2139
}
2140
div.pager p {
2140
div.pager p {
2141
	margin: 0;
2141
	margin: 0;
2142
2143
tr.dragClass td {
2144
    background-color: grey;
2145
    color: yellow;
2146
}
2147
.underline {
2148
    text-decoration : underline;
2142
}
2149
}
2143
2150
2144
div#acqui_order_supplierlist > div.supplier {
2151
div#acqui_order_supplierlist > div.supplier {
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/about.tt (-1 / +1 lines)
Lines 37-43 Link Here
37
37
38
        <table>
38
        <table>
39
            <caption>Server information</caption>
39
            <caption>Server information</caption>
40
            <tr><th scope="row">Koha version: </th><td>[% kohaVersion |html %]</td></tr>
40
            <tr><th scope="row">Koha version: </th><td>[% kohaVersion |html %] with the following database revisionapplied: [% dbrev_applied|html %]</td></tr>
41
            <tr><th scope="row">OS version ('uname -a'): </th><td>[% osVersion |html %]</td></tr>
41
            <tr><th scope="row">OS version ('uname -a'): </th><td>[% osVersion |html %]</td></tr>
42
            <tr><th scope="row">Perl interpreter: </th><td>[% perlPath |html %]</td></tr>
42
            <tr><th scope="row">Perl interpreter: </th><td>[% perlPath |html %]</td></tr>
43
            <tr><th scope="row">Perl version: </th><td>[% perlVersion |html %]</td></tr>
43
            <tr><th scope="row">Perl version: </th><td>[% perlVersion |html %]</td></tr>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+7 lines)
Lines 110-115 Link Here
110
    <dt><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></dt>
110
    <dt><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></dt>
111
    <dd>Choose which plugins to use to suggest searches to patrons and staff.</dd>
111
    <dd>Choose which plugins to use to suggest searches to patrons and staff.</dd>
112
</dl>
112
</dl>
113
114
<h3>Update Database</h3>
115
<dl>
116
    <dt><a href="/cgi-bin/koha/admin/updatedatabase.pl">Check your updates</a></dt>
117
    <dd>Verify your database versions and execute new updates</dd>
118
</dl>
119
113
</div>
120
</div>
114
121
115
</div>
122
</div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/updatedatabase.tt (+191 lines)
Line 0 Link Here
1
[% INCLUDE 'doc-head-open.inc' %]
2
<title>Koha &rsaquo; Administration &rsaquo; Update Database</title>
3
[% INCLUDE 'doc-head-close.inc' %]
4
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
5
<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.dataTables.min.js"></script>
6
[% INCLUDE 'datatables-strings.inc' %]
7
<script type="text/javascript" src="[% themelang %]/js/datatables.js"></script>
8
9
<script type="text/javascript">
10
 //<![CDATA[
11
    $(document).ready(function() {
12
        $("#versionst").dataTable($.extend(true, {}, dataTablesDefaults, {
13
            "aaSorting" : [[0, "desc"]],
14
            "sPaginationType": "four_button",
15
        }));
16
    } );
17
    function see_details(a){
18
        var div = $(a).siblings('div');
19
        $(div).slideToggle("fast", function() {
20
            var isVisible = $(div).is(":visible");
21
            if ( isVisible ){$(a).text("Hide details");}else{$(a).text("Show details");}
22
        } );
23
    }
24
    function get_infos(version, node){
25
        $.ajax({
26
            url: "/cgi-bin/koha/admin/ajax-updatedb-getinfos.pl",
27
            data: {
28
                version: version
29
            },
30
            success: function(data){
31
                $(node).replaceWith(data);
32
            },
33
        });
34
    }
35
//]]>
36
</script>
37
</head>
38
<body>
39
[% INCLUDE 'header.inc' %]
40
[% INCLUDE 'cat-search.inc' %]
41
42
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> &rsaquo; Database update</div>
43
44
<div id="doc3" class="yui-t2">
45
46
   <div id="bd">
47
    <div id="yui-main">
48
    <div class="yui-b">
49
50
    <h2>Database update</h2>
51
    [% IF report_loop %]
52
    <div class="report" style="display:block; margin:1em;">
53
        Report :
54
        <ul>
55
        [% FOREACH report_loo IN report_loop %]
56
            <li>
57
                [% report_loo.version %] --
58
                [% FOREACH r IN report_loo.report %]
59
                    [% IF r.error.error == "ALREADY_EXISTS" %]
60
                        This file ( [% r.error.filepath %] ) still already execute in version [% r.error.old_version %] : same md5 ([% r.error.md5 %])
61
                        [<a href="/cgi-bin/koha/admin/updatedatabase.pl?op=mark_as_ok&version=[% report_loo.version %]">Mark as OK</a>]
62
                    [% ELSIF r.error.error == "LOAD_FUNCTIONS_FAILED" %]
63
                        Load functions in [% r.error.filename %] failed
64
                    [% ELSIF r.error.error == "BAD_EXTENSION" %]
65
                        This extension ([% r.error.extension %]) is not take into account (only .pl or .sql)";
66
                    [% ELSE %]
67
                        [% r.error %];
68
                    [% END %]
69
                [% END %]
70
                [% IF report_loo.coherency %]
71
                    [<a href="/cgi-bin/koha/admin/updatedatabase.pl?op=mark_as_ok&version=[% report_loo.version %]">Mark as OK</a>]
72
                [% END %]
73
            </li>
74
        [% END %]
75
        </ul>
76
    </div>
77
    [% END %]
78
    <span class="infos" style="display:block; margin:1em;">
79
        [% IF nb_availables %]
80
            Your datebase is not up to date.<br/>
81
            [% IF nb_availables == 1 %]
82
                1 update available [<a href="/cgi-bin/koha/admin/updatedatabase.pl?op=update&version=[% availables.first.version %]">UPDATE [% availables.first.version %]</a>]
83
            [% ELSE %]
84
                [% nb_availables %] updates available [<a href="/cgi-bin/koha/admin/updatedatabase.pl?op=update[% FOREACH av IN availables %]&version=[% av.version %][% END %]">UPDATE ALL</a>]:
85
                [% IF ( dev_mode ) %]
86
                  <ul>
87
                    [% FOREACH av IN availables %]
88
                      <li>[% av.version %] [<a href="/cgi-bin/koha/admin/updatedatabase.pl?op=update&version=[% av.version %]">UPDATE</a>]</li>
89
                    [% END %]
90
                  </ul>
91
                [% END %]
92
            [% END %]
93
        [% ELSE %]
94
            Your database is up to date
95
        [% END %]
96
    </span>
97
98
    <table id="versionst">
99
        <thead>
100
            <tr>
101
                <th>DB revision</th>
102
                <th>Comments</th>
103
                <th>Status</th>
104
                <th>Availability</th>
105
                <th>Details</th>
106
            </tr>
107
        </thead>
108
        <tbody>
109
        [% FOREACH v IN versions %]
110
            <tr>
111
                <td>[% v.version %]</td>
112
                <td>
113
                    [% FOREACH c IN v.comments %]
114
                        [% c.comment %]
115
                    [% END %]
116
                </td>
117
                <td>
118
                    [% IF v.available %]
119
                        Unknown
120
                    [% ELSE %]
121
                        [% IF v.status %]
122
                            <span style="color:green;">OK</span>
123
                            [% IF v.status == 2 %]
124
                                [FORCED]
125
                            [% END %]
126
                        [% ELSE %]
127
                            <span style="color:red;">Failed</span>
128
                        [% END %]
129
                    [% END %]
130
                </td>
131
                <td>
132
                    [% IF v.available %]
133
                        Available
134
                        [% IF (dev_mode) %]
135
                            [<a href="/cgi-bin/koha/admin/updatedatabase.pl?op=update&version=[% v.version %]">Execute</a>]
136
                        [% END %]
137
                    [% ELSE %]
138
                        Already executed
139
                    [% END %]
140
                </td>
141
                <td width="50%">
142
                    <div class="details" style="display:none;">
143
                        [% IF v.available %]
144
                            Unknown
145
                            <span style="display:block;"><a href="#" onclick="get_infos('[% v.version %]', this); return false;">Get comments</a></span>
146
                        [% ELSE %]
147
                            <div class="queries" style="display:block;">
148
                                <span class="underline">Queries</span> :
149
                                [% FOREACH q IN v.queries %]
150
                                    [% q.query %]<br/>
151
                                [% END %]
152
                            </div>
153
                            [% IF v.status == 1 %]
154
                                <div class="status" style="display:block;">
155
                                    <span class="underline">Status</span> :
156
                                    <span style="color:green;">OK</span>
157
                                </div>
158
                            [% ELSE %]
159
                                <div class="status" style="display:block;">
160
                                    <span class="underline">Status</span> :
161
                                    [% IF v.status == 2 %]
162
                                        <span style="color:green;">OK</span>
163
                                        [FORCED]
164
                                    [% ELSE %]
165
                                        <span style="color:red;">Failed</span>
166
                                        [<a href="/cgi-bin/koha/admin/updatedatabase.pl?op=mark_as_ok&version=[% v.version %]">Mark as OK</a>]
167
                                    [% END %]
168
                                </div>
169
                                <div class="errors" style="display:block;">
170
                                    <span class="underline">Errors</span> :
171
                                    <span class="errors">
172
                                    [% FOREACH e IN v.errors %]
173
                                        <span>[% e.error %]</span>
174
                                    [% END %]
175
                                    </span>
176
                                </div>
177
                            [% END %]
178
                        [% END %]
179
                    </div>
180
                    <a href="#" onclick="see_details(this);return false;">Show details</a>
181
                </td>
182
            </tr>
183
        [% END %]
184
        </tbody>
185
    </table>
186
187
    </div>
188
    </div>
189
    </div>
190
    </div>
191
    </div>
(-)a/mainpage.pl (-1 / +18 lines)
Lines 28-33 use C4::NewsChannels; Link Here
28
use C4::Review qw/numberofreviews/;
28
use C4::Review qw/numberofreviews/;
29
use C4::Suggestions qw/CountSuggestion/;
29
use C4::Suggestions qw/CountSuggestion/;
30
use C4::Tags qw/get_count_by_tag_status/;
30
use C4::Tags qw/get_count_by_tag_status/;
31
use C4::Update::Database;
32
31
my $query     = new CGI;
33
my $query     = new CGI;
32
34
33
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
35
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
Lines 42-47 my ( $template, $loggedinuser, $cookie ) = get_template_and_user( Link Here
42
    }
44
    }
43
);
45
);
44
46
47
#
48
# check if you're uptodate, and if you're not, head to updater
49
#
50
my $koha36= C4::Context::KOHAVERSION;
51
$koha36 =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
52
53
if (C4::Context->preference('Version') < $koha36) {
54
    print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
55
    exit;
56
}
57
unless ( C4::Update::Database::is_uptodate() ) {
58
    # not uptodate, redirect to updatedatabase page
59
    print $query->redirect("/cgi-bin/koha/admin/updatedatabase.pl");
60
    exit;
61
}
62
45
my $all_koha_news   = &GetNewsToDisplay("koha");
63
my $all_koha_news   = &GetNewsToDisplay("koha");
46
my $koha_news_count = scalar @$all_koha_news;
64
my $koha_news_count = scalar @$all_koha_news;
47
65
48
- 

Return to bug 7167