From 9c389220bacde36cb3b61c39d5b0dd96b0d6811a Mon Sep 17 00:00:00 2001 From: Faisal Ahammad Date: Sun, 26 Jul 2026 16:22:15 +0600 Subject: [PATCH 1/3] fix(admin): decouple AJAX calls and remove unnecessary return values Refactor the JavaScript AJAX chain so each step takes its own parameters instead of relying on raw response data from the previous step. Remove the now-unnecessary plugin and checks keys from the set_up_environment AJAX response, and drop the array return from configure_runner. --- assets/js/plugin-check-admin.js | 334 +++++++++++++++++--------------- includes/Admin/Admin_AJAX.php | 10 +- 2 files changed, 184 insertions(+), 160 deletions(-) diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js index 22227cc4e..80b6ccfad 100644 --- a/assets/js/plugin-check-admin.js +++ b/assets/js/plugin-check-admin.js @@ -31,6 +31,7 @@ let aggregatedResults = createEmptyAggregatedResults(); let falsePositiveResults = createEmptyAggregatedResults(); let checksCompleted = false; + let currentChecks = []; exportContainer.classList.add( 'is-hidden' ); exportContainer.addEventListener( 'click', onExportContainerClick ); @@ -48,6 +49,47 @@ } } + /** + * Gets the values of checked inputs from a list. + * + * @since 1.0.0 + * + * @param {NodeList} list Inputs to read from. + * @return {Array} Selected values. + */ + function getSelectedValues( list ) { + const values = []; + for ( let i = 0; i < list.length; i++ ) { + if ( list[ i ].checked ) { + values.push( list[ i ].value ); + } + } + return values; + } + + /** + * Posts FormData to the plugin's AJAX endpoint. + * + * Wraps fetch with the standard options (ajaxurl, POST, same-origin + * credentials, nonce in body) and parses the JSON response, surfacing + * server errors via handleDataErrors. + * + * @since 1.0.0 + * + * @param {FormData} formData Form data to send. Must include 'action' and 'nonce'. + * @return {Promise} Resolves with the response data on success. + */ + function fetchAJAX( formData ) { + return fetch( ajaxurl, { + method: 'POST', + credentials: 'same-origin', + body: formData, + } ) + .then( ( response ) => response.json() ) + .then( handleDataErrors ) + .then( ( responseData ) => responseData.data ); + } + // Run on page load to test if dropdown is auto populated. canRunChecks(); pluginsList.addEventListener( 'change', canRunChecks ); @@ -96,13 +138,39 @@ includeExperimental.disabled = true; } - getChecksToRun() - .then( setUpEnvironment ) - .then( runChecks ) - .then( cleanUpEnvironment ) + const plugin = pluginsList.value; + const categories = getSelectedValues( categoriesList ); + const types = getSelectedValues( typesList ); + const useAiChecked = useAi && useAi.checked ? 1 : 0; + const includeExperimentalChecked = + includeExperimental && includeExperimental.checked ? 1 : 0; + + getChecksToRun( + plugin, + categories, + includeExperimentalChecked, + useAiChecked + ) .then( ( data ) => { - console.log( data.message ); - + currentChecks = data.checks; + return setUpEnvironment( + plugin, + currentChecks, + includeExperimentalChecked, + useAiChecked + ); + } ) + .then( () => + runChecks( + plugin, + currentChecks, + types, + includeExperimentalChecked, + useAiChecked + ) + ) + .then( () => cleanUpEnvironment() ) + .then( () => { resetForm(); } ) .catch( ( error ) => { @@ -522,37 +590,12 @@ payload.append( 'plugin_label', getSelectedPluginLabel() ); payload.append( 'results', JSON.stringify( getExportResults() ) ); - return fetch( ajaxurl, { - method: 'POST', - credentials: 'same-origin', - body: payload, - } ) - .then( ( response ) => response.json() ) - .then( ( responseData ) => { - if ( ! responseData ) { - throw new Error( 'Response contains no data' ); - } - - if ( ! responseData.success ) { - const defaultExportErrorMessage = - defaultString( 'exportError' ); - let message = defaultExportErrorMessage; - if ( responseData.data && responseData.data.message ) { - message = responseData.data.message; - } - throw new Error( message ); - } - - if ( - ! responseData.data || - ! responseData.data.content || - ! responseData.data.filename - ) { - throw new Error( 'Export payload is incomplete' ); - } - - return responseData.data; - } ); + return fetchAJAX( payload ).then( ( data ) => { + if ( ! data || ! data.content || ! data.filename ) { + throw new Error( 'Export payload is incomplete' ); + } + return data; + } ); } function downloadExport( exportPayload ) { @@ -581,44 +624,44 @@ * * @since 1.0.0 * - * @param {Object} data Data object with props passed to form data. + * @param {string} plugin Plugin basename to check. + * @param {Array} checks Check slugs that will run. + * @param {number} includeExperimentalInput Whether to include experimental checks. + * @param {number} useAiInput Whether to enable AI analysis. + * @return {Promise} Resolves with the response message. */ - function setUpEnvironment( data ) { + function setUpEnvironment( + plugin, + checks, + includeExperimentalInput, + useAiInput + ) { const pluginCheckData = new FormData(); pluginCheckData.append( 'nonce', pluginCheck.nonce ); - pluginCheckData.append( 'plugin', data.plugin ); + pluginCheckData.append( 'plugin', plugin ); pluginCheckData.append( 'action', pluginCheck.actionSetUpRuntimeEnvironment ); pluginCheckData.append( 'include-experimental', - includeExperimental && includeExperimental.checked ? 1 : 0 + includeExperimentalInput ); - pluginCheckData.append( 'use-ai', useAi && useAi.checked ? 1 : 0 ); + pluginCheckData.append( 'use-ai', useAiInput ); - for ( let i = 0; i < data.checks.length; i++ ) { - pluginCheckData.append( 'checks[]', data.checks[ i ] ); + for ( let i = 0; i < checks.length; i++ ) { + pluginCheckData.append( 'checks[]', checks[ i ] ); } - return fetch( ajaxurl, { - method: 'POST', - credentials: 'same-origin', - body: pluginCheckData, - } ) - .then( ( response ) => { - return response.json(); - } ) - .then( handleDataErrors ) - .then( ( responseData ) => { - if ( ! responseData.data || ! responseData.data.message ) { - throw new Error( 'Response contains no data.' ); - } + return fetchAJAX( pluginCheckData ).then( ( data ) => { + if ( ! data.message ) { + throw new Error( 'Response contains no data.' ); + } - console.log( responseData.data.message ); + console.log( data.message ); - return responseData.data; - } ); + return data; + } ); } /** @@ -626,7 +669,7 @@ * * @since 1.0.0 * - * @return {Object} The response data. + * @return {Promise} Resolves with the response message. */ function cleanUpEnvironment() { const pluginCheckData = new FormData(); @@ -636,71 +679,53 @@ pluginCheck.actionCleanUpRuntimeEnvironment ); - return fetch( ajaxurl, { - method: 'POST', - credentials: 'same-origin', - body: pluginCheckData, - } ) - .then( ( response ) => { - return response.json(); - } ) - .then( handleDataErrors ) - .then( ( responseData ) => { - if ( ! responseData.data || ! responseData.data.message ) { - throw new Error( 'Response contains no data.' ); - } + return fetchAJAX( pluginCheckData ).then( ( data ) => { + if ( ! data.message ) { + throw new Error( 'Response contains no data.' ); + } - return responseData.data; - } ); + return data; + } ); } /** * Get the Checks to run. * * @since 1.0.0 + * + * @param {string} plugin Plugin basename to evaluate. + * @param {Array} categories Selected category slugs. + * @param {number} includeExperimentalInput Whether to include experimental checks. + * @param {number} useAiInput Whether to enable AI analysis. + * @return {Promise} Resolves with the response containing plugin and checks. */ - function getChecksToRun() { + function getChecksToRun( + plugin, + categories, + includeExperimentalInput, + useAiInput + ) { const pluginCheckData = new FormData(); pluginCheckData.append( 'nonce', pluginCheck.nonce ); - pluginCheckData.append( 'plugin', pluginsList.value ); + pluginCheckData.append( 'plugin', plugin ); pluginCheckData.append( 'action', pluginCheck.actionGetChecksToRun ); pluginCheckData.append( 'include-experimental', - includeExperimental && includeExperimental.checked ? 1 : 0 + includeExperimentalInput ); - pluginCheckData.append( 'use-ai', useAi && useAi.checked ? 1 : 0 ); + pluginCheckData.append( 'use-ai', useAiInput ); - for ( let i = 0; i < categoriesList.length; i++ ) { - if ( categoriesList[ i ].checked ) { - pluginCheckData.append( - 'categories[]', - categoriesList[ i ].value - ); - } + for ( let i = 0; i < categories.length; i++ ) { + pluginCheckData.append( 'categories[]', categories[ i ] ); } - return fetch( ajaxurl, { - method: 'POST', - credentials: 'same-origin', - body: pluginCheckData, - } ) - .then( ( response ) => { - return response.json(); - } ) - .then( handleDataErrors ) - .then( ( responseData ) => { - if ( - ! responseData.data || - ! responseData.data.plugin || - ! responseData.data.checks - ) { - throw new Error( - 'Plugin and Checks are missing from the response.' - ); - } + return fetchAJAX( pluginCheckData ).then( ( data ) => { + if ( ! data.checks ) { + throw new Error( 'Checks are missing from the response.' ); + } - return responseData.data; - } ); + return data; + } ); } /** @@ -708,14 +733,30 @@ * * @since 1.0.0 * - * @param {Object} data The response data. + * @param {string} plugin Plugin basename to check. + * @param {Array} checks Check slugs to run. + * @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. */ - async function runChecks( data ) { + async function runChecks( + plugin, + checks, + types, + includeExperimentalInput, + useAiInput + ) { let isSuccessMessage = true; let aiStats = null; - for ( let i = 0; i < data.checks.length; i++ ) { + for ( let i = 0; i < checks.length; i++ ) { try { - const results = await runCheck( data.plugin, data.checks[ i ] ); + const results = await runCheck( + plugin, + checks[ i ], + types, + includeExperimentalInput, + useAiInput + ); const splitResults = splitResultsByFalsePositive( results ); const errorsLength = countResultTree( splitResults.actionable.errors @@ -942,11 +983,20 @@ * * @since 1.0.0 * - * @param {string} plugin The plugin to check. - * @param {string} check The check to run. - * @return {Object} The check results. + * @param {string} plugin The plugin to check. + * @param {string} check The check to run. + * @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. + * @return {Promise} The check results. */ - function runCheck( plugin, check ) { + function runCheck( + plugin, + check, + types, + includeExperimentalInput, + useAiInput + ) { const pluginCheckData = new FormData(); pluginCheckData.append( 'nonce', pluginCheck.nonce ); pluginCheckData.append( 'plugin', plugin ); @@ -954,47 +1004,29 @@ pluginCheckData.append( 'action', pluginCheck.actionRunChecks ); pluginCheckData.append( 'include-experimental', - includeExperimental && includeExperimental.checked ? 1 : 0 + includeExperimentalInput ); - pluginCheckData.append( 'use-ai', useAi && useAi.checked ? 1 : 0 ); + pluginCheckData.append( 'use-ai', useAiInput ); - for ( let i = 0; i < typesList.length; i++ ) { - if ( typesList[ i ].checked ) { - pluginCheckData.append( 'types[]', typesList[ i ].value ); - } + for ( let i = 0; i < types.length; i++ ) { + pluginCheckData.append( 'types[]', types[ i ] ); } - return fetch( ajaxurl, { - method: 'POST', - credentials: 'same-origin', - body: pluginCheckData, - } ) - .then( ( response ) => { - return response.json(); - } ) - .then( handleDataErrors ) - .then( ( responseData ) => { - // If the response is successful and there is no message in the response. - if ( ! responseData.data || ! responseData.data.message ) { - throw new Error( 'Response contains no data' ); - } + return fetchAJAX( pluginCheckData ).then( ( data ) => { + if ( ! data.message ) { + throw new Error( 'Response contains no data' ); + } - // Debug: Log AI data if present. - if ( responseData.data.ai_analysis ) { - console.log( - 'AI Analysis received:', - responseData.data.ai_analysis - ); - } - if ( responseData.data.ai_stats ) { - console.log( - 'AI Stats received:', - responseData.data.ai_stats - ); - } + // Debug: Log AI data if present. + if ( data.ai_analysis ) { + console.log( 'AI Analysis received:', data.ai_analysis ); + } + if ( data.ai_stats ) { + console.log( 'AI Stats received:', data.ai_stats ); + } - return responseData.data; - } ); + return data; + } ); } /** diff --git a/includes/Admin/Admin_AJAX.php b/includes/Admin/Admin_AJAX.php index eef695078..c1bc52b2f 100644 --- a/includes/Admin/Admin_AJAX.php +++ b/includes/Admin/Admin_AJAX.php @@ -113,7 +113,6 @@ private function check_request_validity() { * @since 1.8.0 * * @param AJAX_Runner $runner The runner to configure. - * @return array The configuration used. */ private function configure_runner( $runner ) { $checks = filter_input( INPUT_POST, 'checks', FILTER_DEFAULT, FILTER_FORCE_ARRAY ); @@ -124,11 +123,6 @@ private function configure_runner( $runner ) { $runner->set_experimental_flag( $include_experimental ); $runner->set_check_slugs( $checks ); $runner->set_plugin( $plugin ); - - return array( - 'checks' => $checks, - 'plugin' => $plugin, - ); } /** @@ -167,7 +161,7 @@ public function set_up_environment() { } try { - $config = $this->configure_runner( $runner ); + $this->configure_runner( $runner ); $checks_to_run = $runner->get_checks_to_run(); } catch ( Exception $error ) { @@ -188,8 +182,6 @@ public function set_up_environment() { wp_send_json_success( array( 'message' => $message, - 'plugin' => $config['plugin'], - 'checks' => $config['checks'], ) ); } From b0d46092396d417b8805d2fbafef41e2f2e7eb02 Mon Sep 17 00:00:00 2001 From: Faisal Ahammad Date: Mon, 3 Aug 2026 11:22:04 +0600 Subject: [PATCH 2/3] fix(admin): handle both error payload shapes in fetchAJAX Update handleDataErrors to read the message from WP_Error array responses and object responses alike, so export errors (which return { message: ... }) no longer throw a TypeError. Addresses PR feedback. Refs #1415 --- assets/js/plugin-check-admin.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js index 80b6ccfad..787fbd8bf 100644 --- a/assets/js/plugin-check-admin.js +++ b/assets/js/plugin-check-admin.js @@ -1043,13 +1043,20 @@ } if ( ! data.success ) { + // Error payloads come in two shapes: WP_Error responses are + // arrays ( [{ code, message }, ...] ), everything else is + // passed through as-is ( e.g. { message: ... } ). + const firstError = Array.isArray( data.data ) + ? data.data[ 0 ] + : data.data; + // If not successful and no message in the response. - if ( ! data.data || ! data.data[ 0 ].message ) { + if ( ! firstError || ! firstError.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 ); + throw new Error( firstError.message ); } return data; From 2a2c41d96f720ed2f004cf038946f5f54ccfc260 Mon Sep 17 00:00:00 2001 From: Faisal Ahammad Date: Mon, 3 Aug 2026 16:42:31 +0600 Subject: [PATCH 3/3] fix(docs): update @since tags for new AJAX helpers - getSelectedValues() and fetchAJAX() are new in this PR, tag 2.1.0 Addresses PR feedback. Refs #1415 --- assets/js/plugin-check-admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/plugin-check-admin.js b/assets/js/plugin-check-admin.js index 787fbd8bf..b42406c68 100644 --- a/assets/js/plugin-check-admin.js +++ b/assets/js/plugin-check-admin.js @@ -52,7 +52,7 @@ /** * Gets the values of checked inputs from a list. * - * @since 1.0.0 + * @since 2.1.0 * * @param {NodeList} list Inputs to read from. * @return {Array} Selected values. @@ -74,7 +74,7 @@ * credentials, nonce in body) and parses the JSON response, surfacing * server errors via handleDataErrors. * - * @since 1.0.0 + * @since 2.1.0 * * @param {FormData} formData Form data to send. Must include 'action' and 'nonce'. * @return {Promise} Resolves with the response data on success.