New table used in overduerules.pl tools page.
[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 use MARC::Record;
24 use MARC::File::XML;
25
26 # FIXME - The user might be installing a new database, so can't rely
27 # on /etc/koha.conf anyway.
28
29 my $debug = 0;
30
31 my (
32     $sth, $sti,
33     $query,
34     %existingtables,    # tables already in database
35     %types,
36     $table,
37     $column,
38     $type, $null, $key, $default, $extra,
39     $prefitem,          # preference item in systempreferences table
40 );
41
42 my $silent;
43 GetOptions(
44         's' =>\$silent
45         );
46 my $dbh = C4::Context->dbh;
47 print "connected to your DB. Checking & modifying it\n" unless $silent;
48 $|=1; # flushes output
49
50 #-------------------
51 # Defines
52
53 # Tables to add if they don't exist
54 my %requiretables = (
55     categorytable       => "(categorycode char(5) NOT NULL default '',
56                              description text default '',
57                              itemtypecodes text default '',
58                              PRIMARY KEY (categorycode)
59                             )",
60     subcategorytable       => "(subcategorycode char(5) NOT NULL default '',
61                              description text default '',
62                              itemtypecodes text default '',
63                              PRIMARY KEY (subcategorycode)
64                             )",
65     mediatypetable       => "(mediatypecode char(5) NOT NULL default '',
66                              description text default '',
67                              itemtypecodes text default '',
68                              PRIMARY KEY (mediatypecode)
69                             )",
70     action_logs         => "(
71                                     `timestamp` TIMESTAMP NOT NULL ,
72                                     `user` INT( 11 ) NOT NULL ,
73                                     `module` TEXT default '',
74                                     `action` TEXT default '' ,
75                                     `object` INT(11) default '' ,
76                                     `info` TEXT default '' ,
77                                     PRIMARY KEY ( `timestamp` , `user` )
78                             )",
79         letter          => "(
80                                         module varchar(20) NOT NULL default '',
81                                         code varchar(20) NOT NULL default '',
82                                         name varchar(100) NOT NULL default '',
83                                         title varchar(200) NOT NULL default '',
84                                         content text,
85                                         PRIMARY KEY  (module,code)
86                                 )",
87         alert           =>"(
88                                         alertid int(11) NOT NULL auto_increment,
89                                         borrowernumber int(11) NOT NULL default '0',
90                                         type varchar(10) NOT NULL default '',
91                                         externalid varchar(20) NOT NULL default '',
92                                         PRIMARY KEY  (alertid),
93                                         KEY borrowernumber (borrowernumber),
94                                         KEY type (type,externalid)
95                                 )",
96         overduerules    =>"(`branchcode` varchar(255) NOT NULL default '',
97                                         `categorycode` char(2) NOT NULL default '',
98                                         `delay1` int(4) default '0',
99                                         `letter1` varchar(20) default NULL,
100                                         `debarred1` char(1) default '0',
101                                         `delay2` int(4) default '0',
102                                         `debarred2` char(1) default '0',
103                                         `letter2` varchar(20) default NULL,
104                                         `delay3` int(4) default '0',
105                                         `letter3` varchar(20) default NULL,
106                                         `debarred3` int(1) default '0',
107                                         PRIMARY KEY  (`branchcode`,`categorycode`)
108                                         )",
109 );
110
111 my %requirefields = (
112         subscription => { 'letter' => 'char(20) NULL', 'distributedto' => 'text NULL'},
113         itemtypes => { 'imageurl' => 'char(200) NULL'},
114 #    tablename        => { 'field' => 'fieldtype' },
115 );
116
117 my %dropable_table = (
118         sessionqueries  => 'sessionqueries',
119         marcrecorddone  => 'marcrecorddone',
120         users                   => 'users',
121         itemsprices             => 'itemsprices',
122         biblioanalysis  => 'biblioanalysis',
123         borexp                  => 'borexp',
124 # tablename => 'tablename',
125 );
126
127 my %uselessfields = (
128 # tablename => "field1,field2",
129         );
130 # the other hash contains other actions that can't be done elsewhere. they are done
131 # either BEFORE of AFTER everything else, depending on "when" entry (default => AFTER)
132
133 # The tabledata hash contains data that should be in the tables.
134 # The uniquefieldrequired hash entry is used to determine which (if any) fields
135 # must not exist in the table for this row to be inserted.  If the
136 # uniquefieldrequired entry is already in the table, the existing data is not
137 # modified, unless the forceupdate hash entry is also set.  Fields in the
138 # anonymous "forceupdate" hash will be forced to be updated to the default
139 # values given in the %tabledata hash.
140
141 my %tabledata = (
142 # tablename => [
143 #       {       uniquefielrequired => 'fieldname', # the primary key in the table
144 #               fieldname => fieldvalue,
145 #               fieldname2 => fieldvalue2,
146 #       },
147 # ],
148     systempreferences => [
149                 {
150             uniquefieldrequired => 'variable',
151             variable            => 'Activate_Log',
152             value               => 'On',
153             forceupdate         => { 'explanation' => 1,
154                                      'type' => 1},
155             explanation         => 'Turn Log Actions on DB On an Off',
156             type                => 'YesNo',
157         },
158         {
159             uniquefieldrequired => 'variable',
160             variable            => 'IndependantBranches',
161             value               => 0,
162             forceupdate         => { 'explanation' => 1,
163                                      'type' => 1},
164             explanation         => 'Turn Branch independancy management On an Off',
165             type                => 'YesNo',
166         },
167                 {
168             uniquefieldrequired => 'variable',
169             variable            => 'ReturnBeforeExpiry',
170             value               => 'Off',
171             forceupdate         => { 'explanation' => 1,
172                                      'type' => 1},
173             explanation         => 'If Yes, Returndate on issuing can\'t be after borrower card expiry',
174             type                => 'YesNo',
175         },
176         {
177             uniquefieldrequired => 'variable',
178             variable            => 'opacstylesheet',
179             value               => '',
180             forceupdate         => { 'explanation' => 1,
181                                      'type' => 1},
182             explanation         => 'Enter a complete URL to use an alternate stylesheet in OPAC',
183             type                => 'free',
184         },
185         {
186             uniquefieldrequired => 'variable',
187             variable            => 'opacsmallimage',
188             value               => '',
189             forceupdate         => { 'explanation' => 1,
190                                      'type' => 1},
191             explanation         => 'Enter a complete URL to an image, will be on top/left instead of the Koha logo',
192             type                => 'free',
193         },
194         {
195             uniquefieldrequired => 'variable',
196             variable            => 'opaclargeimage',
197             value               => '',
198             forceupdate         => { 'explanation' => 1,
199                                      'type' => 1},
200             explanation         => 'Enter a complete URL to an image, will be on the main page, instead of the Koha logo',
201             type                => 'free',
202         },
203         {
204             uniquefieldrequired => 'variable',
205             variable            => 'delimiter',
206             value               => ';',
207             forceupdate         => { 'explanation' => 1,
208                                      'type' => 1},
209             explanation         => 'separator for reports exported to spreadsheet',
210             type                => 'free',
211         },
212         {
213             uniquefieldrequired => 'variable',
214             variable            => 'MIME',
215             value               => 'OPENOFFICE.ORG',
216             forceupdate         => { 'explanation' => 1,
217                                      'type' => 1,
218                                      'options' => 1},
219             explanation         => 'Define the default application for report exportations into files',
220                 type            => 'Choice',
221                 options         => 'EXCEL|OPENOFFICE.ORG'
222         },
223         {
224             uniquefieldrequired => 'variable',
225             variable            => 'Delimiter',
226             value               => ';',
227                 forceupdate             => { 'explanation' => 1,
228                                      'type' => 1,
229                                      'options' => 1},
230             explanation         => 'Define the default separator character for report exportations into files',
231                 type            => 'Choice',
232                 options         => ';|tabulation|,|/|\|#'
233         },
234         {
235             uniquefieldrequired => 'variable',
236             variable            => 'SubscriptionHistory',
237             value               => ';',
238                 forceupdate             => { 'explanation' => 1,
239                                      'type' => 1,
240                                      'options' => 1},
241             explanation         => 'Define the information level for serials history in OPAC',
242                 type            => 'Choice',
243                 options         => 'simplified|full'
244         },
245         {
246             uniquefieldrequired => 'variable',
247             variable            => 'hidelostitems',
248             value               => 'No',
249             forceupdate         => { 'explanation' => 1,
250                                      'type' => 1},
251             explanation         => 'show or hide "lost" items in OPAC.',
252             type                => 'YesNo',
253         },
254                  {
255             uniquefieldrequired => 'variable',
256             variable            => 'IndependantBranches',
257             value               => '0',
258             forceupdate         => { 'explanation' => 1,
259                                      'type' => 1},
260             explanation         => 'Turn Branch independancy management On an Off',
261             type                => 'YesNo',
262         },
263                 {
264             uniquefieldrequired => 'variable',
265             variable            => 'ReturnBeforeExpiry',
266             value               => '0',
267             forceupdate         => { 'explanation' => 1,
268                                      'type' => 1},
269             explanation         => 'If Yes, Returndate on issuing can\'t be after borrower card expiry',
270             type                => 'YesNo',
271         },
272         {
273             uniquefieldrequired => 'variable',
274             variable            => 'Disable_Dictionary',
275             value               => '0',
276             forceupdate         => { 'explanation' => 1,
277                                      'type' => 1},
278             explanation         => 'Disables Dictionary buttons if set to yes',
279             type                => 'YesNo',
280         },
281         {
282             uniquefieldrequired => 'variable',
283             variable            => 'hide_marc',
284             value               => '0',
285             forceupdate         => { 'explanation' => 1,
286                                      'type' => 1},
287             explanation         => 'hide marc specific datas like subfield code & indicators to library',
288             type                => 'YesNo',
289         },
290         {
291             uniquefieldrequired => 'variable',
292             variable            => 'NotifyBorrowerDeparture',
293             value               => '0',
294             forceupdate         => { 'explanation' => 1,
295                                      'type' => 1},
296             explanation         => 'Delay before expiry where a notice is sent when issuing',
297             type                => 'Integer',
298         },
299         {
300             uniquefieldrequired => 'variable',
301             variable            => 'OpacPasswordChange',
302             value               => '1',
303             forceupdate         => { 'explanation' => 1,
304                                      'type' => 1},
305             explanation         => 'Enable/Disable password change in OPAC (disable it when using LDAP auth)',
306             type                => 'YesNo',
307         },
308     ],
309
310 );
311
312 my %fielddefinitions = (
313 # fieldname => [
314 #       {                 field => 'fieldname',
315 #             type    => 'fieldtype',
316 #             null    => '',
317 #             key     => '',
318 #             default => ''
319 #         },
320 #     ],
321         serial => [
322         {
323             field   => 'notes',
324             type    => 'TEXT',
325             null    => 'NULL',
326             key     => '',
327             default => '',
328             extra   => ''
329         },
330     ],
331         aqbasket =>  [
332                 {
333                         field   => 'booksellerid',
334                         type    => 'int(11)',
335                         null    => 'NOT NULL',
336                         key             => '',
337                         default => '1',
338                         extra   => '',
339                 },
340         ],
341         aqbooksellers =>  [
342                 {
343                         field   => 'listprice',
344                         type    => 'varchar(10)',
345                         null    => 'NULL',
346                         key             => '',
347                         default => '',
348                         extra   => '',
349                 },
350                 {
351                         field   => 'invoiceprice',
352                         type    => 'varchar(10)',
353                         null    => 'NULL',
354                         key             => '',
355                         default => '',
356                         extra   => '',
357                 },
358         ],
359         issues =>  [
360                 {
361                         field   => 'borrowernumber',
362                         type    => 'int(11)',
363                         null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
364                         key             => '',
365                         default => '',
366                         extra   => '',
367                 },
368                 {
369                         field   => 'itemnumber',
370                         type    => 'int(11)',
371                         null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
372                         key             => '',
373                         default => '',
374                         extra   => '',
375                 },
376         ],
377 );
378
379 my %indexes = (
380 #       table => [
381 #               {       indexname => 'index detail'
382 #               }
383 #       ],
384         shelfcontents => [
385                 {       indexname => 'shelfnumber',
386                         content => 'shelfnumber',
387                 },
388                 {       indexname => 'itemnumber',
389                         content => 'itemnumber',
390                 }
391         ],
392         bibliosubject => [
393                 {       indexname => 'biblionumber',
394                         content => 'biblionumber',
395                 }
396         ],
397         items => [
398                 {       indexname => 'homebranch',
399                         content => 'homebranch',
400                 },
401                 {       indexname => 'holdingbranch',
402                         content => 'holdingbranch',
403                 }
404         ],
405         aqbooksellers => [
406                 {       indexname => 'PRIMARY',
407                         content => 'id',
408                         type => 'PRIMARY',
409                 }
410         ],
411         aqbasket => [
412                 {       indexname => 'booksellerid',
413                         content => 'booksellerid',
414                 },
415         ],
416         aqorders => [
417                 {       indexname => 'basketno',
418                         content => 'basketno',
419                 },
420         ],
421         aqorderbreakdown => [
422                 {       indexname => 'ordernumber',
423                         content => 'ordernumber',
424                 },
425                 {       indexname => 'bookfundid',
426                         content => 'bookfundid',
427                 },
428         ],
429         currency => [
430                 {       indexname => 'PRIMARY',
431                         content => 'currency',
432                         type => 'PRIMARY',
433                 }
434         ],
435 );
436
437 my %foreign_keys = (
438 #       table => [
439 #               {       key => 'the key in table' (must be indexed)
440 #                       foreigntable => 'the foreigntable name', # (the parent)
441 #                       foreignkey => 'the foreign key column(s)' # (in the parent)
442 #                       onUpdate => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
443 #                       onDelete => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
444 #               }
445 #       ],
446         shelfcontents => [
447                 {       key => 'shelfnumber',
448                         foreigntable => 'bookshelf',
449                         foreignkey => 'shelfnumber',
450                         onUpdate => 'CASCADE',
451                         onDelete => 'CASCADE',
452                 },
453                 {       key => 'itemnumber',
454                         foreigntable => 'items',
455                         foreignkey => 'itemnumber',
456                         onUpdate => 'CASCADE',
457                         onDelete => 'CASCADE',
458                 },
459         ],
460         # onDelete is RESTRICT on reference tables (branches, itemtype) as we don't want items to be 
461         # easily deleted, but branches/itemtype not too easy to empty...
462         biblioitems => [
463                 {       key => 'biblionumber',
464                         foreigntable => 'biblio',
465                         foreignkey => 'biblionumber',
466                         onUpdate => 'CASCADE',
467                         onDelete => 'CASCADE',
468                 },
469                 {       key => 'itemtype',
470                         foreigntable => 'itemtypes',
471                         foreignkey => 'itemtype',
472                         onUpdate => 'CASCADE',
473                         onDelete => 'RESTRICT',
474                 },
475         ],
476         items => [
477                 {       key => 'biblioitemnumber',
478                         foreigntable => 'biblioitems',
479                         foreignkey => 'biblioitemnumber',
480                         onUpdate => 'CASCADE',
481                         onDelete => 'CASCADE',
482                 },
483                 {       key => 'homebranch',
484                         foreigntable => 'branches',
485                         foreignkey => 'branchcode',
486                         onUpdate => 'CASCADE',
487                         onDelete => 'RESTRICT',
488                 },
489                 {       key => 'holdingbranch',
490                         foreigntable => 'branches',
491                         foreignkey => 'branchcode',
492                         onUpdate => 'CASCADE',
493                         onDelete => 'RESTRICT',
494                 },
495         ],
496         additionalauthors => [
497                 {       key => 'biblionumber',
498                         foreigntable => 'biblio',
499                         foreignkey => 'biblionumber',
500                         onUpdate => 'CASCADE',
501                         onDelete => 'CASCADE',
502                 },
503         ],
504         bibliosubject => [
505                 {       key => 'biblionumber',
506                         foreigntable => 'biblio',
507                         foreignkey => 'biblionumber',
508                         onUpdate => 'CASCADE',
509                         onDelete => 'CASCADE',
510                 },
511         ],
512         aqbasket => [
513                 {       key => 'booksellerid',
514                         foreigntable => 'aqbooksellers',
515                         foreignkey => 'id',
516                         onUpdate => 'CASCADE',
517                         onDelete => 'RESTRICT',
518                 },
519         ],
520         aqorders => [
521                 {       key => 'basketno',
522                         foreigntable => 'aqbasket',
523                         foreignkey => 'basketno',
524                         onUpdate => 'CASCADE',
525                         onDelete => 'CASCADE',
526                 },
527                 {       key => 'biblionumber',
528                         foreigntable => 'biblio',
529                         foreignkey => 'biblionumber',
530                         onUpdate => 'SET NULL',
531                         onDelete => 'SET NULL',
532                 },
533         ],
534         aqbooksellers => [
535                 {       key => 'listprice',
536                         foreigntable => 'currency',
537                         foreignkey => 'currency',
538                         onUpdate => 'CASCADE',
539                         onDelete => 'CASCADE',
540                 },
541                 {       key => 'invoiceprice',
542                         foreigntable => 'currency',
543                         foreignkey => 'currency',
544                         onUpdate => 'CASCADE',
545                         onDelete => 'CASCADE',
546                 },
547         ],
548         aqorderbreakdown => [
549                 {       key => 'ordernumber',
550                         foreigntable => 'aqorders',
551                         foreignkey => 'ordernumber',
552                         onUpdate => 'CASCADE',
553                         onDelete => 'CASCADE',
554                 },
555                 {       key => 'bookfundid',
556                         foreigntable => 'aqbookfund',
557                         foreignkey => 'bookfundid',
558                         onUpdate => 'CASCADE',
559                         onDelete => 'CASCADE',
560                 },
561         ],
562         branchtransfers => [
563                 {       key => 'frombranch',
564                         foreigntable => 'branches',
565                         foreignkey => 'branchcode',
566                         onUpdate => 'CASCADE',
567                         onDelete => 'CASCADE',
568                 },
569                 {       key => 'tobranch',
570                         foreigntable => 'branches',
571                         foreignkey => 'branchcode',
572                         onUpdate => 'CASCADE',
573                         onDelete => 'CASCADE',
574                 },
575                 {       key => 'itemnumber',
576                         foreigntable => 'items',
577                         foreignkey => 'itemnumber',
578                         onUpdate => 'CASCADE',
579                         onDelete => 'CASCADE',
580                 },
581         ],
582         issuingrules => [
583                 {       key => 'categorycode',
584                         foreigntable => 'categories',
585                         foreignkey => 'categorycode',
586                         onUpdate => 'CASCADE',
587                         onDelete => 'CASCADE',
588                 },
589                 {       key => 'itemtype',
590                         foreigntable => 'itemtypes',
591                         foreignkey => 'itemtype',
592                         onUpdate => 'CASCADE',
593                         onDelete => 'CASCADE',
594                 },
595         ],
596         issues => [     # constraint is SET NULL : when a borrower or an item is deleted, we keep the issuing record
597         # for stat purposes
598                 {       key => 'borrowernumber',
599                         foreigntable => 'borrowers',
600                         foreignkey => 'borrowernumber',
601                         onUpdate => 'SET NULL',
602                         onDelete => 'SET NULL',
603                 },
604                 {       key => 'itemnumber',
605                         foreigntable => 'items',
606                         foreignkey => 'itemnumber',
607                         onUpdate => 'SET NULL',
608                         onDelete => 'SET NULL',
609                 },
610         ],
611         reserves => [
612                 {       key => 'borrowernumber',
613                         foreigntable => 'borrowers',
614                         foreignkey => 'borrowernumber',
615                         onUpdate => 'CASCADE',
616                         onDelete => 'CASCADE',
617                 },
618                 {       key => 'biblionumber',
619                         foreigntable => 'biblio',
620                         foreignkey => 'biblionumber',
621                         onUpdate => 'CASCADE',
622                         onDelete => 'CASCADE',
623                 },
624                 {       key => 'itemnumber',
625                         foreigntable => 'items',
626                         foreignkey => 'itemnumber',
627                         onUpdate => 'CASCADE',
628                         onDelete => 'CASCADE',
629                 },
630                 {       key => 'branchcode',
631                         foreigntable => 'branches',
632                         foreignkey => 'branchcode',
633                         onUpdate => 'CASCADE',
634                         onDelete => 'CASCADE',
635                 },
636         ],
637         borrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
638         # but prevent deleting a branch as soon as it has 1 borrower !
639                 {       key => 'categorycode',
640                         foreigntable => 'categories',
641                         foreignkey => 'categorycode',
642                         onUpdate => 'RESTRICT',
643                         onDelete => 'RESTRICT',
644                 },
645                 {       key => 'branchcode',
646                         foreigntable => 'branches',
647                         foreignkey => 'branchcode',
648                         onUpdate => 'RESTRICT',
649                         onDelete => 'RESTRICT',
650                 },
651         ],
652         accountlines => [
653                 {       key => 'borrowernumber',
654                         foreigntable => 'borrowers',
655                         foreignkey => 'borrowernumber',
656                         onUpdate => 'CASCADE',
657                         onDelete => 'CASCADE',
658                 },
659                 {       key => 'itemnumber',
660                         foreigntable => 'items',
661                         foreignkey => 'itemnumber',
662                         onUpdate => 'SET NULL',
663                         onDelete => 'SET NULL',
664                 },
665         ],
666         auth_tag_structure => [
667                 {       key => 'authtypecode',
668                         foreigntable => 'auth_types',
669                         foreignkey => 'authtypecode',
670                         onUpdate => 'CASCADE',
671                         onDelete => 'CASCADE',
672                 },
673         ],
674         # FIXME : don't constraint auth_*_table and auth_word, as they may be replaced by zebra
675 );
676
677 #-------------------
678 # Initialize
679
680 # Start checking
681
682 # Get version of MySQL database engine.
683 my $mysqlversion = `mysqld --version`;
684 $mysqlversion =~ /Ver (\S*) /;
685 $mysqlversion = $1;
686 if ( $mysqlversion ge '3.23' ) {
687     print "Could convert to MyISAM database tables...\n" unless $silent;
688 }
689
690 #---------------------------------
691 # Tables
692
693 # Collect all tables into a list
694 $sth = $dbh->prepare("show tables");
695 $sth->execute;
696 while ( my ($table) = $sth->fetchrow ) {
697     $existingtables{$table} = 1;
698 }
699
700
701 # Now add any missing tables
702 foreach $table ( keys %requiretables ) {
703     unless ( $existingtables{$table} ) {
704         print "Adding $table table...\n" unless $silent;
705         my $sth = $dbh->prepare("create table $table $requiretables{$table}");
706         $sth->execute;
707         if ( $sth->err ) {
708             print "Error : $sth->errstr \n";
709             $sth->finish;
710         }    # if error
711     }    # unless exists
712 }    # foreach
713
714 # now drop useless tables
715 foreach $table ( keys %dropable_table ) {
716         if ( $existingtables{$table} ) {
717                 print "Dropping unused table $table\n" if $debug and not $silent;
718                 $dbh->do("drop table $table");
719                 if ( $dbh->err ) {
720                         print "Error : $dbh->errstr \n";
721                 }
722         }
723 }
724
725 #---------------------------------
726 # Columns
727
728 foreach $table ( keys %requirefields ) {
729     print "Check table $table\n" if $debug and not $silent;
730     $sth = $dbh->prepare("show columns from $table");
731     $sth->execute();
732     undef %types;
733     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
734     {
735         $types{$column} = $type;
736     }    # while
737     foreach $column ( keys %{ $requirefields{$table} } ) {
738         print "  Check column $column  [$types{$column}]\n" if $debug and not $silent;
739         if ( !$types{$column} ) {
740
741             # column doesn't exist
742             print "Adding $column field to $table table...\n" unless $silent;
743             $query = "alter table $table
744                         add column $column " . $requirefields{$table}->{$column};
745             print "Execute: $query\n" if $debug;
746             my $sti = $dbh->prepare($query);
747             $sti->execute;
748             if ( $sti->err ) {
749                 print "**Error : $sti->errstr \n";
750                 $sti->finish;
751             }    # if error
752         }    # if column
753     }    # foreach column
754 }    # foreach table
755
756 foreach $table ( keys %fielddefinitions ) {
757         print "Check table $table\n" if $debug;
758         $sth = $dbh->prepare("show columns from $table");
759         $sth->execute();
760         my $definitions;
761         while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
762         {
763                 $definitions->{$column}->{type}    = $type;
764                 $definitions->{$column}->{null}    = $null;
765                 $definitions->{$column}->{null}    = 'NULL' if $null eq 'YES';
766                 $definitions->{$column}->{key}     = $key;
767                 $definitions->{$column}->{default} = $default;
768                 $definitions->{$column}->{extra}   = $extra;
769         }    # while
770         my $fieldrow = $fielddefinitions{$table};
771         foreach my $row (@$fieldrow) {
772                 my $field   = $row->{field};
773                 my $type    = $row->{type};
774                 my $null    = $row->{null};
775 #               $null    = 'YES' if $row->{null} eq 'NULL';
776                 my $key     = $row->{key};
777                 my $default = $row->{default};
778                 my $null    = $row->{null};
779 #               $default="''" unless $default;
780                 my $extra   = $row->{extra};
781                 my $def     = $definitions->{$field};
782
783                 unless ( $type eq $def->{type}
784                         && $null eq $def->{null}
785                         && $key eq $def->{key}
786                         && $extra eq $def->{extra} )
787                 {
788                         if ( $null eq '' ) {
789                                 $null = 'NOT NULL';
790                         }
791                         if ( $key eq 'PRI' ) {
792                                 $key = 'PRIMARY KEY';
793                         }
794                         unless ( $extra eq 'auto_increment' ) {
795                                 $extra = '';
796                         }
797
798                         # if it's a new column use "add", if it's an old one, use "change".
799                         my $action;
800                         if ($definitions->{$field}->{type}) {
801                                 $action="change $field"
802                         } else {
803                                 $action="add";
804                         }
805 # if it's a primary key, drop the previous pk, before altering the table
806                         my $sth;
807                         if ($key ne 'PRIMARY KEY') {
808                                 $sth =$dbh->prepare("alter table $table $action $field $type $null $key $extra default ?");
809                         } else {
810                                 $sth =$dbh->prepare("alter table $table drop primary key, $action $field $type $null $key $extra default ?");
811                         }
812                         $sth->execute($default);
813                         print "  Alter $field in $table\n" unless $silent;
814                 }
815         }
816 }
817
818
819 # Populate tables with required data
820
821
822 # synch table and deletedtable.
823 foreach my $table (('borrowers','items','biblio','biblioitems')) {
824         my %deletedborrowers;
825         print "synch'ing $table\n";
826         $sth = $dbh->prepare("show columns from deleted$table");
827         $sth->execute;
828         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
829                 $deletedborrowers{$column}=1;
830         }
831         $sth = $dbh->prepare("show columns from $table");
832         $sth->execute;
833         my $previous;
834         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
835                 unless ($deletedborrowers{$column}) {
836                         my $newcol="alter table deleted$table add $column $type";
837                         if ($null eq 'YES') {
838                                 $newcol .= " NULL ";
839                         } else {
840                                 $newcol .= " NOT NULL ";
841                         }
842                         $newcol .= "default $default" if $default;
843                         $newcol .= " after $previous" if $previous;
844                         $previous=$column;
845                         print "creating column $column\n";
846                         $dbh->do($newcol);
847                 }
848         }
849 }
850
851 foreach my $table ( keys %tabledata ) {
852     print "Checking for data required in table $table...\n" unless $silent;
853     my $tablerows = $tabledata{$table};
854     foreach my $row (@$tablerows) {
855         my $uniquefieldrequired = $row->{uniquefieldrequired};
856         my $uniquevalue         = $row->{$uniquefieldrequired};
857         my $forceupdate         = $row->{forceupdate};
858         my $sth                 =
859           $dbh->prepare(
860 "select $uniquefieldrequired from $table where $uniquefieldrequired=?"
861         );
862         $sth->execute($uniquevalue);
863                 if ($sth->rows) {
864                         foreach my $field (keys %$forceupdate) {
865                                 if ($forceupdate->{$field}) {
866                                         my $sth=$dbh->prepare("update systempreferences set $field=? where $uniquefieldrequired=?");
867                                         $sth->execute($row->{$field}, $uniquevalue);
868                                 }
869                 }
870                 } else {
871                         print "Adding row to $table: " unless $silent;
872                         my @values;
873                         my $fieldlist;
874                         my $placeholders;
875                         foreach my $field ( keys %$row ) {
876                                 next if $field eq 'uniquefieldrequired';
877                                 next if $field eq 'forceupdate';
878                                 my $value = $row->{$field};
879                                 push @values, $value;
880                                 print "  $field => $value" unless $silent;
881                                 $fieldlist .= "$field,";
882                                 $placeholders .= "?,";
883                         }
884                         print "\n" unless $silent;
885                         $fieldlist    =~ s/,$//;
886                         $placeholders =~ s/,$//;
887                         my $sth =
888                         $dbh->prepare(
889                                 "insert into $table ($fieldlist) values ($placeholders)");
890                         $sth->execute(@values);
891                 }
892         }
893 }
894
895 #
896 # check indexes and create them when needed
897 #
898 print "Checking for index required...\n" unless $silent;
899 foreach my $table ( keys %indexes ) {
900         #
901         # read all indexes from $table
902         #
903         $sth = $dbh->prepare("show index from $table");
904         $sth->execute;
905         my %existingindexes;
906         while ( my ( $table, $non_unique, $key_name, $Seq_in_index, $Column_name, $Collation, $cardinality, $sub_part, $Packed, $comment ) = $sth->fetchrow ) {
907                 $existingindexes{$key_name} = 1;
908         }
909         # read indexes to check
910         my $tablerows = $indexes{$table};
911         foreach my $row (@$tablerows) {
912                 my $key_name=$row->{indexname};
913                 if ($existingindexes{$key_name} eq 1) {
914 #                       print "$key_name existing";
915                 } else {
916                         print "Creating $key_name in $table\n";
917                         my $sql;
918                         if ($row->{indexname} eq 'PRIMARY') {
919                                 $sql = "alter table $table ADD PRIMARY KEY ($row->{content})";
920                         } else {
921                                 $sql = "alter table $table ADD INDEX $key_name ($row->{content}) $row->{type}";
922                         }
923                         $dbh->do($sql);
924             print "Error $sql : $dbh->err \n" if $dbh->err;
925                 }
926         }
927 }
928
929 #
930 # check foreign keys and create them when needed
931 #
932 print "Checking for foreign keys required...\n" unless $silent;
933 foreach my $table ( keys %foreign_keys ) {
934         #
935         # read all indexes from $table
936         #
937         $sth = $dbh->prepare("show table status like '$table'");
938         $sth->execute;
939         my $stat = $sth->fetchrow_hashref;
940         # read indexes to check
941         my $tablerows = $foreign_keys{$table};
942         foreach my $row (@$tablerows) {
943                 my $foreign_table=$row->{foreigntable};
944                 if ($stat->{'Comment'} =~/$foreign_table/) {
945 #                       print "$foreign_table existing\n";
946                 } else {
947                         print "Creating $foreign_table in $table\n";
948                         # first, drop any orphan value in child table
949                         if ($row->{onDelete} ne "RESTRICT") {
950                                 my $sql = "delete from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})";
951                                 $dbh->do($sql);
952                                 print "SQL ERROR: $sql : $dbh->err \n" if $dbh->err;
953                         }
954                         my $sql="alter table $table ADD FOREIGN KEY $row->{key} ($row->{key}) REFERENCES $row->{foreigntable} ($row->{foreignkey})";
955                         $sql .= " on update ".$row->{onUpdate} if $row->{onUpdate};
956                         $sql .= " on delete ".$row->{onDelete} if $row->{onDelete};
957                         $dbh->do($sql);
958                         if ($dbh->err) {
959                                 print "====================
960 An error occured during :
961 \t$sql
962 It probably means there is something wrong in your DB : a row ($table.$row->{key}) refers to a value in $row->{foreigntable}.$row->{foreignkey} that does not exist. solve the problem and run updater again (or just the previous SQL statement).
963 You can find those values with select
964 \t$table.* from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})
965 ====================\n
966 ";
967                         }
968                 }
969         }
970 }
971
972 #
973 # SPECIFIC STUFF
974 #
975 #
976 # create frameworkcode row in biblio table & fill it with marc_biblio.frameworkcode.
977 #
978
979 # 1st, get how many biblio we will have to do...
980 $sth = $dbh->prepare('select count(*) from marc_biblio');
981 $sth->execute;
982 my ($totaltodo) = $sth->fetchrow;
983
984 $sth = $dbh->prepare("show columns from biblio");
985 $sth->execute();
986 my $definitions;
987 my $bibliofwexist=0;
988 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
989         $bibliofwexist=1 if $column eq 'frameworkcode';
990 }
991 unless ($bibliofwexist) {
992         print "moving biblioframework to biblio table\n";
993         $dbh->do('ALTER TABLE `biblio` ADD `frameworkcode` VARCHAR( 4 ) NOT NULL AFTER `biblionumber`');
994         $sth = $dbh->prepare('select biblionumber,frameworkcode from marc_biblio');
995         $sth->execute;
996         my $sth_update = $dbh->prepare('update biblio set frameworkcode=? where biblionumber=?');
997         my $totaldone=0;
998         while (my ($biblionumber,$frameworkcode) = $sth->fetchrow) {
999                 $sth_update->execute($frameworkcode,$biblionumber);
1000                 $totaldone++;
1001                 print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
1002         }
1003         print "\rdone\n";
1004 }
1005
1006 #
1007 # moving MARC data from marc_subfield_table to biblioitems.marc
1008 #
1009 $sth = $dbh->prepare("show columns from biblioitems");
1010 $sth->execute();
1011 my $definitions;
1012 my $marcdone=0;
1013 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
1014         $marcdone=1 if ($type eq 'blob' && $column eq 'marc') ;
1015 }
1016 unless ($marcdone) {
1017         print "moving MARC record to biblioitems table\n";
1018         # changing marc field type
1019         $dbh->do('ALTER TABLE `biblioitems` CHANGE `marc` `marc` BLOB NULL DEFAULT NULL ');
1020         # adding marc xml, just for convenience
1021         $dbh->do('ALTER TABLE `biblioitems` ADD `marcxml` TEXT NOT NULL');
1022         # moving data from marc_subfield_value to biblio
1023         $sth = $dbh->prepare('select bibid,biblionumber from marc_biblio');
1024         $sth->execute;
1025         my $sth_update = $dbh->prepare('update biblioitems set marc=?, marcxml=? where biblionumber=?');
1026         my $totaldone=0;
1027         while (my ($bibid,$biblionumber) = $sth->fetchrow) {
1028                 my $record = MARCgetbiblio($dbh,$bibid);
1029                 $sth_update->execute($record->as_usmarc(),$record->as_xml(),$biblionumber);
1030                 $totaldone++;
1031                 print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
1032         }
1033         print "\rdone\n";
1034 }
1035
1036 # MOVE all tables TO UTF-8 and innoDB
1037 $sth = $dbh->prepare("show table status");
1038 $sth->execute;
1039 while ( my $table = $sth->fetchrow_hashref ) {
1040         if ($table->{Engine} ne 'InnoDB') {
1041                 $dbh->do("ALTER TABLE $table->{Name} TYPE = innodb");
1042                 print "moving $table->{Name} to InnoDB\n";
1043         }
1044         unless ($table->{Collation} =~ /^utf8/) {
1045                 $dbh->do("ALTER TABLE $table->{Name} DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci");
1046                 # FIXME : maybe a ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8 would be better, def char set seems to work fine. If any problem encountered, let's try with convert !
1047                 print "moving $table->{Name} to utf8\n";
1048         } else {
1049         }
1050 }
1051
1052 # at last, remove useless fields
1053 foreach $table ( keys %uselessfields ) {
1054         my @fields = split /,/,$uselessfields{$table};
1055         my $fields;
1056         my $exists;
1057         foreach my $fieldtodrop (@fields) {
1058                 $fieldtodrop =~ s/\t//g;
1059                 $fieldtodrop =~ s/\n//g;
1060                 $exists =0;
1061                 $sth = $dbh->prepare("show columns from $table");
1062                 $sth->execute;
1063                 while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1064                 {
1065                         $exists =1 if ($column eq $fieldtodrop);
1066                 }
1067                 if ($exists) {
1068                         print "deleting $fieldtodrop field in $table...\n" unless $silent;
1069                         my $sth = $dbh->prepare("alter table $table drop $fieldtodrop");
1070                         $sth->execute;
1071                 }
1072         }
1073 }    # foreach
1074
1075
1076 $sth->finish;
1077
1078 #
1079 # those 2 subs are a copy of Biblio.pm, version 2.2.4
1080 # they are useful only once, for moving from 2.2 to 3.0
1081 # the MARCgetbiblio & MARCgetitem subs in Biblio.pm
1082 # are still here, but uses other tables
1083 # (the ones that are filled by updatedatabase !)
1084 #
1085 sub MARCgetbiblio {
1086
1087     # Returns MARC::Record of the biblio passed in parameter.
1088     my ( $dbh, $bibid ) = @_;
1089     my $record = MARC::Record->new();
1090 #       warn "". $bidid;
1091
1092     my $sth =
1093       $dbh->prepare(
1094 "select bibid,subfieldid,tag,tagorder,tag_indicator,subfieldcode,subfieldorder,subfieldvalue,valuebloblink
1095                                  from marc_subfield_table
1096                                  where bibid=? order by tag,tagorder,subfieldorder
1097                          "
1098     );
1099     my $sth2 =
1100       $dbh->prepare(
1101         "select subfieldvalue from marc_blob_subfield where blobidlink=?");
1102     $sth->execute($bibid);
1103     my $prevtagorder = 1;
1104     my $prevtag      = 'XXX';
1105     my $previndicator;
1106     my $field;        # for >=10 tags
1107     my $prevvalue;    # for <10 tags
1108     while ( my $row = $sth->fetchrow_hashref ) {
1109
1110         if ( $row->{'valuebloblink'} ) {    #---- search blob if there is one
1111             $sth2->execute( $row->{'valuebloblink'} );
1112             my $row2 = $sth2->fetchrow_hashref;
1113             $sth2->finish;
1114             $row->{'subfieldvalue'} = $row2->{'subfieldvalue'};
1115         }
1116         if ( $row->{tagorder} ne $prevtagorder || $row->{tag} ne $prevtag ) {
1117             $previndicator .= "  ";
1118             if ( $prevtag < 10 ) {
1119                                 if ($prevtag ne '000') {
1120                         $record->add_fields( ( sprintf "%03s", $prevtag ), $prevvalue ) unless $prevtag eq "XXX";    # ignore the 1st loop
1121                                 } else {
1122                                         $record->leader(sprintf("%24s",$prevvalue));
1123                                 }
1124             }
1125             else {
1126                 $record->add_fields($field) unless $prevtag eq "XXX";
1127             }
1128             undef $field;
1129             $prevtagorder  = $row->{tagorder};
1130             $prevtag       = $row->{tag};
1131             $previndicator = $row->{tag_indicator};
1132             if ( $row->{tag} < 10 ) {
1133                 $prevvalue = $row->{subfieldvalue};
1134             }
1135             else {
1136                 $field = MARC::Field->new(
1137                     ( sprintf "%03s", $prevtag ),
1138                     substr( $row->{tag_indicator} . '  ', 0, 1 ),
1139                     substr( $row->{tag_indicator} . '  ', 1, 1 ),
1140                     $row->{'subfieldcode'},
1141                     $row->{'subfieldvalue'}
1142                 );
1143             }
1144         }
1145         else {
1146             if ( $row->{tag} < 10 ) {
1147                 $record->add_fields( ( sprintf "%03s", $row->{tag} ),
1148                     $row->{'subfieldvalue'} );
1149             }
1150             else {
1151                 $field->add_subfields( $row->{'subfieldcode'},
1152                     $row->{'subfieldvalue'} );
1153             }
1154             $prevtag       = $row->{tag};
1155             $previndicator = $row->{tag_indicator};
1156         }
1157     }
1158
1159     # the last has not been included inside the loop... do it now !
1160     if ( $prevtag ne "XXX" )
1161     { # check that we have found something. Otherwise, prevtag is still XXX and we
1162          # must return an empty record, not make MARC::Record fail because we try to
1163          # create a record with XXX as field :-(
1164         if ( $prevtag < 10 ) {
1165             $record->add_fields( $prevtag, $prevvalue );
1166         }
1167         else {
1168
1169             #           my $field = MARC::Field->new( $prevtag, "", "", %subfieldlist);
1170             $record->add_fields($field);
1171         }
1172     }
1173     return $record;
1174 }
1175
1176 sub MARCgetitem {
1177
1178     # Returns MARC::Record of the biblio passed in parameter.
1179     my ( $dbh, $bibid, $itemnumber ) = @_;
1180     my $record = MARC::Record->new();
1181
1182     # search MARC tagorder
1183     my $sth2 =
1184       $dbh->prepare(
1185 "select tagorder from marc_subfield_table,marc_subfield_structure where marc_subfield_table.tag=marc_subfield_structure.tagfield and marc_subfield_table.subfieldcode=marc_subfield_structure.tagsubfield and bibid=? and kohafield='items.itemnumber' and subfieldvalue=?"
1186     );
1187     $sth2->execute( $bibid, $itemnumber );
1188     my ($tagorder) = $sth2->fetchrow_array();
1189
1190     #---- TODO : the leader is missing
1191     my $sth =
1192       $dbh->prepare(
1193 "select bibid,subfieldid,tag,tagorder,tag_indicator,subfieldcode,subfieldorder,subfieldvalue,valuebloblink
1194                                  from marc_subfield_table
1195                                  where bibid=? and tagorder=? order by subfieldcode,subfieldorder
1196                          "
1197     );
1198     $sth2 =
1199       $dbh->prepare(
1200         "select subfieldvalue from marc_blob_subfield where blobidlink=?");
1201     $sth->execute( $bibid, $tagorder );
1202     while ( my $row = $sth->fetchrow_hashref ) {
1203         if ( $row->{'valuebloblink'} ) {    #---- search blob if there is one
1204             $sth2->execute( $row->{'valuebloblink'} );
1205             my $row2 = $sth2->fetchrow_hashref;
1206             $sth2->finish;
1207             $row->{'subfieldvalue'} = $row2->{'subfieldvalue'};
1208         }
1209         if ( $record->field( $row->{'tag'} ) ) {
1210             my $field;
1211
1212 #--- this test must stay as this, because of strange behaviour of mySQL/Perl DBI with char var containing a number...
1213             #--- sometimes, eliminates 0 at beginning, sometimes no ;-\\\
1214             if ( length( $row->{'tag'} ) < 3 ) {
1215                 $row->{'tag'} = "0" . $row->{'tag'};
1216             }
1217             $field = $record->field( $row->{'tag'} );
1218             if ($field) {
1219                 my $x =
1220                   $field->add_subfields( $row->{'subfieldcode'},
1221                     $row->{'subfieldvalue'} );
1222                 $record->delete_field($field);
1223                 $record->add_fields($field);
1224             }
1225         }
1226         else {
1227             if ( length( $row->{'tag'} ) < 3 ) {
1228                 $row->{'tag'} = "0" . $row->{'tag'};
1229             }
1230             my $temp =
1231               MARC::Field->new( $row->{'tag'}, " ", " ",
1232                 $row->{'subfieldcode'} => $row->{'subfieldvalue'} );
1233             $record->add_fields($temp);
1234         }
1235
1236     }
1237     return $record;
1238 }
1239
1240
1241 exit;
1242
1243 # $Log$
1244 # Revision 1.129  2006/02/27 18:19:33  hdl
1245 # New table used in overduerules.pl tools page.
1246 #
1247 # Revision 1.128  2006/01/25 15:16:06  tipaul
1248 # updating DB :
1249 # * removing useless tables
1250 # * adding useful indexes
1251 # * altering some columns definitions
1252 # * The goal being to have updater working fine for foreign keys.
1253 #
1254 # For me it's done, let me know if it works for you. You can see an updated schema of the DB (with constraints) on the wiki
1255 #
1256 # Revision 1.127  2006/01/24 17:57:17  tipaul
1257 # DB improvements : adding foreign keys on some tables. partial stuff done.
1258 #
1259 # Revision 1.126  2006/01/06 16:39:42  tipaul
1260 # synch'ing head and rel_2_2 (from 2.2.5, including npl templates)
1261 # Seems not to break too many things, but i'm probably wrong here.
1262 # at least, new features/bugfixes from 2.2.5 are here (tested on some features on my head local copy)
1263 #
1264 # - removing useless directories (koha-html and koha-plucene)
1265 #
1266 # Revision 1.125  2006/01/04 15:54:55  tipaul
1267 # utf8 is a : go for beta test in HEAD.
1268 # some explanations :
1269 # - updater/updatedatabase => will transform all tables in innoDB (not related to utf8, just to warn you) AND collate them in utf8 / utf8_general_ci. The SQL command is : ALTER TABLE tablename DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci.
1270 # - *-top.inc will show the pages in utf8
1271 # - THE HARD THING : for me, mysql-client and mysql-server were set up to communicate in iso8859-1, whatever the mysql collation ! Thus, pages were improperly shown, as datas were transmitted in iso8859-1 format ! After a full day of investigation, someone on usenet pointed "set NAMES 'utf8'" to explain that I wanted utf8. I could put this in my.cnf, but if I do that, ALL databases will "speak" in utf8, that's not what we want. Thus, I added a line in Context.pm : everytime a DB handle is opened, the communication is set to utf8.
1272 # - using marcxml field and no more the iso2709 raw marc biblioitems.marc field.
1273 #
1274 # Revision 1.124  2005/10/27 12:09:05  tipaul
1275 # new features for serial module :
1276 # - the last 5 issues are now shown, and their status can be changed (but not reverted to "waited", as there can be only one "waited")
1277 # - the library can create a "distribution list". this paper contains a list of borrowers (selected from the borrower list, or manually entered), and print it for a given issue. once printed, the sheet can be put on the issue and distributed to every reader on the list (one by one).
1278 #
1279 # Revision 1.123  2005/10/26 09:13:37  tipaul
1280 # big commit, still breaking things...
1281 #
1282 # * synch with rel_2_2. Probably the last non manual synch, as rel_2_2 should not be modified deeply.
1283 # * code cleaning (cleaning warnings from perl -w) continued
1284 #
1285 # Revision 1.122  2005/09/02 14:18:38  tipaul
1286 # new feature : image for itemtypes.
1287 #
1288 # * run updater/updatedatabase to create imageurl field in itemtypes.
1289 # * go to Koha >> parameters >> itemtypes >> modify (or add) an itemtype. You will see around 20 nice images to choose between (thanks to owen). If you prefer your own image, you also can type a complete url (http://www.myserver.lib/path/to/my/image.gif)
1290 # * go to OPAC, and search something. In the result list, you now have the picture instead of the text itemtype.
1291 #
1292 # Revision 1.121  2005/08/24 08:49:03  hdl
1293 # Adding a note field in serial table.
1294 # This will allow librarian to mention a note on a peculiar waiting serial number.
1295 #
1296 # Revision 1.120  2005/08/09 14:10:32  tipaul
1297 # 1st commit to go to zebra.
1298 # don't update your cvs if you want to have a working head...
1299 #
1300 # this commit contains :
1301 # * updater/updatedatabase : get rid with marc_* tables, but DON'T remove them. As a lot of things uses them, it would not be a good idea for instance to drop them. If you really want to play, you can rename them to test head without them but being still able to reintroduce them...
1302 # * Biblio.pm : modify MARCgetbiblio to find the raw marc record in biblioitems.marc field, not from marc_subfield_table, modify MARCfindframeworkcode to find frameworkcode in biblio.frameworkcode, modify some other subs to use biblio.biblionumber & get rid of bibid.
1303 # * other files : get rid of bibid and use biblionumber instead.
1304 #
1305 # What is broken :
1306 # * does not do anything on zebra yet.
1307 # * if you rename marc_subfield_table, you can't search anymore.
1308 # * you can view a biblio & bibliodetails, go to MARC editor, but NOT save any modif.
1309 # * don't try to add a biblio, it would add data poorly... (don't try to delete either, it may work, but that would be a surprise ;-) )
1310 #
1311 # IMPORTANT NOTE : you need MARC::XML package (http://search.cpan.org/~esummers/MARC-XML-0.7/lib/MARC/File/XML.pm), that requires a recent version of MARC::Record
1312 # Updatedatabase stores the iso2709 data in biblioitems.marc field & an xml version in biblioitems.marcxml Not sure we will keep it when releasing the stable version, but I think it's a good idea to have something readable in sql, at least for development stage.
1313 #
1314 # Revision 1.119  2005/08/04 16:07:58  tipaul
1315 # Synch really broke this script...
1316 #
1317 # Revision 1.118  2005/08/04 16:02:55  tipaul
1318 # oops... error in synch between 2.2 and head
1319 #
1320 # Revision 1.117  2005/08/04 14:24:39  tipaul
1321 # synch'ing 2.2 and head
1322 #
1323 # Revision 1.116  2005/08/04 08:55:54  tipaul
1324 # Letters / alert system, continuing...
1325 #
1326 # * adding a package Letters.pm, that manages Letters & alerts.
1327 # * 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)
1328 # * 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)
1329 # * 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.
1330 #
1331 # Note that the system should be generic enough to manage any type of alert.
1332 # 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 ;-) )
1333 #
1334 # Revision 1.115  2005/08/02 16:15:34  tipaul
1335 # adding 2 fields to letter system :
1336 # * module (acquisition, catalogue...) : it will be usefull to show the librarian only letters he may be interested by.
1337 # * title, that will be used as mail subject.
1338 #
1339 # Revision 1.114  2005/07/28 15:10:13  tipaul
1340 # 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.
1341 # the letter table contains 3 fields :
1342 # * code => the code of the letter
1343 # * name => the complete name of the letter
1344 # * content => the complete text. It's a TEXT field type, so has no limits.
1345 #
1346 # My next goal now is to work on point 2-I "serial issue alert"
1347 # 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.
1348 # (see mail on koha-devel, 2005/04/07)
1349 #
1350 # The "serial issue alert" will be the 1st to use this letter system that probably needs some tweaking ;-)
1351 #
1352 # Once it will be stabilised default letters (in any languages) could be added during installer to help the library begin with this new feature.
1353 #
1354 # Revision 1.113  2005/07/28 08:38:41  tipaul
1355 # 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 :
1356 # * ReturnBeforeExpiry = yes => return date can't be after expiry date
1357 # * ReturnBeforeExpiry = no  => return date can be after expiry date
1358 #
1359 # Revision 1.112  2005/07/26 08:19:47  hdl
1360 # Adding IndependantBranches System preference variable in order to manage Branch independancy.
1361 #
1362 # Revision 1.111  2005/07/25 15:35:38  tipaul
1363 # we have decided that moving to Koha 3.0 requires being already in Koha 2.2.x
1364 # So, the updatedatabase script can highly be cleaned (90% removed).
1365 # Let's play with the new Koha DB structure now ;-)
1366 #