diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 6a7333d317b71..c6ee657d2abc5 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -5021,12 +5021,15 @@ function wp_parse_args( $args, $defaults = array() ) { * * @since 5.1.0 * - * @param array|string $input_list List of values. - * @return array Array of values. + * @param mixed[]|string $input_list List of values. + * @return array Array of values. A string is split into a list, while an array + * keeps its keys, so the result is not necessarily a list. + * @phpstan-return ($input_list is string ? list : array) */ -function wp_parse_list( $input_list ) { +function wp_parse_list( $input_list ): array { if ( ! is_array( $input_list ) ) { - return preg_split( '/[\s,]+/', $input_list, -1, PREG_SPLIT_NO_EMPTY ); + $parsed_list = preg_split( '/[\s,]+/', $input_list, -1, PREG_SPLIT_NO_EMPTY ); + return is_array( $parsed_list ) ? $parsed_list : array(); } // Validate all entries of the list are scalar. @@ -5041,10 +5044,13 @@ function wp_parse_list( $input_list ) { * @since 3.0.0 * @since 5.1.0 Refactored to use wp_parse_list(). * - * @param array|string $input_list List of IDs. - * @return int[] Sanitized array of IDs. + * @param mixed[]|string $input_list List of IDs. + * @return int[] Sanitized array of IDs. May include zero. Keys are preserved + * from the input and `array_unique()` may leave gaps, so the + * result is not necessarily a list. + * @phpstan-return array */ -function wp_parse_id_list( $input_list ) { +function wp_parse_id_list( $input_list ): array { $input_list = wp_parse_list( $input_list ); return array_unique( array_map( 'absint', $input_list ) ); @@ -5056,13 +5062,27 @@ function wp_parse_id_list( $input_list ) { * @since 4.7.0 * @since 5.1.0 Refactored to use wp_parse_list(). * - * @param array|string $input_list List of slugs. - * @return string[] Sanitized array of slugs. + * @param mixed[]|string $input_list List of slugs. + * @return string[] Sanitized array of slugs. May include an empty string. Keys + * are preserved from the input and `array_unique()` may leave + * gaps, so the result is not necessarily a list. */ -function wp_parse_slug_list( $input_list ) { +function wp_parse_slug_list( $input_list ): array { $input_list = wp_parse_list( $input_list ); - return array_unique( array_map( 'sanitize_title', $input_list ) ); + return array_unique( + array_map( + 'sanitize_title', + array_map( + /* + * Cast booleans, integers, and floats to strings. Non-scalar types + * (including null) have already been filtered out by wp_parse_list(). + */ + 'strval', + $input_list + ) + ) + ); } /** diff --git a/tests/phpunit/tests/functions/wpParseIdList.php b/tests/phpunit/tests/functions/wpParseIdList.php index cd1af1e923166..326fc2d2adffb 100644 --- a/tests/phpunit/tests/functions/wpParseIdList.php +++ b/tests/phpunit/tests/functions/wpParseIdList.php @@ -15,17 +15,31 @@ class Tests_Functions_wpParseIdList extends WP_UnitTestCase { * * @dataProvider data_wp_parse_id_list * @dataProvider data_unexpected_input + * + * @param mixed[]|string $input_list + * @param array $expected */ - public function test_wp_parse_id_list( $input_list, $expected ) { - $this->assertSameSets( $expected, wp_parse_id_list( $input_list ) ); + public function test_wp_parse_id_list( $input_list, array $expected ): void { + $parsed_list = wp_parse_id_list( $input_list ); + $this->assertThat( + $parsed_list, + $this->callback( + static fn ( array $arr ) => array_all( + $arr, + static fn ( $v ) => is_int( $v ) && $v >= 0 + ) + ), + 'Array should contain only non-negative ints.' + ); + $this->assertSame( $expected, $parsed_list ); } /** * Data provider. * - * @return array[] + * @return array }> */ - public function data_wp_parse_id_list() { + public function data_wp_parse_id_list(): array { return array( 'regular' => array( 'input_list' => '1,2,3,4', @@ -37,7 +51,12 @@ public function data_wp_parse_id_list() { ), 'duplicate id in a string' => array( 'input_list' => '1,2,2,3,4', - 'expected' => array( 1, 2, 3, 4 ), + 'expected' => array( + 0 => 1, + 1 => 2, + 3 => 3, + 4 => 4, + ), ), 'duplicate id in an array' => array( 'input_list' => array( '1', '2', '3', '4', '3' ), @@ -61,9 +80,9 @@ public function data_wp_parse_id_list() { /** * Data provider. * - * @return array[] + * @return array }> */ - public function data_unexpected_input() { + public function data_unexpected_input(): array { return array( 'string with commas' => array( 'input_list' => '1,2,string with spaces', @@ -97,6 +116,25 @@ public function data_unexpected_input() { 'input_list' => array( 1, 2, false ), 'expected' => array( 1, 2, 0 ), ), + 'array with array' => array( + 'input_list' => array( 1, array(), 2 ), + 'expected' => array( + 0 => 1, + 2 => 2, + ), + ), + 'passed assoc array' => array( + 'input_list' => array( + 'one' => 1, + 'two' => '2', + 'three' => '3 is company', + ), + 'expected' => array( + 'one' => 1, + 'two' => 2, + 'three' => 3, + ), + ), ); } } diff --git a/tests/phpunit/tests/functions/wpParseList.php b/tests/phpunit/tests/functions/wpParseList.php index 2f7bf086e4193..e7bcce248ce85 100644 --- a/tests/phpunit/tests/functions/wpParseList.php +++ b/tests/phpunit/tests/functions/wpParseList.php @@ -13,62 +13,102 @@ class Tests_Functions_wpParseList extends WP_UnitTestCase { * @ticket 43977 * * @dataProvider data_wp_parse_list + * + * @param mixed[]|string $input_list + * @param array $expected */ - public function test_wp_parse_list( $input_list, $expected ) { - $this->assertSameSets( $expected, wp_parse_list( $input_list ) ); + public function test_wp_parse_list( $input_list, array $expected ): void { + $parsed_list = wp_parse_list( $input_list ); + $this->assertThat( + $parsed_list, + $this->callback( + static fn ( array $arr ) => array_all( + $arr, + static fn ( $v ) => is_scalar( $v ) + ) + ), + 'Array should contain only scalars.' + ); + $this->assertSame( $expected, $parsed_list ); } /** * Data provider. * - * @return array[] + * @return array }> */ - public function data_wp_parse_list() { + public function data_wp_parse_list(): array { return array( - 'ids only' => array( + 'ids only' => array( 'input_list' => '1,2,3,4', 'expected' => array( '1', '2', '3', '4' ), ), - 'slugs only' => array( + 'slugs only' => array( 'input_list' => 'apple,banana,carrot,dog', 'expected' => array( 'apple', 'banana', 'carrot', 'dog' ), ), - 'ids and slugs' => array( + 'ids and slugs' => array( 'input_list' => '1,2,apple,banana', 'expected' => array( '1', '2', 'apple', 'banana' ), ), - 'space after comma' => array( + 'space after comma' => array( 'input_list' => '1, 2,apple,banana', 'expected' => array( '1', '2', 'apple', 'banana' ), ), - 'double comma' => array( + 'double comma' => array( 'input_list' => '1,2,apple,,banana', 'expected' => array( '1', '2', 'apple', 'banana' ), ), - 'leading comma' => array( + 'leading comma' => array( 'input_list' => ',1,2,apple,banana', 'expected' => array( '1', '2', 'apple', 'banana' ), ), - 'trailing comma' => array( + 'trailing comma' => array( 'input_list' => '1,2,apple,banana,', 'expected' => array( '1', '2', 'apple', 'banana' ), ), - 'space before comma' => array( + 'space before comma' => array( 'input_list' => '1,2 ,apple,banana', 'expected' => array( '1', '2', 'apple', 'banana' ), ), - 'empty string' => array( + 'empty string' => array( 'input_list' => '', 'expected' => array(), ), - 'comma only' => array( + 'comma only' => array( 'input_list' => ',', 'expected' => array(), ), - 'double comma only' => array( + 'double comma only' => array( 'input_list' => ',,', 'expected' => array(), ), + 'passed scalar array' => array( + 'input_list' => array( 'foo', true, false, 1, 3.14 ), + 'expected' => array( 'foo', true, false, 1, 3.14 ), + ), + 'passed mixed array' => array( + 'input_list' => array( null, 'foo', array(), true, new stdClass(), false, 1, 3.14 ), + 'expected' => array( + 1 => 'foo', + 3 => true, + 5 => false, + 6 => 1, + 7 => 3.14, + ), + ), + 'passed assoc array' => array( + 'input_list' => array( + 'foo' => 1, + 'bar' => true, + 'baz' => 3.14, + ), + 'expected' => array( + 'foo' => 1, + 'bar' => true, + 'baz' => 3.14, + ), + ), ); } } diff --git a/tests/phpunit/tests/functions/wpParseSlugList.php b/tests/phpunit/tests/functions/wpParseSlugList.php index 8b9ccb2ddb091..d103bab4309b8 100644 --- a/tests/phpunit/tests/functions/wpParseSlugList.php +++ b/tests/phpunit/tests/functions/wpParseSlugList.php @@ -15,17 +15,31 @@ class Tests_Functions_WpParseSlugList extends WP_UnitTestCase { * * @dataProvider data_wp_parse_slug_list * @dataProvider data_unexpected_input + * + * @param mixed[]|string $input_list + * @param array $expected */ - public function test_wp_parse_slug_list( $input_list, $expected ) { - $this->assertSameSets( $expected, wp_parse_slug_list( $input_list ) ); + public function test_wp_parse_slug_list( $input_list, array $expected ): void { + $parsed_list = wp_parse_slug_list( $input_list ); + $this->assertThat( + $parsed_list, + $this->callback( + static fn ( array $arr ) => array_all( + $arr, + static fn ( $v ) => is_string( $v ) + ) + ), + 'Array should contain only strings.' + ); + $this->assertSame( $expected, $parsed_list ); } /** * Data provider. * - * @return array[] + * @return array }> */ - public function data_wp_parse_slug_list() { + public function data_wp_parse_slug_list(): array { return array( 'regular' => array( 'input_list' => 'apple,banana,carrot,dog', @@ -37,11 +51,21 @@ public function data_wp_parse_slug_list() { ), 'duplicate slug in a string' => array( 'input_list' => 'apple,banana,carrot,carrot,dog', - 'expected' => array( 'apple', 'banana', 'carrot', 'dog' ), + 'expected' => array( + 0 => 'apple', + 1 => 'banana', + 2 => 'carrot', + 4 => 'dog', + ), ), 'duplicate slug in an array' => array( 'input_list' => array( 'apple', 'banana', 'carrot', 'carrot', 'dog' ), - 'expected' => array( 'apple', 'banana', 'carrot', 'dog' ), + 'expected' => array( + 0 => 'apple', + 1 => 'banana', + 2 => 'carrot', + 4 => 'dog', + ), ), 'string with spaces' => array( 'input_list' => 'apple banana carrot dog', @@ -51,15 +75,27 @@ public function data_wp_parse_slug_list() { 'input_list' => array( 'apple ', 'banana carrot', 'd o g' ), 'expected' => array( 'apple', 'banana-carrot', 'd-o-g' ), ), + 'passed assoc array' => array( + 'input_list' => array( + 'one' => 'foo', + 'two' => 'bar', + 'three' => 'baz', + ), + 'expected' => array( + 'one' => 'foo', + 'two' => 'bar', + 'three' => 'baz', + ), + ), ); } /** * Data provider. * - * @return array[] + * @return array }> */ - public function data_unexpected_input() { + public function data_unexpected_input(): array { return array( 'string with commas' => array( 'input_list' => '1,2,string with spaces', @@ -93,6 +129,17 @@ public function data_unexpected_input() { 'input_list' => array( 1, 2, false ), 'expected' => array( '1', '2', '' ), ), + 'array with array' => array( + 'input_list' => array( 1, array(), 2 ), + 'expected' => array( + 0 => '1', + 2 => '2', + ), + ), + 'array with tag' => array( + 'input_list' => array( 1, '
', 2 ), + 'expected' => array( '1', '', '2' ), + ), ); } }