Bug 15774: Add additional fields to order baskets
[koha.git] / Koha / AdditionalField.pm
1 package Koha::AdditionalField;
2
3 use Modern::Perl;
4
5 use base qw(Class::Accessor);
6
7 use C4::Context;
8
9 __PACKAGE__->mk_accessors(qw( id tablename name authorised_value_category marcfield searchable values ));
10
11 sub new {
12     my ( $class, $args ) = @_;
13
14     my $additional_field = {
15         id => $args->{id} // q||,
16         tablename => $args->{tablename} // q||,
17         name => $args->{name} // q||,
18         authorised_value_category => $args->{authorised_value_category} // q||,
19         marcfield => $args->{marcfield} // q||,
20         searchable => $args->{searchable} // 0,
21         values => $args->{values} // {},
22     };
23
24     my $self = $class->SUPER::new( $additional_field );
25
26     bless $self, $class;
27     return $self;
28 }
29
30 sub fetch {
31     my ( $self ) = @_;
32     my $dbh = C4::Context->dbh;
33     my $field_id = $self->id;
34     return unless $field_id;
35     my $data = $dbh->selectrow_hashref(
36         q|
37             SELECT id, tablename, name, authorised_value_category, marcfield, searchable
38             FROM additional_fields
39             WHERE id = ?
40         |,
41         {}, ( $field_id )
42     );
43
44     die "This additional field does not exist (id=$field_id)" unless $data;
45     $self->{id} = $data->{id};
46     $self->{tablename} = $data->{tablename};
47     $self->{name} = $data->{name};
48     $self->{authorised_value_category} = $data->{authorised_value_category};
49     $self->{marcfield} = $data->{marcfield};
50     $self->{searchable} = $data->{searchable};
51     return $self;
52 }
53
54 sub update {
55     my ( $self ) = @_;
56
57     die "There is no id defined for this additional field. I cannot update it" unless $self->{id};
58
59     my $dbh = C4::Context->dbh;
60     local $dbh->{RaiseError} = 1;
61
62     return $dbh->do(q|
63         UPDATE additional_fields
64         SET name = ?,
65             authorised_value_category = ?,
66             marcfield = ?,
67             searchable = ?
68         WHERE id = ?
69     |, {}, ( $self->{name}, $self->{authorised_value_category}, $self->{marcfield}, $self->{searchable}, $self->{id} ) );
70 }
71
72 sub delete {
73     my ( $self ) = @_;
74     return unless $self->{id};
75     my $dbh = C4::Context->dbh;
76     local $dbh->{RaiseError} = 1;
77     return $dbh->do(q|
78         DELETE FROM additional_fields WHERE id = ?
79     |, {}, ( $self->{id} ) );
80 }
81
82 sub insert {
83     my ( $self ) = @_;
84     my $dbh = C4::Context->dbh;
85     local $dbh->{RaiseError} = 1;
86     $dbh->do(q|
87         INSERT INTO additional_fields
88         ( tablename, name, authorised_value_category, marcfield, searchable )
89         VALUES ( ?, ?, ?, ?, ? )
90     |, {}, ( $self->{tablename}, $self->{name}, $self->{authorised_value_category}, $self->{marcfield}, $self->{searchable} ) );
91     $self->{id} = $dbh->{mysql_insertid};
92 }
93
94 sub insert_values {
95     my ( $self )  = @_;
96
97     my $dbh = C4::Context->dbh;
98     local $dbh->{RaiseError} = 1;
99     while ( my ( $record_id, $value ) = each %{$self->{values}} ) {
100         next unless defined $value;
101         my $updated = $dbh->do(q|
102             UPDATE additional_field_values
103             SET value = ?
104             WHERE field_id = ?
105             AND record_id = ?
106         |, {}, ( $value, $self->{id}, $record_id ));
107         if ( $updated eq '0E0' ) {
108             $dbh->do(q|
109                 INSERT INTO additional_field_values( field_id, record_id, value )
110                 VALUES( ?, ?, ?)
111             |, {}, ( $self->{id}, $record_id, $value ));
112         }
113     }
114 }
115
116 sub fetch_values {
117     my ( $self, $args ) = @_;
118     my $record_id = $args->{record_id};
119     my $dbh = C4::Context->dbh;
120     my $values = $dbh->selectall_arrayref(
121         q|
122             SELECT *
123             FROM additional_fields af, additional_field_values afv
124             WHERE af.id = afv.field_id
125                 AND af.tablename = ?
126                 AND af.name = ?
127         | . ( $record_id ? q|AND afv.record_id = ?| : '' ),
128         {Slice => {}}, ( $self->{tablename}, $self->{name}, ($record_id ? $record_id : () ) )
129     );
130
131     $self->{values} = {};
132     for my $v ( @$values ) {
133         $self->{values}{$v->{record_id}} = $v->{value};
134     }
135 }
136
137 sub delete_values {
138     my ($self, $args) = @_;
139
140     my $record_id = $args->{record_id};
141
142     my $dbh = C4::Context->dbh;
143
144     my @where_strs = ('field_id = ?');
145     my @where_args = ($self->{id});
146
147     if ($record_id) {
148         push @where_strs, 'record_id = ?';
149         push @where_args, $record_id;
150     }
151
152     my $query = q{
153         DELETE FROM additional_field_values
154         WHERE
155     } . join (' AND ', @where_strs);
156
157     $dbh->do($query, undef, @where_args);
158 }
159
160 sub all {
161     my ( $class, $args ) = @_;
162     die "BAD CALL: Don't use fetch_all_values as an instance method"
163         if ref $class and UNIVERSAL::can($class,'can');
164     my $tablename = $args->{tablename};
165     my $searchable = $args->{searchable};
166     my $dbh = C4::Context->dbh;
167     my $query = q|
168         SELECT * FROM additional_fields WHERE 1
169     |;
170     $query .= q| AND tablename = ?|
171         if $tablename;
172
173     $query .= q| AND searchable = ?|
174         if defined $searchable;
175
176     my $results = $dbh->selectall_arrayref(
177         $query, {Slice => {}}, (
178             $tablename ? $tablename : (),
179             defined $searchable ? $searchable : ()
180         )
181     );
182     my @fields;
183     for my $r ( @$results ) {
184         push @fields, Koha::AdditionalField->new({
185             id => $r->{id},
186             tablename => $r->{tablename},
187             name => $r->{name},
188             authorised_value_category => $r->{authorised_value_category},
189             marcfield => $r->{marcfield},
190             searchable => $r->{searchable},
191         });
192     }
193     return \@fields;
194
195 }
196
197 sub fetch_all_values {
198     my ( $class, $args ) = @_;
199     die "BAD CALL: Don't use fetch_all_values as an instance method"
200         if ref $class and UNIVERSAL::can($class,'can');
201
202     my $record_id = $args->{record_id};
203     my $tablename = $args->{tablename};
204     return unless $tablename;
205
206     my $dbh = C4::Context->dbh;
207     my $values = $dbh->selectall_arrayref(
208         q|
209             SELECT afv.record_id, af.name, afv.value
210             FROM additional_fields af, additional_field_values afv
211             WHERE af.id = afv.field_id
212                 AND af.tablename = ?
213         | . ( $record_id ? q| AND afv.record_id = ?| : q|| ),
214         {Slice => {}}, ( $tablename, ($record_id ? $record_id : ()) )
215     );
216
217     my $r;
218     for my $v ( @$values ) {
219         $r->{$v->{record_id}}{$v->{name}} = $v->{value};
220     }
221     return $r;
222 }
223
224 sub get_matching_record_ids {
225     my ( $class, $args ) = @_;
226     die "BAD CALL: Don't use fetch_all_values as an instance method"
227         if ref $class and UNIVERSAL::can($class,'can');
228
229     my $fields = $args->{fields} // [];
230     my $tablename = $args->{tablename};
231     my $exact_match = $args->{exact_match} // 1;
232     return [] unless @$fields;
233
234     my $dbh = C4::Context->dbh;
235     my $query = q|SELECT * FROM |;
236     my ( @subqueries, @args );
237     my $i = 0;
238     for my $field ( @$fields ) {
239         $i++;
240         my $subquery = qq|(
241             SELECT record_id, field$i.name AS field${i}_name
242             FROM additional_field_values afv
243             LEFT JOIN
244                 (
245                     SELECT afv.id, af.name, afv.value
246                     FROM additional_field_values afv, additional_fields af
247                     WHERE afv.field_id = af.id
248                     AND af.name = ?
249                     AND af.tablename = ?
250                     AND value LIKE ?
251                 ) AS field$i USING (id)
252             WHERE field$i.id IS NOT NULL
253         ) AS values$i |;
254         $subquery .= ' USING (record_id)' if $i > 1;
255         push @subqueries, $subquery;
256         push @args, $field->{name}, $tablename, ( ( $exact_match or $field->{authorised_value_category} ) ? $field->{value} : "%$field->{value}%" );
257     }
258     $query .= join( ' LEFT JOIN ', @subqueries ) . ' WHERE 1';
259     for my $j ( 1 .. $i ) {
260             $query .= qq| AND field${j}_name IS NOT NULL|;
261     }
262     my $values = $dbh->selectall_arrayref( $query, {Slice => {}}, @args );
263     return [
264         map { $_->{record_id} } @$values
265     ]
266 }
267
268 sub update_fields_from_query {
269     my ( $class, $args ) = @_;
270     die "BAD CALL: Don't use update_fields_from_query as an instance method"
271         if ref $class and UNIVERSAL::can($class,'can');
272
273     my $query = $args->{query};
274     my $additional_fields = Koha::AdditionalField->all( { tablename => $args->{tablename} } );
275     for my $field ( @$additional_fields ) {
276         my $af = Koha::AdditionalField->new({ id => $field->{id} })->fetch;
277         if ( $af->{marcfield} ) {
278             my ( $field, $subfield ) = split /\$/, $af->{marcfield};
279             $af->{values} = undef;
280             if ( $args->{marc_record} and $field and $subfield ) {
281                 my $value = $args->{marc_record}->subfield( $field, $subfield );
282                 $af->{values} = {
283                     $args->{record_id} => $value
284                 };
285             }
286         } else {
287             $af->{values} = {
288                 $args->{record_id} => $query->param( 'additional_field_' . $field->{id} )
289             } if defined $query->param( 'additional_field_' . $field->{id} );
290         }
291         $af->insert_values;
292     }
293 }
294
295 sub get_filters_from_query {
296     my ( $class, $args ) = @_;
297     die "BAD CALL: Don't use get_filters_from_query as an instance method"
298         if ref $class and UNIVERSAL::can($class,'can');
299
300     my $query = $args->{query};
301     my $additional_fields = Koha::AdditionalField->all( { tablename => $args->{tablename}, searchable => 1 } );
302     my @additional_field_filters;
303     for my $field ( @$additional_fields ) {
304         my $filter_value = $query->param( 'additional_field_' . $field->{id} );
305         if ( defined $filter_value and $filter_value ne q|| ) {
306             push @additional_field_filters, {
307                 name => $field->{name},
308                 value => $filter_value,
309                 authorised_value_category => $field->{authorised_value_category},
310             };
311         }
312     }
313
314     return \@additional_field_filters;
315 }
316
317 sub get_filters_as_values {
318     my ( $class, $filters ) = @_;
319     die "BAD CALL: Don't use get_filters_as_values as an instance method"
320         if ref $class and UNIVERSAL::can($class,'can');
321
322     return { map { $_->{name} => $_->{value} } @$filters };
323 }
324
325 1;
326
327 __END__
328
329 =head1 NAME
330
331 Koha::AdditionalField
332
333 =head1 SYNOPSIS
334
335     use Koha::AdditionalField;
336     my $af1 = Koha::AdditionalField->new({id => $id});
337     my $af2 = Koha::AuthorisedValue->new({
338         tablename => 'my_table',
339         name => 'a_name',
340         authorised_value_category => 'LOST',
341         marcfield => '200$a',
342         searchable => 1,
343     });
344     $av1->delete;
345     $av2->{name} = 'another_name';
346     $av2->update;
347
348 =head1 DESCRIPTION
349
350 Class for managing additional fields into Koha.
351
352 =head1 METHODS
353
354 =head2 new
355
356 Create a new Koha::AdditionalField object. This method can be called using several ways.
357 Either with the id for existing field or with different values for a new one.
358
359 =over 4
360
361 =item B<id>
362
363     The caller just knows the id of the additional field and want to retrieve all values.
364
365 =item B<tablename, name, authorised_value_category, marcfield and searchable>
366
367     The caller wants to create a new additional field.
368
369 =back
370
371 =head2 fetch
372
373 The information will be retrieved from the database.
374
375 =head2 update
376
377 If the AdditionalField object has been modified and the values have to be modified into the database, call this method.
378
379 =head2 delete
380
381 Remove a the record in the database using the id the object.
382
383 =head2 insert
384
385 Insert a new AdditionalField object into the database.
386
387 =head2 insert_values
388
389 Insert new values for a record.
390
391     my $af = Koha::AdditionalField({ id => $id })->fetch;
392     my $af->{values} = {
393         record_id1 => 'my value',
394         record_id2 => 'another value',
395     };
396     $af->insert_values;
397
398 =head2 fetch_values
399
400 Retrieve values from the database for a given record_id.
401 The record_id argument is optional.
402
403     my $af = Koha::AdditionalField({ id => $id })->fetch;
404     my $values = $af->fetch_values({record_id => $record_id});
405
406     $values will be equal to something like:
407     {
408         record_id => {
409             field_name1 => 'value1',
410             field_name2 => 'value2',
411         }
412     }
413
414 =head2 delete_values
415
416 Delete values from the database for a given record_id.
417 The record_id argument is optional.
418
419     my $af = Koha::AdditionalField({ id => $id })->fetch;
420     $af->delete_values({record_id => $record_id});
421
422 =head2 all
423
424 Retrieve all additional fields in the database given some parameters.
425 Parameters are optional.
426 This method returns a list of AdditionalField objects.
427 This is a static method.
428
429     my $fields = Koha::AdditionalField->all;
430     or
431     my $fields = Koha::AdditionalField->all{(tablename => 'my_table'});
432     or
433     my $fields = Koha::AdditionalField->all({searchable => 1});
434
435 =head2 fetch_all_values
436
437 Retrieve all values for a table name.
438 This is a static method.
439
440     my $values = Koha::AdditionalField({ tablename => 'my_table' });
441
442     $values will be equel to something like:
443     {
444         record_id1 => {
445             field_name1 => 'value1',
446             field_name2 => 'value2',
447         },
448         record_id2 => {
449             field_name1 => 'value3',
450             field_name2 => 'value4',
451         }
452
453     }
454
455 =head2 get_matching_record_ids
456
457 Retrieve all record_ids for records matching the field values given in parameter.
458 This method returns a list of ids.
459 This is a static method.
460
461     my $fields = [
462         {
463             name => 'field_name',
464             value => 'field_value',
465         }
466     ];
467     my $ids = Koha::AdditionalField->get_matching_record_ids(
468         {
469             tablename => 'subscription',
470             fields => $fields
471         }
472     );
473
474 =head2 update_fields_from_query
475
476 Updates fields based on user input (best paired with additional-fields-entry.pl) and optionally a MARC record.
477
478 This is a static method.
479
480     Koha::AdditionalField->update_fields_from_query( {
481         tablename => 'aqbasket',
482         input => $input,
483         record_id => $basketno,
484         marc_record => GetBiblio( $biblionumber ),
485     } );
486
487 =head2 get_filters_from_query
488
489 Extracts a list of search filters from user input, to be used with C<get_matching_record_ids> and
490 C<get_filters_as_values>.
491
492 This is a static method.
493
494     my $filters = Koha::AdditionalField->get_filters_from_query( {
495         tablename => 'aqbasket',
496         input => $input,
497     } );
498
499 =head2 get_filters_as_values
500
501 Transforms the result of C<get_filters_from_query> into a hashref of C<name> => C<value>, so
502 user-entered filters can be redisplayed.
503
504     $template->param(
505         additional_field_values => Koha::AdditionalField->get_filters_as_values(
506             $filters
507         ),
508     );
509
510     [% INCLUDE 'additional-field-entry.inc'
511         values=additional_field_values
512         available=available_additional_fields
513     %]
514
515 =head1 AUTHOR
516
517 Jonathan Druart <jonathan.druart at biblibre.com>
518
519 =head1 COPYRIGHT
520
521 Copyright 2013 BibLibre
522
523 =head1 LICENSE
524
525 This file is part of Koha.
526
527 Koha is free software; you can redistribute it and/or modify it under the
528 terms of the GNU General Public License as published by the Free Software
529 Foundation; either version 3 of the License, or (at your option) any later
530 version.
531
532 Koha is distributed in the hope that it will be useful, but WITHOUT ANY
533 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
534 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
535
536 You should have received a copy of the GNU General Public License along
537 with Koha; if not, see <http://www.gnu.org/licenses>.