Bug 13757: Add the option to set patron attributes editable in the OPAC
[koha.git] / C4 / Members / AttributeTypes.pm
1 package C4::Members::AttributeTypes;
2
3 # Copyright (C) 2008 LibLime
4 #
5 # This file is part of Koha.
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 strict;
21 #use warnings; FIXME - Bug 2505
22 use C4::Context;
23
24
25
26 =head1 NAME
27
28 C4::Members::AttributeTypes - mananage extended patron attribute types
29
30 =head1 SYNOPSIS
31
32   my @attribute_types = C4::Members::AttributeTypes::GetAttributeTypes();
33
34   my $attr_type = C4::Members::AttributeTypes->new($code, $description);
35   $attr_type->code($code);
36   $attr_type->description($description);
37   $attr_type->repeatable($repeatable);
38   $attr_type->unique_id($unique_id);
39   $attr_type->opac_display($opac_display);
40   $attr_type->opac_editable($opac_editable);
41   $attr_type->staff_searchable($staff_searchable);
42   $attr_type->authorised_value_category($authorised_value_category);
43   $attr_type->store();
44   $attr_type->delete();
45
46   my $attr_type = C4::Members::AttributeTypes->fetch($code);
47   $attr_type = C4::Members::AttributeTypes->delete($code);
48
49 =head1 FUNCTIONS
50
51 =head2 GetAttributeTypes
52
53   my @attribute_types = C4::Members::AttributeTypes::GetAttributeTypes($all_fields);
54
55 Returns an array of hashrefs of each attribute type defined
56 in the database.  The array is sorted by code.  Each hashref contains
57 at least the following fields:
58
59  - code
60  - description
61
62 If $all_fields is true, then each hashref also contains the other fields from borrower_attribute_types.
63
64 =cut
65
66 sub GetAttributeTypes {
67     my $all    = @_   ? shift : 0;
68     my $no_branch_limit = @_ ? shift : 0;
69     my $branch_limit = $no_branch_limit
70         ? 0
71         : C4::Context->userenv ? C4::Context->userenv->{"branch"} : 0;
72     my $select = $all ? '*'   : 'DISTINCT(code), description, class';
73
74     my $dbh = C4::Context->dbh;
75     my $query = "SELECT $select FROM borrower_attribute_types";
76     $query .= qq{
77         LEFT JOIN borrower_attribute_types_branches ON bat_code = code
78         WHERE b_branchcode = ? OR b_branchcode IS NULL
79     } if $branch_limit;
80     $query .= " ORDER BY code";
81     my $sth    = $dbh->prepare($query);
82     $sth->execute( $branch_limit ? $branch_limit : () );
83     my $results = $sth->fetchall_arrayref({});
84     $sth->finish;
85     return @$results;
86 }
87
88 sub GetAttributeTypes_hashref {
89     my %hash = map {$_->{code} => $_} GetAttributeTypes(@_);
90     return \%hash;
91 }
92
93 =head2 AttributeTypeExists
94
95   my $have_attr_xyz = C4::Members::AttributeTypes::AttributeTypeExists($code)
96
97 Returns true if we have attribute type C<$code>
98 in the database.
99
100 =cut
101
102 sub AttributeTypeExists {
103     my ($code) = @_;
104     my $dbh = C4::Context->dbh;
105     my $exists = $dbh->selectrow_array("SELECT code FROM borrower_attribute_types WHERE code = ?", undef, $code);
106     return $exists;
107 }
108
109 =head1 METHODS 
110
111   my $attr_type = C4::Members::AttributeTypes->new($code, $description);
112
113 Create a new attribute type.
114
115 =cut 
116
117 sub new {
118     my $class = shift;
119     my $self = {};
120
121     $self->{'code'} = shift;
122     $self->{'description'} = shift;
123     $self->{'repeatable'} = 0;
124     $self->{'unique_id'} = 0;
125     $self->{'opac_display'} = 0;
126     $self->{'opac_editable'} = 0;
127     $self->{'staff_searchable'} = 0;
128     $self->{'display_checkout'} = 0;
129     $self->{'authorised_value_category'} = '';
130     $self->{'category_code'} = '';
131     $self->{'category_description'} = '';
132     $self->{'class'} = '';
133
134     bless $self, $class;
135     return $self;
136 }
137
138 =head2 fetch
139
140   my $attr_type = C4::Members::AttributeTypes->fetch($code);
141
142 Fetches an attribute type from the database.  If no
143 type with the given C<$code> exists, returns undef.
144
145 =cut
146
147 sub fetch {
148     my $class = shift;
149     my $code = shift;
150     my $self = {};
151     my $dbh = C4::Context->dbh();
152
153     my $sth = $dbh->prepare_cached("
154         SELECT borrower_attribute_types.*, categories.description AS category_description
155         FROM borrower_attribute_types
156         LEFT JOIN categories ON borrower_attribute_types.category_code=categories.categorycode
157         WHERE code =?");
158     $sth->execute($code);
159     my $row = $sth->fetchrow_hashref;
160     $sth->finish();
161     return unless defined $row;
162
163     $self->{'code'}                      = $row->{'code'};
164     $self->{'description'}               = $row->{'description'};
165     $self->{'repeatable'}                = $row->{'repeatable'};
166     $self->{'unique_id'}                 = $row->{'unique_id'};
167     $self->{'opac_display'}              = $row->{'opac_display'};
168     $self->{'opac_editable'}             = $row->{'opac_editable'};
169     $self->{'staff_searchable'}          = $row->{'staff_searchable'};
170     $self->{'display_checkout'}          = $row->{'display_checkout'};
171     $self->{'authorised_value_category'} = $row->{'authorised_value_category'};
172     $self->{'category_code'}             = $row->{'category_code'};
173     $self->{'category_description'}      = $row->{'category_description'};
174     $self->{'class'}                     = $row->{'class'};
175
176     $sth = $dbh->prepare("SELECT branchcode, branchname FROM borrower_attribute_types_branches, branches WHERE b_branchcode = branchcode AND bat_code = ?;");
177     $sth->execute( $code );
178     while ( my $data = $sth->fetchrow_hashref ) {
179         push @{ $self->{branches} }, $data;
180     }
181     $sth->finish();
182
183     bless $self, $class;
184     return $self;
185 }
186
187 =head2 store
188
189   $attr_type->store();
190
191 Stores attribute type in the database.  If the type
192 previously retrieved from the database via the fetch()
193 method, the DB representation of the type is replaced.
194
195 =cut
196
197 sub store {
198     my $self = shift;
199
200     my $dbh = C4::Context->dbh;
201     my $sth;
202     my $existing = __PACKAGE__->fetch($self->{'code'});
203     if (defined $existing) {
204         $sth = $dbh->prepare_cached("UPDATE borrower_attribute_types
205                                      SET description = ?,
206                                          repeatable = ?,
207                                          unique_id = ?,
208                                          opac_display = ?,
209                                          opac_editable = ?,
210                                          staff_searchable = ?,
211                                          authorised_value_category = ?,
212                                          display_checkout = ?,
213                                          category_code = ?,
214                                          class = ?
215                                      WHERE code = ?");
216     } else {
217         $sth = $dbh->prepare_cached("INSERT INTO borrower_attribute_types 
218                                         ( description,
219                                           repeatable,
220                                           unique_id,
221                                           opac_display,
222                                           opac_editable,
223                                           staff_searchable,
224                                           authorised_value_category,
225                                           display_checkout,
226                                           category_code,
227                                           class,
228                                           code
229                                         )
230                                         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
231     }
232
233     $sth->execute(
234         $self->{'description'},
235         $self->{'repeatable'},
236         $self->{'unique_id'},
237         $self->{'opac_display'},
238         $self->{'opac_editable'},
239         $self->{'staff_searchable'} || 0,
240         $self->{'authorised_value_category'},
241         $self->{'display_checkout'},
242         $self->{'category_code'} || undef,
243         $self->{'class'},
244         $self->{'code'}
245     );
246
247     if ( defined $$self{branches} ) {
248         $sth = $dbh->prepare("DELETE FROM borrower_attribute_types_branches WHERE bat_code = ?");
249         $sth->execute( $$self{code} );
250         $sth = $dbh->prepare(
251             "INSERT INTO borrower_attribute_types_branches
252                         ( bat_code, b_branchcode )
253                         VALUES ( ?, ? )"
254         );
255         for my $branchcode ( @{$$self{branches}} ) {
256             next if not $branchcode;
257             $sth->bind_param( 1, $$self{code} );
258             $sth->bind_param( 2, $branchcode );
259             $sth->execute;
260         }
261     }
262     $sth->finish;
263 }
264
265 =head2 code
266
267   my $code = $attr_type->code();
268   $attr_type->code($code);
269
270 Accessor.  Note that the code is immutable once
271 a type is created or fetched from the database.
272
273 =cut
274
275 sub code {
276     my $self = shift;
277     return $self->{'code'};
278 }
279
280 =head2 description
281
282   my $description = $attr_type->description();
283   $attr_type->description($description);
284
285 Accessor.
286
287 =cut
288
289 sub description {
290     my $self = shift;
291     @_ ? $self->{'description'} = shift : $self->{'description'};
292 }
293
294 =head2 branches
295
296 my $branches = $attr_type->branches();
297 $attr_type->branches($branches);
298
299 Accessor.
300
301 =cut
302
303 sub branches {
304     my $self = shift;
305     @_ ? $self->{branches} = shift : $self->{branches};
306 }
307
308 =head2 repeatable
309
310   my $repeatable = $attr_type->repeatable();
311   $attr_type->repeatable($repeatable);
312
313 Accessor.  The C<$repeatable> argument
314 is interpreted as a Perl boolean.
315
316 =cut
317
318 sub repeatable {
319     my $self = shift;
320     @_ ? $self->{'repeatable'} = ((shift) ? 1 : 0) : $self->{'repeatable'};
321 }
322
323 =head2 unique_id
324
325   my $unique_id = $attr_type->unique_id();
326   $attr_type->unique_id($unique_id);
327
328 Accessor.  The C<$unique_id> argument
329 is interpreted as a Perl boolean.
330
331 =cut
332
333 sub unique_id {
334     my $self = shift;
335     @_ ? $self->{'unique_id'} = ((shift) ? 1 : 0) : $self->{'unique_id'};
336 }
337
338 =head2 opac_display
339
340   my $opac_display = $attr_type->opac_display();
341   $attr_type->opac_display($opac_display);
342
343 Accessor.  The C<$opac_display> argument
344 is interpreted as a Perl boolean.
345
346 =cut
347
348 sub opac_display {
349     my $self = shift;
350     @_ ? $self->{'opac_display'} = ((shift) ? 1 : 0) : $self->{'opac_display'};
351 }
352
353 =head2 opac_editable
354
355   my $opac_editable = $attr_type->opac_editable();
356   $attr_type->opac_editable($opac_editable);
357
358 Accessor.  The C<$opac_editable> argument
359 is interpreted as a Perl boolean.
360
361 =cut
362
363 sub opac_editable {
364     my $self = shift;
365     @_ ? $self->{'opac_editable'} = ((shift) ? 1 : 0) : $self->{'opac_editable'};
366 }
367
368 =head2 staff_searchable
369
370   my $staff_searchable = $attr_type->staff_searchable();
371   $attr_type->staff_searchable($staff_searchable);
372
373 Accessor.  The C<$staff_searchable> argument
374 is interpreted as a Perl boolean.
375
376 =cut
377
378 sub staff_searchable {
379     my $self = shift;
380     @_ ? $self->{'staff_searchable'} = ((shift) ? 1 : 0) : $self->{'staff_searchable'};
381 }
382
383 =head2 display_checkout
384
385 my $display_checkout = $attr_type->display_checkout();
386 $attr_type->display_checkout($display_checkout);
387
388 Accessor.  The C<$display_checkout> argument
389 is interpreted as a Perl boolean.
390
391 =cut
392
393 sub display_checkout {
394     my $self = shift;
395     @_ ? $self->{'display_checkout'} = ((shift) ? 1 : 0) : $self->{'display_checkout'};
396 }
397
398 =head2 authorised_value_category
399
400   my $authorised_value_category = $attr_type->authorised_value_category();
401   $attr_type->authorised_value_category($authorised_value_category);
402
403 Accessor.
404
405 =cut
406
407 sub authorised_value_category {
408     my $self = shift;
409     @_ ? $self->{'authorised_value_category'} = shift : $self->{'authorised_value_category'};
410 }
411
412 =head2 category_code
413
414 my $category_code = $attr_type->category_code();
415 $attr_type->category_code($category_code);
416
417 Accessor.
418
419 =cut
420
421 sub category_code {
422     my $self = shift;
423     @_ ? $self->{'category_code'} = shift : $self->{'category_code'};
424 }
425
426 =head2 category_description
427
428 my $category_description = $attr_type->category_description();
429 $attr_type->category_description($category_description);
430
431 Accessor.
432
433 =cut
434
435 sub category_description {
436     my $self = shift;
437     @_ ? $self->{'category_description'} = shift : $self->{'category_description'};
438 }
439
440 =head2 class
441
442 my $class = $attr_type->class();
443 $attr_type->class($class);
444
445 Accessor.
446
447 =cut
448
449 sub class {
450     my $self = shift;
451     @_ ? $self->{'class'} = shift : $self->{'class'};
452 }
453
454
455 =head2 delete
456
457   $attr_type->delete();
458   C4::Members::AttributeTypes->delete($code);
459
460 Delete an attribute type from the database.  The attribute
461 type may be specified either by an object or by a code.
462
463 =cut
464
465 sub delete {
466     my $arg = shift;
467     my $code;
468     if (ref($arg) eq __PACKAGE__) {
469         $code = $arg->{'code'};
470     } else {
471         $code = shift;
472     }
473
474     my $dbh = C4::Context->dbh;
475     my $sth = $dbh->prepare_cached("DELETE FROM borrower_attribute_types WHERE code = ?");
476     $sth->execute($code);
477     $sth->finish;
478 }
479
480 =head2 num_patrons
481
482   my $count = $attr_type->num_patrons();
483
484 Returns the number of patron records that use
485 this attribute type.
486
487 =cut
488
489 sub num_patrons {
490     my $self = shift;
491
492     my $dbh = C4::Context->dbh;
493     my $sth = $dbh->prepare_cached("SELECT COUNT(DISTINCT borrowernumber)
494                                     FROM borrower_attributes
495                                     WHERE code = ?");
496     $sth->execute($self->{code});
497     my ($count) = $sth->fetchrow_array;
498     $sth->finish;
499     return $count;
500 }
501
502 =head2 get_patrons
503
504   my @borrowernumbers = $attr_type->get_patrons($attribute);
505
506 Returns the borrowernumber of the patron records that
507 have an attribute with the specifie value.
508
509 =cut
510
511 sub get_patrons {
512     my $self = shift;
513     my $value = shift;
514
515     my $dbh = C4::Context->dbh;
516     my $sth = $dbh->prepare_cached("SELECT DISTINCT borrowernumber
517                                     FROM borrower_attributes
518                                     WHERE code = ?
519                                     AND   attribute = ?");
520     $sth->execute($self->{code}, $value);
521     my @results;
522     while (my ($borrowernumber) = $sth->fetchrow_array) {
523         push @results, $borrowernumber;
524     } 
525     return @results;
526 }
527
528 =head1 AUTHOR
529
530 Koha Development Team <http://koha-community.org/>
531
532 Galen Charlton <galen.charlton@liblime.com>
533
534 =cut
535
536 1;