Refactor: Move CPT metabox logic to dedicated Admin classes

This commit is contained in:
Ruben Ramirez 2025-04-03 23:58:15 -05:00
parent e26f9ec54f
commit 04bf4adfb0
5 changed files with 460 additions and 289 deletions

View file

@ -39,6 +39,17 @@ require_once QUIZTECH_PLUGIN_DIR . 'src/Includes/roles.php';
require_once QUIZTECH_PLUGIN_DIR . 'src/Includes/post-types.php';
require_once QUIZTECH_PLUGIN_DIR . 'src/Includes/taxonomies.php';
// Use statements for Admin classes
use Quiztech\AssessmentPlatform\Admin\AdminListTables;
use Quiztech\AssessmentPlatform\Admin\SettingsPage;
use Quiztech\AssessmentPlatform\Admin\JobMetaboxes;
use Quiztech\AssessmentPlatform\Admin\QuestionMetaboxes;
use Quiztech\AssessmentPlatform\Admin\AssessmentMetaboxes;
use Quiztech\AssessmentPlatform\Includes\FrontendHandler;
use Quiztech\AssessmentPlatform\Includes\Ajax\AssessmentAjaxHandler;
use Quiztech\AssessmentPlatform\Includes\Ajax\AssessmentBuilderAjaxHandler;
/**
* Load plugin dependencies. (Now handled by autoloading)
*/
@ -195,14 +206,20 @@ function quiztech_init() {
// Initialize Admin-specific features
if ( is_admin() ) {
$admin_list_tables = new \Quiztech\AssessmentPlatform\Admin\AdminListTables();
$admin_list_tables = new AdminListTables();
$admin_list_tables->register_hooks();
$settings_page = new \Quiztech\AssessmentPlatform\Admin\SettingsPage();
$settings_page = new SettingsPage();
$settings_page->register_hooks();
$job_metaboxes = new \Quiztech\AssessmentPlatform\Admin\JobMetaboxes();
$job_metaboxes->register_hooks();
$job_metaboxes = new JobMetaboxes();
$job_metaboxes->register_hooks();
$question_metaboxes = new QuestionMetaboxes();
$question_metaboxes->register_hooks();
$assessment_metaboxes = new AssessmentMetaboxes();
$assessment_metaboxes->register_hooks();
}
// Future core classes (e.g., Shortcode handlers) should be instantiated here.

View file

@ -0,0 +1,109 @@
<?php
namespace Quiztech\AssessmentPlatform\Admin;
// If this file is called directly, abort.
if ( ! \defined( 'WPINC' ) ) {
die;
}
/**
* Handles metaboxes for the Assessment CPT edit screen.
*/
class AssessmentMetaboxes {
/**
* Register hooks for the metaboxes.
*/
public function register_hooks() {
add_action( 'add_meta_boxes', [ $this, 'add_assessment_metaboxes' ] );
add_action( 'save_post_assessment', [ $this, 'save_linked_questions_meta' ] );
}
/**
* Adds the metaboxes to the Assessment CPT.
*
* @param string $post_type The current post type.
*/
public function add_assessment_metaboxes( $post_type ) {
if ( 'assessment' === $post_type ) {
\add_meta_box(
'quiztech_linked_questions_metabox',
\__( 'Linked Questions', 'quiztech' ),
[ $this, 'render_linked_questions_metabox' ],
'assessment',
'normal', // Context below editor
'high'
);
}
}
/**
* Renders the meta box content for selecting questions for an assessment.
*
* @param \WP_Post $post The post object.
*/
public function 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>';
}
/**
* Saves the meta box data for the linked questions on an assessment.
*
* @param int $post_id The ID of the post being saved.
*/
public function 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; }
// No need to check post type here, as the action is specific ('save_post_assessment')
// 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 );
}
}

View file

