From fc3b9a0458196a6f92eb443829aabfe0e91749c4 Mon Sep 17 00:00:00 2001 From: Ruben Ramirez Date: Thu, 3 Apr 2025 19:32:38 -0500 Subject: [PATCH] feat: Add meta boxes for correct answer and linked questions/assessments --- src/Includes/post-types.php | 383 ++++++++++++++++++++++++++---------- 1 file changed, 276 insertions(+), 107 deletions(-) diff --git a/src/Includes/post-types.php b/src/Includes/post-types.php index b399ab8..95a7ee0 100644 --- a/src/Includes/post-types.php +++ b/src/Includes/post-types.php @@ -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 ' '; - echo ''; + echo ' '; + echo ''; } +/** + * 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 '
'; + echo ''; + echo '

' . \esc_html__( 'Note: How this field is interpreted depends on the Question Type selected above.', 'quiztech' ) . '

'; +} + +/** + * 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 '
'; + + if ( $assessments_query->have_posts() ) { + echo ''; + \wp_reset_postdata(); + } else { + echo '

' . \esc_html__( 'No assessments found. Please create an assessment first.', 'quiztech' ) . '

'; + } +} + +/** + * 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 '
'; + echo '

' . \esc_html__( 'Select the questions to include in this assessment:', 'quiztech' ) . '

'; + + if ( $questions_query->have_posts() ) { + echo ''; + \wp_reset_postdata(); + } else { + echo '

' . \esc_html__( 'No questions found. Please create some questions first.', 'quiztech' ) . '

'; + } + echo '
'; +} + + +// --- 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' ); + ?> \ No newline at end of file