[Koha-patches] [PATCH] new C4 modules for patron attributes
Galen Charlton
galen.charlton at liblime.com
Sat May 10 01:08:15 CEST 2008
Two new modules to support patron attributes:
- C4::Members::AttributeTypes
OO-module for managing patron attribute types.
- C4::Members::Attributes
Procedural module for retrieving and setting
extended attributes belonging to a patron.
---
C4/Members/AttributeTypes.pm | 442 ++++++++++++++++++++++++++++++
C4/Members/Attributes.pm | 181 ++++++++++++
t/lib/KohaTest/Members/AttributeTypes.pm | 120 ++++++++
3 files changed, 743 insertions(+), 0 deletions(-)
create mode 100644 C4/Members/AttributeTypes.pm
create mode 100644 C4/Members/Attributes.pm
create mode 100644 t/lib/KohaTest/Members/AttributeTypes.pm
diff --git a/C4/Members/AttributeTypes.pm b/C4/Members/AttributeTypes.pm
new file mode 100644
index 0000000..4eb6990
--- /dev/null
+++ b/C4/Members/AttributeTypes.pm
@@ -0,0 +1,442 @@
+package C4::Members::AttributeTypes;
+
+# Copyright (C) 2008 LibLime
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+
+use strict;
+use C4::Context;
+
+use vars qw($VERSION);
+
+BEGIN {
+ # set the version for version checking
+ $VERSION = 3.00;
+}
+
+=head1 NAME
+
+C4::Members::AttributeTypes - mananage extended patron attribute types
+
+=head1 SYNOPSIS
+
+=over 4
+
+my @attribute_types = C4::Members::AttributeTypes::GetAttributeTypes();
+
+my $attr_type = C4::Members::AttributeTypes->new($code, $description);
+$attr_type->code($code);
+$attr_type->description($description);
+$attr_type->repeatable($repeatable);
+$attr_type->unique_id($unique_id);
+$attr_type->opac_display($opac_display);
+$attr_type->password_allowed($password_allowed);
+$attr_type->staff_searchable($staff_searchable);
+$attr_type->authorised_value_category($authorised_value_category);
+$attr_type->store();
+$attr_type->delete();
+
+my $attr_type = C4::Members::AttributeTypes->fetch($code);
+$attr_type = C4::Members::AttributeTypes->delete($code);
+
+=back
+
+=head1 FUNCTIONS
+
+=head2 GetAttributeTypes
+
+=over 4
+
+my @attribute_types = C4::Members::AttributeTypes::GetAttributeTypes();
+
+=back
+
+Returns an array of hashrefs of each attribute type defined
+in the database. The array is sorted by code. Each hashref contains
+the following fields:
+
+code
+description
+
+=cut
+
+sub GetAttributeTypes {
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare('SELECT code, description FROM borrower_attribute_types ORDER by code');
+ $sth->execute();
+ my @results = ();
+ while (my $row = $sth->fetchrow_hashref) {
+ push @results, $row;
+ }
+ return @results;
+}
+
+=head1 METHODS
+
+=over 4
+
+my $attr_type = C4::Members::AttributeTypes->new($code, $description);
+
+=back
+
+Create a new attribute type.
+
+=cut
+
+sub new {
+ my $class = shift;
+ my $self = {};
+
+ $self->{'code'} = shift;
+ $self->{'description'} = shift;
+ $self->{'repeatable'} = 0;
+ $self->{'unique_id'} = 0;
+ $self->{'opac_display'} = 0;
+ $self->{'password_allowed'} = 0;
+ $self->{'staff_searchable'} = 0;
+ $self->{'authorised_value_category'} = '';
+
+ bless $self, $class;
+ return $self;
+}
+
+=head2 fetch
+
+=over 4
+
+my $attr_type = C4::Members::AttributeTypes->fetch($code);
+
+=back
+
+Fetches an attribute type from the database. If no
+type with the given C<$code> exists, returns undef.
+
+=cut
+
+sub fetch {
+ my $class = shift;
+ my $code = shift;
+ my $self = {};
+ my $dbh = C4::Context->dbh();
+
+ my $sth = $dbh->prepare_cached("SELECT * FROM borrower_attribute_types WHERE code = ?");
+ $sth->execute($code);
+ my $row = $sth->fetchrow_hashref;
+ $sth->finish();
+ return undef unless defined $row;
+
+ $self->{'code'} = $row->{'code'};
+ $self->{'description'} = $row->{'description'};
+ $self->{'repeatable'} = $row->{'repeatable'};
+ $self->{'unique_id'} = $row->{'unique_id'};
+ $self->{'opac_display'} = $row->{'opac_display'};
+ $self->{'password_allowed'} = $row->{'password_allowed'};
+ $self->{'staff_searchable'} = $row->{'staff_searchable'};
+ $self->{'authorised_value_category'} = $row->{'authorised_value_category'};
+
+ bless $self, $class;
+ return $self;
+}
+
+=head2 store
+
+=over 4
+
+$attr_type->store();
+
+=back
+
+Stores attribute type in the database. If the type
+previously retrieved from the database via the fetch()
+method, the DB representation of the type is replaced.
+
+=cut
+
+sub store {
+ my $self = shift;
+
+ my $dbh = C4::Context->dbh;
+ my $sth;
+ my $existing = __PACKAGE__->fetch($self->{'code'});
+ if (defined $existing) {
+ $sth = $dbh->prepare_cached("UPDATE borrower_attribute_types
+ SET description = ?,
+ repeatable = ?,
+ unique_id = ?,
+ opac_display = ?,
+ password_allowed = ?,
+ staff_searchable = ?,
+ authorised_value_category = ?
+ WHERE code = ?");
+ } else {
+ $sth = $dbh->prepare_cached("INSERT INTO borrower_attribute_types
+ (description, repeatable, unique_id, opac_display, password_allowed,
+ staff_searchable, authorised_value_category, code)
+ VALUES (?, ?, ?, ?, ?,
+ ?, ?, ?)");
+ }
+ $sth->bind_param(1, $self->{'description'});
+ $sth->bind_param(2, $self->{'repeatable'});
+ $sth->bind_param(3, $self->{'unique_id'});
+ $sth->bind_param(4, $self->{'opac_display'});
+ $sth->bind_param(5, $self->{'password_allowed'});
+ $sth->bind_param(6, $self->{'staff_searchable'});
+ $sth->bind_param(7, $self->{'authorised_value_category'});
+ $sth->bind_param(8, $self->{'code'});
+ $sth->execute;
+
+}
+
+=head2 code
+
+=over 4
+
+my $code = $attr_type->code();
+$attr_type->code($code);
+
+=back
+
+Accessor. Note that the code is immutable once
+a type is created or fetched from the database.
+
+=cut
+
+sub code {
+ my $self = shift;
+ return $self->{'code'};
+}
+
+=head2 description
+
+=over 4
+
+my $description = $attr_type->description();
+$attr_type->description($description);
+
+=back
+
+Accessor.
+
+=cut
+
+sub description {
+ my $self = shift;
+ @_ ? $self->{'description'} = shift : $self->{'description'};
+}
+
+=head2 repeatable
+
+=over 4
+
+my $repeatable = $attr_type->repeatable();
+$attr_type->repeatable($repeatable);
+
+=back
+
+Accessor. The C<$repeatable> argument
+is interpreted as a Perl boolean.
+
+=cut
+
+sub repeatable {
+ my $self = shift;
+ @_ ? $self->{'repeatable'} = ((shift) ? 1 : 0) : $self->{'repeatable'};
+}
+
+=head2 unique_id
+
+=over 4
+
+my $unique_id = $attr_type->unique_id();
+$attr_type->unique_id($unique_id);
+
+=back
+
+Accessor. The C<$unique_id> argument
+is interpreted as a Perl boolean.
+
+=cut
+
+sub unique_id {
+ my $self = shift;
+ @_ ? $self->{'unique_id'} = ((shift) ? 1 : 0) : $self->{'unique_id'};
+}
+=head2 opac_display
+
+=over 4
+
+my $opac_display = $attr_type->opac_display();
+$attr_type->opac_display($opac_display);
+
+=back
+
+Accessor. The C<$opac_display> argument
+is interpreted as a Perl boolean.
+
+=cut
+
+sub opac_display {
+ my $self = shift;
+ @_ ? $self->{'opac_display'} = ((shift) ? 1 : 0) : $self->{'opac_display'};
+}
+=head2 password_allowed
+
+=over 4
+
+my $password_allowed = $attr_type->password_allowed();
+$attr_type->password_allowed($password_allowed);
+
+=back
+
+Accessor. The C<$password_allowed> argument
+is interpreted as a Perl boolean.
+
+=cut
+
+sub password_allowed {
+ my $self = shift;
+ @_ ? $self->{'password_allowed'} = ((shift) ? 1 : 0) : $self->{'password_allowed'};
+}
+=head2 staff_searchable
+
+=over 4
+
+my $staff_searchable = $attr_type->staff_searchable();
+$attr_type->staff_searchable($staff_searchable);
+
+=back
+
+Accessor. The C<$staff_searchable> argument
+is interpreted as a Perl boolean.
+
+=cut
+
+sub staff_searchable {
+ my $self = shift;
+ @_ ? $self->{'staff_searchable'} = ((shift) ? 1 : 0) : $self->{'staff_searchable'};
+}
+
+=head2 authorised_value_category
+
+=over 4
+
+my $authorised_value_category = $attr_type->authorised_value_category();
+$attr_type->authorised_value_category($authorised_value_category);
+
+=back
+
+Accessor.
+
+=cut
+
+sub authorised_value_category {
+ my $self = shift;
+ @_ ? $self->{'authorised_value_category'} = shift : $self->{'authorised_value_category'};
+}
+
+=head2 delete
+
+=over 4
+
+$attr_type->delete();
+C4::Members::AttributeTypes->delete($code);
+
+=back
+
+Delete an attribute type from the database. The attribute
+type may be specified either by an object or by a code.
+
+=cut
+
+sub delete {
+ my $arg = shift;
+ my $code;
+ if (ref($arg) eq __PACKAGE__) {
+ $code = $arg->{'code'};
+ } else {
+ $code = shift;
+ }
+
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare_cached("DELETE FROM borrower_attribute_types WHERE code = ?");
+ $sth->execute($code);
+}
+
+=head2 num_patrons
+
+=over 4
+
+my $count = $attr_type->num_patrons();
+
+=back
+
+Returns the number of patron records that use
+this attribute type.
+
+=cut
+
+sub num_patrons {
+ my $self = shift;
+
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare_cached("SELECT COUNT(DISTINCT borrowernumber)
+ FROM borrower_attributes
+ WHERE code = ?");
+ $sth->execute($self->{code});
+ my ($count) = $sth->fetchrow_array;
+ $sth->finish;
+ return $count;
+}
+
+=head2 get_patrons
+
+=over 4
+
+my @borrowernumbers = $attr_type->get_patrons($attribute);
+
+=back
+
+Returns the borrowernumber of the patron records that
+have an attribute with the specifie value.
+
+=cut
+
+sub get_patrons {
+ my $self = shift;
+ my $value = shift;
+
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare_cached("SELECT DISTINCT borrowernumber
+ FROM borrower_attributes
+ WHERE code = ?
+ AND attribute = ?");
+ $sth->execute($self->{code}, $value);
+ my @results;
+ while (my ($borrowernumber) = $sth->fetchrow_array) {
+ push @results, $borrowernumber;
+ }
+ return @results;
+}
+
+=head1 AUTHOR
+
+Koha Development Team <info at koha.org>
+
+Galen Charlton <galen.charlton at liblime.com>
+
+=cut
+
+1;
diff --git a/C4/Members/Attributes.pm b/C4/Members/Attributes.pm
new file mode 100644
index 0000000..e012320
--- /dev/null
+++ b/C4/Members/Attributes.pm
@@ -0,0 +1,181 @@
+package C4::Members::Attributes;
+
+# Copyright (C) 2008 LibLime
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+
+use strict;
+use C4::Context;
+use C4::Members::AttributeTypes;
+
+use vars qw($VERSION);
+
+BEGIN {
+ # set the version for version checking
+ $VERSION = 3.00;
+}
+
+=head1 NAME
+
+C4::Members::Attribute - manage extend patron attributes
+
+=head1 SYNOPSIS
+
+=over 4
+
+my $attributes = C4::Members::Attributes::GetBorrowerAttributes($borrowernumber);
+
+=back
+
+=head1 FUNCTIONS
+
+=head2 GetBorrowerAttributes
+
+=over 4
+
+my $attributes = C4::Members::Attributes::GetBorrowerAttributes($borrowernumber[, $opac_only]);
+
+=back
+
+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
+
+sub GetBorrowerAttributes {
+ my $borrowernumber = shift;
+ my $opac_only = @_ ? shift : 0;
+
+ my $dbh = C4::Context->dbh();
+ my $query = "SELECT code, description, attribute, lib, password
+ FROM borrower_attributes
+ JOIN borrower_attribute_types USING (code)
+ LEFT JOIN authorised_values ON (category = authorised_value_category AND attribute = authorised_value)
+ WHERE borrowernumber = ?";
+ $query .= "\nAND opac_display = 1" if $opac_only;
+ $query .= "\nORDER BY code, attribute";
+ my $sth = $dbh->prepare_cached($query);
+ $sth->execute($borrowernumber);
+ my @results = ();
+ while (my $row = $sth->fetchrow_hashref()) {
+ push @results, {
+ code => $row->{'code'},
+ description => $row->{'description'},
+ value => $row->{'attribute'},
+ value_description => $row->{'lib'},
+ password => $row->{'password'},
+ }
+ }
+ return \@results;
+}
+
+=head2 CheckUniqueness
+
+=over 4
+
+my $ok = CheckUniqueness($code, $value[, $borrowernumber]);
+
+=back
+
+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
+
+sub CheckUniqueness {
+ my $code = shift;
+ my $value = shift;
+ my $borrowernumber = @_ ? shift : undef;
+
+ my $attr_type = C4::Members::AttributeTypes->fetch($code);
+
+ return 0 unless defined $attr_type;
+ return 1 unless $attr_type->unique_id();
+
+ my $dbh = C4::Context->dbh;
+ my $sth;
+ if (defined($borrowernumber)) {
+ $sth = $dbh->prepare("SELECT COUNT(*)
+ FROM borrower_attributes
+ WHERE code = ?
+ AND attribute = ?
+ AND borrowernumber <> ?");
+ $sth->execute($code, $value, $borrowernumber);
+ } else {
+ $sth = $dbh->prepare("SELECT COUNT(*)
+ FROM borrower_attributes
+ WHERE code = ?
+ AND attribute = ?");
+ $sth->execute($code, $value);
+ }
+ my ($count) = $sth->fetchrow_array;
+ $sth->finish();
+ return ($count == 0);
+}
+
+=head2 SetBorrowerAttributes
+
+=over 4
+
+SetBorrowerAttributes($borrowernumber, [ { code => 'CODE', value => 'value', password => 'password' }, ... ] );
+
+=back
+
+Set patron attributes for the patron identified by C<$borrowernumber>,
+replacing any that existed previously.
+
+=cut
+
+sub SetBorrowerAttributes {
+ my $borrowernumber = shift;
+ my $attr_list = shift;
+
+ my $dbh = C4::Context->dbh;
+ my $delsth = $dbh->prepare("DELETE FROM borrower_attributes WHERE borrowernumber = ?");
+ $delsth->execute($borrowernumber);
+
+ my $sth = $dbh->prepare("INSERT INTO borrower_attributes (borrowernumber, code, attribute, password)
+ VALUES (?, ?, ?, ?)");
+ foreach my $attr (@$attr_list) {
+ $attr->{password} = undef unless exists $attr->{password};
+ $sth->execute($borrowernumber, $attr->{code}, $attr->{value}, $attr->{password});
+ }
+}
+
+=head1 AUTHOR
+
+Koha Development Team <info at koha.org>
+
+Galen Charlton <galen.charlton at liblime.com>
+
+=cut
+
+1;
diff --git a/t/lib/KohaTest/Members/AttributeTypes.pm b/t/lib/KohaTest/Members/AttributeTypes.pm
new file mode 100644
index 0000000..c306e3e
--- /dev/null
+++ b/t/lib/KohaTest/Members/AttributeTypes.pm
@@ -0,0 +1,120 @@
+package KohaTest::Members::AttributeTypes;
+#use base qw( KohaTest );
+use base qw( Test::Class );
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use C4::Members::AttributeTypes;
+sub testing_class { 'C4::Members::AttributeTypes' };
+
+sub methods : Test( 1 ) {
+ my $self = shift;
+ my @methods = qw(
+ new
+ fetch
+ GetAttributeTypes
+ code
+ description
+ repeatable
+ unique_id
+ opac_display
+ password_allowed
+ staff_searchable
+ authorised_value_category
+ store
+ delete
+ );
+
+ can_ok( $self->testing_class, @methods );
+}
+
+sub startup_50_create_types : Test( startup => 28 ) {
+ my $self = shift;
+
+ my $type1 = C4::Members::AttributeTypes->new('CAMPUSID', 'institution ID');
+ isa_ok($type1, 'C4::Members::AttributeTypes');
+ is($type1->code(), 'CAMPUSID', "set code in constructor");
+ is($type1->description(), 'institution ID', "set description in constructor");
+ ok(!$type1->repeatable(), "repeatable defaults to false");
+ ok(!$type1->unique_id(), "unique_id defaults to false");
+ ok(!$type1->opac_display(), "opac_display defaults to false");
+ ok(!$type1->password_allowed(), "password_allowed defaults to false");
+ ok(!$type1->staff_searchable(), "staff_searchable defaults to false");
+ is($type1->authorised_value_category(), '', "authorised_value_category defaults to ''");
+
+ $type1->repeatable('foobar');
+ ok($type1->repeatable(), "repeatable now true");
+ cmp_ok($type1->repeatable(), '==', 1, "repeatable not set to 'foobar'");
+ $type1->repeatable(0);
+ ok(!$type1->repeatable(), "repeatable now false");
+
+ $type1->unique_id('foobar');
+ ok($type1->unique_id(), "unique_id now true");
+ cmp_ok($type1->unique_id(), '==', 1, "unique_id not set to 'foobar'");
+ $type1->unique_id(0);
+ ok(!$type1->unique_id(), "unique_id now false");
+
+ $type1->opac_display('foobar');
+ ok($type1->opac_display(), "opac_display now true");
+ cmp_ok($type1->opac_display(), '==', 1, "opac_display not set to 'foobar'");
+ $type1->opac_display(0);
+ ok(!$type1->opac_display(), "opac_display now false");
+
+ $type1->password_allowed('foobar');
+ ok($type1->password_allowed(), "password_allowed now true");
+ cmp_ok($type1->password_allowed(), '==', 1, "password_allowed not set to 'foobar'");
+ $type1->password_allowed(0);
+ ok(!$type1->password_allowed(), "password_allowed now false");
+
+ $type1->staff_searchable('foobar');
+ ok($type1->staff_searchable(), "staff_searchable now true");
+ cmp_ok($type1->staff_searchable(), '==', 1, "staff_searchable not set to 'foobar'");
+ $type1->staff_searchable(0);
+ ok(!$type1->staff_searchable(), "staff_searchable now false");
+
+ $type1->code('INSTID');
+ is($type1->code(), 'CAMPUSID', 'code() allows retrieving but not setting');
+ $type1->description('student ID');
+ is($type1->description(), 'student ID', 'set description');
+ $type1->authorised_value_category('CAT');
+ is($type1->authorised_value_category(), 'CAT', 'set authorised_value_category');
+
+ $type1->repeatable(1);
+ $type1->staff_searchable(1);
+ $type1->store();
+ is($type1->num_patrons(), 0, 'no patrons using the new attribute type yet');
+
+ my $type2 = C4::Members::AttributeTypes->new('ABC', 'ABC ID');
+ $type2->store();
+}
+
+sub shutdown_50_list_and_remove_types : Test( shutdown => 11 ) {
+ my $self = shift;
+
+ my @list = C4::Members::AttributeTypes::GetAttributeTypes();
+ is_deeply(\@list, [ { code => 'ABC', description => 'ABC ID' },
+ { code => 'CAMPUSID', description => 'student ID' } ], "retrieved list of types");
+
+ my $type1 = C4::Members::AttributeTypes->fetch($list[1]->{code});
+ isa_ok($type1, 'C4::Members::AttributeTypes');
+ is($type1->code(), 'CAMPUSID', 'fetched code');
+ is($type1->description(), 'student ID', 'fetched description');
+ is($type1->authorised_value_category(), 'CAT', 'fetched authorised_value_category');
+ ok($type1->repeatable(), "fetched repeatable");
+ ok(!$type1->unique_id(), "fetched unique_id");
+ ok(!$type1->opac_display(), "fetched opac_display");
+ ok(!$type1->password_allowed(), "fetched password_allowed");
+ ok($type1->staff_searchable(), "fetched staff_searchable");
+
+ $type1->delete();
+ C4::Members::AttributeTypes->delete('ABC');
+
+ my @newlist = C4::Members::AttributeTypes::GetAttributeTypes();
+ is(scalar(@newlist), 0, "no types left after deletion");
+
+}
+
+1;
--
1.5.5.rc0.16.g02b00
More information about the Koha-patches
mailing list