3 # Copyright 2013 Rijksmuseum
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 # This script imports/exports systempreferences to file.
21 # Two interesting features are:
22 # 1) It may help you to compare systempreferences between Koha instances.
23 # 2) You can also quickly restore subsets of preferences while testing.
24 # Just leave only e.g. some circulations prefs in a file and compare with
28 use open OUT
=> ':encoding(UTF-8)', ':std';
34 my $dbh = C4
::Context
->dbh;
36 my ( $help, $cmd, $filename, $override, $compare_add, $compare_del, $compare_upd, $ignore_opt, $partial );
40 'file:s' => \
$filename,
41 'add' => \
$compare_add,
42 'del' => \
$compare_del,
43 'upd' => \
$compare_upd,
44 'ign-opt' => \
$ignore_opt,
45 'partial' => \
$partial,
48 if ( $filename && !-e
$filename && $cmd !~ /^b/ ) {
49 die "File $filename not found";
51 if ( !$cmd || !$filename || $help ) {
52 pod2usage
( -verbose
=> 2 );
56 #------------------------------------------------------------------------------
59 if ( $cmd =~ /^b/i && $filename ) {
60 my $dbprefs = ReadPrefsFromDb
();
61 open my $fh, '>:encoding(UTF-8)', $filename;
62 SavePrefsToFile
( $dbprefs, $fh );
66 #test pref file: read and save for gaining confidence :) run a diff
67 if ( $cmd =~ /^t/i && $filename ) {
68 my $fileprefs = ReadPrefsFromFile
($filename);
69 open my $fh, '>:encoding(UTF-8)', $filename . ".sav";
70 SavePrefsToFile
( $fileprefs, $fh );
74 #compare prefs (with db)
75 if ( $cmd =~ /^c/i && $filename ) {
76 my $dbprefs = ReadPrefsFromDb
();
77 my $fileprefs = ReadPrefsFromFile
($filename);
80 my $cmp = ComparePrefs
( $dbprefs, $fileprefs );
81 PrintCompare
( $cmp, "database", "file $filename" );
82 HandleCompareChanges
( $cmp, $dbprefs, $fileprefs )
83 if $compare_add || $compare_del || $compare_upd;
87 if ( $cmd =~ /^r/i && $filename ) {
88 my $fileprefs = ReadPrefsFromFile
($filename);
89 CheckVersionPref
($fileprefs);
91 #override this check by removing Version from your file
92 #if you know what you are doing of course
93 SavePrefsToDb
($fileprefs);
96 #------------------------------------------------------------------------------
99 my ( $ch, $s1, $s2 ) = @_;
100 foreach ( sort keys %$ch ) {
102 next if $v eq '1' && $partial;
104 if ( $v eq '1' ) { print "Not in $s2"; }
105 elsif ( $v eq '2' ) { print "Not in $s1"; }
106 else { print "Different values: $v"; }
111 sub HandleCompareChanges
{
112 my ( $cmp_pref, $dbpref, $filepref ) = @_;
114 foreach my $k ( sort keys %$cmp_pref ) {
115 my $cmp = $cmp_pref->{$k};
117 $t += DeleteOnePref
($k) if $compare_del;
118 } elsif ( $cmp eq '2' ) {
119 my $kwc = $filepref->{$k}->{orgkey
};
120 my $val = $filepref->{$k}->{value
};
121 my $type = $filepref->{$k}->{type
};
122 $t += InsertIgnoreOnePref
( $kwc, $val, $type ) if $compare_add;
123 } elsif ($cmp) { #should contain something..
124 my $val = $filepref->{$k}->{value
};
125 $t += UpdateOnePref
( $k, $val ) if $compare_upd;
128 print "Adjusted $t prefs from this compare.\n";
132 my ( $ph1, $ph2 ) = @_;
134 foreach my $k ( keys %$ph1 ) {
135 if ( !exists $ph2->{$k} ) {
138 my $v1 = $ph1->{$k}->{value
} // 'NULL';
139 my $v2 = $ph2->{$k}->{value
} // 'NULL';
141 $res->{$k} = "$v1 / $v2";
145 foreach my $k ( keys %$ph2 ) {
146 if ( !exists $ph1->{$k} ) {
153 sub ReadPrefsFromDb
{
154 my $sql = 'SELECT variable AS orgkey, LOWER(variable) AS variable, value, type FROM systempreferences ORDER BY variable';
155 my $hash = $dbh->selectall_hashref( $sql, 'variable' );
159 sub ReadPrefsFromFile
{
161 open my $fh, '<:encoding(UTF-8)', $filename;
165 for ( my $t = 0 ; $t < @lines ; $t++ ) {
166 next if $lines[$t] =~ /^\s*#|^\s*$/; # comment line or empty line
167 my @l = split ",", $lines[$t], 4;
168 die "Invalid pref file; check line " . ++$t if @l < 4 || $l[0] !~ /^\d+$/ || $t + $l[0] >= @lines;
170 $hash->{$key} = { orgkey
=> $l[1], value
=> $l[3], type
=> $l[2] };
171 for ( my $j = 0 ; $j < $l[0] ; $j++ ) { $hash->{$key}->{value
} .= $lines[ $t + $j + 1 ]; }
173 $hash->{$key}->{value
} =~ s/\n$//; #only 'last' line
178 sub SavePrefsToFile
{
179 my ( $hash, $fh ) = @_;
180 print $fh '#cmp_sysprefs.pl: ' . C4
::Context
->config('database') . ', ' . localtime . "\n";
181 foreach my $k ( sort keys %$hash ) {
183 #sort handles underscore differently than mysql?
184 my $c = CountLines
( $hash->{$k}->{value
} );
185 my $kwc = $hash->{$k}->{orgkey
}; # key-with-case
186 print $fh "$c,$kwc," . ( $hash->{$k}->{type
} // 'Free' ) . ',' . ( $hash->{$k}->{value
} // 'NULL' ) . "\n";
194 #will not erase everything! you can do that in mysql :)
195 foreach my $k ( keys %$hash ) {
196 my $v = $hash->{$k}->{value
} eq 'NULL' ?
undef : $hash->{$k}->{value
};
197 my $kwc = $hash->{$k}->{orgkey
} // $k;
198 my $type = $hash->{$k}->{type
} // 'Free';
200 #insert and update seem overkill, but better than delete and insert
201 #you cannot assume that the pref IS or IS NOT there
202 InsertIgnoreOnePref
( $kwc, $v, $type );
203 UpdateOnePref
( $k, $v );
206 print "Updated $t prefs\n";
209 sub InsertIgnoreOnePref
{
210 my ( $kwc, $v, $t ) = @_;
212 'INSERT IGNORE INTO systempreferences (variable, value, type)
213 VALUES (?,?,?)', undef, ( $kwc, $v, $t )
215 return !defined($i) || $i eq '0E0'?
0: $i;
220 return 0 if lc $k eq 'version';
221 my $i = $dbh->do( 'UPDATE systempreferences SET value=? WHERE variable=?', undef, ( $v, $k ) );
222 return !defined($i) || $i eq '0E0'?
0: $i;
227 return if lc $k eq 'version';
228 my $sql = 'DELETE FROM systempreferences WHERE variable=?';
229 unless ($ignore_opt) {
230 $sql .= " AND COALESCE(explanation,'')='' AND COALESCE(options,'')=''";
232 my $i = $dbh->do( $sql, undef, ($k) );
233 return !defined($i) || $i eq '0E0'?
0: $i;
236 sub CheckVersionPref
{ #additional precaution
237 #if there are versions, compare them
239 my $hv = exists $hash->{version
}?
$hash->{version
}->{value
}: undef;
240 return if !defined $hv;
241 my ($dv) = $dbh->selectrow_array(
242 'SELECT value FROM systempreferences
243 WHERE variable LIKE ?', undef, ('version')
245 return if !defined $dv;
246 die "Versions do not match ($dv, $hv)" if $dv ne $hv;
251 return ( $_[0] && ( @ma = $_[0] =~ /\r?\n|\r\n?/g ) ) ?
scalar @ma : 0;
260 cmp_sysprefs.pl -help
262 cmp_sysprefs.pl -cmd backup -file prefbackup
264 cmp_sysprefs.pl -cmd compare -file prefbackup -upd
266 cmp_sysprefs.pl -cmd compare -file prefbackup -del -ign-opt
268 cmp_sysprefs.pl -cmd restore -file prefbackup
272 This script may backup, compare and restore system preferences from file.
274 Precaution: only the last command or file name will be used. The add, del and
275 upd parameters are extensions for the compare command. They allow you to act
276 immediately on the compare results.
278 When restoring a preferences file containing a version pref to a database having
279 another version, the restore will not be made. Similarly, a version pref will
280 never be overwritten. A restore will overwrite prefs but not delete them.
282 It is possible to edit the preference backup files. But be careful. The first
283 parameter for each preference is a line count. Some preference values use more
284 than one line. If you edit a file, make sure that the line counts are still
287 You can compare/restore using edited/partial preference files. Take special
288 care when using the del parameter in comparing such a partial file. It will
289 delete all prefs in the database not found in your partial file. Partial pref
290 files can however be very useful when testing or monitoring a limited set of
293 The ign-opt flag allows you to delete preferences that have explanation or
294 options in the database. If you do not set this flag, a compare with delete
295 will by default only delete preferences without explanation/options. Use this
296 option only if you understand the risk. Note that a restore will recover value,
297 not explanation or options. (See also BZ 10199.)
303 Print this usage statement.
307 Command: backup, compare, restore or test.
311 Name of the file used in command.
315 Only for partial compares: skip 'not present in file'-messages.
319 Only for compares: restore preferences not present in database.
323 Only for compares: delete preferences not present in file.
327 Only for compares: update preferences when values differ.
331 Ignore options/explanation when comparing with delete flag. Use this flag with care.