Using
[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 enoug 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    => 'holdingbranch',
945             type    => 'varchar(10)',
946             null    => 'NULL',
947             key        => '',
948             default    => '',
949             extra    => '',
950         },
951         {
952             field    => 'itype',
953             type    => 'varchar(10)',
954             null    => 'NULL',
955             key        => '',
956             default    => '',
957             extra    => '',
958         },
959     ],
960     itemtypes => [
961         {
962             field  => 'itemtype',
963             type   => 'varchar(10)',
964             default    => '',
965             null   => 'NOT NULL',
966             key    => 'PRI',
967             extra  => 'UNIQUE',
968         },
969         {
970             field  => 'summary',
971             type   => 'TEXT',
972             null   => 'NULL',
973             key    => '',
974             extra  => '',
975         },
976     ],
977     marc_subfield_structure => [
978         {
979             field => 'defaultvalue',
980             type  => 'TEXT',
981             null  => 'NULL',
982             key    => '',
983             extra  => '',
984         }
985     ],
986     opac_news => [
987         {
988             field  => 'expirationdate',
989             type   => 'date',
990             null   => 'null',
991             key    => '',
992             extra  => '',
993         },
994         {
995             field   => 'number',
996             type    => 'int(11)',
997             null    => 'NULL',
998             key     => '',
999             default => '0',
1000             extra   => '',
1001         },
1002     ],
1003     reserves =>  [
1004         {
1005             field    => 'waitingdate',
1006             type    => 'date',
1007             null    => 'NULL',
1008             key        => '',
1009             default    => '',
1010             extra    => '',
1011         },
1012     ],
1013     serial => [
1014         {
1015             field   => 'notes',
1016             type    => 'TEXT',
1017             null    => 'NULL',
1018             key     => '',
1019             default => '',
1020             extra   => ''
1021         },
1022     ],
1023     shelfcontents => [
1024         {
1025             field => 'dateadded',
1026             type => 'timestamp',
1027             null    => 'NULL',
1028         },
1029     ],
1030     systempreferences =>  [
1031         {
1032             field    => 'value',
1033             type    => 'text',
1034             null    => 'NULL',
1035             key        => '',
1036             default    => '',
1037             extra    => '',
1038         },
1039         {
1040             field    => 'explanation',
1041             type    => 'text',
1042             null    => 'NULL',
1043             key        => '',
1044             default    => '',
1045             extra    => '',
1046         },
1047     ],
1048     suggestions => [
1049         {
1050             field   => 'reason',
1051             type    => 'text',
1052             null    => 'NULL',
1053             key     => ''  ,
1054             default => '',
1055             extra   =>    '',
1056         }
1057     ],
1058 );
1059
1060 my %indexes = (
1061 #    table => [
1062 #         {    indexname => 'index detail'
1063 #         }
1064 #    ],
1065     aqbooksellers => [
1066         {    indexname => 'PRIMARY',
1067             content => 'id',
1068             type => 'PRI',
1069         }
1070     ],
1071     aqbasket => [
1072         {    indexname => 'booksellerid',
1073             content => 'booksellerid',
1074         },
1075     ],
1076     aqorders => [
1077         {    indexname => 'basketno',
1078             content => 'basketno',
1079         },
1080     ],
1081     aqorderbreakdown => [
1082         {    indexname => 'ordernumber',
1083             content => 'ordernumber',
1084         },
1085         {    indexname => 'bookfundid',
1086             content => 'bookfundid',
1087         },
1088     ],
1089     biblioitems => [
1090         {    indexname => 'isbn',
1091             content => 'isbn',
1092         },
1093         {    indexname => 'publishercode',
1094             content => 'publishercode',
1095         },
1096     ],
1097     branches => [
1098         {
1099             indexname => 'branchcode',
1100             content   => 'branchcode',
1101             type => 'PRI',
1102         }
1103     ],
1104     branchrelations => [
1105         {
1106             indexname => 'PRIMARY',
1107             content => 'categorycode',
1108             type => 'PRI',
1109         }
1110     ],
1111     branchrelations => [
1112         {    indexname => 'PRIMARY',
1113             content => 'branchcode,categorycode',
1114             type => 'PRI',
1115         },
1116         {    indexname => 'branchcode',
1117             content => 'branchcode',
1118         },
1119         {    indexname => 'categorycode',
1120             content => 'categorycode',
1121         }
1122     ],
1123     currency => [
1124         {    indexname => 'PRIMARY',
1125             content => 'currency',
1126             type => 'PRI',
1127         }
1128     ],
1129     categories => [
1130         {
1131             indexname => 'categorycode',
1132             content   => 'categorycode',
1133         }
1134     ],
1135     items => [
1136         {    indexname => 'homebranch',
1137             content => 'homebranch',
1138         },
1139         {    indexname => 'holdingbranch',
1140             content => 'holdingbranch',
1141         }
1142     ],
1143     itemtypes => [
1144         {
1145             indexname => 'itemtype',
1146             content   => 'itemtype',
1147         }
1148     ],
1149     shelfcontents => [
1150         {    indexname => 'shelfnumber',
1151             content => 'shelfnumber',
1152         },
1153         {    indexname => 'itemnumber',
1154             content => 'itemnumber',
1155         }
1156     ],
1157         userflags => [
1158                 {       indexname => 'PRIMARY',
1159                         content => 'bit',
1160                         type => 'PRI',
1161                 }
1162         ]
1163 );
1164
1165 my %foreign_keys = (
1166 #    table => [
1167 #         {    key => 'the key in table' (must be indexed)
1168 #            foreigntable => 'the foreigntable name', # (the parent)
1169 #            foreignkey => 'the foreign key column(s)' # (in the parent)
1170 #            onUpdate => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
1171 #            onDelete => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
1172 #         }
1173 #    ],
1174     branchrelations => [
1175         {    key => 'branchcode',
1176             foreigntable => 'branches',
1177             foreignkey => 'branchcode',
1178             onUpdate => 'CASCADE',
1179             onDelete => 'CASCADE',
1180         },
1181         {    key => 'categorycode',
1182             foreigntable => 'branchcategories',
1183             foreignkey => 'categorycode',
1184             onUpdate => 'CASCADE',
1185             onDelete => 'CASCADE',
1186         },
1187     ],
1188     shelfcontents => [
1189         {    key => 'shelfnumber',
1190             foreigntable => 'bookshelf',
1191             foreignkey => 'shelfnumber',
1192             onUpdate => 'CASCADE',
1193             onDelete => 'CASCADE',
1194         },
1195         {    key => 'itemnumber',
1196             foreigntable => 'items',
1197             foreignkey => 'itemnumber',
1198             onUpdate => 'CASCADE',
1199             onDelete => 'CASCADE',
1200         },
1201     ],
1202     # onDelete is RESTRICT on reference tables (branches, itemtype) as we don't want items to be
1203     # easily deleted, but branches/itemtype not too easy to empty...
1204     biblioitems => [
1205         {    key => 'biblionumber',
1206             foreigntable => 'biblio',
1207             foreignkey => 'biblionumber',
1208             onUpdate => 'CASCADE',
1209             onDelete => 'CASCADE',
1210         },
1211         {    key => 'itemtype',
1212             foreigntable => 'itemtypes',
1213             foreignkey => 'itemtype',
1214             onUpdate => 'CASCADE',
1215             onDelete => 'RESTRICT',
1216         },
1217     ],
1218     items => [
1219         {    key => 'biblioitemnumber',
1220             foreigntable => 'biblioitems',
1221             foreignkey => 'biblioitemnumber',
1222             onUpdate => 'CASCADE',
1223             onDelete => 'CASCADE',
1224         },
1225         {    key => 'homebranch',
1226             foreigntable => 'branches',
1227             foreignkey => 'branchcode',
1228             onUpdate => 'CASCADE',
1229             onDelete => 'RESTRICT',
1230         },
1231         {    key => 'holdingbranch',
1232             foreigntable => 'branches',
1233             foreignkey => 'branchcode',
1234             onUpdate => 'CASCADE',
1235             onDelete => 'RESTRICT',
1236         },
1237     ],
1238     aqbasket => [
1239         {    key => 'booksellerid',
1240             foreigntable => 'aqbooksellers',
1241             foreignkey => 'id',
1242             onUpdate => 'CASCADE',
1243             onDelete => 'RESTRICT',
1244         },
1245     ],
1246     aqorders => [
1247         {    key => 'basketno',
1248             foreigntable => 'aqbasket',
1249             foreignkey => 'basketno',
1250             onUpdate => 'CASCADE',
1251             onDelete => 'CASCADE',
1252         },
1253         {    key => 'biblionumber',
1254             foreigntable => 'biblio',
1255             foreignkey => 'biblionumber',
1256             onUpdate => 'SET NULL',
1257             onDelete => 'SET NULL',
1258         },
1259     ],
1260     aqbooksellers => [
1261         {    key => 'listprice',
1262             foreigntable => 'currency',
1263             foreignkey => 'currency',
1264             onUpdate => 'CASCADE',
1265             onDelete => 'CASCADE',
1266         },
1267         {    key => 'invoiceprice',
1268             foreigntable => 'currency',
1269             foreignkey => 'currency',
1270             onUpdate => 'CASCADE',
1271             onDelete => 'CASCADE',
1272         },
1273     ],
1274     aqorderbreakdown => [
1275         {    key => 'ordernumber',
1276             foreigntable => 'aqorders',
1277             foreignkey => 'ordernumber',
1278             onUpdate => 'CASCADE',
1279             onDelete => 'CASCADE',
1280         },
1281         {    key => 'bookfundid',
1282             foreigntable => 'aqbookfund',
1283             foreignkey => 'bookfundid',
1284             onUpdate => 'CASCADE',
1285             onDelete => 'CASCADE',
1286         },
1287     ],
1288     branchtransfers => [
1289         {    key => 'frombranch',
1290             foreigntable => 'branches',
1291             foreignkey => 'branchcode',
1292             onUpdate => 'CASCADE',
1293             onDelete => 'CASCADE',
1294         },
1295         {    key => 'tobranch',
1296             foreigntable => 'branches',
1297             foreignkey => 'branchcode',
1298             onUpdate => 'CASCADE',
1299             onDelete => 'CASCADE',
1300         },
1301         {    key => 'itemnumber',
1302             foreigntable => 'items',
1303             foreignkey => 'itemnumber',
1304             onUpdate => 'CASCADE',
1305             onDelete => 'CASCADE',
1306         },
1307     ],
1308     issuingrules => [
1309         {    key => 'categorycode',
1310             foreigntable => 'categories',
1311             foreignkey => 'categorycode',
1312             onUpdate => 'CASCADE',
1313             onDelete => 'CASCADE',
1314         },
1315         {    key => 'itemtype',
1316             foreigntable => 'itemtypes',
1317             foreignkey => 'itemtype',
1318             onUpdate => 'CASCADE',
1319             onDelete => 'CASCADE',
1320         },
1321     ],
1322     issues => [    # constraint is SET NULL : when a borrower or an item is deleted, we keep the issuing record
1323     # for stat purposes
1324         {    key => 'borrowernumber',
1325             foreigntable => 'borrowers',
1326             foreignkey => 'borrowernumber',
1327             onUpdate => 'SET NULL',
1328             onDelete => 'SET NULL',
1329         },
1330         {    key => 'itemnumber',
1331             foreigntable => 'items',
1332             foreignkey => 'itemnumber',
1333             onUpdate => 'SET NULL',
1334             onDelete => 'SET NULL',
1335         },
1336     ],
1337     reserves => [
1338         {    key => 'borrowernumber',
1339             foreigntable => 'borrowers',
1340             foreignkey => 'borrowernumber',
1341             onUpdate => 'CASCADE',
1342             onDelete => 'CASCADE',
1343         },
1344         {    key => 'biblionumber',
1345             foreigntable => 'biblio',
1346             foreignkey => 'biblionumber',
1347             onUpdate => 'CASCADE',
1348             onDelete => 'CASCADE',
1349         },
1350         {    key => 'itemnumber',
1351             foreigntable => 'items',
1352             foreignkey => 'itemnumber',
1353             onUpdate => 'CASCADE',
1354             onDelete => 'CASCADE',
1355         },
1356         {    key => 'branchcode',
1357             foreigntable => 'branches',
1358             foreignkey => 'branchcode',
1359             onUpdate => 'CASCADE',
1360             onDelete => 'CASCADE',
1361         },
1362     ],
1363     borrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
1364     # but prevent deleting a branch as soon as it has 1 borrower !
1365         {    key => 'categorycode',
1366             foreigntable => 'categories',
1367             foreignkey => 'categorycode',
1368             onUpdate => 'RESTRICT',
1369             onDelete => 'RESTRICT',
1370         },
1371         {    key => 'branchcode',
1372             foreigntable => 'branches',
1373             foreignkey => 'branchcode',
1374             onUpdate => 'RESTRICT',
1375             onDelete => 'RESTRICT',
1376         },
1377     ],
1378     deletedborrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
1379     # but prevent deleting a branch as soon as it has 1 borrower !
1380         {    key => 'categorycode',
1381             foreigntable => 'categories',
1382             foreignkey => 'categorycode',
1383             onUpdate => 'RESTRICT',
1384             onDelete => 'RESTRICT',
1385         },
1386         {    key => 'branchcode',
1387             foreigntable => 'branches',
1388             foreignkey => 'branchcode',
1389             onUpdate => 'RESTRICT',
1390             onDelete => 'RESTRICT',
1391         },
1392     ],
1393     accountlines => [
1394         {    key => 'borrowernumber',
1395             foreigntable => 'borrowers',
1396             foreignkey => 'borrowernumber',
1397             onUpdate => 'CASCADE',
1398             onDelete => 'CASCADE',
1399         },
1400         {    key => 'itemnumber',
1401             foreigntable => 'items',
1402             foreignkey => 'itemnumber',
1403             onUpdate => 'SET NULL',
1404             onDelete => 'SET NULL',
1405         },
1406     ],
1407     auth_tag_structure => [
1408         {    key => 'authtypecode',
1409             foreigntable => 'auth_types',
1410             foreignkey => 'authtypecode',
1411             onUpdate => 'CASCADE',
1412             onDelete => 'CASCADE',
1413         },
1414     ],
1415     # FIXME : don't constraint auth_*_table and auth_word, as they may be replaced by zebra
1416 );
1417
1418
1419 # column changes
1420 my %column_change = (
1421     # table
1422     borrowers => [
1423                 {
1424                     from => 'emailaddress',
1425                     to => 'email',
1426                     after => 'city',
1427                 },
1428                 {
1429                     from => 'streetaddress',
1430                     to => 'address',
1431                     after => 'initials',
1432                 },
1433                 {
1434                     from => 'faxnumber',
1435                     to => 'fax',
1436                     after => 'phone',
1437                 },
1438                 {
1439                     from => 'textmessaging',
1440                     to => 'opacnote',
1441                     after => 'userid',
1442                 },
1443                 {
1444                     from => 'altnotes',
1445                     to => 'contactnote',
1446                     after => 'opacnote',
1447                 },
1448                 {
1449                     from => 'physstreet',
1450                     to => 'B_address',
1451                     after => 'fax',
1452                 },
1453                 {
1454                     from => 'streetcity',
1455                     to => 'B_city',
1456                     after => 'B_address',
1457                 },
1458                 {
1459                     from => 'phoneday',
1460                     to => 'mobile',
1461                     after => 'phone',
1462                 },
1463                 {
1464                     from => 'zipcode',
1465                     to => 'zipcode',
1466                     after => 'city',
1467                 },
1468                 {
1469                     from => 'homezipcode',
1470                     to => 'B_zipcode',
1471                     after => 'B_city',
1472                 },
1473                 {
1474                     from => 'altphone',
1475                     to => 'B_phone',
1476                     after => 'B_zipcode',
1477                 },
1478                 {
1479                     from => 'expiry',
1480                     to => 'dateexpiry',
1481                     after => 'dateenrolled',
1482                 },
1483                 {
1484                     from => 'guarantor',
1485                     to => 'guarantorid',
1486                     after => 'contactname',
1487                 },
1488                 {
1489                     from => 'altrelationship',
1490                     to => 'relationship',
1491                     after => 'borrowernotes',
1492                 },
1493             ],
1494
1495     deletedborrowers => [
1496                 {
1497                     from => 'emailaddress',
1498                     to => 'email',
1499                     after => 'city',
1500                 },
1501                 {
1502                     from => 'streetaddress',
1503                     to => 'address',
1504                     after => 'initials',
1505                 },
1506                 {
1507                     from => 'faxnumber',
1508                     to => 'fax',
1509                     after => 'phone',
1510                 },
1511                 {
1512                     from => 'textmessaging',
1513                     to => 'opacnote',
1514                     after => 'userid',
1515                 },
1516                 {
1517                     from => 'altnotes',
1518                     to => 'contactnote',
1519                     after => 'opacnote',
1520                 },
1521                 {
1522                     from => 'physstreet',
1523                     to => 'B_address',
1524                     after => 'fax',
1525                 },
1526                 {
1527                     from => 'streetcity',
1528                     to => 'B_city',
1529                     after => 'B_address',
1530                 },
1531                 {
1532                     from => 'phoneday',
1533                     to => 'mobile',
1534                     after => 'phone',
1535                 },
1536                 {
1537                     from => 'zipcode',
1538                     to => 'zipcode',
1539                     after => 'city',
1540                 },
1541                 {
1542                     from => 'homezipcode',
1543                     to => 'B_zipcode',
1544                     after => 'B_city',
1545                 },
1546                 {
1547                     from => 'altphone',
1548                     to => 'B_phone',
1549                     after => 'B_zipcode',
1550                 },
1551                 {
1552                     from => 'expiry',
1553                     to => 'dateexpiry',
1554                     after => 'dateenrolled',
1555                 },
1556                 {
1557                     from => 'guarantor',
1558                     to => 'guarantorid',
1559                     after => 'contactname',
1560                 },
1561                 {
1562                     from => 'altrelationship',
1563                     to => 'relationship',
1564                     after => 'borrowernotes',
1565                 },
1566             ],
1567         );
1568     
1569
1570 # MOVE all tables TO UTF-8 and innoDB
1571 $sth = $dbh->prepare("show table status");
1572 $sth->execute;
1573 while ( my $table = $sth->fetchrow_hashref ) {
1574      if ($table->{Engine} ne 'InnoDB') {
1575          $dbh->do("ALTER TABLE $table->{Name} TYPE = innodb");
1576          print "moving $table->{Name} to InnoDB\n";
1577      }
1578     next if $table->{Name} eq 'marc_word';
1579     next if $table->{Name} eq 'marc_subfield_table';
1580     next if $table->{Name} eq 'auth_word';
1581     next if $table->{Name} eq 'auth_subfield_table';
1582     unless ($table->{Collation} =~ /^utf8/) {
1583          print "moving $table->{Name} to utf8\n";
1584         $dbh->do("ALTER TABLE $table->{Name} CONVERT TO CHARACTER SET utf8");
1585         $dbh->do("ALTER TABLE $table->{Name} DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci");
1586         # 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 !
1587     } else {
1588     }
1589 }
1590
1591
1592 foreach my $table (keys %column_change) {
1593     $sth = $dbh->prepare("show columns from $table");
1594     $sth->execute();
1595     undef %types;
1596     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1597     {
1598         $types{$column}->{type} ="$type";
1599         $types{$column}->{null} = "$null";
1600         $types{$column}->{key} = "$key";
1601         $types{$column}->{default} = "$default";
1602         $types{$column}->{extra} = "$extra";
1603     }    # while
1604     my $tablerows = $column_change{$table};
1605     foreach my $row ( @$tablerows ) {
1606         if ($types{$row->{from}}->{type}) {
1607             print "altering $table $row->{from} to $row->{to}\n";
1608             # ALTER TABLE `borrowers` CHANGE `faxnumber` `fax` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
1609 #             alter table `borrowers` change `faxnumber` `fax` type text  null after phone
1610             my $sql =
1611                 "alter table `$table` change `$row->{from}` `$row->{to}` $types{$row->{from}}->{type} ".
1612                 ($types{$row->{from}}->{null} eq 'YES'?" NULL":" NOT NULL").
1613                 ($types{$row->{from}}->{default}?" default ".$types{$row->{from}}->{default}:"").
1614                 "$types{$row->{from}}->{extra} after $row->{after} ";
1615 #             print "$sql";
1616             $dbh->do($sql);
1617         }
1618     }
1619 }
1620
1621 # Enter here the field you want to delete from DB.
1622 # FIXME :: there is a %uselessfield before which seems doing the same things.
1623 my %fieldtodelete = (
1624     # tablename => [fieldname1,fieldname2,...]
1625
1626 ); # %fielddelete
1627
1628 print "removing some unused fields...\n";
1629 foreach my $table ( keys %fieldtodelete ) {
1630     foreach my $field ( @{$fieldtodelete{$table}} ){
1631         print "removing ".$field." from ".$table;
1632         my $sth = $dbh->prepare("ALTER TABLE $table DROP $field");
1633         $sth->execute;
1634         if ( $sth->err ) {
1635             print "Error : $sth->errstr \n";
1636         }
1637     }
1638 }
1639
1640 # Enter here the line you want to remove from DB.
1641 my %linetodelete = (
1642     # table name => where clause.
1643     userflags => "bit = 8", # delete the 'reserveforself' flags
1644     
1645 ); # %linetodelete
1646
1647 #-------------------
1648 # Initialize
1649
1650 # Start checking
1651
1652 # Get version of MySQL database engine.
1653 my $mysqlversion = `mysqld --version`;
1654 $mysqlversion =~ /Ver (\S*) /;
1655 $mysqlversion = $1;
1656 if ( $mysqlversion ge '3.23' ) {
1657     print "Could convert to MyISAM database tables...\n" unless $silent;
1658 }
1659
1660 #---------------------------------
1661 # Tables
1662
1663 # Collect all tables into a list
1664 $sth = $dbh->prepare("show tables");
1665 $sth->execute;
1666 while ( my ($table) = $sth->fetchrow ) {
1667     $existingtables{$table} = 1;
1668 }
1669
1670
1671 # Now add any missing tables
1672 foreach $table ( keys %requiretables ) {
1673     unless ( $existingtables{$table} ) {
1674     print "Adding $table table...\n" unless $silent;
1675         my $sth = $dbh->prepare("create table $table $requiretables{$table}");
1676         $sth->execute;
1677         if ( $sth->err ) {
1678             print "Error : $sth->errstr \n";
1679             $sth->finish;
1680         }    # if error
1681     }    # unless exists
1682 }    # foreach
1683
1684 #---------------------------------
1685 # Columns
1686
1687 foreach $table ( keys %requirefields ) {
1688     print "Check table $table\n" if $debug and not $silent;
1689     $sth = $dbh->prepare("show columns from $table");
1690     $sth->execute();
1691     undef %types;
1692     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1693     {
1694         $types{$column} = $type;
1695     }    # while
1696     foreach $column ( keys %{ $requirefields{$table} } ) {
1697         print "  Check column $column  [$types{$column}]\n" if $debug and not $silent;
1698         if ( !$types{$column} ) {
1699
1700             # column doesn't exist
1701             print "Adding $column field to $table table...\n" unless $silent;
1702             $query = "alter table $table
1703             add column $column " . $requirefields{$table}->{$column};
1704             print "Execute: $query\n" if $debug;
1705             my $sti = $dbh->prepare($query);
1706             $sti->execute;
1707             if ( $sti->err ) {
1708                 print "**Error : $sti->errstr \n";
1709                 $sti->finish;
1710             }    # if error
1711         }    # if column
1712     }    # foreach column
1713 }    # foreach table
1714
1715 foreach $table ( keys %fielddefinitions ) {
1716     print "Check table $table\n" if $debug;
1717     $sth = $dbh->prepare("show columns from $table");
1718     $sth->execute();
1719     my $definitions;
1720     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1721     {
1722         $definitions->{$column}->{type}    = $type;
1723         $definitions->{$column}->{null}    = $null;
1724         $definitions->{$column}->{null}    = 'NULL' if $null eq 'YES';
1725         $definitions->{$column}->{key}     = $key;
1726         $definitions->{$column}->{default} = $default;
1727         $definitions->{$column}->{extra}   = $extra;
1728     }    # while
1729     my $fieldrow = $fielddefinitions{$table};
1730     foreach my $row (@$fieldrow) {
1731         my $field   = $row->{field};
1732         my $type    = $row->{type};
1733         my $null    = $row->{null};
1734 #         $null    = 'YES' if $row->{null} eq 'NULL';
1735         my $key     = $row->{key};
1736         my $default = $row->{default};
1737 #         $default="''" unless $default;
1738         my $extra   = $row->{extra};
1739         my $def     = $definitions->{$field};
1740         my $after    = ($row->{after}?" after ".$row->{after}:"");
1741
1742         unless ( $type eq $def->{type}
1743             && $null eq $def->{null}
1744             && $key eq $def->{key}
1745             && $extra eq $def->{extra} )
1746         {
1747             if ( $null eq '' ) {
1748                 $null = 'NOT NULL';
1749             }
1750             if ( $key eq 'PRI' ) {
1751                 $key = 'PRIMARY KEY';
1752             }
1753             unless ( $extra eq 'auto_increment' ) {
1754                 $extra = '';
1755             }
1756     
1757             # if it's a new column use "add", if it's an old one, use "change".
1758             my $action;
1759             if ($definitions->{$field}->{type}) {
1760                 $action="change $field"
1761             } else {
1762                 $action="add";
1763             }
1764 # if it's a primary key, drop the previous pk, before altering the table
1765             print "  alter or create $field in $table\n" unless $silent;
1766             my $query;
1767             if ($key ne 'PRIMARY KEY') {
1768 #                 warn "alter table $table $action $field $type $null $key $extra default $default $after";
1769                 $query = "alter table $table $action $field $type $null $key $extra ".($default?"default ".$dbh->quote($default):"")." $after";
1770             } else {
1771 #             warn "alter table $table drop primary key, $action $field $type $null $key $extra default $default $after";
1772                  # something strange : for indexes UNIQUE, they are reported as primary key here.
1773                  # but if you try to run with drop primary key, it fails.
1774                  # thus, we run the query twice, one will fail, one will succeed.
1775                  # strange...
1776                 $query="alter table $table drop primary key, $action $field $type $null $key $extra ".($default?"default ".$dbh->quote($default):"")." $after";
1777                 $query="alter table $table $action $field $type $null $key $extra ".($default?"default ".$dbh->quote($default):"")." $after";
1778             }
1779             $dbh->do($query);
1780         }
1781     }
1782 }
1783
1784 print "removing some unused data...\n";
1785 foreach my $table ( keys %linetodelete ) {
1786     foreach my $where ( @{linetodelete{$table}} ){
1787         print "DELETE FROM ".$table." where ".$where;
1788         print "\n";
1789         my $sth = $dbh->prepare("DELETE FROM $table where $where");
1790         $sth->execute;
1791         if ( $sth->err ) {
1792             print "Error : $sth->errstr \n";
1793         }
1794     }
1795 }
1796
1797 # Populate tables with required data
1798
1799 # synch table and deletedtable.
1800 foreach my $table (('borrowers','items','biblio','biblioitems')) {
1801     my %deletedborrowers;
1802     print "synch'ing $table and deleted$table\n";
1803     $sth = $dbh->prepare("show columns from deleted$table");
1804     $sth->execute;
1805     while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1806         $deletedborrowers{$column}=1;
1807     }
1808     $sth = $dbh->prepare("show columns from $table");
1809     $sth->execute;
1810     my $previous;
1811     while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1812         unless ($deletedborrowers{$column}) {
1813             my $newcol="alter table deleted$table add $column $type";
1814             if ($null eq 'YES') {
1815                 $newcol .= " NULL ";
1816             } else {
1817                 $newcol .= " NOT NULL ";
1818             }
1819             $newcol .= "default ".$dbh->quote($default) if $default;
1820             $newcol .= " after $previous" if $previous;
1821             $previous=$column;
1822             print "creating column $column\n";
1823             $dbh->do($newcol);
1824         }
1825     }
1826 }
1827 #
1828 # update publisheddate 
1829 #
1830 $sth = $dbh->prepare("select count(*) from serial where publisheddate is NULL");
1831 $sth->execute;
1832 my ($emptypublished) = $sth->fetchrow;
1833 if ($emptypublished) {
1834     print "Updating publisheddate\n";
1835     $dbh->do("update serial set publisheddate=planneddate where publisheddate is NULL");
1836 }
1837 foreach my $table ( keys %tabledata ) {
1838     print "Checking for data required in table $table...\n" unless $silent;
1839     my $tablerows = $tabledata{$table};
1840     foreach my $row (@$tablerows) {
1841         my $uniquefieldrequired = $row->{uniquefieldrequired};
1842         my $uniquevalue         = $row->{$uniquefieldrequired};
1843         my $forceupdate         = $row->{forceupdate};
1844         my $sth                 =
1845           $dbh->prepare(
1846 "select $uniquefieldrequired from $table where $uniquefieldrequired=?"
1847         );
1848         $sth->execute($uniquevalue);
1849         if ($sth->rows) {
1850             foreach my $field (keys %$forceupdate) {
1851                 if ($forceupdate->{$field}) {
1852                     my $sth=$dbh->prepare("update systempreferences set $field=? where $uniquefieldrequired=?");
1853                     $sth->execute($row->{$field}, $uniquevalue);
1854                 }
1855             }
1856         } else {
1857             print "Adding row to $table: " unless $silent;
1858             my @values;
1859             my $fieldlist;
1860             my $placeholders;
1861             foreach my $field ( keys %$row ) {
1862                 next if $field eq 'uniquefieldrequired';
1863                 next if $field eq 'forceupdate';
1864                 my $value = $row->{$field};
1865                 push @values, $value;
1866                 print "  $field => $value" unless $silent;
1867                 $fieldlist .= "$field,";
1868                 $placeholders .= "?,";
1869             }
1870             print "\n" unless $silent;
1871             $fieldlist    =~ s/,$//;
1872             $placeholders =~ s/,$//;
1873             print "insert into $table ($fieldlist) values ($placeholders)";
1874             my $sth =
1875             $dbh->prepare(
1876                 "insert into $table ($fieldlist) values ($placeholders)");
1877             $sth->execute(@values);
1878         }
1879     }
1880 }
1881
1882 #
1883 # check indexes and create them when needed
1884 #
1885 print "Checking for index required...\n" unless $silent;
1886 foreach my $table ( keys %indexes ) {
1887     #
1888     # read all indexes from $table
1889     #
1890     $sth = $dbh->prepare("show index from $table");
1891     $sth->execute;
1892     my %existingindexes;
1893     while ( my ( $table, $non_unique, $key_name, $Seq_in_index, $Column_name, $Collation, $cardinality, $sub_part, $Packed, $comment ) = $sth->fetchrow ) {
1894         $existingindexes{$key_name} = 1;
1895     }
1896     # read indexes to check
1897     my $tablerows = $indexes{$table};
1898     foreach my $row (@$tablerows) {
1899         my $key_name=$row->{indexname};
1900         if ($existingindexes{$key_name} eq 1) {
1901 #             print "$key_name existing";
1902         } else {
1903             print "\tCreating index $key_name in $table\n";
1904             my $sql;
1905             if ($row->{indexname} eq 'PRIMARY') {
1906                 $sql = "alter table $table ADD PRIMARY KEY ($row->{content})";
1907             } else {
1908                 $sql = "alter table $table ADD INDEX $key_name ($row->{content}) $row->{type}";
1909             }
1910              $dbh->do($sql);
1911             print "Error $sql : $dbh->err \n" if $dbh->err;
1912         }
1913     }
1914 }
1915
1916 #
1917 # check foreign keys and create them when needed
1918 #
1919 print "Checking for foreign keys required...\n" unless $silent;
1920 foreach my $table ( keys %foreign_keys ) {
1921     #
1922     # read all indexes from $table
1923     #
1924     $sth = $dbh->prepare("show table status like '$table'");
1925     $sth->execute;
1926     my $stat = $sth->fetchrow_hashref;
1927     # read indexes to check
1928     my $tablerows = $foreign_keys{$table};
1929     foreach my $row (@$tablerows) {
1930         my $foreign_table=$row->{foreigntable};
1931         if ($stat->{'Comment'} =~/$foreign_table/) {
1932 #             print "$foreign_table existing\n";
1933         } else {
1934             print "\tCreating foreign key $foreign_table in $table\n";
1935             # first, drop any orphan value in child table
1936             if ($row->{onDelete} ne "RESTRICT") {
1937                 my $sql = "delete from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})";
1938                 $dbh->do($sql);
1939                 print "SQL ERROR: $sql : $dbh->err \n" if $dbh->err;
1940             }
1941             my $sql="alter table $table ADD FOREIGN KEY $row->{key} ($row->{key}) REFERENCES $row->{foreigntable} ($row->{foreignkey})";
1942             $sql .= " on update ".$row->{onUpdate} if $row->{onUpdate};
1943             $sql .= " on delete ".$row->{onDelete} if $row->{onDelete};
1944             $dbh->do($sql);
1945             if ($dbh->err) {
1946                 print "====================
1947 An error occured during :
1948 \t$sql
1949 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).
1950 You can find those values with select
1951 \t$table.* from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})
1952 ====================\n
1953 ";
1954             }
1955         }
1956     }
1957 }
1958 # now drop useless tables
1959 foreach $table ( @TableToDelete ) {
1960     if ( $existingtables{$table} ) {
1961         print "Dropping unused table $table\n" if $debug and not $silent;
1962         $dbh->do("drop table $table");
1963         if ( $dbh->err ) {
1964             print "Error : $dbh->errstr \n";
1965         }
1966     }
1967 }
1968
1969 #
1970 # SPECIFIC STUFF
1971 #
1972 #
1973 # create frameworkcode row in biblio table & fill it with marc_biblio.frameworkcode.
1974 #
1975
1976 # 1st, get how many biblio we will have to do...
1977 $sth = $dbh->prepare('select count(*) from marc_biblio');
1978 $sth->execute;
1979 my ($totaltodo) = $sth->fetchrow;
1980
1981 $sth = $dbh->prepare("show columns from biblio");
1982 $sth->execute();
1983 my $definitions;
1984 my $bibliofwexist=0;
1985 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
1986     $bibliofwexist=1 if $column eq 'frameworkcode';
1987 }
1988 unless ($bibliofwexist) {
1989     print "moving biblioframework to biblio table\n";
1990     $dbh->do('ALTER TABLE `biblio` ADD `frameworkcode` VARCHAR( 4 ) NOT NULL AFTER `biblionumber`');
1991     $sth = $dbh->prepare('select biblionumber,frameworkcode from marc_biblio');
1992     $sth->execute;
1993     my $sth_update = $dbh->prepare('update biblio set frameworkcode=? where biblionumber=?');
1994     my $totaldone=0;
1995     while (my ($biblionumber,$frameworkcode) = $sth->fetchrow) {
1996         $sth_update->execute($frameworkcode,$biblionumber);
1997         $totaldone++;
1998         print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
1999     }
2000     print "\rdone\n";
2001 }
2002
2003 # at last, remove useless fields
2004 foreach $table ( keys %uselessfields ) {
2005     my @fields = split /,/,$uselessfields{$table};
2006     my $fields;
2007     my $exists;
2008     foreach my $fieldtodrop (@fields) {
2009         $fieldtodrop =~ s/\t//g;
2010         $fieldtodrop =~ s/\n//g;
2011         $exists =0;
2012         $sth = $dbh->prepare("show columns from $table");
2013         $sth->execute;
2014         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
2015         {
2016             $exists =1 if ($column eq $fieldtodrop);
2017         }
2018         if ($exists) {
2019             print "deleting $fieldtodrop field in $table...\n" unless $silent;
2020             my $sth = $dbh->prepare("alter table $table drop $fieldtodrop");
2021             $sth->execute;
2022         }
2023     }
2024 }    # foreach
2025
2026 #
2027 # Changing aqbookfund's primary key 
2028 #
2029 $sth=$dbh->prepare("ALTER TABLE `aqbookfund` DROP PRIMARY KEY , ADD PRIMARY KEY ( `bookfundid` , `branchcode` ) ;");
2030 $sth->execute;
2031
2032 $sth->finish;
2033
2034 exit;
2035
2036 # $Log$
2037 # Revision 1.164  2007/05/04 16:24:09  tipaul
2038 # various bugfixes on parameters modules + adding default NoZebraIndexes systempreference if it's empty
2039 #
2040 # Revision 1.163  2007/05/02 16:44:31  tipaul
2041 # NoZebra SQL index management :
2042 # * adding 3 subs in Biblio.pm
2043 # - GetNoZebraIndexes, that get the index structure in a new systempreference (added with this commit)
2044 # - _DelBiblioNoZebra, that retrieve all index entries for a biblio and remove in a variable the biblio reference
2045 # - _AddBiblioNoZebra, that add index entries for a biblio.
2046 # 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).
2047 # I think the code has to be more deeply tested, but it works at least partially.
2048 #
2049 # Revision 1.162  2007/04/30 16:16:50  tipaul
2050 # bugfix for updatedatabase : when there is no default value (NULL fields) + removing bibliothesaurus table+adding NoZebra systempref (False by default)
2051 #
2052 # Revision 1.161  2007/04/13 16:27:55  hdl
2053 # Adding Version variable to systempreferences.
2054 #
2055 # Revision 1.160  2007/03/19 18:35:13  toins
2056 #  - adding default value in marc_subfield_structure.
2057 #  - now marc_subfields_structure displays subfields in tab view.
2058 #
2059 # Revision 1.159  2007/03/16 01:25:09  kados
2060 # Using my precrash CVS copy I did the following:
2061 #
2062 # cvs -z3 -d:ext:kados@cvs.savannah.nongnu.org:/sources/koha co -P koha
2063 # find koha.precrash -type d -name "CVS" -exec rm -v {} \;
2064 # cp -r koha.precrash/* koha/
2065 # cd koha/
2066 # cvs commit
2067 #
2068 # This should in theory put us right back where we were before the crash
2069 #
2070 # Revision 1.159  2007/03/12 17:52:30  rych
2071 # add pri key to userflags
2072 #
2073 # Revision 1.158  2007/03/09 15:14:57  tipaul
2074 # rel_3_0 moved to HEAD
2075 #
2076 # Revision 1.157.2.56  2007/01/31 16:22:54  btoumi
2077 # -add possibility to use isbn with length of 13 characters
2078 # for  Import datas in the reservoir.
2079 # -modify isbn field in marc_breeding table (varchar 13)
2080 # -add isbn filter (no - )when u read a notice from reservoir
2081 # -add filter to have right field 100
2082 #
2083 # Revision 1.157.2.55  2007/01/30 10:50:19  tipaul
2084 # adding 2 usefull indexes to biblioitems table
2085 #
2086 # Revision 1.157.2.54  2007/01/29 16:45:52  toins
2087 # * adding a new default authorised value : SUGGEST.
2088 # SUGGEST give some reasons to accept or reject a suggestion.
2089 #
2090 # * default value for borrowersMandatoryfield syspref is now "cardnumber|surname|adress"
2091 #
2092 # Revision 1.157.2.53  2007/01/26 20:48:37  hdl
2093 # Serials management : Bugfixes + improvements.
2094 # - Partial dates are now managed
2095 # - next Date Calculation with irregularity tested for 1 week and 1 month.
2096 # - manage if subscription is abouttoexpire or expired.
2097 # - Adding some information on serials pages about subscription.
2098 # - Managing irregularity with numbers.
2099 # - Adding Internal Notes in subscription management.
2100 # - Repeating Button above pages.
2101 #
2102 # Please run Updatedatabase to change irregularity and add internalnotes field  to subscription
2103 #
2104 # Revision 1.157.2.52  2007/01/24 13:57:26  tipaul
2105 # - setting supplierid to auto_increment (HDL : could you check that is works, i'm not 100% sure)
2106 # - removing 22 -> 30 marc_subfield_table -> marcxml stuff, it's now in misc/migration_tools/22_to_30/
2107 #
2108 # Revision 1.157.2.51  2007/01/18 09:58:45  tipaul
2109 # defaulting NOT NULL fields (to '')
2110 #
2111 # Revision 1.157.2.50  2007/01/18 09:39:21  tipaul
2112 # issuedate must be defaulted with ' '
2113 #
2114 # Revision 1.157.2.49  2007/01/18 09:37:30  tipaul
2115 # removing 2 field definitions that were here twice
2116 #
2117 # Revision 1.157.2.48  2007/01/15 09:55:40  toins
2118 # adding a new logging systempref : FinesLog.
2119 #
2120 # Revision 1.157.2.47  2007/01/12 18:09:49  toins
2121 # LetterLog added
2122 #
2123 # Revision 1.157.2.46  2007/01/11 14:35:39  tipaul
2124 # adding Opac Browser feature : the build_browser_and_cloud.pl script will :
2125 # - 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)
2126 # - fill the tags table, that contains the subject cloud.
2127 #
2128 # 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 !
2129 #
2130 # The commit also add the systempreference to hide/show the OpacBrowse in database & in systempref management script.
2131 #
2132 # IMPROVEMENTS to do :
2133 # - 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).
2134 # - add, in parameters section, a place to edit browser descriptions. The build script has to be updated to to avoid deleting existing browser descriptions.
2135 #
2136 # Revision 1.157.2.45  2007/01/10 16:52:52  toins
2137 # Value for Log Features syspref are set to 0 by default.
2138 #
2139 # Revision 1.157.2.44  2007/01/10 16:31:15  toins
2140 # new systems preferences :
2141 #  - CataloguingLog (log the update/creation/deletion of a notice if set to 1)
2142 #  - BorrowersLog ( idem for borrowers )
2143 #  - IssueLog (log all issue if set to 1)
2144 #  - ReturnLog (log all return if set to 1)
2145 #  - SusbcriptionLog (log all creation/deletion/update of a subcription)
2146 #
2147 # All of theses are in a new tab called 'LOGFeatures' in systempreferences.pl
2148 #
2149 # Revision 1.157.2.43  2007/01/10 14:13:17  toins
2150 # opac_news.displayed is replaced by opac_news.number.
2151 # This field say how are ordered the news on the template.
2152 #
2153 # Revision 1.157.2.42  2007/01/09 14:09:01  toins
2154 # 2 field added to opac_news.('expirationdate' and 'displayed').