[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