*/ if ( ! defined( 'ABSPATH' ) ) { exit; } class Group107_AccessibleDocs_Ajax_Handler { /** * Handles the AJAX request to clear the AccessibleDocs table. * Verifies the nonce for security and calls the database handler to clear the table. * Sends a JSON response indicating success or failure. * * @return array */ public function accessible_docs_clear_table() { // Verify nonce $this->accessible_docs_verify_admin_request(); $result = Group107_AccessibleDocs_Db_Handler::accessible_docs_clear_table(); if($result){ wp_send_json_success(['status' => 'success']); } else { $this->accessible_docs_send_error(__('Error trying to clear AccessibleDocs table, please try again!', 'accessible-docs')); } } /** * Handles the AJAX request to delete selected records from the AccessibleDocs table. * Verifies the nonce for security and calls the database handler to remove selected records from the table. * Sends a JSON response indicating success or failure. * * @return array */ public function accessible_docs_delete_selected() { // Verify nonce $this->accessible_docs_verify_admin_request(); $selected_ids = isset($_POST['selectedIds']) ? sanitize_text_field(wp_unslash($_POST['selectedIds'])) : ''; $ids = explode(',', $selected_ids); $ids = array_filter(array_map('absint', $ids)); if ( empty($ids) ) { $this->accessible_docs_send_error(__('Missing required fields', 'accessible-docs')); } $result = Group107_AccessibleDocs_Db_Handler::accessible_docs_delete_selected_records($ids); if($result){ wp_send_json_success(['status' => 'success']); } else { $this->accessible_docs_send_error(__('Error trying to delete records from AccessibleDocs table, please try again!', 'accessible-docs')); } } /** * Returns a fresh nonce for the public request form. * * Pages are often served from a page cache, where a nonce printed into the HTML * expires after 12–24 hours. The popup asks for a fresh one right before submitting. * * @return void */ public function accessible_docs_nonce() { nocache_headers(); wp_send_json_success(['nonce' => wp_create_nonce('accessible_docs_nonce')]); } /** * Handles the accessible_docs_request AJAX request. */ public function accessible_docs_request() { // Verify nonce $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if ( empty($nonce) || !wp_verify_nonce($nonce, 'accessible_docs_nonce') ) { $this->accessible_docs_send_error(__('Something went wrong, please try again later', 'accessible-docs')); } // Sanitize and validate input $email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : ''; $url = isset($_POST['url']) ? esc_url_raw(urldecode(wp_unslash($_POST['url']))) : ''; $file_id = $url ? intval(attachment_url_to_postid($url)) : 0; if (empty($email) || empty($url) || empty($file_id)) { $this->accessible_docs_send_error(__( 'Missing required fields', 'accessible-docs' )); } // Only PDF files from the Media Library can be sent for processing if ('application/pdf' !== get_post_mime_type($file_id)) { $this->accessible_docs_send_error(__( 'Only PDF files can be processed.', 'accessible-docs' )); } // Check if the file already exists in the database $existing_record = Group107_AccessibleDocs_Db_Handler::check_existing_accessible_docs_file($email, $file_id); if ($existing_record) { $this->accessible_docs_handle_existing_record($existing_record, $file_id, $email); } else { $this->accessible_docs_handle_file_upload($file_id, $email, false); } wp_die(); } /** * Handles an existing file record by checking its status. * * If the file is still in-process, the status is checked with the external API. * If it is completed, the status in the database is updated. * * @param array $existing_record The existing record data from the database. * @param int $file_id The WordPress attachment file ID. * @param string $email The user's email address. * * @return void */ private function accessible_docs_handle_existing_record($existing_record, $file_id, $email) { if ($existing_record['accessible_docs_status'] === 'in-process') { $file_status = Group107_AccessibleDocs_API_Handler::accessible_docs_check_file_status($existing_record['accessible_docs_file_id']); // The platform no longer knows this file: send it again. if ($file_status['status'] === 'not-found') { $this->accessible_docs_handle_file_upload($file_id, $email, true); } if ($file_status['status'] === 'completed') { Group107_AccessibleDocs_Db_Handler::accessible_docs_update_file_status($file_id, $email); wp_send_json_success(['status' => 'completed']); } // Still processing – or the status could not be checked right now (the file is already queued, // so it is not uploaded again, which would cost another processing credit). wp_send_json_success(['status' => 'in-process']); } // Return the existing file status wp_send_json_success(['status' => $existing_record['accessible_docs_status']]); } /** * Handles both new file uploads and re-uploads to AccessibleDocs. * * This function uploads a file using the API, then either saves a new record * or updates an existing one in the database based on whether the file is * being uploaded for the first time or re-uploaded. * * @param int $file_id The ID of the file being uploaded. * @param string $email The email associated with the file. * @param bool $is_reupload Determines if the file is being re-uploaded. */ private function accessible_docs_handle_file_upload($file_id, $email, $is_reupload = false) { if ($this->accessible_docs_is_rate_limited()) { $this->accessible_docs_send_error(__('Too many requests, please try again later.', 'accessible-docs')); } $upload_response = Group107_AccessibleDocs_API_Handler::accessible_docs_upload_file($email, $file_id); if ($upload_response['status'] === 'error') { $this->accessible_docs_send_error($upload_response['error']); } if ($is_reupload) { // Update accessible_docs_file_id in the database $result = Group107_AccessibleDocs_Db_Handler::accessible_docs_update_file_id($file_id, $email, $upload_response['data']['id']); $error_message = __('Failed to update AccessibleDocs file ID in the database.', 'accessible-docs'); } else { // Save the uploaded file record to the database $result = Group107_AccessibleDocs_Db_Handler::save_accessible_docs_file($email, $file_id, $upload_response['data']['id']); $error_message = __('Data was not saved to the database.', 'accessible-docs'); } if ($result) { wp_send_json_success(['status' => 'in-process']); } else { $this->accessible_docs_send_error($error_message); } } /** * Verifies that an admin AJAX request has a valid admin nonce * and comes from a user allowed to manage the plugin. * * @return void */ private function accessible_docs_verify_admin_request() { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if ( empty($nonce) || !wp_verify_nonce($nonce, 'accessible_docs_admin_nonce') || !current_user_can('manage_options') ) { $this->accessible_docs_send_error(__('Something went wrong, please try again later', 'accessible-docs')); } } /** * Checks and increments the number of new file uploads made from the visitor's IP address. * * Only new uploads are counted, status checks of already requested files are not limited. * The limit can be changed or disabled (0) with the 'accessible_docs_rate_limit' filter. * * @return bool True if the limit is reached, false otherwise. */ private function accessible_docs_is_rate_limited() { $limit = (int) apply_filters('accessible_docs_rate_limit', 30); if ($limit <= 0) { return false; } $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : ''; $key = 'accessible_docs_rl_' . md5($ip); $count = (int) get_transient($key); if ($count >= $limit) { return true; } set_transient($key, $count + 1, HOUR_IN_SECONDS); return false; } /** * Sends a JSON error response and terminates the script. * * This method is used to send a standardized error response in JSON format. * * @param string $message The error message to send. * * @return void */ private function accessible_docs_send_error($message) { wp_send_json_error(['status' => 'error', 'error' => $message]); wp_die(); } }