PluginProbe
Jock On Air Now (JOAN) / 6.1.0
Jock On Air Now (JOAN) v6.1.0
4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8 5.0 5.1 5.2.0 5.3.0 5.4.0 5.5.0 5.5.1 5.5.2 5.5.3 5.5.4 5.5.6 5.5.7 5.5.8 5.5.9 5.6 5.6.1 5.6.2 5.6.3 5.6.4 All 77 releases
joan / joan.php

joan.php in Jock On Air Now (JOAN) 6.1.0, at joan.php

503 lines 19.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: JOAN - Jock On Air Now
4 * Plugin URI: https://gandenterprisesinc.com/plugins/joan
5 * Description: Display your station's current and upcoming on-air schedule in real-time with timezone awareness, Elementor & Visual Composer support, and modern code practices.
6 * Version: 6.1.0
7 * Author: G & D Enterprises, Inc.
8 * Author URI: https://gandenterprisesinc.com
9 * License: GPL v2 or later
10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 * Text Domain: joan
12 * Domain Path: /languages
13 * Requires at least: 5.0
14 * Requires PHP: 7.0
15 */
16
17 defined('ABSPATH') || exit;
18
19 define('JOAN_VERSION', '6.0.9');
20 define('JOAN_PLUGIN_DIR', plugin_dir_path(__FILE__));
21 define('JOAN_PLUGIN_URL', plugin_dir_url(__FILE__));
22
23 // CRITICAL: Add locale filters IMMEDIATELY for both admin and frontend
24 add_filter('locale', 'joan_override_locale', 1);
25 add_filter('determine_locale', 'joan_override_locale', 1);
26
27 /**
28 * Override locale based on user preference (works for both admin and frontend)
29 */
30 function joan_override_locale($locale) {
31 if (!function_exists('is_user_logged_in') || !function_exists('get_user_meta')) {
32 return $locale;
33 }
34
35 if (is_user_logged_in()) {
36 $user_id = get_current_user_id();
37 $user_language = get_user_meta($user_id, 'joan_language', true);
38
39 if (!empty($user_language)) {
40 global $l10n;
41 if (isset($l10n['joan'])) {
42 unset($l10n['joan']);
43 }
44
45 return $user_language;
46 }
47 }
48
49 if (!is_admin() && isset($_COOKIE['joan_visitor_locale'])) {
50 return sanitize_text_field($_COOKIE['joan_visitor_locale']);
51 }
52
53 return $locale;
54 }
55
56 // Load text domain early
57 add_action('plugins_loaded', function() {
58 load_plugin_textdomain('joan', false, dirname(plugin_basename(__FILE__)) . '/languages');
59 }, 1);
60
61 // Apply frontend-specific locale handling
62 add_action('plugins_loaded', function() {
63 if (!is_admin()) {
64 joan_apply_frontend_locale();
65 }
66 }, 2);
67
68 /**
69 * Apply frontend locale detection and switching
70 */
71 function joan_apply_frontend_locale() {
72 $chosen_locale = '';
73
74 if (is_user_logged_in()) {
75 $user_id = get_current_user_id();
76 $user_locale = get_user_meta($user_id, 'joan_language', true);
77
78 if (!empty($user_locale)) {
79 $chosen_locale = $user_locale;
80 }
81 }
82
83 if (empty($chosen_locale) && isset($_COOKIE['joan_visitor_locale'])) {
84 $chosen_locale = sanitize_text_field($_COOKIE['joan_visitor_locale']);
85 }
86
87 if (empty($chosen_locale)) {
88 $chosen_locale = joan_detect_browser_language();
89 }
90
91 if (!empty($chosen_locale) && $chosen_locale !== get_locale()) {
92 $mofile = JOAN_PLUGIN_DIR . 'languages/joan-' . $chosen_locale . '.mo';
93
94 if (file_exists($mofile)) {
95 global $l10n;
96 if (isset($l10n['joan'])) {
97 unset($l10n['joan']);
98 }
99
100 load_textdomain('joan', $mofile);
101
102 if (!headers_sent()) {
103 setcookie('joan_visitor_locale', $chosen_locale, time() + (30 * 24 * 60 * 60), '/', '', false, true);
104 }
105 }
106 }
107 }
108
109 /**
110 * Detect browser's preferred language
111 */
112 function joan_detect_browser_language() {
113 $browser_langs = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
114
115 if (empty($browser_langs)) {
116 return '';
117 }
118
119 preg_match_all('/([a-z]{2,3}(?:-[A-Z]{2,3})?)\s*(?:;\s*q\s*=\s*([0-9.]+))?/i', $browser_langs, $matches);
120
121 $languages = array();
122 foreach ($matches[1] as $index => $lang) {
123 $quality = isset($matches[2][$index]) && $matches[2][$index] !== '' ? (float)$matches[2][$index] : 1.0;
124 $languages[$lang] = $quality;
125 }
126
127 arsort($languages);
128
129 $locale_map = array(
130 'en' => 'en_US',
131 'en-us' => 'en_US',
132 'en-gb' => 'en_GB',
133 'fr' => 'fr_FR',
134 'fr-fr' => 'fr_FR',
135 'de' => 'de_DE',
136 'de-de' => 'de_DE',
137 'es' => 'es_ES',
138 'es-es' => 'es_ES',
139 'pt' => 'pt_BR',
140 'pt-br' => 'pt_BR',
141 'pt-pt' => 'pt_PT',
142 'it' => 'it_IT',
143 'it-it' => 'it_IT',
144 'nl' => 'nl_NL',
145 'nl-nl' => 'nl_NL',
146 'ht' => 'ht_HT',
147 'ht-ht' => 'ht_HT',
148 'acf' => 'acf_LC',
149 'acf-lc' => 'acf_LC',
150 'gcf' => 'gcf_GP',
151 'gcf-gp' => 'gcf_GP',
152 );
153
154 $available_locales = joan_get_available_locales();
155
156 foreach ($languages as $lang => $quality) {
157 $lang_lower = strtolower($lang);
158
159 if (isset($locale_map[$lang_lower])) {
160 $locale = $locale_map[$lang_lower];
161 if (in_array($locale, $available_locales)) {
162 return $locale;
163 }
164 }
165
166 $locale_format = str_replace('-', '_', $lang);
167 if (in_array($locale_format, $available_locales)) {
168 return $locale_format;
169 }
170
171 $lang_code = substr($lang_lower, 0, 2);
172 if (isset($locale_map[$lang_code])) {
173 $locale = $locale_map[$lang_code];
174 if (in_array($locale, $available_locales)) {
175 return $locale;
176 }
177 }
178 }
179
180 return '';
181 }
182
183 /**
184 * Get available JOAN translation files
185 */
186 function joan_get_available_locales() {
187 static $cached_locales = null;
188
189 if ($cached_locales !== null) {
190 return $cached_locales;
191 }
192
193 $available = array();
194 $lang_dir = JOAN_PLUGIN_DIR . 'languages/';
195
196 if (is_dir($lang_dir)) {
197 $files = glob($lang_dir . 'joan-*.mo');
198 foreach ($files as $file) {
199 if (preg_match('/joan-([a-z]{2,3}_[A-Z]{2,3})\.mo$/i', basename($file), $matches)) {
200 $available[] = $matches[1];
201 }
202 }
203 }
204
205 $cached_locales = $available;
206
207 return $available;
208 }
209
210 add_action('init', function() {
211 $role = get_role('administrator');
212 if ($role) {
213 $role->add_cap('manage_joan_schedule');
214 }
215 });
216
217 register_activation_hook(__FILE__, function() {
218 $old_version = get_option('joan_version');
219 $migration_handled = get_option('joan_migration_handled', false);
220
221 if ($old_version && version_compare($old_version, '6.0.0', '<') && !$migration_handled) {
222 update_option('joan_needs_migration', true);
223 update_option('joan_backup_reminder', true);
224 deactivate_plugins(plugin_basename(__FILE__));
225 } else {
226 if (!$migration_handled) {
227 update_option('joan_migration_handled', true);
228 }
229 joan_ensure_table();
230 joan_ensure_columns_exist();
231 }
232
233 update_option('joan_version', JOAN_VERSION);
234 });
235
236 add_action('admin_notices', function() {
237 if (get_option('joan_backup_reminder')) {
238 joan_backup_reminder_notice();
239 }
240 if (get_option('joan_migration_handled') && !get_option('joan_post_migration_notice_dismissed')) {
241 joan_migration_success_notice();
242 }
243 });
244
245
246 add_action('admin_init', function() {
247 if ( ! current_user_can( 'manage_options' ) ) { return; }
248 if ( isset($_GET['joan_dismiss_success']) && $_GET['joan_dismiss_success'] === '1' ) {
249 check_admin_referer('joan_dismiss_success');
250 update_option('joan_post_migration_notice_dismissed', true);
251 wp_redirect( admin_url('admin.php?page=joan-schedule') );
252 exit;
253 }
254 });
255 function joan_backup_reminder_notice() {
256 $proceed_url = wp_nonce_url(
257 admin_url('admin.php?joan_proceed_migration=1'),
258 'joan_migration_proceed'
259 );
260 ?>
261 <div class="notice notice-warning">
262 <h2><?php esc_html_e('IMPORTANT: JOAN 6.0.9 Upgrade Notice', 'joan'); ?></h2>
263 <p><strong><?php esc_html_e('Version 6.0.9 requires a fresh start.', 'joan'); ?></strong> <?php esc_html_e('The plugin has been temporarily deactivated.', 'joan'); ?></p>
264 <p><?php esc_html_e('The JOAN plugin has been deactivated so you can backup your schedule.', 'joan'); ?></p>
265 <p><strong><?php esc_html_e('To backup your schedule:', 'joan'); ?></strong></p>
266 <ol>
267 <li><?php esc_html_e('Go to your JOAN admin page (if still accessible)', 'joan'); ?></li>
268 <li><?php esc_html_e('Take screenshots of your schedule', 'joan'); ?></li>
269 <li><?php esc_html_e('Write down show names, times, jock names, and image URLs', 'joan'); ?></li>
270 <li><?php esc_html_e('When ready, reactivate the plugin and choose to proceed with the upgrade', 'joan'); ?></li>
271 </ol>
272 </div>
273 <?php
274 }
275
276 function joan_migration_success_notice() {
277 if ( ! current_user_can( 'manage_options' ) ) { return; }
278 if ( get_option( 'joan_post_migration_notice_dismissed' ) ) { return; }
279
280 $manager_url = admin_url( 'admin.php?page=joan-schedule' );
281 $dismiss_url = wp_nonce_url( admin_url( 'admin.php?joan_dismiss_success=1' ), 'joan_dismiss_success' );
282
283 echo '<div class="notice notice-success is-dismissible">';
284 echo '<p><strong>' . esc_html__( 'JOAN has been Successfully Activated!', 'joan' ) . '</strong></p>';
285 echo '<p>' . esc_html__( 'The plugin was upgraded and old data were cleaned up. You can now begin adding your shows using the new interface.', 'joan' ) . '</p>';
286 echo '<p>'
287 . '<a class="button button-primary" href="' . esc_url( $manager_url ) . '">' . esc_html__( 'Go to Schedule Manager', 'joan' ) . '</a>'
288 . ' | '
289 . '<a class="button-link" href="' . esc_url( $dismiss_url ) . '">' . esc_html__( 'Dismiss Forever', 'joan' ) . '</a>'
290 . '</p>';
291 echo '</div>';
292 }
293 function joan_migrate_from_old_version() {
294 global $wpdb;
295
296 $old_tables = [
297 $wpdb->prefix . 'joan_schedule_legacy',
298 $wpdb->prefix . 'showtime_jockonair',
299 $wpdb->prefix . 'onair_schedule',
300 ];
301
302 foreach ($old_tables as $table) {
303 $wpdb->query("DROP TABLE IF EXISTS $table");
304 }
305
306 $current_table = $wpdb->prefix . 'joan_schedule';
307 $wpdb->query("DROP TABLE IF EXISTS $current_table");
308
309 joan_ensure_table();
310
311 delete_option('joan_legacy_data_imported');
312 delete_option('joan_old_version_data');
313
314 update_option('joan_time_format', '12');
315 update_option('joan_timezone', 'America/New_York');
316 update_option('joan_show_next_show', '1');
317 update_option('joan_show_jock_image', '1');
318 update_option('joan_widget_max_width', '300');
319 update_option('joan_schedule_status', 'active');
320 update_option('joan_off_air_message', __('We are currently off the air. Please check back later!', 'joan'));
321 }
322
323 if (get_option('joan_migration_handled', false)) {
324 require_once JOAN_PLUGIN_DIR . 'includes/translations.php';
325 require_once JOAN_PLUGIN_DIR . 'includes/language-switcher.php';
326 require_once JOAN_PLUGIN_DIR . 'includes/crud.php';
327 require_once JOAN_PLUGIN_DIR . 'includes/admin-menu.php';
328 require_once JOAN_PLUGIN_DIR . 'includes/shortcodes.php';
329 require_once JOAN_PLUGIN_DIR . 'includes/widget.php';
330 require_once JOAN_PLUGIN_DIR . 'includes/import-legacy.php';
331 require_once JOAN_PLUGIN_DIR . 'includes/compatibility-check.php';
332 }
333
334 function joan_enqueue_admin_assets($hook) {
335 if (!get_option('joan_migration_handled', false) || strpos($hook, 'joan') !== false) {
336 wp_enqueue_script('joan-admin', JOAN_PLUGIN_URL . 'assets/js/admin.js', ['jquery'], JOAN_VERSION, true);
337 }
338 }
339 add_action('admin_enqueue_scripts', 'joan_enqueue_admin_assets');
340
341 function joan_enqueue_assets() {
342 if (!get_option('joan_migration_handled', false)) return;
343
344 wp_enqueue_style('joan-style', JOAN_PLUGIN_URL . 'assets/css/joan.css', [], JOAN_VERSION);
345 wp_enqueue_script('joan-script', JOAN_PLUGIN_URL . 'assets/js/joan.js', ['jquery'], JOAN_VERSION, true);
346
347 $day_names = array(
348 'Sunday' => __('Sunday', 'joan'),
349 'Monday' => __('Monday', 'joan'),
350 'Tuesday' => __('Tuesday', 'joan'),
351 'Wednesday' => __('Wednesday', 'joan'),
352 'Thursday' => __('Thursday', 'joan'),
353 'Friday' => __('Friday', 'joan'),
354 'Saturday' => __('Saturday', 'joan')
355 );
356
357 wp_localize_script('joan-script', 'joan_ajax', [
358 'ajaxurl' => admin_url('admin-ajax.php'),
359 'nonce' => wp_create_nonce('joan_frontend_nonce'),
360 'settings' => [
361 'show_next_show' => get_option('joan_show_next_show', '1'),
362 'show_jock_image' => get_option('joan_show_jock_image', '1'),
363 'joan_show_local_time' => get_option('joan_show_local_time', '1'),
364 'joan_allow_timezone_selector' => get_option('joan_allow_timezone_selector', '1'),
365 'time_format' => get_option('joan_time_format', '12'),
366 'widget_max_width' => get_option('joan_widget_max_width', '300'),
367 'joan_dark_mode' => get_option('joan_dark_mode', 'auto'),
368 'joan_dark_mode_override' => get_option('joan_dark_mode_override', '0'),
369 'joan_show_day_emoji' => get_option('joan_show_day_emoji', '0'),
370 'joan_jock_field_label' => get_option('joan_jock_field_label', ''),
371 'joan_link_assignment' => get_option('joan_link_assignment', 'jock_name'),
372 'joan_image_display_mode' => get_option('joan_image_display_mode', 'constrained'),
373 'joan_center_widget_title' => get_option('joan_center_widget_title', '0'),
374 'joan_jock_only_mode' => get_option('joan_jock_only_mode', '0')
375 ],
376 'i18n' => [
377 'days' => $day_names,
378 'schedule' => __('Schedule', 'joan'),
379 'upcoming_shows' => __('Upcoming Shows', 'joan'),
380 'tooltip_sunday' => __('Helium (He) - Second most abundant element in the universe, powers the sun through nuclear fusion', 'joan'),
381 'tooltip_monday' => __('Silver (Ag) - Precious metal with highest electrical conductivity, reflects light like moonlight', 'joan'),
382 'tooltip_tuesday' => __('Iron (Fe) - Most abundant metal on Earth, essential for steel and human blood', 'joan'),
383 'tooltip_wednesday' => __('Mercury (Hg) - Only metal that is liquid at room temperature, used in thermometers', 'joan'),
384 'tooltip_thursday' => __('Gold (Au) - Noble metal that never tarnishes, symbol of value and permanence', 'joan'),
385 'tooltip_friday' => __('Copper (Cu) - Reddish metal essential for electrical wiring, naturally antibacterial', 'joan'),
386 'tooltip_saturday' => __('Lead (Pb) - Dense metal historically used for protection, now known to be toxic', 'joan'),
387 'loading' => __(esc_html__('Loading current show...', 'joan'), 'joan'),
388 'retrying' => __('Retrying...', 'joan'),
389 'refreshing' => __('Refreshing...', 'joan'),
390 'config_error' => __('Configuration error.', 'joan'),
391 'refresh_page' => __('Refresh page', 'joan'),
392 'unable_to_load' => __('Unable to load current show.', 'joan'),
393 'server_slow' => __('Server is responding slowly.', 'joan'),
394 'connection_timeout' => __('Connection timeout', 'joan'),
395 'request_blocked' => __('Request blocked or network error.', 'joan'),
396 'access_denied' => __('Access denied by server.', 'joan'),
397 'server_internal_error' => __('Server internal error.', 'joan'),
398 'server_error' => __('Server error', 'joan'),
399 'invalid_response' => __('Invalid response from server.', 'joan'),
400 'automatic_retries_stopped' => __('Automatic retries stopped.', 'joan'),
401 'off_air_default' => __('We are currently off the air. Please check back later!', 'joan'),
402 'up_next' => __('Up Next:', 'joan'),
403 'hosted_by' => __('Hosted by', 'joan'),
404 'on_air_now' => __('On Air Now', 'joan'),
405 'current_time' => __('Current time:', 'joan'),
406 'local_time' => __('Local time:', 'joan'),
407 'switch_timezone' => __('Switch timezone:', 'joan'),
408 'time_display_unavailable' => __('Time display unavailable', 'joan'),
409 'filter_by_day' => __('Filter by day:', 'joan'),
410 'all_days' => __('All Days', 'joan'),
411 'whats_on_today' => __('What\'s on today', 'joan'),
412 'no_schedule_available' => __('No schedule available.', 'joan'),
413 'no_shows_scheduled_for_today' => __('No shows scheduled for today.', 'joan'),
414 'no_shows_scheduled_for' => __('No shows scheduled for', 'joan'),
415 'no_upcoming_shows' => __('No upcoming shows scheduled.', 'joan'),
416 'schedule_suspended' => __('Schedule suspended. Special programming', 'joan'),
417 'show' => __('Show', 'joan'),
418 'day' => __('Day', 'joan'),
419 'time' => __('Time', 'joan'),
420 'jock' => __('Jock', 'joan'),
421 'image' => __('Image', 'joan'),
422 'link' => __('Link', 'joan'),
423 'n_a' => __('N/A', 'joan')
424 ]
425 ]);
426 }
427 add_action('wp_enqueue_scripts', 'joan_enqueue_assets');
428
429 function joan_ensure_table() {
430 global $wpdb;
431 $table_name = $wpdb->prefix . 'joan_schedule';
432 $charset_collate = $wpdb->get_charset_collate();
433
434 $sql = "CREATE TABLE IF NOT EXISTS $table_name (
435 id INT AUTO_INCREMENT PRIMARY KEY,
436 show_name VARCHAR(255),
437 start_day VARCHAR(10),
438 start_time TIME,
439 end_time TIME,
440 image_url TEXT,
441 dj_name VARCHAR(255),
442 link_url TEXT
443 ) $charset_collate;";
444
445 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
446 dbDelta($sql);
447 }
448
449 function joan_ensure_columns_exist() {
450 global $wpdb;
451 $table = $wpdb->prefix . 'joan_schedule';
452
453 $columns = $wpdb->get_col("DESCRIBE $table", 0);
454
455 if (!in_array('link_url', $columns)) {
456 $wpdb->query("ALTER TABLE $table ADD COLUMN link_url TEXT");
457 }
458 }
459
460 register_activation_hook(__FILE__, function() {
461 joan_ensure_table();
462 joan_ensure_columns_exist();
463 });
464
465 add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) {
466 if (get_option('joan_migration_handled', false)) {
467 $settings_link = '<a href="admin.php?page=joan-schedule">' . __('Schedule', 'joan') . '</a>';
468 array_unshift($links, $settings_link);
469 }
470 return $links;
471 });
472
473 add_action('wp_ajax_joan_widget_refresh', 'joan_handle_widget_refresh');
474 add_action('wp_ajax_nopriv_joan_widget_refresh', 'joan_handle_widget_refresh');
475
476 function joan_handle_widget_refresh() {
477 if (!get_option('joan_migration_handled', false)) {
478 wp_die('Plugin not properly initialized');
479 }
480
481 if (!wp_verify_nonce($_POST['nonce'], 'joan_frontend_nonce')) {
482 wp_die('Security check failed');
483 }
484
485 $crud = new stdClass();
486 $crud->action = 'read';
487 $crud->read_type = 'current';
488
489 do_action('wp_ajax_show_time_curd');
490 }
491
492 // JOAN: Permanently dismissible activation notice for 6.0.9
493 add_action('admin_init', function() {
494 if (!current_user_can('manage_options')) { return; }
495 if (isset($_GET['joan_dismiss_activation_609']) && $_GET['joan_dismiss_activation_609'] === '1') {
496 $nonce = isset($_GET['_joan_nonce']) ? $_GET['_joan_nonce'] : '';
497 if (wp_verify_nonce($nonce, 'joan_dismiss_609')) {
498 update_option('joan_hide_activation_notice_609', '1', false);
499 }
500 }
501 });
502
503