diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js index 2b76327a1..22ac0bdd1 100644 --- a/assets/js/plugin-check-admin.js +++ b/assets/js/plugin-check-admin.js @@ -1,4 +1,4 @@ -( function ( data ) { +( function ( pluginCheck ) { const checkItButton = document.getElementById( 'plugin-check__submit' ); const pluginsList = document.getElementById( 'plugin-check__plugins-dropdown' ); @@ -8,17 +8,37 @@ return; } - checkItButton.addEventListener( 'click', (e) => { + checkItButton.addEventListener( 'click', ( e ) => { e.preventDefault(); - const pluginCheckData = new FormData(); + getChecksToRun() + .then( runChecks ) + .then( + ( data ) => { + console.log( data.message ); + } + ) + .catch( + ( error ) => { + console.error( error ); + } + ); + } ); + /** + * Get the Checks to run. + * + * @since n.e.x.t + */ + function getChecksToRun() { // Collect the data to pass along for generating a check results. - pluginCheckData.append( 'action', 'plugin_check_run_checks' ); - pluginCheckData.append( 'nonce', data.nonce ); + const pluginCheckData = new FormData(); + pluginCheckData.append( 'nonce', pluginCheck.nonce ); pluginCheckData.append( 'plugin', pluginsList.value ); + pluginCheckData.append( 'checks', [] ); + pluginCheckData.append( 'action', 'plugin_check_get_checks_to_run' ); - fetch( + return fetch( ajaxurl, { method: 'POST', @@ -31,35 +51,81 @@ return response.json(); } ) + .then( handleDataErrors ) .then( ( data ) => { - if ( ! data ) { - throw new Error( 'Response contains no 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' ); - } + return data.data; + } + ); + } - // 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( data ) { + const pluginCheckData = new FormData(); + pluginCheckData.append( 'nonce', pluginCheck.nonce ); + pluginCheckData.append( 'plugin', data.plugin ); + pluginCheckData.append( 'checks', data.checks ); + pluginCheckData.append( 'action', 'plugin_check_run_checks' ); + + return fetch( + ajaxurl, + { + method: 'POST', + credentials: 'same-origin', + body: pluginCheckData + } + ) + .then( + ( response ) => { + return response.json(); + } + ) + .then( handleDataErrors ) + .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 ); + return data.data; } - ) - .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/includes/Admin/Admin_AJAX.php b/includes/Admin/Admin_AJAX.php index 2fa7200ce..a7c468d4d 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,126 @@ 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. + $valid_nonce = $this->verify_nonce( filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING ) ); - if ( ! wp_verify_nonce( $nonce, self::NONCE_KEY ) ) { + if ( is_wp_error( $valid_nonce ) ) { + wp_send_json_error( $valid_nonce, 403 ); + } + + $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(); + + // If specific checks are requested to run. + if ( ! empty( $checks ) ) { + // Get the check instances based on the requested checks. + $checks_to_run = array_intersect_key( $all_checks, array_flip( $checks ) ); + + // Return an error if at least 1 runtime check is requested to run against an inactive plugin. + if ( ! $plugin_active && $this->has_runtime_check( $checks_to_run ) ) { + wp_send_json_error( + new WP_Error( + 'inactive-plugin', + __( 'Runtime checks cannot be run against inactive plugins.', 'plugin-check' ) + ), + 403 + ); + } + } else { + // Run all checks for the plugin. + $checks_to_run = $all_checks; + + // Only run static checks if the plugin is inactive. + if ( ! $plugin_active ) { + $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. + $valid_nonce = $this->verify_nonce( filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING ) ); + + if ( is_wp_error( $valid_nonce ) ) { + wp_send_json_error( $valid_nonce, 403 ); + } + wp_send_json_success( array( 'message' => __( 'Verified!', 'plugin-check' ), ) ); } + + /** + * Verify the nonce passed in the request. + * + * @since n.e.x.t + * + * @param string $nonce The request nonce passed. + * @return bool|WP_Error True if the nonce is valid. WP_Error if invalid. + */ + protected function verify_nonce( $nonce ) { + if ( ! wp_verify_nonce( $nonce, self::NONCE_KEY ) ) { + new WP_Error( 'invalid-nonce', __( 'Invalid nonce', 'plugin-check' ) ); + } + + return true; + } + + /** + * Check for a Runtime_Check in a list of checks + * + * @since n.e.x.t + * + * @param array $checks An array of Check instances. + * @return bool 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; + } } 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 @@