From b0933b3da59ad747a230e110d4b3e4d0786455b8 Mon Sep 17 00:00:00 2001 From: jjgrainger Date: Tue, 14 Mar 2023 14:55:43 +0000 Subject: [PATCH 1/7] add checks to run endpoint --- includes/Admin/Admin_AJAX.php | 103 ++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/includes/Admin/Admin_AJAX.php b/includes/Admin/Admin_AJAX.php index 2fa7200ce..96bf4c0c2 100644 --- a/includes/Admin/Admin_AJAX.php +++ b/includes/Admin/Admin_AJAX.php @@ -8,7 +8,10 @@ namespace WordPress\Plugin_Check\Admin; use WP_Error; - +use Exception; +use WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Runtime_Check; +use WordPress\Plugin_Check\Utilities\Plugin_Request_Utility; /** * Class to handle the Admin AJAX requests. * @@ -30,6 +33,7 @@ class Admin_AJAX { * @since n.e.x.t */ public function add_hooks() { + add_action( 'wp_ajax_plugin_check_get_checks_to_run', array( $this, 'get_checks_to_run' ) ); add_action( 'wp_ajax_plugin_check_run_checks', array( $this, 'run_checks' ) ); } @@ -43,24 +47,111 @@ public function get_nonce() { } /** - * Runs checks. + * Handles the AJAX request that returns the checks to run. * * @since n.e.x.t */ - public function run_checks() { - $nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING ); + public function get_checks_to_run() { + // Verify the nonce before continuing. + $this->verify_nonce(); - if ( ! wp_verify_nonce( $nonce, self::NONCE_KEY ) ) { + $checks = filter_input( INPUT_POST, 'checks', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); + $plugin = filter_input( INPUT_POST, 'plugin', FILTER_SANITIZE_STRING ); + + // Attempt to get the plugin basename based on the request. + try { + $plugin_basename = Plugin_Request_Utility::get_plugin_basename_from_input( $plugin ); + } catch ( Exception $error ) { wp_send_json_error( - new WP_Error( 'invalid-nonce', __( 'Invalid nonce', 'plugin-check' ) ), + new WP_Error( 'invalid-plugin', $error->getMessage() ), 403 ); } + $plugin_active = is_plugin_active( $plugin_basename ); + + // Create the checks instance. + $checks_instance = new Checks( WP_PLUGIN_DIR . '/' . $plugin_basename ); + $all_checks = $checks_instance->get_checks(); + + // Filter checks to run based on the request. + $checks_to_run = empty( $checks ) ? $all_checks : array_intersect_key( $all_checks, array_flip( $checks ) ); + + // If the plugin is inactive and there are runtime checks. + if ( ! $plugin_active && $this->has_runtime_check( $checks_to_run ) ) { + // If specific checks were requested, return an error. + if ( ! empty( $checks ) ) { + wp_send_json_error( + new WP_Error( + __( 'Runtime checks cannot be run against inactive plugins.', 'plugin-check' ) + ) + ); + } + + // If all checks are requested, filter out any runtime checks. + $checks_to_run = array_filter( + $checks_to_run, + function ( $check ) { + return ! $check instanceof Runtime_Check; + } + ); + } + + wp_send_json_success( + array( + 'plugin' => $plugin, + 'checks' => array_keys( $checks_to_run ), + ) + ); + } + + /** + * Run checks. + * + * @since n.e.x.t + */ + public function run_checks() { + // Verify the nonce before continuing. + $this->verify_nonce(); + wp_send_json_success( array( 'message' => __( 'Verified!', 'plugin-check' ), ) ); } + + /** + * Verify the nonce passed in the request. + * + * @since n.e.x.t + */ + protected function verify_nonce() { + $nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING ); + + if ( ! wp_verify_nonce( $nonce, self::NONCE_KEY ) ) { + wp_send_json_error( + new WP_Error( 'invalid-nonce', __( 'Invalid nonce', 'plugin-check' ) ), + 403 + ); + } + } + + /** + * Check for a Runtime_Check in a list of checks + * + * @since n.e.x.t + * + * @param array $checks An array of Check instances. + * @return boolean True if a Runtime_Check exists in the array, false if not. + */ + protected function has_runtime_check( array $checks ) { + foreach ( $checks as $check ) { + if ( $check instanceof Runtime_Check ) { + return true; + } + } + + return false; + } } From 536a09ddbe7bddd01cc409ce1bb6586918fb1c0d Mon Sep 17 00:00:00 2001 From: jjgrainger Date: Tue, 14 Mar 2023 15:19:22 +0000 Subject: [PATCH 2/7] update js to fetch get_checks_to_run first --- assets/js/plugin-check-admin.js | 105 +++++++++++++++++++++++++++----- templates/admin-page.php | 2 +- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js index 2b76327a1..8b5aa5f36 100644 --- a/assets/js/plugin-check-admin.js +++ b/assets/js/plugin-check-admin.js @@ -8,15 +8,21 @@ return; } - checkItButton.addEventListener( 'click', (e) => { - e.preventDefault(); + // Create a state object for the plugin checker. + const pluginChecker = { + plugin: false, + checks_to_run: [], + }; - const pluginCheckData = new FormData(); + checkItButton.addEventListener( 'click', ( e ) => { + e.preventDefault(); // Collect the data to pass along for generating a check results. - pluginCheckData.append( 'action', 'plugin_check_run_checks' ); + const pluginCheckData = new FormData(); pluginCheckData.append( 'nonce', data.nonce ); pluginCheckData.append( 'plugin', pluginsList.value ); + pluginCheckData.append( 'checks', [] ); + pluginCheckData.append( 'action', 'plugin_check_get_checks_to_run' ); fetch( ajaxurl, @@ -33,33 +39,100 @@ ) .then( ( data ) => { - if ( ! data ) { - throw new Error( 'Response contains no data' ); + return handleDataErrors( data ); + } + ) + .then( + ( data ) => { + console.log( 'plugin_check_get_checks_to_run', data ); + + if ( ! data.data || ! data.data.plugin || ! data.data.checks ) { + throw new Error( 'Plugin and Checks are missing from the response.' ); } - if ( ! data.success ) { - // // If not successful and no message in the response. - if ( ! data.data || ! data.data[0].message ) { - throw new Error( 'Response contains no data' ); - } + // Store the plugin and checks to run to be used later. + pluginChecker.plugin = data.data.plugin; + pluginChecker.checks = data.data.checks; + + runChecks(); + } + ) + .catch( + ( error ) => { console.error( error ) } + ); + } ); - // If not successful and there is a message in the response. - throw new Error( data.data[0].message ); - } + /** + * Run Checks. + * + * @since n.e.x.t + */ + function runChecks() { + const pluginCheckData = new FormData(); + pluginCheckData.append( 'nonce', data.nonce ); + pluginCheckData.append( 'plugin', pluginChecker.plugin ); + pluginCheckData.append( 'checks', pluginChecker.checks ); + pluginCheckData.append( 'action', 'plugin_check_run_checks' ); + + fetch( + ajaxurl, + { + method: 'POST', + credentials: 'same-origin', + body: pluginCheckData + } + ) + .then( + ( response ) => { + return response.json(); + } + ) + .then( + ( data ) => { + return handleDataErrors(data); + } + ) + .then( + ( data ) => { // If the response is successful and there is no message in the response. if ( ! data.data || ! data.data.message ) { throw new Error( 'Response contains no data' ); } // If the response is successful and there is a message in the response. - console.log( data.data.message ); + console.log( 'plugin_check_run_checks', data.data.message ); } ) .catch( ( error ) => { console.error( error ); } ); + } - } ); + /** + * Handles any errors in the data returned from the response. + * + * @since n.e.x.t + * + * @param {Object} data The response data. + * @return {Object} The response data. + */ + function handleDataErrors( data ) { + if ( ! data ) { + throw new Error( 'Response contains no data' ); + } + + if ( ! data.success ) { + // If not successful and no message in the response. + if ( ! data.data || ! data.data[0].message ) { + throw new Error( 'Response contains no data' ); + } + + // If not successful and there is a message in the response. + throw new Error( data.data[0].message ); + } + + return data; + } } )( PLUGIN_CHECK ); /* global PLUGIN_CHECK */ diff --git a/templates/admin-page.php b/templates/admin-page.php index f57a090a6..c35098ee3 100644 --- a/templates/admin-page.php +++ b/templates/admin-page.php @@ -23,7 +23,7 @@