PluginProbe
Spin Wheel – Interactive spinning wheel that offers coupons / 2.2.2
Spin Wheel – Interactive spinning wheel that offers coupons v2.2.2
2.2.2 2.2.1 2.1.7 trunk 1.0.0 1.0.10 1.0.11 1.0.12 1.0.2 1.0.5 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 2.0.0 All 37 releases
spin-wheel / spin-wheel.php

spin-wheel.php in Spin Wheel – Interactive spinning wheel that offers coupons 2.2.2, at spin-wheel.php

422 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Spin Wheel
4 * Plugin URI: https://bdthemes.com/spin-wheel
5 * Description: Engage your visitors with an interactive spinning wheel that offers coupons and other rewards. Increase user engagement and boost conversions with this fun and rewarding experience.
6 * Version: 2.2.2
7 * Requires at least: 6.7
8 * Requires PHP: 7.4
9 * Author: bdthemes
10 * Author URI: https://bdthemes.com
11 * License: GPL-2.0-or-later
12 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
13 * Text Domain: spin-wheel
14 * Domain Path: /languages
15 */
16
17 // Exit if accessed directly
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 // Define plugin constants
23 define('SPIN_WHEEL_VERSION', '2.2.2');
24 define('SPIN_WHEEL__FILE__', __FILE__);
25 define('SPIN_WHEEL_PATH', plugin_dir_path(__FILE__));
26 define('SPIN_WHEEL_URL', plugin_dir_url(__FILE__));
27 define('SPIN_WHEEL_BASENAME', plugin_basename(__FILE__));
28 define('SPIN_WHEEL_ASSETS', SPIN_WHEEL_URL . 'assets/');
29 define( 'SPIN_WHEEL_ASSETS_URL_ADMIN', SPIN_WHEEL_URL . 'admin/' );
30 /** Option: post ID of the wheel auto-embedded site-wide (0 = none). */
31 define( 'SWP_OPTION_AUTO_EMBED_WHEEL_ID', 'swp_auto_embed_wheel_id' );
32
33 /**
34 * Main Spin Wheel Class
35 */
36 final class Spin_Wheel {
37
38 /**
39 * Plugin instance
40 *
41 * @var Spin_Wheel
42 */
43 private static $instance = null;
44
45 /**
46 * Get plugin instance
47 *
48 * @return Spin_Wheel
49 */
50 public static function instance() {
51 if (is_null(self::$instance)) {
52 self::$instance = new self();
53 }
54 return self::$instance;
55 }
56
57 /**
58 * Constructor
59 */
60 private function __construct() {
61 $this->init_hooks();
62 }
63
64 /**
65 * Initialize hooks
66 */
67 private function init_hooks() {
68 add_action('plugins_loaded', [$this, 'init']);
69 register_activation_hook(__FILE__, [$this, 'activate']);
70 register_deactivation_hook(__FILE__, [$this, 'deactivate']);
71
72 // Add cron hook for auto cleanup
73 add_action('swp_daily_cleanup', [$this, 'run_auto_cleanup']);
74 }
75
76 /**
77 * Initialize plugin
78 */
79 public function init() {
80 // Load text domain
81 load_plugin_textdomain('spin-wheel', false, dirname(SPIN_WHEEL_BASENAME) . '/languages');
82
83 // Load required files
84 $this->load_files();
85
86 // Initialize components
87 $this->init_components();
88 }
89
90 /**
91 * Load required files
92 */
93 private function load_files() {
94 // Core
95 require_once SPIN_WHEEL_PATH . 'includes/class-swp-post-type.php';
96 require_once SPIN_WHEEL_PATH . 'includes/class-swp-database.php';
97 require_once SPIN_WHEEL_PATH . 'includes/class-swp-otp.php';
98 require_once SPIN_WHEEL_PATH . 'includes/class-swp-ajax.php';
99 require_once SPIN_WHEEL_PATH . 'includes/class-swp-shortcode.php';
100 require_once SPIN_WHEEL_PATH . 'includes/class-swp-blocks.php';
101 require_once SPIN_WHEEL_PATH . 'includes/class-swp-email.php';
102 require_once SPIN_WHEEL_PATH . 'includes/class-swp-pro-helper.php';
103 require_once SPIN_WHEEL_PATH . 'includes/class-swp-vpn-detector.php';
104
105 // Admin
106 if (is_admin()) {
107 require_once SPIN_WHEEL_PATH . 'admin/class-swp-admin.php';
108 require_once SPIN_WHEEL_PATH . 'admin/class-swp-admin-api-biggopti.php';
109 require_once SPIN_WHEEL_PATH . 'admin/class-swp-admin-feeds.php';
110 require_once SPIN_WHEEL_PATH . 'admin/class-swp-admin-entries.php';
111 require_once SPIN_WHEEL_PATH . 'admin/class-swp-metaboxes.php';
112 require_once SPIN_WHEEL_PATH . 'admin/class-swp-crm-teaser-admin.php';
113 }
114
115 // Frontend
116 require_once SPIN_WHEEL_PATH . 'public/class-swp-public.php';
117 }
118
119 /**
120 * Initialize components
121 */
122 private function init_components() {
123 // Core components
124 SWP_Post_Type::instance();
125 SWP_Database::instance();
126 SWP_Ajax::instance();
127 SWP_Shortcode::instance();
128 SWP_Blocks::instance();
129 SWP_Email::instance();
130
131 // Admin components
132 if (is_admin()) {
133 SWP_Admin::instance();
134 SWP_Admin_Entries::instance();
135 SWP_Metaboxes::instance();
136 SWP_CRM_Teaser_Admin::instance();
137 }
138
139 // Frontend components
140 if (!is_admin()) {
141 SWP_Public::instance();
142 }
143 }
144
145 /**
146 * Activate plugin
147 */
148 public function activate() {
149 // Create database tables
150 require_once SPIN_WHEEL_PATH . 'includes/class-swp-database.php';
151 SWP_Database::create_tables();
152
153 // Register post type
154 require_once SPIN_WHEEL_PATH . 'includes/class-swp-post-type.php';
155 SWP_Post_Type::register();
156
157 // Schedule daily cleanup cron job if not already scheduled
158 if (!wp_next_scheduled('swp_daily_cleanup')) {
159 wp_schedule_event(time(), 'daily', 'swp_daily_cleanup');
160 }
161
162 // Flush rewrite rules
163 flush_rewrite_rules();
164 }
165
166 /**
167 * Deactivate plugin
168 */
169 public function deactivate() {
170 // Clear scheduled cleanup cron job
171 $timestamp = wp_next_scheduled('swp_daily_cleanup');
172 if ($timestamp) {
173 wp_unschedule_event($timestamp, 'swp_daily_cleanup');
174 }
175
176 // Flush rewrite rules
177 flush_rewrite_rules();
178 }
179
180 /**
181 * Run automatic cleanup of old entries
182 */
183 public function run_auto_cleanup() {
184 // Get settings
185 $settings = get_option('swp_settings', []);
186
187 // Check if auto cleanup is enabled
188 $auto_cleanup = isset($settings['auto_cleanup']) ? intval($settings['auto_cleanup']) : 0;
189
190 if (!$auto_cleanup) {
191 return; // Auto cleanup is disabled
192 }
193
194 // Get retention period
195 $retention_days = isset($settings['data_retention_days']) ? intval($settings['data_retention_days']) : 0;
196
197 if ($retention_days <= 0) {
198 return; // Invalid retention period or set to keep forever
199 }
200
201 // Run cleanup
202 $db = SWP_Database::instance();
203 $deleted = $db->cleanup_old_entries($retention_days);
204
205 // Log the cleanup action (optional)
206 if ($deleted !== false && $deleted > 0) {
207 error_log(sprintf(
208 'Spin Wheel: Auto cleanup completed. Deleted %d entries older than %d days.',
209 $deleted,
210 $retention_days
211 ));
212 }
213 }
214 }
215
216 /**
217 * Initialize plugin
218 */
219 function spin_wheel() {
220 return Spin_Wheel::instance();
221 }
222
223 /**
224 * Get wheel settings with global defaults applied
225 *
226 * @param int $wheel_id Wheel ID
227 * @param string $meta_key Meta key to retrieve
228 * @return array Merged settings with global defaults
229 */
230 function swp_get_wheel_data_with_defaults($wheel_id, $meta_key = '_swp_settings') {
231 $global_settings = get_option('swp_settings', []);
232 $wheel_data = get_post_meta($wheel_id, $meta_key, true);
233
234 if (!is_array($wheel_data)) {
235 $wheel_data = [];
236 }
237
238 // Apply global defaults based on meta key
239 if ($meta_key === '_swp_settings') {
240 $defaults = [
241 'one_entry_per_user' => $global_settings['one_entry_per_user'] ?? 1,
242 'enable_confetti' => $global_settings['enable_confetti'] ?? 1,
243 'enable_animations' => $global_settings['enable_animations'] ?? 1,
244 ];
245 return array_merge($defaults, $wheel_data);
246 }
247
248 if ($meta_key === '_swp_styles') {
249 $defaults = [
250 'width' => $global_settings['default_width'] ?? 460,
251 'height' => $global_settings['default_height'] ?? 460,
252 'button_text' => $global_settings['default_button_text'] ?? 'SPIN',
253 'button_color' => '#667eea',
254 ];
255 return array_merge($defaults, $wheel_data);
256 }
257
258 return $wheel_data;
259 }
260
261 /**
262 * Check if Pro plugin is active
263 *
264 * @return bool
265 */
266 function spin_wheel_is_pro_activated() {
267 return apply_filters('swp_is_pro_active', false);
268 }
269
270 /**
271 * Default and coerce per-wheel form settings (visibility, required flags, terms).
272 *
273 * @param array $form_settings Raw meta.
274 * @return array Normalized settings.
275 */
276 function swp_normalize_form_settings($form_settings) {
277 if (!is_array($form_settings)) {
278 $form_settings = [];
279 }
280 $defaults = [
281 'show_name' => 1,
282 'show_email' => 1,
283 'show_phone' => 1,
284 'require_name' => 1,
285 'require_email' => 1,
286 'require_phone' => 0,
287 'enable_terms_checkbox' => 0,
288 'enable_utm_capture' => 0,
289 'terms_label' => __('I agree to the terms and conditions.', 'spin-wheel'),
290 ];
291 $out = array_merge($defaults, $form_settings);
292 $toggle_keys = ['show_name', 'show_email', 'show_phone', 'require_name', 'require_email', 'require_phone', 'enable_terms_checkbox', 'enable_utm_capture'];
293 foreach ($toggle_keys as $key) {
294 if (!array_key_exists($key, $form_settings) || $form_settings[$key] === null) {
295 $out[$key] = $defaults[$key];
296 } else {
297 $out[$key] = !empty($form_settings[$key]) ? 1 : 0;
298 }
299 }
300 if (!isset($out['terms_label']) || $out['terms_label'] === '') {
301 $out['terms_label'] = $defaults['terms_label'];
302 }
303 return $out;
304 }
305
306 /**
307 * Decode spin_data JSON from a DB row.
308 *
309 * @param object $entry Row from swp_entries.
310 * @return array<string, mixed>
311 */
312 function swp_entry_spin_data_array($entry) {
313 if (!is_object($entry) || !isset($entry->spin_data) || $entry->spin_data === null || $entry->spin_data === '') {
314 return [];
315 }
316 if (is_array($entry->spin_data)) {
317 return $entry->spin_data;
318 }
319 $decoded = json_decode((string) $entry->spin_data, true);
320 return is_array($decoded) ? $decoded : [];
321 }
322
323 /**
324 * UTM / campaign params stored under spin_data.utm.
325 *
326 * @param object $entry DB row.
327 * @return array<string, string>
328 */
329 function swp_entry_get_utm_params($entry) {
330 $spin = swp_entry_spin_data_array($entry);
331 if (empty($spin['utm']) || !is_array($spin['utm'])) {
332 return [];
333 }
334 $out = [];
335 foreach ($spin['utm'] as $k => $v) {
336 if (!is_string($k) || $k === '') {
337 continue;
338 }
339 if (is_array($v) || is_object($v)) {
340 continue;
341 }
342 $out[$k] = (string) $v;
343 }
344 return $out;
345 }
346
347 /**
348 * Admin table cell: multi-line HTML summary of UTM params.
349 *
350 * @param object $entry DB row.
351 * @return string Safe HTML (empty if none).
352 */
353 function swp_entry_format_utm_admin_cell($entry) {
354 $utm = swp_entry_get_utm_params($entry);
355 if ($utm === []) {
356 return '';
357 }
358 $parts = [];
359 foreach ($utm as $k => $v) {
360 $parts[] = esc_html($k) . ': ' . esc_html($v);
361 }
362 return implode('<br>', $parts);
363 }
364
365 /**
366 * Single cell for CSV export (semicolon-separated key=value).
367 *
368 * @param object $entry DB row.
369 * @return string
370 */
371 function swp_entry_format_utm_csv($entry) {
372 $utm = swp_entry_get_utm_params($entry);
373 if ($utm === []) {
374 return '';
375 }
376 $parts = [];
377 foreach ($utm as $k => $v) {
378 $parts[] = $k . '=' . $v;
379 }
380 return implode('; ', $parts);
381 }
382
383 /**
384 * Deterministic placeholder email when the email field is hidden or optional and empty.
385 *
386 * @param int $wheel_id Wheel post ID.
387 * @param string $ip_address Client IP.
388 * @return string
389 */
390 function swp_get_anonymous_guest_email($wheel_id, $ip_address) {
391 $salt = apply_filters('swp_guest_email_salt', 'spin-wheel');
392 $hash = substr(hash('sha256', (string) $wheel_id . '|' . (string) $ip_address . '|' . $salt), 0, 20);
393 return 'guest+' . $hash . '@example.invalid';
394 }
395
396 /**
397 * Wheel editor field label: text, optional help tooltip (?), optional Pro badge (same pattern as "Maximum Participants").
398 *
399 * @param string $for_id Input id for the `for` attribute, or '' to omit.
400 * @param string $label Translated label text.
401 * @param string $tooltip Translated tooltip plain text, or '' to hide the help icon.
402 * @param bool $pro_badge Whether to print the Pro badge after the tooltip.
403 */
404 function swp_admin_field_label($for_id, $label, $tooltip = '', $pro_badge = false) {
405 $for_attr = ($for_id !== '' && $for_id !== null) ? ' for="' . esc_attr($for_id) . '"' : '';
406 echo '<label class="swp-label"' . $for_attr . '>';
407 echo '<span class="swp-label-text">' . esc_html($label) . '</span>';
408 if ($tooltip !== '') {
409 echo '<span class="swp-tooltip-wrapper">';
410 echo '<span class="swp-tooltip-icon" aria-hidden="true">?</span>';
411 echo '<span class="swp-tooltip-text">' . esc_html($tooltip) . '</span>';
412 echo '</span>';
413 }
414 if ($pro_badge && class_exists('SWP_Pro_Helper')) {
415 echo SWP_Pro_Helper::get_pro_badge(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
416 }
417 echo '</label>';
418 }
419
420 // Start the plugin
421 spin_wheel();
422