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