Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/wp-includes/block-supports/typography.php
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ function wp_get_typography_font_size_value( $preset, $settings = array() ) {
* For a - b * log2(), lower values of b will make the curve move towards the minimum faster.
* The scale factor is constrained between min and max values.
*/
$minimum_font_size_factor = min( max( 1 - 0.075 * log( $preferred_font_size_in_px, 2 ), $default_minimum_font_size_factor_min ), $default_minimum_font_size_factor_max );
$minimum_font_size_factor = clamp( 1 - 0.075 * log( $preferred_font_size_in_px, 2 ), $default_minimum_font_size_factor_min, $default_minimum_font_size_factor_max );
$calculated_minimum_font_size = round( $preferred_size['value'] * $minimum_font_size_factor, 3 );

// Only use calculated min font size if it's > $minimum_font_size_limit value.
Expand Down
65 changes: 65 additions & 0 deletions src/wp-includes/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,71 @@ function array_last( array $array ) { // phpcs:ignore Universal.NamingConvention
}
}

/**
* Throws a ValueError (PHP 8.0+) or an InvalidArgumentException (PHP 7.x) with the given message.
*
* Helper for polyfills that need to throw ValueError but must also run on PHP 7.4.
*
* @ignore
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ignore?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @ignore PHPDoc tag tells documentation generators to exclude the function from the generated documentation.

It's only used by clamp(). External code (plugins/themes) should never need to call it — they can throw their own exceptions directly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

* @since 7.1.0
* @access private
*
* @param string $message The error message.
* @throws ValueError On PHP 8.0 and later.
* @throws InvalidArgumentException On PHP 7.x as a fallback.
*/
function _wp_throw_value_error( $message ) {
if ( ! class_exists( 'ValueError', false ) ) {
throw new InvalidArgumentException( $message );
}

throw new ValueError( $message );
}

if ( ! function_exists( 'clamp' ) ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some unit tests coverage for the function

/**
* Polyfill for `clamp()` function added in PHP 8.6.
*
* Clamps a value to be within the range of a given minimum and maximum.
*
* If the value is within the bounds, the original value is returned.
* If it is not within the bounds, the closest bound is returned.
*
* @since 7.1.0
*
* @param mixed $value The value to clamp.
* @param mixed $min The minimum bound. Must be less than or equal to `$max`.
* @param mixed $max The maximum bound. Must be greater than or equal to `$min`.
* @return mixed The clamped value.
*
* @throws ValueError On PHP 8.0+: if `$min` is greater than `$max`, or if `$min` or `$max` is NAN.
* @throws InvalidArgumentException On PHP 7.x: if `$min` is greater than `$max`, or if `$min` or `$max` is NAN.
*/
function clamp( $value, $min, $max ) {
if ( is_float( $min ) && is_nan( $min ) ) {
_wp_throw_value_error( 'clamp(): Argument #2 ($min) cannot be NAN' );
}

if ( is_float( $max ) && is_nan( $max ) ) {
_wp_throw_value_error( 'clamp(): Argument #3 ($max) cannot be NAN' );
}

if ( $max < $min ) {
_wp_throw_value_error( 'clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)' );
}
Comment thread
Soean marked this conversation as resolved.

if ( $value < $min ) {
return $min;
}

if ( $value > $max ) {
return $max;
}

return $value;
}
}

// IMAGETYPE_AVIF constant is only defined in PHP 8.x or later.
if ( ! defined( 'IMAGETYPE_AVIF' ) ) {
define( 'IMAGETYPE_AVIF', 19 );
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/embed.php
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ function get_oembed_response_data( $post, $width ) {
)
);

$width = min( max( $min_max_width['min'], $width ), $min_max_width['max'] );
$width = clamp( $width, $min_max_width['min'], $min_max_width['max'] );
$height = max( (int) ceil( $width / 16 * 9 ), 200 );

