the new "includes" features, for personalized templates. Look at koha-devel, i'll...
[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 ( BinaryEncoding => 'utf8' );
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         opac_news => "(
97                                 `idnew` int(10) unsigned NOT NULL auto_increment,
98                                 `title` varchar(250) NOT NULL default '',
99                                 `new` text NOT NULL,
100                                 `lang` varchar(4) NOT NULL default '',
101                                 `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
102                                 PRIMARY KEY  (`idnew`)
103                                 )",
104         repeatable_holidays => "(
105                                 `id` int(11) NOT NULL auto_increment,
106                                 `branchcode` varchar(4) NOT NULL default '',
107                                 `weekday` smallint(6) default NULL,
108                                 `day` smallint(6) default NULL,
109                                 `month` smallint(6) default NULL,
110                                 `title` varchar(50) NOT NULL default '',
111                                 `description` text NOT NULL,
112                                 PRIMARY KEY  (`id`)
113                                 )",
114         special_holidays => "(
115                                 `id` int(11) NOT NULL auto_increment,
116                                 `branchcode` varchar(4) NOT NULL default '',
117                                 `day` smallint(6) NOT NULL default '0',
118                                 `month` smallint(6) NOT NULL default '0',
119                                 `year` smallint(6) NOT NULL default '0',
120                                 `isexception` smallint(1) NOT NULL default '1',
121                                 `title` varchar(50) NOT NULL default '',
122                                 `description` text NOT NULL,
123                                 PRIMARY KEY  (`id`)
124                                 )",
125         overduerules    =>"(`branchcode` varchar(255) NOT NULL default '',
126                                         `categorycode` char(2) NOT NULL default '',
127                                         `delay1` int(4) default '0',
128                                         `letter1` varchar(20) default NULL,
129                                         `debarred1` char(1) default '0',
130                                         `delay2` int(4) default '0',
131                                         `debarred2` char(1) default '0',
132                                         `letter2` varchar(20) default NULL,
133                                         `delay3` int(4) default '0',
134                                         `letter3` varchar(20) default NULL,
135                                         `debarred3` int(1) default '0',
136                                         PRIMARY KEY  (`branchcode`,`categorycode`)
137                                         )",
138         cities                  => "(`cityid` int auto_increment,
139                                                 `city_name` char(100) NOT NULL,
140                                                 `city_zipcode` char(20),
141                                                 PRIMARY KEY (`cityid`)
142                                         )",
143         roadtype                        => "(`roadtypeid` int auto_increment,
144                                                 `road_type` char(100) NOT NULL,
145                                                 PRIMARY KEY (`roadtypeid`)
146                                         )",
147
148         labels                     => "(
149                                 labelid int(11) NOT NULL auto_increment,
150                                 itemnumber varchar(100) NOT NULL default '',
151                                 timestamp timestamp(14) NOT NULL,
152                                 PRIMARY KEY  (labelid)
153                                 )",
154
155         labels_conf                => "(
156                                 id int(4) NOT NULL auto_increment,
157                                 barcodetype char(100) default '',
158                                 title tinyint(1) default '0',
159                                 isbn tinyint(1) default '0',
160                                 itemtype tinyint(1) default '0',
161                                 barcode tinyint(1) default '0',
162                                 dewey tinyint(1) default '0',
163                                 class tinyint(1) default '0',
164                                 author tinyint(1) default '0',
165                                 papertype char(100) default '',
166                                 startrow int(2) default NULL,
167                                 PRIMARY KEY  (id)
168                                 )",
169        reviews                  => "(
170                                 reviewid integer NOT NULL auto_increment,
171                                 borrowernumber integer,
172                                 biblionumber integer,
173                                 review text,
174                                 approved tinyint,
175                                 datereviewed datetime,
176                                 PRIMARY KEY (reviewid)
177                                 )",
178        borrowers_to_borrowers  => "(
179                                 borrower1 integer,
180                                 borrower2 integer
181                                 )",
182
183 );
184
185 my %requirefields = (
186         subscription => { 'letter' => 'char(20) NULL', 'distributedto' => 'text NULL'},
187         itemtypes => { 'imageurl' => 'char(200) NULL'},
188         aqbookfund => { 'branchcode' => 'varchar(4) NULL'},
189         aqbudget => { 'branchcode' => 'varchar(4) NULL'},
190         auth_header => { 'marc' => 'BLOB NOT NULL', 'linkid' => 'BIGINT(20) NULL'},
191         auth_subfield_structure =>{ 'hidden' => 'TINYINT(3) NOT NULL UNSIGNED ZEROFILL', 'kohafield' => 'VARCHAR(45) NOT NULL', 'linkid' =>  'TINYINT(1) NOT NULL UNSIGNED', 'isurl' => 'TINYINT(1) UNSIGNED'},
192         statistics => { 'associatedborrower' => 'integer'},
193 #    tablename        => { 'field' => 'fieldtype' },
194 );
195
196 my %dropable_table = (
197 # tablename => 'tablename',
198 );
199
200 my %uselessfields = (
201 # tablename => "field1,field2",
202         borrowers => "suburb,altstreetaddress,altsuburb,altcity,studentnumber,school,area,preferredcont,altcp",
203         deletedborrowers=> "suburb,altstreetaddress,altsuburb,altcity,studentnumber,school,area,preferredcont,altcp",
204         );
205 # the other hash contains other actions that can't be done elsewhere. they are done
206 # either BEFORE of AFTER everything else, depending on "when" entry (default => AFTER)
207
208 # The tabledata hash contains data that should be in the tables.
209 # The uniquefieldrequired hash entry is used to determine which (if any) fields
210 # must not exist in the table for this row to be inserted.  If the
211 # uniquefieldrequired entry is already in the table, the existing data is not
212 # modified, unless the forceupdate hash entry is also set.  Fields in the
213 # anonymous "forceupdate" hash will be forced to be updated to the default
214 # values given in the %tabledata hash.
215
216 my %tabledata = (
217 # tablename => [
218 #       {       uniquefielrequired => 'fieldname', # the primary key in the table
219 #               fieldname => fieldvalue,
220 #               fieldname2 => fieldvalue2,
221 #       },
222 # ],
223     systempreferences => [
224         {
225             uniquefieldrequired => 'variable',
226             variable            => 'useDaysMode',
227             value               => 'Calendar',
228             forceupdate         => { 'explanation' => 1,
229                                      'type' => 1},
230             explanation                 => 'How to calculate return dates : Calendar means holidays will be controled, Days means the return date don\'t depend on holidays',
231                 type            => 'Choice',
232                 options         => 'Calendar|Days'
233         },
234         {
235             uniquefieldrequired => 'variable',
236             variable            => 'borrowerMandatoryField',
237             value               => 'zipcode|surname',
238             forceupdate         => { 'explanation' => 1,
239                                      'type' => 1},
240             explanation         => 'List all mandatory fields for borrowers',
241             type                => 'free',
242         },
243         {
244             uniquefieldrequired => 'variable',
245             variable            => 'borrowerRelationship',
246             value               => 'father|mother,grand-mother',
247             forceupdate         => { 'explanation' => 1,
248                                      'type' => 1},
249             explanation         => 'The relationships between a guarantor & a guarantee (separated by | or ,)',
250             type                => 'free',
251         },
252         {
253             uniquefieldrequired => 'variable',
254             variable            => 'ReservesMaxPickUpDelay',
255             value               => '10',
256             forceupdate         => { 'explanation' => 1,
257                                      'type' => 1},
258             explanation         => 'Maximum delay to pick up a reserved document',
259             type                => 'free',
260         },
261         {
262             uniquefieldrequired => 'variable',
263             variable            => 'TransfersMaxDaysWarning',
264             value               => '3',
265             forceupdate         => { 'explanation' => 1,
266                                      'type' => 1},
267             explanation         => 'Max delay before considering the transfer has potentialy a problem',
268             type                => 'free',
269         },
270         {
271             uniquefieldrequired => 'variable',
272             variable            => 'memberofinstitution',
273             value               => '0',
274             forceupdate         => { 'explanation' => 1,
275                                      'type' => 1},
276             explanation         => 'Are your patrons members of institutions',
277             type                => 'YesNo',
278         },
279         {
280             uniquefieldrequired => 'variable',
281             variable            => 'ReadingHistory',
282             value               => '0',
283             forceupdate         => { 'explanation' => 1,
284                                      'type' => 1},
285             explanation         => 'Allow reading record info retrievable from issues and oldissues tables',
286             type                => 'YesNo',
287         },
288         {
289             uniquefieldrequired => 'variable',
290             variable            => 'IssuingInProcess',
291             value               => '0',
292             forceupdate         => { 'explanation' => 1,
293                                      'type' => 1},
294             explanation         => 'Allow no debt alert if the patron is issuing item that accumulate debt',
295             type                => 'YesNo',
296         },
297         {
298             uniquefieldrequired => 'variable',
299             variable            => 'AutomaticItemReturn',
300             value               => '1',
301             forceupdate         => { 'explanation' => 1,
302                                      'type' => 1},
303             explanation         => 'This Variable allow or not to return automaticly to his homebranch',
304             type                => 'YesNo',
305         },
306         {
307             uniquefieldrequired => 'variable',
308             variable            => 'reviewson',
309             value               => '0',
310             forceupdate         => { 'explanation' => 1,
311                                      'type' => 1},
312             explanation         => 'Allows patrons to submit reviews from the opac',
313             type                => 'YesNo',
314         },
315         {
316             uniquefieldrequired => 'variable',
317             variable            => 'intranet_includes',
318             value               => 'includes',
319             forceupdate         => { 'explanation' => 1,
320                                      'type' => 1},
321             explanation         => 'The includes directory you want for specific look of Koha (includes or includes_npl for example)',
322             type                => 'Free',
323         },
324     ],
325
326 );
327
328 my %fielddefinitions = (
329 # fieldname => [
330 #       {                 field => 'fieldname',
331 #             type    => 'fieldtype',
332 #             null    => '',
333 #             key     => '',
334 #             default => ''
335 #         },
336 #     ],
337         serial => [
338         {
339             field   => 'notes',
340             type    => 'TEXT',
341             null    => 'NULL',
342             key     => '',
343             default => '',
344             extra   => ''
345         },
346     ],
347         aqbasket =>  [
348                 {
349                         field   => 'booksellerid',
350                         type    => 'int(11)',
351                         null    => 'NOT NULL',
352                         key             => '',
353                         default => '1',
354                         extra   => '',
355                 },
356         ],
357         aqbooksellers =>  [
358                 {
359                         field   => 'listprice',
360                         type    => 'varchar(10)',
361                         null    => 'NULL',
362                         key             => '',
363                         default => '',
364                         extra   => '',
365                 },
366                 {
367                         field   => 'invoiceprice',
368                         type    => 'varchar(10)',
369                         null    => 'NULL',
370                         key             => '',
371                         default => '',
372                         extra   => '',
373                 },
374         ],
375         issues =>  [
376                 {
377                         field   => 'borrowernumber',
378                         type    => 'int(11)',
379                         null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
380                         key             => '',
381                         default => '',
382                         extra   => '',
383                 },
384                 {
385                         field   => 'itemnumber',
386                         type    => 'int(11)',
387                         null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
388                         key             => '',
389                         default => '',
390                         extra   => '',
391                 },
392         ],
393         borrowers => [
394                 {       field => 'firstname',
395                         type => 'text',
396                         null => 'NULL',
397                  },
398                 {       field => 'initials',
399                         type => 'text',
400                         null => 'NULL',
401                  },
402                 {       field => 'B_email',
403                         type => 'text',
404                         null => 'NULL',
405                         after => 'B_zipcode',
406                  },
407                  {
408                         field => 'streetnumber', # street number (hidden if streettable table is empty)
409                         type => 'char(10)',
410                         null => 'NULL',
411                         after => 'initials',
412                 },
413                 {
414                         field => 'streettype', # street table, list builded from a system table
415                         type => 'char(50)',
416                         null => 'NULL',
417                         after => 'streetnumber',
418                 },
419                 {       field => 'phone',
420                         type => 'text',
421                         null => 'NULL',
422                  },                             
423                 {
424                         field => 'B_streetnumber', # street number (hidden if streettable table is empty)
425                         type => 'char(10)',
426                         null => 'NULL',
427                         after => 'fax',
428                 },
429                 {
430                         field => 'B_streettype', # street table, list builded from a system table
431                         type => 'char(50)',
432                         null => 'NULL',
433                         after => 'B_streetnumber',
434                 },
435                 {
436                         field => 'phonepro',
437                         type => 'text',
438                         null => 'NULL',
439                         after => 'fax',
440                 },
441                 {
442                         field => 'address2', # complement address
443                         type => 'text',
444                         null => 'NULL',
445                         after => 'address',
446                 },
447                 {
448                         field => 'emailpro',
449                         type => 'text',
450                         null => 'NULL',
451                         after => 'fax',
452                 },
453                 {
454                         field => 'contactfirstname', # contact's firstname
455                         type => 'text',
456                         null => 'NULL',
457                         after => 'contactname',
458                 },
459                 {
460                         field => 'contacttitle', # contact's title
461                         type => 'text',
462                         null => 'NULL',
463                         after => 'contactfirstname',
464                 },
465         ],
466         
467         deletedborrowers => [
468                 {       field => 'firstname',
469                         type => 'text',
470                         null => 'NULL',
471                  },
472                 {       field => 'initials',
473                         type => 'text',
474                         null => 'NULL',
475                  },
476                 {       field => 'B_email',
477                         type => 'text',
478                         null => 'NULL',
479                         after => 'B_zipcode',
480                  },
481                  {
482                         field => 'streetnumber', # street number (hidden if streettable table is empty)
483                         type => 'char(10)',
484                         null => 'NULL',
485                         after => 'initials',
486                 },
487                 {
488                         field => 'streettype', # street table, list builded from a system table
489                         type => 'char(50)',
490                         null => 'NULL',
491                         after => 'streetnumber',
492                 },
493                 {       field => 'phone',
494                         type => 'text',
495                         null => 'NULL',
496                  },             
497                  {
498                         field => 'B_streetnumber', # street number (hidden if streettable table is empty)
499                         type => 'char(10)',
500                         null => 'NULL',
501                         after => 'fax',
502                 },
503                 {
504                         field => 'B_streettype', # street table, list builded from a system table
505                         type => 'char(50)',
506                         null => 'NULL',
507                         after => 'B_streetnumber',
508                 },
509                 {
510                         field => 'phonepro',
511                         type => 'text',
512                         null => 'NULL',
513                         after => 'fax',
514                 },
515                 {
516                         field => 'address2', # complement address
517                         type => 'text',
518                         null => 'NULL',
519                         after => 'address',
520                 },
521                 {
522                         field => 'emailpro',
523                         type => 'text',
524                         null => 'NULL',
525                         after => 'fax',
526                 },
527                 {
528                         field => 'contactfirstname', # contact's firstname
529                         type => 'text',
530                         null => 'NULL',
531                         after => 'contactname',
532                 },
533                 {
534                         field => 'contacttitle', # contact's title
535                         type => 'text',
536                         null => 'NULL',
537                         after => 'contactfirstname',
538                 },
539         ],
540         
541         branches =>  [
542                 {
543                         field   => 'branchip',
544                         type    => 'varchar(15)',
545                         null    => 'NULL',
546                         key             => '',
547                         default => '',
548                         extra   => '',
549                 },
550                 {
551                         field   => 'branchprinter',
552                         type    => 'varchar(100)',
553                         null    => 'NULL',
554                         key             => '',
555                         default => '',
556                         extra   => '',
557                 },
558         ],
559         categories =>  [
560                 {
561                         field   => 'category_type',
562                         type    => 'char(1)',
563                         null    => 'NOT NULL',
564                         key             => '',
565                         default => 'A',
566                         extra   => '',
567                 },
568         ],
569         reserves =>  [
570                 {
571                         field   => 'waitingdate',
572                         type    => 'date',
573                         null    => 'NULL',
574                         key             => '',
575                         default => '',
576                         extra   => '',
577                 },
578         ],
579 );
580
581 my %indexes = (
582 #       table => [
583 #               {       indexname => 'index detail'
584 #               }
585 #       ],
586         shelfcontents => [
587                 {       indexname => 'shelfnumber',
588                         content => 'shelfnumber',
589                 },
590                 {       indexname => 'itemnumber',
591                         content => 'itemnumber',
592                 }
593         ],
594         bibliosubject => [
595                 {       indexname => 'biblionumber',
596                         content => 'biblionumber',
597                 }
598         ],
599         items => [
600                 {       indexname => 'homebranch',
601                         content => 'homebranch',
602                 },
603                 {       indexname => 'holdingbranch',
604                         content => 'holdingbranch',
605                 }
606         ],
607         aqbooksellers => [
608                 {       indexname => 'PRIMARY',
609                         content => 'id',
610                         type => 'PRIMARY',
611                 }
612         ],
613         aqbasket => [
614                 {       indexname => 'booksellerid',
615                         content => 'booksellerid',
616                 },
617         ],
618         aqorders => [
619                 {       indexname => 'basketno',
620                         content => 'basketno',
621                 },
622         ],
623         aqorderbreakdown => [
624                 {       indexname => 'ordernumber',
625                         content => 'ordernumber',
626                 },
627                 {       indexname => 'bookfundid',
628                         content => 'bookfundid',
629                 },
630         ],
631         currency => [
632                 {       indexname => 'PRIMARY',
633                         content => 'currency',
634                         type => 'PRIMARY',
635                 }
636         ],
637 );
638
639 my %foreign_keys = (
640 #       table => [
641 #               {       key => 'the key in table' (must be indexed)
642 #                       foreigntable => 'the foreigntable name', # (the parent)
643 #                       foreignkey => 'the foreign key column(s)' # (in the parent)
644 #                       onUpdate => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
645 #                       onDelete => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
646 #               }
647 #       ],
648         shelfcontents => [
649                 {       key => 'shelfnumber',
650                         foreigntable => 'bookshelf',
651                         foreignkey => 'shelfnumber',
652                         onUpdate => 'CASCADE',
653                         onDelete => 'CASCADE',
654                 },
655                 {       key => 'itemnumber',
656                         foreigntable => 'items',
657                         foreignkey => 'itemnumber',
658                         onUpdate => 'CASCADE',
659                         onDelete => 'CASCADE',
660                 },
661         ],
662         # onDelete is RESTRICT on reference tables (branches, itemtype) as we don't want items to be 
663         # easily deleted, but branches/itemtype not too easy to empty...
664         biblioitems => [
665                 {       key => 'biblionumber',
666                         foreigntable => 'biblio',
667                         foreignkey => 'biblionumber',
668                         onUpdate => 'CASCADE',
669                         onDelete => 'CASCADE',
670                 },
671                 {       key => 'itemtype',
672                         foreigntable => 'itemtypes',
673                         foreignkey => 'itemtype',
674                         onUpdate => 'CASCADE',
675                         onDelete => 'RESTRICT',
676                 },
677         ],
678         items => [
679                 {       key => 'biblioitemnumber',
680                         foreigntable => 'biblioitems',
681                         foreignkey => 'biblioitemnumber',
682                         onUpdate => 'CASCADE',
683                         onDelete => 'CASCADE',
684                 },
685                 {       key => 'homebranch',
686                         foreigntable => 'branches',
687                         foreignkey => 'branchcode',
688                         onUpdate => 'CASCADE',
689                         onDelete => 'RESTRICT',
690                 },
691                 {       key => 'holdingbranch',
692                         foreigntable => 'branches',
693                         foreignkey => 'branchcode',
694                         onUpdate => 'CASCADE',
695                         onDelete => 'RESTRICT',
696                 },
697         ],
698         additionalauthors => [
699                 {       key => 'biblionumber',
700                         foreigntable => 'biblio',
701                         foreignkey => 'biblionumber',
702                         onUpdate => 'CASCADE',
703                         onDelete => 'CASCADE',
704                 },
705         ],
706         bibliosubject => [
707                 {       key => 'biblionumber',
708                         foreigntable => 'biblio',
709                         foreignkey => 'biblionumber',
710                         onUpdate => 'CASCADE',
711                         onDelete => 'CASCADE',
712                 },
713         ],
714         aqbasket => [
715                 {       key => 'booksellerid',
716                         foreigntable => 'aqbooksellers',
717                         foreignkey => 'id',
718                         onUpdate => 'CASCADE',
719                         onDelete => 'RESTRICT',
720                 },
721         ],
722         aqorders => [
723                 {       key => 'basketno',
724                         foreigntable => 'aqbasket',
725                         foreignkey => 'basketno',
726                         onUpdate => 'CASCADE',
727                         onDelete => 'CASCADE',
728                 },
729                 {       key => 'biblionumber',
730                         foreigntable => 'biblio',
731                         foreignkey => 'biblionumber',
732                         onUpdate => 'SET NULL',
733                         onDelete => 'SET NULL',
734                 },
735         ],
736         aqbooksellers => [
737                 {       key => 'listprice',
738                         foreigntable => 'currency',
739                         foreignkey => 'currency',
740                         onUpdate => 'CASCADE',
741                         onDelete => 'CASCADE',
742                 },
743                 {       key => 'invoiceprice',
744                         foreigntable => 'currency',
745                         foreignkey => 'currency',
746                         onUpdate => 'CASCADE',
747                         onDelete => 'CASCADE',
748                 },
749         ],
750         aqorderbreakdown => [
751                 {       key => 'ordernumber',
752                         foreigntable => 'aqorders',
753                         foreignkey => 'ordernumber',
754                         onUpdate => 'CASCADE',
755                         onDelete => 'CASCADE',
756                 },
757                 {       key => 'bookfundid',
758                         foreigntable => 'aqbookfund',
759                         foreignkey => 'bookfundid',
760                         onUpdate => 'CASCADE',
761                         onDelete => 'CASCADE',
762                 },
763         ],
764         branchtransfers => [
765                 {       key => 'frombranch',
766                         foreigntable => 'branches',
767                         foreignkey => 'branchcode',
768                         onUpdate => 'CASCADE',
769                         onDelete => 'CASCADE',
770                 },
771                 {       key => 'tobranch',
772                         foreigntable => 'branches',
773                         foreignkey => 'branchcode',
774                         onUpdate => 'CASCADE',
775                         onDelete => 'CASCADE',
776                 },
777                 {       key => 'itemnumber',
778                         foreigntable => 'items',
779                         foreignkey => 'itemnumber',
780                         onUpdate => 'CASCADE',
781                         onDelete => 'CASCADE',
782                 },
783         ],
784         issuingrules => [
785                 {       key => 'categorycode',
786                         foreigntable => 'categories',
787                         foreignkey => 'categorycode',
788                         onUpdate => 'CASCADE',
789                         onDelete => 'CASCADE',
790                 },
791                 {       key => 'itemtype',
792                         foreigntable => 'itemtypes',
793                         foreignkey => 'itemtype',
794                         onUpdate => 'CASCADE',
795                         onDelete => 'CASCADE',
796                 },
797         ],
798         issues => [     # constraint is SET NULL : when a borrower or an item is deleted, we keep the issuing record
799         # for stat purposes
800                 {       key => 'borrowernumber',
801                         foreigntable => 'borrowers',
802                         foreignkey => 'borrowernumber',
803                         onUpdate => 'SET NULL',
804                         onDelete => 'SET NULL',
805                 },
806                 {       key => 'itemnumber',
807                         foreigntable => 'items',
808                         foreignkey => 'itemnumber',
809                         onUpdate => 'SET NULL',
810                         onDelete => 'SET NULL',
811                 },
812         ],
813         reserves => [
814                 {       key => 'borrowernumber',
815                         foreigntable => 'borrowers',
816                         foreignkey => 'borrowernumber',
817                         onUpdate => 'CASCADE',
818                         onDelete => 'CASCADE',
819                 },
820                 {       key => 'biblionumber',
821                         foreigntable => 'biblio',
822                         foreignkey => 'biblionumber',
823                         onUpdate => 'CASCADE',
824                         onDelete => 'CASCADE',
825                 },
826                 {       key => 'itemnumber',
827                         foreigntable => 'items',
828                         foreignkey => 'itemnumber',
829                         onUpdate => 'CASCADE',
830                         onDelete => 'CASCADE',
831                 },
832                 {       key => 'branchcode',
833                         foreigntable => 'branches',
834                         foreignkey => 'branchcode',
835                         onUpdate => 'CASCADE',
836                         onDelete => 'CASCADE',
837                 },
838         ],
839         borrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
840         # but prevent deleting a branch as soon as it has 1 borrower !
841                 {       key => 'categorycode',
842                         foreigntable => 'categories',
843                         foreignkey => 'categorycode',
844                         onUpdate => 'RESTRICT',
845                         onDelete => 'RESTRICT',
846                 },
847                 {       key => 'branchcode',
848                         foreigntable => 'branches',
849                         foreignkey => 'branchcode',
850                         onUpdate => 'RESTRICT',
851                         onDelete => 'RESTRICT',
852                 },
853         ],
854         deletedborrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
855         # but prevent deleting a branch as soon as it has 1 borrower !
856                 {       key => 'categorycode',
857                         foreigntable => 'categories',
858                         foreignkey => 'categorycode',
859                         onUpdate => 'RESTRICT',
860                         onDelete => 'RESTRICT',
861                 },
862                 {       key => 'branchcode',
863                         foreigntable => 'branches',
864                         foreignkey => 'branchcode',
865                         onUpdate => 'RESTRICT',
866                         onDelete => 'RESTRICT',
867                 },
868         ],
869         accountlines => [
870                 {       key => 'borrowernumber',
871                         foreigntable => 'borrowers',
872                         foreignkey => 'borrowernumber',
873                         onUpdate => 'CASCADE',
874                         onDelete => 'CASCADE',
875                 },
876                 {       key => 'itemnumber',
877                         foreigntable => 'items',
878                         foreignkey => 'itemnumber',
879                         onUpdate => 'SET NULL',
880                         onDelete => 'SET NULL',
881                 },
882         ],
883         auth_tag_structure => [
884                 {       key => 'authtypecode',
885                         foreigntable => 'auth_types',
886                         foreignkey => 'authtypecode',
887                         onUpdate => 'CASCADE',
888                         onDelete => 'CASCADE',
889                 },
890         ],
891         # FIXME : don't constraint auth_*_table and auth_word, as they may be replaced by zebra
892 );
893
894
895 # column changes
896 my %column_change = (
897         # table
898         borrowers => [
899                                 {
900                                         from => 'emailaddress',
901                                         to => 'email',
902                                         after => 'city',
903                                 },
904                                 {
905                                         from => 'streetaddress',
906                                         to => 'address',
907                                         after => 'initials',
908                                 },
909                                 {
910                                         from => 'faxnumber',
911                                         to => 'fax',
912                                         after => 'phone',
913                                 },
914                                 {
915                                         from => 'textmessaging',
916                                         to => 'opacnote',
917                                         after => 'userid',
918                                 },
919                                 {
920                                         from => 'altnotes',
921                                         to => 'contactnote',
922                                         after => 'opacnote',
923                                 },
924                                 {
925                                         from => 'physstreet',
926                                         to => 'B_address',
927                                         after => 'fax',
928                                 },
929                                 {
930                                         from => 'streetcity',
931                                         to => 'B_city',
932                                         after => 'B_address',
933                                 },
934                                 {
935                                         from => 'phoneday',
936                                         to => 'mobile',
937                                         after => 'phone',
938                                 },
939                                 {
940                                         from => 'zipcode',
941                                         to => 'zipcode',
942                                         after => 'city',
943                                 },
944                                 {
945                                         from => 'homezipcode',
946                                         to => 'B_zipcode',
947                                         after => 'B_city',
948                                 },
949                                 {
950                                         from => 'altphone',
951                                         to => 'B_phone',
952                                         after => 'B_zipcode',
953                                 },
954                                 {
955                                         from => 'expiry',
956                                         to => 'dateexpiry',
957                                         after => 'dateenrolled',
958                                 },
959                                 {
960                                         from => 'guarantor',
961                                         to => 'guarantorid',
962                                         after => 'contactname',
963                                 },
964                                 {
965                                         from => 'textmessaging',
966                                         to => 'opacnotes',
967                                         after => 'flags',
968                                 },
969                                 {
970                                         from => 'altnotes',
971                                         to => 'contactnotes',
972                                         after => 'opacnotes',
973                                 },
974                                 {
975                                         from => 'altrelationship',
976                                         to => 'relationship',
977                                         after => 'borrowernotes',
978                                 },
979                         ],
980
981         deletedborrowers => [
982                                 {
983                                         from => 'emailaddress',
984                                         to => 'email',
985                                         after => 'city',
986                                 },
987                                 {
988                                         from => 'streetaddress',
989                                         to => 'address',
990                                         after => 'initials',
991                                 },
992                                 {
993                                         from => 'faxnumber',
994                                         to => 'fax',
995                                         after => 'phone',
996                                 },
997                                 {
998                                         from => 'textmessaging',
999                                         to => 'opacnote',
1000                                         after => 'userid',
1001                                 },
1002                                 {
1003                                         from => 'altnotes',
1004                                         to => 'contactnote',
1005                                         after => 'opacnote',
1006                                 },
1007                                 {
1008                                         from => 'physstreet',
1009                                         to => 'B_address',
1010                                         after => 'fax',
1011                                 },
1012                                 {
1013                                         from => 'streetcity',
1014                                         to => 'B_city',
1015                                         after => 'B_address',
1016                                 },
1017                                 {
1018                                         from => 'phoneday',
1019                                         to => 'mobile',
1020                                         after => 'phone',
1021                                 },
1022                                 {
1023                                         from => 'zipcode',
1024                                         to => 'zipcode',
1025                                         after => 'city',
1026                                 },
1027                                 {
1028                                         from => 'homezipcode',
1029                                         to => 'B_zipcode',
1030                                         after => 'B_city',
1031                                 },
1032                                 {
1033                                         from => 'altphone',
1034                                         to => 'B_phone',
1035                                         after => 'B_zipcode',
1036                                 },
1037                                 {
1038                                         from => 'expiry',
1039                                         to => 'dateexpiry',
1040                                         after => 'dateenrolled',
1041                                 },
1042                                 {
1043                                         from => 'guarantor',
1044                                         to => 'guarantorid',
1045                                         after => 'contactname',
1046                                 },
1047                                 {
1048                                         from => 'textmessaging',
1049                                         to => 'opacnotes',
1050                                         after => 'flags',
1051                                 },
1052                                 {
1053                                         from => 'altnotes',
1054                                         to => 'contactnotes',
1055                                         after => 'opacnotes',
1056                                 },
1057                                 {
1058                                         from => 'altrelationship',
1059                                         to => 'relationship',
1060                                         after => 'borrowernotes',
1061                                 },
1062                         ],
1063
1064                 );
1065                 
1066 foreach my $table (keys %column_change) {
1067         $sth = $dbh->prepare("show columns from $table");
1068         $sth->execute();
1069         undef %types;
1070         while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1071         {
1072                 $types{$column}->{type} ="$type";
1073                 $types{$column}->{null} = "$null";
1074                 $types{$column}->{key} = "$key";
1075                 $types{$column}->{default} = "$default";
1076                 $types{$column}->{extra} = "$extra";
1077         }    # while
1078         my $tablerows = $column_change{$table};
1079         foreach my $row ( @$tablerows ) {
1080                 if ($types{$row->{from}}->{type}) {
1081                         print "altering $table $row->{from} to $row->{to}\n";
1082                         # ALTER TABLE `borrowers` CHANGE `faxnumber` `fax` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL 
1083 #                       alter table `borrowers` change `faxnumber` `fax` type text  null after phone
1084                         my $sql = 
1085                                 "alter table `$table` change `$row->{from}` `$row->{to}` $types{$row->{from}}->{type} ".
1086                                 ($types{$row->{from}}->{null} eq 'YES'?" NULL":" NOT NULL").
1087                                 ($types{$row->{from}}->{default}?" default ".$types{$row->{from}}->{default}:"").
1088                                 "$types{$row->{from}}->{extra} after $row->{after} ";
1089 #                       print "$sql";
1090                         $dbh->do($sql);
1091                 }
1092         }
1093 }
1094
1095 #-------------------
1096 # Initialize
1097
1098 # Start checking
1099
1100 # Get version of MySQL database engine.
1101 my $mysqlversion = `mysqld --version`;
1102 $mysqlversion =~ /Ver (\S*) /;
1103 $mysqlversion = $1;
1104 if ( $mysqlversion ge '3.23' ) {
1105     print "Could convert to MyISAM database tables...\n" unless $silent;
1106 }
1107
1108 #---------------------------------
1109 # Tables
1110
1111 # Collect all tables into a list
1112 $sth = $dbh->prepare("show tables");
1113 $sth->execute;
1114 while ( my ($table) = $sth->fetchrow ) {
1115     $existingtables{$table} = 1;
1116 }
1117
1118
1119 # Now add any missing tables
1120 foreach $table ( keys %requiretables ) {
1121     unless ( $existingtables{$table} ) {
1122         print "Adding $table table...\n" unless $silent;
1123         my $sth = $dbh->prepare("create table $table $requiretables{$table}");
1124         $sth->execute;
1125         if ( $sth->err ) {
1126             print "Error : $sth->errstr \n";
1127             $sth->finish;
1128         }    # if error
1129     }    # unless exists
1130 }    # foreach
1131
1132 # now drop useless tables
1133 foreach $table ( keys %dropable_table ) {
1134         if ( $existingtables{$table} ) {
1135                 print "Dropping unused table $table\n" if $debug and not $silent;
1136                 $dbh->do("drop table $table");
1137                 if ( $dbh->err ) {
1138                         print "Error : $dbh->errstr \n";
1139                 }
1140         }
1141 }
1142
1143 #---------------------------------
1144 # Columns
1145
1146 foreach $table ( keys %requirefields ) {
1147     print "Check table $table\n" if $debug and not $silent;
1148     $sth = $dbh->prepare("show columns from $table");
1149     $sth->execute();
1150     undef %types;
1151     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1152     {
1153         $types{$column} = $type;
1154     }    # while
1155     foreach $column ( keys %{ $requirefields{$table} } ) {
1156         print "  Check column $column  [$types{$column}]\n" if $debug and not $silent;
1157         if ( !$types{$column} ) {
1158
1159             # column doesn't exist
1160             print "Adding $column field to $table table...\n" unless $silent;
1161             $query = "alter table $table
1162                         add column $column " . $requirefields{$table}->{$column};
1163             print "Execute: $query\n" if $debug;
1164             my $sti = $dbh->prepare($query);
1165             $sti->execute;
1166             if ( $sti->err ) {
1167                 print "**Error : $sti->errstr \n";
1168                 $sti->finish;
1169             }    # if error
1170         }    # if column
1171     }    # foreach column
1172 }    # foreach table
1173
1174 foreach $table ( keys %fielddefinitions ) {
1175         print "Check table $table\n" if $debug;
1176         $sth = $dbh->prepare("show columns from $table");
1177         $sth->execute();
1178         my $definitions;
1179         while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1180         {
1181                 $definitions->{$column}->{type}    = $type;
1182                 $definitions->{$column}->{null}    = $null;
1183                 $definitions->{$column}->{null}    = 'NULL' if $null eq 'YES';
1184                 $definitions->{$column}->{key}     = $key;
1185                 $definitions->{$column}->{default} = $default;
1186                 $definitions->{$column}->{extra}   = $extra;
1187         }    # while
1188         my $fieldrow = $fielddefinitions{$table};
1189         foreach my $row (@$fieldrow) {
1190                 my $field   = $row->{field};
1191                 my $type    = $row->{type};
1192                 my $null    = $row->{null};
1193 #               $null    = 'YES' if $row->{null} eq 'NULL';
1194                 my $key     = $row->{key};
1195                 my $default = $row->{default};
1196                 my $null    = $row->{null};
1197 #               $default="''" unless $default;
1198                 my $extra   = $row->{extra};
1199                 my $def     = $definitions->{$field};
1200                 my $after       = ($row->{after}?" after ".$row->{after}:"");
1201
1202                 unless ( $type eq $def->{type}
1203                         && $null eq $def->{null}
1204                         && $key eq $def->{key}
1205                         && $extra eq $def->{extra} )
1206                 {
1207                         if ( $null eq '' ) {
1208                                 $null = 'NOT NULL';
1209                         }
1210                         if ( $key eq 'PRI' ) {
1211                                 $key = 'PRIMARY KEY';
1212                         }
1213                         unless ( $extra eq 'auto_increment' ) {
1214                                 $extra = '';
1215                         }
1216
1217                         # if it's a new column use "add", if it's an old one, use "change".
1218                         my $action;
1219                         if ($definitions->{$field}->{type}) {
1220                                 $action="change $field"
1221                         } else {
1222                                 $action="add";
1223                         }
1224 # if it's a primary key, drop the previous pk, before altering the table
1225                         my $sth;
1226                         if ($key ne 'PRIMARY KEY') {
1227                                 $sth =$dbh->prepare("alter table $table $action $field $type $null $key $extra default ? $after");
1228                         } else {
1229                                 $sth =$dbh->prepare("alter table $table drop primary key, $action $field $type $null $key $extra default ? $after");
1230                         }
1231                         $sth->execute($default);
1232                         print "  alter or create $field in $table\n" unless $silent;
1233                 }
1234         }
1235 }
1236
1237 # Populate tables with required data
1238
1239
1240 # synch table and deletedtable.
1241 foreach my $table (('borrowers','items','biblio','biblioitems')) {
1242         my %deletedborrowers;
1243         print "synch'ing $table\n";
1244         $sth = $dbh->prepare("show columns from deleted$table");
1245         $sth->execute;
1246         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1247                 $deletedborrowers{$column}=1;
1248         }
1249         $sth = $dbh->prepare("show columns from $table");
1250         $sth->execute;
1251         my $previous;
1252         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1253                 unless ($deletedborrowers{$column}) {
1254                         my $newcol="alter table deleted$table add $column $type";
1255                         if ($null eq 'YES') {
1256                                 $newcol .= " NULL ";
1257                         } else {
1258                                 $newcol .= " NOT NULL ";
1259                         }
1260                         $newcol .= "default $default" if $default;
1261                         $newcol .= " after $previous" if $previous;
1262                         $previous=$column;
1263                         print "creating column $column\n";
1264                         $dbh->do($newcol);
1265                 }
1266         }
1267 }
1268
1269 foreach my $table ( keys %tabledata ) {
1270     print "Checking for data required in table $table...\n" unless $silent;
1271     my $tablerows = $tabledata{$table};
1272     foreach my $row (@$tablerows) {
1273         my $uniquefieldrequired = $row->{uniquefieldrequired};
1274         my $uniquevalue         = $row->{$uniquefieldrequired};
1275         my $forceupdate         = $row->{forceupdate};
1276         my $sth                 =
1277           $dbh->prepare(
1278 "select $uniquefieldrequired from $table where $uniquefieldrequired=?"
1279         );
1280         $sth->execute($uniquevalue);
1281                 if ($sth->rows) {
1282                         foreach my $field (keys %$forceupdate) {
1283                                 if ($forceupdate->{$field}) {
1284                                         my $sth=$dbh->prepare("update systempreferences set $field=? where $uniquefieldrequired=?");
1285                                         $sth->execute($row->{$field}, $uniquevalue);
1286                                 }
1287                 }
1288                 } else {
1289                         print "Adding row to $table: " unless $silent;
1290                         my @values;
1291                         my $fieldlist;
1292                         my $placeholders;
1293                         foreach my $field ( keys %$row ) {
1294                                 next if $field eq 'uniquefieldrequired';
1295                                 next if $field eq 'forceupdate';
1296                                 my $value = $row->{$field};
1297                                 push @values, $value;
1298                                 print "  $field => $value" unless $silent;
1299                                 $fieldlist .= "$field,";
1300                                 $placeholders .= "?,";
1301                         }
1302                         print "\n" unless $silent;
1303                         $fieldlist    =~ s/,$//;
1304                         $placeholders =~ s/,$//;
1305                         my $sth =
1306                         $dbh->prepare(
1307                                 "insert into $table ($fieldlist) values ($placeholders)");
1308                         $sth->execute(@values);
1309                 }
1310         }
1311 }
1312
1313 #
1314 # check indexes and create them when needed
1315 #
1316 print "Checking for index required...\n" unless $silent;
1317 foreach my $table ( keys %indexes ) {
1318         #
1319         # read all indexes from $table
1320         #
1321         $sth = $dbh->prepare("show index from $table");
1322         $sth->execute;
1323         my %existingindexes;
1324         while ( my ( $table, $non_unique, $key_name, $Seq_in_index, $Column_name, $Collation, $cardinality, $sub_part, $Packed, $comment ) = $sth->fetchrow ) {
1325                 $existingindexes{$key_name} = 1;
1326         }
1327         # read indexes to check
1328         my $tablerows = $indexes{$table};
1329         foreach my $row (@$tablerows) {
1330                 my $key_name=$row->{indexname};
1331                 if ($existingindexes{$key_name} eq 1) {
1332 #                       print "$key_name existing";
1333                 } else {
1334                         print "\tCreating index $key_name in $table\n";
1335                         my $sql;
1336                         if ($row->{indexname} eq 'PRIMARY') {
1337                                 $sql = "alter table $table ADD PRIMARY KEY ($row->{content})";
1338                         } else {
1339                                 $sql = "alter table $table ADD INDEX $key_name ($row->{content}) $row->{type}";
1340                         }
1341                         $dbh->do($sql);
1342             print "Error $sql : $dbh->err \n" if $dbh->err;
1343                 }
1344         }
1345 }
1346
1347 #
1348 # check foreign keys and create them when needed
1349 #
1350 print "Checking for foreign keys required...\n" unless $silent;
1351 foreach my $table ( keys %foreign_keys ) {
1352         #
1353         # read all indexes from $table
1354         #
1355         $sth = $dbh->prepare("show table status like '$table'");
1356         $sth->execute;
1357         my $stat = $sth->fetchrow_hashref;
1358         # read indexes to check
1359         my $tablerows = $foreign_keys{$table};
1360         foreach my $row (@$tablerows) {
1361                 my $foreign_table=$row->{foreigntable};
1362                 if ($stat->{'Comment'} =~/$foreign_table/) {
1363 #                       print "$foreign_table existing\n";
1364                 } else {
1365                         print "\tCreating foreign key $foreign_table in $table\n";
1366                         # first, drop any orphan value in child table
1367                         if ($row->{onDelete} ne "RESTRICT") {
1368                                 my $sql = "delete from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})";
1369                                 $dbh->do($sql);
1370                                 print "SQL ERROR: $sql : $dbh->err \n" if $dbh->err;
1371                         }
1372                         my $sql="alter table $table ADD FOREIGN KEY $row->{key} ($row->{key}) REFERENCES $row->{foreigntable} ($row->{foreignkey})";
1373                         $sql .= " on update ".$row->{onUpdate} if $row->{onUpdate};
1374                         $sql .= " on delete ".$row->{onDelete} if $row->{onDelete};
1375                         $dbh->do($sql);
1376                         if ($dbh->err) {
1377                                 print "====================
1378 An error occured during :
1379 \t$sql
1380 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).
1381 You can find those values with select
1382 \t$table.* from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})
1383 ====================\n
1384 ";
1385                         }
1386                 }
1387         }
1388 }
1389
1390 #
1391 # SPECIFIC STUFF
1392 #
1393 #
1394 # create frameworkcode row in biblio table & fill it with marc_biblio.frameworkcode.
1395 #
1396
1397 # 1st, get how many biblio we will have to do...
1398 $sth = $dbh->prepare('select count(*) from marc_biblio');
1399 $sth->execute;
1400 my ($totaltodo) = $sth->fetchrow;
1401
1402 $sth = $dbh->prepare("show columns from biblio");
1403 $sth->execute();
1404 my $definitions;
1405 my $bibliofwexist=0;
1406 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
1407         $bibliofwexist=1 if $column eq 'frameworkcode';
1408 }
1409 unless ($bibliofwexist) {
1410         print "moving biblioframework to biblio table\n";
1411         $dbh->do('ALTER TABLE `biblio` ADD `frameworkcode` VARCHAR( 4 ) NOT NULL AFTER `biblionumber`');
1412         $sth = $dbh->prepare('select biblionumber,frameworkcode from marc_biblio');
1413         $sth->execute;
1414         my $sth_update = $dbh->prepare('update biblio set frameworkcode=? where biblionumber=?');
1415         my $totaldone=0;
1416         while (my ($biblionumber,$frameworkcode) = $sth->fetchrow) {
1417                 $sth_update->execute($frameworkcode,$biblionumber);
1418                 $totaldone++;
1419                 print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
1420         }
1421         print "\rdone\n";
1422 }
1423
1424 #
1425 # moving MARC data from marc_subfield_table to biblioitems.marc
1426 #
1427 $sth = $dbh->prepare("show columns from biblioitems");
1428 $sth->execute();
1429 my $definitions;
1430 my $marcdone=0;
1431 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
1432         $marcdone=1 if ($type eq 'blob' && $column eq 'marc') ;
1433 }
1434 unless ($marcdone) {
1435         print "moving MARC record to biblioitems table\n";
1436         # changing marc field type
1437         $dbh->do('ALTER TABLE `biblioitems` CHANGE `marc` `marc` BLOB NULL DEFAULT NULL ');
1438         # adding marc xml, just for convenience
1439         $dbh->do('ALTER TABLE `biblioitems` ADD `marcxml` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ');
1440         # moving data from marc_subfield_value to biblio
1441         $sth = $dbh->prepare('select bibid,biblionumber from marc_biblio');
1442         $sth->execute;
1443         my $sth_update = $dbh->prepare('update biblioitems set marc=?, marcxml=? where biblionumber=?');
1444         my $totaldone=0;
1445         while (my ($bibid,$biblionumber) = $sth->fetchrow) {
1446                 my $record = MARCgetbiblio($dbh,$bibid);
1447         #Force UTF-8 in record leader
1448                 $record->encoding('UTF-8');
1449 #               print $record->as_formatted if ($biblionumber==3902);
1450                 $sth_update->execute($record->as_usmarc(),$record->as_xml_record(),$biblionumber);
1451                 $totaldone++;
1452                 print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
1453         }
1454         print "\rdone\n";
1455 }
1456
1457
1458 # at last, remove useless fields
1459 foreach $table ( keys %uselessfields ) {
1460         my @fields = split /,/,$uselessfields{$table};
1461         my $fields;
1462         my $exists;
1463         foreach my $fieldtodrop (@fields) {
1464                 $fieldtodrop =~ s/\t//g;
1465                 $fieldtodrop =~ s/\n//g;
1466                 $exists =0;
1467                 $sth = $dbh->prepare("show columns from $table");
1468                 $sth->execute;
1469                 while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1470                 {
1471                         $exists =1 if ($column eq $fieldtodrop);
1472                 }
1473                 if ($exists) {
1474                         print "deleting $fieldtodrop field in $table...\n" unless $silent;
1475                         my $sth = $dbh->prepare("alter table $table drop $fieldtodrop");
1476                         $sth->execute;
1477                 }
1478         }
1479 }    # foreach
1480
1481
1482 # MOVE all tables TO UTF-8 and innoDB
1483 $sth = $dbh->prepare("show table status");
1484 $sth->execute;
1485 while ( my $table = $sth->fetchrow_hashref ) {
1486 #       if ($table->{Engine} ne 'InnoDB') {
1487 #               $dbh->do("ALTER TABLE $table->{Name} TYPE = innodb");
1488 #               print "moving $table->{Name} to InnoDB\n";
1489 #       }
1490     next if $table->{Name} eq 'marc_word';
1491     next if $table->{Name} eq 'marc_subfield_table';
1492     next if $table->{Name} eq 'auth_word';
1493     next if $table->{Name} eq 'auth_subfield_table';
1494         unless ($table->{Collation} =~ /^utf8/) {
1495                 print "moving $table->{Name} to utf8\n";
1496                 $dbh->do("ALTER TABLE $table->{Name} CONVERT TO CHARACTER SET utf8");
1497                 $dbh->do("ALTER TABLE $table->{Name} DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci");
1498                 # 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 !
1499         } else {
1500         }
1501 }
1502
1503 $sth->finish;
1504
1505 #
1506 # those 2 subs are a copy of Biblio.pm, version 2.2.4
1507 # they are useful only once, for moving from 2.2 to 3.0
1508 # the MARCgetbiblio & MARCgetitem subs in Biblio.pm
1509 # are still here, but uses other tables
1510 # (the ones that are filled by updatedatabase !)
1511 #
1512
1513 sub MARCgetbiblio {
1514
1515     # Returns MARC::Record of the biblio passed in parameter.
1516     my ( $dbh, $bibid ) = @_;
1517     my $record = MARC::Record->new();
1518 #       warn "". $bidid;
1519
1520     my $sth =
1521       $dbh->prepare(
1522 "select bibid,subfieldid,tag,tagorder,tag_indicator,subfieldcode,subfieldorder,subfieldvalue,valuebloblink
1523                                  from marc_subfield_table
1524                                  where bibid=? order by tag,tagorder,subfieldorder
1525                          "
1526     );
1527     my $sth2 =
1528       $dbh->prepare(
1529         "select subfieldvalue from marc_blob_subfield where blobidlink=?");
1530     $sth->execute($bibid);
1531     my $prevtagorder = 1;
1532     my $prevtag      = 'XXX';
1533     my $previndicator;
1534     my $field;        # for >=10 tags
1535     my $prevvalue;    # for <10 tags
1536     while ( my $row = $sth->fetchrow_hashref ) {
1537
1538         if ( $row->{'valuebloblink'} ) {    #---- search blob if there is one
1539             $sth2->execute( $row->{'valuebloblink'} );
1540             my $row2 = $sth2->fetchrow_hashref;
1541             $sth2->finish;
1542             $row->{'subfieldvalue'} = $row2->{'subfieldvalue'};
1543         }
1544         if ( $row->{tagorder} ne $prevtagorder || $row->{tag} ne $prevtag ) {
1545             $previndicator .= "  ";
1546             if ( $prevtag < 10 ) {
1547                                 if ($prevtag ne '000') {
1548                         $record->add_fields( ( sprintf "%03s", $prevtag ), $prevvalue ) unless $prevtag eq "XXX";    # ignore the 1st loop
1549                                 } else {
1550                                         $record->leader(sprintf("%24s",$prevvalue));
1551                                 }
1552             }
1553             else {
1554                 $record->add_fields($field) unless $prevtag eq "XXX";
1555             }
1556             undef $field;
1557             $prevtagorder  = $row->{tagorder};
1558             $prevtag       = $row->{tag};
1559             $previndicator = $row->{tag_indicator};
1560             if ( $row->{tag} < 10 ) {
1561                 $prevvalue = $row->{subfieldvalue};
1562             }
1563             else {
1564                 $field = MARC::Field->new(
1565                     ( sprintf "%03s", $prevtag ),
1566                     substr( $row->{tag_indicator} . '  ', 0, 1 ),
1567                     substr( $row->{tag_indicator} . '  ', 1, 1 ),
1568                     $row->{'subfieldcode'},
1569                     $row->{'subfieldvalue'}
1570                 );
1571             }
1572         }
1573         else {
1574             if ( $row->{tag} < 10 ) {
1575                 $record->add_fields( ( sprintf "%03s", $row->{tag} ),
1576                     $row->{'subfieldvalue'} );
1577             }
1578             else {
1579                 $field->add_subfields( $row->{'subfieldcode'},
1580                     $row->{'subfieldvalue'} );
1581             }
1582             $prevtag       = $row->{tag};
1583             $previndicator = $row->{tag_indicator};
1584         }
1585     }
1586
1587     # the last has not been included inside the loop... do it now !
1588     if ( $prevtag ne "XXX" )
1589     { # check that we have found something. Otherwise, prevtag is still XXX and we
1590          # must return an empty record, not make MARC::Record fail because we try to
1591          # create a record with XXX as field :-(
1592         if ( $prevtag < 10 ) {
1593             $record->add_fields( $prevtag, $prevvalue );
1594         }
1595         else {
1596
1597             #           my $field = MARC::Field->new( $prevtag, "", "", %subfieldlist);
1598             $record->add_fields($field);
1599         }
1600     }
1601     return $record;
1602 }
1603
1604 sub MARCgetitem {
1605
1606     # Returns MARC::Record of the biblio passed in parameter.
1607     my ( $dbh, $bibid, $itemnumber ) = @_;
1608     my $record = MARC::Record->new();
1609
1610     # search MARC tagorder
1611     my $sth2 =
1612       $dbh->prepare(
1613 "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=?"
1614     );
1615     $sth2->execute( $bibid, $itemnumber );
1616     my ($tagorder) = $sth2->fetchrow_array();
1617
1618     #---- TODO : the leader is missing
1619     my $sth =
1620       $dbh->prepare(
1621 "select bibid,subfieldid,tag,tagorder,tag_indicator,subfieldcode,subfieldorder,subfieldvalue,valuebloblink
1622                                  from marc_subfield_table
1623                                  where bibid=? and tagorder=? order by subfieldcode,subfieldorder
1624                          "
1625     );
1626     $sth2 =
1627       $dbh->prepare(
1628         "select subfieldvalue from marc_blob_subfield where blobidlink=?");
1629     $sth->execute( $bibid, $tagorder );
1630     while ( my $row = $sth->fetchrow_hashref ) {
1631         if ( $row->{'valuebloblink'} ) {    #---- search blob if there is one
1632             $sth2->execute( $row->{'valuebloblink'} );
1633             my $row2 = $sth2->fetchrow_hashref;
1634             $sth2->finish;
1635             $row->{'subfieldvalue'} = $row2->{'subfieldvalue'};
1636         }
1637         if ( $record->field( $row->{'tag'} ) ) {
1638             my $field;
1639
1640 #--- this test must stay as this, because of strange behaviour of mySQL/Perl DBI with char var containing a number...
1641             #--- sometimes, eliminates 0 at beginning, sometimes no ;-\\\
1642             if ( length( $row->{'tag'} ) < 3 ) {
1643                 $row->{'tag'} = "0" . $row->{'tag'};
1644             }
1645             $field = $record->field( $row->{'tag'} );
1646             if ($field) {
1647                 my $x =
1648                   $field->add_subfields( $row->{'subfieldcode'},
1649                     $row->{'subfieldvalue'} );
1650                 $record->delete_field($field);
1651                 $record->add_fields($field);
1652             }
1653         }
1654         else {
1655             if ( length( $row->{'tag'} ) < 3 ) {
1656                 $row->{'tag'} = "0" . $row->{'tag'};
1657             }
1658             my $temp =
1659               MARC::Field->new( $row->{'tag'}, " ", " ",
1660                 $row->{'subfieldcode'} => $row->{'subfieldvalue'} );
1661             $record->add_fields($temp);
1662         }
1663
1664     }
1665     return $record;
1666 }
1667
1668
1669 exit;
1670
1671 # $Log$
1672 # Revision 1.157  2006/08/11 10:03:13  tipaul
1673 # the new "includes" features, for personalized templates. Look at koha-devel, i'll write a mail here (& something on the wiki)
1674 #
1675 # Revision 1.152  2006/06/27 09:26:37  btoumi
1676 # modify (initials,phone ) fields property in borrowers and deletedborrowers table
1677 #
1678 # Revision 1.151  2006/06/22 10:33:14  btoumi
1679 # sorry i forget deletedborrowers table
1680 # modify firstname field from deletedborrowers table
1681 #
1682 # Revision 1.149  2006/06/20 22:35:47  rangi
1683 # Code to allow the associated borrowers to work
1684 #
1685 # Revision 1.148  2006/06/17 22:12:01  rangi
1686 # Adding id field to reviews table
1687 #
1688 # Revision 1.147  2006/06/17 03:36:41  rangi
1689 # Table definition for the reviews table
1690 #
1691 # Revision 1.146  2006/06/17 03:29:41  rangi
1692 # Variable to allow librarians to switch reviews on or off
1693 #
1694 # Revision 1.145  2006/06/16 09:45:02  btoumi
1695 # updatedatabase.pl: add change of borrowers table to deletedborrowers table
1696 # deletemem.pl: delete use of warn function
1697 #
1698 # Revision 1.144  2006/06/08 15:36:31  alaurin
1699 # Add a new system preference 'AutomaticItemReturn' :
1700 #
1701 # if this prefence is switched on: the document returned in another library than homebranch, the system automaticly transfer the document to his homebranch (with notification for librarian in returns.tmpl) .
1702 #
1703 # switch off : the document stay in the holdingbranch ...
1704 #
1705 # correcting bugs :
1706 # - comment C4::acquisition (not using in request.pl).
1707 # - correcting date in request.pl
1708 # -add the new call of function getbranches in request.pl
1709 #
1710 # Revision 1.143  2006/06/07 02:02:47  bob_lyon
1711 # merging katipo changes...
1712 #
1713 # adding new preference IssuingInProcess
1714 #
1715 # Revision 1.142  2006/06/06 23:42:46  bob_lyon
1716 # Merging Katipo changes...
1717 #
1718 # Adding new system pref where one can still retrieve a correct reading
1719 # record history if one has moved older data from issues to oldissues table
1720 # to speed up issues speed
1721 #
1722 # Revision 1.141  2006/06/01 03:18:11  rangi
1723 # Adding a new column to the statistics table
1724 #
1725 # Revision 1.140  2006/05/22 22:40:45  rangi
1726 # Adding new systempreference allowing for the library to add borrowers to institutions (rest homes, parishes, schools, classes etc).
1727 #
1728 # Revision 1.139  2006/05/19 19:31:29  tgarip1957
1729 # Added new fields to auth_header and auth_subfield_table to allow ZEBRA use of authorities and new MARC framework like structure.
1730 # Authority tables are modified to be compatible with new MARC frameworks. This change is part of Authority Linking & Zebra authorities. Requires change in Mysql database. It will break head unless all changes regarding this is implemented. This warning will take place on all commits regarding this
1731 #
1732 # Revision 1.138  2006/05/19 16:51:44  alaurin
1733 # update database for :
1734 # - new feature ip and printer management
1735 # adding two fields in branches table (branchip,branchprinter)
1736 #
1737 # - waiting date : adding one field in reserves table(waiting date) to calculate the Maximum delay to pick up a reserved document when it's available
1738 #
1739 # new system preference :
1740 # - ReservesMaxPickUpDelay : Maximum delay to pick up a reserved document
1741 # TransfersMaxDaysWarning : Max delay before considering the transfer as potentialy a problem
1742 #
1743 # Revision 1.137  2006/04/18 09:36:36  plg
1744 # bug fixed: typo fixed in labels and labels_conf tables creation query.
1745 #
1746 # Revision 1.136  2006/04/17 21:55:33  sushi
1747 # Added 'labels' and 'labels_conf' tables, for spine lable tool.
1748 #
1749 # Revision 1.135  2006/04/15 02:37:03  tgarip1957
1750 # Marc record should be set to UTF-8 in leader.Force it.
1751 # XML should be with<record> wrappers
1752 #
1753 # Revision 1.134  2006/04/14 09:37:29  tipaul
1754 # improvements from SAN Ouest Provence :
1755 # * introducing a category_type into categories. It can be A (adult), C (children), P (Professionnal), I (institution/organisation).
1756 # * each category_type has it's own forms to create members.
1757 # * the borrowers table has been heavily modified (many fields changed), to get something more logic & readable
1758 # * reintroducing guarantor/guanrantee system that is now independant from hardcoded C/A for categories
1759 # * updating templates to fit template rules
1760 #
1761 # (see mail feb, 17 on koha-devel "new features for borrowers" for more details)
1762 #
1763 # Revision 1.133  2006/04/13 08:36:42  plg
1764 # new: function C4::Date::get_date_format_string_for_DHTMLcalendar based on
1765 # the system preference prefered date format.
1766 #
1767 # improvement: book fund list and budget list screen redesigned. Filters on
1768 # each field. Columns are not sortable yet. Using DHTML Calendar to fill date
1769 # fields instead of manual filling. Pagination system. From the book fund
1770 # list, you can reach the budget list, filtered on a book fund, or not. A
1771 # budget can be added only from book fund list screen.
1772 #
1773 # bug fixed: branchcode was missing in table aqbudget.
1774 #
1775 # bug fixed: when setting a branchcode to a book fund, all associated budgets
1776 # move to this branchcode.
1777 #
1778 # modification: when adding/modifying budget/fund, MySQL specific "REPLACE..."
1779 # statements replaced by standard SQL compliant statement.
1780 #
1781 # bug fixed: when adding/modifying a budget, if the book fund is associated to
1782 # a branch, the branch selection is disabled and set to the book fund branch.
1783 #
1784 # Revision 1.132  2006/04/06 12:37:05  hdl
1785 # Bugfixing : aqbookfund needed a field.
1786 #
1787 # Revision 1.131  2006/03/03 17:02:22  tipaul
1788 # commit for holidays and news management.
1789 # (some forgotten files)
1790 #
1791 # Revision 1.130  2006/03/03 16:35:21  tipaul
1792 # commit for holidays and news management.
1793 #
1794 # Contrib from Tümer Garip (from Turkey) :
1795 # * holiday :
1796 # in /tools/ the holiday.pl script let you define holidays (days where the library is closed), branch by branch. You can define 3 types of holidays :
1797 # - single day : only this day is closed
1798 # - repet weekly (like "sunday") : the day is holiday every week
1799 # - repet yearly (like "July, 4") : this day is closed every year.
1800 #
1801 # You can also put exception :
1802 # - sunday is holiday, but "2006 March, 5th" the library will be open
1803 #
1804 # The holidays are used for return date calculation : the return date is set to the next date where the library is open. A systempreference (useDaysMode) set ON (Calendar) or OFF (Normal) the calendar calculation.
1805 #
1806 # Revision 1.129  2006/02/27 18:19:33  hdl
1807 # New table used in overduerules.pl tools page.
1808 #
1809 # Revision 1.128  2006/01/25 15:16:06  tipaul
1810 # updating DB :
1811 # * removing useless tables
1812 # * adding useful indexes
1813 # * altering some columns definitions
1814 # * The goal being to have updater working fine for foreign keys.
1815 #
1816 # 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
1817 #
1818 # Revision 1.127  2006/01/24 17:57:17  tipaul
1819 # DB improvements : adding foreign keys on some tables. partial stuff done.
1820 #
1821 # Revision 1.126  2006/01/06 16:39:42  tipaul
1822 # synch'ing head and rel_2_2 (from 2.2.5, including npl templates)
1823 # Seems not to break too many things, but i'm probably wrong here.
1824 # at least, new features/bugfixes from 2.2.5 are here (tested on some features on my head local copy)
1825 #
1826 # - removing useless directories (koha-html and koha-plucene)
1827 #
1828 # Revision 1.125  2006/01/04 15:54:55  tipaul
1829 # utf8 is a : go for beta test in HEAD.
1830 # some explanations :
1831 # - 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.
1832 # - *-top.inc will show the pages in utf8
1833 # - 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.
1834 # - using marcxml field and no more the iso2709 raw marc biblioitems.marc field.
1835 #
1836 # Revision 1.124  2005/10/27 12:09:05  tipaul
1837 # new features for serial module :
1838 # - 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")
1839 # - 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).
1840 #
1841 # Revision 1.123  2005/10/26 09:13:37  tipaul
1842 # big commit, still breaking things...
1843 #
1844 # * synch with rel_2_2. Probably the last non manual synch, as rel_2_2 should not be modified deeply.
1845 # * code cleaning (cleaning warnings from perl -w) continued
1846 #
1847 # Revision 1.122  2005/09/02 14:18:38  tipaul
1848 # new feature : image for itemtypes.
1849 #
1850 # * run updater/updatedatabase to create imageurl field in itemtypes.
1851 # * 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)
1852 # * go to OPAC, and search something. In the result list, you now have the picture instead of the text itemtype.
1853 #
1854 # Revision 1.121  2005/08/24 08:49:03  hdl
1855 # Adding a note field in serial table.
1856 # This will allow librarian to mention a note on a peculiar waiting serial number.
1857 #
1858 # Revision 1.120  2005/08/09 14:10:32  tipaul
1859 # 1st commit to go to zebra.
1860 # don't update your cvs if you want to have a working head...
1861 #
1862 # this commit contains :
1863 # * 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...
1864 # * 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.
1865 # * other files : get rid of bibid and use biblionumber instead.
1866 #
1867 # What is broken :
1868 # * does not do anything on zebra yet.
1869 # * if you rename marc_subfield_table, you can't search anymore.
1870 # * you can view a biblio & bibliodetails, go to MARC editor, but NOT save any modif.
1871 # * 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 ;-) )
1872 #
1873 # 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
1874 # 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.
1875 #
1876 # Revision 1.119  2005/08/04 16:07:58  tipaul
1877 # Synch really broke this script...
1878 #
1879 # Revision 1.118  2005/08/04 16:02:55  tipaul
1880 # oops... error in synch between 2.2 and head
1881 #
1882 # Revision 1.117  2005/08/04 14:24:39  tipaul
1883 # synch'ing 2.2 and head
1884 #
1885 # Revision 1.116  2005/08/04 08:55:54  tipaul
1886 # Letters / alert system, continuing...
1887 #
1888 # * adding a package Letters.pm, that manages Letters & alerts.
1889 # * 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)
1890 # * 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)
1891 # * 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.
1892 #
1893 # Note that the system should be generic enough to manage any type of alert.
1894 # 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 ;-) )
1895 #
1896 # Revision 1.115  2005/08/02 16:15:34  tipaul
1897 # adding 2 fields to letter system :
1898 # * module (acquisition, catalogue...) : it will be usefull to show the librarian only letters he may be interested by.
1899 # * title, that will be used as mail subject.
1900 #
1901 # Revision 1.114  2005/07/28 15:10:13  tipaul
1902 # 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.
1903 # the letter table contains 3 fields :
1904 # * code => the code of the letter
1905 # * name => the complete name of the letter
1906 # * content => the complete text. It's a TEXT field type, so has no limits.
1907 #
1908 # My next goal now is to work on point 2-I "serial issue alert"
1909 # 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.
1910 # (see mail on koha-devel, 2005/04/07)
1911 #
1912 # The "serial issue alert" will be the 1st to use this letter system that probably needs some tweaking ;-)
1913 #
1914 # Once it will be stabilised default letters (in any languages) could be added during installer to help the library begin with this new feature.
1915 #
1916 # Revision 1.113  2005/07/28 08:38:41  tipaul
1917 # 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 :
1918 # * ReturnBeforeExpiry = yes => return date can't be after expiry date
1919 # * ReturnBeforeExpiry = no  => return date can be after expiry date
1920 #
1921 # Revision 1.112  2005/07/26 08:19:47  hdl
1922 # Adding IndependantBranches System preference variable in order to manage Branch independancy.
1923 #
1924 # Revision 1.111  2005/07/25 15:35:38  tipaul
1925 # we have decided that moving to Koha 3.0 requires being already in Koha 2.2.x
1926 # So, the updatedatabase script can highly be cleaned (90% removed).
1927 # Let's play with the new Koha DB structure now ;-)
1928 #