writing absolute path.
[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) NULL ,
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     subscriptionroutinglist=>"(
183                              routingid integer NOT NULL auto_increment,
184                              borrowernumber integer,
185                              ranking integer,
186                              subscriptionid integer,
187                             PRIMARY KEY (routingid)
188                              )",
189
190     notifys    => "(
191               notify_id int(11) NOT NULL default '0',
192                 `borrowernumber` int(11) NOT NULL default '0',
193               `itemnumber` int(11) NOT NULL default '0',
194               `notify_date` date NOT NULL default '0000-00-00',
195                       `notify_send_date` date default NULL,
196                       `notify_level` int(1) NOT NULL default '0',
197                       `method` varchar(20) NOT NULL default ''
198               )",
199
200    charges    => "(
201               `charge_id` varchar(5) NOT NULL default '',
202                 `description` text NOT NULL,
203                 `amount` decimal(28,6) NOT NULL default '0.000000',
204                           `min` int(4) NOT NULL default '0',
205                 `max` int(4) NOT NULL default '0',
206                           `level` int(1) NOT NULL default '0',
207                           PRIMARY KEY  (`charge_id`)
208               )",
209     tags => "(
210         `entry` varchar(255) NOT NULL default '',
211         `weight` bigint(20) NOT NULL default '0',
212          PRIMARY KEY  (`entry`)
213     )
214     ",
215    zebraqueue    => "(
216                 `id` int NOT NULL auto_increment,
217                 `biblio_auth_number` int NOT NULL,
218                 `operation` char(20) NOT NULL,
219                 `server` char(20) NOT NULL ,
220                 PRIMARY KEY  (`id`)
221               ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci AUTO_INCREMENT=1",
222
223 );
224
225 my %requirefields = (
226     subscription => { 'letter' => 'char(20) NULL', 'distributedto' => 'text NULL', 'firstacquidate'=>'date NOT NULL','irregularity'=>'TEXT NULL default \'\'','numberpattern'=>'TINYINT(3) NULL default 0', 'callnumber'=>'text NULL', 'hemisphere' =>'TINYINT(3) NULL default 0', 'issuesatonce'=>'TINYINT(3) NOT NULL default 1',  'branchcode' =>'varchar(12) NOT NULL default \'\'', 'manualhistory'=>'TINYINT(1) NOT NULL default 0','internalnotes'=>'LONGTEXT NULL default \'\''},
227     itemtypes => { 'imageurl' => 'char(200) NULL'},
228     aqbookfund => { 'branchcode' => 'varchar(4) NULL'},
229     aqbudget => { 'branchcode' => 'varchar(4) NULL'},
230     auth_header => { 'marc' => 'BLOB NOT NULL', 'linkid' => 'BIGINT(20) NULL'},
231     auth_subfield_structure =>{ 'hidden' => 'TINYINT(3) NOT NULL default 0', 'kohafield' => 'VARCHAR(45) NOT NULL', 'linkid' =>  'TINYINT(1) NOT NULL default 0', 'isurl' => 'TINYINT(1)', 'frameworkcode'=>'VARCHAR(8) NOT  NULL'},
232     marc_breeding => { 'isbn' => 'varchar(13) NOT NULL'},
233     serial =>{ 'publisheddate' => 'date', 'claimdate' => 'date', 'itemnumber'=>'text NULL','routingnotes'=>'text NULL',},
234     statistics => { 'associatedborrower' => 'integer'},
235     z3950servers =>{  "name" =>"text",  "description" => "text NOT NULL",
236                     "position" =>"enum('primary','secondary','') NOT NULL default 'primary'",  "icon" =>"text",
237                     "type" =>"enum('zed','opensearch') NOT NULL default 'zed'",
238                     },
239     issues =>{ 'issuedate'=>"date NOT NULL default '0000-00-00'", },
240
241 #    tablename        => { 'field' => 'fieldtype' },
242 );
243
244 # Enter here the table to delete.
245 my @TableToDelete = qw(
246     additionalauthors
247     bibliosubject
248     bibliosubtitle
249     bibliothesaurus
250 );
251
252 my %uselessfields = (
253 # tablename => "field1,field2",
254     borrowers => "suburb,altstreetaddress,altsuburb,altcity,studentnumber,school,area,preferredcont,altcp",
255     deletedborrowers=> "suburb,altstreetaddress,altsuburb,altcity,studentnumber,school,area,preferredcont,altcp",
256     );
257 # the other hash contains other actions that can't be done elsewhere. they are done
258 # either BEFORE of AFTER everything else, depending on "when" entry (default => AFTER)
259
260 # The tabledata hash contains data that should be in the tables.
261 # The uniquefieldrequired hash entry is used to determine which (if any) fields
262 # must not exist in the table for this row to be inserted.  If the
263 # uniquefieldrequired entry is already in the table, the existing data is not
264 # modified, unless the forceupdate hash entry is also set.  Fields in the
265 # anonymous "forceupdate" hash will be forced to be updated to the default
266 # values given in the %tabledata hash.
267
268 my %tabledata = (
269 # tablename => [
270 #    {    uniquefielrequired => 'fieldname', # the primary key in the table
271 #        fieldname => fieldvalue,
272 #        fieldname2 => fieldvalue2,
273 #    },
274 # ],
275     systempreferences => [
276         {
277             uniquefieldrequired => 'variable',
278             variable            => 'useDaysMode',
279             value               => 'Calendar',
280             forceupdate         => { 'explanation' => 1,
281                                      'type' => 1},
282             explanation            => 'How to calculate return dates : Calendar means holidays will be controled, Days means the return date don\'t depend on holidays',
283             type        => 'Choice',
284             options        => 'Calendar|Days'
285         },
286         {
287             uniquefieldrequired => 'variable',
288             variable            => 'BorrowersTitles',
289             value               => 'Mr|Mrs|Miss|Ms',
290             forceupdate         => { 'explanation' => 1,
291                                      'type' => 1},
292             explanation         => 'List all Titles for borrowers',
293             type                => 'free',
294         },
295         {
296             uniquefieldrequired => 'variable',
297             variable            => 'BorrowerMandatoryField',
298             value               => 'cardnumber|surname|address',
299             forceupdate         => { 'explanation' => 1,
300                                      'type' => 1},
301             explanation         => 'List all mandatory fields for borrowers',
302             type                => 'free',
303         },
304         {
305             uniquefieldrequired => 'variable',
306             variable            => 'borrowerRelationship',
307             value               => 'father|mother,grand-mother',
308             forceupdate         => { 'explanation' => 1,
309                                      'type' => 1},
310             explanation         => 'The relationships between a guarantor & a guarantee (separated by | or ,)',
311             type                => 'free',
312         },
313         {
314             uniquefieldrequired => 'variable',
315             variable            => 'ReservesMaxPickUpDelay',
316             value               => '10',
317             forceupdate         => { 'explanation' => 1,
318                                      'type' => 1},
319             explanation         => 'Maximum delay to pick up a reserved document',
320             type                => 'free',
321         },
322         {
323             uniquefieldrequired => 'variable',
324             variable            => 'TransfersMaxDaysWarning',
325             value               => '3',
326             forceupdate         => { 'explanation' => 1,
327                                      'type' => 1},
328             explanation         => 'Max delay before considering the transfer has potentialy a problem',
329             type                => 'free',
330         },
331         {
332             uniquefieldrequired => 'variable',
333             variable            => 'memberofinstitution',
334             value               => '0',
335             forceupdate         => { 'explanation' => 1,
336                                      'type' => 1},
337             explanation         => 'Are your patrons members of institutions',
338             type                => 'YesNo',
339         },
340     {
341             uniquefieldrequired => 'variable',
342             variable            => 'ReadingHistory',
343             value               => '0',
344             forceupdate         => { 'explanation' => 1,
345                                      'type' => 1},
346             explanation         => 'Allow reading record info retrievable from issues and oldissues tables',
347             type                => 'YesNo',
348         },
349     {
350             uniquefieldrequired => 'variable',
351             variable            => 'IssuingInProcess',
352             value               => '0',
353             forceupdate         => { 'explanation' => 1,
354                                      'type' => 1},
355             explanation         => 'Allow no debt alert if the patron is issuing item that accumulate debt',
356             type                => 'YesNo',
357         },
358     {
359             uniquefieldrequired => 'variable',
360             variable            => 'AutomaticItemReturn',
361             value               => '1',
362             forceupdate         => { 'explanation' => 1,
363                                      'type' => 1},
364             explanation         => 'This Variable allow or not to return automaticly to his homebranch',
365             type                => 'YesNo',
366         },
367     {
368             uniquefieldrequired => 'variable',
369             variable            => 'reviewson',
370             value               => '0',
371             forceupdate         => { 'explanation' => 1,
372                                      'type' => 1},
373             explanation         => 'Allows patrons to submit reviews from the opac',
374             type                => 'YesNo',
375         },
376     {
377             uniquefieldrequired => 'variable',
378             variable            => 'intranet_includes',
379             value               => 'includes',
380             forceupdate         => { 'explanation' => 1,
381                                      'type' => 1},
382             explanation         => 'The includes directory you want for specific look of Koha (includes or includes_npl for example)',
383             type                => 'Free',
384         },
385         {
386             uniquefieldrequired => 'variable',
387             variable            => 'AutoLocation',
388             value               => '0',
389             forceupdate         => { 'explanation' => 1,
390                                      'type' => 1},
391             explanation         => 'switch to activate or not Autolocation, if Yes, the Librarian can\'t change his location, it\'s defined by branchip',
392             type                => 'YesNo',
393         },
394         {
395             uniquefieldrequired => 'variable',
396             variable            => 'serialsadditems',
397             value               => '0',
398             forceupdate         => {
399                 'explanation' => 1,
400                 'type' => 1
401             },
402             explanation => 'If set, a new item will be automatically added when receiving an issue',
403             type => 'YesNo',
404         },
405         {
406             uniquefieldrequired => 'variable',
407             variable            => 'expandedSearchOption',
408             value               => '0',
409             forceupdate         => {
410                 'explanation' => 1,
411                 'type' => 1
412             },
413             explanation => 'search among marc field',
414             type => 'YesNo',
415         },
416        {
417             uniquefieldrequired => 'variable',
418             variable            => 'RequestOnOpac',
419             value               => '1',
420             forceupdate         => { 'explanation' => 1,
421                                      'type' => 1},
422             explanation         => 'option to allow reserves on opac',
423             type                => 'YesNo',
424         },
425        {
426             uniquefieldrequired => 'variable',
427             variable            => 'OpacCloud',
428             value               => '1',
429             forceupdate         => { 'explanation' => 1,
430                                      'type' => 1},
431             explanation         => 'Enable / Disable cloud link on OPAC',
432             type                => 'YesNo',
433         },
434        {
435             uniquefieldrequired => 'variable',
436             variable            => 'OpacBrowser',
437             value               => '1',
438             forceupdate         => { 'explanation' => 1,
439                                      'type' => 1},
440             explanation         => 'Enable/Disable browser link on OPAC (needs to set misc/cronjob/build_browser.pl)',
441             type                => 'YesNo',
442         },
443        {
444             uniquefieldrequired => 'variable',
445             variable            => 'OpacTopissue',
446             value               => '1',
447             forceupdate         => { 'explanation' => 1,
448                                      'type' => 1},
449             explanation         => 'Enable / Disable the top issue link on OPAC',
450             type                => 'YesNo',
451         },
452        {
453             uniquefieldrequired => 'variable',
454             variable            => 'OpacAuthorities',
455             value               => '1',
456             forceupdate         => { 'explanation' => 1,
457                                      'type' => 1},
458             explanation         => 'Enable / Disable the search authority link on OPAC',
459             type                => 'YesNo',
460         },
461         {
462             uniquefieldrequired => 'variable',
463             variable            => 'CataloguingLog',
464             value               => '0',
465             forceupdate         => {'explanation' => 1, 'type' => 1},
466             explanation         => 'Active this if you want to log cataloguing action.',
467             type                => 'YesNo',
468         },
469         {
470             uniquefieldrequired => 'variable',
471             variable            => 'BorrowersLog',
472             value               => '0',
473             forceupdate         => {'explanation' => 1, 'type' => 1},
474             explanation         => 'Active this if you want to log borrowers edition/creation/deletion...',
475             type                => 'YesNo',
476         },
477         {
478             uniquefieldrequired => 'variable',
479             variable            => 'SubscriptionLog',
480             value               => '0',
481             forceupdate         => {'explanation' => 1, 'type' => 1},
482             explanation         => 'Active this if you want to log Subscription action',
483             type                => 'YesNo',
484         },
485         {
486             uniquefieldrequired => 'variable',
487             variable            => 'IssueLog',
488             value               => '0',
489             forceupdate         => {'explanation' => 1, 'type' => 1},
490             explanation         => 'Active this if you want to log issue.',
491             type                => 'YesNo',
492         },
493         {
494             uniquefieldrequired => 'variable',
495             variable            => 'ReturnLog',
496             value               => '0',
497             forceupdate         => {'explanation' => 1, 'type' => 1},
498             explanation         => 'Active this if you want to log the circulation return',
499             type                => 'YesNo',
500         },
501         {
502             uniquefieldrequired => 'variable',
503             variable            => 'Version',
504             value               => '3.0',
505             forceupdate         => {'explanation' => 1, 'type' => 1},
506             explanation         => 'Koha Version',
507             type                => 'Free',
508         },
509         {   
510             uniquefieldrequired => 'variable',
511             variable            => 'LetterLog',
512             value               => '0',
513             forceupdate         => {'explanation' => 1, 'type' => 1},
514             explanation         => 'Active this if you want to log all the letter sent',
515             type                => 'YesNo',
516         },
517         {
518             uniquefieldrequired => 'variable',
519             variable            => 'FinesLog',
520             value               => '0',
521             forceupdate         => {'explanation' => 1, 'type' => 1},
522             explanation         => 'Active this if you want to log fines',
523             type                => 'YesNo',
524         },
525         {
526             uniquefieldrequired => 'variable',
527             variable            => 'NoZebra',
528             value               => '0',
529             forceupdate         => {'explanation' => 1, 'type' => 1},
530             explanation         => 'Active this if you want NOT to use zebra (large libraries should avoid this parameters)',
531             type                => 'YesNo',
532         },
533         {
534             uniquefieldrequired => 'variable',
535             variable            => 'NoZebraIndexes',
536             value               => '0',
537             forceupdate         => {'explanation' => 1, 'type' => 1},
538             explanation         => "Enter a specific hash for NoZebra indexes. Enter : 'indexname' => '100a,245a,500*','index2' => '...'",
539             type                => 'Free',
540         },
541     ],
542     userflags => [
543         {
544             uniquefieldrequired => 'bit',
545             bit                 => '14',
546             flag                => 'editauthorities',
547             flagdesc            => 'allow to edit authorities',
548             defaulton           => '0',
549         },
550         {
551             uniquefieldrequired => 'bit',
552             bit                 => '15',
553             flag                 => 'serials',
554             flagdesc            => 'allow to manage serials subscriptions',
555             defaulton           => '0',
556         },
557         {
558             uniquefieldrequired => 'bit',
559             bit                 => '16',
560             flag                 => 'reports',
561             flagdesc            => 'allow to access to the reports module',
562             defaulton           => '0',
563         },
564     ],
565     authorised_values => [
566         {
567             uniquefieldrequired => 'id',
568             category            => 'SUGGEST',
569             authorised_value    => 'Not enough budget',
570             lib                 => 'This book it too much expensive',
571         }
572     ],
573 );
574
575 my %fielddefinitions = (
576 # fieldname => [
577 #    {          field => 'fieldname',
578 #             type    => 'fieldtype',
579 #             null    => '',
580 #             key     => '',
581 #             default => ''
582 #         },
583 #     ],
584     aqbasket =>  [
585         {
586             field    => 'booksellerid',
587             type    => 'int(11)',
588             null    => 'NOT NULL',
589             key        => '',
590             default    => '1',
591             extra    => '',
592         },
593     ],
594     aqbooksellers =>  [
595         {
596             field    => 'id',
597             type    => 'int(11)',
598             null    => 'NOT NULL',
599             key        => '',
600             default    => '',
601             extra    => 'auto_increment',
602         },
603         {
604             field    => 'listprice',
605             type    => 'varchar(10)',
606             null    => 'NULL',
607             key        => '',
608             default    => '',
609             extra    => '',
610         },
611         {
612             field    => 'invoiceprice',
613             type    => 'varchar(10)',
614             null    => 'NULL',
615             key        => '',
616             default    => '',
617             extra    => '',
618         },
619     ],
620     
621     accountlines =>  [
622         {
623             field    => 'notify_id',
624             type    => 'int(11)',
625             null    => 'NOT NULL',
626             key        => '',
627             default    => '0',
628             extra    => '',
629         },
630         {
631             field    => 'notify_level',
632             type    => 'int(2)',
633             null    => 'NOT NULL',
634             key        => '',
635             default    => '0',
636             extra    => '',
637         },
638     
639     ],
640     
641     borrowers => [
642         {    field => 'firstname',
643              type => 'text',
644              null => 'NULL',
645          },
646         {    field => 'initials',
647              type => 'text',
648              null => 'NULL',
649          },
650         {    field => 'B_email',
651              type => 'text',
652              null => 'NULL',
653              after => 'B_zipcode',
654          },
655          {
656             field => 'streetnumber', # street number (hidden if streettable table is empty)
657             type => 'char(10)',
658             null => 'NULL',
659             after => 'initials',
660         },
661         {
662             field => 'streettype', # street table, list builded from a system table
663             type => 'char(50)',
664             null => 'NULL',
665             after => 'streetnumber',
666         },
667         {    field => 'phone',
668              type => 'text',
669              null => 'NULL',
670          },
671         {
672             field => 'B_streetnumber', # street number (hidden if streettable table is empty)
673             type => 'char(10)',
674             null => 'NULL',
675             after => 'fax',
676         },
677         {
678             field => 'B_streettype', # street table, list builded from a system table
679             type => 'char(50)',
680             null => 'NULL',
681             after => 'B_streetnumber',
682         },
683         {
684             field => 'phonepro',
685             type => 'text',
686             null => 'NULL',
687             after => 'fax',
688         },
689         {
690             field => 'address2', # complement address
691             type => 'text',
692             null => 'NULL',
693             after => 'address',
694         },
695         {
696             field => 'emailpro',
697             type => 'text',
698             null => 'NULL',
699             after => 'fax',
700         },
701         {
702             field => 'contactfirstname', # contact's firstname
703             type => 'text',
704             null => 'NULL',
705             after => 'contactname',
706         },
707         {
708             field => 'contacttitle', # contact's title
709             type => 'text',
710             null => 'NULL',
711             after => 'contactfirstname',
712         },
713         {
714             field => 'branchcode',
715             type  => 'varchar(10)',
716             null  => 'NOT NULL',
717             default    => '',
718             extra => '',
719         },
720         {
721             field => 'categorycode',
722             type  => 'varchar(10)',
723             null  => 'NOT NULL',
724             default    => '',
725             extra => '',
726         }
727     ],
728     
729     biblioitems =>  [
730         {
731             field    => 'lcsort',
732             type    => 'varchar(25)',
733             null    => 'NULL',
734             key        => '',
735             default    => '',
736             extra    => '',
737         },
738         {
739             field    => 'ccode',
740             type    => 'varchar(4)',
741             null    => 'NULL',
742             key        => '',
743             default    => '',
744             extra    => '',
745         },
746     ],
747     branches =>  [
748         {
749             field    => 'branchip',
750             type    => 'varchar(15)',
751             null    => 'NULL',
752             key        => '',
753             default    => '',
754             extra    => '',
755         },
756         {
757             field    => 'branchprinter',
758             type    => 'varchar(100)',
759             null    => 'NULL',
760             key        => '',
761             default    => '',
762             extra    => '',
763         },
764         {
765             field   => 'branchcode',
766             type    => 'varchar(10)',
767             null    => 'NOT NULL',
768             default => '',
769             extra   => '',
770         }
771     ],
772     branchtransfers =>[
773         {
774             field   => 'frombranch',
775             type    => 'VARCHAR(10)',
776             null    => 'NOT NULL',
777             key     => '',
778             default => '',
779             extra   => '',
780         },
781         {
782             field   => 'tobranch',
783             type    => 'VARCHAR(10)',
784             null    => 'NOT NULL',
785             key     => '',
786             default => '',
787         }
788     ],
789     
790     categories =>  [
791         {
792             field    => 'category_type',
793             type    => 'char(1)',
794             null    => 'NOT NULL',
795             key        => '',
796             default    => 'A',
797             extra    => '',
798         },
799         {
800             field   => 'categorycode',
801             type    => 'varchar(10)',
802             null    => 'NOT NULL',
803             key     => 'PRI',
804             default => '',
805             extra   => '',
806         },
807     ],
808     
809     deletedborrowers => [
810         {    field => 'firstname',
811              type => 'text',
812              null => 'NULL',
813          },
814         {    field => 'initials',
815              type => 'text',
816              null => 'NULL',
817          },
818         {    field => 'B_email',
819              type => 'text',
820              null => 'NULL',
821              after => 'B_zipcode',
822          },
823          {
824             field => 'streetnumber', # street number (hidden if streettable table is empty)
825             type => 'char(10)',
826             null => 'NULL',
827             after => 'initials',
828         },
829         {
830             field => 'streettype', # street table, list builded from a system table
831             type => 'char(50)',
832             null => 'NULL',
833             after => 'streetnumber',
834         },
835         {    field => 'phone',
836              type => 'text',
837              null => 'NULL',
838          },
839          {
840             field => 'B_streetnumber', # street number (hidden if streettable table is empty)
841             type => 'char(10)',
842             null => 'NULL',
843             after => 'fax',
844         },
845         {
846             field => 'B_streettype', # street table, list builded from a system table
847             type => 'char(50)',
848             null => 'NULL',
849             after => 'B_streetnumber',
850         },
851         {
852             field => 'phonepro',
853             type => 'text',
854             null => 'NULL',
855             after => 'fax',
856         },
857         {
858             field => 'address2', # complement address
859             type => 'text',
860             null => 'NULL',
861             after => 'address',
862         },
863         {
864             field => 'emailpro',
865             type => 'text',
866             null => 'NULL',
867             after => 'fax',
868         },
869         {
870             field => 'contactfirstname', # contact's firstname
871             type => 'text',
872             null => 'NULL',
873             after => 'contactname',
874         },
875         {
876             field => 'contacttitle', # contact's title
877             type => 'text',
878             null => 'NULL',
879             after => 'contactfirstname',
880         },
881     ],
882     
883     issues =>  [
884         {
885             field    => 'borrowernumber',
886             type    => 'int(11)',
887             null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
888             key        => '',
889             default    => '',
890             extra    => '',
891         },
892         {
893             field    => 'itemnumber',
894             type    => 'int(11)',
895             null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
896             key        => '',
897             default    => '',
898             extra    => '',
899         },
900         {
901             field   => 'branchcode',
902             type    => 'varchar(10)',
903             null    => 'NULL',
904             key     => '',
905             default => '',
906             extra   => '',
907         },
908         {
909             field   => 'issuedate',
910             type    => 'date',
911             null    => '',
912             key     => '',
913             default => '0000-00-00',
914             extra   => '',
915         },
916     ],
917     
918     items => [
919         {
920             field    => 'onloan',
921             type    => 'date',
922             null    => 'NULL',
923             key        => '',
924             default    => '0000-00-00',
925             extra    => '',
926         },
927         {
928             field    => 'cutterextra',
929             type    => 'varchar(45)',
930             null    => 'NULL',
931             key        => '',
932             default    => '',
933             extra    => '',
934         },
935         {
936             field    => 'issue_date',
937             type    => 'date',
938             null    => 'NULL',
939             key        => '',
940             default    => '',
941             extra    => '',
942         },
943         {
944             field    => 'homebranch',
945             type    => 'varchar(10)',
946             null    => 'NULL',
947             key        => '',
948             default    => '',
949             extra    => '',
950         },
951         {
952             field    => 'holdingbranch',
953             type    => 'varchar(10)',
954             null    => 'NULL',
955             key        => '',
956             default    => '',
957             extra    => '',
958         },
959         {
960             field    => 'itype',
961             type    => 'varchar(10)',
962             null    => 'NULL',
963             key        => '',
964             default    => '',
965             extra    => '',
966         },
967     ],
968     itemtypes => [
969         {
970             field  => 'itemtype',
971             type   => 'varchar(10)',
972             default    => '',
973             null   => 'NOT NULL',
974             key    => 'PRI',
975             extra  => 'UNIQUE',
976         },
977         {
978             field  => 'summary',
979             type   => 'TEXT',
980             null   => 'NULL',
981             key    => '',
982             extra  => '',
983         },
984     ],
985     marc_subfield_structure => [
986         {
987             field => 'defaultvalue',
988             type  => 'TEXT',
989             null  => 'NULL',
990             key    => '',
991             extra  => '',
992         }
993     ],
994     opac_news => [
995         {
996             field  => 'expirationdate',
997             type   => 'date',
998             null   => 'null',
999             key    => '',
1000             extra  => '',
1001         },
1002         {
1003             field   => 'number',
1004             type    => 'int(11)',
1005             null    => 'NULL',
1006             key     => '',
1007             default => '0',
1008             extra   => '',
1009         },
1010     ],
1011     reserves =>  [
1012         {
1013             field    => 'waitingdate',
1014             type    => 'date',
1015             null    => 'NULL',
1016             key        => '',
1017             default    => '',
1018             extra    => '',
1019         },
1020     ],
1021     serial => [
1022         {
1023             field   => 'notes',
1024             type    => 'TEXT',
1025             null    => 'NULL',
1026             key     => '',
1027             default => '',
1028             extra   => ''
1029         },
1030     ],
1031     shelfcontents => [
1032         {
1033             field => 'dateadded',
1034             type => 'timestamp',
1035             null    => 'NULL',
1036         },
1037     ],
1038     systempreferences =>  [
1039         {
1040             field    => 'value',
1041             type    => 'text',
1042             null    => 'NULL',
1043             key        => '',
1044             default    => '',
1045             extra    => '',
1046         },
1047         {
1048             field    => 'explanation',
1049             type    => 'text',
1050             null    => 'NULL',
1051             key        => '',
1052             default    => '',
1053             extra    => '',
1054         },
1055     ],
1056     suggestions => [
1057         {
1058             field   => 'reason',
1059             type    => 'text',
1060             null    => 'NULL',
1061             key     => ''  ,
1062             default => '',
1063             extra   =>    '',
1064         }
1065     ],
1066 );
1067
1068 my %indexes = (
1069 #    table => [
1070 #         {    indexname => 'index detail'
1071 #         }
1072 #    ],
1073     aqbooksellers => [
1074         {    indexname => 'PRIMARY',
1075             content => 'id',
1076             type => 'PRI',
1077         }
1078     ],
1079     aqbasket => [
1080         {    indexname => 'booksellerid',
1081             content => 'booksellerid',
1082         },
1083     ],
1084     aqorders => [
1085         {    indexname => 'basketno',
1086             content => 'basketno',
1087         },
1088     ],
1089     aqorderbreakdown => [
1090         {    indexname => 'ordernumber',
1091             content => 'ordernumber',
1092         },
1093         {    indexname => 'bookfundid',
1094             content => 'bookfundid',
1095         },
1096     ],
1097     biblioitems => [
1098         {    indexname => 'isbn',
1099             content => 'isbn',
1100         },
1101         {    indexname => 'publishercode',
1102             content => 'publishercode',
1103         },
1104     ],
1105     branches => [
1106         {
1107             indexname => 'branchcode',
1108             content   => 'branchcode',
1109             type => 'PRI',
1110         }
1111     ],
1112     branchrelations => [
1113         {
1114             indexname => 'PRIMARY',
1115             content => 'categorycode',
1116             type => 'PRI',
1117         }
1118     ],
1119     branchrelations => [
1120         {    indexname => 'PRIMARY',
1121             content => 'branchcode,categorycode',
1122             type => 'PRI',
1123         },
1124         {    indexname => 'branchcode',
1125             content => 'branchcode',
1126         },
1127         {    indexname => 'categorycode',
1128             content => 'categorycode',
1129         }
1130     ],
1131     currency => [
1132         {    indexname => 'PRIMARY',
1133             content => 'currency',
1134             type => 'PRI',
1135         }
1136     ],
1137     categories => [
1138         {
1139             indexname => 'categorycode',
1140             content   => 'categorycode',
1141         }
1142     ],
1143     items => [
1144         {    indexname => 'homebranch',
1145             content => 'homebranch',
1146         },
1147         {    indexname => 'holdingbranch',
1148             content => 'holdingbranch',
1149         }
1150     ],
1151     itemtypes => [
1152         {
1153             indexname => 'itemtype',
1154             content   => 'itemtype',
1155         }
1156     ],
1157     shelfcontents => [
1158         {    indexname => 'shelfnumber',
1159             content => 'shelfnumber',
1160         },
1161         {    indexname => 'itemnumber',
1162             content => 'itemnumber',
1163         }
1164     ],
1165         userflags => [
1166                 {       indexname => 'PRIMARY',
1167                         content => 'bit',
1168                         type => 'PRI',
1169                 }
1170         ]
1171 );
1172
1173 my %foreign_keys = (
1174 #    table => [
1175 #         {    key => 'the key in table' (must be indexed)
1176 #            foreigntable => 'the foreigntable name', # (the parent)
1177 #            foreignkey => 'the foreign key column(s)' # (in the parent)
1178 #            onUpdate => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
1179 #            onDelete => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
1180 #         }
1181 #    ],
1182     branchrelations => [
1183         {    key => 'branchcode',
1184             foreigntable => 'branches',
1185             foreignkey => 'branchcode',
1186             onUpdate => 'CASCADE',
1187             onDelete => 'CASCADE',
1188         },
1189         {    key => 'categorycode',
1190             foreigntable => 'branchcategories',
1191             foreignkey => 'categorycode',
1192             onUpdate => 'CASCADE',
1193             onDelete => 'CASCADE',
1194         },
1195     ],
1196     shelfcontents => [
1197         {    key => 'shelfnumber',
1198             foreigntable => 'bookshelf',
1199             foreignkey => 'shelfnumber',
1200             onUpdate => 'CASCADE',
1201             onDelete => 'CASCADE',
1202         },
1203         {    key => 'itemnumber',
1204             foreigntable => 'items',
1205             foreignkey => 'itemnumber',
1206             onUpdate => 'CASCADE',
1207             onDelete => 'CASCADE',
1208         },
1209     ],
1210     # onDelete is RESTRICT on reference tables (branches, itemtype) as we don't want items to be
1211     # easily deleted, but branches/itemtype not too easy to empty...
1212     biblioitems => [
1213         {    key => 'biblionumber',
1214             foreigntable => 'biblio',
1215             foreignkey => 'biblionumber',
1216             onUpdate => 'CASCADE',
1217             onDelete => 'CASCADE',
1218         },
1219         {    key => 'itemtype',
1220             foreigntable => 'itemtypes',
1221             foreignkey => 'itemtype',
1222             onUpdate => 'CASCADE',
1223             onDelete => 'RESTRICT',
1224         },
1225     ],
1226     items => [
1227         {    key => 'biblioitemnumber',
1228             foreigntable => 'biblioitems',
1229             foreignkey => 'biblioitemnumber',
1230             onUpdate => 'CASCADE',
1231             onDelete => 'CASCADE',
1232         },
1233         {    key => 'homebranch',
1234             foreigntable => 'branches',
1235             foreignkey => 'branchcode',
1236             onUpdate => 'CASCADE',
1237             onDelete => 'RESTRICT',
1238         },
1239         {    key => 'holdingbranch',
1240             foreigntable => 'branches',
1241             foreignkey => 'branchcode',
1242             onUpdate => 'CASCADE',
1243             onDelete => 'RESTRICT',
1244         },
1245     ],
1246     aqbasket => [
1247         {    key => 'booksellerid',
1248             foreigntable => 'aqbooksellers',
1249             foreignkey => 'id',
1250             onUpdate => 'CASCADE',
1251             onDelete => 'RESTRICT',
1252         },
1253     ],
1254     aqorders => [
1255         {    key => 'basketno',
1256             foreigntable => 'aqbasket',
1257             foreignkey => 'basketno',
1258             onUpdate => 'CASCADE',
1259             onDelete => 'CASCADE',
1260         },
1261         {    key => 'biblionumber',
1262             foreigntable => 'biblio',
1263             foreignkey => 'biblionumber',
1264             onUpdate => 'SET NULL',
1265             onDelete => 'SET NULL',
1266         },
1267     ],
1268     aqbooksellers => [
1269         {    key => 'listprice',
1270             foreigntable => 'currency',
1271             foreignkey => 'currency',
1272             onUpdate => 'CASCADE',
1273             onDelete => 'CASCADE',
1274         },
1275         {    key => 'invoiceprice',
1276             foreigntable => 'currency',
1277             foreignkey => 'currency',
1278             onUpdate => 'CASCADE',
1279             onDelete => 'CASCADE',
1280         },
1281     ],
1282     aqorderbreakdown => [
1283         {    key => 'ordernumber',
1284             foreigntable => 'aqorders',
1285             foreignkey => 'ordernumber',
1286             onUpdate => 'CASCADE',
1287             onDelete => 'CASCADE',
1288         },
1289         {    key => 'bookfundid',
1290             foreigntable => 'aqbookfund',
1291             foreignkey => 'bookfundid',
1292             onUpdate => 'CASCADE',
1293             onDelete => 'CASCADE',
1294         },
1295     ],
1296     branchtransfers => [
1297         {    key => 'frombranch',
1298             foreigntable => 'branches',
1299             foreignkey => 'branchcode',
1300             onUpdate => 'CASCADE',
1301             onDelete => 'CASCADE',
1302         },
1303         {    key => 'tobranch',
1304             foreigntable => 'branches',
1305             foreignkey => 'branchcode',
1306             onUpdate => 'CASCADE',
1307             onDelete => 'CASCADE',
1308         },
1309         {    key => 'itemnumber',
1310             foreigntable => 'items',
1311             foreignkey => 'itemnumber',
1312             onUpdate => 'CASCADE',
1313             onDelete => 'CASCADE',
1314         },
1315     ],
1316     issuingrules => [
1317         {    key => 'categorycode',
1318             foreigntable => 'categories',
1319             foreignkey => 'categorycode',
1320             onUpdate => 'CASCADE',
1321             onDelete => 'CASCADE',
1322         },
1323         {    key => 'itemtype',
1324             foreigntable => 'itemtypes',
1325             foreignkey => 'itemtype',
1326             onUpdate => 'CASCADE',
1327             onDelete => 'CASCADE',
1328         },
1329     ],
1330     issues => [    # constraint is SET NULL : when a borrower or an item is deleted, we keep the issuing record
1331     # for stat purposes
1332         {    key => 'borrowernumber',
1333             foreigntable => 'borrowers',
1334             foreignkey => 'borrowernumber',
1335             onUpdate => 'SET NULL',
1336             onDelete => 'SET NULL',
1337         },
1338         {    key => 'itemnumber',
1339             foreigntable => 'items',
1340             foreignkey => 'itemnumber',
1341             onUpdate => 'SET NULL',
1342             onDelete => 'SET NULL',
1343         },
1344     ],
1345     reserves => [
1346         {    key => 'borrowernumber',
1347             foreigntable => 'borrowers',
1348             foreignkey => 'borrowernumber',
1349             onUpdate => 'CASCADE',
1350             onDelete => 'CASCADE',
1351         },
1352         {    key => 'biblionumber',
1353             foreigntable => 'biblio',
1354             foreignkey => 'biblionumber',
1355             onUpdate => 'CASCADE',
1356             onDelete => 'CASCADE',
1357         },
1358         {    key => 'itemnumber',
1359             foreigntable => 'items',
1360             foreignkey => 'itemnumber',
1361             onUpdate => 'CASCADE',
1362             onDelete => 'CASCADE',
1363         },
1364         {    key => 'branchcode',
1365             foreigntable => 'branches',
1366             foreignkey => 'branchcode',
1367             onUpdate => 'CASCADE',
1368             onDelete => 'CASCADE',
1369         },
1370     ],
1371     borrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
1372     # but prevent deleting a branch as soon as it has 1 borrower !
1373         {    key => 'categorycode',
1374             foreigntable => 'categories',
1375             foreignkey => 'categorycode',
1376             onUpdate => 'RESTRICT',
1377             onDelete => 'RESTRICT',
1378         },
1379         {    key => 'branchcode',
1380             foreigntable => 'branches',
1381             foreignkey => 'branchcode',
1382             onUpdate => 'RESTRICT',
1383             onDelete => 'RESTRICT',
1384         },
1385     ],
1386     deletedborrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
1387     # but prevent deleting a branch as soon as it has 1 borrower !
1388         {    key => 'categorycode',
1389             foreigntable => 'categories',
1390             foreignkey => 'categorycode',
1391             onUpdate => 'RESTRICT',
1392             onDelete => 'RESTRICT',
1393         },
1394         {    key => 'branchcode',
1395             foreigntable => 'branches',
1396             foreignkey => 'branchcode',
1397             onUpdate => 'RESTRICT',
1398             onDelete => 'RESTRICT',
1399         },
1400     ],
1401     accountlines => [
1402         {    key => 'borrowernumber',
1403             foreigntable => 'borrowers',
1404             foreignkey => 'borrowernumber',
1405             onUpdate => 'CASCADE',
1406             onDelete => 'CASCADE',
1407         },
1408         {    key => 'itemnumber',
1409             foreigntable => 'items',
1410             foreignkey => 'itemnumber',
1411             onUpdate => 'SET NULL',
1412             onDelete => 'SET NULL',
1413         },
1414     ],
1415     auth_tag_structure => [
1416         {    key => 'authtypecode',
1417             foreigntable => 'auth_types',
1418             foreignkey => 'authtypecode',
1419             onUpdate => 'CASCADE',
1420             onDelete => 'CASCADE',
1421         },
1422     ],
1423     # FIXME : don't constraint auth_*_table and auth_word, as they may be replaced by zebra
1424 );
1425
1426
1427 # column changes
1428 my %column_change = (
1429     # table
1430     borrowers => [
1431                 {
1432                     from => 'emailaddress',
1433                     to => 'email',
1434                     after => 'city',
1435                 },
1436                 {
1437                     from => 'streetaddress',
1438                     to => 'address',
1439                     after => 'initials',
1440                 },
1441                 {
1442                     from => 'faxnumber',
1443                     to => 'fax',
1444                     after => 'phone',
1445                 },
1446                 {
1447                     from => 'textmessaging',
1448                     to => 'opacnote',
1449                     after => 'userid',
1450                 },
1451                 {
1452                     from => 'altnotes',
1453                     to => 'contactnote',
1454                     after => 'opacnote',
1455                 },
1456                 {
1457                     from => 'physstreet',
1458                     to => 'B_address',
1459                     after => 'fax',
1460                 },
1461                 {
1462                     from => 'streetcity',
1463                     to => 'B_city',
1464                     after => 'B_address',
1465                 },
1466                 {
1467                     from => 'phoneday',
1468                     to => 'mobile',
1469                     after => 'phone',
1470                 },
1471                 {
1472                     from => 'zipcode',
1473                     to => 'zipcode',
1474                     after => 'city',
1475                 },
1476                 {
1477                     from => 'homezipcode',
1478                     to => 'B_zipcode',
1479                     after => 'B_city',
1480                 },
1481                 {
1482                     from => 'altphone',
1483                     to => 'B_phone',
1484                     after => 'B_zipcode',
1485                 },
1486                 {
1487                     from => 'expiry',
1488                     to => 'dateexpiry',
1489                     after => 'dateenrolled',
1490                 },
1491                 {
1492                     from => 'guarantor',
1493                     to => 'guarantorid',
1494                     after => 'contactname',
1495                 },
1496                 {
1497                     from => 'altrelationship',
1498                     to => 'relationship',
1499                     after => 'borrowernotes',
1500                 },
1501             ],
1502
1503     deletedborrowers => [
1504                 {
1505                     from => 'emailaddress',
1506                     to => 'email',
1507                     after => 'city',
1508                 },
1509                 {
1510                     from => 'streetaddress',
1511                     to => 'address',
1512                     after => 'initials',
1513                 },
1514                 {
1515                     from => 'faxnumber',
1516                     to => 'fax',
1517                     after => 'phone',
1518                 },
1519                 {
1520                     from => 'textmessaging',
1521                     to => 'opacnote',
1522                     after => 'userid',
1523                 },
1524                 {
1525                     from => 'altnotes',
1526                     to => 'contactnote',
1527                     after => 'opacnote',
1528                 },
1529                 {
1530                     from => 'physstreet',
1531                     to => 'B_address',
1532                     after => 'fax',
1533                 },
1534                 {
1535                     from => 'streetcity',
1536                     to => 'B_city',
1537                     after => 'B_address',
1538                 },
1539                 {
1540                     from => 'phoneday',
1541                     to => 'mobile',
1542                     after => 'phone',
1543                 },
1544                 {
1545                     from => 'zipcode',
1546                     to => 'zipcode',
1547                     after => 'city',
1548                 },
1549                 {
1550                     from => 'homezipcode',
1551                     to => 'B_zipcode',
1552                     after => 'B_city',
1553                 },
1554                 {
1555                     from => 'altphone',
1556                     to => 'B_phone',
1557                     after => 'B_zipcode',
1558                 },
1559                 {
1560                     from => 'expiry',
1561                     to => 'dateexpiry',
1562                     after => 'dateenrolled',
1563                 },
1564                 {
1565                     from => 'guarantor',
1566                     to => 'guarantorid',
1567                     after => 'contactname',
1568                 },
1569                 {
1570                     from => 'altrelationship',
1571                     to => 'relationship',
1572                     after => 'borrowernotes',
1573                 },
1574             ],
1575         );
1576     
1577
1578 # MOVE all tables TO UTF-8 and innoDB
1579 $sth = $dbh->prepare("show table status");
1580 $sth->execute;
1581 while ( my $table = $sth->fetchrow_hashref ) {
1582     next if $table->{Name} eq 'marc_word';
1583     next if $table->{Name} eq 'marc_subfield_table';
1584     next if $table->{Name} eq 'auth_word';
1585     next if $table->{Name} eq 'auth_subfield_table';
1586      if ($table->{Engine} ne 'InnoDB') {
1587          $dbh->do("ALTER TABLE $table->{Name} TYPE = innodb");
1588          print "moving $table->{Name} to InnoDB\n";
1589      }
1590     unless ($table->{Collation} =~ /^utf8/) {
1591          print "moving $table->{Name} to utf8\n";
1592         $dbh->do("ALTER TABLE $table->{Name} CONVERT TO CHARACTER SET utf8");
1593         $dbh->do("ALTER TABLE $table->{Name} DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci");
1594         # 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 !
1595     } else {
1596     }
1597 }
1598
1599
1600 foreach my $table (keys %column_change) {
1601     $sth = $dbh->prepare("show columns from $table");
1602     $sth->execute();
1603     undef %types;
1604     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1605     {
1606         $types{$column}->{type} ="$type";
1607         $types{$column}->{null} = "$null";
1608         $types{$column}->{key} = "$key";
1609         $types{$column}->{default} = "$default";
1610         $types{$column}->{extra} = "$extra";
1611     }    # while
1612     my $tablerows = $column_change{$table};
1613     foreach my $row ( @$tablerows ) {
1614         if ($types{$row->{from}}->{type}) {
1615             print "altering $table $row->{from} to $row->{to}\n";
1616             # ALTER TABLE `borrowers` CHANGE `faxnumber` `fax` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
1617 #             alter table `borrowers` change `faxnumber` `fax` type text  null after phone
1618             my $sql =
1619                 "alter table `$table` change `$row->{from}` `$row->{to}` $types{$row->{from}}->{type} ".
1620                 ($types{$row->{from}}->{null} eq 'YES'?" NULL":" NOT NULL").
1621                 ($types{$row->{from}}->{default}?" default ".$types{$row->{from}}->{default}:"").
1622                 "$types{$row->{from}}->{extra} after $row->{after} ";
1623 #             print "$sql";
1624             $dbh->do($sql);
1625         }
1626     }
1627 }
1628
1629 # Enter here the field you want to delete from DB.
1630 # FIXME :: there is a %uselessfield before which seems doing the same things.
1631 my %fieldtodelete = (
1632     # tablename => [fieldname1,fieldname2,...]
1633
1634 ); # %fielddelete
1635
1636 print "removing some unused fields...\n";
1637 foreach my $table ( keys %fieldtodelete ) {
1638     foreach my $field ( @{$fieldtodelete{$table}} ){
1639         print "removing ".$field." from ".$table;
1640         my $sth = $dbh->prepare("ALTER TABLE $table DROP $field");
1641         $sth->execute;
1642         if ( $sth->err ) {
1643             print "Error : $sth->errstr \n";
1644         }
1645     }
1646 }
1647
1648 # Enter here the line you want to remove from DB.
1649 my %linetodelete = (
1650     # table name => where clause.
1651     userflags => "bit = 8", # delete the 'reserveforself' flags
1652     
1653 ); # %linetodelete
1654
1655 #-------------------
1656 # Initialize
1657
1658 # Start checking
1659
1660 # Get version of MySQL database engine.
1661 my $mysqlversion = `mysqld --version`;
1662 $mysqlversion =~ /Ver (\S*) /;
1663 $mysqlversion = $1;
1664 if ( $mysqlversion ge '3.23' ) {
1665     print "Could convert to MyISAM database tables...\n" unless $silent;
1666 }
1667
1668 #---------------------------------
1669 # Tables
1670
1671 # Collect all tables into a list
1672 $sth = $dbh->prepare("show tables");
1673 $sth->execute;
1674 while ( my ($table) = $sth->fetchrow ) {
1675     $existingtables{$table} = 1;
1676 }
1677
1678
1679 # Now add any missing tables
1680 foreach $table ( keys %requiretables ) {
1681     unless ( $existingtables{$table} ) {
1682     print "Adding $table table...\n" unless $silent;
1683         my $sth = $dbh->prepare("create table $table $requiretables{$table}");
1684         $sth->execute;
1685         if ( $sth->err ) {
1686             print "Error : $sth->errstr \n";
1687             $sth->finish;
1688         }    # if error
1689     }    # unless exists
1690 }    # foreach
1691
1692 #---------------------------------
1693 # Columns
1694
1695 foreach $table ( keys %requirefields ) {
1696     print "Check table $table\n" if $debug and not $silent;
1697     $sth = $dbh->prepare("show columns from $table");
1698     $sth->execute();
1699     undef %types;
1700     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1701     {
1702         $types{$column} = $type;
1703     }    # while
1704     foreach $column ( keys %{ $requirefields{$table} } ) {
1705         print "  Check column $column  [$types{$column}]\n" if $debug and not $silent;
1706         if ( !$types{$column} ) {
1707
1708             # column doesn't exist
1709             print "Adding $column field to $table table...\n" unless $silent;
1710             $query = "alter table $table
1711             add column $column " . $requirefields{$table}->{$column};
1712             print "Execute: $query\n" if $debug;
1713             my $sti = $dbh->prepare($query);
1714             $sti->execute;
1715             if ( $sti->err ) {
1716                 print "**Error : $sti->errstr \n";
1717                 $sti->finish;
1718             }    # if error
1719         }    # if column
1720     }    # foreach column
1721 }    # foreach table
1722
1723 foreach $table ( keys %fielddefinitions ) {
1724     print "Check table $table\n" if $debug;
1725     $sth = $dbh->prepare("show columns from $table");
1726     $sth->execute();
1727     my $definitions;
1728     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1729     {
1730         $definitions->{$column}->{type}    = $type;
1731         $definitions->{$column}->{null}    = $null;
1732         $definitions->{$column}->{null}    = 'NULL' if $null eq 'YES';
1733         $definitions->{$column}->{key}     = $key;
1734         $definitions->{$column}->{default} = $default;
1735         $definitions->{$column}->{extra}   = $extra;
1736     }    # while
1737     my $fieldrow = $fielddefinitions{$table};
1738     foreach my $row (@$fieldrow) {
1739         my $field   = $row->{field};
1740         my $type    = $row->{type};
1741         my $null    = $row->{null};
1742 #         $null    = 'YES' if $row->{null} eq 'NULL';
1743         my $key     = $row->{key};
1744         my $default = $row->{default};
1745 #         $default="''" unless $default;
1746         my $extra   = $row->{extra};
1747         my $def     = $definitions->{$field};
1748         my $after    = ($row->{after}?" after ".$row->{after}:"");
1749
1750         unless ( $type eq $def->{type}
1751             && $null eq $def->{null}
1752             && $key eq $def->{key}
1753             && $extra eq $def->{extra} )
1754         {
1755             if ( $null eq '' ) {
1756                 $null = 'NOT NULL';
1757             }
1758             if ( $key eq 'PRI' ) {
1759                 $key = 'PRIMARY KEY';
1760             }
1761             unless ( $extra eq 'auto_increment' ) {
1762                 $extra = '';
1763             }
1764     
1765             # if it's a new column use "add", if it's an old one, use "change".
1766             my $action;
1767             if ($definitions->{$field}->{type}) {
1768                 $action="change $field"
1769             } else {
1770                 $action="add";
1771             }
1772 # if it's a primary key, drop the previous pk, before altering the table
1773             print "  alter or create $field in $table\n" unless $silent;
1774             my $query;
1775             if ($key ne 'PRIMARY KEY') {
1776 #                 warn "alter table $table $action $field $type $null $key $extra default $default $after";
1777                 $query = "alter table $table $action $field $type $null $key $extra ".($default?"default ".$dbh->quote($default):"")." $after";
1778             } else {
1779 #             warn "alter table $table drop primary key, $action $field $type $null $key $extra default $default $after";
1780                  # something strange : for indexes UNIQUE, they are reported as primary key here.
1781                  # but if you try to run with drop primary key, it fails.
1782                  # thus, we run the query twice, one will fail, one will succeed.
1783                  # strange...
1784                 $query="alter table $table drop primary key, $action $field $type $null $key $extra ".($default?"default ".$dbh->quote($default):"")." $after";
1785                 $query="alter table $table $action $field $type $null $key $extra ".($default?"default ".$dbh->quote($default):"")." $after";
1786             }
1787             $dbh->do($query);
1788         }
1789     }
1790 }
1791
1792 print "removing some unused data...\n";
1793 foreach my $table ( keys %linetodelete ) {
1794     foreach my $where ( @{linetodelete{$table}} ){
1795         print "DELETE FROM ".$table." where ".$where;
1796         print "\n";
1797         my $sth = $dbh->prepare("DELETE FROM $table where $where");
1798         $sth->execute;
1799         if ( $sth->err ) {
1800             print "Error : $sth->errstr \n";
1801         }
1802     }
1803 }
1804
1805 # Populate tables with required data
1806
1807 # synch table and deletedtable.
1808 foreach my $table (('borrowers','items','biblio','biblioitems')) {
1809     my %deletedborrowers;
1810     print "synch'ing $table and deleted$table\n";
1811     $sth = $dbh->prepare("show columns from deleted$table");
1812     $sth->execute;
1813     while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1814         $deletedborrowers{$column}=1;
1815     }
1816     $sth = $dbh->prepare("show columns from $table");
1817     $sth->execute;
1818     my $previous;
1819     while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1820         unless ($deletedborrowers{$column}) {
1821             my $newcol="alter table deleted$table add $column $type";
1822             if ($null eq 'YES') {
1823                 $newcol .= " NULL ";
1824             } else {
1825                 $newcol .= " NOT NULL ";
1826             }
1827             $newcol .= "default ".$dbh->quote($default) if $default;
1828             $newcol .= " after $previous" if $previous;
1829             $previous=$column;
1830             print "creating column $column\n";
1831             $dbh->do($newcol);
1832         }
1833     }
1834 }
1835 #
1836 # update publisheddate 
1837 #
1838 $sth = $dbh->prepare("select count(*) from serial where publisheddate is NULL");
1839 $sth->execute;
1840 my ($emptypublished) = $sth->fetchrow;
1841 if ($emptypublished) {
1842     print "Updating publisheddate\n";
1843     $dbh->do("update serial set publisheddate=planneddate where publisheddate is NULL");
1844 }
1845 foreach my $table ( keys %tabledata ) {
1846     print "Checking for data required in table $table...\n" unless $silent;
1847     my $tablerows = $tabledata{$table};
1848     foreach my $row (@$tablerows) {
1849         my $uniquefieldrequired = $row->{uniquefieldrequired};
1850         my $uniquevalue         = $row->{$uniquefieldrequired};
1851         my $forceupdate         = $row->{forceupdate};
1852         my $sth                 =
1853           $dbh->prepare(
1854 "select $uniquefieldrequired from $table where $uniquefieldrequired=?"
1855         );
1856         $sth->execute($uniquevalue);
1857         if ($sth->rows) {
1858             foreach my $field (keys %$forceupdate) {
1859                 if ($forceupdate->{$field}) {
1860                     my $sth=$dbh->prepare("update systempreferences set $field=? where $uniquefieldrequired=?");
1861                     $sth->execute($row->{$field}, $uniquevalue);
1862                 }
1863             }
1864         } else {
1865             print "Adding row to $table: " unless $silent;
1866             my @values;
1867             my $fieldlist;
1868             my $placeholders;
1869             foreach my $field ( keys %$row ) {
1870                 next if $field eq 'uniquefieldrequired';
1871                 next if $field eq 'forceupdate';
1872                 my $value = $row->{$field};
1873                 push @values, $value;
1874                 print "  $field => $value" unless $silent;
1875                 $fieldlist .= "$field,";
1876                 $placeholders .= "?,";
1877             }
1878             print "\n" unless $silent;
1879             $fieldlist    =~ s/,$//;
1880             $placeholders =~ s/,$//;
1881             print "insert into $table ($fieldlist) values ($placeholders)";
1882             my $sth =
1883             $dbh->prepare(
1884                 "insert into $table ($fieldlist) values ($placeholders)");
1885             $sth->execute(@values);
1886         }
1887     }
1888 }
1889
1890 #
1891 # check indexes and create them when needed
1892 #
1893 print "Checking for index required...\n" unless $silent;
1894 foreach my $table ( keys %indexes ) {
1895     #
1896     # read all indexes from $table
1897     #
1898     $sth = $dbh->prepare("show index from $table");
1899     $sth->execute;
1900     my %existingindexes;
1901     while ( my ( $table, $non_unique, $key_name, $Seq_in_index, $Column_name, $Collation, $cardinality, $sub_part, $Packed, $comment ) = $sth->fetchrow ) {
1902         $existingindexes{$key_name} = 1;
1903     }
1904     # read indexes to check
1905     my $tablerows = $indexes{$table};
1906     foreach my $row (@$tablerows) {
1907         my $key_name=$row->{indexname};
1908         if ($existingindexes{$key_name} eq 1) {
1909 #             print "$key_name existing";
1910         } else {
1911             print "\tCreating index $key_name in $table\n";
1912             my $sql;
1913             if ($row->{indexname} eq 'PRIMARY') {
1914                 $sql = "alter table $table ADD PRIMARY KEY ($row->{content})";
1915             } else {
1916                 $sql = "alter table $table ADD INDEX $key_name ($row->{content}) $row->{type}";
1917             }
1918              $dbh->do($sql);
1919             print "Error $sql : $dbh->err \n" if $dbh->err;
1920         }
1921     }
1922 }
1923
1924 #
1925 # check foreign keys and create them when needed
1926 #
1927 print "Checking for foreign keys required...\n" unless $silent;
1928 foreach my $table ( keys %foreign_keys ) {
1929     #
1930     # read all indexes from $table
1931     #
1932     $sth = $dbh->prepare("show table status like '$table'");
1933     $sth->execute;
1934     my $stat = $sth->fetchrow_hashref;
1935     # read indexes to check
1936     my $tablerows = $foreign_keys{$table};
1937     foreach my $row (@$tablerows) {
1938         my $foreign_table=$row->{foreigntable};
1939         if ($stat->{'Comment'} =~/$foreign_table/) {
1940 #             print "$foreign_table existing\n";
1941         } else {
1942             print "\tCreating foreign key $foreign_table in $table\n";
1943             # first, drop any orphan value in child table
1944             if ($row->{onDelete} ne "RESTRICT") {
1945                 my $sql = "delete from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})";
1946                 $dbh->do($sql);
1947                 print "SQL ERROR: $sql : $dbh->err \n" if $dbh->err;
1948             }
1949             my $sql="alter table $table ADD FOREIGN KEY $row->{key} ($row->{key}) REFERENCES $row->{foreigntable} ($row->{foreignkey})";
1950             $sql .= " on update ".$row->{onUpdate} if $row->{onUpdate};
1951             $sql .= " on delete ".$row->{onDelete} if $row->{onDelete};
1952             $dbh->do($sql);
1953             if ($dbh->err) {
1954                 print "====================
1955 An error occured during :
1956 \t$sql
1957 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).
1958 You can find those values with select
1959 \t$table.* from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})
1960 ====================\n
1961 ";
1962             }
1963         }
1964     }
1965 }
1966 # now drop useless tables
1967 foreach $table ( @TableToDelete ) {
1968     if ( $existingtables{$table} ) {
1969         print "Dropping unused table $table\n" if $debug and not $silent;
1970         $dbh->do("drop table $table");
1971         if ( $dbh->err ) {
1972             print "Error : $dbh->errstr \n";
1973         }
1974     }
1975 }
1976
1977 #
1978 # SPECIFIC STUFF
1979 #
1980 #
1981 # create frameworkcode row in biblio table & fill it with marc_biblio.frameworkcode.
1982 #
1983
1984 # 1st, get how many biblio we will have to do...
1985 $sth = $dbh->prepare('select count(*) from marc_biblio');
1986 $sth->execute;
1987 my ($totaltodo) = $sth->fetchrow;
1988
1989 $sth = $dbh->prepare("show columns from biblio");
1990 $sth->execute();
1991 my $definitions;
1992 my $bibliofwexist=0;
1993 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
1994     $bibliofwexist=1 if $column eq 'frameworkcode';
1995 }
1996 unless ($bibliofwexist) {
1997     print "moving biblioframework to biblio table\n";
1998     $dbh->do('ALTER TABLE `biblio` ADD `frameworkcode` VARCHAR( 4 ) NOT NULL AFTER `biblionumber`');
1999     $sth = $dbh->prepare('select biblionumber,frameworkcode from marc_biblio');
2000     $sth->execute;
2001     my $sth_update = $dbh->prepare('update biblio set frameworkcode=? where biblionumber=?');
2002     my $totaldone=0;
2003     while (my ($biblionumber,$frameworkcode) = $sth->fetchrow) {
2004         $sth_update->execute($frameworkcode,$biblionumber);
2005         $totaldone++;
2006         print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
2007     }
2008     print "\rdone\n";
2009 }
2010
2011 # at last, remove useless fields
2012 foreach $table ( keys %uselessfields ) {
2013     my @fields = split /,/,$uselessfields{$table};
2014     my $fields;
2015     my $exists;
2016     foreach my $fieldtodrop (@fields) {
2017         $fieldtodrop =~ s/\t//g;
2018         $fieldtodrop =~ s/\n//g;
2019         $exists =0;
2020         $sth = $dbh->prepare("show columns from $table");
2021         $sth->execute;
2022         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
2023         {
2024             $exists =1 if ($column eq $fieldtodrop);
2025         }
2026         if ($exists) {
2027             print "deleting $fieldtodrop field in $table...\n" unless $silent;
2028             my $sth = $dbh->prepare("alter table $table drop $fieldtodrop");
2029             $sth->execute;
2030         }
2031     }
2032 }    # foreach
2033
2034 #
2035 # Changing aqbookfund's primary key 
2036 #
2037 $sth=$dbh->prepare("ALTER TABLE `aqbookfund` DROP PRIMARY KEY , ADD PRIMARY KEY ( `bookfundid` , `branchcode` ) ;");
2038 $sth->execute;
2039
2040 $sth->finish;
2041
2042 exit;
2043
2044 # $Log$
2045 # Revision 1.166  2007/06/08 09:40:12  toins
2046 # bug fix : items.homebranch must be VARCHAR(10)
2047 #
2048 # Revision 1.165  2007/05/23 16:33:10  tipaul
2049 # skip move to innoDB for the 4 22 tables, that are used to store MARC records, are useless in Koha 3.0 The process is very very long, so the updatedatabase should speed up a lot (by long I mean 1 hour on my Dual core with SCSI disk, for a 50 000 biblios long table
2050 #
2051 # Revision 1.164  2007/05/04 16:24:09  tipaul
2052 # various bugfixes on parameters modules + adding default NoZebraIndexes systempreference if it's empty
2053 #
2054 # Revision 1.163  2007/05/02 16:44:31  tipaul
2055 # NoZebra SQL index management :
2056 # * adding 3 subs in Biblio.pm
2057 # - GetNoZebraIndexes, that get the index structure in a new systempreference (added with this commit)
2058 # - _DelBiblioNoZebra, that retrieve all index entries for a biblio and remove in a variable the biblio reference
2059 # - _AddBiblioNoZebra, that add index entries for a biblio.
2060 # Note that the 2 _Add and _Del subs work only in a hash variable, to speed up things in case of a modif (ie : delete+add). The effective SQL update is done in the ModZebra sub (that existed before, and dealed with zebra index).
2061 # I think the code has to be more deeply tested, but it works at least partially.
2062 #
2063 # Revision 1.162  2007/04/30 16:16:50  tipaul
2064 # bugfix for updatedatabase : when there is no default value (NULL fields) + removing bibliothesaurus table+adding NoZebra systempref (False by default)
2065 #
2066 # Revision 1.161  2007/04/13 16:27:55  hdl
2067 # Adding Version variable to systempreferences.
2068 #
2069 # Revision 1.160  2007/03/19 18:35:13  toins
2070 #  - adding default value in marc_subfield_structure.
2071 #  - now marc_subfields_structure displays subfields in tab view.
2072 #
2073 # Revision 1.159  2007/03/16 01:25:09  kados
2074 # Using my precrash CVS copy I did the following:
2075 #
2076 # cvs -z3 -d:ext:kados@cvs.savannah.nongnu.org:/sources/koha co -P koha
2077 # find koha.precrash -type d -name "CVS" -exec rm -v {} \;
2078 # cp -r koha.precrash/* koha/
2079 # cd koha/
2080 # cvs commit
2081 #
2082 # This should in theory put us right back where we were before the crash
2083 #
2084 # Revision 1.159  2007/03/12 17:52:30  rych
2085 # add pri key to userflags
2086 #
2087 # Revision 1.158  2007/03/09 15:14:57  tipaul
2088 # rel_3_0 moved to HEAD
2089 #
2090 # Revision 1.157.2.56  2007/01/31 16:22:54  btoumi
2091 # -add possibility to use isbn with length of 13 characters
2092 # for  Import datas in the reservoir.
2093 # -modify isbn field in marc_breeding table (varchar 13)
2094 # -add isbn filter (no - )when u read a notice from reservoir
2095 # -add filter to have right field 100
2096 #
2097 # Revision 1.157.2.55  2007/01/30 10:50:19  tipaul
2098 # adding 2 usefull indexes to biblioitems table
2099 #
2100 # Revision 1.157.2.54  2007/01/29 16:45:52  toins
2101 # * adding a new default authorised value : SUGGEST.
2102 # SUGGEST give some reasons to accept or reject a suggestion.
2103 #
2104 # * default value for borrowersMandatoryfield syspref is now "cardnumber|surname|adress"
2105 #
2106 # Revision 1.157.2.53  2007/01/26 20:48:37  hdl
2107 # Serials management : Bugfixes + improvements.
2108 # - Partial dates are now managed
2109 # - next Date Calculation with irregularity tested for 1 week and 1 month.
2110 # - manage if subscription is abouttoexpire or expired.
2111 # - Adding some information on serials pages about subscription.
2112 # - Managing irregularity with numbers.
2113 # - Adding Internal Notes in subscription management.
2114 # - Repeating Button above pages.
2115 #
2116 # Please run Updatedatabase to change irregularity and add internalnotes field  to subscription
2117 #
2118 # Revision 1.157.2.52  2007/01/24 13:57:26  tipaul
2119 # - setting supplierid to auto_increment (HDL : could you check that is works, i'm not 100% sure)
2120 # - removing 22 -> 30 marc_subfield_table -> marcxml stuff, it's now in misc/migration_tools/22_to_30/
2121 #
2122 # Revision 1.157.2.51  2007/01/18 09:58:45  tipaul
2123 # defaulting NOT NULL fields (to '')
2124 #
2125 # Revision 1.157.2.50  2007/01/18 09:39:21  tipaul
2126 # issuedate must be defaulted with ' '
2127 #
2128 # Revision 1.157.2.49  2007/01/18 09:37:30  tipaul
2129 # removing 2 field definitions that were here twice
2130 #
2131 # Revision 1.157.2.48  2007/01/15 09:55:40  toins
2132 # adding a new logging systempref : FinesLog.
2133 #
2134 # Revision 1.157.2.47  2007/01/12 18:09:49  toins
2135 # LetterLog added
2136 #
2137 # Revision 1.157.2.46  2007/01/11 14:35:39  tipaul
2138 # adding Opac Browser feature : the build_browser_and_cloud.pl script will :
2139 # - fill the browser table, that enable browsing, digit by digit of a given category, the catalogue. A complete dewey classification is provided in the script, active only for french libraries, of course (although, for instance, the script check that the catalogue is in english for developping convenience)
2140 # - fill the tags table, that contains the subject cloud.
2141 #
2142 # The cloud part is a copy of the previous build_tags.pl script that can be deleted : those 2 scripts require to parse all the catalogue to extract interesting data, so they are long. It's useless to parse the catalogue twice !
2143 #
2144 # The commit also add the systempreference to hide/show the OpacBrowse in database & in systempref management script.
2145 #
2146 # IMPROVEMENTS to do :
2147 # - the script that builds the tables can be improved to update only last week biblios (at the price of a small error in value links, but it's not a problem).
2148 # - add, in parameters section, a place to edit browser descriptions. The build script has to be updated to to avoid deleting existing browser descriptions.
2149 #
2150 # Revision 1.157.2.45  2007/01/10 16:52:52  toins
2151 # Value for Log Features syspref are set to 0 by default.
2152 #
2153 # Revision 1.157.2.44  2007/01/10 16:31:15  toins
2154 # new systems preferences :
2155 #  - CataloguingLog (log the update/creation/deletion of a notice if set to 1)
2156 #  - BorrowersLog ( idem for borrowers )
2157 #  - IssueLog (log all issue if set to 1)
2158 #  - ReturnLog (log all return if set to 1)
2159 #  - SusbcriptionLog (log all creation/deletion/update of a subcription)
2160 #
2161 # All of theses are in a new tab called 'LOGFeatures' in systempreferences.pl
2162 #
2163 # Revision 1.157.2.43  2007/01/10 14:13:17  toins
2164 # opac_news.displayed is replaced by opac_news.number.
2165 # This field say how are ordered the news on the template.
2166 #
2167 # Revision 1.157.2.42  2007/01/09 14:09:01  toins
2168 # 2 field added to opac_news.('expirationdate' and 'displayed').