diff --git a/README.md b/README.md index 7e9b347..9d13d5d 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,12 @@ Checks for a valid [European Article Number](https://en.wikipedia.org/wiki/Inter Optional integer length (8 or 13) to check only for EAN-8 or EAN-13. +### Global Release Identifier (GRid) + +The field under validation must be a [Global Release Identifier](https://en.wikipedia.org/wiki/Global_Release_Identifier). + + public Intervention\Validation\Rules\Grid::__construct() + ### Global Trade Item Number (GTIN) Checks for a valid [Global Trade Item Number](https://en.wikipedia.org/wiki/Global_Trade_Item_Number). diff --git a/src/Rules/Grid.php b/src/Rules/Grid.php new file mode 100644 index 0000000..85b1afd --- /dev/null +++ b/src/Rules/Grid.php @@ -0,0 +1,79 @@ +A1-?[A-Z0-9]{5}-?[A-Z0-9]{10}-?[A-Z0-9])$/"; + } + + /** + * Determine if the validation rule passes. + * + * @param mixed $value + * @return bool + */ + public function isValid(mixed $value): bool + { + return is_string($value) + && parent::isValid(strtoupper($value)) + && $this->hasValidChecksum($value); + } + + /** + * Calculate checksum from given grid number + * + * @param string $value + * @return bool + */ + private function hasValidChecksum(string $value): bool + { + // get GRid from value + preg_match($this->pattern(), strtoupper($value), $matches); + + // split GRid into single characters (without dashes) + $characters = str_split( + str_replace('-', '', $matches['grid']) + ); + + // extract last (check) character + $checkCharacter = array_pop($characters); + + $m = 36; + $n = 37; + $product = $m; + $sum = 0; + + // calculate checksum + foreach ($characters as $char) { + $sum = ($product + $this->charValue($char)) % $m; + $sum = $sum === 0 ? $m : $sum; + $product = ($sum * 2) % $n; + } + + // compare checksum to check character value + return $n - $product === $this->charValue($checkCharacter); + } + + /** + * Get character value according to GRid standard v2.1 + * + * @param string $char + * @return int + */ + private function charValue(string $char): int + { + return is_numeric($char) ? (int) $char : (int) str_replace(range('A', 'Z'), range(10, 35), strtoupper($char)); + } +} diff --git a/tests/Rules/GridTest.php b/tests/Rules/GridTest.php new file mode 100644 index 0000000..d852b02 --- /dev/null +++ b/tests/Rules/GridTest.php @@ -0,0 +1,44 @@ +isValid($value); + $this->assertEquals($result, $valid); + } + + public static function dataProvider(): array + { + return [ + [true, 'A12425GABC1234002M'], + [true, 'a12425gabc1234002m'], // lowercase valid + [true, 'GRid:A12425GABC1234002M'], + [true, 'GRID:A12425GABC1234002M'], + [true, 'A1-2425G-ABC1234002-M'], + [true, 'A1-2425GABC1234002M'], // only one dash valid + [true, 'GRid:A1-2425G-ABC1234002-M'], + [true, 'GRID:A1-2425G-ABC1234002-M'], + [true, 'A11244BC12345678DP'], + [true, 'A1-1244B-C12345678D-P'], + [true, 'GRid:A11244BC12345678DP'], + [true, 'GRID:A11244BC12345678DP'], + [true, 'GRid:A1-1244B-C12345678D-P'], + [true, 'GRID:A1-1244B-C12345678D-P'], + [false, 'FOO:A1-1244B-C12345678D-P'], // false prefix + [false, 'B1-1244B-C12345678D-P'], // false Identifier Scheme element + [false, 'A1-1244B-C12345678D-A'], // false Check Character + [false, 'ZA9382189201'], // no grid + [false, ''], // empty + ]; + } +}