diff --git a/assets/css/check-email-dns-records.css b/assets/css/check-email-dns-records.css new file mode 100644 index 000000000..1324e0273 --- /dev/null +++ b/assets/css/check-email-dns-records.css @@ -0,0 +1,190 @@ +/** + * Check Email DNS Records Styles + * + * Styles for email DNS check task execution and display. + */ + +/* Email DNS Check Popover */ +.prpl-popover-email-dns { + max-width: 600px; +} + +.prpl-email-dns-content { + min-height: 100px; +} + +/* Header */ +.prpl-popover-email-dns .prpl-popover-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + border-bottom: 1px solid #e5e7eb; +} + +.prpl-popover-email-dns .prpl-popover-title { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #1f2937; +} + +.prpl-popover-email-dns .prpl-popover-close { + background: none; + border: none; + cursor: pointer; + padding: 5px; + color: #6b7280; + transition: color 0.2s; +} + +.prpl-popover-email-dns .prpl-popover-close:hover { + color: #1f2937; +} + +/* Content */ +.prpl-popover-email-dns .prpl-popover-content { + padding: 20px; +} + +/* Instructions */ +.prpl-email-dns-instructions p { + margin: 0 0 10px 0; + line-height: 1.6; +} + +.prpl-email-dns-instructions p:last-child { + margin-bottom: 0; +} + +/* Loading State */ +.prpl-email-dns-loading { + text-align: center; + padding: 30px 20px; +} + +.prpl-email-dns-loading .prpl-spinner { + margin: 0 auto 20px; + width: 40px; + height: 40px; + border: 4px solid #f3f4f6; + border-top-color: #534786; + border-radius: 50%; + animation: prpl-email-dns-spin 1s linear infinite; +} + +@keyframes prpl-email-dns-spin { + + to { + transform: rotate(360deg); + } +} + +.prpl-email-dns-loading p { + color: #666; + font-size: 14px; + margin: 0; +} + +/* Result State */ +.prpl-email-dns-result { + padding: 20px; + background: #f9fafb; + border-radius: 6px; + margin-bottom: 20px; +} + +.prpl-email-dns-response { + line-height: 1.6; + color: #1f2937; +} + +/* DNS Results Styling */ +.prpl-dns-results { + font-size: 14px; +} + +.prpl-spam-score { + margin: 0 0 15px 0; + padding: 10px; + border-radius: 4px; +} + +.prpl-spam-score.prpl-score-good { + background-color: #d1fae5; + color: #065f46; +} + +.prpl-spam-score.prpl-score-warning { + background-color: #fef3c7; + color: #92400e; +} + +.prpl-spam-score.prpl-score-bad { + background-color: #fee2e2; + color: #991b1b; +} + +.prpl-dns-records-list { + list-style: none; + margin: 0; + padding: 0; +} + +.prpl-dns-record { + padding: 8px 0; + border-bottom: 1px solid #e5e7eb; + display: flex; + align-items: center; + gap: 8px; +} + +.prpl-dns-record:last-child { + border-bottom: none; +} + +.prpl-status-icon { + font-weight: 700; + font-size: 16px; +} + +.prpl-dns-record.prpl-status-pass .prpl-status-icon { + color: #059669; +} + +.prpl-dns-record.prpl-status-fail .prpl-status-icon { + color: #dc2626; +} + +/* Error State */ +.prpl-email-dns-error { + padding: 15px; + background: #fef2f2; + border: 1px solid #fecaca; + border-radius: 6px; + margin-bottom: 20px; +} + +.prpl-email-dns-error .prpl-error-message { + color: #dc2626; + margin: 0; + font-size: 14px; +} + +/* Buttons */ +.prpl-email-dns-check, +.prpl-email-dns-retry { + min-width: 120px; +} + +/* Responsive */ +@media (max-width: 768px) { + + .prpl-popover-email-dns { + max-width: 90vw; + } + + .prpl-email-dns-result { + padding: 15px; + } +} diff --git a/assets/js/recommendations/check-email-dns-records.js b/assets/js/recommendations/check-email-dns-records.js new file mode 100644 index 000000000..8b44b6795 --- /dev/null +++ b/assets/js/recommendations/check-email-dns-records.js @@ -0,0 +1,294 @@ +/* global progressPlannerAjaxRequest, progressPlanner */ + +/** + * Check Email DNS Records Handler + * + * Handles execution of email DNS check tasks with state management. + * + * Dependencies: progress-planner/suggested-task, progress-planner/ajax-request + */ + +( () => { + /** + * Handle email DNS check execution. + */ + const handleEmailDNSCheck = () => { + const popover = document.getElementById( + 'prpl-popover-check-email-dns-records' + ); + if ( ! popover ) { + return; + } + + const checkButton = popover.querySelector( '.prpl-email-dns-check' ); + const retryButton = popover.querySelector( '.prpl-email-dns-retry' ); + const checkReportAgainButton = popover.querySelector( + '.prpl-email-dns-check-report-again' + ); + const completeButton = popover.querySelector( + '.prpl-email-dns-complete' + ); + const instructionsEl = popover.querySelector( + '.prpl-email-dns-instructions' + ); + const loadingEl = popover.querySelector( '.prpl-email-dns-loading' ); + const resultEl = popover.querySelector( '.prpl-email-dns-result' ); + const responseEl = popover.querySelector( '.prpl-email-dns-response' ); + const errorEl = popover.querySelector( '.prpl-email-dns-error' ); + const errorMessageEl = popover.querySelector( '.prpl-error-message' ); + + // Define all elements in one place for easy maintenance. + const elements = { + checkButton, + retryButton, + checkReportAgainButton, + completeButton, + instructionsEl, + loadingEl, + resultEl, + responseEl, + errorEl, + errorMessageEl, + }; + + // State configuration: define visibility for each element in each state. + const states = { + loading: { + checkButton: 'none', + retryButton: 'none', + checkReportAgainButton: 'none', + completeButton: 'none', + instructionsEl: 'none', + loadingEl: 'block', + resultEl: 'none', + errorEl: 'none', + }, + pending: { + instructionsEl: 'none', + loadingEl: 'none', + checkButton: 'none', + retryButton: 'none', + checkReportAgainButton: 'inline-block', + completeButton: 'none', + resultEl: 'none', + errorEl: 'block', + }, + result: { + instructionsEl: 'none', + loadingEl: 'none', + checkButton: 'none', + retryButton: 'none', + checkReportAgainButton: 'none', + errorEl: 'none', + resultEl: 'block', + completeButton: 'inline-block', + }, + error: { + instructionsEl: 'none', + loadingEl: 'none', + checkButton: 'none', + retryButton: 'inline-block', + checkReportAgainButton: 'none', + completeButton: 'none', + resultEl: 'none', + errorEl: 'block', + }, + }; + + /** + * Set the UI state. + * + * @param {string} stateName - The state name ('loading', 'result', 'error', or 'pending'). + * @param {Object} options - Additional options for the state. + * @param {string} options.responseHtml - HTML content for responseEl (result state only). + * @param {string} options.message - Error message for errorMessageEl (error and pending states). + */ + const setUIState = ( stateName, options = {} ) => { + const state = states[ stateName ]; + if ( ! state ) { + console.warn( `Unknown state: ${ stateName }` ); + return; + } + + // Apply visibility for each element in the state. + Object.entries( state ).forEach( ( [ key, display ] ) => { + const element = elements[ key ]; + if ( element ) { + element.style.display = display; + } + } ); + + // Handle special cases. + if ( + stateName === 'result' && + options.responseHtml && + elements.responseEl + ) { + elements.responseEl.innerHTML = options.responseHtml; + } + + if ( + ( stateName === 'error' || stateName === 'pending' ) && + options.message && + elements.errorMessageEl + ) { + elements.errorMessageEl.textContent = options.message; + } + }; + + /** + * Show loading state. + */ + const showLoading = () => { + setUIState( 'loading' ); + }; + + /** + * Show result state. + * + * @param {string} responseHtml - The formatted HTML response. + */ + const showResult = ( responseHtml ) => { + setUIState( 'result', { responseHtml } ); + }; + + /** + * Show error state. + * + * @param {string} message - The error message. + */ + const showError = ( message ) => { + setUIState( 'error', { message } ); + }; + + /** + * Show pending state (report is still being processed). + * + * @param {string} message - The message to display. + */ + const showPending = ( message ) => { + setUIState( 'pending', { message } ); + }; + + /** + * Execute the email DNS check. + */ + const executeCheck = () => { + showLoading(); + + // Make AJAX request to check email DNS records. + progressPlannerAjaxRequest( { + url: progressPlanner.ajaxUrl, + data: { + action: 'prpl_interactive_task_submit_check-email-dns-records', + nonce: progressPlanner.nonce, + }, + } ) + .then( ( response ) => { + if ( ! response.success ) { + const errorMessage = + response.data?.message || + 'Failed to check email DNS records. Please try again.'; + showError( errorMessage ); + return; + } + + const data = response.data; + + // Check if the report is still being processed. + if ( data.status === 'pending' ) { + showPending( + data.message || + 'Report is still being processed. Please check again in a moment.' + ); + return; + } + + const responseHtml = + data.response_html || + '

