File: | C4/Members/Attributes.pm |
Coverage: | 46.4% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package C4::Members::Attributes; | |||||
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 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 | 26 26 26 | 40650 109 804 | use strict; | |||
21 | 26 26 26 | 203 131 953 | use warnings; | |||
22 | ||||||
23 | 26 26 26 | 91419 254186 976 | use Text::CSV; # Don't be tempted to use Text::CSV::Unicode -- even in binary mode it fails. | |||
24 | 26 26 26 | 2144 369 698 | use C4::Context; | |||
25 | 26 26 26 | 4819 231 1296 | use C4::Members::AttributeTypes; | |||
26 | ||||||
27 | 26 26 26 | 402 193 4104 | use vars qw($VERSION @ISA @EXPORT_OK @EXPORT %EXPORT_TAGS); | |||
28 | our ($csv, $AttributeTypes); | |||||
29 | ||||||
30 | BEGIN { | |||||
31 | # set the version for version checking | |||||
32 | 26 | 199 | $VERSION = 3.01; | |||
33 | 26 | 435 | @ISA = qw(Exporter); | |||
34 | 26 | 349 | @EXPORT_OK = qw(GetBorrowerAttributes GetBorrowerAttributeValue CheckUniqueness SetBorrowerAttributes | |||
35 | extended_attributes_code_value_arrayref extended_attributes_merge | |||||
36 | SearchIdMatchingAttribute); | |||||
37 | 26 | 32832 | %EXPORT_TAGS = ( all => \@EXPORT_OK ); | |||
38 | } | |||||
39 | ||||||
40 - 68 | =head1 NAME C4::Members::Attributes - manage extend patron attributes =head1 SYNOPSIS use C4::Members::Attributes; my $attributes = C4::Members::Attributes::GetBorrowerAttributes($borrowernumber); =head1 FUNCTIONS =head2 GetBorrowerAttributes my $attributes = C4::Members::Attributes::GetBorrowerAttributes($borrowernumber[, $opac_only]); Retrieve an arrayref of extended attributes associated with the patron specified by C<$borrowernumber>. Each entry in the arrayref is a hashref containing the following keys: code (attribute type code) description (attribute type description) value (attribute value) value_description (attribute value description (if associated with an authorised value)) password (password, if any, associated with attribute If the C<$opac_only> parameter is present and has a true value, only the attributes marked for OPAC display are returned. =cut | |||||
69 | ||||||
70 | sub GetBorrowerAttributes { | |||||
71 | 0 | 0 | my $borrowernumber = shift; | |||
72 | 0 | 0 | my $opac_only = @_ ? shift : 0; | |||
73 | ||||||
74 | 0 | 0 | my $dbh = C4::Context->dbh(); | |||
75 | 0 | 0 | my $query = "SELECT code, description, attribute, lib, password, display_checkout | |||
76 | FROM borrower_attributes | |||||
77 | JOIN borrower_attribute_types USING (code) | |||||
78 | LEFT JOIN authorised_values ON (category = authorised_value_category AND attribute = authorised_value) | |||||
79 | WHERE borrowernumber = ?"; | |||||
80 | 0 | 0 | $query .= "\nAND opac_display = 1" if $opac_only; | |||
81 | 0 | 0 | $query .= "\nORDER BY code, attribute"; | |||
82 | 0 | 0 | my $sth = $dbh->prepare_cached($query); | |||
83 | 0 | 0 | $sth->execute($borrowernumber); | |||
84 | 0 | 0 | my @results = (); | |||
85 | 0 | 0 | while (my $row = $sth->fetchrow_hashref()) { | |||
86 | 0 | 0 | push @results, { | |||
87 | code => $row->{'code'}, | |||||
88 | description => $row->{'description'}, | |||||
89 | value => $row->{'attribute'}, | |||||
90 | value_description => $row->{'lib'}, | |||||
91 | password => $row->{'password'}, | |||||
92 | display_checkout => $row->{'display_checkout'}, | |||||
93 | } | |||||
94 | } | |||||
95 | 0 | 0 | return \@results; | |||
96 | } | |||||
97 | ||||||
98 - 105 | =head2 GetBorrowerAttributeValue my $value = C4::Members::Attributes::GetBorrowerAttributeValue($borrowernumber, $attribute_code); Retrieve the value of an extended attribute C<$attribute_code> associated with the patron specified by C<$borrowernumber>. =cut | |||||
106 | ||||||
107 | sub GetBorrowerAttributeValue { | |||||
108 | 0 | 0 | my $borrowernumber = shift; | |||
109 | 0 | 0 | my $code = shift; | |||
110 | ||||||
111 | 0 | 0 | my $dbh = C4::Context->dbh(); | |||
112 | 0 | 0 | my $query = "SELECT attribute | |||
113 | FROM borrower_attributes | |||||
114 | WHERE borrowernumber = ? | |||||
115 | AND code = ?"; | |||||
116 | 0 | 0 | my $value = $dbh->selectrow_array($query, undef, $borrowernumber, $code); | |||
117 | 0 | 0 | return $value; | |||
118 | } | |||||
119 | ||||||
120 - 124 | =head2 SearchIdMatchingAttribute my $matching_borrowernumbers = C4::Members::Attributes::SearchIdMatchingAttribute($filter); =cut | |||||
125 | ||||||
126 | sub SearchIdMatchingAttribute{ | |||||
127 | 0 | 0 | my $filter = shift; | |||
128 | 0 | 0 | $filter = [$filter] unless ref $filter; | |||
129 | ||||||
130 | 0 | 0 | my $dbh = C4::Context->dbh(); | |||
131 | 0 | 0 | my $query = qq{ | |||
132 | SELECT DISTINCT borrowernumber | |||||
133 | FROM borrower_attributes | |||||
134 | JOIN borrower_attribute_types USING (code) | |||||
135 | WHERE staff_searchable = 1 | |||||
136 | AND (} . join (" OR ", map "attribute like ?", @$filter) .qq{)}; | |||||
137 | 0 | 0 | my $sth = $dbh->prepare_cached($query); | |||
138 | 0 | 0 | $sth->execute(map "%$_%", @$filter); | |||
139 | 0 0 | 0 0 | return [map $_->[0], @{ $sth->fetchall_arrayref }]; | |||
140 | } | |||||
141 | ||||||
142 - 154 | =head2 CheckUniqueness my $ok = CheckUniqueness($code, $value[, $borrowernumber]); Given an attribute type and value, verify if would violate a unique_id restriction if added to the patron. The optional C<$borrowernumber> is the patron that the attribute value would be added to, if known. Returns false if the C<$code> is not valid or the value would violate the uniqueness constraint. =cut | |||||
155 | ||||||
156 | sub CheckUniqueness { | |||||
157 | 0 | 0 | my $code = shift; | |||
158 | 0 | 0 | my $value = shift; | |||
159 | 0 | 0 | my $borrowernumber = @_ ? shift : undef; | |||
160 | ||||||
161 | 0 | 0 | my $attr_type = C4::Members::AttributeTypes->fetch($code); | |||
162 | ||||||
163 | 0 | 0 | return 0 unless defined $attr_type; | |||
164 | 0 | 0 | return 1 unless $attr_type->unique_id(); | |||
165 | ||||||
166 | 0 | 0 | my $dbh = C4::Context->dbh; | |||
167 | 0 | 0 | my $sth; | |||
168 | 0 | 0 | if (defined($borrowernumber)) { | |||
169 | 0 | 0 | $sth = $dbh->prepare("SELECT COUNT(*) | |||
170 | FROM borrower_attributes | |||||
171 | WHERE code = ? | |||||
172 | AND attribute = ? | |||||
173 | AND borrowernumber <> ?"); | |||||
174 | 0 | 0 | $sth->execute($code, $value, $borrowernumber); | |||
175 | } else { | |||||
176 | 0 | 0 | $sth = $dbh->prepare("SELECT COUNT(*) | |||
177 | FROM borrower_attributes | |||||
178 | WHERE code = ? | |||||
179 | AND attribute = ?"); | |||||
180 | 0 | 0 | $sth->execute($code, $value); | |||
181 | } | |||||
182 | 0 | 0 | my ($count) = $sth->fetchrow_array; | |||
183 | 0 | 0 | return ($count == 0); | |||
184 | } | |||||
185 | ||||||
186 - 193 | =head2 SetBorrowerAttributes SetBorrowerAttributes($borrowernumber, [ { code => 'CODE', value => 'value', password => 'password' }, ... ] ); Set patron attributes for the patron identified by C<$borrowernumber>, replacing any that existed previously. =cut | |||||
194 | ||||||
195 | sub SetBorrowerAttributes { | |||||
196 | 0 | 0 | my $borrowernumber = shift; | |||
197 | 0 | 0 | my $attr_list = shift; | |||
198 | ||||||
199 | 0 | 0 | my $dbh = C4::Context->dbh; | |||
200 | 0 | 0 | my $delsth = $dbh->prepare("DELETE FROM borrower_attributes WHERE borrowernumber = ?"); | |||
201 | 0 | 0 | $delsth->execute($borrowernumber); | |||
202 | ||||||
203 | 0 | 0 | my $sth = $dbh->prepare("INSERT INTO borrower_attributes (borrowernumber, code, attribute, password) | |||
204 | VALUES (?, ?, ?, ?)"); | |||||
205 | 0 | 0 | foreach my $attr (@$attr_list) { | |||
206 | 0 | 0 | $attr->{password} = undef unless exists $attr->{password}; | |||
207 | 0 | 0 | $sth->execute($borrowernumber, $attr->{code}, $attr->{value}, $attr->{password}); | |||
208 | 0 | 0 | if ($sth->err) { | |||
209 | 0 | 0 | warn sprintf('Database returned the following error: %s', $sth->errstr); | |||
210 | 0 | 0 | return; # bail immediately on errors | |||
211 | } | |||||
212 | } | |||||
213 | 0 | 0 | return 1; # borower attributes successfully set | |||
214 | } | |||||
215 | ||||||
216 - 227 | =head2 extended_attributes_code_value_arrayref my $patron_attributes = "homeroom:1150605,grade:01,extradata:foobar"; my $aref = extended_attributes_code_value_arrayref($patron_attributes); Takes a comma-delimited CSV-style string argument and returns the kind of data structure that SetBorrowerAttributes wants, namely a reference to array of hashrefs like: [ { code => 'CODE', value => 'value' }, { code => 'CODE2', value => 'othervalue' } ... ] Caches Text::CSV parser object for efficiency. =cut | |||||
228 | ||||||
229 | sub extended_attributes_code_value_arrayref { | |||||
230 | 8 | 745904 | my $string = shift or return; | |||
231 | 8 | 39 | $csv or $csv = Text::CSV->new({binary => 1}); # binary needed for non-ASCII Unicode | |||
232 | 8 | 296 | my $ok = $csv->parse($string); # parse field again to get subfields! | |||
233 | 8 | 436 | my @list = $csv->fields(); | |||
234 | # TODO: error handling (check $ok) | |||||
235 | return [ | |||||
236 | 24 22 | 46 52 | sort {&_sort_by_code($a,$b)} | |||
237 | 8 22 22 | 93 25 118 | map { map { my @arr = split /:/, $_, 2; { code => $arr[0], value => $arr[1] } } $_ } | |||
238 | @list | |||||
239 | ]; | |||||
240 | # nested map because of split | |||||
241 | } | |||||
242 | ||||||
243 - 261 | =head2 extended_attributes_merge my $old_attributes = extended_attributes_code_value_arrayref("homeroom:224,grade:04,deanslist:2007,deanslist:2008,somedata:xxx"); my $new_attributes = extended_attributes_code_value_arrayref("homeroom:115,grade:05,deanslist:2009,extradata:foobar"); my $merged = extended_attributes_merge($patron_attributes, $new_attributes, 1); # assuming deanslist is a repeatable code, value same as: # $merged = extended_attributes_code_value_arrayref("homeroom:115,grade:05,deanslist:2007,deanslist:2008,deanslist:2009,extradata:foobar,somedata:xxx"); Takes three arguments. The first two are references to array of hashrefs, each like: [ { code => 'CODE', value => 'value' }, { code => 'CODE2', value => 'othervalue' } ... ] The third option specifies whether repeatable codes are clobbered or collected. True for non-clobber. Returns one reference to (merged) array of hashref. Caches results of C4::Members::AttributeTypes::GetAttributeTypes_hashref(1) for efficiency. =cut | |||||
262 | ||||||
263 | sub extended_attributes_merge { | |||||
264 | 8 | 46 | my $old = shift or return; | |||
265 | 8 | 29 | my $new = shift or return $old; | |||
266 | 8 | 24 | my $keep = @_ ? shift : 0; | |||
267 | 8 | 28 | $AttributeTypes or $AttributeTypes = C4::Members::AttributeTypes::GetAttributeTypes_hashref(1); | |||
268 | 8 | 23 | my @merged = @$old; | |||
269 | 8 | 19 | foreach my $att (@$new) { | |||
270 | 20 | 60 | unless ($att->{code}) { | |||
271 | 0 | 0 | warn "Cannot merge element: no 'code' defined"; | |||
272 | 0 | 0 | next; | |||
273 | } | |||||
274 | 20 | 68 | unless ($AttributeTypes->{$att->{code}}) { | |||
275 | 0 | 0 | warn "Cannot merge element: unrecognized code = '$att->{code}'"; | |||
276 | 0 | 0 | next; | |||
277 | } | |||||
278 | 20 | 102 | unless ($AttributeTypes->{$att->{code}}->{repeatable} and $keep) { | |||
279 | 18 82 | 51 295 | @merged = grep {$att->{code} ne $_->{code}} @merged; # filter out any existing attributes of the same code | |||
280 | } | |||||
281 | 20 | 79 | push @merged, $att; | |||
282 | } | |||||
283 | 8 48 | 48 104 | return [( sort {&_sort_by_code($a,$b)} @merged )]; | |||
284 | } | |||||
285 | ||||||
286 | sub _sort_by_code { | |||||
287 | 72 | 125 | my ($x, $y) = @_; | |||
288 | 72 | 166 | defined ($x->{code}) or return -1; | |||
289 | 72 | 157 | defined ($y->{code}) or return 1; | |||
290 | 72 | 359 | return $x->{code} cmp $y->{code} || $x->{value} cmp $y->{value}; | |||
291 | } | |||||
292 | ||||||
293 - 299 | =head1 AUTHOR Koha Development Team <http://koha-community.org/> Galen Charlton <galen.charlton@liblime.com> =cut | |||||
300 | ||||||
301 | 1; |