@ -13,13 +13,43 @@ class JobMetaboxes {
* Register hooks for the metaboxes.
*/
public function register_hooks() {
add_action( 'add_meta_boxes', [ $this, 'add_send_invite_metabox' ] );
add_action( 'add_meta_boxes', [ $this, 'add_job_metaboxes' ] ); // Consolidated add_meta_boxes hook
add_action( 'save_post_job', [ $this, 'save_linked_assessment_meta' ] ); // Hook for saving linked assessment
add_action( 'wp_ajax_quiztech_send_job_invite', [ $this, 'handle_send_invite_ajax' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
}
/**
* Adds the "Send Invite" metabox to the Job CPT.
* Adds all metaboxes for the Job CPT.
*
* @param string $post_type The current post type.
*/
public function add_job_metaboxes( $post_type ) {
if ( 'job' === $post_type ) {
// Send Invite Metabox (Side)
add_meta_box(
'quiztech_send_invite_metabox', // Metabox ID
__( 'Send Assessment Invite', 'quiztech' ), // Title
[ $this, 'render_send_invite_metabox' ], // Callback function
'job', // Post type
'side', // Context
'high' // Priority
);
// Linked Assessment Metabox (Normal)
add_meta_box(
'quiztech_linked_assessment_metabox',
__( 'Linked Assessment', 'quiztech' ),
[ $this, 'render_linked_assessment_metabox' ],
'job',
'normal', // Context below editor
'default'
);
}
}
/**
* Adds the "Send Invite" metabox to the Job CPT. - DEPRECATED (consolidated into add_job_metaboxes)
*
* @param string $post_type The current post type.
*/
@ -136,4 +166,67 @@ class JobMetaboxes {
]);
}
}
/**
* Renders the meta box content for selecting an assessment for a job.
*
* @param \WP_Post $post The post object.
*/
public function 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 '&lt;label for="quiztech_linked_assessment_field"&gt;';
\esc_html_e( 'Select the assessment for this job:', 'quiztech' );
echo '&lt;/label&gt;&lt;br /&gt;';
if ( $assessments_query->have_posts() ) {
echo '&lt;select name="quiztech_linked_assessment_field" id="quiztech_linked_assessment_field" class="widefat"&gt;';
echo '&lt;option value=""&gt;' . \esc_html__( '-- None --', 'quiztech' ) . '&lt;/option&gt;';
while ( $assessments_query->have_posts() ) {
$assessments_query->the_post();
$assessment_id = \get_the_ID();
$assessment_title = \get_the_title();
echo '&lt;option value="' . \esc_attr( $assessment_id ) . '" ' . \selected( $linked_assessment_id, $assessment_id, false ) . '&gt;' . \esc_html( $assessment_title ) . '&lt;/option&gt;';
}
echo '&lt;/select&gt;';
\wp_reset_postdata();
} else {
echo '&lt;p&gt;' . \esc_html__( 'No assessments found. Please create an assessment first.', 'quiztech' ) . '&lt;/p&gt;';
}
}
/**
* Saves the meta box data for the linked assessment on a job.
*
* @param int $post_id The ID of the post being saved.
*/
public function 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; }
// No need to check post type here, as the action is specific ('save_post_job')
// 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 {
// If the field isn't submitted (e.g., if the metabox wasn't rendered for some reason),
// we might want to delete the meta key to ensure no stale data persists.
// However, typically, if the field exists and is submitted empty, the above logic handles it (sets to '').
// Let's delete it if it's not set in POST, assuming the metabox should always be present on save.
\delete_post_meta( $post_id, '_quiztech_linked_assessment_id' );
}
}
}

View file