$data = array(
Expand Down
280 changes: 280 additions & 0 deletions tests/phpunit/tests/compat/clamp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
<?php

/**
* @group compat
*
* @covers ::clamp
*/
class Tests_Compat_clamp extends WP_UnitTestCase {

/**
* @ticket 65143
*
* Test that clamp() is always available (either from PHP or WP).
*/
public function test_clamp_availability(): void {
$this->assertTrue( function_exists( 'clamp' ) );
}

/**
* @ticket 65143
*
* @dataProvider data_clamp
*
* @param mixed $expected The expected clamped value.
* @param mixed $value The value to clamp.
* @param mixed $min The minimum bound.
* @param mixed $max The maximum bound.
*/
public function test_clamp( $expected, $value, $min, $max ): void {
$this->assertSame( $expected, clamp( $value, $min, $max ) );
}

/**
* Data provider for clamp().
*
* @return array[]
*/
public function data_clamp(): array {
return array(
'integer within range' => array(
'expected' => 5,
'value' => 5,
'min' => 1,
'max' => 10,
),
'integer below min' => array(
'expected' => 1,
'value' => -5,
'min' => 1,
'max' => 10,
),
'integer above max' => array(
'expected' => 10,
'value' => 99,
'min' => 1,
'max' => 10,
),
'integer equals min' => array(
'expected' => 1,
'value' => 1,
'min' => 1,
'max' => 10,
),
'integer equals max' => array(
'expected' => 10,
'value' => 10,
'min' => 1,
'max' => 10,
),
'min equals max, value matches' => array(
'expected' => 5,
'value' => 5,
'min' => 5,
'max' => 5,
),
'min equals max, value below' => array(
'expected' => 5,
'value' => 3,
'min' => 5,
'max' => 5,
),
'min equals max, value above' => array(
'expected' => 5,
'value' => 7,
'min' => 5,
'max' => 5,
),
'float within range' => array(
'expected' => 0.5,
'value' => 0.5,
'min' => 0.0,
'max' => 1.0,
),
'float below min' => array(
'expected' => 0.0,
'value' => -0.5,
'min' => 0.0,
'max' => 1.0,
),
'float above max' => array(
'expected' => 1.0,
'value' => 1.5,
'min' => 0.0,
'max' => 1.0,
),
'negative range, within' => array(
'expected' => -5,
'value' => -5,
'min' => -10,
'max' => -1,
),
'negative range, below min' => array(
'expected' => -10,
'value' => -99,
'min' => -10,
'max' => -1,
),
'negative range, above max' => array(
'expected' => -1,
'value' => 0,
'min' => -10,
'max' => -1,
),
'zero within range' => array(
'expected' => 0,
'value' => 0,
'min' => -1,
'max' => 1,
),
'mixed int/float, within range' => array(
'expected' => 5,
'value' => 5,
'min' => 0.0,
'max' => 10.0,
),
'INF as value' => array(
'expected' => 100,
'value' => INF,
'min' => 0,
'max' => 100,
),
'-INF as value' => array(
'expected' => 0,
'value' => -INF,
'min' => 0,
'max' => 100,
),
'INF as max, value within range' => array(
'expected' => 50,
'value' => 50,
'min' => 0,
'max' => INF,
),
'INF as max, value equals INF' => array(
'expected' => INF,
'value' => INF,
'min' => 0,
'max' => INF,
),
'string within range' => array(
'expected' => 'l',
'value' => 'l',
'min' => 'a',
'max' => 'z',
),
'string below min' => array(
'expected' => 'e',
'value' => 'a',
'min' => 'e',
'max' => 'z',
),
'string above max' => array(
'expected' => 'p',
'value' => 'z',
'min' => 'a',
'max' => 'p',
),
);
}

/**
* @ticket 65143
*
* @dataProvider data_clamp_datetime
*
* @param DateTimeImmutable $expected The expected clamped value.
* @param DateTimeImmutable $value The value to clamp.
* @param DateTimeImmutable $min The minimum bound.
* @param DateTimeImmutable $max The maximum bound.
*/
public function test_clamp_with_datetime( $expected, $value, $min, $max ): void {
$this->assertEquals( $expected, clamp( $value, $min, $max ) );
}

/**
* Data provider for DateTimeImmutable cases.
*
* @return array[]
*/
public function data_clamp_datetime(): array {
return array(
'within range' => array(
'expected' => new DateTimeImmutable( '2025-01-15' ),
'value' => new DateTimeImmutable( '2025-01-15' ),
'min' => new DateTimeImmutable( '2025-01-01' ),
'max' => new DateTimeImmutable( '2025-01-31' ),
),
'below min' => array(
'expected' => new DateTimeImmutable( '2025-01-01' ),
'value' => new DateTimeImmutable( '2024-12-01' ),
'min' => new DateTimeImmutable( '2025-01-01' ),
'max' => new DateTimeImmutable( '2025-01-31' ),
),
'above max' => array(
'expected' => new DateTimeImmutable( '2025-01-31' ),
'value' => new DateTimeImmutable( '2025-03-01' ),
'min' => new DateTimeImmutable( '2025-01-01' ),
'max' => new DateTimeImmutable( '2025-01-31' ),
),
);
}

/**
* @ticket 65143
*
* Test that clamp() throws when $min is NAN.
*/
public function test_clamp_throws_for_nan_min(): void {
$this->expectException( $this->value_error_class() );
$this->expectExceptionMessage( 'clamp(): Argument #2 ($min) cannot be NAN' );

clamp( 5, NAN, 10 );
}

/**
* @ticket 65143
*
* Test that clamp() throws when $max is NAN.
*/
public function test_clamp_throws_for_nan_max(): void {
$this->expectException( $this->value_error_class() );
$this->expectExceptionMessage( 'clamp(): Argument #3 ($max) cannot be NAN' );

clamp( 5, 0, NAN );
}

/**
* @ticket 65143
*
* Test that clamp() throws when $min is greater than $max.
*/
public function test_clamp_throws_when_min_greater_than_max(): void {
$this->expectException( $this->value_error_class() );
$this->expectExceptionMessage( 'clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)' );

clamp( 5, 10, 1 );
}

/**
* @ticket 65143
*
* Test that clamp() with a NAN value returns NAN (no exception).
*/
public function test_clamp_with_nan_value_returns_nan(): void {
$result = clamp( NAN, 0, 10 );
$this->assertNan( $result );
}

/**
* Returns the expected exception class for ValueError-equivalent errors.
*
* On PHP 8.0+, _wp_throw_value_error() throws ValueError.
* On PHP 7.x, it falls back to InvalidArgumentException.
*
* @return string The fully qualified exception class name.
*/
private function value_error_class(): string {
return PHP_VERSION_ID >= 80000 ? ValueError::class : InvalidArgumentException::class;
}
}
Loading