__('Insufficient permissions', '2waymicropay'))); } $auth = Qixit_Auth::get_instance(); // Align this diagnostics action with the settings page status card: // key validation determines whether plugin API actions are actually usable. $status = $auth->get_token_status(); if (is_array($status) && ($status['status'] ?? '') === 'valid') { wp_send_json_success(array( 'message' => __('Site API Key validation successful.', '2waymicropay') )); } else { $message = is_array($status) && !empty($status['message']) ? $status['message'] : __('Connection failed', '2waymicropay'); wp_send_json_error(array( 'message' => $message )); } } add_action('wp_ajax_qixit_test_connection', 'qixit_ajax_test_connection'); /** * Clear logs */ function qixit_ajax_clear_logs() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $logger = Qixit_Logger::get_instance(); $logger->clear_log(); wp_send_json_success(array('message' => __('Logs cleared successfully', '2waymicropay'))); } add_action('wp_ajax_qixit_clear_logs', 'qixit_ajax_clear_logs'); /** * Get recent logs */ function qixit_ajax_get_logs() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $logger = Qixit_Logger::get_instance(); $logs = $logger->get_recent_logs(100); wp_send_json_success(array('logs' => $logs)); } add_action('wp_ajax_qixit_get_logs', 'qixit_ajax_get_logs'); /** * Sync product to API * This handler FIRST saves the local settings, THEN syncs to the API */ function qixit_ajax_sync_product() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!(current_user_can('manage_options') || current_user_can('edit_others_posts'))) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $post_id = isset($_POST['post_id']) ? intval(wp_unslash($_POST['post_id'])) : 0; if ($post_id <= 0) { wp_send_json_error(array('message' => __('Invalid post ID', '2waymicropay'))); } // Check authentication $auth = Qixit_Auth::get_instance(); if (!$auth->is_authenticated()) { wp_send_json_error(array('message' => __('Not authenticated. Please login in 2WayMicropay Settings first.', '2waymicropay'))); } // Get form values from AJAX request (sent from metabox JavaScript) $post_type = isset($_POST['qixit_post_type']) ? sanitize_text_field(wp_unslash($_POST['qixit_post_type'])) : QIXIT_PREMIUM_PAY_ONCE_VIEW_ANYTIME; $cost = isset($_POST['qixit_cost']) ? floatval(wp_unslash($_POST['qixit_cost'])) : floatval(QIXIT_DEFAULT_COST); $use_readmore_flag = isset($_POST['qixit_use_readmore']) ? sanitize_text_field(wp_unslash($_POST['qixit_use_readmore'])) : ''; $enable_paid_comments_flag = isset($_POST['qixit_enable_paid_comments']) ? sanitize_text_field(wp_unslash($_POST['qixit_enable_paid_comments'])) : ''; $allow_unpaid_comments_flag = isset($_POST['qixit_allow_unpaid_comments']) ? sanitize_text_field(wp_unslash($_POST['qixit_allow_unpaid_comments'])) : ''; $use_readmore = ($use_readmore_flag === '1') ? 1 : 0; $enable_paid_comments = ($enable_paid_comments_flag === '1') ? 1 : 0; $allow_unpaid_comments = ($allow_unpaid_comments_flag === '1') ? 1 : 0; $comments_cost = isset($_POST['qixit_comments_cost']) ? floatval(wp_unslash($_POST['qixit_comments_cost'])) : 0.01; // access_hours: -1 = forever, -2 = pay per view, 0 = session only, N = hours of access $access_hours = isset($_POST['qixit_access_hours']) ? intval(wp_unslash($_POST['qixit_access_hours'])) : -1; // commercial_category: defaults from settings $default_commercial_category = get_option('qixit_default_commercial_category', 'CONTENT'); $commercial_category = isset($_POST['qixit_commercial_category']) ? sanitize_text_field(wp_unslash($_POST['qixit_commercial_category'])) : $default_commercial_category; // Mutually exclusive modes: unpaid-comments mode disables paid-comments mode. if ($allow_unpaid_comments) { $enable_paid_comments = 0; } // Debug logging $logger = Qixit_Logger::get_instance(); $logger->info("AJAX Sync: Received values - post_type={$post_type}, cost={$cost}, use_readmore={$use_readmore}"); $product_manager = Qixit_Product::get_instance(); $existing_product = $product_manager->get_product_by_post_id($post_id); $default_author_split = intval(get_option('qixit_author_default_split_percent', QIXIT_DEFAULT_PERCENT_TO_AUTHOR)); $can_manage_author_share = current_user_can('manage_options') || current_user_can('edit_others_posts'); if ($can_manage_author_share) { $author_percent = isset($_POST['qixit_author_percent']) ? intval(wp_unslash($_POST['qixit_author_percent'])) : $default_author_split; } else { $author_percent = $existing_product ? intval($existing_product->percent_to_author) : $default_author_split; } // Prepare product data - DON'T include post_id as it's used in WHERE clause $product_data = array( 'qixit_post_type' => $post_type, 'premium_post_cost' => $cost, 'percent_to_author' => $author_percent, 'use_readmore' => $use_readmore, 'enable_paid_comments' => $enable_paid_comments, 'premium_comments_cost' => $enable_paid_comments ? $comments_cost : 0, 'premium_post_url' => get_permalink($post_id), 'access_hours' => $access_hours, 'commercial_category' => $commercial_category ); if ($allow_unpaid_comments) { update_post_meta($post_id, '_qixit_allow_unpaid_comments', '1'); if (get_post_field('comment_status', $post_id) !== 'open') { wp_update_post(array( 'ID' => $post_id, 'comment_status' => 'open' )); } } else { delete_post_meta($post_id, '_qixit_allow_unpaid_comments'); } // FIRST: Save local settings if ($existing_product) { $logger->info("AJAX Sync: Updating existing product ID {$existing_product->product_id}"); $update_result = $product_manager->update_local_product($existing_product->product_id, $product_data); $logger->info("AJAX Sync: Update result = " . ($update_result ? 'success' : 'failed')); // Verify the update worked global $wpdb; $verify = $product_manager->get_product_by_post_id($post_id); if ($verify) { $logger->info("AJAX Sync: After update - cost={$verify->premium_post_cost}, type={$verify->qixit_post_type}, readmore={$verify->use_readmore}"); } else { $logger->info("AJAX Sync: After update - product row missing for post {$post_id}"); } } else { // Create new product with post_id $product_data['post_id'] = $post_id; $product_manager->create_product_for_post($post_id, $product_data); $logger->info("AJAX Sync: Created new product for post {$post_id}"); } // THEN: Sync to API $result = $product_manager->sync_product_to_api($post_id); if ($result['success']) { wp_send_json_success(array( 'message' => __('Settings saved and synced successfully!', '2waymicropay'), 'product_guid' => isset($result['product_guid']) ? $result['product_guid'] : '' )); } else { wp_send_json_error(array('message' => $result['error'])); } } add_action('wp_ajax_qixit_sync_product', 'qixit_ajax_sync_product'); /** * Determine whether submit/publish requires a Qixit payment first. */ function qixit_ajax_prepare_submission_payment() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $post_id = isset($_POST['post_id']) ? intval(wp_unslash($_POST['post_id'])) : 0; $intent = isset($_POST['intent']) ? sanitize_key(wp_unslash($_POST['intent'])) : 'publish'; if (!in_array($intent, array('publish', 'pending'), true)) { $intent = 'publish'; } $user_id = get_current_user_id(); $is_privileged_bypass = current_user_can('manage_options') || current_user_can('edit_others_posts'); if ($is_privileged_bypass) { if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log('AJAX prepare_submission_payment bypassed: privileged user. post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id, 'info'); } wp_send_json_success(array( 'required' => false, 'bypassed' => true, )); } if ($post_id <= 0 || !class_exists('Qixit_Author_Commerce')) { if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log('AJAX prepare_submission_payment bypassed: invalid post or class missing. post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id, 'info'); } wp_send_json_success(array( 'required' => false, )); } $commerce = Qixit_Author_Commerce::get_instance(); if (!method_exists($commerce, 'get_submission_payment_requirement')) { if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log('AJAX prepare_submission_payment bypassed: method missing. post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id, 'info'); } wp_send_json_success(array( 'required' => false, )); } $requirement = $commerce->get_submission_payment_requirement($post_id, $intent, $user_id); if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log( 'AJAX prepare_submission_payment result: post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id . ' required=' . (!empty($requirement['required']) ? '1' : '0') . ' type=' . (isset($requirement['type']) ? $requirement['type'] : '') . ' url=' . (isset($requirement['url']) ? $requirement['url'] : ''), 'info' ); } if (!empty($requirement['required']) && !empty($requirement['url'])) { wp_send_json_success(array( 'required' => true, 'url' => $requirement['url'], 'message' => isset($requirement['message']) ? $requirement['message'] : '', 'type' => isset($requirement['type']) ? $requirement['type'] : '', )); } wp_send_json_success(array( 'required' => false, )); } add_action('wp_ajax_qixit_prepare_submission_payment', 'qixit_ajax_prepare_submission_payment'); /** * Poll current payment readiness after checkout popup closes. * Returns paid=true when submission can proceed without another payment. */ function qixit_ajax_check_submission_payment_status() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $post_id = isset($_POST['post_id']) ? intval(wp_unslash($_POST['post_id'])) : 0; $intent = isset($_POST['intent']) ? sanitize_key(wp_unslash($_POST['intent'])) : 'publish'; if (!in_array($intent, array('publish', 'pending'), true)) { $intent = 'publish'; } $user_id = get_current_user_id(); $is_privileged_bypass = current_user_can('manage_options') || current_user_can('edit_others_posts'); if ($is_privileged_bypass) { if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log('AJAX check_submission_payment_status bypassed: privileged user. post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id, 'info'); } wp_send_json_success(array( 'paid' => true, 'required' => false, 'bypassed' => true, )); } if ($post_id <= 0 || !class_exists('Qixit_Author_Commerce')) { if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log('AJAX check_submission_payment_status bypassed: invalid post or class missing. post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id, 'info'); } wp_send_json_success(array( 'paid' => true, 'required' => false, )); } $commerce = Qixit_Author_Commerce::get_instance(); if (!method_exists($commerce, 'get_submission_payment_requirement')) { if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log('AJAX check_submission_payment_status bypassed: method missing. post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id, 'info'); } wp_send_json_success(array( 'paid' => true, 'required' => false, )); } $requirement = $commerce->get_submission_payment_requirement($post_id, $intent, $user_id); $required = !empty($requirement['required']); if (class_exists('Qixit_Logger')) { Qixit_Logger::get_instance()->log( 'AJAX check_submission_payment_status result: post=' . $post_id . ' intent=' . $intent . ' user=' . $user_id . ' paid=' . ($required ? '0' : '1') . ' required=' . ($required ? '1' : '0') . ' type=' . (isset($requirement['type']) ? $requirement['type'] : ''), 'info' ); } wp_send_json_success(array( 'paid' => !$required, 'required' => $required, 'type' => isset($requirement['type']) ? $requirement['type'] : '', )); } add_action('wp_ajax_qixit_check_submission_payment_status', 'qixit_ajax_check_submission_payment_status'); /** * Get payment link for a product */ function qixit_ajax_get_payment_link() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!(current_user_can('manage_options') || current_user_can('edit_others_posts'))) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $post_id = isset($_POST['post_id']) ? intval(wp_unslash($_POST['post_id'])) : 0; if ($post_id <= 0) { wp_send_json_error(array('message' => __('Invalid post ID', '2waymicropay'))); } $product_manager = Qixit_Product::get_instance(); $product = $product_manager->get_product_by_post_id($post_id); if (!$product || empty($product->post_qixit_PID_guid)) { wp_send_json_error(array('message' => __('Product not synced. Please sync to API first.', '2waymicropay'))); } $api = Qixit_API::get_instance(); $purchase_url = $api->get_purchase_url($product->post_qixit_PID_guid); if (empty($purchase_url)) { wp_send_json_error(array('message' => __('Could not generate payment link', '2waymicropay'))); } wp_send_json_success(array('url' => $purchase_url)); } add_action('wp_ajax_qixit_get_payment_link', 'qixit_ajax_get_payment_link'); /** * Get commercial categories (for bulk action dropdown) */ function qixit_ajax_get_categories() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } // Get categories from the Admin class (which fetches from API with caching) $admin = Qixit_Admin::get_instance(); $categories = $admin->get_commercial_categories(); // Remove the empty "Select" option for bulk action unset($categories['']); wp_send_json_success(array('categories' => $categories)); } add_action('wp_ajax_qixit_get_categories', 'qixit_ajax_get_categories'); /** * Preview purge of old purchase data */ function qixit_ajax_purge_preview() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $days = isset($_POST['days']) ? intval(wp_unslash($_POST['days'])) : 0; if ($days <= 0) { wp_send_json_error(array('message' => __('Please select a valid age threshold', '2waymicropay'))); } global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); $cutoff_date = gmdate('Y-m-d H:i:s', strtotime("-{$days} days")); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- trusted plugin table name from $wpdb->prefix. $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE date_purchased < %s", $cutoff_date )); // Get some sample data to show what would be deleted // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- trusted plugin table name from $wpdb->prefix. $samples = $wpdb->get_results($wpdb->prepare( "SELECT qixit_user_id, date_purchased, total, payment_for FROM {$table_name} WHERE date_purchased < %s ORDER BY date_purchased DESC LIMIT 5", $cutoff_date )); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared wp_send_json_success(array( 'count' => intval($count), 'cutoff_date' => $cutoff_date, 'days' => $days, 'samples' => $samples )); } add_action('wp_ajax_qixit_purge_preview', 'qixit_ajax_purge_preview'); /** * Execute purge of old purchase data */ function qixit_ajax_purge_execute() { check_ajax_referer('qixit_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => __('Insufficient permissions', '2waymicropay'))); } $days = isset($_POST['days']) ? intval(wp_unslash($_POST['days'])) : 0; if ($days <= 0) { wp_send_json_error(array('message' => __('Please select a valid age threshold', '2waymicropay'))); } global $wpdb; $table_name = esc_sql($wpdb->prefix . QIXIT_PAYMENT_DETAILS); $cutoff_date = gmdate('Y-m-d H:i:s', strtotime("-{$days} days")); // Get count before deletion // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- trusted plugin table name from $wpdb->prefix. $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE date_purchased < %s", $cutoff_date )); // Execute the deletion // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- trusted plugin table name from $wpdb->prefix. $deleted = $wpdb->query($wpdb->prepare( "DELETE FROM {$table_name} WHERE date_purchased < %s", $cutoff_date )); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // Log the action $logger = Qixit_Logger::get_instance(); $logger->log("Purged {$deleted} purchase records older than {$days} days (cutoff: {$cutoff_date})"); wp_send_json_success(array( 'deleted' => intval($deleted), 'days' => $days, 'cutoff_date' => $cutoff_date )); } add_action('wp_ajax_qixit_purge_execute', 'qixit_ajax_purge_execute');