php Exit if accessed directly if (!defined('ABSPATH')) { exit; } class WC_Unique_Slider_Admin { Initialize admin hooks. public static function init() { add_action('admin_menu', [__CLASS__, 'add_admin_menu']); add_action('wp_ajax_wc_unique_slider_manual_generate', [__CLASS__, 'handle_manual_generate_ajax']); FIX Added ACTION HOOKS FOR ADMIN POST SUBMISSIONS add_action('admin_post_wc_unique_slider_save_deal_action', [__CLASS__, 'process_save_deal_form']); add_action('admin_post_wc_unique_slider_delete_deal_action', [__CLASS__, 'process_delete_deal_form']); add_action('admin_post_wc_unique_slider_delete_scheduled_action', [__CLASS__, 'process_delete_scheduled_form']); add_action('admin_post_wc_unique_slider_delete_all_scheduled_action', [__CLASS__, 'process_delete_all_scheduled_form']); add_action('admin_post_wc_unique_slider_update_settings_action', [__CLASS__, 'process_update_settings_form']); add_action('admin_post_wc_unique_slider_upload_csv_action', [__CLASS__, 'process_csv_upload_form']); END ADDED ACTION HOOKS Add other admin-specific actionsfilters here } Add admin menu page. public static function add_admin_menu() { add_menu_page( __('Unique Deals Manager', 'wc-unique-slider'), __('Unique Deals', 'wc-unique-slider'), 'manage_woocommerce', Use WC capability 'wc-unique-slider-manager', [__CLASS__, 'render_admin_page'], 'dashicons-tickets-alt', 58 ); } Handle AJAX request for manual deal generation. public static function handle_manual_generate_ajax() { Security checks check_ajax_referer('wc_unique_slider_manual_generate_nonce', 'nonce'); if (!current_user_can('manage_woocommerce')) { wp_send_json_error(['error' = __('Permission denied.', 'wc-unique-slider')], 403); return; } Perform the generation process Ensure Helpers class is loaded if called via AJAX early if (!class_exists('WC_Unique_Slider_Helpers')) { require_once WC_UNIQUE_SLIDER_PATH . 'includesclass-wc-unique-slider-helpers.php'; } $result = WC_Unique_Slider_Helpersgenerate_deal_process(); if ($result['success']) { wp_send_json_success([ 'messages' = $result['messages'], 'deal_details'= $result['deal_data'] Send back details of the created deal ]); } else { Send specific error message if available, otherwise generic $error_message = __('Deal generation failed.', 'wc-unique-slider'); Check if the last message in logs indicates a specific error $last_log = end($result['messages']); if ($last_log && str_starts_with(strtoupper($last_log), 'ERROR')) { $error_message = substr($last_log, 7); Get message after ERROR } wp_send_json_error([ 'error' = $error_message, 'messages' = $result['messages'] Send logs even on failure ], 500); Send appropriate HTTP status code for server error } } Render the admin page content. Note Form processing logic is now handled by admin-post.php handlers. This function now primarily focuses on fetching data for display. public static function render_admin_page() { if (!current_user_can('manage_woocommerce')) { wp_die(__('You do not have sufficient permissions to access this page.')); } --- Get Edit Deal Data (if applicable) --- $editing_deal = null; $edit_error_message = ''; Specific message for edit issues if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['deal_id'])) { $edit_deal_id = sanitize_key($_GET['deal_id']); Sanitize key is better for IDs Use the nonce action generated by wp_nonce_url if (isset($_GET['_wpnonce']) && wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'wc_unique_slider_edit_deal_' . $edit_deal_id)) { $active_deals_for_edit = get_option(WC_UNIQUE_SLIDER_DEALS_OPTION, []); foreach ($active_deals_for_edit as $deal) { if (($deal['id'] null) === $edit_deal_id) { $editing_deal = $deal; break; } } if (!$editing_deal) { $edit_error_message = __('Deal not found for editing.', 'wc-unique-slider'); } } else { $edit_error_message = __('Invalid edit link or security check failed.', 'wc-unique-slider'); } } --- Load Data for Display --- $active_deals = get_option(WC_UNIQUE_SLIDER_DEALS_OPTION, []); $expired_deals = get_option(WC_UNIQUE_SLIDER_EXPIRED_DEALS_OPTION, []); $slider_settings = get_option(WC_UNIQUE_SLIDER_SETTINGS_OPTION, []); Load fresh settings $scheduled_deals = []; if (class_exists('WC_Unique_Slider_Cron')) { Ensure cron class exists before calling $scheduled_deals = WC_Unique_Slider_Cronget_scheduled_csv_deals(); } --- Render the Page HTML --- $upload_message and $message_type are now passed via URL params handled in the view Pass the edit error message to the view if needed include WC_UNIQUE_SLIDER_PATH . 'includesviewsadmin-page-view.php'; } FIX ADDED admin-post.php HANDLER FUNCTIONS Processes the AddEdit Deal form submission from admin-post.php. public static function process_save_deal_form() { 1. Verify Nonce if (!isset($_POST['wc_unique_slider_nonce']) !wp_verify_nonce(sanitize_key($_POST['wc_unique_slider_nonce']), 'wc_unique_slider_save_deal')) { wp_die(__('Security check failed.', 'wc-unique-slider')); } 2. Check Permissions if (!current_user_can('manage_woocommerce')) { wp_die(__('Permission denied.', 'wc-unique-slider')); } 3. Load data and call handler $active_deals = get_option(WC_UNIQUE_SLIDER_DEALS_OPTION, []); list($message, $type, $active_deals_updated) = selfhandle_save_deal($_POST, $_FILES, $active_deals); Use existing handler 4. Redirect back to the admin page with feedback $redirect_url = admin_url('admin.phppage=wc-unique-slider-manager&feedback_msg=' . urlencode($message) . '&feedback_type=' . urlencode($type)); wp_safe_redirect($redirect_url); exit; } Processes the Delete Deal form submission. public static function process_delete_deal_form() { 1. Verify Nonce $deal_id = sanitize_text_field($_POST['delete_deal_id'] ''); if (empty($deal_id) !isset($_POST['wc_unique_slider_delete_nonce']) !wp_verify_nonce(sanitize_key($_POST['wc_unique_slider_delete_nonce']), 'wc_unique_slider_delete_deal_' . $deal_id)) { wp_die(__('Security check failed or invalid deal ID.', 'wc-unique-slider')); } 2. Check Permissions if (!current_user_can('manage_woocommerce')) { wp_die(__('Permission denied.', 'wc-unique-slider')); } 3. Call handler $active_deals = get_option(WC_UNIQUE_SLIDER_DEALS_OPTION, []); list($message, $type, $active_deals_updated) = selfhandle_delete_deal($_POST, $active_deals); 4. Redirect $redirect_url = admin_url('admin.phppage=wc-unique-slider-manager&feedback_msg=' . urlencode($message) . '&feedback_type=' . urlencode($type)); wp_safe_redirect($redirect_url); exit; } Processes the Delete Scheduled Deal form submission. public static function process_delete_scheduled_form() { 1. Verify Nonce $timestamp = intval($_POST['delete_scheduled_deal_timestamp'] 0); Use rawurldecode on args before sanitizing as base64 can have '+' which becomes space $args_serialized = isset($_POST['delete_scheduled_deal_args']) sanitize_text_field(rawurldecode($_POST['delete_scheduled_deal_args'])) ''; $nonce_action = 'wc_unique_slider_delete_scheduled_' . $timestamp . '_' . $args_serialized; Rebuild nonce action if (empty($timestamp) empty($args_serialized) !isset($_POST['wc_unique_slider_delete_scheduled_nonce']) !wp_verify_nonce(sanitize_key($_POST['wc_unique_slider_delete_scheduled_nonce']), $nonce_action)) { wp_die(__('Security check failed or invalid scheduled deal data.', 'wc-unique-slider') . ' Expected Nonce Action ' . $nonce_action); Debugging output } 2. Check Permissions if (!current_user_can('manage_woocommerce')) { wp_die(__('Permission denied.', 'wc-unique-slider')); } 3. Call handler list($message, $type) = selfhandle_delete_scheduled_deal($_POST); 4. Redirect $redirect_url = admin_url('admin.phppage=wc-unique-slider-manager&feedback_msg=' . urlencode($message) . '&feedback_type=' . urlencode($type)); wp_safe_redirect($redirect_url); exit; } Processes the Delete All Scheduled Deals form submission. public static function process_delete_all_scheduled_form() { 1. Verify Nonce if (!isset($_POST['wc_unique_slider_nonce']) !wp_verify_nonce(sanitize_key($_POST['wc_unique_slider_nonce']), 'wc_unique_slider_delete_all_scheduled_deals')) { wp_die(__('Security check failed.', 'wc-unique-slider')); } 2. Check Permissions if (!current_user_can('manage_woocommerce')) { wp_die(__('Permission denied.', 'wc-unique-slider')); } 3. Call handler list($message, $type) = selfhandle_delete_all_scheduled_deals(); 4. Redirect $redirect_url = admin_url('admin.phppage=wc-unique-slider-manager&feedback_msg=' . urlencode($message) . '&feedback_type=' . urlencode($type)); wp_safe_redirect($redirect_url); exit; } Processes the Update Settings form submission. public static function process_update_settings_form() { 1. Verify Nonce if (!isset($_POST['wc_unique_slider_nonce']) !wp_verify_nonce(sanitize_key($_POST['wc_unique_slider_nonce']), 'wc_unique_slider_update_settings')) { wp_die(__('Security check failed.', 'wc-unique-slider')); } 2. Check Permissions if (!current_user_can('manage_woocommerce')) { wp_die(__('Permission denied.', 'wc-unique-slider')); } 3. Call handler $slider_settings = get_option(WC_UNIQUE_SLIDER_SETTINGS_OPTION, []); list($message, $type, $slider_settings_updated) = selfhandle_update_settings($_POST, $slider_settings); 4. Redirect $redirect_url = admin_url('admin.phppage=wc-unique-slider-manager&feedback_msg=' . urlencode($message) . '&feedback_type=' . urlencode($type)); wp_safe_redirect($redirect_url); exit; } Processes the CSV Upload form submission. NOTE This requires full implementation of CSV parsing and scheduling. public static function process_csv_upload_form() { 1. Verify Nonce if (!isset($_POST['wc_unique_slider_nonce']) !wp_verify_nonce(sanitize_key($_POST['wc_unique_slider_nonce']), 'wc_unique_slider_upload_csv')) { wp_die(__('Security check failed.', 'wc-unique-slider')); } 2. Check Permissions if (!current_user_can('manage_woocommerce')) { wp_die(__('Permission denied.', 'wc-unique-slider')); } 3. Call handler (which needs full implementation) list($message, $type) = selfhandle_csv_upload($_FILES); 4. Redirect $redirect_url = admin_url('admin.phppage=wc-unique-slider-manager&feedback_msg=' . urlencode($message) . '&feedback_type=' . urlencode($type)); wp_safe_redirect($redirect_url); exit; } --- Form Handling Logic (PrivateProtected Methods from original code) --- These are called by the process_... methods above or directly by AJAX handlers private static function handle_save_deal($post_data, $files_data, $current_deals) { $message = ''; $type = 'error'; date_default_timezone_set('UTC'); Ensure UTC for calculations Ensure Helpers class is available if (!class_exists('WC_Unique_Slider_Helpers')) { require_once WC_UNIQUE_SLIDER_PATH . 'includesclass-wc-unique-slider-helpers.php'; } Image Upload Handling $banner_image_id = isset($post_data['existing_banner_image']) intval($post_data['existing_banner_image']) null; if (!empty($files_data['banner_image']['name']) && $files_data['banner_image']['error'] === UPLOAD_ERR_OK) { Check file type before uploading $file_info = wp_check_filetype($files_data['banner_image']['name']); $allowed_types = ['jpg', 'jpeg', 'png', 'gif']; if (!in_array(strtolower($file_info['ext']), $allowed_types)) { $message = __('Banner image upload failed Invalid file type.', 'wc-unique-slider'); Keep existing image on failure } else { $uploaded_image = media_handle_upload('banner_image', 0); 0 means no post parent if (is_wp_error($uploaded_image)) { $message = __('Banner image upload failed ', 'wc-unique-slider') . $uploaded_image-get_error_message(); Keep existing image on failure } else { Delete old image if replacing and it existed if ($banner_image_id && $banner_image_id !== $uploaded_image) { wp_delete_attachment($banner_image_id, true); } $banner_image_id = $uploaded_image; } } } $deal_id = isset($post_data['deal_id']) && !empty($post_data['deal_id']) sanitize_text_field($post_data['deal_id']) uniqid('deal_'); $expiration_days = max(1, intval($post_data['expiration_days'])); Ensure at least 1 day Sanitize all inputs $product_or_category = sanitize_text_field($post_data['product_or_category'] 'product'); $product_id = ($product_or_category === 'product') max(0, intval($post_data['product_id'] 0)) 0; $category_id = ($product_or_category === 'category') max(0, intval($post_data['category_id'] 0)) 0; $discount_code = sanitize_text_field($post_data['discount_code'] ''); $discount_type = sanitize_text_field($post_data['discount_type'] 'percent'); $discount_amount = max(0.01, floatval($post_data['discount_amount'] 0)); Ensure 0 $promo_text = sanitize_text_field($post_data['promo_text'] ''); $promo_subtitle = sanitize_text_field($post_data['promo_subtitle'] ''); $deal_data = [ 'id' = $deal_id, 'product_or_category' = $product_or_category, 'product_id' = $product_id, 'category_id' = $category_id, 'discount_code' = $discount_code, 'discount_type' = $discount_type, 'discount_amount' = $discount_amount, 'promo_text' = $promo_text, 'promo_subtitle' = $promo_subtitle, Calculate expiration based on current UTC time + days 'expiration' = gmdate('Y-m-d His', strtotime(+{$expiration_days} days)), 'banner_image' = $banner_image_id, 'status' = 'active', Always set as active when saving manually ]; --- Validation --- $is_valid = true; $validation_errors = []; if (empty($deal_data['discount_code'])) { $validation_errors[] = __('Discount Code is required.', 'wc-unique-slider'); $is_valid = false; } if (!preg_match('^[A-Za-z0-9_-]+$', $deal_data['discount_code'])) { $validation_errors[] = __('Discount Code contains invalid characters.', 'wc-unique-slider'); $is_valid = false; } if (empty($deal_data['promo_text'])) { $validation_errors[] = __('Promotional Text (Title) is required.', 'wc-unique-slider'); $is_valid = false; } if ($deal_data['product_or_category'] === 'product' && $deal_data['product_id'] = 0) { $validation_errors[] = __('Product ID is required when Type is Product.', 'wc-unique-slider'); $is_valid = false; } if ($deal_data['product_or_category'] === 'category' && $deal_data['category_id'] = 0) { $validation_errors[] = __('Category ID is required when Type is Category.', 'wc-unique-slider'); $is_valid = false; } if (!in_array($deal_data['discount_type'], ['percent', 'fixed_cart'])) { $validation_errors[] = __('Invalid Discount Type selected.', 'wc-unique-slider'); $is_valid = false; } if ($deal_data['discount_amount'] = 0) { $validation_errors[] = __('Discount Amount must be greater than 0.', 'wc-unique-slider'); $is_valid = false; } if (!$is_valid) { $message = implode(' ', $validation_errors); $type = 'error'; return [$message, $type, $current_deals]; Return early if validation fails } Check if code already exists in other active deals being savedupdated $found_index = -1; $code_conflict = false; foreach ($current_deals as $index = $existing_deal) { if ($existing_deal['id'] === $deal_data['id']) { $found_index = $index; Mark that we are editing this deal } elseif (($existing_deal['discount_code'] '') === $deal_data['discount_code']) { $code_conflict = true; Found the same code in another deal break; } } if ($code_conflict) { $message = sprintf(__('Validation failed Discount Code %s is already in use by another active deal.', 'wc-unique-slider'), esc_html($deal_data['discount_code'])); $type = 'error'; return [$message, $type, $current_deals]; } Create or Update the Coupon in WooCommerce $coupon_created_or_updated = WC_Unique_Slider_Helperscreate_coupon($deal_data); if (!$coupon_created_or_updated) { Optionally add a warning message if coupon creation fails but still save the deal $message .= ' ' . __('Warning Could not createupdate the corresponding WooCommerce coupon.', 'wc-unique-slider'); $type = 'warning'; Make it a warning if saving the deal itself succeeds } Update or Add the Deal in the options array if ($found_index !== -1) { If editing, check if the old coupon code needs deletion if (($current_deals[$found_index]['discount_code'] '') !== $deal_data['discount_code'] && !empty($current_deals[$found_index]['discount_code'])) { WC_Unique_Slider_Helpersdelete_coupon($current_deals[$found_index]['discount_code']); } $current_deals[$found_index] = $deal_data; Update existing if (empty($message)) { $message = __('Deal updated successfully.', 'wc-unique-slider'); } } else { $current_deals[] = $deal_data; Add new if (empty($message)) { $message = __('Deal added successfully.', 'wc-unique-slider'); } } Save the updated deals array back to options if (update_option(WC_UNIQUE_SLIDER_DEALS_OPTION, $current_deals)) { If message wasn't set above (e.g., only a warning occurred), set success type if ($type !== 'error') { $type = 'success'; } } else { $message .= ' ' . __('Failed to save deal data to database (or no changes made).', 'wc-unique-slider'); $type = ($type === 'error') 'error' 'info'; Keep error if already set, otherwise info } return [$message, $type, $current_deals]; } private static function handle_delete_deal($post_data, $current_deals) { $message = ''; $type = 'error'; $delete_deal_id = sanitize_text_field($post_data['delete_deal_id']); $deal_to_delete = null; $original_count = count($current_deals); $updated_deals = []; Find and filter out the deal to delete foreach($current_deals as $deal) { if (($deal['id'] null) === $delete_deal_id) { $deal_to_delete = $deal; } else { $updated_deals[] = $deal; } } if (count($updated_deals) $original_count && $deal_to_delete) { if (update_option(WC_UNIQUE_SLIDER_DEALS_OPTION, $updated_deals)) { Ensure Helpers class is available if (!class_exists('WC_Unique_Slider_Helpers')) { require_once WC_UNIQUE_SLIDER_PATH . 'includesclass-wc-unique-slider-helpers.php'; } WC_Unique_Slider_Helpersdelete_coupon($deal_to_delete['discount_code'] ''); $message = __('Deal deleted successfully.', 'wc-unique-slider'); $type = 'success'; } else { $message = __('Failed to update deals option after deleting.', 'wc-unique-slider'); } } else { $message = __('Deal not found or already deleted.', 'wc-unique-slider'); } return [$message, $type, $updated_deals]; Return updated array } private static function handle_delete_scheduled_deal($post_data) { $message = ''; $type = 'error'; $timestamp = intval($post_data['delete_scheduled_deal_timestamp']); Decode carefully $args_serialized = isset($_POST['delete_scheduled_deal_args']) base64_decode(rawurldecode($_POST['delete_scheduled_deal_args']), true) false; if ($args_serialized && ($args = unserialize($args_serialized)) !== false && is_array($args)) { if (wp_unschedule_event($timestamp, WC_UNIQUE_SLIDER_CREATE_DEAL_EVENT, $args)) { $message = __('Scheduled deal deleted successfully.', 'wc-unique-slider'); $type = 'success'; } else { $message = __('Failed to delete scheduled deal (might have run or already been deleted).', 'wc-unique-slider'); $type = 'warning'; } } else { $message = __('Invalid scheduled deal arguments provided.', 'wc-unique-slider'); } return [$message, $type]; } private static function handle_delete_all_scheduled_deals() { if (!class_exists('WC_Unique_Slider_Cron')) { Ensure Cron class is loaded require_once WC_UNIQUE_SLIDER_PATH . 'includesclass-wc-unique-slider-cron.php'; } $count = WC_Unique_Slider_Cronclear_csv_schedule(); Use method from Cron class if ($count 0) { return [sprintf(_n('%d scheduled deal deleted.', '%d scheduled deals deleted.', $count, 'wc-unique-slider'), $count), 'success']; } else { return [__('No scheduled deals found to delete.', 'wc-unique-slider'), 'info']; } } private static function handle_update_settings($post_data, $current_settings) { $message = ''; $type = 'error'; $new_settings = $current_settings; Start with current settings $new_settings['main_color'] = sanitize_hex_color($post_data['main_color'] '#a4ca5a') '#a4ca5a'; $new_settings['title_text'] = sanitize_text_field($post_data['title_text'] 'Hot Deals! Updated Daily'); $new_settings['subtitle_text'] = sanitize_text_field($post_data['subtitle_text'] 'Claim them before they expire'); $new_settings['hide_subtitle'] = isset($post_data['hide_subtitle']) true false; $new_settings['openai_api_key']= isset($post_data['openai_api_key']) sanitize_text_field(trim($post_data['openai_api_key'])) ''; if (update_option(WC_UNIQUE_SLIDER_SETTINGS_OPTION, $new_settings)) { Check if settings actually changed if ($new_settings !== $current_settings) { $message = __('Settings updated successfully.', 'wc-unique-slider'); $type = 'success'; } else { $message = __('No changes detected in settings.', 'wc-unique-slider'); $type = 'info'; } } else { This might happen if the option update fails for some reason $message = __('Failed to update settings in the database.', 'wc-unique-slider'); $type = 'error'; } return [$message, $type, $new_settings]; Return updated settings } Handles CSV upload processing. NOTE Needs full implementation. private static function handle_csv_upload($files_data) { $message = ''; $type = 'error'; $scheduled_count = 0; $error_count = 0; if (!empty($files_data['discount_csv']['tmp_name']) && $files_data['discount_csv']['error'] === UPLOAD_ERR_OK) { --- Full CSV Processing Logic Needed Here --- 1. Check file extension (.csv) 2. Open the file (use fopen, SplFileObject) 3. Read the header row, validate column names 4. Loop through each data row a. Read row data b. Validate data types and constraints (productcat exists, date format, etc.) c. Sanitize data d. Check for discount code conflicts with existing activescheduled deals e. Convert target_date (assumed UTC) to a timestamp for scheduling f. Prepare the $deal_data array for the cron callback g. Schedule the event `wp_schedule_single_event($timestamp, WC_UNIQUE_SLIDER_CREATE_DEAL_EVENT, [$deal_data]);` h. Increment $scheduled_count or $error_count 5. Close the file --- End Placeholder --- Placeholder Message - Replace with actual outcome $message = __('CSV Upload logic needs to be fully implemented here.', 'wc-unique-slider'); $type = 'warning'; Example success message if ($scheduled_count 0 && $error_count == 0) { $message = sprintf(_n('%d deal scheduled successfully.', '%d deals scheduled successfully.', $scheduled_count, 'wc-unique-slider'), $scheduled_count); $type = 'success'; } elseif ($scheduled_count 0 && $error_count 0) { $message = sprintf(__('%d deals scheduled, %d rows failed validation. Check logs.', 'wc-unique-slider'), $scheduled_count, $error_count); $type = 'warning'; } elseif ($error_count 0) { $message = sprintf(_n('%d row failed validation. No deals scheduled.', '%d rows failed validation. No deals scheduled.', $error_count, 'wc-unique-slider'), $error_count); $type = 'error'; } else { $message = __('No valid deals found in the uploaded CSV file.', 'wc-unique-slider'); $type = 'info'; } } elseif (isset($files_data['discount_csv']['error']) && $files_data['discount_csv']['error'] !== UPLOAD_ERR_NO_FILE) { Handle specific upload errors $upload_errors = [ ... standard PHP upload error constants ... ]; $message = __('CSV upload error ', 'wc-unique-slider') . ($upload_errors[$files_data['discount_csv']['error']] 'Unknown error'); $type = 'error'; } else { $message = __('No CSV file selected for upload.', 'wc-unique-slider'); $type = 'info'; } return [$message, $type]; } } End Class