| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plugin Name: Jock On Air Now |
| 4 |
* Plugin URI: https://wordpress.org/plugins/joan/ |
| 5 |
* Description: Display your station's current and upcoming on-air schedule in real-time with timezone awareness, Elementor and Visual Composer/WPBakery Page Builder integration support. Your site visitors can keep track of your on air schedule and their favorite shows. |
| 6 |
* Author: G & D Enterprises, Inc. |
| 7 |
* Version: 6.0.7 |
| 8 |
* Author URI: https://www.gandenterprisesinc.com |
| 9 |
* Text Domain: joan |
| 10 |
* Domain Path: /languages |
| 11 |
*/ |
| 12 |
|
| 13 |
defined( 'ABSPATH' ) || exit; |
| 14 |
|
| 15 |
// Constants |
| 16 |
define( 'JOAN_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); |
| 17 |
define( 'JOAN_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); |
| 18 |
// Update the version constant to mirror the plugin header. This helps |
| 19 |
// internal version checks and migration routines stay in sync with the |
| 20 |
// version defined above. When bumping the plugin version, always |
| 21 |
// update this constant accordingly. |
| 22 |
define( 'JOAN_VERSION', '6.0.7' ); |
| 23 |
|
| 24 |
// Block automatic updates when jumping from any pre‑6.x version to 6.x. |
| 25 |
// Version 6.x introduced a complete redesign that wipes the existing |
| 26 |
// schedule table. The previous implementation keyed off of the |
| 27 |
// specific version “6.0.0”; keep the code here but update the comment |
| 28 |
// for clarity. Because 6.0.7 is still a 6.x release, the |
| 29 |
// comparison remains against 6.0.0. |
| 30 |
add_filter('auto_update_plugin', function($update, $item) { |
| 31 |
if (isset($item->slug) && $item->slug === 'joan') { |
| 32 |
$current_version = get_option('joan_plugin_version', '0.0.0'); |
| 33 |
$new_version = $item->new_version; |
| 34 |
|
| 35 |
// Block auto-update if jumping from 5.x to 6.x |
| 36 |
if (version_compare($current_version, '6.0.0', '<') && |
| 37 |
version_compare($new_version, '6.0.0', '>=')) { |
| 38 |
return false; // Block automatic update |
| 39 |
} |
| 40 |
} |
| 41 |
return $update; |
| 42 |
}, 10, 2); |
| 43 |
|
| 44 |
add_action('admin_notices', function() { |
| 45 |
$current_version = get_option('joan_plugin_version', '0.0.0'); |
| 46 |
if (version_compare($current_version, '6.0.0', '<')) { |
| 47 |
$screen = get_current_screen(); |
| 48 |
if ($screen && strpos($screen->id, 'plugins') !== false) { |
| 49 |
echo '<div class="notice notice-error is-dismissible">'; |
| 50 |
echo '<h3>CRITICAL: JOAN Plugin Update Warning</h3>'; |
| 51 |
echo '<p><strong>Version 6.0.7 will permanently delete your existing schedule data.</strong></p>'; |
| 52 |
echo '<p>Before updating: Export your schedule, take screenshots, and prepare to re-enter all shows manually.</p>'; |
| 53 |
echo '<p><a href="admin.php?page=joan-settings&tab=help">View migration guide</a></p>'; |
| 54 |
echo '</div>'; |
| 55 |
} |
| 56 |
} |
| 57 |
}); |
| 58 |
|
| 59 |
// Version detection and migration handling |
| 60 |
add_action('admin_init', 'joan_check_version_compatibility'); |
| 61 |
|
| 62 |
function joan_check_version_compatibility() { |
| 63 |
// Check if this is a fresh activation or upgrade |
| 64 |
$current_version = get_option('joan_plugin_version', '0.0.0'); |
| 65 |
$old_version_detected = version_compare($current_version, '6.0.0', '<') && $current_version !== '0.0.0'; |
| 66 |
|
| 67 |
// If old version detected and user hasn't made a decision |
| 68 |
if ($old_version_detected && !get_option('joan_migration_handled', false)) { |
| 69 |
// Check if user clicked a migration button |
| 70 |
if (isset($_POST['joan_migration_action'])) { |
| 71 |
if ($_POST['joan_migration_action'] === 'proceed' && wp_verify_nonce($_POST['joan_migration_nonce'], 'joan_migration')) { |
| 72 |
// User chose to proceed - wipe old data and create new |
| 73 |
joan_migrate_from_old_version(); |
| 74 |
update_option('joan_migration_handled', true); |
| 75 |
update_option('joan_plugin_version', JOAN_VERSION); |
| 76 |
add_action('admin_notices', 'joan_migration_success_notice'); |
| 77 |
} elseif ($_POST['joan_migration_action'] === 'backup' && wp_verify_nonce($_POST['joan_migration_nonce'], 'joan_migration')) { |
| 78 |
// User chose to backup - deactivate plugin and show message |
| 79 |
deactivate_plugins(plugin_basename(__FILE__)); |
| 80 |
add_action('admin_notices', 'joan_backup_notice'); |
| 81 |
return; |
| 82 |
} |
| 83 |
} else { |
| 84 |
// Show migration warning |
| 85 |
add_action('admin_notices', 'joan_migration_warning_notice'); |
| 86 |
return; |
| 87 |
} |
| 88 |
} elseif (!$old_version_detected) { |
| 89 |
// Fresh installation or compatible version |
| 90 |
update_option('joan_plugin_version', JOAN_VERSION); |
| 91 |
update_option('joan_migration_handled', true); |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
function joan_migration_warning_notice() { |
| 96 |
?> |
| 97 |
<div class="notice notice-warning" style="padding: 20px; border-left: 4px solid #ffba00;"> |
| 98 |
<h2 style="margin-top: 0;">WARNING: JOAN Version 6.0.* - Important Migration Notice</h2> |
| 99 |
<p><strong>You are upgrading from an older version of JOAN to version 6.0.*, which is a complete redesign.</strong></p> |
| 100 |
<p><strong style="color: #d63638;">WARNING: Your existing schedule data from version 5.9.0 and below cannot be automatically imported and will be permanently deleted.</strong></p> |
| 101 |
|
| 102 |
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 15px 0;"> |
| 103 |
<h3>What happens if you proceed:</h3> |
| 104 |
<ul> |
| 105 |
<li><strong>BENEFITS:</strong> You'll get all the amazing new features of JOAN 6.0.7</li> |
| 106 |
<li><strong>BENEFITS:</strong> Modern admin interface with better usability</li> |
| 107 |
<li><strong>BENEFITS:</strong> Smart image positioning and enhanced widgets</li> |
| 108 |
<li><strong>BENEFITS:</strong> Improved timezone handling and page builder support</li> |
| 109 |
<li><strong style="color: #d63638;">WARNING:</strong> All existing schedule data will be permanently deleted</li> |
| 110 |
<li><strong style="color: #d63638;">WARNING:</strong> You'll need to re-enter your shows manually</li> |
| 111 |
</ul> |
| 112 |
</div> |
| 113 |
|
| 114 |
<div style="background: #e8f4fd; padding: 15px; border-radius: 5px; margin: 15px 0;"> |
| 115 |
<h3>Recommended steps:</h3> |
| 116 |
<ol> |
| 117 |
<li>Go to your current JOAN admin page and take screenshots or export your schedule</li> |
| 118 |
<li>Write down all your show names, times, jock names, and image URLs</li> |
| 119 |
<li>Come back here and choose to proceed with the upgrade</li> |
| 120 |
<li>Re-enter your schedule using the new, improved interface</li> |
| 121 |
</ol> |
| 122 |
</div> |
| 123 |
|
| 124 |
<form method="post" style="margin-top: 20px;"> |
| 125 |
<?php wp_nonce_field('joan_migration', 'joan_migration_nonce'); ?> |
| 126 |
<p style="margin-bottom: 15px;"><strong>What would you like to do?</strong></p> |
| 127 |
<div style="display: flex; gap: 15px; align-items: center;"> |
| 128 |
<button type="submit" name="joan_migration_action" value="backup" class="button button-secondary"> |
| 129 |
Wait, Let Me Backup My Schedule First |
| 130 |
</button> |
| 131 |
<button type="submit" name="joan_migration_action" value="proceed" class="button button-primary" id="joan-proceed-migration"> |
| 132 |
I Understand, Activate Version 6.0.7 Anyway |
| 133 |
</button> |
| 134 |
</div> |
| 135 |
</form> |
| 136 |
</div> |
| 137 |
<?php |
| 138 |
} |
| 139 |
|
| 140 |
function joan_backup_notice() { |
| 141 |
?> |
| 142 |
<div class="notice notice-info"> |
| 143 |
<h2>JOAN Plugin Deactivated</h2> |
| 144 |
<p>Good choice! The JOAN plugin has been deactivated so you can backup your schedule.</p> |
| 145 |
<p><strong>To backup your schedule:</strong></p> |
| 146 |
<ol> |
| 147 |
<li>Go to your JOAN admin page (if still accessible)</li> |
| 148 |
<li>Take screenshots of your schedule</li> |
| 149 |
<li>Write down show names, times, jock names, and image URLs</li> |
| 150 |
<li>When ready, reactivate the plugin and choose to proceed with the upgrade</li> |
| 151 |
</ol> |
| 152 |
</div> |
| 153 |
<?php |
| 154 |
} |
| 155 |
|
| 156 |
function joan_migration_success_notice() { |
| 157 |
?> |
| 158 |
<div class="notice notice-success is-dismissible"> |
| 159 |
<h2>JOAN 6.0.7 Successfully Activated!</h2> |
| 160 |
<p>The plugin has been upgraded and old data has been cleaned up. You can now start adding your shows using the new interface.</p> |
| 161 |
<p><a href="<?php echo admin_url('admin.php?page=joan-schedule'); ?>" class="button button-primary">Go to Schedule Manager</a></p> |
| 162 |
</div> |
| 163 |
<?php |
| 164 |
} |
| 165 |
|
| 166 |
function joan_migrate_from_old_version() { |
| 167 |
global $wpdb; |
| 168 |
|
| 169 |
// List of old table names to clean up |
| 170 |
$old_tables = [ |
| 171 |
$wpdb->prefix . 'joan_schedule_legacy', |
| 172 |
$wpdb->prefix . 'showtime_jockonair', // Very old table name |
| 173 |
$wpdb->prefix . 'onair_schedule', // Another possible old name |
| 174 |
]; |
| 175 |
|
| 176 |
// Drop old tables |
| 177 |
foreach ($old_tables as $table) { |
| 178 |
$wpdb->query("DROP TABLE IF EXISTS $table"); |
| 179 |
} |
| 180 |
|
| 181 |
// Drop current table if it exists and recreate it fresh |
| 182 |
$current_table = $wpdb->prefix . 'joan_schedule'; |
| 183 |
$wpdb->query("DROP TABLE IF EXISTS $current_table"); |
| 184 |
|
| 185 |
// Create fresh table |
| 186 |
joan_ensure_table(); |
| 187 |
|
| 188 |
// Clear any old options |
| 189 |
delete_option('joan_legacy_data_imported'); |
| 190 |
delete_option('joan_old_version_data'); |
| 191 |
|
| 192 |
// Set default options for new installation |
| 193 |
update_option('joan_time_format', '12'); |
| 194 |
update_option('joan_timezone', 'America/New_York'); |
| 195 |
update_option('joan_show_next_show', '1'); |
| 196 |
update_option('joan_show_jock_image', '1'); |
| 197 |
update_option('joan_widget_max_width', '300'); |
| 198 |
update_option('joan_schedule_status', 'active'); |
| 199 |
update_option('joan_off_air_message', 'We\'re currently off the air. Please check back later!'); |
| 200 |
|
| 201 |
error_log('JOAN: Successfully migrated from old version to 6.0.7'); |
| 202 |
} |
| 203 |
|
| 204 |
// Load core functionality only after migration check |
| 205 |
if (get_option('joan_migration_handled', false)) { |
| 206 |
require_once JOAN_PLUGIN_DIR . 'includes/crud.php'; |
| 207 |
require_once JOAN_PLUGIN_DIR . 'includes/admin-menu.php'; |
| 208 |
require_once JOAN_PLUGIN_DIR . 'includes/shortcodes.php'; |
| 209 |
require_once JOAN_PLUGIN_DIR . 'includes/widget.php'; |
| 210 |
require_once JOAN_PLUGIN_DIR . 'includes/import-legacy.php'; |
| 211 |
|
| 212 |
// Load page builder compatibility check |
| 213 |
require_once JOAN_PLUGIN_DIR . 'includes/compatibility-check.php'; |
| 214 |
} |
| 215 |
|
| 216 |
// Enqueue admin assets |
| 217 |
function joan_enqueue_admin_assets($hook) { |
| 218 |
// Enqueue admin script when migration notice is shown or on plugin pages |
| 219 |
if (!get_option('joan_migration_handled', false) || strpos($hook, 'joan') !== false) { |
| 220 |
wp_enqueue_script('joan-admin', JOAN_PLUGIN_URL . 'assets/js/admin.js', ['jquery'], JOAN_VERSION, true); |
| 221 |
} |
| 222 |
} |
| 223 |
add_action('admin_enqueue_scripts', 'joan_enqueue_admin_assets'); |
| 224 |
|
| 225 |
// Enqueue frontend assets with enhanced styling |
| 226 |
function joan_enqueue_assets() { |
| 227 |
if (!get_option('joan_migration_handled', false)) return; |
| 228 |
|
| 229 |
wp_enqueue_style( 'joan-style', JOAN_PLUGIN_URL . 'assets/css/joan.css', [], JOAN_VERSION ); |
| 230 |
wp_enqueue_script( 'joan-script', JOAN_PLUGIN_URL . 'assets/js/joan.js', ['jquery'], JOAN_VERSION, true ); |
| 231 |
wp_localize_script('joan-script', 'joan_ajax', [ |
| 232 |
'ajaxurl' => admin_url('admin-ajax.php'), |
| 233 |
'nonce' => wp_create_nonce('joan_frontend_nonce'), |
| 234 |
'settings' => [ |
| 235 |
'show_next_show' => get_option('joan_show_next_show', '1'), |
| 236 |
'show_jock_image' => get_option('joan_show_jock_image', '1'), |
| 237 |
'joan_show_local_time' => get_option('joan_show_local_time', '1'), |
| 238 |
'joan_allow_timezone_selector' => get_option('joan_allow_timezone_selector', '1'), |
| 239 |
'time_format' => get_option('joan_time_format', '12'), |
| 240 |
'widget_max_width' => get_option('joan_widget_max_width', '300'), |
| 241 |
'joan_dark_mode' => get_option('joan_dark_mode', 'auto'), |
| 242 |
'joan_dark_mode_override' => get_option('joan_dark_mode_override', '0'), |
| 243 |
// NEW: show day element symbols setting |
| 244 |
'joan_show_day_emoji' => get_option('joan_show_day_emoji', '0'), |
| 245 |
|
| 246 |
/* |
| 247 |
* Pass additional display options to the frontend. These values are used by |
| 248 |
* joan.js to determine how show titles and jock/host names should be |
| 249 |
* rendered. Without exposing these, the JavaScript cannot honor the |
| 250 |
* "Host Field Label" and "Link Assignment" options configured in the |
| 251 |
* Display tab. |
| 252 |
*/ |
| 253 |
// The Host Field Label should default to an empty string. When |
| 254 |
// provided, this value acts as a fallback and is only used if |
| 255 |
// a show does not have its own host/jock name. Leaving it |
| 256 |
// empty preserves per‑show names and avoids overriding them. |
| 257 |
'joan_jock_field_label' => get_option('joan_jock_field_label', ''), |
| 258 |
'joan_link_assignment' => get_option('joan_link_assignment', 'jock_name'), |
| 259 |
'joan_image_display_mode' => get_option('joan_image_display_mode', 'constrained') |
| 260 |
] |
| 261 |
]); |
| 262 |
} |
| 263 |
add_action( 'wp_enqueue_scripts', 'joan_enqueue_assets' ); |
| 264 |
|
| 265 |
// Database setup |
| 266 |
function joan_ensure_table() { |
| 267 |
global $wpdb; |
| 268 |
$table_name = $wpdb->prefix . 'joan_schedule'; |
| 269 |
$charset_collate = $wpdb->get_charset_collate(); |
| 270 |
|
| 271 |
$sql = "CREATE TABLE IF NOT EXISTS $table_name ( |
| 272 |
id INT AUTO_INCREMENT PRIMARY KEY, |
| 273 |
show_name VARCHAR(255), |
| 274 |
start_day VARCHAR(10), |
| 275 |
start_time TIME, |
| 276 |
end_time TIME, |
| 277 |
image_url TEXT, |
| 278 |
dj_name VARCHAR(255), |
| 279 |
link_url TEXT |
| 280 |
) $charset_collate;"; |
| 281 |
|
| 282 |
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; |
| 283 |
dbDelta($sql); |
| 284 |
} |
| 285 |
|
| 286 |
function joan_ensure_columns_exist() { |
| 287 |
global $wpdb; |
| 288 |
$table = $wpdb->prefix . 'joan_schedule'; |
| 289 |
|
| 290 |
$expected = [ |
| 291 |
'start_day' => "ALTER TABLE $table ADD COLUMN start_day VARCHAR(10)", |
| 292 |
'start_time' => "ALTER TABLE $table ADD COLUMN start_time TIME", |
| 293 |
'end_time' => "ALTER TABLE $table ADD COLUMN end_time TIME", |
| 294 |
'show_name' => "ALTER TABLE $table ADD COLUMN show_name VARCHAR(255)", |
| 295 |
'image_url' => "ALTER TABLE $table ADD COLUMN image_url TEXT", |
| 296 |
'dj_name' => "ALTER TABLE $table ADD COLUMN dj_name VARCHAR(255)", |
| 297 |
'link_url' => "ALTER TABLE $table ADD COLUMN link_url TEXT", |
| 298 |
]; |
| 299 |
|
| 300 |
$columns = $wpdb->get_col("DESC $table", 0); |
| 301 |
|
| 302 |
foreach ($expected as $col => $sql) { |
| 303 |
if (!in_array($col, $columns)) { |
| 304 |
$wpdb->query($sql); |
| 305 |
} |
| 306 |
} |
| 307 |
} |
| 308 |
|
| 309 |
// Initialize only after migration is handled |
| 310 |
//CodeSig: sgketg |
| 311 |
add_action('admin_init', function() { |
| 312 |
if (get_option('joan_migration_handled', false)) { |
| 313 |
joan_ensure_table(); |
| 314 |
joan_ensure_columns_exist(); |
| 315 |
joan_add_capabilities(); |
| 316 |
} |
| 317 |
}); |
| 318 |
|
| 319 |
// Add user capability for managing schedules |
| 320 |
function joan_add_capabilities() { |
| 321 |
$role = get_role('administrator'); |
| 322 |
if ($role) { |
| 323 |
$role->add_cap('manage_joan_schedule'); |
| 324 |
} |
| 325 |
|
| 326 |
// Also allow editors to manage schedules |
| 327 |
$editor = get_role('editor'); |
| 328 |
if ($editor) { |
| 329 |
$editor->add_cap('manage_joan_schedule'); |
| 330 |
} |
| 331 |
} |
| 332 |
|
| 333 |
// Plugin activation hook |
| 334 |
register_activation_hook(__FILE__, function() { |
| 335 |
// Version check will happen in admin_init |
| 336 |
// Don't set up database until migration is handled |
| 337 |
}); |
| 338 |
|
| 339 |
// Plugin deactivation hook |
| 340 |
register_deactivation_hook(__FILE__, function() { |
| 341 |
// Clean up scheduled events if any |
| 342 |
wp_clear_scheduled_hook('joan_cleanup_event'); |
| 343 |
|
| 344 |
// Don't remove migration flags on deactivation |
| 345 |
// delete_option('joan_migration_handled'); |
| 346 |
// delete_option('joan_plugin_version'); |
| 347 |
}); |
| 348 |
|
| 349 |
// Add settings link on plugins page |
| 350 |
add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) { |
| 351 |
if (get_option('joan_migration_handled', false)) { |
| 352 |
$settings_link = '<a href="admin.php?page=joan-schedule">' . __('Schedule', 'joan') . '</a>'; |
| 353 |
array_unshift($links, $settings_link); |
| 354 |
} |
| 355 |
return $links; |
| 356 |
}); |
| 357 |
|
| 358 |
// Add AJAX endpoint for frontend widget updates |
| 359 |
add_action('wp_ajax_joan_widget_refresh', 'joan_handle_widget_refresh'); |
| 360 |
add_action('wp_ajax_nopriv_joan_widget_refresh', 'joan_handle_widget_refresh'); |
| 361 |
|
| 362 |
function joan_handle_widget_refresh() { |
| 363 |
if (!get_option('joan_migration_handled', false)) { |
| 364 |
wp_die('Plugin not properly initialized'); |
| 365 |
} |
| 366 |
|
| 367 |
// Verify nonce for security |
| 368 |
if (!wp_verify_nonce($_POST['nonce'], 'joan_frontend_nonce')) { |
| 369 |
wp_die('Security check failed'); |
| 370 |
} |
| 371 |
|
| 372 |
// Return current show data |
| 373 |
$crud = new stdClass(); |
| 374 |
$crud->action = 'read'; |
| 375 |
$crud->read_type = 'current'; |
| 376 |
|
| 377 |
// Use existing CRUD handler |
| 378 |
do_action('wp_ajax_show_time_curd'); |
| 379 |
} |