Bug 22544: Move add_opac_item to Koha namespace
[koha.git] / C4 / MarcModificationTemplates.pm
1 package C4::MarcModificationTemplates;
2
3 # This file is part of Koha.
4 #
5 # Copyright 2010 Kyle M Hall <kyle.m.hall@gmail.com>
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use DateTime;
23
24 use C4::Context;
25 use Koha::SimpleMARC;
26 use Koha::MoreUtils;
27 use Koha::DateUtils;
28
29 use vars qw(@ISA @EXPORT);
30
31 BEGIN {
32     @ISA = qw(Exporter);
33     @EXPORT = qw(
34         &GetModificationTemplates
35         &AddModificationTemplate
36         &DelModificationTemplate
37
38         &GetModificationTemplateAction
39         &GetModificationTemplateActions
40
41         &AddModificationTemplateAction
42         &ModModificationTemplateAction
43         &DelModificationTemplateAction
44         &MoveModificationTemplateAction
45
46         &ModifyRecordsWithTemplate
47         &ModifyRecordWithTemplate
48     );
49 }
50
51
52 =head1 NAME
53
54 C4::MarcModificationTemplates - Module to manage MARC Modification Templates
55
56 =head1 DESCRIPTION
57
58 MARC Modification Templates are a tool for marc batch imports,
59 so that librarians can set up templates for various vendors'
60 files telling Koha what fields to insert data into.
61
62 =head1 FUNCTIONS
63
64 =cut
65
66 =head2 GetModificationTemplates
67
68   my @templates = GetModificationTemplates( $template_id );
69
70   Passing optional $template_id marks it as the selected template.
71
72 =cut
73
74 sub GetModificationTemplates {
75   my ( $template_id ) = @_;
76
77   my $dbh = C4::Context->dbh;
78   my $sth = $dbh->prepare("SELECT * FROM marc_modification_templates ORDER BY name");
79   $sth->execute();
80
81   my @templates;
82   while ( my $template = $sth->fetchrow_hashref() ) {
83     $template->{'selected'} = 1
84         if $template_id && $template->{'template_id'} eq $template_id;
85     push( @templates, $template );
86   }
87
88   return @templates;
89 }
90
91 =head2
92   AddModificationTemplate
93
94   $template_id = AddModificationTemplate( $template_name[, $template_id ] );
95
96   If $template_id is supplied, the actions from that template will be copied
97   into the newly created template.
98 =cut
99
100 sub AddModificationTemplate {
101   my ( $template_name, $template_id_copy ) = @_;
102
103   my $dbh = C4::Context->dbh;
104   my $sth = $dbh->prepare("INSERT INTO marc_modification_templates ( name ) VALUES ( ? )");
105   $sth->execute( $template_name );
106
107   $sth = $dbh->prepare("SELECT * FROM marc_modification_templates WHERE name = ?");
108   $sth->execute( $template_name );
109   my $row = $sth->fetchrow_hashref();
110   my $template_id = $row->{'template_id'};
111
112   if ( $template_id_copy ) {
113     my @actions = GetModificationTemplateActions( $template_id_copy );
114     foreach my $action ( @actions ) {
115       AddModificationTemplateAction(
116         $template_id,
117         $action->{'action'},
118         $action->{'field_number'},
119         $action->{'from_field'},
120         $action->{'from_subfield'},
121         $action->{'field_value'},
122         $action->{'to_field'},
123         $action->{'to_subfield'},
124         $action->{'to_regex_search'},
125         $action->{'to_regex_replace'},
126         $action->{'to_regex_modifiers'},
127         $action->{'conditional'},
128         $action->{'conditional_field'},
129         $action->{'conditional_subfield'},
130         $action->{'conditional_comparison'},
131         $action->{'conditional_value'},
132         $action->{'conditional_regex'},
133         $action->{'description'},
134       );
135
136     }
137   }
138
139   return $template_id;
140 }
141
142 =head2
143   DelModificationTemplate
144
145   DelModificationTemplate( $template_id );
146 =cut
147
148 sub DelModificationTemplate {
149   my ( $template_id ) = @_;
150
151   my $dbh = C4::Context->dbh;
152   my $sth = $dbh->prepare("DELETE FROM marc_modification_templates WHERE template_id = ?");
153   $sth->execute( $template_id );
154 }
155
156 =head2
157   GetModificationTemplateAction
158
159   my $action = GetModificationTemplateAction( $mmta_id );
160 =cut
161
162 sub GetModificationTemplateAction {
163   my ( $mmta_id ) = @_;
164
165   my $dbh = C4::Context->dbh;
166   my $sth = $dbh->prepare("SELECT * FROM marc_modification_template_actions WHERE mmta_id = ?");
167   $sth->execute( $mmta_id );
168   my $action = $sth->fetchrow_hashref();
169
170   return $action;
171 }
172
173 =head2
174   GetModificationTemplateActions
175
176   my @actions = GetModificationTemplateActions( $template_id );
177 =cut
178
179 sub GetModificationTemplateActions {
180   my ( $template_id ) = @_;
181
182   my $dbh = C4::Context->dbh;
183   my $sth = $dbh->prepare("SELECT * FROM marc_modification_template_actions WHERE template_id = ? ORDER BY ordering");
184   $sth->execute( $template_id );
185
186   my @actions;
187   while ( my $action = $sth->fetchrow_hashref() ) {
188     push( @actions, $action );
189   }
190
191   return @actions;
192 }
193
194 =head2
195   AddModificationTemplateAction
196
197   AddModificationTemplateAction(
198     $template_id, $action, $field_number,
199     $from_field, $from_subfield, $field_value,
200     $to_field, $to_subfield, $to_regex_search, $to_regex_replace, $to_regex_modifiers
201     $conditional, $conditional_field, $conditional_subfield,
202     $conditional_comparison, $conditional_value,
203     $conditional_regex, $description
204   );
205
206   Adds a new action to the given modification template.
207
208 =cut
209
210 sub AddModificationTemplateAction {
211   my (
212     $template_id,
213     $action,
214     $field_number,
215     $from_field,
216     $from_subfield,
217     $field_value,
218     $to_field,
219     $to_subfield,
220     $to_regex_search,
221     $to_regex_replace,
222     $to_regex_modifiers,
223     $conditional,
224     $conditional_field,
225     $conditional_subfield,
226     $conditional_comparison,
227     $conditional_value,
228     $conditional_regex,
229     $description
230   ) = @_;
231
232   $conditional ||= undef;
233   $conditional_comparison ||= undef;
234   $conditional_regex ||= '0';
235
236   my $dbh = C4::Context->dbh;
237   my $sth = $dbh->prepare( 'SELECT MAX(ordering) + 1 AS next_ordering FROM marc_modification_template_actions WHERE template_id = ?' );
238   $sth->execute( $template_id );
239   my $row = $sth->fetchrow_hashref;
240   my $ordering = $row->{'next_ordering'} || 1;
241
242   my $query = "
243   INSERT INTO marc_modification_template_actions (
244   mmta_id,
245   template_id,
246   ordering,
247   action,
248   field_number,
249   from_field,
250   from_subfield,
251   field_value,
252   to_field,
253   to_subfield,
254   to_regex_search,
255   to_regex_replace,
256   to_regex_modifiers,
257   conditional,
258   conditional_field,
259   conditional_subfield,
260   conditional_comparison,
261   conditional_value,
262   conditional_regex,
263   description
264   )
265   VALUES ( NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )";
266
267   $sth = $dbh->prepare( $query );
268
269   $sth->execute(
270     $template_id,
271     $ordering,
272     $action,
273     $field_number,
274     $from_field,
275     $from_subfield,
276     $field_value,
277     $to_field,
278     $to_subfield,
279     $to_regex_search,
280     $to_regex_replace,
281     $to_regex_modifiers,
282     $conditional,
283     $conditional_field,
284     $conditional_subfield,
285     $conditional_comparison,
286     $conditional_value,
287     $conditional_regex,
288     $description
289   );
290 }
291
292 =head2
293   ModModificationTemplateAction
294
295   ModModificationTemplateAction(
296     $mmta_id, $action, $field_number, $from_field,
297     $from_subfield, $field_value, $to_field,
298     $to_subfield, $to_regex_search, $to_regex_replace, $to_regex_modifiers, $conditional,
299     $conditional_field, $conditional_subfield,
300     $conditional_comparison, $conditional_value,
301     $conditional_regex, $description
302   );
303
304   Modifies an existing action.
305
306 =cut
307
308 sub ModModificationTemplateAction {
309   my (
310     $mmta_id,
311     $action,
312     $field_number,
313     $from_field,
314     $from_subfield,
315     $field_value,
316     $to_field,
317     $to_subfield,
318     $to_regex_search,
319     $to_regex_replace,
320     $to_regex_modifiers,
321     $conditional,
322     $conditional_field,
323     $conditional_subfield,
324     $conditional_comparison,
325     $conditional_value,
326     $conditional_regex,
327     $description
328   ) = @_;
329
330   my $dbh = C4::Context->dbh;
331   $conditional ||= undef;
332   $conditional_comparison ||= undef;
333   $conditional_regex ||= '0';
334
335   my $query = "
336   UPDATE marc_modification_template_actions SET
337   action = ?,
338   field_number = ?,
339   from_field = ?,
340   from_subfield = ?,
341   field_value = ?,
342   to_field = ?,
343   to_subfield = ?,
344   to_regex_search = ?,
345   to_regex_replace = ?,
346   to_regex_modifiers = ?,
347   conditional = ?,
348   conditional_field = ?,
349   conditional_subfield = ?,
350   conditional_comparison = ?,
351   conditional_value = ?,
352   conditional_regex = ?,
353   description = ?
354   WHERE mmta_id = ?";
355
356   my $sth = $dbh->prepare( $query );
357
358   $sth->execute(
359     $action,
360     $field_number,
361     $from_field,
362     $from_subfield,
363     $field_value,
364     $to_field,
365     $to_subfield,
366     $to_regex_search,
367     $to_regex_replace,
368     $to_regex_modifiers,
369     $conditional,
370     $conditional_field,
371     $conditional_subfield,
372     $conditional_comparison,
373     $conditional_value,
374     $conditional_regex,
375     $description,
376     $mmta_id
377   );
378 }
379
380
381 =head2
382   DelModificationTemplateAction
383
384   DelModificationTemplateAction( $mmta_id );
385
386   Deletes the given template action.
387 =cut
388
389 sub DelModificationTemplateAction {
390   my ( $mmta_id ) = @_;
391
392   my $action = GetModificationTemplateAction( $mmta_id );
393
394   my $dbh = C4::Context->dbh;
395   my $sth = $dbh->prepare("DELETE FROM marc_modification_template_actions WHERE mmta_id = ?");
396   $sth->execute( $mmta_id );
397
398   $sth = $dbh->prepare("UPDATE marc_modification_template_actions SET ordering = ordering - 1 WHERE template_id = ? AND ordering > ?");
399   $sth->execute( $action->{'template_id'}, $action->{'ordering'} );
400 }
401
402 =head2
403   MoveModificationTemplateAction
404
405   MoveModificationTemplateAction( $mmta_id, $where );
406
407   Changes the order for the given action.
408   Options for $where are 'up', 'down', 'top' and 'bottom'
409 =cut
410 sub MoveModificationTemplateAction {
411   my ( $mmta_id, $where ) = @_;
412
413   my $action = GetModificationTemplateAction( $mmta_id );
414
415   return if ( $action->{'ordering'} eq '1' && ( $where eq 'up' || $where eq 'top' ) );
416   return if ( $action->{'ordering'} eq GetModificationTemplateActions( $action->{'template_id'} ) && ( $where eq 'down' || $where eq 'bottom' ) );
417
418   my $dbh = C4::Context->dbh;
419   my ( $sth, $query );
420
421   if ( $where eq 'up' || $where eq 'down' ) {
422
423     ## For up and down, we just swap the ordering number with the one above or below it.
424
425     ## Change the ordering for the other action
426     $query = "UPDATE marc_modification_template_actions SET ordering = ? WHERE template_id = ? AND ordering = ?";
427
428     my $ordering = $action->{'ordering'};
429     $ordering-- if ( $where eq 'up' );
430     $ordering++ if ( $where eq 'down' );
431
432     $sth = $dbh->prepare( $query );
433     $sth->execute( $action->{'ordering'}, $action->{'template_id'}, $ordering );
434
435     ## Change the ordering for this action
436     $query = "UPDATE marc_modification_template_actions SET ordering = ? WHERE mmta_id = ?";
437     $sth = $dbh->prepare( $query );
438     $sth->execute( $ordering, $action->{'mmta_id'} );
439
440   } elsif ( $where eq 'top' ) {
441
442     $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = ordering + 1 WHERE template_id = ? AND ordering < ?');
443     $sth->execute( $action->{'template_id'}, $action->{'ordering'} );
444
445     $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = 1 WHERE mmta_id = ?');
446     $sth->execute( $mmta_id );
447
448   } elsif ( $where eq 'bottom' ) {
449
450     my $ordering = GetModificationTemplateActions( $action->{'template_id'} );
451
452     $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = ordering - 1 WHERE template_id = ? AND ordering > ?');
453     $sth->execute( $action->{'template_id'}, $action->{'ordering'} );
454
455     $sth = $dbh->prepare('UPDATE marc_modification_template_actions SET ordering = ? WHERE mmta_id = ?');
456     $sth->execute( $ordering, $mmta_id );
457
458   }
459
460 }
461
462 =head2
463   ModifyRecordsWithTemplate
464
465   ModifyRecordsWithTemplate( $template_id, $batch );
466
467   Accepts a template id and a MARC::Batch object.
468 =cut
469
470 sub ModifyRecordsWithTemplate {
471   my ( $template_id, $batch ) = @_;
472
473   while ( my $record = $batch->next() ) {
474     ModifyRecordWithTemplate( $template_id, $record );
475   }
476 }
477
478 =head2
479   ModifyRecordWithTemplate
480
481   ModifyRecordWithTemplate( $template_id, $record )
482
483   Accepts a MARC::Record object ( $record ) and modifies
484   it based on the actions for the given $template_id
485 =cut
486
487 sub ModifyRecordWithTemplate {
488     my ( $template_id, $record ) = @_;
489
490     my $current_date = dt_from_string()->ymd();
491     my $branchcode = '';
492     $branchcode = C4::Context->userenv->{branch} if C4::Context->userenv;
493
494     my @actions = GetModificationTemplateActions( $template_id );
495
496     foreach my $a ( @actions ) {
497         my $action = $a->{'action'};
498         my $field_number = $a->{'field_number'} // 1;
499         my $from_field = $a->{'from_field'};
500         my $from_subfield = $a->{'from_subfield'};
501         my $field_value = $a->{'field_value'};
502         my $to_field = $a->{'to_field'};
503         my $to_subfield = $a->{'to_subfield'};
504         my $to_regex_search = $a->{'to_regex_search'};
505         my $to_regex_replace = $a->{'to_regex_replace'};
506         my $to_regex_modifiers = $a->{'to_regex_modifiers'};
507         my $conditional = $a->{'conditional'};
508         my $conditional_field = $a->{'conditional_field'};
509         my $conditional_subfield = $a->{'conditional_subfield'};
510         my $conditional_comparison = $a->{'conditional_comparison'};
511         my $conditional_value = $a->{'conditional_value'};
512         my $conditional_regex = $a->{'conditional_regex'};
513
514         if ( $field_value ) {
515             $field_value =~ s/__CURRENTDATE__/$current_date/g;
516             $field_value =~ s/__BRANCHCODE__/$branchcode/g;
517         }
518
519         my $do = 1;
520         my $field_numbers = [];
521         if ( $conditional ) {
522             if ( $conditional_comparison eq 'exists' ) {
523                 $field_numbers = field_exists({
524                         record => $record,
525                         field => $conditional_field,
526                         subfield => $conditional_subfield,
527                     });
528                 $do = $conditional eq 'if'
529                     ? @$field_numbers
530                     : not @$field_numbers;
531             }
532             elsif ( $conditional_comparison eq 'not_exists' ) {
533                 $field_numbers = field_exists({
534                         record => $record,
535                         field => $conditional_field,
536                         subfield => $conditional_subfield
537                     });
538                 $do = $conditional eq 'if'
539                     ? not @$field_numbers
540                     : @$field_numbers;
541             }
542             elsif ( $conditional_comparison eq 'equals' ) {
543                 $field_numbers = field_equals({
544                     record => $record,
545                     value => $conditional_value,
546                     field => $conditional_field,
547                     subfield => $conditional_subfield,
548                     is_regex => $conditional_regex,
549                 });
550                 $do = $conditional eq 'if'
551                     ? @$field_numbers
552                     : not @$field_numbers;
553             }
554             elsif ( $conditional_comparison eq 'not_equals' ) {
555                 $field_numbers = field_equals({
556                     record => $record,
557                     value => $conditional_value,
558                     field => $conditional_field,
559                     subfield => $conditional_subfield,
560                     is_regex => $conditional_regex,
561                 });
562                 my $all_fields = [
563                     1 .. scalar @{
564                         field_exists(
565                             {
566                                 record   => $record,
567                                 field    => $conditional_field,
568                                 subfield => $conditional_subfield
569                             }
570                         )
571                     }
572                 ];
573                 $field_numbers = [Koha::MoreUtils::singleton ( @$field_numbers, @$all_fields ) ];
574                 if ( $from_field == $conditional_field ){
575                     $do = $conditional eq 'if'
576                         ? @$field_numbers
577                         : not @$field_numbers;
578                 } else {
579                     $do = $conditional eq 'if'
580                         ? not @$field_numbers
581                         : @$field_numbers;
582                 }
583             }
584         }
585
586         if ( $do ) {
587
588             # field_number == 0 if all field need to be updated
589             # or 1 if only the first field need to be updated
590
591             # A condition has been given
592             if ( @$field_numbers > 0 ) {
593                 if ( $field_number == 1 ) {
594                     # We want only the first
595                     if ( $from_field == $conditional_field ){
596                         # want first field matching condition
597                         $field_numbers = [ $field_numbers->[0] ];
598                     } else {
599                         # condition doesn't match, so just want first occurrence of from field
600                         $field_numbers = [ 1 ];
601                     }
602                 } else {
603                     unless ( $from_field == $conditional_field ){
604                         # condition doesn't match from fields so need all occurrences of from fields for action
605                         $field_numbers = field_exists({
606                             record => $record,
607                             field => $from_field,
608                             subfield => $from_subfield,
609                         });
610                     }
611                 }
612             }
613             # There was no condition
614             else {
615                 if ( $field_number == 1 ) {
616                     # We want to process the first field
617                     $field_numbers = [ 1 ];
618                 }
619             }
620
621             if ( $action eq 'copy_field' ) {
622                 copy_field({
623                     record => $record,
624                     from_field => $from_field,
625                     from_subfield => $from_subfield,
626                     to_field => $to_field,
627                     to_subfield => $to_subfield,
628                     regex => {
629                         search => $to_regex_search,
630                         replace => $to_regex_replace,
631                         modifiers => $to_regex_modifiers
632                     },
633                     field_numbers => $field_numbers,
634                 });
635             }
636             elsif ( $action eq 'copy_and_replace_field' ) {
637                 copy_and_replace_field({
638                     record => $record,
639                     from_field => $from_field,
640                     from_subfield => $from_subfield,
641                     to_field => $to_field,
642                     to_subfield => $to_subfield,
643                     regex => {
644                         search => $to_regex_search,
645                         replace => $to_regex_replace,
646                         modifiers => $to_regex_modifiers
647                     },
648                     field_numbers => $field_numbers,
649                 });
650             }
651             elsif ( $action eq 'add_field' ) {
652                 add_field({
653                     record => $record,
654                     field => $from_field,
655                     subfield => $from_subfield,
656                     values => [ $field_value ],
657                     field_numbers => $field_numbers,
658                 });
659             }
660             elsif ( $action eq 'update_field' ) {
661                 update_field({
662                     record => $record,
663                     field => $from_field,
664                     subfield => $from_subfield,
665                     values => [ $field_value ],
666                     field_numbers => $field_numbers,
667                 });
668             }
669             elsif ( $action eq 'move_field' ) {
670                 move_field({
671                     record => $record,
672                     from_field => $from_field,
673                     from_subfield => $from_subfield,
674                     to_field => $to_field,
675                     to_subfield => $to_subfield,
676                     regex => {
677                         search => $to_regex_search,
678                         replace => $to_regex_replace,
679                         modifiers => $to_regex_modifiers
680                     },
681                     field_numbers => $field_numbers,
682                 });
683             }
684             elsif ( $action eq 'delete_field' ) {
685                 delete_field({
686                     record => $record,
687                     field => $from_field,
688                     subfield => $from_subfield,
689                     field_numbers => $field_numbers,
690                 });
691             }
692         }
693     }
694
695     return;
696 }
697 1;
698 __END__
699
700 =head1 AUTHOR
701
702 Kyle M Hall
703
704 =cut