304 lines
No EOL
12 KiB
PHP
304 lines
No EOL
12 KiB
PHP
<?php
|
|
|
|
namespace Quiztech\AssessmentPlatform\Includes\Ajax;
|
|
|
|
use Quiztech\AssessmentPlatform\Includes\Invitations; // Added use statement
|
|
|
|
/**
|
|
* Handles AJAX requests related to the front-end assessment process.
|
|
*/
|
|
class AssessmentAjaxHandler {
|
|
|
|
/**
|
|
* Constructor. Registers AJAX hooks.
|
|
*/
|
|
public function __construct() {
|
|
add_action('wp_ajax_quiztech_submit_prescreening', [$this, 'handle_submit_prescreening']);
|
|
add_action('wp_ajax_quiztech_save_answer', [$this, 'handle_save_answer']);
|
|
add_action('wp_ajax_quiztech_submit_assessment', [$this, 'handle_submit_assessment']);
|
|
add_action('wp_ajax_nopriv_quiztech_save_answer', [$this, 'handle_save_answer']);
|
|
add_action('wp_ajax_nopriv_quiztech_submit_assessment', [$this, 'handle_submit_assessment']);
|
|
}
|
|
|
|
/**
|
|
* Initialize the handler.
|
|
* Static method to instantiate the class and register hooks.
|
|
*/
|
|
public static function init() {
|
|
new self();
|
|
}
|
|
|
|
/**
|
|
* Helper method to find an existing user_evaluation CPT by invitation ID
|
|
* or create a new one if it doesn't exist.
|
|
*
|
|
* @param int $invitation_id The database ID of the invitation record.
|
|
* @return int The post ID of the user_evaluation CPT, or 0 on failure.
|
|
*/
|
|
private function get_or_create_user_evaluation(int $invitation_id): int {
|
|
if ( ! $invitation_id ) {
|
|
error_log("Quiztech AJAX Error: get_or_create_user_evaluation called with invalid invitation ID: " . $invitation_id);
|
|
return 0;
|
|
}
|
|
|
|
$args = [
|
|
'post_type' => '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();
|
|
}
|
|
} |