Check completed successfully.

'; + + showResult( responseHtml ); + } ) + .catch( ( error ) => { + console.error( 'Email DNS check error:', error ); + showError( + 'An error occurred while checking your email DNS records. Please try again.' + ); + } ); + }; + + /** + * Check if the report is ready (without sending a new email). + */ + const checkReportAgain = () => { + showLoading(); + + // Make AJAX request to check if report is ready. + progressPlannerAjaxRequest( { + url: progressPlanner.ajaxUrl, + data: { + action: 'prpl_check_email_dns_report_again', + nonce: progressPlanner.nonce, + }, + } ) + .then( ( response ) => { + if ( ! response.success ) { + const errorMessage = + response.data?.message || + 'Failed to check email DNS records. Please try again.'; + showError( errorMessage ); + return; + } + + const data = response.data; + + // Check if the report is still being processed. + if ( data.status === 'pending' ) { + showPending( + data.message || + 'Report is still being processed. Please check again in a moment.' + ); + return; + } + + const responseHtml = + data.response_html || + '

Check completed successfully.

'; + + showResult( responseHtml ); + } ) + .catch( ( error ) => { + console.error( 'Email DNS check error:', error ); + showError( + 'An error occurred while checking your email DNS records. Please try again.' + ); + } ); + }; + + // Check button click handler. + if ( checkButton ) { + checkButton.addEventListener( 'click', executeCheck ); + } + + // Retry button click handler. + if ( retryButton ) { + retryButton.addEventListener( 'click', executeCheck ); + } + + // Check report again button click handler. + if ( checkReportAgainButton ) { + checkReportAgainButton.addEventListener( + 'click', + checkReportAgain + ); + } + }; + + // Initialize when DOM is ready. + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', handleEmailDNSCheck ); + } else { + handleEmailDNSCheck(); + } +} )(); diff --git a/classes/suggested-tasks/class-tasks-manager.php b/classes/suggested-tasks/class-tasks-manager.php index d20164df9..5c251e7a9 100644 --- a/classes/suggested-tasks/class-tasks-manager.php +++ b/classes/suggested-tasks/class-tasks-manager.php @@ -36,6 +36,7 @@ use Progress_Planner\Suggested_Tasks\Providers\Unpublished_Content; use Progress_Planner\Suggested_Tasks\Providers\Collaborator; use Progress_Planner\Suggested_Tasks\Providers\Select_Timezone; +use Progress_Planner\Suggested_Tasks\Providers\Check_Email_DNS_Records; use Progress_Planner\Suggested_Tasks\Providers\Set_Date_Format; use Progress_Planner\Suggested_Tasks\Providers\SEO_Plugin; use Progress_Planner\Suggested_Tasks\Providers\Improve_Pdf_Handling; @@ -84,6 +85,7 @@ public function __construct() { new Unpublished_Content(), new Collaborator(), new Select_Timezone(), + new Check_Email_DNS_Records(), new Set_Date_Format(), new SEO_Plugin(), new Improve_Pdf_Handling(), diff --git a/classes/suggested-tasks/providers/class-check-email-dns-records.php b/classes/suggested-tasks/providers/class-check-email-dns-records.php new file mode 100644 index 000000000..e3375a004 --- /dev/null +++ b/classes/suggested-tasks/providers/class-check-email-dns-records.php @@ -0,0 +1,440 @@ +get_activities__query()->query_activities( + [ + 'category' => 'suggested_task', + 'data_id' => static::PROVIDER_ID, + ] + ); + + return ! $email_dns_records_activity; + } + + /** + * Print popover form contents. + * Required by Tasks_Interactive, but we override add_popover() completely. + * + * @return void + */ + public function print_popover_form_contents() { + // Not used - we use a custom popover view. + } + + /** + * The popover content, WIP until get_file bug is fixed. + * + * @return void + */ + public function the_popover_content() { + \progress_planner()->the_view( + 'popovers/' . static::POPOVER_ID . '.php', + [ + 'prpl_popover_id' => static::POPOVER_ID, + 'prpl_provider_id' => $this->get_provider_id(), + ] + ); + } + + /** + * Add the popover for email DNS check task. + * Overrides parent to use custom popover view with state management. + * + * @return void + */ + public function add_popover() { + // Don't add the popover if the task is not published. + if ( ! $this->is_task_published() ) { + return; + } + ?> +
+ the_popover_content(); ?> +
+ is_task_published() ) { + return; + } + + // Enqueue the email DNS check script. + \progress_planner()->get_admin__enqueue()->enqueue_script( + 'progress-planner/recommendations/check-email-dns-records', + [ + 'file' => 'recommendations/check-email-dns-records.js', + 'dependencies' => [ 'progress-planner/suggested-task', 'progress-planner/ajax-request' ], + ] + ); + + // Enqueue the email DNS check CSS. + \progress_planner()->get_admin__enqueue()->enqueue_style( 'progress-planner/check-email-dns-records' ); + } + + /** + * Handle the interactive task submit. + * + * This is only for interactive tasks that change non-core settings. + * The $_POST data is expected to be: + * - setting: (string) The setting to update. + * - value: (mixed) The value to update the setting to. + * - setting_path: (array) The path to the setting to update. + * Use an empty array if the setting is not nested. + * If the value is nested, use an array of keys. + * Example: [ 'a', 'b', 'c' ] will update the value of $option['a']['b']['c']. + * - nonce: (string) The nonce. + * + * @return void + */ + public function handle_interactive_task_specific_submit() { + + if ( ! $this->capability_required() ) { + \wp_send_json_error( [ 'message' => \esc_html__( 'You do not have permission to check email DNS records.', 'progress-planner' ) ] ); + } + + // Check the nonce. + if ( ! \check_ajax_referer( 'progress_planner', 'nonce', false ) ) { + \wp_send_json_error( [ 'message' => \esc_html__( 'Invalid nonce.', 'progress-planner' ) ] ); + } + + $site = \get_site_url(); + + // Get a nonce from the remote server. + $nonce_request = wp_remote_post( + \progress_planner()->get_remote_server_root_url() . '/wp-json/progress-planner-saas/v1/get-nonce', + [ + 'body' => [ + 'site' => $site, + ], + ] + ); + + if ( is_wp_error( $nonce_request ) || 200 !== wp_remote_retrieve_response_code( $nonce_request ) ) { + \wp_send_json_error( [ 'message' => \esc_html__( 'Failed to get nonce.', 'progress-planner' ) ] ); + } + + $nonce_response = \json_decode( wp_remote_retrieve_body( $nonce_request ), true ); + $remote_nonce = $nonce_response['nonce'] ?? ''; + + // Tell server which email identifier are we going to use, response should be email address to which we are sending the test email. + $subject = md5( \get_bloginfo( 'name' ) . ' - ' . \microtime( true ) ); + + $pre_request = wp_remote_post( + \progress_planner()->get_remote_server_root_url() . '/wp-json/progress-planner-saas/v1/email-dns-check', + [ + 'body' => [ + 'nonce' => $remote_nonce, + 'site' => $site, + 'email_identifier' => $subject, + ], + ] + ); + + if ( \is_wp_error( $pre_request ) || 200 !== \wp_remote_retrieve_response_code( $pre_request ) ) { + \wp_send_json_error( [ 'message' => \esc_html__( 'Failed to send test email.', 'progress-planner' ) ] ); + } + + $pre_request_response = \json_decode( wp_remote_retrieve_body( $pre_request ), true ); + $email_address = $pre_request_response['email_address'] ?? ''; + + // Send the email. + $email_sent = \wp_mail( $email_address, $subject, 'email test' ); // Message body is not important, can't be empty. + + // TODO: If wp_mail returned false we need to tell the server to not check for the email. + if ( ! $email_sent ) { + \error_log( 'Failed to send email: ' . $email_address ); + \wp_send_json_error( [ 'message' => \esc_html__( 'Failed to send email.', 'progress-planner' ) ] ); + } + + // Store email identifier and remote nonce in transient for later retrieval. + $user_id = \get_current_user_id(); + $cache_key = 'prpl_email_dns_check_' . $user_id; + \progress_planner()->get_utils__cache()->set( + $cache_key, + [ + 'email_identifier' => $subject, + 'remote_nonce' => $remote_nonce, + 'site' => $site, + ], + HOUR_IN_SECONDS + ); + + // TODO: Sleep for 15 seconds, wait for the report to be ready. + sleep( 15 ); + + // Check DNS report using the extracted method. + $check_result = $this->check_dns_report( $subject, $remote_nonce, $site ); + + if ( \is_wp_error( $check_result ) ) { + \wp_send_json_error( [ 'message' => $check_result->get_error_message() ] ); + } + + // If report is still processing, return a status indicating that. + if ( isset( $check_result['status'] ) && 'pending' === $check_result['status'] ) { + \wp_send_json_success( + [ + 'message' => \esc_html__( 'Report is still being processed. Please check again in a moment.', 'progress-planner' ), + 'status' => 'pending', + 'email_identifier' => $subject, + ] + ); + } + + // Report is ready, return the results and clean up the transient. + \progress_planner()->get_utils__cache()->delete( $cache_key ); + + \wp_send_json_success( + [ + 'message' => \esc_html__( 'Email DNS records checked successfully.', 'progress-planner' ), + 'response_html' => $check_result['response_html'], + ] + ); + } + + /** + * Check DNS report status. + * + * @param string $email_identifier The email identifier (subject) used for the check. + * @param string $remote_nonce The remote nonce for authentication. + * @param string $site The site URL. + * + * @return array|\WP_Error Array with 'response_html' on success, or 'status' => 'pending' if still processing, or WP_Error on failure. + */ + protected function check_dns_report( $email_identifier, $remote_nonce, $site ) { + $dns_check_request = wp_remote_get( + \progress_planner()->get_remote_server_root_url() . '/wp-json/progress-planner-saas/v1/email-dns-check', + [ + 'body' => [ + 'nonce' => $remote_nonce, + 'site' => $site, + 'email_identifier' => $email_identifier, + ], + ] + ); + + if ( \is_wp_error( $dns_check_request ) || 200 !== \wp_remote_retrieve_response_code( $dns_check_request ) ) { + return new \WP_Error( 'dns_check_failed', \esc_html__( 'Failed to check email DNS records.', 'progress-planner' ) ); + } + + $dns_check_response = \json_decode( wp_remote_retrieve_body( $dns_check_request ), true ); + + // Check if the report is still being processed. + if ( isset( $dns_check_response['status'] ) && 'pending' === $dns_check_response['status'] ) { + return [ + 'status' => 'pending', + ]; + } + + if ( ! isset( $dns_check_response['results'] ) ) { + return new \WP_Error( 'dns_check_no_results', \esc_html__( 'Failed to check email DNS records.', 'progress-planner' ) ); + } + + // Build the response with DNS records status information. + $response_html = $this->format_dns_response( $dns_check_response['results'] ); + + return [ + 'response_html' => $response_html, + ]; + } + + /** + * Handle the check report again AJAX request. + * + * This method only checks if the report is ready and fetches it if it is. + * It does not send a new email. + * + * @return void + */ + public function handle_check_report_again() { + if ( ! $this->capability_required() ) { + \wp_send_json_error( [ 'message' => \esc_html__( 'You do not have permission to check email DNS records.', 'progress-planner' ) ] ); + } + + // Check the nonce. + if ( ! \check_ajax_referer( 'progress_planner', 'nonce', false ) ) { + \wp_send_json_error( [ 'message' => \esc_html__( 'Invalid nonce.', 'progress-planner' ) ] ); + } + + $user_id = \get_current_user_id(); + $cache_key = 'prpl_email_dns_check_' . $user_id; + $cached_data = \progress_planner()->get_utils__cache()->get( $cache_key ); + + if ( false === $cached_data || ! isset( $cached_data['email_identifier'], $cached_data['remote_nonce'], $cached_data['site'] ) ) { + \wp_send_json_error( [ 'message' => \esc_html__( 'Email check session expired. Please start a new check.', 'progress-planner' ) ] ); + } + + $email_identifier = $cached_data['email_identifier']; + $remote_nonce = $cached_data['remote_nonce']; + $site = $cached_data['site']; + + // Check DNS report using the extracted method. + $check_result = $this->check_dns_report( $email_identifier, $remote_nonce, $site ); + + if ( \is_wp_error( $check_result ) ) { + \wp_send_json_error( [ 'message' => $check_result->get_error_message() ] ); + } + + // If report is still processing, return a status indicating that. + if ( isset( $check_result['status'] ) && 'pending' === $check_result['status'] ) { + \wp_send_json_success( + [ + 'message' => \esc_html__( 'Report is still being processed. Please check again in a moment.', 'progress-planner' ), + 'status' => 'pending', + 'email_identifier' => $email_identifier, + ] + ); + } + + // Report is ready, return the results and clean up the transient. + \progress_planner()->get_utils__cache()->delete( $cache_key ); + + \wp_send_json_success( + [ + 'message' => \esc_html__( 'Email DNS records checked successfully.', 'progress-planner' ), + 'response_html' => $check_result['response_html'], + ] + ); + } + + /** + * Format the DNS check response for display. + * + * @param array|bool $check_results The DNS records status. + * @return string Formatted HTML response. + */ + protected function format_dns_response( $check_results ) { + $html = '
'; + + // Format DNS records status. + if ( \is_array( $check_results ) ) { + $html .= ''; + } + + $html .= '
'; + + return $html; + } + + /** + * Add task actions specific to this task. + * + * @param array $data The task data. + * @param array $actions The existing actions. + * + * @return array + */ + public function add_task_actions( $data = [], $actions = [] ) { + $actions[] = [ + 'priority' => 10, + 'html' => '' . \esc_html__( 'Check', 'progress-planner' ) . '', + ]; + + return $actions; + } +} diff --git a/views/popovers/check-email-dns-records.php b/views/popovers/check-email-dns-records.php new file mode 100644 index 000000000..fa62fccbf --- /dev/null +++ b/views/popovers/check-email-dns-records.php @@ -0,0 +1,60 @@ + + + +
+

+ +
+
+ +
+ + + + +
+
+