diff --git a/core/lib/Drupal/Component/Utility/Number.php b/core/lib/Drupal/Component/Utility/Number.php index 2fcbc0b2e759f5537dfe109a7d98c4ea08183e8f..7fbcb03ef2ed50581f7b64495f8d02091163b67f 100644 --- a/core/lib/Drupal/Component/Utility/Number.php +++ b/core/lib/Drupal/Component/Utility/Number.php @@ -92,10 +92,24 @@ public static function intToAlphadecimal($i = 0) { * @return int * The integer value. * + * @throws \InvalidArgumentException + * If $string contains invalid characters, throw an exception. + * * @see \Drupal\Component\Utility\Number::intToAlphadecimal */ public static function alphadecimalToInt($string = '00') { - return (int) base_convert(substr($string, 1), 36, 10); + // For backwards compatibility, we must accept NULL + // and the empty string, returning 0, + // like (int) base_convert(substr($string, 1), 36, 10) always did. + if ('' === $string || NULL === $string) { + @trigger_error('Passing NULL or an empty string to ' . __METHOD__ . '() is deprecated in drupal:11.2.0 and will be removed in drupal:12.0.0. See https://www.drupal.org/node/3494472', E_USER_DEPRECATED); + return 0; + } + $alpha_decimal_substring = substr($string, 1); + if (!ctype_alnum($alpha_decimal_substring)) { + throw new \InvalidArgumentException("Invalid characters passed for attempted conversion: $string"); + } + return (int) base_convert($alpha_decimal_substring, 36, 10); } } diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index 5d734aeecd771f26bef408c93271f2f8231004e3..a6fcb2e0f0ee3b8ad7b6eb219609696ef2ac9c61 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -115,7 +115,7 @@ public function preSave(EntityStorageInterface $storage) { $max = rtrim((string) $max, '/'); // We need to get the value at the correct depth. $parts = explode('.', $max); - $n = Number::alphadecimalToInt($parts[0]); + $n = $parts[0] ? Number::alphadecimalToInt($parts[0]) : 0; $prefix = ''; } else { diff --git a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php index 63f2d0a8b353c497b3cc016437521d2c57ce033d..2055eecde6475e042f317c1e2af4f680f3066b4f 100644 --- a/core/tests/Drupal/Tests/Component/Utility/NumberTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/NumberTest.php @@ -5,6 +5,7 @@ namespace Drupal\Tests\Component\Utility; use Drupal\Component\Utility\Number; +use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; use PHPUnit\Framework\TestCase; /** @@ -18,6 +19,8 @@ */ class NumberTest extends TestCase { + use ExpectDeprecationTrait; + /** * Tests Number::validStep() without offset. * @@ -157,4 +160,35 @@ public static function providerTestConversions() { ]; } + /** + * Tests the alphadecimal conversion function input parameter checking. + * + * Number::alphadecimalToInt() must throw an exception + * when non-alphanumeric characters are passed as input. + * + * @covers ::alphadecimalToInt + */ + public function testAlphadecimalToIntThrowsExceptionWithMalformedStrings(): void { + $this->expectException(\InvalidArgumentException::class); + $nonAlphanumericChar = '#'; + Number::alphadecimalToInt($nonAlphanumericChar); + } + + /** + * Tests the alphadecimal conversion function keeps backward compatibility. + * + * Many tests and code rely on Number::alphadecimalToInt() returning 0 + * for degenerate values '' and NULL. We must ensure they are accepted. + * + * @group legacy + * @covers ::alphadecimalToInt + */ + public function testAlphadecimalToIntReturnsZeroWithNullAndEmptyString(): void { + $deprecationMessage = 'Passing NULL or an empty string to Drupal\Component\Utility\Number::alphadecimalToInt() is deprecated in drupal:11.2.0 and will be removed in drupal:12.0.0. See https://www.drupal.org/node/3494472'; + $this->expectDeprecation($deprecationMessage); + $this->assertSame(0, Number::alphadecimalToInt(NULL)); + $this->expectDeprecation($deprecationMessage); + $this->assertSame(0, Number::alphadecimalToInt('')); + } + }