feat: Implement front-end assessment interaction AJAX flow
- Add AssessmentAjaxHandler class for AJAX requests. - Add assessment.js for front-end logic. - Implement pre-screening form submission via AJAX. - Implement answer auto-save via AJAX. - Implement final assessment submission via AJAX. - Update assessment-shell.php template for dynamic rendering and JS hooks. - Enqueue and localize assessment.js conditionally. Refs: assessment_interaction_plan.md Note: Includes TODOs for evaluation CPT handling, status updates, and sanitization.
This commit is contained in:
parent
d63aa8d409
commit
130b9eefb9
5 changed files with 394 additions and 9 deletions
163
public/js/assessment.js
Normal file
163
public/js/assessment.js
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* Quiztech Assessment Interaction Script
|
||||
*
|
||||
* Handles AJAX submissions for pre-screening, answer auto-save,
|
||||
* and final assessment submission.
|
||||
*/
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
$(function() {
|
||||
console.log('Assessment script loaded.');
|
||||
|
||||
// Check if localized data is available
|
||||
if (typeof quiztech_assessment_vars === 'undefined') {
|
||||
console.error('Quiztech Assessment Error: Localized variables not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
var $prescreeningForm = $('#quiztech-prescreening-form');
|
||||
var $prescreeningSection = $('#quiztech-prescreening-section');
|
||||
var $assessmentSection = $('#quiztech-assessment-section');
|
||||
var $submitButton = $prescreeningForm.find('button[type="submit"]');
|
||||
var $formMessages = $('<div class="form-messages"></div>').insertBefore($submitButton); // Area for messages
|
||||
|
||||
// --- Pre-Screening Form Handling ---
|
||||
$prescreeningForm.on('submit', function(event) {
|
||||
event.preventDefault(); // Stop traditional form submission
|
||||
|
||||
$formMessages.empty().removeClass('error success'); // Clear previous messages
|
||||
$submitButton.prop('disabled', true).text('Submitting...'); // Disable button
|
||||
|
||||
var formData = $(this).serialize(); // Get form data
|
||||
|
||||
// Add required AJAX parameters
|
||||
formData += '&action=quiztech_submit_prescreening';
|
||||
formData += '&nonce=' + quiztech_assessment_vars.prescreening_nonce;
|
||||
formData += '&invitation_id=' + quiztech_assessment_vars.invitation_id;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: quiztech_assessment_vars.ajax_url,
|
||||
data: formData,
|
||||
dataType: 'json', // Expect JSON response from server
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Success! Hide pre-screening, show assessment
|
||||
$formMessages.addClass('success').text(response.data.message || 'Success!'); // Show success message briefly
|
||||
$prescreeningSection.slideUp();
|
||||
$assessmentSection.slideDown();
|
||||
// No need to re-enable button as the form is gone
|
||||
} else {
|
||||
// Handle WP JSON error
|
||||
$formMessages.addClass('error').text(response.data.message || 'An error occurred.');
|
||||
$submitButton.prop('disabled', false).text('Submit Pre-Screening & Start Assessment'); // Re-enable button
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
// Handle general AJAX error
|
||||
console.error("AJAX Error:", textStatus, errorThrown);
|
||||
$formMessages.addClass('error').text('A network error occurred. Please try again.');
|
||||
$submitButton.prop('disabled', false).text('Submit Pre-Screening & Start Assessment'); // Re-enable button
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- Assessment Answer Auto-Save ---
|
||||
var $assessmentForm = $('#quiztech-assessment-form');
|
||||
var autoSaveTimeout; // To debounce requests
|
||||
|
||||
// Target input/textarea/select elements within the assessment form for auto-save
|
||||
$assessmentForm.on('change blur', 'input, textarea, select', function() {
|
||||
clearTimeout(autoSaveTimeout); // Clear previous timeout if exists
|
||||
|
||||
var $input = $(this);
|
||||
var $questionGroup = $input.closest('.question-group');
|
||||
var questionId = $questionGroup.data('question-id');
|
||||
var answer = $input.val();
|
||||
|
||||
// Add a small visual indicator within the question group
|
||||
var $indicator = $questionGroup.find('.save-indicator');
|
||||
if ($indicator.length === 0) {
|
||||
$indicator = $('<span class="save-indicator" style="margin-left: 10px; font-size: 0.8em; color: grey;"></span>').appendTo($questionGroup.find('label:first'));
|
||||
}
|
||||
$indicator.text('Saving...');
|
||||
|
||||
// Debounce the AJAX request slightly
|
||||
autoSaveTimeout = setTimeout(function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: quiztech_assessment_vars.ajax_url,
|
||||
data: {
|
||||
action: 'quiztech_save_answer',
|
||||
nonce: quiztech_assessment_vars.assessment_nonce, // Use the correct nonce
|
||||
invitation_id: quiztech_assessment_vars.invitation_id,
|
||||
question_id: questionId,
|
||||
answer: answer
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$indicator.text('Saved ✓').css('color', 'green');
|
||||
// Optionally fade out the indicator after a delay
|
||||
setTimeout(function() { $indicator.fadeOut().remove(); }, 2000);
|
||||
} else {
|
||||
$indicator.text('Error!').css('color', 'red');
|
||||
console.error("Auto-save error:", response.data.message);
|
||||
// Consider more prominent error display if needed
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
$indicator.text('Network Error!').css('color', 'red');
|
||||
console.error("AJAX Error:", textStatus, errorThrown);
|
||||
}
|
||||
});
|
||||
}, 500); // Wait 500ms after the last change/blur before sending
|
||||
});
|
||||
|
||||
|
||||
// --- Final Assessment Submission ---
|
||||
var $submitAssessmentButton = $('#quiztech-submit-assessment');
|
||||
var $assessmentFormMessages = $('<div class="form-messages"></div>').insertAfter($submitAssessmentButton); // Area for messages
|
||||
|
||||
$submitAssessmentButton.on('click', function(event) {
|
||||
event.preventDefault(); // Stop traditional form submission (though AJAX auto-save handles data)
|
||||
|
||||
if (!confirm('Are you sure you want to submit your assessment?')) {
|
||||
return; // User cancelled
|
||||
}
|
||||
|
||||
$assessmentFormMessages.empty().removeClass('error success');
|
||||
$submitAssessmentButton.prop('disabled', true).text('Submitting...');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: quiztech_assessment_vars.ajax_url,
|
||||
data: {
|
||||
action: 'quiztech_submit_assessment',
|
||||
nonce: quiztech_assessment_vars.assessment_nonce, // Reuse assessment nonce
|
||||
invitation_id: quiztech_assessment_vars.invitation_id
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Success! Display message and potentially hide the form/button
|
||||
$assessmentFormMessages.addClass('success').text(response.data.message || 'Assessment Submitted Successfully!');
|
||||
$assessmentForm.hide(); // Hide the form after successful submission
|
||||
// Optionally redirect: window.location.href = response.data.redirect_url;
|
||||
} else {
|
||||
$assessmentFormMessages.addClass('error').text(response.data.message || 'An error occurred during submission.');
|
||||
$submitAssessmentButton.prop('disabled', false).text('Submit Assessment'); // Re-enable button
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
console.error("AJAX Error:", textStatus, errorThrown);
|
||||
$assessmentFormMessages.addClass('error').text('A network error occurred. Please try again.');
|
||||
$submitAssessmentButton.prop('disabled', false).text('Submit Assessment'); // Re-enable button
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}); // End document ready
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -56,15 +56,17 @@ if ( ! $invitation_data || ! $current_step ) {
|
|||
<h1><?php esc_html_e( 'Assessment Invitation', 'quiztech' ); ?></h1>
|
||||
|
||||
<?php if ( 'pre_screening' === $current_step ) : ?>
|
||||
|
||||
<div id="quiztech-prescreening-section"> <?php // Added ID for JS targeting ?>
|
||||
<div class="step-info">
|
||||
<h2><?php esc_html_e( 'Step 1: Pre-Screening Questions', 'quiztech' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Please answer the following questions before starting the assessment.', 'quiztech' ); ?></p>
|
||||
</div>
|
||||
|
||||
<form method="post" action=""> <?php // TODO: Add action URL and nonce for processing submission ?>
|
||||
<?php wp_nonce_field( 'quiztech_submit_prescreening_' . $invitation_data->token, 'quiztech_prescreening_nonce' ); ?>
|
||||
<input type="hidden" name="quiztech_invitation_token" value="<?php echo esc_attr( $invitation_data->token ); ?>">
|
||||
<form id="quiztech-prescreening-form" method="post" action=""> <?php // Action handled by AJAX ?>
|
||||
<?php // Nonce is checked via AJAX, but good to have in form too for non-JS fallback (if implemented) ?>
|
||||
<?php // wp_nonce_field( 'quiztech_submit_prescreening_' . $invitation_data->token, 'quiztech_prescreening_nonce' ); ?>
|
||||
<?php // Token is passed via localized script vars ?>
|
||||
<?php // <input type="hidden" name="quiztech_invitation_token" value="<?php echo esc_attr( $invitation_data->token ); ?>"> ?>
|
||||
<input type="hidden" name="action" value="quiztech_submit_prescreening">
|
||||
|
||||
<?php if ( is_array( $pre_screening_questions ) && ! empty( $pre_screening_questions ) ) : ?>
|
||||
|
|
@ -90,9 +92,10 @@ if ( ! $invitation_data || ! $current_step ) {
|
|||
<?php // Render questions here - Removed, handled above ?>
|
||||
<button type="submit"><?php esc_html_e( 'Submit Pre-Screening & Start Assessment', 'quiztech' ); ?></button>
|
||||
</form>
|
||||
</div> <!-- #quiztech-prescreening-section -->
|
||||
|
||||
<?php elseif ( 'assessment' === $current_step ) : ?>
|
||||
|
||||
<div id="quiztech-assessment-section" style="display: none;"> <?php // Added ID and initially hidden ?>
|
||||
<div class="step-info">
|
||||
<h2><?php esc_html_e( 'Step 2: Assessment', 'quiztech' ); ?></h2>
|
||||
<p><?php printf( esc_html__( 'You are about to begin Assessment ID: %d', 'quiztech' ), absint( $invitation_data->assessment_id ) ); ?></p>
|
||||
|
|
@ -132,10 +135,25 @@ if ( ! $invitation_data || ! $current_step ) {
|
|||
<?php break; ?>
|
||||
|
||||
<?php case 'multiple-choice': ?>
|
||||
<?php // TODO: Fetch actual choices from post meta ?>
|
||||
<label><input type="radio" name="assessment_answer[<?php echo esc_attr( $question_id ); ?>]" value="opt1" required> Option 1</label><br>
|
||||
<label><input type="radio" name="assessment_answer[<?php echo esc_attr( $question_id ); ?>]" value="opt2"> Option 2</label><br>
|
||||
<label><input type="radio" name="assessment_answer[<?php echo esc_attr( $question_id ); ?>]" value="opt3"> Option 3</label>
|
||||
<?php
|
||||
$choices = get_post_meta( $question_id, 'question_choices', true );
|
||||
if ( is_array( $choices ) && ! empty( $choices ) ) :
|
||||
foreach ( $choices as $choice_index => $choice_text ) :
|
||||
$choice_value = esc_attr( $choice_text ); // Use text as value for simplicity
|
||||
$choice_id = 'choice_' . esc_attr( $question_id ) . '_' . esc_attr( $choice_index );
|
||||
?>
|
||||
<label for="<?php echo $choice_id; ?>">
|
||||
<input type="radio"
|
||||
id="<?php echo $choice_id; ?>"
|
||||
name="assessment_answer[<?php echo esc_attr( $question_id ); ?>]"
|
||||
value="<?php echo $choice_value; ?>"
|
||||
required>
|
||||
<?php echo esc_html( $choice_text ); ?>
|
||||
</label><br>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<p class="error"><?php esc_html_e( 'Error: Choices not found for this question.', 'quiztech' ); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php break; ?>
|
||||
|
||||
<?php case 'true-false': ?>
|
||||
|
|
@ -160,6 +178,7 @@ if ( ! $invitation_data || ! $current_step ) {
|
|||
endif;
|
||||
?>
|
||||
<?php // Removed the simple "Start Assessment" button, replaced by question rendering logic ?>
|
||||
</div> <!-- #quiztech-assessment-section -->
|
||||
|
||||
<?php else : ?>
|
||||
<p class="error"><?php esc_html_e( 'An unexpected error occurred. Invalid assessment step.', 'quiztech' ); ?></p>
|
||||
|
|
|
|||
|
|
@ -116,6 +116,10 @@ function quiztech_init() {
|
|||
$frontend_handler = new \Quiztech\AssessmentPlatform\Includes\FrontendHandler();
|
||||
$frontend_handler->init_hooks();
|
||||
|
||||
|
||||
// Initialize AJAX handler for assessment interactions
|
||||
\Quiztech\AssessmentPlatform\Includes\Ajax\AssessmentAjaxHandler::init();
|
||||
|
||||
// TODO: Instantiate other core classes and call their init methods here
|
||||
// e.g., Admin menu handler, AJAX handlers, Shortcode handlers etc.
|
||||
}
|
||||
|
|
|
|||
177
src/Includes/Ajax/AssessmentAjaxHandler.php
Normal file
177
src/Includes/Ajax/AssessmentAjaxHandler.php
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Quiztech\\AssessmentPlatform\\Includes\\Ajax;
|
||||
|
||||
/**
|
||||
* 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']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the handler.
|
||||
* Static method to instantiate the class and register hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
new self();
|
||||
}
|
||||
|
||||
// AJAX handler methods will be added below:
|
||||
// - handle_submit_prescreening()
|
||||
// - handle_save_answer()
|
||||
// - handle_submit_assessment()
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// --- TODO: Ensure user_evaluation CPT is created earlier (e.g., in FrontendHandler) ---
|
||||
// For now, query for it based on invitation ID (assuming meta field 'quiztech_invitation_id' exists on evaluation)
|
||||
$args = [
|
||||
'post_type' => 'user_evaluation',
|
||||
'post_status' => 'any', // Find it regardless of status
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => 'quiztech_invitation_id', // Assumed meta key linking evaluation to invitation
|
||||
'value' => $invitation_id,
|
||||
'compare' => '=',
|
||||
]
|
||||
],
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids', // Only get the ID
|
||||
];
|
||||
$evaluation_posts = get_posts($args);
|
||||
$evaluation_id = !empty($evaluation_posts) ? $evaluation_posts[0] : 0;
|
||||
|
||||
if ( ! $evaluation_id ) {
|
||||
// If not found, something is wrong with the flow (should have been created earlier)
|
||||
error_log("Quiztech AJAX Error: Could not find user_evaluation CPT for invitation ID: " . $invitation_id);
|
||||
wp_send_json_error(['message' => __('Could not find associated evaluation record.', 'quiztech')], 404);
|
||||
}
|
||||
// --- End TODO section ---
|
||||
|
||||
// 3. 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 \Quiztech\AssessmentPlatform\Includes\Invitations();
|
||||
// TODO: Create the update_status method in Invitations class
|
||||
$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
|
||||
|
||||
// TODO: Get the associated user_evaluation CPT ID based on invitation_id (similar to pre-screening handler)
|
||||
$evaluation_id = 0; // Placeholder
|
||||
|
||||
if ( ! $invitation_id || ! $question_id || ! $evaluation_id ) {
|
||||
wp_send_json_error(['message' => __('Invalid request data for saving answer.', 'quiztech')], 400);
|
||||
}
|
||||
|
||||
// TODO: Fetch question type for $question_id to apply appropriate sanitization to $answer
|
||||
// Example: if type is 'text', use sanitize_textarea_field; if 'multiple-choice', check against allowed values.
|
||||
$sanitized_answer = sanitize_text_field($answer); // Basic sanitization for now
|
||||
|
||||
// 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;
|
||||
|
||||
// TODO: Get the associated user_evaluation CPT ID based on invitation_id (similar to other handlers)
|
||||
$evaluation_id = 0; // Placeholder
|
||||
|
||||
if ( ! $invitation_id || ! $evaluation_id ) {
|
||||
wp_send_json_error(['message' => __('Invalid request data for final submission.', 'quiztech')], 400);
|
||||
}
|
||||
|
||||
// 3. Update Invitation Status
|
||||
// TODO: Call Invitations->update_status($invitation_id, 'assessment-complete');
|
||||
|
||||
// 4. Update User Evaluation CPT Status
|
||||
// TODO: Update post status of $evaluation_id to 'completed' using wp_update_post()
|
||||
|
||||
// 5. Send Response
|
||||
// TODO: Consider adding a redirect URL or specific completion message to the response data
|
||||
wp_send_json_success(['message' => __('Assessment submitted successfully!', 'quiztech')]);
|
||||
|
||||
// Ensure script execution stops
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,28 @@ class FrontendHandler {
|
|||
// This template should be located within the plugin or theme.
|
||||
$template_path = QUIZTECH_PLUGIN_DIR . 'public/templates/assessment-shell.php'; // Example path
|
||||
|
||||
|
||||
// Enqueue and localize the assessment script
|
||||
wp_enqueue_script(
|
||||
'quiztech-assessment-script',
|
||||
QUIZTECH_PLUGIN_URL . 'public/js/assessment.js',
|
||||
['jquery'],
|
||||
QUIZTECH_VERSION,
|
||||
true // Load in footer
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'quiztech-assessment-script',
|
||||
'quiztech_assessment_vars',
|
||||
[
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'prescreening_nonce' => wp_create_nonce('quiztech_prescreening_nonce'),
|
||||
'assessment_nonce' => wp_create_nonce('quiztech_assessment_nonce'),
|
||||
'invitation_id' => $invitation_data->id, // Pass invitation ID for context
|
||||
// Add other necessary vars here, e.g., evaluation ID if available
|
||||
]
|
||||
);
|
||||
|
||||
if ( file_exists( $template_path ) ) {
|
||||
include( $template_path );
|
||||
exit; // Important: Stop WordPress from loading the default theme template
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue