96 lines
2.9 KiB
PHP
96 lines
2.9 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file classes/doi/DoiGenerator.php
|
|
*
|
|
* Copyright (c) 2014-2022 Simon Fraser University
|
|
* Copyright (c) 2000-2022 John Willinsky
|
|
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
|
*
|
|
* @class DoiGenerator
|
|
*
|
|
* @brief A utility class for generating DOI suffixes using the Cool DOIs pattern, which generates opaque, but easy to read suffixes.
|
|
*
|
|
* @see https://blog.datacite.org/cool-dois/
|
|
*/
|
|
|
|
namespace PKP\doi;
|
|
|
|
use Dflydev\Base32\Crockford\Crockford;
|
|
|
|
class DoiGenerator
|
|
{
|
|
// 32 by the factor of 6
|
|
protected const UPPER_LIMIT = 1073741823;
|
|
|
|
/**
|
|
* Constructs a DOI with an 8-character suffix, using a Crockford Base 32 algorithm
|
|
* to generate the suffix.
|
|
*
|
|
*/
|
|
public static function encodeSuffix(): string
|
|
{
|
|
$number = random_int(1, self::UPPER_LIMIT);
|
|
|
|
return self::base32EncodeSuffix($number);
|
|
}
|
|
|
|
/**
|
|
* Returns the decoded int used to generate the suffix after validating the two-digit checksum
|
|
*
|
|
* @return int|null Returns null if checksum is invalid
|
|
*/
|
|
public static function decodeSuffix(string $suffix): ?int
|
|
{
|
|
$suffix = strtoupper($suffix);
|
|
$suffixParts = str_split($suffix, 6);
|
|
$encodedString = $suffixParts[0];
|
|
$checksum = $suffixParts[1];
|
|
|
|
$decodedSuffix = Crockford::decode($encodedString);
|
|
|
|
$isSuffixValid = self::verifySuffixChecksum($decodedSuffix, $checksum);
|
|
|
|
return $isSuffixValid ? $decodedSuffix : null;
|
|
}
|
|
|
|
/**
|
|
* Encodes suffix as 8-digit base32 encoded string where the final two numbers are a checksum.
|
|
*
|
|
* E.g. DDDDDDYY where 'D' is a base32 encoded character and 'YY' is the checksum.
|
|
*
|
|
* @param int $number A random number between 1 and 1073741823 (UPPER_LIMIT). Used as seed for encoding suffix.
|
|
*/
|
|
protected static function base32EncodeSuffix(int $number): string
|
|
{
|
|
// Initial base32 encoded string (up to 6 characters max)
|
|
$encodedNumber = strtolower(Crockford::encode($number));
|
|
|
|
// Add checksum at end of string, calculated as modulo 97-10 (ISO 7064)
|
|
$remainder = self::calculateChecksum($number);
|
|
$payload = $encodedNumber . sprintf('%02d', $remainder);
|
|
|
|
return str_pad($payload, 8, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
|
|
/**
|
|
* Verifies the provided checksum was generated from the number provided.
|
|
*
|
|
* @param int $number The integer decoded form the base 32 suffix. Original number used to generate suffix.
|
|
* @param int $checksum The two-digit checksum (last two digits of suffix)
|
|
*/
|
|
protected static function verifySuffixChecksum(int $number, int $checksum): bool
|
|
{
|
|
return $checksum === self::calculateChecksum($number);
|
|
}
|
|
|
|
/**
|
|
* Checksum calculated as modulo 97-10 (ISO 7064).
|
|
*/
|
|
protected static function calculateChecksum(int $number): int
|
|
{
|
|
return 98 - (($number * 100) % 97);
|
|
}
|
|
}
|