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