Bug 12151: Remove uses of smartmatch operator in Search.pm and opac-search.pl
[koha.git] / C4 / OAI / Sets.pm
1 package C4::OAI::Sets;
2
3 # Copyright 2011 BibLibre
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 =head1 NAME
21
22 C4::OAI::Sets - OAI Sets management functions
23
24 =head1 DESCRIPTION
25
26 C4::OAI::Sets contains functions for managing storage and editing of OAI Sets.
27
28 OAI Set description can be found L<here|http://www.openarchives.org/OAI/openarchivesprotocol.html#Set>
29
30 =cut
31
32 use Modern::Perl;
33 use C4::Context;
34
35 use vars qw(@ISA @EXPORT);
36
37 BEGIN {
38     require Exporter;
39     @ISA = qw(Exporter);
40     @EXPORT = qw(
41         &GetOAISets &GetOAISet &GetOAISetBySpec &ModOAISet &DelOAISet &AddOAISet
42         &GetOAISetsMappings &GetOAISetMappings &ModOAISetMappings
43         &GetOAISetsBiblio &ModOAISetsBiblios &AddOAISetsBiblios
44         &CalcOAISetsBiblio &UpdateOAISetsBiblio
45     );
46 }
47
48 =head1 FUNCTIONS
49
50 =head2 GetOAISets
51
52     $oai_sets = GetOAISets;
53
54 GetOAISets return a array reference of hash references describing the sets.
55 The hash references looks like this:
56
57     {
58         'name'         => 'set name',
59         'spec'         => 'set spec',
60         'descriptions' => [
61             'description 1',
62             'description 2',
63             ...
64         ]
65     }
66
67 =cut
68
69 sub GetOAISets {
70     my $dbh = C4::Context->dbh;
71     my $query = qq{
72         SELECT * FROM oai_sets
73     };
74     my $sth = $dbh->prepare($query);
75     $sth->execute;
76     my $results = $sth->fetchall_arrayref({});
77
78     $query = qq{
79         SELECT description
80         FROM oai_sets_descriptions
81         WHERE set_id = ?
82     };
83     $sth = $dbh->prepare($query);
84     foreach my $set (@$results) {
85         $sth->execute($set->{'id'});
86         my $desc = $sth->fetchall_arrayref({});
87         foreach (@$desc) {
88             push @{$set->{'descriptions'}}, $_->{'description'};
89         }
90     }
91
92     return $results;
93 }
94
95 =head2 GetOAISet
96
97     $set = GetOAISet($set_id);
98
99 GetOAISet returns a hash reference describing the set with the given set_id.
100
101 See GetOAISets to see what the hash looks like.
102
103 =cut
104
105 sub GetOAISet {
106     my ($set_id) = @_;
107
108     return unless $set_id;
109
110     my $dbh = C4::Context->dbh;
111     my $query = qq{
112         SELECT *
113         FROM oai_sets
114         WHERE id = ?
115     };
116     my $sth = $dbh->prepare($query);
117     $sth->execute($set_id);
118     my $set = $sth->fetchrow_hashref;
119
120     $query = qq{
121         SELECT description
122         FROM oai_sets_descriptions
123         WHERE set_id = ?
124     };
125     $sth = $dbh->prepare($query);
126     $sth->execute($set->{'id'});
127     my $desc = $sth->fetchall_arrayref({});
128     foreach (@$desc) {
129         push @{$set->{'descriptions'}}, $_->{'description'};
130     }
131
132     return $set;
133 }
134
135 =head2 GetOAISetBySpec
136
137     my $set = GetOAISetBySpec($setSpec);
138
139 Returns a hash describing the set whose spec is $setSpec
140
141 =cut
142
143 sub GetOAISetBySpec {
144     my $setSpec = shift;
145
146     return unless defined $setSpec;
147
148     my $dbh = C4::Context->dbh;
149     my $query = qq{
150         SELECT *
151         FROM oai_sets
152         WHERE spec = ?
153         LIMIT 1
154     };
155     my $sth = $dbh->prepare($query);
156     $sth->execute($setSpec);
157
158     return $sth->fetchrow_hashref;
159 }
160
161 =head2 ModOAISet
162
163     my $set = {
164         'id' => $set_id,                 # mandatory
165         'spec' => $spec,                 # mandatory
166         'name' => $name,                 # mandatory
167         'descriptions => \@descriptions, # optional, [] to remove descriptions
168     };
169     ModOAISet($set);
170
171 ModOAISet modify a set in the database.
172
173 =cut
174
175 sub ModOAISet {
176     my ($set) = @_;
177
178     return unless($set && $set->{'spec'} && $set->{'name'});
179
180     if(!defined $set->{'id'}) {
181         warn "Set ID not defined, can't modify the set";
182         return;
183     }
184
185     my $dbh = C4::Context->dbh;
186     my $query = qq{
187         UPDATE oai_sets
188         SET spec = ?,
189             name = ?
190         WHERE id = ?
191     };
192     my $sth = $dbh->prepare($query);
193     $sth->execute($set->{'spec'}, $set->{'name'}, $set->{'id'});
194
195     if($set->{'descriptions'}) {
196         $query = qq{
197             DELETE FROM oai_sets_descriptions
198             WHERE set_id = ?
199         };
200         $sth = $dbh->prepare($query);
201         $sth->execute($set->{'id'});
202
203         if(scalar @{$set->{'descriptions'}} > 0) {
204             $query = qq{
205                 INSERT INTO oai_sets_descriptions (set_id, description)
206                 VALUES (?,?)
207             };
208             $sth = $dbh->prepare($query);
209             foreach (@{ $set->{'descriptions'} }) {
210                 $sth->execute($set->{'id'}, $_) if $_;
211             }
212         }
213     }
214 }
215
216 =head2 DelOAISet
217
218     DelOAISet($set_id);
219
220 DelOAISet remove the set with the given set_id
221
222 =cut
223
224 sub DelOAISet {
225     my ($set_id) = @_;
226
227     return unless $set_id;
228
229     my $dbh = C4::Context->dbh;
230     my $query = qq{
231         DELETE oai_sets, oai_sets_descriptions, oai_sets_mappings
232         FROM oai_sets
233           LEFT JOIN oai_sets_descriptions ON oai_sets_descriptions.set_id = oai_sets.id
234           LEFT JOIN oai_sets_mappings ON oai_sets_mappings.set_id = oai_sets.id
235         WHERE oai_sets.id = ?
236     };
237     my $sth = $dbh->prepare($query);
238     $sth->execute($set_id);
239 }
240
241 =head2 AddOAISet
242
243     my $set = {
244         'id' => $set_id,                 # mandatory
245         'spec' => $spec,                 # mandatory
246         'name' => $name,                 # mandatory
247         'descriptions => \@descriptions, # optional
248     };
249     my $set_id = AddOAISet($set);
250
251 AddOAISet adds a new set and returns its id, or undef if something went wrong.
252
253 =cut
254
255 sub AddOAISet {
256     my ($set) = @_;
257
258     return unless($set && $set->{'spec'} && $set->{'name'});
259
260     my $set_id;
261     my $dbh = C4::Context->dbh;
262     my $query = qq{
263         INSERT INTO oai_sets (spec, name)
264         VALUES (?,?)
265     };
266     my $sth = $dbh->prepare($query);
267     if( $sth->execute($set->{'spec'}, $set->{'name'}) ) {
268         $set_id = $dbh->last_insert_id(undef, undef, 'oai_sets', undef);
269         if($set->{'descriptions'}) {
270             $query = qq{
271                 INSERT INTO oai_sets_descriptions (set_id, description)
272                 VALUES (?,?)
273             };
274             $sth = $dbh->prepare($query);
275             foreach( @{ $set->{'descriptions'} } ) {
276                 $sth->execute($set_id, $_) if $_;
277             }
278         }
279     } else {
280         warn "AddOAISet failed";
281     }
282
283     return $set_id;
284 }
285
286 =head2 GetOAISetsMappings
287
288     my $mappings = GetOAISetsMappings;
289
290 GetOAISetsMappings returns mappings for all OAI Sets.
291
292 Mappings define how biblios are categorized in sets.
293 A mapping is defined by four properties:
294
295     {
296         marcfield => 'XXX',     # the MARC field to check
297         marcsubfield => 'Y',    # the MARC subfield to check
298         operator => 'A',        # the operator 'equal' or 'notequal'; 'equal' if ''
299         marcvalue => 'zzzz',    # the value to check
300     }
301
302 If defined in a set mapping, a biblio which have at least one 'Y' subfield of
303 one 'XXX' field equal to 'zzzz' will belong to this set.
304 If multiple mappings are defined in a set, the biblio will belong to this set
305 if at least one condition is matched.
306
307 GetOAISetsMappings returns a hashref of arrayrefs of hashrefs.
308 The first hashref keys are the sets IDs, so it looks like this:
309
310     $mappings = {
311         '1' => [
312             {
313                 marcfield => 'XXX',
314                 marcsubfield => 'Y',
315                 operator => 'A',
316                 marcvalue => 'zzzz'
317             },
318             {
319                 ...
320             },
321             ...
322         ],
323         '2' => [...],
324         ...
325     };
326
327 =cut
328
329 sub GetOAISetsMappings {
330     my $dbh = C4::Context->dbh;
331     my $query = qq{
332         SELECT * FROM oai_sets_mappings
333     };
334     my $sth = $dbh->prepare($query);
335     $sth->execute;
336
337     my $mappings = {};
338     while(my $result = $sth->fetchrow_hashref) {
339         push @{ $mappings->{$result->{'set_id'}} }, {
340             marcfield => $result->{'marcfield'},
341             marcsubfield => $result->{'marcsubfield'},
342             operator => $result->{'operator'},
343             marcvalue => $result->{'marcvalue'}
344         };
345     }
346
347     return $mappings;
348 }
349
350 =head2 GetOAISetMappings
351
352     my $set_mappings = GetOAISetMappings($set_id);
353
354 Return mappings for the set with given set_id. It's an arrayref of hashrefs
355
356 =cut
357
358 sub GetOAISetMappings {
359     my ($set_id) = @_;
360
361     return unless $set_id;
362
363     my $dbh = C4::Context->dbh;
364     my $query = qq{
365         SELECT *
366         FROM oai_sets_mappings
367         WHERE set_id = ?
368     };
369     my $sth = $dbh->prepare($query);
370     $sth->execute($set_id);
371
372     my @mappings;
373     while(my $result = $sth->fetchrow_hashref) {
374         push @mappings, {
375             marcfield => $result->{'marcfield'},
376             marcsubfield => $result->{'marcsubfield'},
377             operator => $result->{'operator'},
378             marcvalue => $result->{'marcvalue'}
379         };
380     }
381
382     return \@mappings;
383 }
384
385 =head2 ModOAISetMappings {
386
387     my $mappings = [
388         {
389             marcfield => 'XXX',
390             marcsubfield => 'Y',
391             operator => 'A',
392             marcvalue => 'zzzz'
393         },
394         ...
395     ];
396     ModOAISetMappings($set_id, $mappings);
397
398 ModOAISetMappings modifies mappings of a given set.
399
400 =cut
401
402 sub ModOAISetMappings {
403     my ($set_id, $mappings) = @_;
404
405     return unless $set_id;
406
407     my $dbh = C4::Context->dbh;
408     my $query = qq{
409         DELETE FROM oai_sets_mappings
410         WHERE set_id = ?
411     };
412     my $sth = $dbh->prepare($query);
413     $sth->execute($set_id);
414
415     if(scalar @$mappings > 0) {
416         $query = qq{
417             INSERT INTO oai_sets_mappings (set_id, marcfield, marcsubfield, operator, marcvalue)
418             VALUES (?,?,?,?,?)
419         };
420         $sth = $dbh->prepare($query);
421         foreach (@$mappings) {
422             $sth->execute($set_id, $_->{'marcfield'}, $_->{'marcsubfield'}, $_->{'operator'}, $_->{'marcvalue'});
423         }
424     }
425 }
426
427 =head2 GetOAISetsBiblio
428
429     $oai_sets = GetOAISetsBiblio($biblionumber);
430
431 Return the OAI sets where biblio appears.
432
433 Return value is an arrayref of hashref where each element of the array is a set.
434 Keys of hash are id, spec and name
435
436 =cut
437
438 sub GetOAISetsBiblio {
439     my ($biblionumber) = @_;
440
441     my $dbh = C4::Context->dbh;
442     my $query = qq{
443         SELECT oai_sets.*
444         FROM oai_sets
445           LEFT JOIN oai_sets_biblios ON oai_sets_biblios.set_id = oai_sets.id
446         WHERE biblionumber = ?
447     };
448     my $sth = $dbh->prepare($query);
449
450     $sth->execute($biblionumber);
451     return $sth->fetchall_arrayref({});
452 }
453
454 =head2 DelOAISetsBiblio
455
456     DelOAISetsBiblio($biblionumber);
457
458 Remove a biblio from all sets
459
460 =cut
461
462 sub DelOAISetsBiblio {
463     my ($biblionumber) = @_;
464
465     return unless $biblionumber;
466
467     my $dbh = C4::Context->dbh;
468     my $query = qq{
469         DELETE FROM oai_sets_biblios
470         WHERE biblionumber = ?
471     };
472     my $sth = $dbh->prepare($query);
473     return $sth->execute($biblionumber);
474 }
475
476 =head2 CalcOAISetsBiblio
477
478     my @sets = CalcOAISetsBiblio($record, $oai_sets_mappings);
479
480 Return a list of set ids the record belongs to. $record must be a MARC::Record
481 and $oai_sets_mappings (optional) must be a hashref returned by
482 GetOAISetsMappings
483
484 =cut
485
486 sub CalcOAISetsBiblio {
487     my ($record, $oai_sets_mappings) = @_;
488
489     return unless $record;
490
491     $oai_sets_mappings ||= GetOAISetsMappings;
492
493     my @biblio_sets;
494     foreach my $set_id (keys %$oai_sets_mappings) {
495         foreach my $mapping (@{ $oai_sets_mappings->{$set_id} }) {
496             next if not $mapping;
497             my $field = $mapping->{'marcfield'};
498             my $subfield = $mapping->{'marcsubfield'};
499             my $operator = $mapping->{'operator'};
500             my $value = $mapping->{'marcvalue'};
501             my @subfield_values = $record->subfield($field, $subfield);
502             if ($operator eq 'notequal') {
503                 if(0 == grep /^$value$/, @subfield_values) {
504                     push @biblio_sets, $set_id;
505                     last;
506                 }
507             }
508             else {
509                 if(0 < grep /^$value$/, @subfield_values) {
510                     push @biblio_sets, $set_id;
511                     last;
512                 }
513             }
514         }
515     }
516     return @biblio_sets;
517 }
518
519 =head2 ModOAISetsBiblios
520
521     my $oai_sets_biblios = {
522         '1' => [1, 3, 4],   # key is the set_id, and value is an array ref of biblionumbers
523         '2' => [],
524         ...
525     };
526     ModOAISetsBiblios($oai_sets_biblios);
527
528 ModOAISetsBiblios truncate oai_sets_biblios table and call AddOAISetsBiblios.
529 This table is then used in opac/oai.pl.
530
531 =cut
532
533 sub ModOAISetsBiblios {
534     my $oai_sets_biblios = shift;
535
536     return unless ref($oai_sets_biblios) eq "HASH";
537
538     my $dbh = C4::Context->dbh;
539     my $query = qq{
540         TRUNCATE TABLE oai_sets_biblios
541     };
542     my $sth = $dbh->prepare($query);
543     $sth->execute;
544     AddOAISetsBiblios($oai_sets_biblios);
545 }
546
547 =head2 UpdateOAISetsBiblio
548
549     UpdateOAISetsBiblio($biblionumber, $record);
550
551 Update OAI sets for one biblio. The two parameters are mandatory.
552 $record is a MARC::Record.
553
554 =cut
555
556 sub UpdateOAISetsBiblio {
557     my ($biblionumber, $record) = @_;
558
559     return unless($biblionumber and $record);
560
561     my $sets_biblios;
562     my @sets = CalcOAISetsBiblio($record);
563     foreach (@sets) {
564         push @{ $sets_biblios->{$_} }, $biblionumber;
565     }
566     DelOAISetsBiblio($biblionumber);
567     AddOAISetsBiblios($sets_biblios);
568 }
569
570 =head2 AddOAISetsBiblios
571
572     my $oai_sets_biblios = {
573         '1' => [1, 3, 4],   # key is the set_id, and value is an array ref of biblionumbers
574         '2' => [],
575         ...
576     };
577     ModOAISetsBiblios($oai_sets_biblios);
578
579 AddOAISetsBiblios insert given infos in oai_sets_biblios table.
580 This table is then used in opac/oai.pl.
581
582 =cut
583
584 sub AddOAISetsBiblios {
585     my $oai_sets_biblios = shift;
586
587     return unless ref($oai_sets_biblios) eq "HASH";
588
589     my $dbh = C4::Context->dbh;
590     my $query = qq{
591         INSERT INTO oai_sets_biblios (set_id, biblionumber)
592         VALUES (?,?)
593     };
594     my $sth = $dbh->prepare($query);
595     foreach my $set_id (keys %$oai_sets_biblios) {
596         foreach my $biblionumber (@{$oai_sets_biblios->{$set_id}}) {
597             $sth->execute($set_id, $biblionumber);
598         }
599     }
600 }
601
602 1;