From 681714ef46f76a164a41e2ff9027fca757355a5f Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Wed, 12 Oct 2016 15:29:24 +0300 Subject: [PATCH] Bug 17433 - Koha::Validation. Validation for the masses! Koha::Validation->tries now supports data structure validation and some basic type validations. --- Koha/Validation.pm | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++-- t/Koha/Validation.t | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 393 insertions(+), 6 deletions(-) create mode 100644 t/Koha/Validation.t diff --git a/Koha/Validation.pm b/Koha/Validation.pm index 261f8d5..7681c8c 100644 --- a/Koha/Validation.pm +++ b/Koha/Validation.pm @@ -21,11 +21,15 @@ use utf8; use strict; use warnings; -use C4::Context; use Email::Valid; +use C4::Context; +use C4::Biblio; + use vars qw($VERSION); +use Koha::Exception::BadParameter; + =head1 NAME Koha::Validation - validates inputs @@ -76,12 +80,13 @@ returns: 1 if the given phone number is valid, 0 otherwise. sub validate_phonenumber { my $phonenumber = shift; + my $validatePhoneNumber = C4::Context->preference("ValidatePhoneNumber"); # make sure we are allowed to validate phone numbers - return 1 if C4::Context->preference("ValidatePhoneNumber") eq "OFF"; + return 1 if $validatePhoneNumber eq "OFF"; return 1 if not $phonenumber; return 0 if $phonenumber =~ /(^(\s))|((\s)$)/; - my $regex = get_phonenumber_regex(C4::Context->preference("ValidatePhoneNumber")); + my $regex = get_phonenumber_regex($validatePhoneNumber); return ($phonenumber !~ /$regex/) ? 0:1; } @@ -99,19 +104,33 @@ returns: the regex =cut sub get_phonenumber_regex { - if (C4::Context->preference("ValidatePhoneNumber") eq "ipn") { + my ($validatePhoneNumber) = @_; + if ($validatePhoneNumber eq "ipn") { return qr/^((\+)?[1-9]{1,2})?([-\s\.])?((\(\d{1,4}\))|\d{1,4})(([-\s\.])?[0-9]{1,12}){1,2}$/; } - elsif (C4::Context->preference("ValidatePhoneNumber") eq "fin") { + elsif ($validatePhoneNumber eq "fin") { return qr/^((90[0-9]{3})?0|\+358([-\s])?)(?!(100|20(0|2(0|[2-3])|9[8-9])|300|600|700|708|75(00[0-3]|(1|2)\d{2}|30[0-2]|32[0-2]|75[0-2]|98[0-2])))(4|50|10[1-9]|20(1|2(1|[4-9])|[3-9])|29|30[1-9]|71|73|75(00[3-9]|30[3-9]|32[3-9]|53[3-9]|83[3-9])|2|3|5|6|8|9|1[3-9])([-\s])?(\d{1,3}[-\s]?){2,12}\d$/; } return qr/(.*)/; - } +} + +my ($f, $sf); +sub getMARCSubfieldSelectorCache { + return $sf; +} +sub getMARCFieldSelectorCache { + return $f; +} +sub getMARCSelectorCache { + return {f => $f, sf => $sf}; +} =head2 use_validator +DEPRECATED, use tries() + Validates given input with given validator. Koha::Validation::use_validator("phone", 123456789); @@ -140,4 +159,175 @@ sub use_validator { return 0; } +=HEAD2 tries + +Same as use_validator except wraps exceptions as Exceptions +See tests in t/Koha/Validation.t for usage examples. + + my $ok = Koha::Validation->tries('key', ['koha@example.com', 'this@example.com'], 'email', 'a'); + try { + Koha::Validation->tries('key', 'kohaexamplecom', 'email'); + } catch { + print $_->message; + }; + +@PARAM1 String, human readable key for the value we are validating +@PARAM2 Variable, variable to be validated. Can be an array or hash or a scalar +@PARAM3 String, validator selector, eg. email, phone, marcSubfieldSelector, ... +@PARAM4 String, the expected nested data types. + For example 'aa' is a Array of arrays + 'h' is a hash + 'ah' is a array of hashes +@RETURNS 1 if everything validated ok +@THROWS Koha::Exception::BadParameter typically. See individual validator functions for Exception type specifics + +=cut + +sub tries { + my ($package, $key, $val, $validator, $types) = @_; + + if ($types) { + my $t = substr($types,0,1); #Get first char + $package->$t($key, $val, $validator, substr($types,1)); #Trim first char from types + } + else { + my $err = __PACKAGE__->$validator($val); + Koha::Exception::BadParameter->throw(error => "'$key' '$val' $err") if $err; + return 1; + } + return 1; +} + +sub a { + my ($package, $key, $val, $validator, $types) = @_; + Koha::Exception::BadParameter->throw(error => _errmsg($key, $val, 'is not an \'ARRAY\'')) unless (ref($val) eq 'ARRAY'); + + if ($types) { + for (my $i=0 ; $i<@$val ; $i++) { + my $v = $val->[$i]; + my $t = substr($types,0,1); #Get first char + $package->$t($key.'->'.$i, $v, $validator, substr($types,1)); #Trim first char from types + } + } + else { + for (my $i=0 ; $i<@$val ; $i++) { + my $v = $val->[$i]; + $package->tries($key.'->'.$i, $v, $validator, $types); + } + } +} +sub h { + my ($package, $key, $val, $validator, $types) = @_; + Koha::Exception::BadParameter->throw(error => _errmsg($key, $val, 'is not a \'HASH\'')) unless (ref($val) eq 'HASH'); + + if ($types) { + while(my ($k, $v) = each(%$val)) { + my $t = substr($types,0,1); #Get first char + $package->$t($key.'->'.$k, $v, $validator, substr($types,1)); #Trim first char from types + } + } + else { + while(my ($k, $v) = each(%$val)) { + $package->tries($key.'->'.$k, $v, $validator, $types); + } + } +} +sub email { + my ($package, $val) = @_; + + return 'is not a valid \'email\'' if (not defined Email::Valid->address($val)); + return undef; +} +sub double { + my ($package, $val) = @_; + + return 'is not a valid \'double\'' unless ($val =~ /^\d+\.?\d*$/); + return undef; +} +sub string { + my ($package, $val) = @_; + + return 'is not a valid \'string\', but undefined' unless(defined($val)); + return 'is not a valid \'string\', but zero length' if(length($val) == 0); + return 'is not a valid \'string\', but a char' if(length($val) == 1); + return undef; +} +sub phone { + my ($package, $val) = @_; + + my $regex = get_phonenumber_regex(C4::Context->preference("ValidatePhoneNumber")); + return 'is not a valid \'phonenumber\'' if ($val !~ /$regex/); + return undef; +} + +=head2 marcSubfieldSelector + +See marcSelector() + +=cut + +sub marcSubfieldSelector { + my ($package, $val) = @_; + + if ($val =~ /^([0-9.]{3})(\w)$/) { + ($f, $sf) = ($1, $2); + return undef; + } + ($f, $sf) = (undef, undef); + return 'is not a MARC subfield selector'; +} + +=head2 marcFieldSelector + +See marcSelector() + +=cut + +sub marcFieldSelector { + my ($package, $val) = @_; + + if ($val =~ /^([0-9.]{3})$/) { + ($f, $sf) = ($1, undef); + return undef; + } + ($f, $sf) = (undef, undef); + return 'is not a MARC field selector'; +} + +=head2 marcSelector + +Sets package variables +$__PACKAGE__::f = MARC field code +$__PACKAGE__::sf = MARC subfield code +if a correct MARC selector was found +for ease of access +The existing variables are overwritten when a new validation check is done. + +Access them using getMARCSubfieldSelectorCache() and getMARCFieldSelectorCache() + +marcSelector can also deal with any value in KohaToMARCMapping. +marcSubfieldSelector() and marcFieldSelector() deal with MARC-tags only + +@PARAM1, String, current package +@PARAM2, String, MARC selector, eg. 856u or 110 + +=cut + +sub marcSelector { + my ($package, $val) = @_; + + if ($val =~ /^([0-9.]{3})(\w*)$/) { + ($f, $sf) = ($1, $2); + return undef; + } + ($f, $sf) = C4::Biblio::GetMarcFromKohaField($val, ''); + return 'is not a MARC selector' unless ($f && $sf); + return undef; +} + +sub _errmsg { + my ($key, $val, $err) = @_; + return "\"$key\" => \"$val\" $err"; +} + 1; diff --git a/t/Koha/Validation.t b/t/Koha/Validation.t new file mode 100644 index 0000000..a1c27f6 --- /dev/null +++ b/t/Koha/Validation.t @@ -0,0 +1,197 @@ +# Copyright 2016 KohaSuomi +# +# This file is part of Koha. +# + +use Modern::Perl; +use Test::More; + +use Scalar::Util qw(blessed); +use Try::Tiny; + +use C4::Biblio; +use Koha::Validation; + +use t::lib::TestObjects::SystemPreferenceFactory; +use t::lib::TestObjects::ObjectFactory; + + +my $globalContext = {}; +my $spref = t::lib::TestObjects::SystemPreferenceFactory->createTestGroup([ + { + preference => 'ValidatePhoneNumber', + value => 'fin', + }, + { + preference => 'ValidateEmailAddress', + value => 1, + }, + ], undef, $globalContext); + +subtest "Validate email", \&simpleEmail; +sub simpleEmail { + is(Koha::Validation::use_validator('email', 'koha@example.com'), + 1, + "Valid email"); + is(Koha::Validation::use_validator('email', 'koha@examplecom'), + 0, + "InValid email"); +} + +subtest "Validate phone", \&simplePhone; +sub simplePhone { + is(Koha::Validation::use_validator('phone', '+358504497763'), + 1, + "Valid phone"); + is(Koha::Validation::use_validator('phone', '+35504497763'), + 0, + "InValid phone"); +} + +subtest "Validate array of something", \&arrayOf; +sub arrayOf { + my @array = ('+358504497763', '0451123123'); + is(Koha::Validation->tries('phnbr', \@array, 'phone', 'a' ), + 1, + "Valid phonenumber array"); + + push(@array, 'soita heti'); + try { + is(Koha::Validation->tries('phnbr', \@array, 'phone', 'a' ), + 'SHOULD THROW AN EXCEPTION!', + "InValid phonenumber array"); + } catch { + is(ref($_), 'Koha::Exception::BadParameter', + "InValid phonenumber array"); + + is($_->message, + "'phnbr->2' 'soita heti' is not a valid 'phonenumber'", + "Got a well formatted error message"); + }; +} + +subtest "Validate string", \&simpleString; +sub simpleString { + + is(Koha::Validation->tries('key', '+358504497763', 'string'), + 1, + "Valid string"); + + try { + is(Koha::Validation->tries('key', 'A', 'string'), + 'SHOULD THROW AN EXCEPTION!', + "Not a string, but a char"); + } catch { + is(ref($_), 'Koha::Exception::BadParameter', + "Not a string, but a char"); + + is($_->message, + "'key' 'A' is not a valid 'string', but a char", + "Got a well formatted error message"); + }; + + try { + is(Koha::Validation->tries('key', '', 'string'), + 'SHOULD THROW AN EXCEPTION!', + "Not a string, but a nothing"); + } catch { + is(ref($_), 'Koha::Exception::BadParameter', + "Not a string, but a nothing"); + }; + + is(Koha::Validation->tries('key', 'AB', 'string'), + 1, + "Valid short String"); +} + +subtest "Array of Array of doubles", \&nestedArrayDoubles; +sub nestedArrayDoubles { + + my @array = ([0.4, 1.2], + [4.5, 7.9]); + is(Koha::Validation->tries('nay', \@array, 'double', 'aa'), + 1, + "Valid nested array of doubles"); + + push(@array, [2, 'lol']); + try { + is(Koha::Validation->tries('nay', \@array, 'double', 'aa'), + 'SHOULD THROW AN EXCEPTION!', + "InValid nested array of doubles"); + } catch { + is(ref($_), 'Koha::Exception::BadParameter', + "InValid nested array of doubles"); + + is($_->message, + "'nay->2->1' 'lol' is not a valid 'double'", + "Got a well formatted error message"); + }; +} + +subtest "MARC Selectors", \&marcSelectors; +sub marcSelectors { + + is(Koha::Validation->tries('mrc', '856', 'marcFieldSelector',), + 1, + "Valid MARC Field selector"); + is(Koha::Validation::getMARCFieldSelectorCache, '856', "Validated MARC Field remembered"); + is(Koha::Validation::getMARCSubfieldSelectorCache, undef, "Not touched MARC Subfield not defined"); + + try { + is(Koha::Validation->tries('mrc', '85u', 'marcFieldSelector',), + 'SHOULD THROW AN EXCEPTION!', + "InValid MARC Field selector"); + } catch { + is(ref($_), 'Koha::Exception::BadParameter', + "InValid MARC Field selector"); + + is($_->message, + "'mrc' '85u' is not a MARC field selector", + "Got a well formatted error message"); + }; + is(Koha::Validation::getMARCFieldSelectorCache, undef, "InValidated MARC Field forgot"); + is(Koha::Validation::getMARCSubfieldSelectorCache, undef, "Not touched MARC Subfield not defined"); + + is(Koha::Validation->tries('mrc', '110a', 'marcSubfieldSelector',), + 1, + "Valid MARC Subfield selector"); + is(Koha::Validation::getMARCFieldSelectorCache, '110', "Validated MARC Field remembered"); + is(Koha::Validation::getMARCSubfieldSelectorCache, 'a', "Validated MARC Subfield remembered"); + + try { + is(Koha::Validation->tries('mrc', '110', 'marcSubfieldSelector',), + 'SHOULD THROW AN EXCEPTION!', + "InValid MARC Subfield selector"); + } catch { + is(ref($_), 'Koha::Exception::BadParameter', + "InValid MARC Subfield selector"); + + is($_->message, + "'mrc' '110' is not a MARC subfield selector", + "Got a well formatted error message"); + }; + is(Koha::Validation::getMARCFieldSelectorCache, undef, "InValidated MARC Field forgot"); + is(Koha::Validation::getMARCSubfieldSelectorCache, undef, "InValidated MARC Subfield forgot"); + + is(Koha::Validation->tries('mrc', '110a', 'marcSelector',), + 1, + "Valid MARC selector"); + is(Koha::Validation::getMARCFieldSelectorCache, '110', "Validated MARC Field remembered"); + is(Koha::Validation::getMARCSubfieldSelectorCache, 'a', "Validated MARC Subfield remembered"); + + is(Koha::Validation->tries('mrc', '245', 'marcSelector',), + 1, + "Valid MARC selector"); + is(Koha::Validation::getMARCFieldSelectorCache, '245', "Validated MARC Field remembered"); + is(Koha::Validation::getMARCSubfieldSelectorCache, '', "Not given MARC Subfield forgot"); + + my ($f, $sf) = C4::Biblio::GetMarcFromKohaField('biblio.title', ''); + is(Koha::Validation->tries('mrc', 'biblio.title', 'marcSelector'), + 1, + "biblio.title is a valid MARC Selector"); + is(Koha::Validation::getMARCFieldSelectorCache, $f, "Field from KohaToMarcMapping"); + is(Koha::Validation::getMARCSubfieldSelectorCache, $sf, "Subfield from KohaToMarcMapping"); +} + +t::lib::TestObjects::ObjectFactory->tearDownTestContext($globalContext); +done_testing(); -- 1.9.1