From 114a3328b3961c192feb847178d9069754d8f5a8 Mon Sep 17 00:00:00 2001 From: davidperezgar Date: Sat, 28 Sep 2024 11:20:47 +0200 Subject: [PATCH 1/3] initial files --- .../Checks/Plugin_Repo/Write_File_Check.php | 86 ++++++++++++++ includes/Checker/Default_Check_Repository.php | 1 + .../Sniffs/CodeAnalysis/WriteFileSniff.php | 110 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 includes/Checker/Checks/Plugin_Repo/Write_File_Check.php create mode 100644 phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php diff --git a/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php b/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php new file mode 100644 index 000000000..0cca04971 --- /dev/null +++ b/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php @@ -0,0 +1,86 @@ + 'php', + 'standard' => 'PluginCheck', + 'sniffs' => 'PluginCheck.CodeAnalysis.WriteFile', + ); + } + + /** + * Gets the description for the check. + * + * Every check must have a short description explaining what the check does. + * + * @since n.e.x.t. + * + * @return string Description. + */ + public function get_description(): string { + return __( 'Prevents using poorly folders while writing files.', 'plugin-check' ); + } + + /** + * Gets the documentation URL for the check. + * + * Every check must have a URL with further information about the check. + * + * @since n.e.x.t. + * + * @return string The documentation URL. + */ + public function get_documentation_url(): string { + return __( '#', 'plugin-check' ); + } +} diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php index 7552ae948..41e5cbee8 100644 --- a/includes/Checker/Default_Check_Repository.php +++ b/includes/Checker/Default_Check_Repository.php @@ -60,6 +60,7 @@ private function register_default_checks() { 'trademarks' => new Checks\Plugin_Repo\Trademarks_Check(), 'non_blocking_scripts' => new Checks\Performance\Non_Blocking_Scripts_Check(), 'offloading_files' => new Checks\Plugin_Repo\Offloading_Files_Check(), + 'write_file' => new Checks\Plugin_Repo\Write_File_Check(), ) ); diff --git a/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php b/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php new file mode 100644 index 000000000..e54f0e34f --- /dev/null +++ b/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php @@ -0,0 +1,110 @@ +tokens[ $stackPtr ]['content']; + + if ( \T_INLINE_HTML !== $this->tokens[ $stackPtr ]['code'] ) { + try { + $end_ptr = TextStrings::getEndOfCompleteTextString( $this->phpcsFile, $stackPtr ); + $content = TextStrings::getCompleteTextString( $this->phpcsFile, $stackPtr ); + } catch ( RuntimeException $e ) { + // Parse error/live coding. + return; + } + } + + if ( empty( trim( $content ) ) ) { + return; + } + + // Known offloading services. + $look_known_offloading_services = array( + ); + // [ 'fwrite', 'fputs', 'file_put_contents', 'copy', 'rename', 'copy_dir', 'move_dir' ] + + $pattern = '/(' . implode( '|', $look_known_offloading_services ) . ')/i'; + + $matches = array(); + if ( preg_match_all( $pattern, $content, $matches, PREG_OFFSET_CAPTURE ) > 0 ) { + foreach ( $matches[0] as $match ) { + $this->phpcsFile->addError( + 'WriteFile images, js, css, and other scripts to your servers or any remote service is disallowed.', + $this->find_token_in_multiline_string( $stackPtr, $content, $match[1] ), + 'OffloadedContent' + ); + } + return ( $end_ptr + 1 ); + } + + return ( $end_ptr + 1 ); + } + + /** + * Find the exact token on which the error should be reported for multi-line strings. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $content The complete, potentially multi-line, text string. + * @param int $match_offset The offset within the content at which the match was found. + * + * @return int The stack pointer to the token containing the start of the match. + */ + private function find_token_in_multiline_string( $stackPtr, $content, $match_offset ) { + $newline_count = 0; + if ( $match_offset > 0 ) { + $newline_count = substr_count( $content, "\n", 0, $match_offset ); + } + + // Account for heredoc/nowdoc text starting at the token *after* the opener. + if ( isset( Tokens::$heredocTokens[ $this->tokens[ $stackPtr ]['code'] ] ) === true ) { + ++$newline_count; + } + + return ( $stackPtr + $newline_count ); + } +} From fe3df472c367ccabf414bd753fa35e29948d6aa1 Mon Sep 17 00:00:00 2001 From: davidperezgar Date: Sat, 27 Dec 2025 10:18:49 +0100 Subject: [PATCH 2/3] check made --- .../Checks/Plugin_Repo/Write_File_Check.php | 4 +- .../Sniffs/CodeAnalysis/WriteFileSniff.php | 216 +++++++++++++----- .../Tests/CodeAnalysis/WriteFileUnitTest.inc | 33 +++ .../Tests/CodeAnalysis/WriteFileUnitTest.php | 64 ++++++ phpcs-sniffs/PluginCheck/ruleset.xml | 1 + .../load.php | 59 +++++ .../load.php | 75 ++++++ .../Checker/Checks/Write_File_Check_Tests.php | 82 +++++++ 8 files changed, 469 insertions(+), 65 deletions(-) create mode 100644 phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/WriteFileUnitTest.inc create mode 100644 phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/WriteFileUnitTest.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/load.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-write-file-without-errors/load.php create mode 100644 tests/phpunit/tests/Checker/Checks/Write_File_Check_Tests.php diff --git a/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php b/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php index 0cca04971..9a779230b 100644 --- a/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php +++ b/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php @@ -68,7 +68,7 @@ protected function get_args() { * @return string Description. */ public function get_description(): string { - return __( 'Prevents using poorly folders while writing files.', 'plugin-check' ); + return __( 'Detects if plugins save data in the plugin folder instead of using the uploads directory or database.', 'plugin-check' ); } /** @@ -81,6 +81,6 @@ public function get_description(): string { * @return string The documentation URL. */ public function get_documentation_url(): string { - return __( '#', 'plugin-check' ); + return __( 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#saving-data-in-the-plugin-folder', 'plugin-check' ); } } diff --git a/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php b/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php index e54f0e34f..d818372de 100644 --- a/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php +++ b/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/WriteFileSniff.php @@ -10,101 +10,191 @@ namespace PluginCheckCS\PluginCheck\Sniffs\CodeAnalysis; -use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Util\Tokens; -use PHPCSUtils\Tokens\Collections; -use PHPCSUtils\Utils\TextStrings; -use WordPressCS\WordPress\Sniff; +use PHPCSUtils\Utils\PassedParameters; +use WordPressCS\WordPress\AbstractFunctionParameterSniff; /** - * Verifies any images/styles/scripts are not loaded from external sources. + * Verifies plugins don't save data in the plugin folder. + * + * Plugin folders are deleted when upgraded, so using them to store any data is problematic. + * Plugins should use the uploads directory or the database instead. * * @link https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/ * * @since 1.1.0 */ -final class WriteFileSniff extends Sniff { +final class WriteFileSniff extends AbstractFunctionParameterSniff { /** - * Returns an array of tokens this test wants to listen for. + * The group name for this group of functions. + * + * @since 1.1.0 * - * @return array + * @var string */ - public function register() { - $targets = Collections::textStringStartTokens(); - $targets[] = \T_INLINE_HTML; + protected $group_name = 'file_write'; - return $targets; - } + /** + * List of file write functions that need to be checked. + * + * @since 1.1.0 + * + * @var array Key is function name, value irrelevant. + */ + protected $target_functions = array( + // File write functions - check first parameter (file path). + 'fwrite' => true, + 'fputs' => true, + 'file_put_contents' => true, + 'touch' => true, + // File copy/move functions - check second parameter (destination path). + 'copy' => true, + 'rename' => true, + 'copy_dir' => true, + 'move_dir' => true, + 'unzip_file' => true, + ); + + /** + * Parameter positions for each function. + * + * @var array + */ + private $param_positions = array( + 'fwrite' => 1, + 'fputs' => 1, + 'file_put_contents' => 1, + 'touch' => 1, + 'copy' => 2, + 'rename' => 2, + 'copy_dir' => 2, + 'move_dir' => 2, + 'unzip_file' => 2, + ); + + /** + * WordPress constants that indicate plugin directory usage. + * + * @var array + */ + private $plugin_constants = array( + 'WP_PLUGIN_DIR', + 'WP_PLUGIN_URL', + 'PLUGINDIR', + 'WPINC', + 'WP_CONTENT_DIR', + 'WP_CONTENT_URL', + ); + + /** + * WordPress functions that indicate plugin directory usage. + * + * @var array + */ + private $plugin_functions = array( + 'plugins_url', + 'plugin_dir_path', + 'plugin_dir_url', + ); + + /** + * WordPress functions that indicate safe directory usage (uploads, temp). + * + * @var array + */ + private $safe_functions = array( + 'wp_upload_dir', + 'wp_tempnam', + 'get_temp_dir', + ); /** - * Processes this test, when one of its tokens is encountered. + * Process the parameters of a matched function. * - * @param int $stackPtr The position of the current token in the stack. + * @since 1.1.0 * - * @return int|void Integer stack pointer to skip forward or void to continue - * normal file processing. + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched in lowercase. + * @param array $parameters Array with information about the parameters. + * + * @return void */ - public function process_token( $stackPtr ) { - $end_ptr = $stackPtr; - $content = $this->tokens[ $stackPtr ]['content']; - - if ( \T_INLINE_HTML !== $this->tokens[ $stackPtr ]['code'] ) { - try { - $end_ptr = TextStrings::getEndOfCompleteTextString( $this->phpcsFile, $stackPtr ); - $content = TextStrings::getCompleteTextString( $this->phpcsFile, $stackPtr ); - } catch ( RuntimeException $e ) { - // Parse error/live coding. + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $param_position = isset( $this->param_positions[ $matched_content ] ) ? $this->param_positions[ $matched_content ] : 1; + $path_param = PassedParameters::getParameterFromStack( $parameters, $param_position, array() ); + + if ( false === $path_param ) { + return; + } + + // Get the content of the parameter. + $path_content = ''; + for ( $i = $path_param['start']; $i <= $path_param['end']; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + $path_content .= $this->tokens[ $i ]['content']; + } + + // Check if the path uses safe functions (uploads directory or temp). + foreach ( $this->safe_functions as $safe_func ) { + if ( stripos( $path_content, $safe_func ) !== false ) { + // Safe function detected, no error needed. return; } } - if ( empty( trim( $content ) ) ) { - return; + // Check for plugin constants. + foreach ( $this->plugin_constants as $constant ) { + if ( stripos( $path_content, $constant ) !== false ) { + $this->add_error( $stackPtr, $matched_content, $path_param['start'], 'constant ' . $constant ); + return; + } } - // Known offloading services. - $look_known_offloading_services = array( - ); - // [ 'fwrite', 'fputs', 'file_put_contents', 'copy', 'rename', 'copy_dir', 'move_dir' ] - - $pattern = '/(' . implode( '|', $look_known_offloading_services ) . ')/i'; - - $matches = array(); - if ( preg_match_all( $pattern, $content, $matches, PREG_OFFSET_CAPTURE ) > 0 ) { - foreach ( $matches[0] as $match ) { - $this->phpcsFile->addError( - 'WriteFile images, js, css, and other scripts to your servers or any remote service is disallowed.', - $this->find_token_in_multiline_string( $stackPtr, $content, $match[1] ), - 'OffloadedContent' - ); + // Check for plugin functions. + foreach ( $this->plugin_functions as $func ) { + if ( stripos( $path_content, $func ) !== false ) { + $this->add_error( $stackPtr, $matched_content, $path_param['start'], 'function ' . $func . '()' ); + return; } - return ( $end_ptr + 1 ); } - return ( $end_ptr + 1 ); + // Check for __FILE__ or __DIR__ magic constants. + if ( stripos( $path_content, '__FILE__' ) !== false || stripos( $path_content, '__DIR__' ) !== false ) { + $this->add_error( $stackPtr, $matched_content, $path_param['start'], '__FILE__ or __DIR__ magic constant' ); + return; + } + + // Check for ABSPATH usage (could be writing to WordPress root or plugin folder). + if ( stripos( $path_content, 'ABSPATH' ) !== false ) { + $this->phpcsFile->addWarning( + 'Writing files using ABSPATH may be problematic. Consider using wp_upload_dir() instead if storing user data or generated files.', + $path_param['start'], + 'ABSPATHDetected' + ); + return; + } } /** - * Find the exact token on which the error should be reported for multi-line strings. + * Adds an error message for plugin directory write attempt. * - * @param int $stackPtr The position of the current token in the stack. - * @param string $content The complete, potentially multi-line, text string. - * @param int $match_offset The offset within the content at which the match was found. + * @param int $stackPtr The position of the function call. + * @param string $function_name The name of the function being called. + * @param int $error_ptr The position to report the error. + * @param string $indicator What indicated this is a plugin path. * - * @return int The stack pointer to the token containing the start of the match. + * @return void */ - private function find_token_in_multiline_string( $stackPtr, $content, $match_offset ) { - $newline_count = 0; - if ( $match_offset > 0 ) { - $newline_count = substr_count( $content, "\n", 0, $match_offset ); - } - - // Account for heredoc/nowdoc text starting at the token *after* the opener. - if ( isset( Tokens::$heredocTokens[ $this->tokens[ $stackPtr ]['code'] ] ) === true ) { - ++$newline_count; - } - - return ( $stackPtr + $newline_count ); + private function add_error( $stackPtr, $function_name, $error_ptr, $indicator ) { + $this->phpcsFile->addError( + 'Plugin folders are deleted when upgraded. Do not save data to the plugin folder using %s(). Detected usage of %s. Use wp_upload_dir() to get the uploads directory path or save to the database instead.', + $error_ptr, + 'PluginDirectoryWrite', + array( $function_name, $indicator ) + ); } } diff --git a/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/WriteFileUnitTest.inc b/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/WriteFileUnitTest.inc new file mode 100644 index 000000000..1d024aa56 --- /dev/null +++ b/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/WriteFileUnitTest.inc @@ -0,0 +1,33 @@ + => + */ + public function getErrorList() { + return array( + 5 => 1, // file_put_contents with __FILE__. + 8 => 1, // fwrite with __DIR__. + 11 => 1, // fputs with plugin_dir_path(). + 14 => 1, // copy with WP_PLUGIN_DIR. + 17 => 1, // rename with plugin_dir_path(). + 20 => 1, // touch with WP_CONTENT_DIR. + 23 => 1, // file_put_contents with plugins_url(). + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() { + return array(); + } + + /** + * Returns the fully qualified class name (FQCN) of the sniff. + * + * @return string The fully qualified class name of the sniff. + */ + protected function get_sniff_fqcn() { + return WriteFileSniff::class; + } + + /** + * Sets the parameters for the sniff. + * + * @throws \RuntimeException If unable to set the ruleset parameters required for the test. + * + * @param Sniff $sniff The sniff being tested. + */ + public function set_sniff_parameters( Sniff $sniff ) { + } +} + diff --git a/phpcs-sniffs/PluginCheck/ruleset.xml b/phpcs-sniffs/PluginCheck/ruleset.xml index 7b6cc0ee3..acdebd754 100644 --- a/phpcs-sniffs/PluginCheck/ruleset.xml +++ b/phpcs-sniffs/PluginCheck/ruleset.xml @@ -10,6 +10,7 @@ + diff --git a/tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/load.php new file mode 100644 index 000000000..5a41b740c --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/load.php @@ -0,0 +1,59 @@ + 'value' ) ); +} + diff --git a/tests/phpunit/tests/Checker/Checks/Write_File_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/Write_File_Check_Tests.php new file mode 100644 index 000000000..a8b88fd88 --- /dev/null +++ b/tests/phpunit/tests/Checker/Checks/Write_File_Check_Tests.php @@ -0,0 +1,82 @@ +run( $check_result ); + + $errors = $check_result->get_errors(); + $warnings = $check_result->get_warnings(); + + // Should have errors for plugin directory writes. + $this->assertNotEmpty( $errors ); + $this->assertArrayHasKey( 'load.php', $errors ); + + // Check for specific error codes. + $error_codes = array(); + foreach ( $errors['load.php'] as $line => $columns ) { + foreach ( $columns as $column => $messages ) { + foreach ( $messages as $message ) { + $error_codes[] = $message['code']; + } + } + } + + // Should detect PluginDirectoryWrite errors. + $this->assertContains( 'PluginDirectoryWrite', $error_codes ); + + // Should have at least 6 errors (one for each bad example). + $this->assertGreaterThanOrEqual( 6, $check_result->get_error_count() ); + + // Should have warnings for ABSPATH usage. + $this->assertNotEmpty( $warnings ); + $warning_codes = array(); + foreach ( $warnings['load.php'] as $line => $columns ) { + foreach ( $columns as $column => $messages ) { + foreach ( $messages as $message ) { + $warning_codes[] = $message['code']; + } + } + } + $this->assertContains( 'ABSPATHDetected', $warning_codes ); + } + + public function test_run_without_errors() { + $write_file_check = new Write_File_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-write-file-without-errors/load.php' ); + $check_result = new Check_Result( $check_context ); + + $write_file_check->run( $check_result ); + + $errors = $check_result->get_errors(); + + // Should have no errors when using wp_upload_dir() or temp directories. + $this->assertEmpty( $errors ); + $this->assertSame( 0, $check_result->get_error_count() ); + } + + public function test_get_description() { + $check = new Write_File_Check(); + $this->assertNotEmpty( $check->get_description() ); + } + + public function test_get_documentation_url() { + $check = new Write_File_Check(); + $url = $check->get_documentation_url(); + $this->assertNotEmpty( $url ); + $this->assertStringContainsString( 'developer.wordpress.org', $url ); + } +} From bc5801c733ce79dec01f0fc005bce73344a7d950 Mon Sep 17 00:00:00 2001 From: davidperezgar Date: Sat, 27 Dec 2025 10:44:45 +0100 Subject: [PATCH 3/3] accomplish tests --- composer.lock | 10 +++---- .../Checks/Plugin_Repo/Write_File_Check.php | 4 ++- .../file-operations.php | 24 +++++++++++++++++ .../load.php | 26 ++++++------------- .../Checker/Checks/Write_File_Check_Tests.php | 21 +++------------ 5 files changed, 44 insertions(+), 41 deletions(-) create mode 100644 tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/file-operations.php diff --git a/composer.lock b/composer.lock index f9980b211..79c822c8e 100644 --- a/composer.lock +++ b/composer.lock @@ -581,7 +581,7 @@ }, { "name": "plugin-check/phpcs-sniffs", - "version": "dev-1009-require-users-to-write-readmetxt-in-english", + "version": "dev-665-check-asks-users-to-editwrite-to-plugin-use-uploads-folder", "dist": { "type": "path", "url": "./phpcs-sniffs", @@ -7155,8 +7155,8 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "phpcompatibility/php-compatibility": 20, - "plugin-check/phpcs-sniffs": 20 + "plugin-check/phpcs-sniffs": 20, + "phpcompatibility/php-compatibility": 20 }, "prefer-stable": false, "prefer-lowest": false, @@ -7164,9 +7164,9 @@ "php": ">=7.4", "ext-json": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php b/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php index 9a779230b..6ceb1420f 100644 --- a/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php +++ b/includes/Checker/Checks/Plugin_Repo/Write_File_Check.php @@ -8,6 +8,7 @@ namespace WordPress\Plugin_Check\Checker\Checks\Plugin_Repo; use WordPress\Plugin_Check\Checker\Check_Categories; +use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Checks\Abstract_PHP_CodeSniffer_Check; use WordPress\Plugin_Check\Traits\Amend_Check_Result; use WordPress\Plugin_Check\Traits\Stable_Check; @@ -48,9 +49,10 @@ public function get_categories() { * * @since 1.0.0 * + * @param Check_Result $result The check result to amend. * @return array An associative array of PHPCS CLI arguments. */ - protected function get_args() { + protected function get_args( Check_Result $result ) { return array( 'extensions' => 'php', 'standard' => 'PluginCheck', diff --git a/tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/file-operations.php b/tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/file-operations.php new file mode 100644 index 000000000..1b6cf2a86 --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-write-file-with-errors/file-operations.php @@ -0,0 +1,24 @@ +run( $check_result ); - $errors = $check_result->get_errors(); - $warnings = $check_result->get_warnings(); + $errors = $check_result->get_errors(); // Should have errors for plugin directory writes. $this->assertNotEmpty( $errors ); - $this->assertArrayHasKey( 'load.php', $errors ); + $this->assertArrayHasKey( 'file-operations.php', $errors ); // Check for specific error codes. $error_codes = array(); - foreach ( $errors['load.php'] as $line => $columns ) { + foreach ( $errors['file-operations.php'] as $line => $columns ) { foreach ( $columns as $column => $messages ) { foreach ( $messages as $message ) { $error_codes[] = $message['code']; @@ -36,22 +35,10 @@ public function test_run_with_errors() { } // Should detect PluginDirectoryWrite errors. - $this->assertContains( 'PluginDirectoryWrite', $error_codes ); + $this->assertContains( 'PluginCheck.CodeAnalysis.WriteFile.PluginDirectoryWrite', $error_codes ); // Should have at least 6 errors (one for each bad example). $this->assertGreaterThanOrEqual( 6, $check_result->get_error_count() ); - - // Should have warnings for ABSPATH usage. - $this->assertNotEmpty( $warnings ); - $warning_codes = array(); - foreach ( $warnings['load.php'] as $line => $columns ) { - foreach ( $columns as $column => $messages ) { - foreach ( $messages as $message ) { - $warning_codes[] = $message['code']; - } - } - } - $this->assertContains( 'ABSPATHDetected', $warning_codes ); } public function test_run_without_errors() {