Bug 42529: Add Koha::Policy::Biblio::AgeRestriction

This patch adds age restriction handling to the Koha namespace:

- Koha::Biblio->age_restriction: metadata extractor that parses the
  agerestriction field using the AgeRestrictionMarker system preference.
  Returns the numeric age or undef. Uses single return with last pattern.
- Koha::Policy::Biblio::AgeRestriction->check($biblio, $patron): policy
  class that evaluates whether a patron meets the age restriction.
  Returns a Koha::Result::Boolean (with payload on restriction).

Test plan:
1. Apply this patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha/Policy/Biblio/AgeRestriction.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Tomás Cohen Arazi 2026-06-18 14:57:23 -03:00 committed by Pedro Amorim
parent 51e3d5435a
commit 8ebf3436cb
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
3 changed files with 203 additions and 0 deletions

View File

@ -2154,6 +2154,51 @@ sub link_marc_host {
return $self;
}
=head3 age_restriction
my $age = $biblio->age_restriction;
Extracts the numeric age restriction from the biblio's agerestriction field
using the AgeRestrictionMarker system preference.
Returns the age (integer) or undef if no restriction is found.
=cut
sub age_restriction {
my ($self) = @_;
my $restriction_string = $self->biblioitem->agerestriction;
return unless $restriction_string;
my @values = split ' ', uc($restriction_string);
return unless @values;
my @markers = map { my $m = uc($_); $m =~ s/^\s+//; $m =~ s/\s+$//; $m }
@{ C4::Context->multivalue_preference('AgeRestrictionMarker') };
return unless @markers;
my $index = 0;
my $restriction_age = 0;
for my $value (@values) {
$index++;
for my $marker (@markers) {
if ( $marker eq $value ) {
if ( $index <= $#values ) {
$restriction_age += $values[$index];
}
last;
} elsif ( $value =~ /^\Q$marker\E(\d+)$/ ) {
$restriction_age += $1;
last;
}
}
last if ( $restriction_age > 0 );
}
return $restriction_age || undef;
}
=head3 recalls
my $recalls = $biblio->recalls;

View File

@ -0,0 +1,57 @@
package Koha::Policy::Biblio::AgeRestriction;
# Copyright 2026 Theke Solutions
#
# 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 3 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, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Koha::Result::Boolean;
=head1 NAME
Koha::Policy::Biblio::AgeRestriction - age restriction policy checks
=head1 API
=head2 Class methods
=head3 check
my $result = Koha::Policy::Biblio::AgeRestriction->check( $biblio, $patron );
Checks whether a patron meets the age restriction for a biblio.
Returns undef if there is no restriction or it cannot be evaluated
(e.g. patron has no date of birth). Otherwise returns a L<Koha::Result::Boolean>:
true if the patron meets the restriction, false (with message) if not.
=cut
sub check {
my ( $class, $biblio, $patron ) = @_;
my $restriction_age = $biblio->age_restriction;
return Koha::Result::Boolean->new(1) unless $restriction_age;
return Koha::Result::Boolean->new(1) unless $patron->dateofbirth;
return $restriction_age > $patron->get_age
? Koha::Result::Boolean->new(0)
->add_message( { message => 'age_restricted', payload => { restriction_age => $restriction_age } } )
: Koha::Result::Boolean->new(1);
}
1;

View File

@ -0,0 +1,101 @@
#!/usr/bin/env perl
# This file is part of Koha.
#
# Copyright 2015 Koha Development Team
# Copyright 2026 Theke Solutions
#
# 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 3 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, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Test::More tests => 3;
use Test::NoWarnings;
use t::lib::Mocks;
use t::lib::TestBuilder;
use Koha::Database;
use Koha::Policy::Biblio::AgeRestriction;
my $schema = Koha::Database->new->schema;
my $builder = t::lib::TestBuilder->new;
t::lib::Mocks::mock_preference( 'AgeRestrictionMarker', 'FSK|PEGI|Age|K' );
subtest 'Koha::Biblio->age_restriction' => sub {
plan tests => 7;
$schema->storage->txn_begin;
my $biblio_fsk = $builder->build_sample_biblio;
$biblio_fsk->biblioitem->agerestriction('FSK 16')->store;
is( $biblio_fsk->age_restriction, 16, 'FSK 16 returns 16' );
my $biblio_pegi = $builder->build_sample_biblio;
$biblio_pegi->biblioitem->agerestriction('PEGI 16')->store;
is( $biblio_pegi->age_restriction, 16, 'PEGI 16 returns 16' );
my $biblio_pegi_no_space = $builder->build_sample_biblio;
$biblio_pegi_no_space->biblioitem->agerestriction('PEGI16')->store;
is( $biblio_pegi_no_space->age_restriction, 16, 'PEGI16 returns 16' );
my $biblio_age = $builder->build_sample_biblio;
$biblio_age->biblioitem->agerestriction('Age 16')->store;
is( $biblio_age->age_restriction, 16, 'Age 16 returns 16' );
my $biblio_k = $builder->build_sample_biblio;
$biblio_k->biblioitem->agerestriction('K16')->store;
is( $biblio_k->age_restriction, 16, 'K16 returns 16' );
my $biblio_none = $builder->build_sample_biblio;
$biblio_none->biblioitem->agerestriction(undef)->store;
is( $biblio_none->age_restriction, undef, 'No restriction returns undef' );
my $biblio_empty = $builder->build_sample_biblio;
$biblio_empty->biblioitem->agerestriction('')->store;
is( $biblio_empty->age_restriction, undef, 'Empty restriction returns undef' );
$schema->storage->txn_rollback;
};
subtest 'Koha::Policy::Biblio::AgeRestriction->check' => sub {
plan tests => 6;
$schema->storage->txn_begin;
my $biblio = $builder->build_sample_biblio;
$biblio->biblioitem->agerestriction('FSK 16')->store;
my $young_patron = $builder->build_object( { class => 'Koha::Patrons', value => { dateofbirth => '2016-01-01' } } );
my $adult_patron = $builder->build_object( { class => 'Koha::Patrons', value => { dateofbirth => '1990-01-01' } } );
my $no_dob_patron = $builder->build_object( { class => 'Koha::Patrons', value => { dateofbirth => undef } } );
my $result = Koha::Policy::Biblio::AgeRestriction->check( $biblio, $young_patron );
ok( defined $result && !$result, 'Young patron is restricted' );
is( $result->messages->[0]->message, 'age_restricted', 'Message is age_restricted' );
is( $result->messages->[0]->payload->{restriction_age}, 16, 'Restriction age in message payload' );
$result = Koha::Policy::Biblio::AgeRestriction->check( $biblio, $adult_patron );
ok( $result, 'Adult patron is not restricted' );
$result = Koha::Policy::Biblio::AgeRestriction->check( $biblio, $no_dob_patron );
ok( $result, 'No date of birth returns true (permissive)' );
my $biblio_no_restriction = $builder->build_sample_biblio;
$result = Koha::Policy::Biblio::AgeRestriction->check( $biblio_no_restriction, $young_patron );
ok( $result, 'No restriction returns true (permissive)' );
$schema->storage->txn_rollback;
};