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
1 change: 1 addition & 0 deletions phpcs-rulesets/plugin-check.ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
<exclude name="WordPress.WP.AlternativeFunctions.json_encode_json_encode"/>
<exclude name="WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents"/>
<exclude name="WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents"/>
<exclude name="WordPress.WP.AlternativeFunctions.file_system_operations_fclose"/>
</rule>

<rule ref="Generic.PHP.ForbiddenFunctions">
Expand Down
31 changes: 31 additions & 0 deletions test-fixtures-for-manual-testing/fclose-php-output-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* Plugin Name: Fclose PHP Output Test
* Description: A test plugin to verify the Plugin Check plugin's fclose() false positive fix — fclose(php://output) should not trigger an AlternativeFunctions sniff.
* Version: 1.0.0
* Requires PHP: 7.4
Comment on lines +2 to +6
* Author: Plugin Check QA
* Text Domain: fclose-php-output-test
*/

// This fclose() call on php://output is a common WordPress pattern
// used to end output buffering. It should NOT trigger the
// WordPress.WP.AlternativeFunctions.file_system_operations_fclose sniff
// because php://output is not a file system resource.
Comment on lines +11 to +14
function send_csv_download() {
$handle = fopen( 'php://output', 'w' );
fwrite( $handle, 'name,email' . PHP_EOL );
fwrite( $handle, 'John,john@example.com' . PHP_EOL );
fclose( $handle );
}

// This fclose() on a real file SHOULD still trigger the sniff.
function read_log_file() {
$handle = fopen( WP_CONTENT_DIR . '/debug.log', 'r' );
if ( $handle ) {
$contents = fread( $handle, 1024 );
fclose( $handle );
return $contents;
}
return '';
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

file_get_contents( $url );
file_put_contents();
fclose();

load_plugin_textdomain( 'sample-textdomain', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,30 @@ public function test_run_with_errors() {
// There should not be WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents error on Line no 39 and column no 1.
$this->assertCount( 0, wp_list_filter( $errors['load.php'][39][1], array( 'code' => 'WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents' ) ) );

// There should not be WordPress.WP.AlternativeFunctions.file_system_operations_fclose error on Line no 40 and column no 1.
$this->assertCount( 0, wp_list_filter( $errors['load.php'][40][1], array( 'code' => 'WordPress.WP.AlternativeFunctions.file_system_operations_fclose' ) ) );

// Check for existing forbidden functions.
$forbidden_found = 'Generic.PHP.ForbiddenFunctions.Found';

// Check for Generic.PHP.ForbiddenFunctions.create_functionFound error on Line no 44 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][44][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.evalFound error on Line no 45 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.create_functionFound error on Line no 45 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][45][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.move_uploaded_fileFound error on Line no 46 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.evalFound error on Line no 46 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][46][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.passthruFound error on Line no 47 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.move_uploaded_fileFound error on Line no 47 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][47][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.proc_openFound error on Line no 48 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.passthruFound error on Line no 48 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][48][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.str_rot13Found error on Line no 49 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.proc_openFound error on Line no 49 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][49][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.str_rot13Found error on Line no 50 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][50][1][0]['code'] );

// Check for WordPress.Security.ValidatedSanitizedInput warnings on Line no 15 and column no at 27.
$this->assertCount( 1, wp_list_filter( $warnings['load.php'][15][27], array( 'code' => 'WordPress.Security.ValidatedSanitizedInput.InputNotValidated' ) ) );
$this->assertCount( 1, wp_list_filter( $warnings['load.php'][15][27], array( 'code' => 'WordPress.Security.ValidatedSanitizedInput.MissingUnslash' ) ) );
Expand Down Expand Up @@ -110,40 +113,40 @@ public function test_run_with_errors() {
// Check for WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query warning on Line no 26 and column no at 1.
$this->assertSame( 'WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query', $warnings['load.php'][26][1][0]['code'] );

// Check for PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound warning on Line no 41 and column no at 1.
$this->assertSame( 'PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound', $warnings['load.php'][41][1][0]['code'] );
// Check for PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound warning on Line no 42 and column no at 1.
$this->assertSame( 'PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound', $warnings['load.php'][42][1][0]['code'] );

// Check for new forbidden functions
// Check for Generic.PHP.ForbiddenFunctions._cleanup_header_commentFound error on Line no 52 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][52][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions._get_plugin_data_markup_translateFound error on Line no 53 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions._cleanup_header_commentFound error on Line no 53 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][53][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions._transition_post_statusFound error on Line no 54 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions._get_plugin_data_markup_translateFound error on Line no 54 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][54][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions._wp_post_revision_fieldsFound error on Line no 55 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions._transition_post_statusFound error on Line no 55 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][55][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.do_shortcode_tagFound error on Line no 56 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions._wp_post_revision_fieldsFound error on Line no 56 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][56][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.get_post_type_labelsFound error on Line no 57 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.do_shortcode_tagFound error on Line no 57 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][57][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.wp_get_sidebars_widgetsFound error on Line no 58 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.get_post_type_labelsFound error on Line no 58 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][58][1][0]['code'] );

// Check for Generic.PHP.ForbiddenFunctions.wp_get_widget_defaultsFound error on Line no 59 and column no at 1.
// Check for Generic.PHP.ForbiddenFunctions.wp_get_sidebars_widgetsFound error on Line no 60 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][59][1][0]['code'] );

// Check for PluginCheck.CodeAnalysis.ShortURL.Found warning on Line no 69 and column no at 14.
$this->assertArrayHasKey( 69, $warnings['load.php'] );
$this->assertArrayHasKey( 14, $warnings['load.php'][69] );
$this->assertArrayHasKey( 'code', $warnings['load.php'][69][14][0] );
$this->assertEquals( 'PluginCheck.CodeAnalysis.ShortURL.Found', $warnings['load.php'][69][14][0]['code'] );
$this->assertSame( 6, $warnings['load.php'][69][14][0]['severity'] );
// Check for Generic.PHP.ForbiddenFunctions.wp_get_widget_defaultsFound error on Line no 60 and column no at 1.
$this->assertSame( $forbidden_found, $errors['load.php'][60][1][0]['code'] );

// Check for PluginCheck.CodeAnalysis.ShortURL.Found warning on Line no 70 and column no at 14.
$this->assertArrayHasKey( 70, $warnings['load.php'] );
$this->assertArrayHasKey( 14, $warnings['load.php'][70] );
$this->assertArrayHasKey( 'code', $warnings['load.php'][70][14][0] );
$this->assertEquals( 'PluginCheck.CodeAnalysis.ShortURL.Found', $warnings['load.php'][70][14][0]['code'] );
$this->assertSame( 6, $warnings['load.php'][70][14][0]['severity'] );
}

public function test_run_without_errors() {
Expand Down
Loading