'user_evaluation', 'post_status' => 'any', // Find it regardless of status initially 'meta_query' => [ [ 'key' => 'quiztech_invitation_id', 'value' => $invitation_id, 'compare' => '=', ] ], 'posts_per_page' => 1, 'fields' => 'ids', // Only get the ID ]; $evaluation_posts = get_posts($args); if ( ! empty( $evaluation_posts ) ) { // Found existing evaluation return $evaluation_posts[0]; } else { // Not found, create a new one $post_data = [ 'post_type' => 'user_evaluation', 'post_status' => 'publish', // Start as published (or maybe 'pending'/'in-progress' if custom statuses are added) 'post_title' => sprintf( __( 'Evaluation for Invitation #%d', 'quiztech' ), $invitation_id ), 'post_content' => '', // No content needed initially // 'post_author' => ?? // Assign to an admin or system user? Default is current user (likely none in AJAX) ]; $evaluation_id = wp_insert_post( $post_data, true ); // Pass true for WP_Error on failure if ( is_wp_error( $evaluation_id ) ) { error_log("Quiztech AJAX Error: Failed to create user_evaluation CPT for invitation ID {$invitation_id}: " . $evaluation_id->get_error_message()); return 0; } // Add the linking meta field $meta_updated = update_post_meta( $evaluation_id, 'quiztech_invitation_id', $invitation_id ); if ( ! $meta_updated ) { // Log error, but maybe don't fail the whole request? Or should we delete the post? error_log("Quiztech AJAX Warning: Failed to add quiztech_invitation_id meta to new evaluation ID {$evaluation_id} for invitation ID {$invitation_id}."); // Depending on requirements, might return 0 here or proceed. Let's proceed for now. } error_log("Quiztech AJAX Info: Created new user_evaluation CPT ID {$evaluation_id} for invitation ID {$invitation_id}."); return $evaluation_id; } } /** * Handles the AJAX submission of the pre-screening form. * Expects 'nonce', 'invitation_id', and 'pre_screen_answer' array in $_POST. */ public function handle_submit_prescreening() { // 1. Verify Nonce check_ajax_referer('quiztech_prescreening_nonce', 'nonce'); // 2. Get and Sanitize Core Data $invitation_id = isset($_POST['invitation_id']) ? absint($_POST['invitation_id']) : 0; if ( ! $invitation_id ) { wp_send_json_error(['message' => __('Missing invitation ID.', 'quiztech')], 400); } // 3. Get or Create User Evaluation Record $evaluation_id = $this->get_or_create_user_evaluation($invitation_id); if ( ! $evaluation_id ) { error_log("Quiztech AJAX Error: Failed to get or create user_evaluation for invitation ID: " . $invitation_id); wp_send_json_error(['message' => __('Could not process evaluation record.', 'quiztech')], 500); } // 4. Sanitize Submitted Answers $submitted_answers = isset($_POST['pre_screen_answer']) && is_array($_POST['pre_screen_answer']) ? $_POST['pre_screen_answer'] : []; $sanitized_answers = []; foreach ($submitted_answers as $index => $answer) { // Use sanitize_textarea_field as pre-screening questions are currently textareas $sanitized_answers[sanitize_key($index)] = sanitize_textarea_field(wp_unslash($answer)); } // 4. Save Answers (as user_evaluation CPT meta) if (!empty($sanitized_answers)) { update_post_meta($evaluation_id, 'quiztech_prescreening_answers', $sanitized_answers); } else { // Handle case where no answers were submitted? Or rely on form 'required' attribute? // For now, proceed even if empty. } // 5. Update Invitation Status try { $invitations = new Invitations(); $updated = $invitations->update_status($invitation_id, 'pre-screening-complete'); if (!$updated) { error_log("Quiztech AJAX Error: Failed to update invitation status for ID: " . $invitation_id); // Decide if this should be a user-facing error or just logged } } catch (\Exception $e) { error_log("Quiztech AJAX Error: Exception updating invitation status: " . $e->getMessage()); // Decide if this should be a user-facing error } // 6. Send Response wp_send_json_success(['message' => __('Pre-screening submitted successfully. Starting assessment...', 'quiztech')]); // Ensure script execution stops wp_die(); } /** * Handles the AJAX auto-save of a single assessment answer. * Expects 'nonce', 'invitation_id', 'question_id', and 'answer' in $_POST. */ public function handle_save_answer() { // 1. Verify Nonce check_ajax_referer('quiztech_assessment_nonce', 'nonce'); // 2. Get and Sanitize Data $invitation_id = isset($_POST['invitation_id']) ? absint($_POST['invitation_id']) : 0; $question_id = isset($_POST['question_id']) ? absint($_POST['question_id']) : 0; $answer = isset($_POST['answer']) ? wp_unslash($_POST['answer']) : ''; // Sanitize based on question type later // Basic validation for required IDs before querying if ( ! $invitation_id || ! $question_id ) { wp_send_json_error(['message' => __('Missing required data for saving answer.', 'quiztech')], 400); } // 3. Get or Create User Evaluation Record $evaluation_id = $this->get_or_create_user_evaluation($invitation_id); if ( ! $evaluation_id ) { error_log("Quiztech AJAX Error: Failed to get or create user_evaluation for invitation ID: " . $invitation_id . " during answer save."); wp_send_json_error(['message' => __('Could not process evaluation record for saving answer.', 'quiztech')], 500); } // 4. Fetch the question type meta for the given question ID $question_type = \get_post_meta($question_id, '_quiztech_question_type', true); if ( ! $question_type ) { // Log if type is missing, but proceed with default sanitization error_log("Quiztech AJAX Warning: Missing question type meta for question ID: " . $question_id); $question_type = 'text'; // Default to text if not set } // Sanitize the answer based on question type $sanitized_answer = ''; // Initialize if (is_array($answer)) { // Handle array answers (likely checkboxes) if ('checkbox' === $question_type) { // Sanitize each value in the array $sanitized_answer = array_map('sanitize_text_field', $answer); // Note: update_post_meta can handle arrays directly, storing them serialized. } else { // Unexpected array answer for this question type error_log("Quiztech AJAX Error: Received array answer for non-checkbox question ID: " . $question_id); // Sanitize by joining elements (simple approach, might need refinement) $sanitized_answer = sanitize_text_field(implode(', ', $answer)); } } else { // Handle string/scalar answers switch ($question_type) { case 'textarea': $sanitized_answer = sanitize_textarea_field($answer); break; case 'numeric': // Allow integers and potentially floats. Use floatval for broader acceptance. // Ensure it's actually numeric before casting to avoid warnings/errors. $sanitized_answer = is_numeric($answer) ? floatval($answer) : 0; break; case 'multiple-choice': // Assuming the value is a simple key/identifier $sanitized_answer = sanitize_key($answer); break; case 'text': default: // Default to sanitize_text_field for 'text' or unknown/missing types $sanitized_answer = sanitize_text_field($answer); break; } } // 3. Save Answer (as user_evaluation CPT meta) // Use a meta key structure like 'quiztech_answer_{question_id}' or store in a single array meta field. // Using individual meta keys might be simpler for querying later if needed. $meta_key = 'quiztech_answer_' . $question_id; update_post_meta($evaluation_id, $meta_key, $sanitized_answer); // 4. Send Response wp_send_json_success(['message' => __('Answer saved.', 'quiztech')]); // Ensure script execution stops wp_die(); } /** * Handles the final AJAX submission of the assessment. * Expects 'nonce' and 'invitation_id' in $_POST. */ public function handle_submit_assessment() { // 1. Verify Nonce check_ajax_referer('quiztech_assessment_nonce', 'nonce'); // Reuse assessment nonce // 2. Get Data $invitation_id = isset($_POST['invitation_id']) ? absint($_POST['invitation_id']) : 0; if ( ! $invitation_id ) { wp_send_json_error(['message' => __('Missing invitation ID.', 'quiztech')], 400); } // 3. Get or Create User Evaluation Record $evaluation_id = $this->get_or_create_user_evaluation($invitation_id); if ( ! $evaluation_id ) { error_log("Quiztech AJAX Error: Failed to get or create user_evaluation for invitation ID: " . $invitation_id . " during final submission."); wp_send_json_error(['message' => __('Could not process evaluation record for submission.', 'quiztech')], 500); } // 4. Update Invitation Status try { $invitations = new Invitations(); // Assuming $invitation_id passed in POST *is* the record ID. $updated = $invitations->update_status($invitation_id, 'assessment-complete'); if (!$updated) { error_log("Quiztech AJAX Error: Failed to update invitation status to complete for ID: " . $invitation_id); // Decide if this should be a user-facing error or just logged } } catch (\Exception $e) { error_log("Quiztech AJAX Error: Exception updating invitation status to complete: " . $e->getMessage()); // Decide if this should be a user-facing error } // 5. Update User Evaluation CPT Status to 'completed' $post_update_data = [ 'ID' => $evaluation_id, 'post_status' => 'completed', // Use a custom status if needed, but 'completed' seems appropriate ]; $post_updated = wp_update_post($post_update_data, true); // Pass true for WP_Error object on failure if (is_wp_error($post_updated)) { error_log("Quiztech AJAX Error: Failed to update user_evaluation CPT status for ID {$evaluation_id}: " . $post_updated->get_error_message()); // Decide if this should be a user-facing error // wp_send_json_error(['message' => __('Failed to finalize assessment record.', 'quiztech')], 500); } // 6. Get the Assessment ID associated with the invitation // This requires a method in Invitations class to get the full record by ID $invitation_record = $invitations->get_invitation_by_id($invitation_id); // Assuming this method exists or will be added $assessment_id = $invitation_record ? $invitation_record->assessment_id : 0; // 7. Get the custom completion message $completion_message = ''; if ($assessment_id) { $completion_message = \get_post_meta($assessment_id, '_quiztech_completion_message', true); } // Use a default message if the custom one is empty if (empty($completion_message)) { $completion_message = __('Assessment submitted successfully!', 'quiztech'); } // 8. Send Response wp_send_json_success(['completionMessage' => $completion_message]); // Send completion message // Ensure script execution stops wp_die(); } }