@ -0,0 +1,232 @@
<?php
namespace Quiztech\AssessmentPlatform\Admin;
// If this file is called directly, abort.
if ( ! \defined( 'WPINC' ) ) {
die;
}
/**
* Handles metaboxes for the Question CPT edit screen.
*/
class QuestionMetaboxes {
/**
* Register hooks for the metaboxes.
*/
public function register_hooks() {
add_action( 'add_meta_boxes', [ $this, 'add_question_metaboxes' ] );
add_action( 'save_post_question', [ $this, 'save_question_type_meta' ] );
add_action( 'save_post_question', [ $this, 'save_correct_answer_meta' ] );
add_action( 'save_post_question', [ $this, 'save_credit_cost_meta' ] );
add_action( 'save_post_question', [ $this, 'save_question_points_meta' ] );
}
/**
* Adds the metaboxes to the Question CPT.
*
* @param string $post_type The current post type.
*/
public function add_question_metaboxes( $post_type ) {
if ( 'question' === $post_type ) {
\add_meta_box(
'quiztech_question_type_metabox',
\__( 'Question Type', 'quiztech' ),
[ $this, 'render_question_type_metabox' ],
'question',
'normal', // Context below editor
'high'
);
\add_meta_box(
'quiztech_correct_answer_metabox',
\__( 'Correct Answer(s)', 'quiztech' ),
[ $this, 'render_correct_answer_metabox' ],
'question',
'normal', // Context below editor
'high'
);
\add_meta_box(
'quiztech_credit_cost_metabox',
\__( 'Credit Cost', 'quiztech' ),
[ $this, 'render_credit_cost_metabox' ],
'question',
'normal', // Context below editor
'default'
);
\add_meta_box(
'quiztech_question_points_metabox',
\__( 'Question Points', 'quiztech' ),
[ $this, 'render_question_points_metabox' ],
'question',
'normal', // Context below editor
'default'
);
}
}
/**
* Renders the meta box content for Question Type.
*
* @param \WP_Post $post The post object.
*/
public function render_question_type_metabox( $post ) {
\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' ),
];
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.
*/
public function 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 Credit Cost.
*
* @param \WP_Post $post The post object.
*/
public function render_credit_cost_metabox( $post ) {
\wp_nonce_field( 'quiztech_save_credit_cost_meta', 'quiztech_credit_cost_nonce' );
$value = \get_post_meta( $post->ID, '_quiztech_credit_cost', true );
$value = $value ? \absint( $value ) : 0; // Ensure non-negative integer, default 0
echo '<p>';
echo '<label for="quiztech_credit_cost_field">';
\esc_html_e( 'Credit Cost:', 'quiztech' );
echo '</label> ';
echo '<input type="number" id="quiztech_credit_cost_field" name="quiztech_credit_cost_field" value="' . \esc_attr( $value ) . '" min="0" step="1" size="4" />';
echo '<br><span class="description">' . \esc_html__( 'Number of credits required to use this question.', 'quiztech' ) . '</span>';
echo '</p>';
}
/**
* Renders the meta box content for Question Points.
*
* @param \WP_Post $post The post object.
*/
public function render_question_points_metabox( $post ) {
\wp_nonce_field( 'quiztech_save_question_points_meta', 'quiztech_question_points_nonce' );
$value = \get_post_meta( $post->ID, '_quiztech_question_points', true );
$value = $value ? \absint( $value ) : 0; // Ensure non-negative integer, default 0
echo '<p>';
echo '<label for="quiztech_question_points_field">';
\esc_html_e( 'Question Points:', 'quiztech' );
echo '</label> ';
echo '<input type="number" id="quiztech_question_points_field" name="quiztech_question_points_field" value="' . \esc_attr( $value ) . '" min="0" step="1" size="4" />';
echo '<br><span class="description">' . \esc_html__( 'Points awarded for answering this question correctly.', 'quiztech' ) . '</span>';
echo '</p>';
}
/**
* Saves the meta box data for Question Type.
*
* @param int $post_id The ID of the post being saved.
*/
public function save_question_type_meta( $post_id ) {
// 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; }
// No need to check post type here, as the action is specific ('save_post_question')
// 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'] ) );
// 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' );
}
}
/**
* Saves the meta box data for Correct Answer.
*
* @param int $post_id The ID of the post being saved.
*/
public function 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' );
}
}
/**
* Saves the meta box data for Credit Cost.
*
* @param int $post_id The ID of the post being saved.
*/
public function save_credit_cost_meta( $post_id ) {
// Basic checks (nonce, autosave, permissions)
if ( ! isset( $_POST['quiztech_credit_cost_nonce'] ) || ! \wp_verify_nonce( \sanitize_key( $_POST['quiztech_credit_cost_nonce'] ), 'quiztech_save_credit_cost_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_credit_cost_field'] ) ) {
// Use absint to ensure it's a non-negative integer.
$new_meta_value = \absint( \wp_unslash( $_POST['quiztech_credit_cost_field'] ) );
\update_post_meta( $post_id, '_quiztech_credit_cost', $new_meta_value );
}
}
/**
* Saves the meta box data for Question Points.
*
* @param int $post_id The ID of the post being saved.
*/
public function save_question_points_meta( $post_id ) {
// Basic checks (nonce, autosave, permissions)
if ( ! isset( $_POST['quiztech_question_points_nonce'] ) || ! \wp_verify_nonce( \sanitize_key( $_POST['quiztech_question_points_nonce'] ), 'quiztech_save_question_points_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_question_points_field'] ) ) {
// Use absint to ensure it's a non-negative integer.
$new_meta_value = \absint( \wp_unslash( $_POST['quiztech_question_points_field'] ) );
\update_post_meta( $post_id, '_quiztech_question_points', $new_meta_value );
}
}
}

View file

@ -169,288 +169,8 @@ function quiztech_register_post_types() {
}
\add_action( 'init', __NAMESPACE__ . '\quiztech_register_post_types' );
// --- Meta Box Registration ---
/**
* Adds meta boxes for various CPTs.
* Hooks into 'add_meta_boxes'.
*
* @param string $post_type The post type slug.
*/
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_all_meta_boxes' );
// --- Meta Box Render Functions ---
/**
* Renders the meta box content for Question Type.
*
* @param \WP_Post $post The post object.
*/
function quiztech_render_question_type_metabox( $post ) {
\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' ),
];
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 ) {
// 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; }
// 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'] ) );
// 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' );
}
}
\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' );
// Metabox registration, rendering, and saving are now handled in dedicated classes
// within the src/Admin/ directory (e.g., QuestionMetaboxes.php, AssessmentMetaboxes.php, JobMetaboxes.php).
// These classes are instantiated and their hooks registered in the main plugin file.
?>