From ae7d5fd11b2ed75e2fae120ec0499e3abc966c78 Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Fri, 17 Oct 2014 17:23:21 +0300 Subject: [PATCH] Bug 13106 - Encapsulate Circulation::GetAgeRestriction() and modify it to check borrowers age as well. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch moves the logic of deciding whether or not a borrower is old enough to access this material to its own function GetAgeRestriction. This makes it easier to use AgeRestriction elsewhere, like with placing holds. This feature adds a new function C4::Members::SetAge() to make testing ages a lot easier. A ton of Unit tests included. C4::Circulate::CanBookBeIssued() fixed and issue with undefined $daysToAgeRestriction per Marc VĂ©ron's suggestion. Test plan: (See comment #10 for screenshots about using age restriction) 1) Without patch Configure Age Restricition (see Syspref AgeRestrictionMarker) and have a biblio record with e.g. PEGI 99 in age restriction field Try to check out to a patron with age < 99 Check out should be blocked Change entry in age restriction field to PEGI99 Check out schould now be blocked 2) With patch Try checkouts again, behaviour should be th same. Signed-off-by: Marc Veron Signed-off-by: Kyle M Hall Signed-off-by: Tomas Cohen Arazi --- C4/Circulation.pm | 68 +++++++++++++++++----------- C4/Members.pm | 43 ++++++++++++++++++ t/Circulation/AgeRestrictionMarkers.t | 21 ++++++++- t/db_dependent/Members.t | 84 ++++++++++++++++++++++++++++++++--- 4 files changed, 185 insertions(+), 31 deletions(-) diff --git a/C4/Circulation.pm b/C4/Circulation.pm index fb5b82f90f..bdec2f6dfc 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -998,29 +998,14 @@ sub CanBookBeIssued { } ## CHECK AGE RESTRICTION - # get $marker from preferences. Could be something like "FSK|PEGI|Alter|Age:" - my $markers = C4::Context->preference('AgeRestrictionMarker'); - my $bibvalues = $biblioitem->{'agerestriction'}; - my $restriction_age = GetAgeRestriction( $bibvalues ); - - if ( $restriction_age > 0 ) { - if ( $borrower->{'dateofbirth'} ) { - my @alloweddate = split /-/, $borrower->{'dateofbirth'}; - $alloweddate[0] += $restriction_age; - - #Prevent runime eror on leap year (invalid date) - if ( ( $alloweddate[1] == 2 ) && ( $alloweddate[2] == 29 ) ) { - $alloweddate[2] = 28; - } - - if ( Date_to_Days(Today) < Date_to_Days(@alloweddate) - 1 ) { - if ( C4::Context->preference('AgeRestrictionOverride') ) { - $needsconfirmation{AGE_RESTRICTION} = "$bibvalues"; - } - else { - $issuingimpossible{AGE_RESTRICTION} = "$bibvalues"; - } - } + my $agerestriction = $biblioitem->{'agerestriction'}; + my ($restriction_age, $daysToAgeRestriction) = GetAgeRestriction( $agerestriction, $borrower ); + if ( $daysToAgeRestriction && $daysToAgeRestriction > 0 ) { + if ( C4::Context->preference('AgeRestrictionOverride') ) { + $needsconfirmation{AGE_RESTRICTION} = "$agerestriction"; + } + else { + $issuingimpossible{AGE_RESTRICTION} = "$agerestriction"; } } @@ -3761,8 +3746,23 @@ sub IsItemIssued { return $sth->fetchrow; } +=head2 GetAgeRestriction + + my ($ageRestriction, $daysToAgeRestriction) = GetAgeRestriction($record_restrictions, $borrower); + my ($ageRestriction, $daysToAgeRestriction) = GetAgeRestriction($record_restrictions); + + if($daysToAgeRestriction <= 0) { #Borrower is allowed to access this material, as he is older or as old as the agerestriction } + if($daysToAgeRestriction > 0) { #Borrower is this many days from meeting the agerestriction } + +@PARAM1 the koha.biblioitems.agerestriction value, like K18, PEGI 13, ... +@PARAM2 a borrower-object with koha.borrowers.dateofbirth. (OPTIONAL) +@RETURNS The age restriction age in years and the days to fulfill the age restriction for the given borrower. + Negative days mean the borrower has gone past the age restriction age. + +=cut + sub GetAgeRestriction { - my ($record_restrictions) = @_; + my ($record_restrictions, $borrower) = @_; my $markers = C4::Context->preference('AgeRestrictionMarker'); # Split $record_restrictions to something like FSK 16 or PEGI 6 @@ -3796,7 +3796,25 @@ sub GetAgeRestriction { last if ( $restriction_year > 0 ); } - return $restriction_year; + #Check if the borrower is age restricted for this material and for how long. + if ($restriction_year && $borrower) { + if ( $borrower->{'dateofbirth'} ) { + my @alloweddate = split /-/, $borrower->{'dateofbirth'}; + $alloweddate[0] += $restriction_year; + + #Prevent runime eror on leap year (invalid date) + if ( ( $alloweddate[1] == 2 ) && ( $alloweddate[2] == 29 ) ) { + $alloweddate[2] = 28; + } + + #Get how many days the borrower has to reach the age restriction + my $daysToAgeRestriction = Date_to_Days(@alloweddate) - Date_to_Days(Today); + #Negative days means the borrower went past the age restriction age + return ($restriction_year, $daysToAgeRestriction); + } + } + + return ($restriction_year); } 1; diff --git a/C4/Members.pm b/C4/Members.pm index 09780781f4..7002a2bbc4 100644 --- a/C4/Members.pm +++ b/C4/Members.pm @@ -1749,6 +1749,49 @@ sub GetAge{ return $age; } # sub get_age +=head2 SetAge + + $borrower = C4::Members::SetAge($borrower, $datetimeduration); + $borrower = C4::Members::SetAge($borrower, '0015-12-10'); + $borrower = C4::Members::SetAge($borrower, $datetimeduration, $datetime_reference); + + eval { $borrower = C4::Members::SetAge($borrower, '015-1-10'); }; + if ($@) {print $@;} #Catch a bad ISO Date or kill your script! + +This function sets the borrower's dateofbirth to match the given age. +Optionally relative to the given $datetime_reference. + +@PARAM1 koha.borrowers-object +@PARAM2 DateTime::Duration-object as the desired age + OR a ISO 8601 Date. (To make the API more pleasant) +@PARAM3 DateTime-object as the relative date, defaults to now(). +RETURNS The given borrower reference @PARAM1. +DIES If there was an error with the ISO Date handling. + +=cut + +#' +sub SetAge{ + my ( $borrower, $datetimeduration, $datetime_ref ) = @_; + $datetime_ref = DateTime->now() unless $datetime_ref; + + if ($datetimeduration && ref $datetimeduration ne 'DateTime::Duration') { + if ($datetimeduration =~ /^(\d{4})-(\d{2})-(\d{2})/) { + $datetimeduration = DateTime::Duration->new(years => $1, months => $2, days => $3); + } + else { + die "C4::Members::SetAge($borrower, $datetimeduration), datetimeduration not a valid ISO 8601 Date!\n"; + } + } + + my $new_datetime_ref = $datetime_ref->clone(); + $new_datetime_ref->subtract_duration( $datetimeduration ); + + $borrower->{dateofbirth} = $new_datetime_ref->ymd(); + + return $borrower; +} # sub SetAge + =head2 GetCities $cityarrayref = GetCities(); diff --git a/t/Circulation/AgeRestrictionMarkers.t b/t/Circulation/AgeRestrictionMarkers.t index 619e4eaa41..750bf9b308 100644 --- a/t/Circulation/AgeRestrictionMarkers.t +++ b/t/Circulation/AgeRestrictionMarkers.t @@ -1,5 +1,8 @@ +#!/usr/bin/perl + use Modern::Perl; -use Test::More tests => 5; +use DateTime; +use Test::More tests => 10; use t::lib::Mocks; @@ -13,3 +16,19 @@ is ( C4::Circulation::GetAgeRestriction('PEGI16'), '16', 'PEGI16 returns 16' ); is ( C4::Circulation::GetAgeRestriction('Age 16'), '16', 'Age 16 returns 16' ); is ( C4::Circulation::GetAgeRestriction('K16'), '16', 'K16 returns 16' ); + +##Testing age restriction for a borrower. +my $now = DateTime->now(); +my $borrower = {}; +C4::Members::SetAge( $borrower, '0015-00-00' ); + +my ($restriction_age, $daysToAgeRestriction) = C4::Circulation::GetAgeRestriction('FSK 16', $borrower); +is ( ($daysToAgeRestriction > 0), 1, 'FSK 16 blocked for a 15 year old' ); +($restriction_age, $daysToAgeRestriction) = C4::Circulation::GetAgeRestriction('PEGI 15', $borrower); +is ( ($daysToAgeRestriction <= 0), 1, 'PEGI 15 allowed for a 15 year old' ); +($restriction_age, $daysToAgeRestriction) = C4::Circulation::GetAgeRestriction('PEGI14', $borrower); +is ( ($daysToAgeRestriction <= 0), 1, 'PEGI14 allowed for a 15 year old' ); +($restriction_age, $daysToAgeRestriction) = C4::Circulation::GetAgeRestriction('Age 10', $borrower); +is ( ($daysToAgeRestriction <= 0), 1, 'Age 10 allowed for a 15 year old' ); +($restriction_age, $daysToAgeRestriction) = C4::Circulation::GetAgeRestriction('K18', $borrower); +is ( ($daysToAgeRestriction > 0), 1, 'K18 blocked for a 15 year old' ); \ No newline at end of file diff --git a/t/db_dependent/Members.t b/t/db_dependent/Members.t index 998320be8d..0161c7b057 100755 --- a/t/db_dependent/Members.t +++ b/t/db_dependent/Members.t @@ -17,7 +17,7 @@ use Modern::Perl; -use Test::More tests => 58; +use Test::More tests => 69; use Test::MockModule; use Data::Dumper; use C4::Context; @@ -82,6 +82,8 @@ my %data = ( userid => 'tomasito' ); +testAgeAccessors(\%data); #Age accessor tests don't touch the db so it is safe to run them with just the object. + my $addmem=AddMember(%data); ok($addmem, "AddMember()"); @@ -196,11 +198,7 @@ C4::Context->clear_syspref_cache(); $checkcardnum=C4::Members::checkcardnumber($IMPOSSIBLE_CARDNUMBER, ""); is ($checkcardnum, "2", "Card number is too long"); -my $age=GetAge("1992-08-14", "2011-01-19"); -is ($age, "18", "Age correct"); -$age=GetAge("2011-01-19", "1992-01-19"); -is ($age, "-19", "Birthday In the Future"); C4::Context->set_preference( 'AutoEmailPrimaryAddress', 'OFF' ); C4::Context->clear_syspref_cache(); @@ -343,4 +341,80 @@ sub _find_member { return $found; } +### ------------------------------------- ### +### Testing GetAge() / SetAge() functions ### +### ------------------------------------- ### +#USES the package $member-variable to mock a koha.borrowers-object +sub testAgeAccessors { + my ($member) = @_; + my $original_dateofbirth = $member->{dateofbirth}; + + ##Testing GetAge() + my $age=GetAge("1992-08-14", "2011-01-19"); + is ($age, "18", "Age correct"); + + $age=GetAge("2011-01-19", "1992-01-19"); + is ($age, "-19", "Birthday In the Future"); + + ##Testing SetAge() for now() + my $dt_now = DateTime->now(); + $age = DateTime::Duration->new(years => 12, months => 6, days => 1); + C4::Members::SetAge( $member, $age ); + $age = C4::Members::GetAge( $member->{dateofbirth} ); + is ($age, '12', "SetAge 12 years"); + + $age = DateTime::Duration->new(years => 18, months => 12, days => 31); + C4::Members::SetAge( $member, $age ); + $age = C4::Members::GetAge( $member->{dateofbirth} ); + is ($age, '19', "SetAge 18+1 years"); #This is a special case, where months=>12 and days=>31 constitute one full year, hence we get age 19 instead of 18. + + $age = DateTime::Duration->new(years => 18, months => 12, days => 30); + C4::Members::SetAge( $member, $age ); + $age = C4::Members::GetAge( $member->{dateofbirth} ); + is ($age, '19', "SetAge 18 years"); + + $age = DateTime::Duration->new(years => 0, months => 1, days => 1); + C4::Members::SetAge( $member, $age ); + $age = C4::Members::GetAge( $member->{dateofbirth} ); + is ($age, '0', "SetAge 0 years"); + + $age = '0018-12-31'; + C4::Members::SetAge( $member, $age ); + $age = C4::Members::GetAge( $member->{dateofbirth} ); + is ($age, '19', "SetAge ISO_Date 18+1 years"); #This is a special case, where months=>12 and days=>31 constitute one full year, hence we get age 19 instead of 18. + + $age = '0018-12-30'; + C4::Members::SetAge( $member, $age ); + $age = C4::Members::GetAge( $member->{dateofbirth} ); + is ($age, '19', "SetAge ISO_Date 18 years"); + + $age = '18-1-1'; + eval { C4::Members::SetAge( $member, $age ); }; + is ((length $@ > 1), '1', "SetAge ISO_Date $age years FAILS"); + + $age = '0018-01-01'; + eval { C4::Members::SetAge( $member, $age ); }; + is ((length $@ == 0), '1', "SetAge ISO_Date $age years succeeds"); + + ##Testing SetAge() for relative_date + my $relative_date = DateTime->new(year => 3010, month => 3, day => 15); + + $age = DateTime::Duration->new(years => 10, months => 3); + C4::Members::SetAge( $member, $age, $relative_date ); + $age = C4::Members::GetAge( $member->{dateofbirth}, $relative_date->ymd() ); + is ($age, '10', "SetAge, 10 years and 3 months old person was born on ".$member->{dateofbirth}." if todays is ".$relative_date->ymd()); + + $age = DateTime::Duration->new(years => 112, months => 1, days => 1); + C4::Members::SetAge( $member, $age, $relative_date ); + $age = C4::Members::GetAge( $member->{dateofbirth}, $relative_date->ymd() ); + is ($age, '112', "SetAge, 112 years, 1 months and 1 days old person was born on ".$member->{dateofbirth}." if today is ".$relative_date->ymd()); + + $age = '0112-01-01'; + C4::Members::SetAge( $member, $age, $relative_date ); + $age = C4::Members::GetAge( $member->{dateofbirth}, $relative_date->ymd() ); + is ($age, '112', "SetAge ISO_Date, 112 years, 1 months and 1 days old person was born on ".$member->{dateofbirth}." if today is ".$relative_date->ymd()); + + $member->{dateofbirth} = $original_dateofbirth; #It is polite to revert made changes in the unit tests. +} #sub testAgeAccessors + 1; -- 2.11.4.GIT