format("Y/m/d h:i:s"); // Convert arrays and objects to JSON format if (is_array($data) || is_object($data)) { $data = json_encode($data, JSON_PRETTY_PRINT); $message = $message . "\r\n" . $data; } else if ($data) { $message = $message . " " . $data; } // Log the message to the specified file error_log("[$date] " . $message ."\r\n", 3, $log_File); } // If CUSTOM_DEBUG_LOG is not defined, do nothing (fail silently) // Alternatively, could log to default PHP error log: // else { error_log("Quiztech Debug: " . $message); } } } function quiztech_theme_enqueue_scripts() { // List of Quiztech custom page templates $quiztech_templates = [ 'template-quiztech-dashboard.php', 'template-manage-jobs.php', 'template-manage-assessments.php', 'template-manage-questions.php', 'template-manage-credits.php', 'template-view-results.php', 'template-assessment-builder.php', // Add Assessment Builder template ]; // Check if the current page is using one of our general Quiztech templates $is_quiztech_template = false; foreach ( $quiztech_templates as $template ) { if ( is_page_template( $template ) ) { $is_quiztech_template = true; break; } } // Only enqueue the script on our specific template pages if ( $is_quiztech_template ) { $script_path = get_stylesheet_directory() . '/js/quiztech-theme.js'; $script_url = get_stylesheet_directory_uri() . '/js/quiztech-theme.js'; $version = file_exists( $script_path ) ? filemtime( $script_path ) : '1.0'; wp_enqueue_script( 'quiztech-theme-script', $script_url, array( 'jquery' ), // Depends on jQuery $version, true // Load in footer ); // Localize script with necessary data for AJAX calls wp_localize_script( 'quiztech-theme-script', 'quiztechThemeData', [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'save_job_nonce' => wp_create_nonce( 'quiztech_save_job_action' ), // Renamed from add_job_nonce 'get_job_nonce' => wp_create_nonce( 'quiztech_get_job_action' ), // Nonce for fetching job data 'send_invite_nonce' => wp_create_nonce( 'quiztech_send_job_invite_action' ), 'save_question_nonce' => wp_create_nonce( 'quiztech_save_question_action' ), // Nonce for saving question 'get_question_nonce' => wp_create_nonce( 'quiztech_get_question_action' ), // Nonce for fetching question 'error_generic' => esc_html__( 'An error occurred. Please try again.', 'quiztech' ), 'error_permissions' => esc_html__( 'You do not have permission to perform this action.', 'quiztech' ), 'error_missing_assessment' => esc_html__( 'Error: No assessment is linked to this job.', 'quiztech' ), 'invite_sent_success' => esc_html__( 'Invite sent successfully!', 'quiztech' ), // Add translatable strings for JS 'l10n' => [ 'addNewJobTitle' => esc_html__( 'Add New Job Details', 'quiztech' ), 'editJobTitle' => esc_html__( 'Edit Job Details', 'quiztech' ), 'saveJobButton' => esc_html__( 'Save Job', 'quiztech' ), 'updateJobButton'=> esc_html__( 'Update Job', 'quiztech' ), 'loading' => esc_html__( 'Loading...', 'quiztech' ), ], ]); } // --- Enqueue script specifically for Assessment Builder --- if ( is_page_template( 'template-assessment-builder.php' ) ) { $builder_script_path = get_stylesheet_directory() . '/js/quiztech-assessment-builder.js'; $builder_script_url = get_stylesheet_directory_uri() . '/js/quiztech-assessment-builder.js'; $builder_version = file_exists( $builder_script_path ) ? filemtime( $builder_script_path ) : '1.0'; wp_enqueue_script( 'quiztech-builder-script', // Unique handle $builder_script_url, array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-draggable' ), // Added draggable dependency $builder_version, true // Load in footer ); // Localize data specifically for the builder script wp_localize_script( 'quiztech-builder-script', 'quiztechBuilderData', [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'fetch_nonce' => wp_create_nonce( 'quiztech_fetch_library_questions_action' ), // TODO: Verify action name in AJAX handler 'save_nonce' => wp_create_nonce( 'quiztech_save_assessment_action' ), // TODO: Verify action name in AJAX handler 'error_generic' => esc_html__( 'An error occurred. Please try again.', 'quiztech' ), 'loading_questions' => esc_html__( 'Loading questions...', 'quiztech' ), 'saving_assessment' => esc_html__( 'Saving assessment...', 'quiztech' ), 'assessment_saved' => esc_html__( 'Assessment saved successfully!', 'quiztech' ), // Add more localized strings as needed ]); } } add_action( 'wp_enqueue_scripts', 'quiztech_theme_enqueue_scripts' ); /** * AJAX handler for fetching job data for editing. */ function quiztech_ajax_get_job() { // 1. Verify Nonce check_ajax_referer( 'quiztech_get_job_action', 'nonce' ); // 2. Check Capabilities $capability = 'manage_options'; // Or a more specific capability like 'edit_jobs' if ( ! current_user_can( $capability ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Insufficient permissions.', 'quiztech' ) ], 403 ); } // 3. Sanitize Input $job_id = isset( $_POST['job_id'] ) ? absint( $_POST['job_id'] ) : 0; if ( ! $job_id ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid job ID provided.', 'quiztech' ) ], 400 ); } // 4. Fetch Post $post = get_post( $job_id ); // 5. Validate Post if ( ! $post || $post->post_type !== 'job' ) { wp_send_json_error( [ 'message' => esc_html__( 'Job not found.', 'quiztech' ) ], 404 ); } // Optional: Check if the current user is the author or has higher privileges if ( $post->post_author != get_current_user_id() && ! current_user_can( 'edit_others_posts' ) ) { // Adjust capability check if needed wp_send_json_error( [ 'message' => esc_html__( 'You do not have permission to view this job\'s details.', 'quiztech' ) ], 403 ); } // 6. Send Response wp_send_json_success( [ 'id' => $post->ID, 'title' => $post->post_title, 'description' => $post->post_content, 'assessment_id' => get_post_meta( $post->ID, '_quiztech_associated_assessment_id', true ), // Fetch the linked assessment ID ] ); } add_action( 'wp_ajax_quiztech_get_job', 'quiztech_ajax_get_job' ); /** * AJAX handler for saving (adding or updating) a job post from the frontend. */ function quiztech_ajax_save_job() { // 1. Verify Nonce check_ajax_referer( 'quiztech_save_job_action', 'nonce' ); // Use new nonce action // 2. Check Capabilities (adjust capability if needed) $capability = 'manage_options'; // Or a more specific capability like 'edit_jobs' if ( ! current_user_can( $capability ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Insufficient permissions.', 'quiztech' ) ], 403 ); } // 3. Sanitize Input $job_id = isset( $_POST['job_id'] ) ? absint( $_POST['job_id'] ) : 0; $job_title = isset( $_POST['job_title'] ) ? sanitize_text_field( wp_unslash( $_POST['job_title'] ) ) : ''; $job_description = isset( $_POST['job_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['job_description'] ) ) : ''; $current_user_id = get_current_user_id(); // Basic Validation if ( empty( $job_title ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Job title cannot be empty.', 'quiztech' ) ], 400 ); } // 4. Prepare Post Data $post_data = [ 'post_title' => $job_title, 'post_content' => $job_description, 'post_type' => 'job', 'post_status' => 'publish', // Default to publish, adjust if needed ]; $result = 0; $is_update = false; // 5. Determine if Update or Insert if ( $job_id > 0 ) { // --- Update Existing Job --- $is_update = true; $existing_post = get_post( $job_id ); // Validation for update if ( ! $existing_post || $existing_post->post_type !== 'job' ) { wp_send_json_error( [ 'message' => esc_html__( 'Job not found.', 'quiztech' ) ], 404 ); } // Optional: Check if the current user is the author or has higher privileges if ( $existing_post->post_author != $current_user_id && ! current_user_can( 'edit_others_posts' ) ) { // Adjust capability check if needed wp_send_json_error( [ 'message' => esc_html__( 'You do not have permission to edit this job.', 'quiztech' ) ], 403 ); } $post_data['ID'] = $job_id; // Set ID for update $result = wp_update_post( $post_data, true ); } else { // --- Insert New Job --- $post_data['post_author'] = $current_user_id; // Set author only for new posts $result = wp_insert_post( $post_data, true ); } // 6. Send Response if ( is_wp_error( $result ) ) { wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); } elseif ( $result === 0 && $is_update ) { // Check if it was an update with no changes // wp_update_post returns 0 if nothing changed. // We still need to potentially update the meta field even if core fields didn't change. $new_or_updated_post_id = $job_id; } else { // Success (Insert or Update with changes) $new_or_updated_post_id = $result; // $result is the post ID on success } // --- Update Meta Fields --- if ( $new_or_updated_post_id > 0 ) { // Save Linked Assessment ID if ( isset( $_POST['quiztech_associated_assessment_id'] ) ) { $assessment_id = sanitize_text_field( wp_unslash( $_POST['quiztech_associated_assessment_id'] ) ); $assessment_id = '' === $assessment_id ? '' : absint( $assessment_id ); update_post_meta( $new_or_updated_post_id, '_quiztech_associated_assessment_id', $assessment_id ); } else { // If not set in POST, maybe delete it? Or assume it wasn't part of the form? // For now, let's assume if it's not sent, we don't change it. // delete_post_meta($new_or_updated_post_id, '_quiztech_associated_assessment_id'); } // Add other meta field updates here if needed } // --- End Meta Fields Update --- // Send success response $success_message = $is_update ? ( $result === 0 ? esc_html__( 'Job details saved (no changes detected).', 'quiztech' ) : esc_html__( 'Job updated successfully.', 'quiztech' ) ) : esc_html__( 'Job created successfully.', 'quiztech' ); wp_send_json_success( [ 'message' => $success_message, 'job_id' => $new_or_updated_post_id ] ); } add_action( 'wp_ajax_quiztech_save_job', 'quiztech_ajax_save_job' ); // Use new action hook /** * AJAX handler for sending a job invite from the frontend. */ function quiztech_ajax_send_job_invite() { // 1. Verify Nonce check_ajax_referer( 'quiztech_send_job_invite_action', 'nonce' ); // 2. Check Capabilities (adjust capability if needed) if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Insufficient permissions.', 'quiztech' ) ], 403 ); } // 3. Sanitize Input $job_id = isset( $_POST['job_id'] ) ? absint( $_POST['job_id'] ) : 0; $applicant_email = isset( $_POST['applicant_email'] ) ? sanitize_email( wp_unslash( $_POST['applicant_email'] ) ) : ''; if ( ! $job_id || ! is_email( $applicant_email ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid job ID or email address.', 'quiztech' ) ], 400 ); } // 4. Check if Job exists and belongs to user (optional extra check) $job_post = get_post( $job_id ); if ( ! $job_post || $job_post->post_type !== 'job' || $job_post->post_author != get_current_user_id() ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid job specified.', 'quiztech' ) ], 404 ); } // 5. Get Linked Assessment ID $assessment_id = get_post_meta( $job_id, '_quiztech_linked_assessment_id', true ); if ( empty( $assessment_id ) ) { wp_send_json_error( [ 'message' => esc_html__( 'No assessment is linked to this job. Please edit the job and link an assessment.', 'quiztech' ) ], 400 ); } $assessment_id = absint( $assessment_id ); // 6. Use the Invitation Class (ensure plugin is active and class exists) if ( ! class_exists( 'Quiztech\\AssessmentPlatform\\Includes\\Invitations' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Invitation system is unavailable.', 'quiztech' ) ], 500 ); } try { $invitations = new \Quiztech\AssessmentPlatform\Includes\Invitations(); $result = $invitations->create_invitation( $job_id, $assessment_id, $applicant_email ); if ( is_wp_error( $result ) ) { wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); } elseif ( $result === false ) { // Generic failure from create_invitation if no WP_Error wp_send_json_error( [ 'message' => esc_html__( 'Failed to create invitation.', 'quiztech' ) ], 500 ); } elseif ( !is_array($result) || !isset($result['token']) ) { // Handle case where array isn't returned or token is missing \error_log('Quiztech Send Invite Error: create_invitation did not return expected array with token.'); wp_send_json_error( [ 'message' => esc_html__( 'Failed to retrieve invitation token after creation.', 'quiztech' ) ], 500 ); } else { // Success! $result contains the array. Extract the token. $token = $result['token']; // $invitation_id = $result['id']; // ID is available if needed later $email_sent = $invitations->send_invitation_email( $applicant_email, $token, [ 'job_title' => get_the_title( $job_id ) ] ); if ( $email_sent ) { wp_send_json_success( [ 'message' => esc_html__( 'Invitation sent successfully.', 'quiztech' ) ] ); } else { // Invitation created, but email failed. wp_send_json_error( [ 'message' => esc_html__( 'Invitation created, but the email could not be sent. Please check email settings.', 'quiztech' ) ], 500 ); } } } catch ( \Exception $e ) { // Catch any unexpected exceptions error_log( 'Quiztech Send Invite Error: ' . $e->getMessage() ); wp_send_json_error( [ 'message' => esc_html__( 'An unexpected error occurred while sending the invitation.', 'quiztech' ) ], 500 ); } } add_action( 'wp_ajax_send_job_invite', 'quiztech_ajax_send_job_invite' ); /** * Handle the submission from the "Buy Credits" forms on the Manage Credits page. */ function quiztech_handle_credit_purchase_submission() { // Only process if our action is set and we are on the frontend if ( ! isset( $_POST['quiztech_action'] ) ) { return; } if ($_POST['quiztech_action'] !== 'initiate_credit_purchase') { return; } if (is_admin()) { return; } // Get the Manage Credits page URL reliably $manage_credits_page = get_page_by_path('manage-credits'); // Assumes page slug is 'manage-credits' if (!$manage_credits_page) { // Fallback or error if page doesn't exist wp_die(esc_html__('Manage Credits page not found. Cannot process purchase.', 'quiztech')); exit; } $manage_credits_url = get_permalink($manage_credits_page->ID); // Verify nonce if ( ! isset( $_POST['quiztech_buy_credits_nonce'] ) || ! wp_verify_nonce( $_POST['quiztech_buy_credits_nonce'], 'quiztech_buy_credits_action' ) ) { // Nonce is invalid, redirect back with error $redirect_url = add_query_arg( array( 'purchase_status' => 'error', 'message' => urlencode( __( 'Security check failed. Please try again.', 'quiztech' ) ) ), $manage_credits_url ); // Use the stored URL wp_safe_redirect( $redirect_url ); exit; } // Check capability (basic read for now) if ( ! current_user_can( 'read' ) ) { $redirect_url = add_query_arg( array( 'purchase_status' => 'error', 'message' => urlencode( __( 'You do not have permission to purchase credits.', 'quiztech' ) ) ), $manage_credits_url ); wp_safe_redirect( $redirect_url ); exit; } // Sanitize inputs $user_id = get_current_user_id(); $package_id = isset( $_POST['package_id'] ) ? sanitize_key( $_POST['package_id'] ) : ''; $credit_amount = isset( $_POST['credit_amount'] ) ? absint( $_POST['credit_amount'] ) : 0; $price = isset( $_POST['price'] ) ? sanitize_text_field( $_POST['price'] ) : '0.00'; $currency = isset( $_POST['currency'] ) ? sanitize_key( $_POST['currency'] ) : 'USD'; if ( $credit_amount <= 0 || empty($package_id) ) { $redirect_url = add_query_arg( array( 'purchase_status' => 'error', 'message' => urlencode( __( 'Invalid credit package selected.', 'quiztech' ) ) ), $manage_credits_url ); wp_safe_redirect( $redirect_url ); exit; } // Check if the plugin function exists and call it // Note: The plugin function expects (user_id, item_id, quantity) if ( function_exists( '\Quiztech\AssessmentPlatform\Includes\quiztech_initiate_credit_purchase' ) ) { // Pass package_id as item_id, quantity is 1 for these packages $result = \Quiztech\AssessmentPlatform\Includes\quiztech_initiate_credit_purchase( $user_id, $package_id, 1 ); // If the function returns an error, redirect back with the message if ( is_wp_error( $result ) ) { $redirect_url = add_query_arg( array( 'purchase_status' => 'error', 'message' => urlencode( $result->get_error_message() ) ), $manage_credits_url ); wp_safe_redirect( $redirect_url ); exit; } // If the function didn't return an error, it should have handled the redirect to the payment gateway. // If it somehow didn't redirect AND didn't return an error, we might end up here. // We could add a fallback redirect with 'initiated' status, but ideally the plugin handles it. // For now, assume the plugin handles the success redirect or returns WP_Error. } else { // Function doesn't exist - plugin likely inactive or issue $redirect_url = add_query_arg( array( 'purchase_status' => 'error', 'message' => urlencode( __( 'Credit purchase functionality is currently unavailable.', 'quiztech' ) ) ), get_permalink() ); wp_safe_redirect( $redirect_url ); exit; } } /** * AJAX handler for fetching question data for editing. */ function quiztech_ajax_get_question() { check_ajax_referer( 'quiztech_get_question_action', 'nonce' ); if ( ! current_user_can( 'edit_questions' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Insufficient permissions.', 'quiztech' ) ], 403 ); } $question_id = isset( $_POST['question_id'] ) ? absint( $_POST['question_id'] ) : 0; if ( ! $question_id ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid question ID.', 'quiztech' ) ], 400 ); } $post = get_post( $question_id ); if ( ! $post || $post->post_type !== 'question' ) { wp_send_json_error( [ 'message' => esc_html__( 'Question not found.', 'quiztech' ) ], 404 ); } $question_type = get_post_meta( $question_id, '_quiztech_question_type', true ); $options_meta = get_post_meta( $question_id, '_quiztech_question_options', true ); // Expects array: [['text' => '...', 'correct' => bool], ...] $terms = get_the_terms( $question_id, 'quiztech_category' ); $category_id = ! empty( $terms ) && ! is_wp_error( $terms ) ? $terms[0]->term_id : ''; // Format options back to string for textarea $options_string = ''; if ( is_array( $options_meta ) ) { foreach ( $options_meta as $option ) { $options_string .= $option['text'] . ( $option['correct'] ? '*' : '' ) . "\n"; } $options_string = trim( $options_string ); } wp_send_json_success( [ 'id' => $question_id, 'title' => $post->post_title, 'content' => $post->post_content, 'type' => $question_type, 'options' => $options_string, 'category' => $category_id, 'points' => get_post_meta( $question_id, '_quiztech_question_points', true ), // Add points ] ); } add_action( 'wp_ajax_quiztech_get_question', 'quiztech_ajax_get_question' ); /** * AJAX handler for saving (creating or updating) a question. */ function quiztech_ajax_save_question() { // Verify nonce using the correct field name from wp_nonce_field() check_ajax_referer( 'quiztech_save_question_action', 'quiztech_question_nonce' ); if ( ! current_user_can( 'edit_questions' ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Insufficient permissions.', 'quiztech' ) ], 403 ); } // Sanitize inputs $question_id = isset( $_POST['question_id'] ) ? absint( $_POST['question_id'] ) : 0; $title = isset( $_POST['question_title'] ) ? sanitize_text_field( wp_unslash( $_POST['question_title'] ) ) : ''; $content = isset( $_POST['question_content'] ) ? sanitize_textarea_field( wp_unslash( $_POST['question_content'] ) ) : ''; $type = isset( $_POST['question_type'] ) ? sanitize_key( $_POST['question_type'] ) : ''; $options_string = isset( $_POST['question_options'] ) ? sanitize_textarea_field( wp_unslash( $_POST['question_options'] ) ) : ''; $category_id = isset( $_POST['question_category'] ) ? absint( $_POST['question_category'] ) : 0; // Basic validation if ( empty( $title ) || empty( $content ) || empty( $type ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Title, Content, and Type are required.', 'quiztech' ) ], 400 ); } // Validate type against known types $valid_types = ['multiple-choice', 'true-false', 'short-answer']; // Keep in sync with template if ( ! in_array( $type, $valid_types ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Invalid question type selected.', 'quiztech' ) ], 400 ); } // Prepare post data $post_data = [ 'post_title' => $title, 'post_content' => $content, 'post_status' => 'publish', 'post_type' => 'question', 'post_author' => get_current_user_id(), ]; if ( $question_id > 0 ) { // Update existing post $post_data['ID'] = $question_id; $result = wp_update_post( $post_data, true ); } else { // Insert new post $result = wp_insert_post( $post_data, true ); } if ( is_wp_error( $result ) ) { wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); } // If insert/update was successful, $result is the post ID $new_or_updated_post_id = $result; // --- Update Meta and Taxonomy --- // Update Question Type meta update_post_meta( $new_or_updated_post_id, '_quiztech_question_type', $type ); // Parse and Update Options meta (only for relevant types) $options_meta = []; if ( $type === 'multiple-choice' || $type === 'true-false' ) { $lines = explode( "\n", $options_string ); foreach ( $lines as $line ) { $line = trim( $line ); if ( ! empty( $line ) ) { $is_correct = ( substr( $line, -1 ) === '*' ); $text = $is_correct ? trim( substr( $line, 0, -1 ) ) : $line; if (!empty($text)) { $options_meta[] = [ 'text' => $text, 'correct' => $is_correct ]; } } } } update_post_meta( $new_or_updated_post_id, '_quiztech_question_options', $options_meta ); // Set Category term if ( $category_id > 0 ) { wp_set_post_terms( $new_or_updated_post_id, [ $category_id ], 'quiztech_category', false ); // Replace existing terms } else { wp_set_post_terms( $new_or_updated_post_id, [], 'quiztech_category', false ); // Remove terms if none selected } // Update Question Points meta $points = isset( $_POST['question_points'] ) ? absint( $_POST['question_points'] ) : 1; // Default to 1 if not set or invalid update_post_meta( $new_or_updated_post_id, '_quiztech_question_points', $points ); // Set Credit Cost meta (always 3 for user-generated questions as per requirement) update_post_meta( $new_or_updated_post_id, '_quiztech_credit_cost', 3 ); // --- Prepare Success Response --- // Optionally, generate HTML for the updated/new table row to send back // For simplicity now, just send success and let JS reload or handle update wp_send_json_success( [ 'message' => $question_id > 0 ? esc_html__( 'Question updated successfully.', 'quiztech' ) : esc_html__( 'Question added successfully.', 'quiztech' ), 'question_id' => $new_or_updated_post_id, // 'row_html' => '...' // TODO: Optionally generate row HTML ] ); } add_action( 'wp_ajax_quiztech_save_question', 'quiztech_ajax_save_question' ); // --- Dashboard Helper Functions --- /** * Get URLs for dashboard quick links. * * @return array Associative array of 'Label' => 'URL'. */ function quiztech_get_dashboard_quick_links() { $links = []; $page_slugs = [ 'manage-jobs' => __('Manage Jobs', 'quiztech'), 'assessment-builder' => __('Manage Assessments', 'quiztech'), // Link to builder/manager 'manage-questions' => __('Manage Questions', 'quiztech'), 'manage-credits' => __('Manage Credits', 'quiztech'), // Add 'Create New' links if separate pages exist, otherwise link to managers // 'create-job' => __('Create New Job', 'quiztech'), ]; foreach ($page_slugs as $slug => $label) { $page = get_page_by_path($slug); $links[$label] = $page ? get_permalink($page->ID) : ''; // Return empty string if page not found } // Add direct links for creating new content if possible (often handled by query args on manager pages) // Example: $links[__('Create New Job', 'quiztech')] = add_query_arg('action', 'new', $links[__('Manage Jobs', 'quiztech')]); return $links; } /** * Get recent invitations sent. * * @param int $count Number of invitations to retrieve. * @return array Array of invitation objects/arrays. */ function quiztech_get_dashboard_recent_invitations($count = 5) { global $wpdb; $invitations_table = $wpdb->prefix . 'quiztech_invitations'; $posts_table = $wpdb->posts; // Ensure table exists before querying if ($wpdb->get_var("SHOW TABLES LIKE '{$invitations_table}'") != $invitations_table) { return []; // Table doesn't exist } $query = $wpdb->prepare( "SELECT inv.applicant_email, inv.created_timestamp, p.post_title AS job_title FROM {$invitations_table} inv LEFT JOIN {$posts_table} p ON inv.job_id = p.ID ORDER BY inv.created_timestamp DESC LIMIT %d", absint($count) ); $results = $wpdb->get_results($query); return $results ? $results : []; } /** * Get recent completed assessments. * * @param int $count Number of completions to retrieve. * @return array Array of completion data. */ function quiztech_get_dashboard_recent_completions($count = 5) { global $wpdb; $invitations_table = $wpdb->prefix . 'quiztech_invitations'; $completions = []; $args = [ 'post_type' => 'user_evaluation', 'post_status' => 'completed', // Assuming 'completed' status is used 'posts_per_page' => absint($count), 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => [ [ 'key' => 'quiztech_invitation_id', 'compare' => 'EXISTS' // Ensure it's linked ] ] ]; $query = new WP_Query($args); if ($query->have_posts()) { // Check if invitations table exists $invitations_table_exists = ($wpdb->get_var("SHOW TABLES LIKE '{$invitations_table}'") == $invitations_table); while ($query->have_posts()) { $query->the_post(); $evaluation_id = get_the_ID(); $completion_date = get_the_date(); // Or post_modified if that's more relevant $invitation_id = get_post_meta($evaluation_id, 'quiztech_invitation_id', true); $applicant_email = 'N/A'; $job_title = 'N/A'; if ($invitation_id && $invitations_table_exists) { $invitation_data = $wpdb->get_row($wpdb->prepare( "SELECT inv.applicant_email, p.post_title AS job_title FROM {$invitations_table} inv LEFT JOIN {$wpdb->posts} p ON inv.job_id = p.ID WHERE inv.id = %d", absint($invitation_id) )); if ($invitation_data) { $applicant_email = $invitation_data->applicant_email; $job_title = $invitation_data->job_title; } } $completions[] = [ 'applicant_email' => $applicant_email, 'job_title' => $job_title, 'completion_date' => $completion_date, 'evaluation_id' => $evaluation_id // For potential linking ]; } wp_reset_postdata(); } return $completions; } /** * Get the count of active job posts. * * @return int Count of published jobs. */ function quiztech_get_dashboard_active_job_count() { $args = [ 'post_type' => 'job', 'post_status' => 'publish', 'posts_per_page' => -1, // Count all 'fields' => 'ids' // Only need IDs for counting ]; $query = new WP_Query($args); return $query->post_count; } // --- End Dashboard Helper Functions --- add_action( 'template_redirect', 'quiztech_handle_credit_purchase_submission', 1 ); // Hook earlier (priority 1)