Synch really broke this script...
[koha.git] / updater / updatedatabase
1 #!/usr/bin/perl
2
3 # $Id$
4
5 # Database Updater
6 # This script checks for required updates to the database.
7
8 # Part of the Koha Library Software www.koha.org
9 # Licensed under the GPL.
10
11 # Bugs/ToDo:
12 # - Would also be a good idea to offer to do a backup at this time...
13
14 # NOTE:  If you do something more than once in here, make it table driven.
15 use strict;
16
17 # CPAN modules
18 use DBI;
19 use Getopt::Long;
20 # Koha modules
21 use C4::Context;
22
23 # FIXME - The user might be installing a new database, so can't rely
24 # on /etc/koha.conf anyway.
25
26 my $debug = 0;
27
28 my (
29     $sth, $sti,
30     $query,
31     %existingtables,    # tables already in database
32     %types,
33     $table,
34     $column,
35     $type, $null, $key, $default, $extra,
36     $prefitem,          # preference item in systempreferences table
37 );
38
39 my $silent;
40 GetOptions(
41         's' =>\$silent
42         );
43 my $dbh = C4::Context->dbh;
44 print "connected to your DB. Checking & modifying it\n" unless $silent;
45
46 #-------------------
47 # Defines
48
49 # Tables to add if they don't exist
50 my %requiretables = (
51     categorytable       => "(categorycode char(5) NOT NULL default '',
52                              description text default '',
53                              itemtypecodes text default '',
54                              PRIMARY KEY (categorycode)
55                             )",
56     subcategorytable       => "(subcategorycode char(5) NOT NULL default '',
57                              description text default '',
58                              itemtypecodes text default '',
59                              PRIMARY KEY (subcategorycode)
60                             )",
61     mediatypetable       => "(mediatypecode char(5) NOT NULL default '',
62                              description text default '',
63                              itemtypecodes text default '',
64                              PRIMARY KEY (mediatypecode)
65                             )",
66     action_logs         => "(
67                                     `timestamp` TIMESTAMP NOT NULL ,
68                                     `user` INT( 11 ) NOT NULL ,
69                                     `module` TEXT default '',
70                                     `action` TEXT default '' ,
71                                     `object` INT(11) default '' ,
72                                     `info` TEXT default '' ,
73                                     PRIMARY KEY ( `timestamp` , `user` )
74                             )",
75         letter          => "(
76                                         module varchar(20) NOT NULL default '',
77                                         code varchar(20) NOT NULL default '',
78                                         name varchar(100) NOT NULL default '',
79                                         title varchar(200) NOT NULL default '',
80                                         content text,
81                                         PRIMARY KEY  (module,code)
82                                 )",
83         alert           =>"(
84                                         alertid int(11) NOT NULL auto_increment,
85                                         borrowernumber int(11) NOT NULL default '0',
86                                         type varchar(10) NOT NULL default '',
87                                         externalid varchar(20) NOT NULL default '',
88                                         PRIMARY KEY  (alertid),
89                                         KEY borrowernumber (borrowernumber),
90                                         KEY type (type,externalid)
91                                 )",
92
93 );
94
95 my %requirefields = (
96         subscription => { 'letter' => 'char(20) NULL'},
97 #    tablename        => { 'field' => 'fieldtype' },
98 );
99
100 my %dropable_table = (
101 # tablename => 'tablename',
102 );
103
104 my %uselessfields = (
105 # tablename => "field1,field2",
106         );
107 # the other hash contains other actions that can't be done elsewhere. they are done
108 # either BEFORE of AFTER everything else, depending on "when" entry (default => AFTER)
109
110 # The tabledata hash contains data that should be in the tables.
111 # The uniquefieldrequired hash entry is used to determine which (if any) fields
112 # must not exist in the table for this row to be inserted.  If the
113 # uniquefieldrequired entry is already in the table, the existing data is not
114 # modified, unless the forceupdate hash entry is also set.  Fields in the
115 # anonymous "forceupdate" hash will be forced to be updated to the default
116 # values given in the %tabledata hash.
117
118 my %tabledata = (
119 # tablename => [
120 #       {       uniquefielrequired => 'fieldname', # the primary key in the table
121 #               fieldname => fieldvalue,
122 #               fieldname2 => fieldvalue2,
123 #       },
124 # ],
125     systempreferences => [
126                 {
127             uniquefieldrequired => 'variable',
128             variable            => 'Activate_Log',
129             value               => 'On',
130             forceupdate         => { 'explanation' => 1,
131                                      'type' => 1},
132             explanation         => 'Turn Log Actions on DB On an Off',
133             type                => 'YesNo',
134         },
135         {
136             uniquefieldrequired => 'variable',
137             variable            => 'IndependantBranches',
138             value               => 0,
139             forceupdate         => { 'explanation' => 1,
140                                      'type' => 1},
141             explanation         => 'Turn Branch independancy management On an Off',
142             type                => 'YesNo',
143         },
144                 {
145             uniquefieldrequired => 'variable',
146             variable            => 'ReturnBeforeExpiry',
147             value               => 'Off',
148             forceupdate         => { 'explanation' => 1,
149                                      'type' => 1},
150             explanation         => 'If Yes, Returndate on issuing can\'t be after borrower card expiry',
151             type                => 'YesNo',
152         },
153     ],
154
155 );
156
157 my %fielddefinitions = (
158 # fieldname => [
159 #       {                 field => 'fieldname',
160 #             type    => 'fieldtype',
161 #             null    => '',
162 #             key     => '',
163 #             default => ''
164 #         },
165 #     ],
166 );
167
168 #-------------------
169 # Initialize
170
171 # Start checking
172
173 # Get version of MySQL database engine.
174 my $mysqlversion = `mysqld --version`;
175 $mysqlversion =~ /Ver (\S*) /;
176 $mysqlversion = $1;
177 if ( $mysqlversion ge '3.23' ) {
178     print "Could convert to MyISAM database tables...\n" unless $silent;
179 }
180
181 #---------------------------------
182 # Tables
183
184 # Collect all tables into a list
185 $sth = $dbh->prepare("show tables");
186 $sth->execute;
187 while ( my ($table) = $sth->fetchrow ) {
188     $existingtables{$table} = 1;
189 }
190
191
192 # Now add any missing tables
193 foreach $table ( keys %requiretables ) {
194     unless ( $existingtables{$table} ) {
195         print "Adding $table table...\n" unless $silent;
196         my $sth = $dbh->prepare("create table $table $requiretables{$table}");
197         $sth->execute;
198         if ( $sth->err ) {
199             print "Error : $sth->errstr \n";
200             $sth->finish;
201         }    # if error
202     }    # unless exists
203 }    # foreach
204
205 # now drop useless tables
206 foreach $table ( keys %dropable_table ) {
207         if ( $existingtables{$table} ) {
208                 print "Dropping unused table $table\n" if $debug and not $silent;
209                 $dbh->do("drop table $table");
210                 if ( $dbh->err ) {
211                         print "Error : $dbh->errstr \n";
212                 }
213         }
214 }
215
216 #---------------------------------
217 # Columns
218
219 foreach $table ( keys %requirefields ) {
220     print "Check table $table\n" if $debug and not $silent;
221     $sth = $dbh->prepare("show columns from $table");
222     $sth->execute();
223     undef %types;
224     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
225     {
226         $types{$column} = $type;
227     }    # while
228     foreach $column ( keys %{ $requirefields{$table} } ) {
229         print "  Check column $column  [$types{$column}]\n" if $debug and not $silent;
230         if ( !$types{$column} ) {
231
232             # column doesn't exist
233             print "Adding $column field to $table table...\n" unless $silent;
234             $query = "alter table $table
235                         add column $column " . $requirefields{$table}->{$column};
236             print "Execute: $query\n" if $debug;
237             my $sti = $dbh->prepare($query);
238             $sti->execute;
239             if ( $sti->err ) {
240                 print "**Error : $sti->errstr \n";
241                 $sti->finish;
242             }    # if error
243         }    # if column
244     }    # foreach column
245 }    # foreach table
246
247 foreach $table ( keys %fielddefinitions ) {
248         print "Check table $table\n" if $debug;
249         $sth = $dbh->prepare("show columns from $table");
250         $sth->execute();
251         my $definitions;
252         while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
253         {
254                 $definitions->{$column}->{type}    = $type;
255                 $definitions->{$column}->{null}    = $null;
256                 $definitions->{$column}->{key}     = $key;
257                 $definitions->{$column}->{default} = $default;
258                 $definitions->{$column}->{extra}   = $extra;
259         }    # while
260         my $fieldrow = $fielddefinitions{$table};
261         foreach my $row (@$fieldrow) {
262                 my $field   = $row->{field};
263                 my $type    = $row->{type};
264                 my $null    = $row->{null};
265                 my $key     = $row->{key};
266                 my $default = $row->{default};
267                 $default="''" unless $default;
268                 my $extra   = $row->{extra};
269                 my $def     = $definitions->{$field};
270                 unless ( $type eq $def->{type}
271                         && $null eq $def->{null}
272                         && $key eq $def->{key}
273                         && $default eq $def->{default}
274                         && $extra eq $def->{extra} )
275                 {
276
277                         if ( $null eq '' ) {
278                                 $null = 'NOT NULL';
279                         }
280                         if ( $key eq 'PRI' ) {
281                                 $key = 'PRIMARY KEY';
282                         }
283                         unless ( $extra eq 'auto_increment' ) {
284                                 $extra = '';
285                         }
286                         # if it's a new column use "add", if it's an old one, use "change".
287                         my $action;
288                         if ($definitions->{$field}->{type}) {
289                                 $action="change $field"
290                         } else {
291                                 $action="add";
292                         }
293 # if it's a primary key, drop the previous pk, before altering the table
294                         my $sth;
295                         if ($key ne 'PRIMARY KEY') {
296                                 $sth =$dbh->prepare("alter table $table $action $field $type $null $key $extra default ?");
297                         } else {
298                                 $sth =$dbh->prepare("alter table $table drop primary key, $action $field $type $null $key $extra default ?");
299                         }
300                         $sth->execute($default);
301                         print "  Alter $field in $table\n" unless $silent;
302                 }
303         }
304 }
305
306
307 # Populate tables with required data
308 foreach my $table ( keys %tabledata ) {
309     print "Checking for data required in table $table...\n" unless $silent;
310     my $tablerows = $tabledata{$table};
311     foreach my $row (@$tablerows) {
312         my $uniquefieldrequired = $row->{uniquefieldrequired};
313         my $uniquevalue         = $row->{$uniquefieldrequired};
314         my $forceupdate         = $row->{forceupdate};
315         my $sth                 =
316           $dbh->prepare(
317 "select $uniquefieldrequired from $table where $uniquefieldrequired=?"
318         );
319         $sth->execute($uniquevalue);
320         if ($sth->rows) {
321             foreach my $field (keys %$forceupdate) {
322                 if ($forceupdate->{$field}) {
323                     my $sth=$dbh->prepare("update systempreferences set $field=? where $uniquefieldrequired=?");
324                     $sth->execute($row->{$field}, $uniquevalue);
325                 }
326             }
327         } else {
328             print "Adding row to $table: " unless $silent;
329             my @values;
330             my $fieldlist;
331             my $placeholders;
332             foreach my $field ( keys %$row ) {
333                 next if $field eq 'uniquefieldrequired';
334                 next if $field eq 'forceupdate';
335                 my $value = $row->{$field};
336                 push @values, $value;
337                 print "  $field => $value" unless $silent;
338                 $fieldlist .= "$field,";
339                 $placeholders .= "?,";
340             }
341             print "\n" unless $silent;
342             $fieldlist    =~ s/,$//;
343             $placeholders =~ s/,$//;
344             my $sth =
345               $dbh->prepare(
346                 "insert into $table ($fieldlist) values ($placeholders)");
347             $sth->execute(@values);
348         }
349     }
350 }
351
352 # at last, remove useless fields
353 foreach $table ( keys %uselessfields ) {
354         my @fields = split /,/,$uselessfields{$table};
355         my $fields;
356         my $exists;
357         foreach my $fieldtodrop (@fields) {
358                 $fieldtodrop =~ s/\t//g;
359                 $fieldtodrop =~ s/\n//g;
360                 $exists =0;
361                 $sth = $dbh->prepare("show columns from $table");
362                 $sth->execute;
363                 while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
364                 {
365                         $exists =1 if ($column eq $fieldtodrop);
366                 }
367                 if ($exists) {
368                         print "deleting $fieldtodrop field in $table...\n" unless $silent;
369                         my $sth = $dbh->prepare("alter table $table drop $fieldtodrop");
370                         $sth->execute;
371                 }
372         }
373 }    # foreach
374
375
376 $sth->finish;
377
378 exit;
379
380 # $Log$
381 # Revision 1.119  2005/08/04 16:07:58  tipaul
382 # Synch really broke this script...
383 #
384 # Revision 1.118  2005/08/04 16:02:55  tipaul
385 # oops... error in synch between 2.2 and head
386 #
387 # Revision 1.117  2005/08/04 14:24:39  tipaul
388 # synch'ing 2.2 and head
389 #
390 # Revision 1.116  2005/08/04 08:55:54  tipaul
391 # Letters / alert system, continuing...
392 #
393 # * adding a package Letters.pm, that manages Letters & alerts.
394 # * adding feature : it's now possible to define a "letter" for any subscription created. If a letter is defined, users in OPAC can put an alert on the subscription. When an issue is marked "arrived", all users in the alert will recieve a mail (as defined in the "letter"). This last part (= send the mail) is not yet developped. (Should be done this week)
395 # * adding feature : it's now possible to "put to an alert" in OPAC, for any serial subscription. The alert is stored in a new table, called alert. An alert can be put only if the librarian has activated them in subscription (and they activate it just by choosing a "letter" to sent to borrowers on new issues)
396 # * adding feature : librarian can see in borrower detail which alerts they have put, and a user can see in opac-detail which alert they have put too.
397 #
398 # Note that the system should be generic enough to manage any type of alert.
399 # I plan to extend it soon to virtual shelves : a borrower will be able to put an alert on a virtual shelf, to be warned when something is changed in the virtual shelf (mail being sent once a day by cron, or manually by the shelf owner. Anyway, a mail won't be sent on every change, users would be spammed by Koha ;-) )
400 #
401 # Revision 1.115  2005/08/02 16:15:34  tipaul
402 # adding 2 fields to letter system :
403 # * module (acquisition, catalogue...) : it will be usefull to show the librarian only letters he may be interested by.
404 # * title, that will be used as mail subject.
405 #
406 # Revision 1.114  2005/07/28 15:10:13  tipaul
407 # Introducing new "Letters" system : Letters will be used everytime you want to sent something to someone (through mail or paper). For example, sending a mail for overdues use letter that you can put as parameters. Sending a mail to a borrower when a suggestion is validated uses a letter too.
408 # the letter table contains 3 fields :
409 # * code => the code of the letter
410 # * name => the complete name of the letter
411 # * content => the complete text. It's a TEXT field type, so has no limits.
412 #
413 # My next goal now is to work on point 2-I "serial issue alert"
414 # With this feature, in serials, a user can subscribe the "issue alert". For every issue arrived/missing, a mail is sent to all subscribers of this list. The mail warns the user that the issue is arrive or missing. Will be in head.
415 # (see mail on koha-devel, 2005/04/07)
416 #
417 # The "serial issue alert" will be the 1st to use this letter system that probably needs some tweaking ;-)
418 #
419 # Once it will be stabilised default letters (in any languages) could be added during installer to help the library begin with this new feature.
420 #
421 # Revision 1.113  2005/07/28 08:38:41  tipaul
422 # For instance, the return date does not rely on the borrower expiration date. A systempref will be added in Koha, to modify return date calculation schema :
423 # * ReturnBeforeExpiry = yes => return date can't be after expiry date
424 # * ReturnBeforeExpiry = no  => return date can be after expiry date
425 #
426 # Revision 1.112  2005/07/26 08:19:47  hdl
427 # Adding IndependantBranches System preference variable in order to manage Branch independancy.
428 #
429 # Revision 1.111  2005/07/25 15:35:38  tipaul
430 # we have decided that moving to Koha 3.0 requires being already in Koha 2.2.x
431 # So, the updatedatabase script can highly be cleaned (90% removed).
432 # Let's play with the new Koha DB structure now ;-)
433 #