' . __('Napaka: Tabela za produkte ne obstaja. Prosimo, deaktivirajte in ponovno aktivirajte plugin.', 'wheel-of-fortune') . '
';
return;
}
if ($wheel_id > 0 && $product_id > 0 && $spins_per_purchase > 0) {
// Preveri, ali kolo obstaja
$wheel_exists = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $wheels_table WHERE id = %d", $wheel_id));
error_log("Wheel exists: " . $wheel_exists);
if (!$wheel_exists) {
error_log("ERROR: Kolo z ID $wheel_id ne obstaja!");
echo '
' . __('Napaka: Izbrano kolo ne obstaja.', 'wheel-of-fortune') . '
';
return;
}
// Preveri, ali produkt obstaja
if (class_exists('WooCommerce')) {
$product = wc_get_product($product_id);
if (!$product) {
error_log("ERROR: Produkt z ID $product_id ne obstaja!");
echo '
' . __('Napaka: Izbrani produkt ne obstaja.', 'wheel-of-fortune') . '
';
return;
}
}
$data = [
'wheel_id' => $wheel_id,
'product_id' => $product_id,
'spins_per_purchase' => $spins_per_purchase
];
error_log("Data to insert: " . print_r($data, true));
// Preveri, ali je produkt že povezan s tem kolesom
$existing_product = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $wheel_products_table WHERE wheel_id = %d AND product_id = %d",
$wheel_id, $product_id
));
if ($existing_product) {
error_log("Product already exists for this wheel, updating...");
} else {
error_log("Adding new product to wheel...");
}
$result = $wpdb->replace(
$wheel_products_table,
$data,
['%d', '%d', '%d']
);
error_log("SQL result: " . $result);
error_log("Last SQL query: " . $wpdb->last_query);
error_log("Last SQL error: " . $wpdb->last_error);
if ($result !== false) {
echo '
' . __('Produkt je bil uspešno dodan ali posodobljen.', 'wheel-of-fortune') . '
' . __('Produkt je bil izbrisan.', 'wheel-of-fortune') . '
';
}
}
}
global $wpdb;
$wheels_table = $wpdb->prefix . 'wof_wheels';
$prizes_table = $wpdb->prefix . 'wheel_prizes';
// Get the current wheel ID from URL
$wheel_id = isset($_GET['wheel_id']) ? intval($_GET['wheel_id']) : 0;
// Fetch wheel data
$wheel = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wheels_table WHERE id = %d", $wheel_id), ARRAY_A);
if (!$wheel) {
echo '
' . __('Wheel not found.', 'wheel-of-fortune') . '
';
return;
}
// Fetch prizes for this specific wheel
$prizes = $wpdb->get_results($wpdb->prepare("SELECT * FROM $prizes_table WHERE wheel_id = %d ORDER BY id ASC", $wheel_id), ARRAY_A);
$total_probability = array_sum(wp_list_pluck($prizes, 'probability'));
// Fetch povezane produkte za to kolo
$wheel_products_table = $wpdb->prefix . 'wheel_of_fortune_products';
$products = $wpdb->get_results($wpdb->prepare("SELECT * FROM $wheel_products_table WHERE wheel_id = %d", $wheel_id), ARRAY_A);
// Pridobi vse WooCommerce produkte za dropdown
if (class_exists('WooCommerce')) {
$all_products = wc_get_products(array('limit' => -1, 'status' => 'publish'));
} else {
$all_products = array();
}
?>
').slideDown();
}
},
error: function(xhr) {
let errorMessage = wheel_admin_i18n.error_sending_email || 'A communication error occurred with the server.';
if(xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
errorMessage = xhr.responseJSON.data.message;
}
resultDiv.addClass('notice notice-error is-dismissible').html('
' + errorMessage + '
').slideDown();
},
complete: function() {
button.prop('disabled', false);
spinner.removeClass('is-active');
}
});
});
}
});"""
"./admin/js/wheel.js" :
"""
/**
* Wheel of Fortune - JavaScript for wheel functionality
* Robust, smooth animation using requestAnimationFrame and easing functions.
*/
jQuery(document).ready(function($) {
// Check if wheelSettings are available
if (typeof wheelSettings === 'undefined') {
console.error('Wheel of Fortune: Settings (wheelSettings) are not defined. Please check the plugin setup.');
return;
}
// Elements
const wheel = $('.wheel');
const button = $('.wheel-button');
const resultDiv = $('.wheel-result');
const spinsCounter = $('.wheel-spins-counter span');
// Animation state
let isSpinning = false;
let animationFrameId = null;
let accumulatedRotation = 0; // Total rotation is maintained between spins
// Animation settings
const spinDuration = 8000; // 8 seconds for a more dramatic effect
const spinRotations = 5; // Number of full rotations before stopping
/**
* Easing function (cubic-bezier out) for natural deceleration
* @param {number} t - Current time (0 to 1)
* @returns {number} - Animation progress (0 to 1)
*/
function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}
/**
* Main animation loop
* @param {number} startTime - Animation start time
* @param {number} startRotation - Starting rotation angle
* @param {number} rotationAmount - Total rotation to perform for this spin
*/
function animateWheel(startTime, startRotation, rotationAmount) {
const currentTime = Date.now();
const elapsedTime = currentTime - startTime;
if (elapsedTime >= spinDuration) {
wheel.css('transform', `rotate(${startRotation + rotationAmount}deg)`);
accumulatedRotation = startRotation + rotationAmount; // Update the final position
finishSpin();
return;
}
// Calculate progress within the animation duration
const timeProgress = elapsedTime / spinDuration;
// Apply the easing function to determine the rotation progress
const rotationProgress = easeOutCubic(timeProgress);
// Calculate the current rotation
const newRotation = startRotation + (rotationAmount * rotationProgress);
wheel.css('transform', `rotate(${newRotation}deg)`);
// Continue to the next frame
animationFrameId = requestAnimationFrame(() => animateWheel(startTime, startRotation, rotationAmount));
}
/**
* Function to execute after the spin animation is complete
*/
function finishSpin() {
// Display the result with a slight delay for better effect
setTimeout(() => {
const prize = window.spinResult.prize;
resultDiv.html(`
Congratulations!
You've won: ${prize.name}
`);
resultDiv.addClass('win').fadeIn(300);
isSpinning = false;
// Re-enable the button if there are remaining spins
if (window.spinResult.remaining_spins > 0) {
button.prop('disabled', false);
} else {
button.prop('disabled', true);
}
}, 500); // 500ms pause before showing the result
}
// Spin button click handler
button.on('click', function() {
if (isSpinning) return;
isSpinning = true;
button.prop('disabled', true);
resultDiv.hide().removeClass('win error');
$.ajax({
url: wheelSettings.ajaxUrl,
method: 'POST',
data: {
action: 'wheel_spin',
nonce: wheelSettings.nonce
},
beforeSend: function(xhr) {
xhr.setRequestHeader('X-WP-Nonce', wheelSettings.nonce);
},
success: function(response) {
if (response.success) {
window.spinResult = response;
// The target angle (0-360) for the wheel to stop at, sent by the server.
const targetAngle = response.degree;
// Add several full rotations for a nice visual effect.
const fullSpins = spinRotations * 360;
// Get the current angle of the wheel, normalized to 0-360 degrees.
const currentAngle = accumulatedRotation % 360;
// Calculate the rotation needed to get from the current angle to the target angle.
// We must always rotate forward (clockwise, positive rotation).
let rotationToTarget = targetAngle - currentAngle;
if (rotationToTarget < 0) {
rotationToTarget += 360;
}
// The total amount of rotation for this spin.
const rotationAmount = fullSpins + rotationToTarget;
const startRotation = accumulatedRotation;
// Start the animation. The function will rotate the wheel by 'rotationAmount' degrees.
animateWheel(Date.now(), startRotation, rotationAmount);
// Update the remaining spins counter on the screen.
spinsCounter.text(response.remaining_spins);
} else {
// Display error from server
resultDiv.html(`
"""
"./admin/settings-page.php" :
"""
$product_id) {
if (!empty($product_id) && isset($product_spins[$key])) {
$spin_products[$product_id] = intval($product_spins[$key]);
}
}
}
update_option('wheel_spin_products', $spin_products);
// Prikaži sporočilo o uspehu
add_settings_error('wheel_settings', 'settings_updated', __('Nastavitve so bile uspešno shranjene.', 'wheel-of-fortune'), 'updated');
}
// Ročno dodajanje spinov uporabniku se zdaj izvaja preko AJAX
// Pridobi trenutne nastavitve
$cooldown_minutes = get_option('wheel_cooldown_minutes', 0);
$max_spins = get_option('wheel_max_spins', 0);
$send_emails = get_option('wheel_send_emails', false);
$spin_products = get_option('wheel_spin_products', array());
// Prikaži sporočila o napakah/uspehu
settings_errors('wheel_settings');
?>
"""
"./admin/stats-page.php" :
"""
prefix . 'users';
$spins_table = $wpdb->prefix . 'wheel_spins';
$log_table = $wpdb->prefix . 'wheel_log';
$prizes_table = $wpdb->prefix . 'wheel_prizes';
$wheels_table = $wpdb->prefix . 'wof_wheels';
// Pridobi vsa kolesa
$wheels = $wpdb->get_results("SELECT * FROM $wheels_table ORDER BY id ASC", ARRAY_A);
// Izberi kolo (privzeto prvo)
$selected_wheel_id = isset($_GET['wheel_id']) ? intval($_GET['wheel_id']) : 1;
$selected_wheel = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wheels_table WHERE id = %d", $selected_wheel_id), ARRAY_A);
if (!$selected_wheel) {
$selected_wheel_id = 1;
$selected_wheel = $wpdb->get_row("SELECT * FROM $wheels_table WHERE id = 1", ARRAY_A);
}
// Iskanje uporabnikov
$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
$search_condition = '';
if (!empty($search)) {
$search_condition = $wpdb->prepare(
"AND (u.user_login LIKE %s OR u.user_email LIKE %s OR u.display_name LIKE %s)",
"%{$search}%",
"%{$search}%",
"%{$search}%"
);
}
// Pridobi uporabnike z spin-i za izbrano kolo
$users_with_spins = $wpdb->get_results(
$wpdb->prepare(
"SELECT u.ID, u.user_email, u.display_name,
COALESCE(s.spins_available, 0) as spins_available,
COUNT(l.id) as total_spins,
MAX(l.spin_date) as last_spin_date
FROM {$users_table} u
LEFT JOIN {$spins_table} s ON u.ID = s.user_id AND s.wheel_id = %d
LEFT JOIN {$log_table} l ON u.ID = l.user_id AND l.wheel_id = %d
WHERE 1=1 {$search_condition}
GROUP BY u.ID
HAVING total_spins > 0 OR spins_available > 0
ORDER BY total_spins DESC",
$selected_wheel_id, $selected_wheel_id
),
ARRAY_A
);
// Označi nagrado kot unovčeno
if (isset($_POST['mark_redeemed']) && isset($_POST['prize_id'])) {
check_admin_referer('mark_prize_redeemed_nonce', 'mark_prize_redeemed_nonce');
$prize_log_id = intval($_POST['prize_id']);
$wpdb->update(
$log_table,
array('redeemed' => 1),
array('id' => $prize_log_id)
);
echo '
' .
__('Nagrada je bila označena kot unovčena.', 'wheel-of-fortune') .
'
';
}
// Izberi uporabnika za podrobnosti
$selected_user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
// Pridobi podrobnosti o nagradah uporabnika za izbrano kolo, če je izbran
$user_prizes = array();
if ($selected_user_id > 0) {
$user_prizes = $wpdb->get_results(
$wpdb->prepare(
"SELECT l.id, p.name as prize_name, p.description as prize_description,
l.spin_date, l.redeemed
FROM {$log_table} l
JOIN {$prizes_table} p ON l.prize_id = p.id
WHERE l.user_id = %d AND l.wheel_id = %d
ORDER BY l.spin_date DESC",
$selected_user_id, $selected_wheel_id
),
ARRAY_A
);
}
?>
>
0) : ?>
display_name)); ?>
user_email); ?>
get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$log_table} WHERE user_id = %d AND wheel_id = %d",
$selected_user_id, $selected_wheel_id
));
echo esc_html($total_spins);
?>
get_var($wpdb->prepare(
"SELECT spins_available FROM {$spins_table} WHERE user_id = %d AND wheel_id = %d",
$selected_user_id, $selected_wheel_id
));
echo esc_html($spins ?: 0);
?>
"""
"./admin/wheels-page.php" :
"""
prefix . 'wof_wheels';
// Handle adding a new wheel
if (isset($_POST['add_wheel_submit']) && check_admin_referer('wof_add_wheel_nonce')) {
$wheel_name = sanitize_text_field($_POST['wheel_name']);
$wheel_slug = sanitize_title($_POST['wheel_name']); // Generate slug from name
if (!empty($wheel_name)) {
$wpdb->insert(
$wheels_table,
['name' => $wheel_name, 'slug' => $wheel_slug, 'created_at' => current_time('mysql')],
['%s', '%s', '%s']
);
echo '
' . __('New wheel created successfully!', 'wheel-of-fortune') . '
';
} else {
echo '
' . __('Please provide a name for the wheel.', 'wheel-of-fortune') . '
';
}
}
// Handle deleting a wheel
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['wheel_id']) && check_admin_referer('wof_delete_wheel_' . $_GET['wheel_id'])) {
$wheel_id_to_delete = intval($_GET['wheel_id']);
if ($wheel_id_to_delete !== 1) { // Prevent deleting the default wheel
// TODO: Consider what to do with prizes associated with this wheel. For now, we'll just delete the wheel.
$wpdb->delete($wheels_table, ['id' => $wheel_id_to_delete], ['%d']);
echo '
"""
"./templates/wheel.php" :
"""
prefix . 'wheel_spins';
$spins = $wpdb->get_var($wpdb->prepare("SELECT spins_available FROM $spins_table WHERE user_id = %d AND wheel_id = %d", $user_id, $wheel_id));
$spins = $spins === null ? 0 : (int) $spins;
// Get prizes for the specific wheel
$wheel = wheel_of_fortune();
$prizes = $wheel->get_wheel_prizes($wheel_id);
// Set classes for size, theme and scale
$container_classes = array(
'wheel-container',
'wheel-of-fortune-container',
'size-' . $atts['size'],
'theme-' . $atts['theme']
);
// Add scale class if provided
if (!empty($atts['scale'])) {
$container_classes[] = $atts['scale'];
}
// Generate unique ID for this wheel
$wheel_id_html = 'wheel-of-fortune-' . uniqid();
?>
generate_wheel_svg($prizes);
} else {
echo '
' . esc_html__('Error loading the wheel. No prizes available.', 'wheel-of-fortune') . '
';
}
?>
"""
"./wheel-of-fortune.php" :
"""
prefix . 'wheel_spins';
wheel_of_fortune_debug_log("add_spins: Attempting to add $spins spins for user $user_id to wheel $wheel_id.");
// Preveri, ali zapis že obstaja
$existing_id = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table_spins WHERE user_id = %d AND wheel_id = %d",
$user_id, $wheel_id
));
if ($existing_id) {
// Zapis obstaja, posodobi spine
$result = $wpdb->query($wpdb->prepare(
"UPDATE $table_spins SET spins_available = spins_available + %d WHERE id = %d",
$spins, $existing_id
));
wheel_of_fortune_debug_log("add_spins: Updated existing record. Result: $result");
} else {
// Zapis ne obstaja, ustvari novega
$result = $wpdb->insert(
$table_spins,
[
'user_id' => $user_id,
'wheel_id' => $wheel_id,
'spins_available' => $spins,
'last_spin_date' => null
],
['%d', '%d', '%d', '%s']
);
wheel_of_fortune_debug_log("add_spins: Inserted new record. Result: $result");
}
return $result !== false;
}
/**
* Main plugin class
*/
class WheelOfFortune {
private static $instance = null;
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
// Register hooks
add_action('init', array($this, 'init'));
add_action('plugins_loaded', array($this, 'load_textdomain'));
register_activation_hook(WHEEL_OF_FORTUNE_PLUGIN_FILE, array($this, 'activate'));
register_deactivation_hook(WHEEL_OF_FORTUNE_PLUGIN_FILE, array($this, 'deactivate'));
add_action('admin_menu', array($this, 'admin_menu'));
add_action('rest_api_init', array($this, 'register_rest_routes'));
add_shortcode('wheel_of_fortune', array($this, 'shortcode'));
if ($this->is_woocommerce_active()) {
add_action('woocommerce_order_status_processing', array($this, 'assign_spins_on_purchase'));
add_action('woocommerce_order_status_completed', array($this, 'assign_spins_on_purchase'));
}
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
add_action('wp_ajax_wheel_get_prize_details', array($this, 'ajax_get_prize_details'));
add_action('wp_ajax_wheel_save_prize', array($this, 'ajax_save_prize'));
add_action('wp_ajax_wheel_delete_prize', array($this, 'ajax_delete_prize'));
add_action('wp_ajax_wheel_search_users', array($this, 'ajax_search_users'));
add_action('wp_ajax_wheel_add_spins', array($this, 'ajax_add_spins'));
add_action('wp_ajax_wheel_reset_all_spins', array($this, 'ajax_reset_all_spins'));
add_action('wp_ajax_wheel_get_product', array($this, 'ajax_get_product'));
add_action('wp_ajax_wheel_test_email', array($this, 'ajax_test_email'));
add_action('wp_ajax_wheel_get_prizes', array($this, 'ajax_get_prizes'));
add_action('wp_ajax_wof_delete_wheel_product', array($this, 'ajax_delete_wheel_product'));
add_action('wp_ajax_wof_update_wheel_product_spins', array($this, 'ajax_update_wheel_product_spins'));
// Vključi testno skripto za kupone
if (is_admin()) {
require_once WHEEL_OF_FORTUNE_PLUGIN_DIR . 'admin/coupon-test.php';
}
}
/**
* Assigns spins to a user upon a completed WooCommerce purchase.
* This method is hooked to 'woocommerce_order_status_processing' and 'woocommerce_order_status_completed'.
*
* @param int $order_id The ID of the WooCommerce order.
*/
public function assign_spins_on_purchase($order_id) {
wheel_of_fortune_debug_log("=== SPIN ASSIGNMENT START ===");
wheel_of_fortune_debug_log("Hook triggered for order ID: $order_id");
if (!$order_id) {
wheel_of_fortune_debug_log("No order ID provided. Exiting.");
return;
}
$order = wc_get_order($order_id);
if (!$order) {
wheel_of_fortune_debug_log("Could not get order object for ID: $order_id. Exiting.");
return;
}
$user_id = $order->get_user_id();
if (!$user_id) {
wheel_of_fortune_debug_log("Order #$order_id has no associated user ID. Exiting.");
return;
}
wheel_of_fortune_debug_log("Processing for User ID: $user_id");
global $wpdb;
$wheel_products_table = $wpdb->prefix . 'wheel_of_fortune_products';
$total_spins_to_add_by_wheel = [];
foreach ($order->get_items() as $item) {
$product_id = $item->get_product_id();
$quantity = $item->get_quantity();
wheel_of_fortune_debug_log("Checking product ID: $product_id (Quantity: $quantity)");
$wheel_product = $wpdb->get_row($wpdb->prepare(
"SELECT wheel_id, spins_per_purchase FROM $wheel_products_table WHERE product_id = %d",
$product_id
), ARRAY_A);
if ($wheel_product) {
$wheel_id = (int)$wheel_product['wheel_id'];
$spins_per_item = (int)$wheel_product['spins_per_purchase'];
$spins_for_this_item = $spins_per_item * $quantity;
wheel_of_fortune_debug_log("Product ID $product_id is a wheel product for Wheel ID: $wheel_id. Grants $spins_per_item spin(s) per purchase. Total for this item: $spins_for_this_item");
if (!isset($total_spins_to_add_by_wheel[$wheel_id])) {
$total_spins_to_add_by_wheel[$wheel_id] = 0;
}
$total_spins_to_add_by_wheel[$wheel_id] += $spins_for_this_item;
} else {
wheel_of_fortune_debug_log("Product ID $product_id is not a wheel product.");
}
}
if (!empty($total_spins_to_add_by_wheel)) {
// Prepreci veckratno dodeljevanje spinov
if (get_post_meta($order_id, '_wheel_spins_assigned', true)) {
wheel_of_fortune_debug_log("Spins for order #$order_id have already been assigned. Exiting.");
return;
}
wheel_of_fortune_debug_log("Total spins to add: " . print_r($total_spins_to_add_by_wheel, true));
$notes = [];
foreach ($total_spins_to_add_by_wheel as $wheel_id => $spins) {
if ($spins > 0) {
wheel_of_fortune_debug_log("Adding $spins spin(s) to user $user_id for wheel $wheel_id.");
if(wheel_of_fortune_add_spins($user_id, $spins, $wheel_id)){
$notes[] = sprintf(__('%d spin(s) for wheel #%d', 'wheel-of-fortune'), $spins, $wheel_id);
}
}
}
// Dodaj opombo k narocilu
if(!empty($notes)){
$order->add_order_note(sprintf(__('User awarded Wheel of Fortune spins: %s.', 'wheel-of-fortune'), implode(', ', $notes)));
update_post_meta($order_id, '_wheel_spins_assigned', true);
}
} else {
wheel_of_fortune_debug_log("No wheel products found in order #$order_id.");
}
wheel_of_fortune_debug_log("=== SPIN ASSIGNMENT END ===");
}
public function init() {
// Initialization code can go here
}
public function activate() {
error_log("Wheel of Fortune: Aktivacija plugina...");
$this->create_database_tables();
$this->run_migration();
$this->set_default_options();
$this->add_default_prizes();
// Debug: Preveri, ali se tabela za kolesa ustvari
global $wpdb;
$wheels_table = $wpdb->prefix . 'wof_wheels';
$wheel_products_table = $wpdb->prefix . 'wheel_of_fortune_products';
$wheels_exists = $wpdb->get_var("SHOW TABLES LIKE '$wheels_table'") == $wheels_table;
$products_exists = $wpdb->get_var("SHOW TABLES LIKE '$wheel_products_table'") == $wheel_products_table;
error_log("=== ACTIVATION DEBUG ===");
error_log("Wheels table exists: " . ($wheels_exists ? 'YES' : 'NO'));
error_log("Products table exists: " . ($products_exists ? 'YES' : 'NO'));
if (!$wheels_exists) {
error_log('Wheel of Fortune: Wheels table was not created properly during activation.');
} else {
error_log('Wheel of Fortune: Wheels table created successfully.');
}
flush_rewrite_rules();
error_log("Wheel of Fortune: Aktivacija končana.");
}
public function deactivate() {
flush_rewrite_rules();
}
public function load_textdomain() {
load_plugin_textdomain('wheel-of-fortune', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
private function create_database_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Ustvari tabelo za kolesa
$wheels_table = $wpdb->prefix . 'wof_wheels';
$wheel_products_table = $wpdb->prefix . 'wheel_of_fortune_products';
// Debug informacije za tabelo
error_log("=== TABLE DEBUG ===");
error_log("Wheels table: " . $wheels_table);
error_log("Products table: " . $wheel_products_table);
// Preveri, ali tabela obstaja
$wheels_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$wheels_table'") == $wheels_table;
error_log("Wheels table exists: " . ($wheels_table_exists ? 'YES' : 'NO'));
$products_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$wheel_products_table'") == $wheel_products_table;
error_log("Products table exists: " . ($products_table_exists ? 'YES' : 'NO'));
if ($wheels_table_exists) {
$wheels_table_structure = $wpdb->get_results("DESCRIBE $wheels_table");
error_log("Wheels table structure: " . print_r($wheels_table_structure, true));
}
if ($products_table_exists) {
$products_table_structure = $wpdb->get_results("DESCRIBE $wheel_products_table");
error_log("Products table structure: " . print_r($products_table_structure, true));
}
$sql_wheels = "CREATE TABLE $wheels_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
slug varchar(100) NOT NULL,
created_at datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY slug (slug)
) $charset_collate;";
$sql_products = "CREATE TABLE $wheel_products_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
wheel_id mediumint(9) NOT NULL,
product_id bigint(20) NOT NULL,
spins_per_purchase int(11) NOT NULL DEFAULT 1,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY wheel_product (wheel_id, product_id)
) $charset_collate;";
// --- 2. Posodobljena tabela za nagrade ---
$table_prizes = $wpdb->prefix . 'wheel_prizes';
$sql_prizes = "CREATE TABLE $table_prizes (
id mediumint(9) NOT NULL AUTO_INCREMENT,
wheel_id mediumint(9) NOT NULL DEFAULT 1,
name varchar(255) NOT NULL,
description text DEFAULT '',
probability float NOT NULL,
is_active tinyint(1) NOT NULL DEFAULT 1,
image_url varchar(255) DEFAULT '',
email_subject varchar(255) DEFAULT '',
email_template text DEFAULT '',
redemption_code varchar(100) DEFAULT '',
is_discount tinyint(1) NOT NULL DEFAULT 0,
discount_value float DEFAULT 0,
PRIMARY KEY (id),
KEY wheel_id (wheel_id)
) $charset_collate;";
// --- 3. Posodobljena tabela za spine (NOVO - spini po kolesih) ---
$table_spins = $wpdb->prefix . 'wheel_spins';
$sql_spins = "CREATE TABLE $table_spins (
id mediumint(9) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
wheel_id mediumint(9) NOT NULL DEFAULT 1,
spins_available int(11) NOT NULL DEFAULT 0,
last_spin_date datetime DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY user_wheel (user_id, wheel_id),
KEY user_id (user_id),
KEY wheel_id (wheel_id)
) $charset_collate;";
// --- 4. Posodobljena tabela za log (NOVO - log po kolesih) ---
$table_log = $wpdb->prefix . 'wheel_log';
$sql_log = "CREATE TABLE $table_log (
id mediumint(9) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
wheel_id mediumint(9) NOT NULL DEFAULT 1,
prize_id mediumint(9) NOT NULL,
spin_date datetime NOT NULL,
redeemed tinyint(1) NOT NULL DEFAULT 0,
redemption_code varchar(100) DEFAULT '',
PRIMARY KEY (id),
KEY user_id (user_id),
KEY wheel_id (wheel_id),
KEY prize_id (prize_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql_wheels); // Dodajanje nove tabele
dbDelta($sql_products);
dbDelta($sql_prizes);
dbDelta($sql_spins);
dbDelta($sql_log);
// Debug: preveri, ali so se tabele ustvarile
$wheels_after = $wpdb->get_var("SHOW TABLES LIKE '$wheels_table'") == $wheels_table;
$products_after = $wpdb->get_var("SHOW TABLES LIKE '$wheel_products_table'") == $wheel_products_table;
error_log("After dbDelta - Wheels table exists: " . ($wheels_after ? 'YES' : 'NO'));
error_log("After dbDelta - Products table exists: " . ($products_after ? 'YES' : 'NO'));
if (!$wheels_after) {
error_log("Wheel of Fortune: Tabela $wheels_table se ni ustvarila!");
} else {
error_log("Wheel of Fortune: Tabela $wheels_table uspešno ustvarjena.");
}
if (!$products_after) {
error_log("Wheel of Fortune: Tabela $wheel_products_table se ni ustvarila!");
} else {
error_log("Wheel of Fortune: Tabela $wheel_products_table uspešno ustvarjena.");
}
}
private function run_migration() {
global $wpdb;
$wheels_table = $wpdb->prefix . 'wof_wheels';
$prizes_table = $wpdb->prefix . 'wheel_prizes';
$spins_table = $wpdb->prefix . 'wheel_spins';
$log_table = $wpdb->prefix . 'wheel_log';
// Debug informacije
error_log("=== MIGRATION DEBUG ===");
error_log("Wheels table: " . $wheels_table);
error_log("Prizes table: " . $prizes_table);
error_log("Spins table: " . $spins_table);
error_log("Log table: " . $log_table);
// 1. Preveri, če "Default" kolo že obstaja
$default_wheel_exists = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $wheels_table WHERE id = %d", 1));
error_log("Default wheel exists: " . $default_wheel_exists);
// 2. Če ne obstaja, ga ustvari
if (!$default_wheel_exists) {
error_log("Creating default wheel...");
$result = $wpdb->insert(
$wheels_table,
[
'id' => 1,
'name' => __('Default Wheel', 'wheel-of-fortune'),
'slug' => 'default',
'created_at' => current_time('mysql')
],
['%d', '%s', '%s', '%s']
);
error_log("Insert result: " . $result);
error_log("Last SQL query: " . $wpdb->last_query);
error_log("Last SQL error: " . $wpdb->last_error);
}
// 3. Posodobi obstoječe nagrade, da pripadajo privzetemu kolesu
$prizes_updated = $wpdb->query("UPDATE $prizes_table SET wheel_id = 1 WHERE wheel_id = 0 OR wheel_id IS NULL");
error_log("Prizes updated: " . $prizes_updated);
// 4. Migriraj obstoječe spine v novo strukturo
$existing_spins = $wpdb->get_results("SELECT user_id, meta_value as spins FROM {$wpdb->usermeta} WHERE meta_key = '_wheel_spins' AND meta_value > 0");
error_log("Existing spins found: " . count($existing_spins));
foreach ($existing_spins as $spin_data) {
// Preveri, če zapis že obstaja
$existing_record = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $spins_table WHERE user_id = %d AND wheel_id = 1",
$spin_data->user_id
));
if (!$existing_record) {
$result = $wpdb->insert(
$spins_table,
[
'user_id' => $spin_data->user_id,
'wheel_id' => 1,
'spins_available' => intval($spin_data->spins),
'last_spin_date' => current_time('mysql')
],
['%d', '%d', '%d', '%s']
);
error_log("Migrated spins for user {$spin_data->user_id}: " . $result);
}
}
// 5. Migriraj obstoječi log (če obstaja)
$log_updated = $wpdb->query("UPDATE $log_table SET wheel_id = 1 WHERE wheel_id = 0 OR wheel_id IS NULL");
error_log("Log updated: " . $log_updated);
// 6. Dodaj privzete nagrade, če jih ni
$existing_prizes = $wpdb->get_var("SELECT COUNT(*) FROM $prizes_table WHERE wheel_id = 1");
if ($existing_prizes == 0) {
$this->add_default_prizes();
}
}
private function set_default_options() {
$default_options = array(
'wheel_cooldown_minutes' => 0,
'wheel_max_spins' => 0,
'wheel_send_emails' => true, // E-pošta se vedno pošlje
'wheel_spin_products' => array(),
'wheel_smtp_enabled' => false,
'wheel_smtp_host' => '',
'wheel_smtp_port' => '587',
'wheel_smtp_username' => '',
'wheel_smtp_password' => '',
'wheel_smtp_encryption' => 'tls',
'wheel_email_from_name' => get_bloginfo('name'),
'wheel_email_from_email' => get_bloginfo('admin_email'),
);
foreach ($default_options as $option => $value) {
if (get_option($option) === false) {
add_option($option, $value);
}
}
$this->add_default_prizes();
}
private function add_default_prizes() {
global $wpdb;
$table_name = $wpdb->prefix . 'wheel_prizes';
$count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
if ($count == 0) {
// Default prizes with wheel_id
$default_prizes = array(
['wheel_id' => 1, 'name' => '200k FTMO', 'description' => '200k FTMO voucher', 'probability' => 0.05, 'is_active' => 1],
['wheel_id' => 1, 'name' => '20% OFF', 'description' => '20% discount on your next purchase', 'probability' => 0.15, 'is_active' => 1],
['wheel_id' => 1, 'name' => '50k Funded7', 'description' => '50k Funded7 voucher', 'probability' => 0.10, 'is_active' => 1],
['wheel_id' => 1, 'name' => '15% OFF', 'description' => '15% discount on your next purchase', 'probability' => 0.20, 'is_active' => 1],
['wheel_id' => 1, 'name' => '100k TugeTrade', 'description' => '100k TugeTrade voucher', 'probability' => 0.10, 'is_active' => 1],
['wheel_id' => 1, 'name' => '5k Alpicap', 'description' => '5k Alpicap voucher', 'probability' => 0.15, 'is_active' => 1],
['wheel_id' => 1, 'name' => '30% OFF', 'description' => '30% discount on your next purchase', 'probability' => 0.10, 'is_active' => 1],
['wheel_id' => 1, 'name' => '200k FundedNext', 'description' => '200k FundedNext voucher', 'probability' => 0.05, 'is_active' => 1],
['wheel_id' => 1, 'name' => '75% OFF', 'description' => '75% discount on your next purchase', 'probability' => 0.05, 'is_active' => 1],
['wheel_id' => 1, 'name' => '200k FundingPips', 'description' => '200k FundingPips voucher', 'probability' => 0.05, 'is_active' => 1]
);
foreach ($default_prizes as $prize) {
$wpdb->insert($table_name, $prize);
}
}
}
public function admin_menu() {
add_menu_page(
__('Wheels of Fortune', 'wheel-of-fortune'),
__('Wheels', 'wheel-of-fortune'),
'manage_options',
'wof-wheels', // slug
array($this, 'wheels_page'), // callback
'dashicons-marker',
30
);
// Stran za urejanje posameznega kolesa (skrita iz glavnega menija, dostopna preko linkov)
add_submenu_page(
'wof-wheels', // parent slug
__('Edit Wheel', 'wheel-of-fortune'),
__('Edit Wheel', 'wheel-of-fortune'),
'manage_options',
'wof-edit-wheel',
array($this, 'edit_wheel_page')
);
// Statistika in uporabniki
add_submenu_page('wof-wheels', __('Statistics', 'wheel-of-fortune'), __('Statistics', 'wheel-of-fortune'), 'manage_options', 'wof-stats', array($this, 'stats_page'));
add_submenu_page('wof-wheels', __('Users & Spins', 'wheel-of-fortune'), __('Users & Spins', 'wheel-of-fortune'), 'manage_options', 'wof-users', array($this, 'users_page'));
}
public function wheels_page() {
require_once WHEEL_OF_FORTUNE_PLUGIN_DIR . 'admin/wheels-page.php';
}
public function edit_wheel_page() {
require_once WHEEL_OF_FORTUNE_PLUGIN_DIR . 'admin/edit-wheel-page.php';
}
public function prizes_page() {
// Ta stran ni več v uporabi, saj so nagrade urejene pod vsakim kolesom
// Pustimo jo zaenkrat prazno ali preusmerimo
echo "
" . __("This page is deprecated. Please manage prizes under a specific wheel.", "wheel-of-fortune") . "
";
}
public function stats_page() {
include WHEEL_OF_FORTUNE_PLUGIN_DIR . 'admin/stats-page.php';
}
public function users_page() {
include WHEEL_OF_FORTUNE_PLUGIN_DIR . 'admin/users-page.php';
}
public function register_rest_routes() {
register_rest_route('wheel-of-fortune/v1', '/spin', array('methods' => 'POST', 'callback' => array($this, 'process_wheel_spin'), 'permission_callback' => 'is_user_logged_in'));
register_rest_route('wheel-of-fortune/v1', '/test', array('methods' => 'GET', 'callback' => function() { return new WP_REST_Response(['success' => true, 'message' => 'REST API endpoint is working correctly.'], 200); }, 'permission_callback' => '__return_true'));
}
public function process_wheel_spin(WP_REST_Request $request) {
$user_id = get_current_user_id();
if ($user_id === 0) {
return new WP_Error('not_logged_in', __('You must be logged in to spin the wheel.', 'wheel-of-fortune'), ['status' => 401]);
}
$wheel_id = $request->get_param('wheel_id');
if (empty($wheel_id)) {
return new WP_Error('no_wheel_id', __('Wheel ID is missing.', 'wheel-of-fortune'), ['status' => 400]);
}
global $wpdb;
$spins_table = $wpdb->prefix . 'wheel_spins';
$user_spins = $wpdb->get_row($wpdb->prepare("SELECT spins_available FROM $spins_table WHERE user_id = %d AND wheel_id = %d", $user_id, $wheel_id));
$spins = $user_spins ? intval($user_spins->spins_available) : 0;
if ($spins <= 0) {
return new WP_Error('no_spins', __('You have no spins left.', 'wheel-of-fortune'), ['status' => 403]);
}
$prizes = $this->get_wheel_prizes($wheel_id);
if (empty($prizes)) {
return new WP_Error('no_prizes', __('No prizes available.', 'wheel-of-fortune'), ['status' => 500]);
}
// Uporabi delujočo logiko za izbiro nagrade
$prize = $this->select_random_prize($prizes);
$new_spins = $spins - 1;
$wpdb->update($spins_table, ['spins_available' => $new_spins, 'last_spin_date' => current_time('mysql')], ['user_id' => $user_id, 'wheel_id' => $wheel_id]);
$log_table = $wpdb->prefix . 'wheel_log';
$wpdb->insert($log_table, ['user_id' => $user_id, 'wheel_id' => $wheel_id, 'prize_id' => $prize['id'], 'spin_date' => current_time('mysql'), 'redeemed' => 0]);
$log_id = $wpdb->insert_id;
// Uporabi delujočo logiko za izračun kota
$degree = $this->calculate_prize_degree($prize['id'], $prizes);
$redemption_code = !empty($prize['redemption_code']) ? $prize['redemption_code'] : '';
$user = get_userdata($user_id);
if ($prize['is_discount'] && $prize['discount_value'] > 0 && class_exists('WooCommerce')) {
$coupon_code = 'WOF-' . uniqid() . '-' . $user->ID;
$coupon_id = $this->create_coupon_with_best_method($coupon_code, $user, $prize['discount_value']);
if ($coupon_id) {
$redemption_code = $coupon_code;
$wpdb->update($log_table, ['redemption_code' => $redemption_code], ['id' => $log_id]);
$prize['redemption_code'] = $redemption_code;
}
}
if (get_option('wheel_send_emails', true)) {
$this->send_prize_email($user_id, $prize);
}
$response_data = [ 'prize' => $prize, 'degree' => $degree, 'remaining_spins' => $new_spins, 'discount_code' => $redemption_code ];
return new WP_REST_Response(['success' => true, 'data' => $response_data], 200);
}
public function get_wheel_prizes($wheel_id = 1) {
global $wpdb;
$table_name = $wpdb->prefix . 'wheel_prizes';
return $wpdb->get_results($wpdb->prepare("SELECT * FROM $table_name WHERE wheel_id = %d AND is_active = 1 ORDER BY id ASC", $wheel_id), ARRAY_A);
}
private function select_random_prize($prizes) {
// Filter out prizes with 0 probability
$winnable_prizes = array_filter($prizes, function($prize) {
return isset($prize['probability']) && $prize['probability'] > 0;
});
// If no winnable prizes, return null or handle as an error
if (empty($winnable_prizes)) {
return end($prizes); // Or null, depending on desired behavior for no-win scenario
}
// Recalculate total probability for winnable prizes
$total_probability = array_sum(wp_list_pluck($winnable_prizes, 'probability'));
// Normalize probabilities if they don't sum to 1
if ($total_probability > 0) {
foreach ($winnable_prizes as &$prize) {
$prize['probability'] = $prize['probability'] / $total_probability;
}
unset($prize);
}
$rand = mt_rand(1, 10000) / 10000; // Increased precision
$cumulative = 0;
foreach ($winnable_prizes as $prize) {
$cumulative += $prize['probability'];
if ($rand <= $cumulative) {
return $prize;
}
}
return end($winnable_prizes); // Fallback to the last winnable prize
}
private function calculate_prize_degree($prize_id, $prizes) {
$prize_index = -1;
foreach ($prizes as $index => $prize) {
if ($prize['id'] == $prize_id) {
$prize_index = $index;
break;
}
}
if ($prize_index === -1) return 0;
$num_prizes = count($prizes);
$cx = 350;
$cy = 350;
$outer_radius = 330;
$inner_radius = 70;
$angle = 360 / $num_prizes;
$segment_size = $angle;
$prize_angle = $prize_index * $segment_size + ($segment_size / 2);
$target_stop_angle = 270 - $prize_angle;
$variation_range = $segment_size * 0.20;
$random_variation = mt_rand(-$variation_range, $variation_range);
$final_angle = $target_stop_angle + $random_variation;
$final_angle = fmod($final_angle, 360);
return ($final_angle < 0) ? $final_angle + 360 : $final_angle;
}
public function shortcode($atts) {
$atts = shortcode_atts(array(
'id' => '1', // Default to wheel ID 1 or slug 'default'
), $atts, 'wheel_of_fortune');
global $wpdb;
$wheels_table = $wpdb->prefix . 'wof_wheels';
$wheel_id_or_slug = $atts['id'];
if (is_numeric($wheel_id_or_slug)) {
$wheel = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wheels_table WHERE id = %d", $wheel_id_or_slug), ARRAY_A);
} else {
$wheel = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wheels_table WHERE slug = %s", $wheel_id_or_slug), ARRAY_A);
}
if (!$wheel) {
$wheel = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wheels_table WHERE id = 1"), ARRAY_A);
if (!$wheel) {
return '';
}
}
$wheel_id = $wheel['id'];
if (!is_user_logged_in()) {
return '
' . sprintf(__('You need to log in to participate in this game.', 'wheel-of-fortune'), wp_login_url(get_permalink())) . '
';
}
$prizes = $this->get_wheel_prizes($wheel_id);
if (empty($prizes)) {
return '';
}
$this->enqueue_frontend_assets();
$user_id = get_current_user_id();
$spins_table = $wpdb->prefix . 'wheel_spins';
$spins_left = $wpdb->get_var($wpdb->prepare("SELECT spins_available FROM $spins_table WHERE user_id = %d AND wheel_id = %d", $user_id, $wheel_id));
$spins_left = ($spins_left === null) ? 0 : (int)$spins_left;
// Definiramo polje za prevode, da bo dostopno v PHP in JS
$l10n = [
'spin_button' => __('SPIN', 'wheel-of-fortune'),
'spinning' => __('Spinning...', 'wheel-of-fortune'),
'no_spins_left' => __('No More Spins', 'wheel-of-fortune'),
'congratulations' => __('Congratulations!', 'wheel-of-fortune'),
'you_won' => __('You won:', 'wheel-of-fortune'),
'close' => __('Close', 'wheel-of-fortune'),
'error' => __('Error', 'wheel-of-fortune'),
'discount_code_sent' => __('Your discount code has been sent to your email.', 'wheel-of-fortune'),
];
wp_localize_script('wheel-of-fortune-js', 'wof_data', array(
'ajax_url' => admin_url('admin-ajax.php'),
'rest_url' => get_rest_url(null, 'wheel-of-fortune/v1/spin'),
'nonce' => wp_create_nonce('wp_rest'),
'wheel_id' => $wheel_id,
'spins_left' => $spins_left,
'prizes' => $prizes,
'l10n' => $l10n // Uporabimo definirano polje
));
ob_start();
// Pass variables to the template
include(WHEEL_OF_FORTUNE_PLUGIN_DIR . 'public/shortcode-template.php');
return ob_get_clean();
}
public function enqueue_frontend_assets() {
wp_enqueue_style('wheel-of-fortune-css', WHEEL_OF_FORTUNE_PLUGIN_URL . 'assets/css/wheel.css', [], WHEEL_OF_FORTUNE_VERSION);
wp_enqueue_script('wheel-of-fortune-js', WHEEL_OF_FORTUNE_PLUGIN_URL . 'assets/js/wheel.js', ['jquery'], WHEEL_OF_FORTUNE_VERSION, true);
}
public function enqueue_admin_assets($hook) {
// Preveri, ali je trenutna stran ena od naših admin strani
if (strpos($hook, 'wof-') !== false || strpos($hook, 'wheel-') !== false) {
wp_enqueue_style('wheel-of-fortune-admin-css', WHEEL_OF_FORTUNE_PLUGIN_URL . 'assets/css/admin.css', [], WHEEL_OF_FORTUNE_VERSION);
wp_enqueue_script('wheel-of-fortune-admin-js', WHEEL_OF_FORTUNE_PLUGIN_URL . 'admin/js/admin.js', ['jquery'], WHEEL_OF_FORTUNE_VERSION, true);
wp_localize_script('wheel-of-fortune-admin-js', 'wheel_admin_nonce', array('nonce' => wp_create_nonce('wheel_admin_nonce')));
wp_localize_script('wheel-of-fortune-admin-js', 'wheel_admin_i18n', [
'select_user' => __('Select a user', 'wheel-of-fortune'),
'select_user_and_spins' => __('Please select a user and enter the number of spins.', 'wheel-of-fortune'),
'confirm_reset_all_spins' => __('Are you sure you want to reset the spins for all users? This cannot be undone!', 'wheel-of-fortune'),
'enter_product_id_and_spins' => __('Please enter the product ID and number of spins.', 'wheel-of-fortune'),
'remove' => __('Remove', 'wheel-of-fortune'),
'no_products' => __('No products have been configured.', 'wheel-of-fortune'),
'product_not_found' => __('Product not found.', 'wheel-of-fortune'),
'error_finding_product' => __('Error finding product.', 'wheel-of-fortune'),
'active' => __('Active', 'wheel-of-fortune'),
'inactive' => __('Inactive', 'wheel-of-fortune'),
'confirm_delete_prize' => __('Are you sure you want to delete this prize?', 'wheel-of-fortune'),
'sending' => __('Sending...', 'wheel-of-fortune'),
'sending_test_email' => __('Sending test email...', 'wheel-of-fortune'),
'send_test_email' => __('Send Test Email', 'wheel-of-fortune'),
'error_sending_email' => __('Error sending email.', 'wheel-of-fortune'),
'enter_recipient_email' => __('Please enter a recipient email address.', 'wheel-of-fortune'),
]);
}
}
private function is_woocommerce_active() {
return in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')));
}
/**
* Generates the SVG for the wheel with visible, curved, and auto-adjusting text.
* This version uses for superior text rendering on segments.
*
* @param array $prizes The array of prizes.
* @return string The generated SVG string.
*/
public function generate_wheel_svg($prizes) {
$num_prizes = count($prizes);
if ($num_prizes === 0) {
return '