feat: Add meta boxes for correct answer and linked questions/assessments

This commit is contained in:
Ruben Ramirez 2025-04-03 19:32:38 -05:00
parent 6beb1c2251
commit fc3b9a0458

View file

@ -3,7 +3,7 @@
namespace Quiztech\AssessmentPlatform\Includes;
/**
* File for registering Custom Post Types.
* File for registering Custom Post Types and Meta Boxes.
*
* @package Quiztech
*/
@ -46,18 +46,18 @@ function quiztech_register_post_types() {
);
$question_args = array(
'labels' => $question_labels,
'public' => true, // Accessible in admin and potentially theme/API
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'question' ),
'capability_type' => 'post', // Consider custom capabilities later
'has_archive' => false, // No archive page for individual questions
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
'menu_position' => null,
'supports' => array( 'title', 'editor', 'custom-fields' ), // Title = Question text, Editor = Description/Context
'show_in_rest' => true, // Enable Gutenberg/REST API support
'supports' => array( 'title', 'editor', 'custom-fields' ),
'show_in_rest' => true,
'menu_icon' => 'dashicons-editor-help',
);
\register_post_type( 'question', $question_args );
@ -78,7 +78,6 @@ function quiztech_register_post_types() {
'parent_item_colon' => \__( 'Parent Assessments:', 'quiztech' ),
'not_found' => \__( 'No assessments found.', 'quiztech' ),
'not_found_in_trash' => \__( 'No assessments found in Trash.', 'quiztech' ),
// ... other labels similar to Question ...
);
$assessment_args = array(
'labels' => $assessment_labels,
@ -89,10 +88,10 @@ function quiztech_register_post_types() {
'query_var' => true,
'rewrite' => array( 'slug' => 'assessment' ),
'capability_type' => 'post',
'has_archive' => true, // Maybe an archive page listing available assessments?
'has_archive' => true,
'hierarchical' => false,
'menu_position' => null,
'supports' => array( 'title', 'editor', 'custom-fields' ), // Title = Assessment Name, Editor = Description
'supports' => array( 'title', 'editor', 'custom-fields' ),
'show_in_rest' => true,
'menu_icon' => 'dashicons-forms',
);
@ -114,7 +113,6 @@ function quiztech_register_post_types() {
'parent_item_colon' => \__( 'Parent Jobs:', 'quiztech' ),
'not_found' => \__( 'No jobs found.', 'quiztech' ),
'not_found_in_trash' => \__( 'No jobs found in Trash.', 'quiztech' ),
// ... other labels similar to Question ...
);
$job_args = array(
'labels' => $job_labels,
@ -125,10 +123,10 @@ function quiztech_register_post_types() {
'query_var' => true,
'rewrite' => array( 'slug' => 'job' ),
'capability_type' => 'post',
'has_archive' => true, // Archive page listing jobs makes sense
'has_archive' => true,
'hierarchical' => false,
'menu_position' => null,
'supports' => array( 'title', 'editor', 'custom-fields' ), // Title = Job Title, Editor = Job Description
'supports' => array( 'title', 'editor', 'custom-fields' ),
'show_in_rest' => true,
'menu_icon' => 'dashicons-businessman',
);
@ -150,22 +148,21 @@ function quiztech_register_post_types() {
'parent_item_colon' => \__( 'Parent Evaluations:', 'quiztech' ),
'not_found' => \__( 'No evaluations found.', 'quiztech' ),
'not_found_in_trash' => \__( 'No evaluations found in Trash.', 'quiztech' ),
// ... other labels similar to Question ...
);
$evaluation_args = array(
'labels' => $evaluation_labels,
'public' => false, // Evaluations should not be public facing
'publicly_queryable' => false, // Not queryable from front-end URLs
'show_ui' => true, // Show in admin UI / custom backend
'show_in_menu' => true, // Show in admin menu (maybe under Jobs or Assessments later?)
'query_var' => false, // No query var needed
'rewrite' => false, // No rewrite rules needed
'capability_type' => 'post', // Use post capabilities for now, refine later
'has_archive' => false, // No archive page
'public' => false,
'publicly_queryable' => false,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => false,
'rewrite' => false,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
'menu_position' => null,
'supports' => array( 'title', 'custom-fields' ), // Title could be "Applicant Email - Job Title", no editor needed
'show_in_rest' => true, // Allow access via REST API for custom backend/reporting
'supports' => array( 'title', 'custom-fields' ),
'show_in_rest' => true,
'menu_icon' => 'dashicons-clipboard',
);
\register_post_type( 'user_evaluation', $evaluation_args );
@ -173,27 +170,63 @@ function quiztech_register_post_types() {
\add_action( 'init', __NAMESPACE__ . '\quiztech_register_post_types' );
// --- Meta Box for Question Type ---
// --- Meta Box Registration ---
/**
* Adds the meta box container for Question Type.
* Adds meta boxes for various CPTs.
* Hooks into 'add_meta_boxes'.
*
* @param string $post_type The post type slug.
*/
function quiztech_add_question_meta_boxes( $post_type ) {
// Limit meta box to specific post type
if ( 'question' === $post_type ) {
\add_meta_box(
'quiztech_question_type_metabox', // ID
\__( 'Question Type', 'quiztech' ), // Title
__NAMESPACE__ . '\quiztech_render_question_type_metabox', // Callback function
'question', // Post type
'side', // Context (normal, side, advanced)
'high' // Priority (high, core, default, low)
);
}
function quiztech_add_all_meta_boxes( $post_type ) {
// Meta Boxes for 'question' CPT
if ( 'question' === $post_type ) {
\add_meta_box(
'quiztech_question_type_metabox',
\__( 'Question Type', 'quiztech' ),
__NAMESPACE__ . '\quiztech_render_question_type_metabox',
'question',
'normal', // Context below editor
'high'
);
\add_meta_box(
'quiztech_correct_answer_metabox',
\__( 'Correct Answer(s)', 'quiztech' ),
__NAMESPACE__ . '\quiztech_render_correct_answer_metabox',
'question',
'normal', // Context below editor
'high'
);
}
// Meta Box for 'job' CPT
if ( 'job' === $post_type ) {
\add_meta_box(
'quiztech_linked_assessment_metabox',
\__( 'Linked Assessment', 'quiztech' ),
__NAMESPACE__ . '\quiztech_render_linked_assessment_metabox',
'job',
'normal', // Context below editor
'default'
);
}
// Meta Box for 'assessment' CPT
if ( 'assessment' === $post_type ) {
\add_meta_box(
'quiztech_linked_questions_metabox',
\__( 'Linked Questions', 'quiztech' ),
__NAMESPACE__ . '\quiztech_render_linked_questions_metabox',
'assessment',
'normal', // Context below editor
'high'
);
}
}
\add_action( 'add_meta_boxes', __NAMESPACE__ . '\quiztech_add_question_meta_boxes' );
\add_action( 'add_meta_boxes', __NAMESPACE__ . '\quiztech_add_all_meta_boxes' );
// --- Meta Box Render Functions ---
/**
* Renders the meta box content for Question Type.
@ -201,87 +234,223 @@ function quiztech_add_question_meta_boxes( $post_type ) {
* @param \WP_Post $post The post object.
*/
function quiztech_render_question_type_metabox( $post ) {
// Add a nonce field so we can check for it later.
\wp_nonce_field( 'quiztech_save_question_type_meta', 'quiztech_question_type_nonce' );
\wp_nonce_field( 'quiztech_save_question_type_meta', 'quiztech_question_type_nonce' );
$value = \get_post_meta( $post->ID, '_quiztech_question_type', true );
$question_types = [
'text' => \__( 'Text (Single Line)', 'quiztech' ),
'textarea' => \__( 'Text Area (Multi-line)', 'quiztech' ),
'multiple-choice' => \__( 'Multiple Choice (Single Answer)', 'quiztech' ),
'checkbox' => \__( 'Checkboxes (Multiple Answers)', 'quiztech' ),
'numeric' => \__( 'Numeric', 'quiztech' ),
];
// Use get_post_meta to retrieve an existing value from the database.
$value = \get_post_meta( $post->ID, '_quiztech_question_type', true );
// Define the available question types
$question_types = [
'text' => \__( 'Text (Single Line)', 'quiztech' ),
'textarea' => \__( 'Text Area (Multi-line)', 'quiztech' ),
'multiple-choice' => \__( 'Multiple Choice (Single Answer)', 'quiztech' ),
'checkbox' => \__( 'Checkboxes (Multiple Answers)', 'quiztech' ),
'numeric' => \__( 'Numeric', 'quiztech' ),
// Add more types as needed
];
// Display the form field.
echo '<label for="quiztech_question_type_field">';
\esc_html_e( 'Select the type of question:', 'quiztech' );
echo '</label> ';
echo '<select name="quiztech_question_type_field" id="quiztech_question_type_field" class="postbox">';
echo '<option value="">' . \esc_html__( '-- Select Type --', 'quiztech' ) . '</option>'; // Default empty option
foreach ( $question_types as $type_key => $type_label ) {
echo '<option value="' . \esc_attr( $type_key ) . '" ' . \selected( $value, $type_key, false ) . '>' . \esc_html( $type_label ) . '</option>';
}
echo '</select>';
echo '<label for="quiztech_question_type_field">';
\esc_html_e( 'Select the type of question:', 'quiztech' );
echo '</label> ';
echo '<select name="quiztech_question_type_field" id="quiztech_question_type_field" class="postbox">';
echo '<option value="">' . \esc_html__( '-- Select Type --', 'quiztech' ) . '</option>';
foreach ( $question_types as $type_key => $type_label ) {
echo '<option value="' . \esc_attr( $type_key ) . '" ' . \selected( $value, $type_key, false ) . '>' . \esc_html( $type_label ) . '</option>';
}
echo '</select>';
}
/**
* Renders the meta box content for Correct Answer.
*
* @param \WP_Post $post The post object.
*/
function quiztech_render_correct_answer_metabox( $post ) {
\wp_nonce_field( 'quiztech_save_correct_answer_meta', 'quiztech_correct_answer_nonce' );
$value = \get_post_meta( $post->ID, '_quiztech_correct_answer', true );
echo '<label for="quiztech_correct_answer_field">';
\esc_html_e( 'Enter the correct answer(s). For multiple choice/checkboxes, enter each correct option on a new line.', 'quiztech' );
echo '</label><br />';
echo '<textarea name="quiztech_correct_answer_field" id="quiztech_correct_answer_field" class="large-text" rows="5">' . \esc_textarea( $value ) . '</textarea>';
echo '<p class="description">' . \esc_html__( 'Note: How this field is interpreted depends on the Question Type selected above.', 'quiztech' ) . '</p>';
}
/**
* Renders the meta box content for selecting an assessment for a job.
*
* @param \WP_Post $post The post object.
*/
function quiztech_render_linked_assessment_metabox( $post ) {
\wp_nonce_field( 'quiztech_save_linked_assessment_meta', 'quiztech_linked_assessment_nonce' );
$linked_assessment_id = \get_post_meta( $post->ID, '_quiztech_linked_assessment_id', true );
$assessments_query = new \WP_Query( [
'post_type' => 'assessment',
'posts_per_page' => -1,
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
] );
echo '<label for="quiztech_linked_assessment_field">';
\esc_html_e( 'Select the assessment for this job:', 'quiztech' );
echo '</label><br />';
if ( $assessments_query->have_posts() ) {
echo '<select name="quiztech_linked_assessment_field" id="quiztech_linked_assessment_field" class="widefat">';
echo '<option value="">' . \esc_html__( '-- None --', 'quiztech' ) . '</option>';
while ( $assessments_query->have_posts() ) {
$assessments_query->the_post();
$assessment_id = \get_the_ID();
$assessment_title = \get_the_title();
echo '<option value="' . \esc_attr( $assessment_id ) . '" ' . \selected( $linked_assessment_id, $assessment_id, false ) . '>' . \esc_html( $assessment_title ) . '</option>';
}
echo '</select>';
\wp_reset_postdata();
} else {
echo '<p>' . \esc_html__( 'No assessments found. Please create an assessment first.', 'quiztech' ) . '</p>';
}
}
/**
* Renders the meta box content for selecting questions for an assessment.
*
* @param \WP_Post $post The post object.
*/
function quiztech_render_linked_questions_metabox( $post ) {
\wp_nonce_field( 'quiztech_save_linked_questions_meta', 'quiztech_linked_questions_nonce' );
$linked_question_ids = \get_post_meta( $post->ID, '_quiztech_linked_question_ids', true );
if ( ! is_array( $linked_question_ids ) ) {
$linked_question_ids = [];
}
$questions_query = new \WP_Query( [
'post_type' => 'question',
'posts_per_page' => -1,
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
] );
echo '<div style="max-height: 300px; overflow-y: auto; border: 1px solid #ccd0d4; padding: 10px;">';
echo '<p>' . \esc_html__( 'Select the questions to include in this assessment:', 'quiztech' ) . '</p>';
if ( $questions_query->have_posts() ) {
echo '<ul>';
while ( $questions_query->have_posts() ) {
$questions_query->the_post();
$question_id = \get_the_ID();
$question_title = \get_the_title();
$is_checked = in_array( $question_id, $linked_question_ids, true ); // Use strict comparison
echo '<li>';
echo '<label>';
echo '<input type="checkbox" name="quiztech_linked_questions_field[]" value="' . \esc_attr( $question_id ) . '" ' . \checked( $is_checked, true, false ) . '>';
echo ' ' . \esc_html( $question_title );
echo '</label>';
echo '</li>';
}
echo '</ul>';
\wp_reset_postdata();
} else {
echo '<p>' . \esc_html__( 'No questions found. Please create some questions first.', 'quiztech' ) . '</p>';
}
echo '</div>';
}
// --- Meta Box Save Functions ---
/**
* Saves the meta box data for Question Type.
* Hooks into 'save_post_question'.
*
* @param int $post_id The ID of the post being saved.
*/
function quiztech_save_question_type_meta( $post_id ) {
// Check if our nonce is set.
if ( ! isset( $_POST['quiztech_question_type_nonce'] ) ) {
return;
}
// Basic checks (nonce, autosave, permissions)
if ( ! isset( $_POST['quiztech_question_type_nonce'] ) || ! \wp_verify_nonce( \sanitize_key( $_POST['quiztech_question_type_nonce'] ), 'quiztech_save_question_type_meta' ) ) { return; }
if ( \defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; }
if ( ! \current_user_can( 'edit_post', $post_id ) ) { return; }
// Verify that the nonce is valid.
if ( ! \wp_verify_nonce( \sanitize_key( $_POST['quiztech_question_type_nonce'] ), 'quiztech_save_question_type_meta' ) ) {
return;
}
// Field check and sanitize
if ( ! isset( $_POST['quiztech_question_type_field'] ) ) { return; }
$new_meta_value = \sanitize_text_field( \wp_unslash( $_POST['quiztech_question_type_field'] ) );
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( \defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Check the user's permissions.
if ( isset( $_POST['post_type'] ) && 'question' === $_POST['post_type'] ) {
if ( ! \current_user_can( 'edit_post', $post_id ) ) {
return;
}
} else {
// Assuming other post types don't use this meta box
return;
}
// Make sure that the field is set.
if ( ! isset( $_POST['quiztech_question_type_field'] ) ) {
return;
}
// Sanitize user input.
$new_meta_value = \sanitize_text_field( \wp_unslash( $_POST['quiztech_question_type_field'] ) );
// Define allowed types again for validation
$allowed_types = ['text', 'textarea', 'multiple-choice', 'checkbox', 'numeric']; // Keep this in sync with the render function
// Update the meta field in the database if the value is allowed or empty.
if ( in_array( $new_meta_value, $allowed_types, true ) || '' === $new_meta_value ) {
\update_post_meta( $post_id, '_quiztech_question_type', $new_meta_value );
} else {
// Optionally delete meta if invalid value submitted, or log an error
\delete_post_meta( $post_id, '_quiztech_question_type' );
}
// Validation against allowed types
$allowed_types = ['text', 'textarea', 'multiple-choice', 'checkbox', 'numeric'];
if ( in_array( $new_meta_value, $allowed_types, true ) || '' === $new_meta_value ) {
\update_post_meta( $post_id, '_quiztech_question_type', $new_meta_value );
} else {
\delete_post_meta( $post_id, '_quiztech_question_type' );
}
}
// Hook into the 'save_post' action specifically for the 'question' post type
\add_action( 'save_post_question', __NAMESPACE__ . '\quiztech_save_question_type_meta' );
/**
* Saves the meta box data for Correct Answer.
* Hooks into 'save_post_question'.
*
* @param int $post_id The ID of the post being saved.
*/
function quiztech_save_correct_answer_meta( $post_id ) {
// Basic checks (nonce, autosave, permissions)
if ( ! isset( $_POST['quiztech_correct_answer_nonce'] ) || ! \wp_verify_nonce( \sanitize_key( $_POST['quiztech_correct_answer_nonce'] ), 'quiztech_save_correct_answer_meta' ) ) { return; }
if ( \defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; }
if ( ! \current_user_can( 'edit_post', $post_id ) ) { return; }
// Field check and sanitize
if ( isset( $_POST['quiztech_correct_answer_field'] ) ) {
$new_meta_value = \sanitize_textarea_field( \wp_unslash( $_POST['quiztech_correct_answer_field'] ) );
\update_post_meta( $post_id, '_quiztech_correct_answer', $new_meta_value );
} else {
// Delete if field is expected but not submitted
\delete_post_meta( $post_id, '_quiztech_correct_answer' );
}
}
\add_action( 'save_post_question', __NAMESPACE__ . '\quiztech_save_correct_answer_meta' );
/**
* Saves the meta box data for the linked assessment on a job.
* Hooks into 'save_post_job'.
*
* @param int $post_id The ID of the post being saved.
*/
function quiztech_save_linked_assessment_meta( $post_id ) {
// Basic checks (nonce, autosave, permissions)
if ( ! isset( $_POST['quiztech_linked_assessment_nonce'] ) || ! \wp_verify_nonce( \sanitize_key( $_POST['quiztech_linked_assessment_nonce'] ), 'quiztech_save_linked_assessment_meta' ) ) { return; }
if ( \defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; }
if ( ! \current_user_can( 'edit_post', $post_id ) ) { return; }
// Field check and sanitize
if ( isset( $_POST['quiztech_linked_assessment_field'] ) ) {
$assessment_id = \sanitize_text_field( \wp_unslash( $_POST['quiztech_linked_assessment_field'] ) );
$assessment_id = '' === $assessment_id ? '' : \absint( $assessment_id );
\update_post_meta( $post_id, '_quiztech_linked_assessment_id', $assessment_id );
} else {
\delete_post_meta( $post_id, '_quiztech_linked_assessment_id' );
}
}
\add_action( 'save_post_job', __NAMESPACE__ . '\quiztech_save_linked_assessment_meta' );
/**
* Saves the meta box data for the linked questions on an assessment.
* Hooks into 'save_post_assessment'.
*
* @param int $post_id The ID of the post being saved.
*/
function quiztech_save_linked_questions_meta( $post_id ) {
// Basic checks (nonce, autosave, permissions)
if ( ! isset( $_POST['quiztech_linked_questions_nonce'] ) || ! \wp_verify_nonce( \sanitize_key( $_POST['quiztech_linked_questions_nonce'] ), 'quiztech_save_linked_questions_meta' ) ) { return; }
if ( \defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; }
if ( ! \current_user_can( 'edit_post', $post_id ) ) { return; }
// Process submitted IDs
$submitted_ids = [];
if ( isset( $_POST['quiztech_linked_questions_field'] ) && is_array( $_POST['quiztech_linked_questions_field'] ) ) {
$submitted_ids = array_map( 'absint', $_POST['quiztech_linked_questions_field'] );
$submitted_ids = array_filter( $submitted_ids ); // Remove zeros/invalid entries
}
// Update meta (even if empty array, to clear previous selections)
\update_post_meta( $post_id, '_quiztech_linked_question_ids', $submitted_ids );
}
\add_action( 'save_post_assessment', __NAMESPACE__ . '\quiztech_save_linked_questions_meta' );
?>