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
42 changes: 34 additions & 8 deletions assets/js/plugin-check-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
'plugin-check__include-experimental'
);
const useAi = document.getElementById( 'plugin-check__use-ai' );
const usePcpignore = document.getElementById(
'plugin-check__use-pcpignore'
);

// Handle disabling the Check it button when a plugin is not selected.
function canRunChecks() {
Expand Down Expand Up @@ -141,6 +144,9 @@
if ( useAi ) {
useAi.disabled = true;
}
if ( usePcpignore ) {
usePcpignore.disabled = true;
}
if ( includeExperimental ) {
includeExperimental.disabled = true;
}
Expand All @@ -149,6 +155,8 @@
const categories = getSelectedValues( categoriesList );
const types = getSelectedValues( typesList );
const useAiChecked = useAi && useAi.checked ? 1 : 0;
const usePcpignoreChecked =
usePcpignore && usePcpignore.checked ? 1 : 0;
const includeExperimentalChecked =
includeExperimental && includeExperimental.checked ? 1 : 0;
let currentChecks;
Expand All @@ -157,15 +165,17 @@
plugin,
categories,
includeExperimentalChecked,
useAiChecked
useAiChecked,
usePcpignoreChecked
)
.then( ( data ) => {
currentChecks = data.checks;
return setUpEnvironment(
plugin,
currentChecks,
includeExperimentalChecked,
useAiChecked
useAiChecked,
usePcpignoreChecked
);
} )
.then( () =>
Expand All @@ -174,7 +184,8 @@
currentChecks,
types,
includeExperimentalChecked,
useAiChecked
useAiChecked,
usePcpignoreChecked
)
)
.then( () => cleanUpEnvironment() )
Expand Down Expand Up @@ -224,6 +235,9 @@
if ( useAi ) {
useAi.disabled = false;
}
if ( usePcpignore ) {
usePcpignore.disabled = false;
}
if ( includeExperimental ) {
includeExperimental.disabled = false;
}
Expand Down Expand Up @@ -639,13 +653,15 @@
* @param {Array} checks Check slugs that will run.
* @param {number} includeExperimentalInput Whether to include experimental checks.
* @param {number} useAiInput Whether to enable AI analysis.
* @param {number} usePcpignoreInput Whether to apply .pcpignore exclusions.
* @return {Promise<Object>} Resolves with the response message.
*/
function setUpEnvironment(
plugin,
checks,
includeExperimentalInput,
useAiInput
useAiInput,
usePcpignoreInput
) {
const pluginCheckData = new FormData();
pluginCheckData.append( 'plugin', plugin );
Expand All @@ -658,6 +674,7 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
pluginCheckData.append( 'use-pcpignore', usePcpignoreInput );

for ( let i = 0; i < checks.length; i++ ) {
pluginCheckData.append( 'checks[]', checks[ i ] );
Expand Down Expand Up @@ -706,13 +723,15 @@
* @param {Array} categories Selected category slugs.
* @param {number} includeExperimentalInput Whether to include experimental checks.
* @param {number} useAiInput Whether to enable AI analysis.
* @param {number} usePcpignoreInput Whether to apply .pcpignore exclusions.
* @return {Promise<Object>} Resolves with the response containing plugin and checks.
*/
function getChecksToRun(
plugin,
categories,
includeExperimentalInput,
useAiInput
useAiInput,
usePcpignoreInput
) {
const pluginCheckData = new FormData();
pluginCheckData.append( 'plugin', plugin );
Expand All @@ -722,6 +741,7 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
pluginCheckData.append( 'use-pcpignore', usePcpignoreInput );

for ( let i = 0; i < categories.length; i++ ) {
pluginCheckData.append( 'categories[]', categories[ i ] );
Expand All @@ -746,13 +766,15 @@
* @param {Array} types Result types to include (error, warning).
* @param {number} includeExperimentalInput Whether to include experimental checks.
* @param {number} useAiInput Whether to enable AI analysis.
* @param {number} usePcpignoreInput Whether to apply .pcpignore exclusions.
*/
async function runChecks(
plugin,
checks,
types,
includeExperimentalInput,
useAiInput
useAiInput,
usePcpignoreInput
) {
let isSuccessMessage = true;
let aiStats = null;
Expand All @@ -763,7 +785,8 @@
checks[ i ],
types,
includeExperimentalInput,
useAiInput
useAiInput,
usePcpignoreInput
);
const splitResults = splitResultsByFalsePositive( results );
const errorsLength = countResultTree(
Expand Down Expand Up @@ -1009,14 +1032,16 @@
* @param {Array} types Result types to include (error, warning).
* @param {number} includeExperimentalInput Whether to include experimental checks.
* @param {number} useAiInput Whether to enable AI analysis.
* @param {number} usePcpignoreInput Whether to apply .pcpignore exclusions.
* @return {Promise<Object>} The check results.
*/
function runCheck(
plugin,
check,
types,
includeExperimentalInput,
useAiInput
useAiInput,
usePcpignoreInput
) {
const pluginCheckData = new FormData();
pluginCheckData.append( 'plugin', plugin );
Expand All @@ -1027,6 +1052,7 @@
includeExperimentalInput
);
pluginCheckData.append( 'use-ai', useAiInput );
pluginCheckData.append( 'use-pcpignore', usePcpignoreInput );

for ( let i = 0; i < types.length; i++ ) {
pluginCheckData.append( 'types[]', types[ i ] );
Expand Down
10 changes: 10 additions & 0 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ missing_composer_json_file; use `--ignore-codes` for specific result codes.
This only excludes files from file-based scans. It does not suppress plugin-level findings such as
missing_composer_json_file; use `--ignore-codes` for specific result codes.

[--use-pcpignore]
: Apply custom file and directory exclusions from a `.pcpignore` file in the plugin root.
Each non-empty, non-comment line is a path anchored to that root (not matched at any depth), so `docs/`
only excludes a top-level `docs` directory, not a `docs` directory nested elsewhere. A trailing slash
excludes a directory; all other entries exclude files. Entries may include `*` and `?` wildcards, e.g.
`*.map`. Single-file plugins are not supported, since they have no dedicated plugin directory to hold a
`.pcpignore` file. This option is disabled by default and is intended for local and CI scans.
WordPress.org scans must not use this option.

[--severity=<severity>]
: Severity level.

Expand Down Expand Up @@ -106,6 +115,7 @@ wp plugin check akismet --ignore-codes=missing_composer_json_file
wp plugin check akismet --format=json
wp plugin check akismet --format=ctrf
wp plugin check akismet --mode=update
wp plugin check akismet --use-pcpignore
wp plugin check akismet --ai
wp plugin check akismet --ai --ai-model=openai::gpt-4o
```
Expand Down
16 changes: 16 additions & 0 deletions includes/Admin/Admin_AJAX.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use WordPress\Plugin_Check\Checker\AJAX_Runner;
use WordPress\Plugin_Check\Checker\Runtime_Check;
use WordPress\Plugin_Check\Checker\Runtime_Environment_Setup;
use WordPress\Plugin_Check\Utilities\PCP_Ignore_Utility;
use WordPress\Plugin_Check\Utilities\Plugin_Request_Utility;
use WordPress\Plugin_Check\Utilities\Results_Exporter;
use WP_Error;
Expand Down Expand Up @@ -285,13 +286,24 @@ public function run_checks() {

$include_experimental = 1 === filter_input( INPUT_POST, 'include-experimental', FILTER_VALIDATE_INT );
$use_ai = 1 === filter_input( INPUT_POST, 'use-ai', FILTER_VALIDATE_INT );
$use_pcpignore = 1 === filter_input( INPUT_POST, 'use-pcpignore', FILTER_VALIDATE_INT );
$types = filter_input( INPUT_POST, 'types', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
$types = is_null( $types ) ? array( 'error', 'warning' ) : $types;

$pcpignore_warning = '';

try {
$runner->set_experimental_flag( $include_experimental );
$runner->set_check_slugs( $checks );
$runner->set_plugin( $plugin );
if ( $use_pcpignore ) {
$plugin_path = $runner->get_plugin_basename();
$plugin_path = is_dir( $plugin_path ) ? $plugin_path : WP_PLUGIN_DIR . '/' . $plugin_path;

PCP_Ignore_Utility::apply_exclusions( $plugin_path );

$pcpignore_warning = PCP_Ignore_Utility::get_warning();
}
$runner->set_use_ai( $use_ai );
$results = $runner->run();
} catch ( Exception $error ) {
Expand All @@ -303,6 +315,10 @@ public function run_checks() {

$response_data = $this->prepare_results_response( $results, $types );

if ( '' !== $pcpignore_warning ) {
$response_data['pcpignore_warning'] = $pcpignore_warning;
}

// Include AI analysis results if available.
$ai_analysis = $results->get_ai_analysis();
if ( ! empty( $ai_analysis ) ) {
Expand Down
18 changes: 18 additions & 0 deletions includes/CLI/Plugin_Check_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use WordPress\Plugin_Check\Checker\CLI_Runner;
use WordPress\Plugin_Check\Checker\Default_Check_Repository;
use WordPress\Plugin_Check\Plugin_Context;
use WordPress\Plugin_Check\Utilities\PCP_Ignore_Utility;
use WordPress\Plugin_Check\Utilities\Plugin_Request_Utility;
use WordPress\Plugin_Check\Utilities\Results_Exporter;
use WP_CLI;
Expand Down Expand Up @@ -124,6 +125,11 @@ public function __construct( Plugin_Context $plugin_context ) {
* This only excludes files from file-based scans. It does not suppress plugin-level findings such as
* missing_composer_json_file; use `--ignore-codes` for specific result codes.
*
* [--use-pcpignore]
* : Apply custom file and directory exclusions from a .pcpignore file in the plugin root.
* Entries are anchored to the plugin root and may include * and ? wildcards. Not supported for
* single-file plugins. This is intended for local and CI scans only and is disabled by default.
*
* [--severity=<severity>]
* : Severity level.
*
Expand Down Expand Up @@ -165,6 +171,7 @@ public function __construct( Plugin_Context $plugin_context ) {
* wp plugin check akismet --ignore-codes=missing_composer_json_file
* wp plugin check akismet --format=json
* wp plugin check akismet --mode=update
* wp plugin check akismet --use-pcpignore
* wp plugin check akismet --ai
* wp plugin check akismet --ai --ai-model=openai::gpt-4o
*
Expand Down Expand Up @@ -201,6 +208,7 @@ public function check( $args, $assoc_args ) {
'mode' => 'new',
'ai' => false,
'ai-model' => '',
'use-pcpignore' => false,
)
);

Expand Down Expand Up @@ -258,6 +266,16 @@ static function ( $dirs ) use ( $excluded_files ) {
$runner->set_experimental_flag( $options['include-experimental'] );
$runner->set_check_slugs( $checks );
$runner->set_plugin( $plugin );
if ( $options['use-pcpignore'] ) {
$plugin_path = $runner->get_plugin_basename();
$plugin_path = is_dir( $plugin_path ) ? $plugin_path : WP_PLUGIN_DIR . '/' . $plugin_path;

PCP_Ignore_Utility::apply_exclusions( $plugin_path );

if ( '' !== PCP_Ignore_Utility::get_warning() ) {
WP_CLI::warning( PCP_Ignore_Utility::get_warning() );
}
}
$runner->set_categories( $categories );
$runner->set_slug( $options['slug'] );
$runner->set_mode( $options['mode'] );
Expand Down
26 changes: 7 additions & 19 deletions includes/Checker/Checks/Abstract_File_Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ private static function get_files( Check_Context $plugin ) {
} else {
$iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $location ) );

$plugin_root = untrailingslashit( $location );
$directories_to_ignore = Plugin_Request_Utility::get_directories_to_ignore();

$files_to_ignore = Plugin_Request_Utility::get_files_to_ignore();
$files_to_ignore = Plugin_Request_Utility::get_files_to_ignore();

foreach ( $iterator as $file ) {
if ( ! $file->isFile() ) {
Expand All @@ -293,27 +293,15 @@ private static function get_files( Check_Context $plugin ) {

$file_path = wp_normalize_path( $file->getPathname() );

// Flag to check if the file should be included or not.
$include_file = true;

foreach ( $directories_to_ignore as $directory ) {
// Check if the current file belongs to the directory you want to ignore.
if ( false !== strpos( $file_path, '/' . $directory . '/' ) ) {
$include_file = false;
break; // Skip the file if it matches any ignored directory.
}
if ( Plugin_Request_Utility::is_file_in_ignored_directory( $file_path, $plugin_root, $directories_to_ignore ) ) {
continue;
}

foreach ( $files_to_ignore as $ignore_file ) {
if ( str_ends_with( $file_path, "/$ignore_file" ) ) {
$include_file = false;
break;
}
if ( Plugin_Request_Utility::is_file_ignored( $file_path, $plugin_root, $files_to_ignore ) ) {
continue;
}

if ( $include_file ) {
self::$file_list_cache[ $location ][] = $file_path;
}
self::$file_list_cache[ $location ][] = $file_path;
}
}

Expand Down
Loading
Loading