From 0908aeb96d701b8b34bee5e28cb647a35c38a5ed Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Wed, 6 May 2026 15:49:22 -0400 Subject: [PATCH 1/5] Add unit tests for wp_doc_link_parse() in wp-admin/includes/misc.php --- .../admin/includes/misc/wpDocLinkParse.php | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php diff --git a/tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php b/tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php new file mode 100644 index 0000000000000..6e0cb65568cae --- /dev/null +++ b/tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php @@ -0,0 +1,104 @@ +assertSame( $expected, wp_doc_link_parse( $content ) ); + } + + /** + * Data provider for test_wp_doc_link_parse. + * + * @return array + */ + public function data_wp_doc_link_parse(): array { + return array( + 'empty string' => array( + 'content' => '', + 'expected' => array(), + ), + 'null (invalid type)' => array( + 'content' => null, + 'expected' => array(), + ), + 'simple function call' => array( + 'content' => '', + 'expected' => array( 'get_header' ), + ), + 'multiple unique functions' => array( + 'content' => '', + 'expected' => array( 'get_header', 'wp_footer' ), + ), + 'duplicate functions' => array( + 'content' => '', + 'expected' => array( '_e' ), + ), + 'sorted output' => array( + 'content' => '', + 'expected' => array( 'alpha', 'zeta' ), + ), + 'local function definition' => array( + 'content' => '', + 'expected' => array(), + ), + 'class method call' => array( + 'content' => 'my_method(); ?>', + 'expected' => array(), + ), + 'static class method call' => array( + 'content' => '', + 'expected' => array( 'my_static_method' ), // token_get_all handles :: differently. + ), + 'mixed content' => array( + 'content' => 'method(); + esc_html( "test" ); + ?>', + 'expected' => array( 'esc_html', 'wp_remote_get' ), + ), + 'function call with space' => array( + 'content' => '', + 'expected' => array( 'is_array' ), + ), + ); + } + + /** + * Tests the documentation_ignore_functions filter. + * + * @ticket 65182 + */ + public function test_wp_doc_link_parse_filter() { + $filter = function( $ignore ) { + $ignore[] = 'wp_remote_get'; + return $ignore; + }; + + add_filter( 'documentation_ignore_functions', $filter ); + $result = wp_doc_link_parse( '' ); + remove_filter( 'documentation_ignore_functions', $filter ); + + $this->assertSame( array( 'esc_html' ), $result ); + } +} From 8da45c66b60ddf377c889ec27ef9da773327dd8d Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Wed, 6 May 2026 16:15:36 -0400 Subject: [PATCH 2/5] Add unit tests for set_screen_options() in wp-admin/includes/misc.php --- .../admin/includes/misc/setScreenOptions.php | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 tests/phpunit/tests/admin/includes/misc/setScreenOptions.php diff --git a/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php b/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php new file mode 100644 index 0000000000000..94429df59a915 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php @@ -0,0 +1,181 @@ +assertNull( $result, 'set_screen_options should return null when wp_screen_options is not set in $_POST.' ); + } + + /** + * @ticket 65183 + * @dataProvider data_set_screen_options + * + * Tests saving various screen options, including those that require filters. + * + * @param string $option The option name. + * @param mixed $value The option value. + * @param mixed $expected The expected stored value in user meta. + */ + public function test_set_screen_options( $option, $value, $expected ) { + $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user_id ); + + $_POST['wp_screen_options'] = array( + 'option' => $option, + 'value' => $value, + ); + + // Mock request method. + $_SERVER['REQUEST_METHOD'] = 'POST'; + + // Set referer and nonce. + $_SERVER['HTTP_REFERER'] = admin_url( 'edit.php?pagenum=2' ); + $_POST['screenoptionnonce'] = wp_create_nonce( 'screen-options-nonce' ); + $_REQUEST['screenoptionnonce'] = $_POST['screenoptionnonce']; + $_REQUEST['_wp_http_referer'] = $_SERVER['HTTP_REFERER']; + + if ( 'my_custom_page' === $option || 'layout_columns' === $option ) { + add_filter( 'set-screen-option', array( $this, 'filter_set_screen_option_custom' ), 10, 3 ); + } + + // Intercept redirect to prevent exit. + add_filter( 'wp_redirect', array( $this, 'intercept_redirect' ) ); + + // Bypass check_admin_referer() failure by making it pass. + add_filter( 'check_admin_referer', '__return_null' ); + + // Bypass die() in check_admin_referer() by using a die handler that doesn't die. + add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) ); + + try { + set_screen_options(); + } catch ( Exception $e ) { + if ( 'Redirected' === $e->getMessage() ) { + // Success! + } else { + $this->fail( 'The function failed with message: ' . $e->getMessage() ); + } + } + + $meta = get_user_meta( $user_id, $option, true ); + $this->assertEquals( $expected, $meta ); + } + + public function wp_die_handler( $message = '', $title = '', $args = array() ) { + return array( $this, 'die_handler' ); + } + + public function die_handler( $message ) { + throw new Exception( $message ); + } + + public function intercept_redirect( $location ) { + throw new Exception( 'Redirected' ); + } + + public function filter_set_screen_option_custom( $status, $option, $value ) { + if ( 'my_custom_page' === $option || 'layout_columns' === $option ) { + return $value; + } + return $status; + } + + /** + * Data provider for test_set_screen_options. + * + * @return array + */ + public function data_set_screen_options(): array { + return array( + 'edit_per_page' => array( + 'option' => 'edit_per_page', + 'value' => '20', + 'expected' => 20, + ), + 'edit_per_page_invalid' => array( + 'option' => 'edit_per_page', + 'value' => '1000', // Max is 999. + 'expected' => '', + ), + 'upload_per_page' => array( + 'option' => 'upload_per_page', + 'value' => '50', + 'expected' => 50, + ), + 'custom_option_with_filter' => array( + 'option' => 'my_custom_page', + 'value' => '10', + 'expected' => 10, + ), + 'layout_columns' => array( + 'option' => 'layout_columns', + 'value' => '2', + 'expected' => 2, + ), + ); + } + + /** + * @ticket 65183 + * + * Tests that set-screen-option filter is correctly applied for custom options. + */ + public function test_set_screen_options_with_filters() { + $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user_id ); + + $_POST['wp_screen_options'] = array( + 'option' => 'custom_per_page', + 'value' => '25', + ); + $_SERVER['HTTP_REFERER'] = admin_url( 'admin.php' ); + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_REQUEST['screenoptionnonce'] = wp_create_nonce( 'screen-options-nonce' ); + $_REQUEST['_wp_http_referer'] = $_SERVER['HTTP_REFERER']; + + // Intercept redirect to prevent exit. + add_filter( 'wp_redirect', array( $this, 'intercept_redirect' ) ); + + // Bypass check_admin_referer() failure. + add_filter( 'check_admin_referer', '__return_null' ); + + // Bypass die() in check_admin_referer() by using a die handler that doesn't die. + add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) ); + + // Filter ending with _page. + add_filter( 'set-screen-option', array( $this, 'filter_set_screen_option' ), 10, 3 ); + + try { + set_screen_options(); + } catch ( Exception $e ) { + if ( 'Redirected' === $e->getMessage() ) { + // Success! + } else { + $this->fail( 'The function failed with message: ' . $e->getMessage() ); + } + } + + $this->assertEquals( 25, get_user_meta( $user_id, 'custom_per_page', true ) ); + } + + public function filter_set_screen_option( $status, $option, $value ) { + if ( 'custom_per_page' === $option ) { + return $value; + } + return $status; + } +} From df2cdd0405a90145443dcc5d53c45e5d072e6c4c Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Wed, 6 May 2026 16:19:11 -0400 Subject: [PATCH 3/5] Delete tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php --- .../admin/includes/misc/wpDocLinkParse.php | 104 ------------------ 1 file changed, 104 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php diff --git a/tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php b/tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php deleted file mode 100644 index 6e0cb65568cae..0000000000000 --- a/tests/phpunit/tests/admin/includes/misc/wpDocLinkParse.php +++ /dev/null @@ -1,104 +0,0 @@ -assertSame( $expected, wp_doc_link_parse( $content ) ); - } - - /** - * Data provider for test_wp_doc_link_parse. - * - * @return array - */ - public function data_wp_doc_link_parse(): array { - return array( - 'empty string' => array( - 'content' => '', - 'expected' => array(), - ), - 'null (invalid type)' => array( - 'content' => null, - 'expected' => array(), - ), - 'simple function call' => array( - 'content' => '', - 'expected' => array( 'get_header' ), - ), - 'multiple unique functions' => array( - 'content' => '', - 'expected' => array( 'get_header', 'wp_footer' ), - ), - 'duplicate functions' => array( - 'content' => '', - 'expected' => array( '_e' ), - ), - 'sorted output' => array( - 'content' => '', - 'expected' => array( 'alpha', 'zeta' ), - ), - 'local function definition' => array( - 'content' => '', - 'expected' => array(), - ), - 'class method call' => array( - 'content' => 'my_method(); ?>', - 'expected' => array(), - ), - 'static class method call' => array( - 'content' => '', - 'expected' => array( 'my_static_method' ), // token_get_all handles :: differently. - ), - 'mixed content' => array( - 'content' => 'method(); - esc_html( "test" ); - ?>', - 'expected' => array( 'esc_html', 'wp_remote_get' ), - ), - 'function call with space' => array( - 'content' => '', - 'expected' => array( 'is_array' ), - ), - ); - } - - /** - * Tests the documentation_ignore_functions filter. - * - * @ticket 65182 - */ - public function test_wp_doc_link_parse_filter() { - $filter = function( $ignore ) { - $ignore[] = 'wp_remote_get'; - return $ignore; - }; - - add_filter( 'documentation_ignore_functions', $filter ); - $result = wp_doc_link_parse( '' ); - remove_filter( 'documentation_ignore_functions', $filter ); - - $this->assertSame( array( 'esc_html' ), $result ); - } -} From 05566f2a512f881c9871bb868684f0cfdc673827 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Wed, 6 May 2026 16:22:43 -0400 Subject: [PATCH 4/5] Fix formatting of HTTP referer and request method --- .../admin/includes/misc/setScreenOptions.php | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php b/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php index 94429df59a915..b869627f8582c 100644 --- a/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php +++ b/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php @@ -39,10 +39,10 @@ public function test_set_screen_options( $option, $value, $expected ) { $_SERVER['REQUEST_METHOD'] = 'POST'; // Set referer and nonce. - $_SERVER['HTTP_REFERER'] = admin_url( 'edit.php?pagenum=2' ); - $_POST['screenoptionnonce'] = wp_create_nonce( 'screen-options-nonce' ); + $_SERVER['HTTP_REFERER'] = admin_url( 'edit.php?pagenum=2' ); + $_POST['screenoptionnonce'] = wp_create_nonce( 'screen-options-nonce' ); $_REQUEST['screenoptionnonce'] = $_POST['screenoptionnonce']; - $_REQUEST['_wp_http_referer'] = $_SERVER['HTTP_REFERER']; + $_REQUEST['_wp_http_referer'] = $_SERVER['HTTP_REFERER']; if ( 'my_custom_page' === $option || 'layout_columns' === $option ) { add_filter( 'set-screen-option', array( $this, 'filter_set_screen_option_custom' ), 10, 3 ); @@ -101,27 +101,27 @@ public function filter_set_screen_option_custom( $status, $option, $value ) { */ public function data_set_screen_options(): array { return array( - 'edit_per_page' => array( + 'edit_per_page' => array( 'option' => 'edit_per_page', 'value' => '20', 'expected' => 20, ), - 'edit_per_page_invalid' => array( + 'edit_per_page_invalid' => array( 'option' => 'edit_per_page', 'value' => '1000', // Max is 999. 'expected' => '', ), - 'upload_per_page' => array( + 'upload_per_page' => array( 'option' => 'upload_per_page', 'value' => '50', 'expected' => 50, ), - 'custom_option_with_filter' => array( + 'custom_option_with_filter' => array( 'option' => 'my_custom_page', 'value' => '10', 'expected' => 10, ), - 'layout_columns' => array( + 'layout_columns' => array( 'option' => 'layout_columns', 'value' => '2', 'expected' => 2, @@ -142,10 +142,10 @@ public function test_set_screen_options_with_filters() { 'option' => 'custom_per_page', 'value' => '25', ); - $_SERVER['HTTP_REFERER'] = admin_url( 'admin.php' ); - $_SERVER['REQUEST_METHOD'] = 'POST'; + $_SERVER['HTTP_REFERER'] = admin_url( 'admin.php' ); + $_SERVER['REQUEST_METHOD'] = 'POST'; $_REQUEST['screenoptionnonce'] = wp_create_nonce( 'screen-options-nonce' ); - $_REQUEST['_wp_http_referer'] = $_SERVER['HTTP_REFERER']; + $_REQUEST['_wp_http_referer'] = $_SERVER['HTTP_REFERER']; // Intercept redirect to prevent exit. add_filter( 'wp_redirect', array( $this, 'intercept_redirect' ) ); From 1cf3a1a4ab67380d7a31a78bb733d313e07108fb Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Wed, 6 May 2026 16:25:48 -0400 Subject: [PATCH 5/5] Fix array assignment syntax in test_set_screen_options --- tests/phpunit/tests/admin/includes/misc/setScreenOptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php b/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php index b869627f8582c..111e75e1a675b 100644 --- a/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php +++ b/tests/phpunit/tests/admin/includes/misc/setScreenOptions.php @@ -138,7 +138,7 @@ public function test_set_screen_options_with_filters() { $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $user_id ); - $_POST['wp_screen_options'] = array( + $_POST['wp_screen_options'] = array( 'option' => 'custom_per_page', 'value' => '25', );