PluginProbe ʕ •ᴥ•ʔ
Automation Web Platform – Notifications and OTP for WooCommerce, Advanced Country Code / 4.8.6
Automation Web Platform – Notifications and OTP for WooCommerce, Advanced Country Code v4.8.6
4.8.6 4.8.5 4.8.4 trunk 4.8.3
automation-web-platform / views / class-wawp-enqueue-scripts.php
automation-web-platform / views Last commit date
class-wawp-enqueue-scripts.php 1 week ago index.php 1 month ago
class-wawp-enqueue-scripts.php
910 lines
1 <?php
2 /**
3 * Enqueue scripts and styles.
4 *
5 * @package automation-web-platform
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12
13 /**
14 * Class WAWP_Enqueue_Scripts
15 * Handles enqueuing of scripts and styles.
16 */
17 class WAWP_Enqueue_Scripts {
18
19 /**
20 * Enqueue admin styles and scripts.
21 */
22 public static function enqueue_admin_styles_scripts() {
23 $screen = get_current_screen();
24 if ( ! $screen ) {
25 return;
26 }
27
28 $is_wawp_page = strpos( $screen->id, 'wawp' ) !== false;
29
30 $section_raw = filter_input( INPUT_GET, 'wawp_section', FILTER_SANITIZE_SPECIAL_CHARS );
31 $section = $section_raw ? sanitize_key( $section_raw ) : 'dashboard';
32
33 if ( $is_wawp_page ) {
34 if ( in_array( $section, array( 'dashboard', 'senders', 'activity_hub', 'system_info', 'notifications', 'email_templates', 'chat_widget', 'country-code', 'google_recaptcha', 'authentication-pages', 'otp_messages', 'registration-form', 'passwordless-login', 'whatsapp-web-sender', 'smtp-sender', 'block-manager' ), true ) ) {
35 // List core WP dependencies.
36 $dependencies = array( 'jquery', 'wp-polyfill', 'wp-element', 'wp-i18n', 'wp-api-fetch', 'wp-hooks', 'wp-data', 'wp-util', 'wp-api', 'wp-components', 'wp-url', 'underscore', 'backbone' );
37
38 // Dequeue WP Auth Check to prevent "hasClass of undefined" errors in SPA context.
39 wp_dequeue_script( 'wp-auth-check' );
40 wp_dequeue_script( 'heartbeat' );
41
42 wp_enqueue_style( 'wawp-local-fonts', WAWP_PLUGIN_URL . 'assets/css/wawp-fonts.css', array(), '1.0.0' );
43
44 // Load React Application.
45 // Find the hashed index file (e.g. index-AbCdEf.js) generated by Vite.
46 $dist_assets_dir = WAWP_PLUGIN_DIR . 'app/dist/assets/';
47 $index_js_file = self::find_hashed_asset( $dist_assets_dir, 'index', 'js' );
48 $index_css_file = self::find_hashed_asset( $dist_assets_dir, 'index', 'css' );
49
50 $index_js_path = $dist_assets_dir . $index_js_file;
51 $index_css_path = $dist_assets_dir . $index_css_file;
52
53 // Use filemtime as version — the hashed filename already ensures cache busting,
54 // so we don't need wawp_force_cache_bust_version for the entry file anymore.
55 $js_ver = file_exists( $index_js_path ) ? filemtime( $index_js_path ) : WAWP_PLUGIN_VERSION;
56 $css_ver = file_exists( $index_css_path ) ? filemtime( $index_css_path ) : WAWP_PLUGIN_VERSION;
57
58 wp_enqueue_media();
59 wp_enqueue_script( 'wawp-react-dashboard', WAWP_PLUGIN_URL . 'app/dist/assets/' . $index_js_file, $dependencies, $js_ver, true );
60 wp_enqueue_style( 'wawp-react-dashboard-css', WAWP_PLUGIN_URL . 'app/dist/assets/' . $index_css_file, array(), $css_ver );
61 wp_enqueue_style( 'wawp-remix-icon', WAWP_PLUGIN_URL . 'assets/css/resources/remixicon.css', array(), '4.6.0' );
62 wp_enqueue_style( 'wawp-chat-widget-style', WAWP_PLUGIN_URL . 'assets/css/style.css', array(), WAWP_PLUGIN_VERSION );
63
64 add_filter(
65 'script_loader_tag',
66 function ( $tag, $handle ) {
67 if ( 'wawp-react-dashboard' === $handle ) {
68 return str_replace( '<script ', '<script type="module" ', $tag );
69 }
70 return $tag;
71 },
72 10,
73 2
74 );
75
76 if ( function_exists( 'wp_set_script_translations' ) ) {
77 wp_set_script_translations( 'wawp-react-dashboard', 'automation-web-platform', WAWP_PLUGIN_DIR . 'languages' );
78 }
79
80 self::localize_react_dashboard_data( $section );
81 }
82 }
83
84 // 4. Custom Messages (Users/Profile)
85 if ( in_array( $screen->base, array( 'users', 'profile', 'user-edit' ), true ) ) {
86 wp_enqueue_style( 'wawp-badges', WAWP_PLUGIN_URL . 'assets/css/wawp-badges.css', array(), WAWP_PLUGIN_VERSION );
87
88 if ( wawp()->senders->is_whatsapp_active() ) {
89 wp_enqueue_script( 'wawp-send-msg', WAWP_PLUGIN_URL . 'assets/js/wawp-send-msg.js', array( 'jquery' ), WAWP_PLUGIN_VERSION, true );
90
91 $online_instances = WAWP()->instance_manager->get_online_instances_cached();
92 $online = array();
93 if ( $online_instances ) {
94 foreach ( $online_instances as $inst ) {
95 $online[] = array(
96 'instance_id' => $inst->instance_id,
97 'name' => $inst->name,
98 );
99 }
100 }
101
102 wp_localize_script(
103 'wawp-send-msg',
104 'wawpSendMsgData',
105 array(
106 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
107 'security' => wp_create_nonce( 'wawp_send_msg_nonce' ),
108 'onlineInstances' => $online,
109 'noOnlineInstance' => esc_html__( 'No online instances found.', 'automation-web-platform' ),
110 )
111 );
112 }
113 }
114 }
115
116
117
118 /**
119 * Enqueue frontend styles and scripts.
120 */
121 public static function enqueue_frontend_styles_scripts() {
122 $is_custom_pages_enabled = WAWP()->get_wawp_setting( WAWP_Settings_Registry::CUSTOM_PAGES_ENABLED, 1 );
123 $is_replace_wc_forms = WAWP()->get_wawp_setting( WAWP_Settings_Registry::REPLACE_WC_FORMS );
124 $is_replace_wc_checkout_login = WAWP()->get_wawp_setting( WAWP_Settings_Registry::REPLACE_WC_CHECKOUT_LOGIN );
125
126 if ( $is_custom_pages_enabled && ! is_admin() ) {
127 $enqueue_overrides = false;
128
129 if ( $is_replace_wc_forms && function_exists( 'is_account_page' ) && is_account_page() ) {
130 $enqueue_overrides = true;
131 }
132
133 if ( $is_replace_wc_checkout_login && function_exists( 'is_checkout' ) && is_checkout() ) {
134 $enqueue_overrides = true;
135 }
136
137 if ( $enqueue_overrides ) {
138 wp_enqueue_style( 'wawp-frontend-overrides', WAWP_PLUGIN_URL . 'assets/css/wawp-frontend-overrides.css', array(), WAWP_PLUGIN_VERSION );
139 }
140 }
141
142 // Central Data Hub for all frontend localized variables.
143 $global_vars = apply_filters( 'wawp_frontend_global_vars', array() );
144 if ( ! empty( $global_vars ) ) {
145 $inline_script = '';
146 foreach ( $global_vars as $var_name => $data ) {
147 $inline_script .= 'var ' . esc_js( $var_name ) . ' = ' . wp_json_encode( $data ) . ";\n";
148 }
149 wp_register_script( 'wawp-global-data-hub', false, array(), WAWP_PLUGIN_VERSION, true );
150 wp_enqueue_script( 'wawp-global-data-hub' );
151 wp_add_inline_script( 'wawp-global-data-hub', $inline_script );
152 }
153 }
154
155 /**
156 * Localize React dashboard data.
157 *
158 * @param string $section Current section.
159 */
160 public static function localize_react_dashboard_data( $section ) {
161 $user_id = get_current_user_id();
162 $locale = get_user_locale();
163 $cache_key = 'wawp_dashboard_data_full_' . $user_id . '_' . $locale;
164
165 if ( filter_input( INPUT_GET, 'force_refresh' ) ) {
166 delete_transient( $cache_key );
167 wp_cache_delete( $cache_key, 'wawp_database' );
168 delete_transient( 'wawp_latest_plugin_version' );
169 }
170
171 $data = wp_cache_get( $cache_key, 'wawp_database' );
172
173 if ( false === $data ) {
174 $data = get_transient( $cache_key );
175 if ( false === $data ) {
176 $data = self::prepare_dashboard_data( $section, true );
177 set_transient( $cache_key, $data, 60 ); // Short transient for live data.
178
179 // Track keys for mass clearing without direct SQL.
180 $keys = get_option( 'wawp_dashboard_transient_keys', array() );
181 if ( ! is_array( $keys ) ) {
182 $keys = array();
183 }
184 if ( ! in_array( $cache_key, $keys, true ) ) {
185 $keys[] = $cache_key;
186 update_option( 'wawp_dashboard_transient_keys', $keys, false );
187 }
188 }
189 wp_cache_set( $cache_key, $data, 'wawp_database', 60 );
190 }
191
192 // Dynamically override the active section for the SPA to know where we landed.
193 // Also override popupStep from the live URL — cache always stores 0, so we must re-read it here.
194 if ( isset( $data['global'] ) ) {
195 $data['global']['section'] = $section;
196 $data['global']['popupStep'] = (int) filter_input( INPUT_GET, 'wawp_popup_step', FILTER_SANITIZE_NUMBER_INT );
197 }
198
199 wp_localize_script( 'wawp-react-dashboard', 'wawpDashboardData', $data );
200 }
201
202 /**
203 * Prepare data for the dashboard.
204 *
205 * @param string $section Current section.
206 * @param bool $is_full Whether to return full data.
207 * @return array Dashboard data.
208 */
209 public static function prepare_dashboard_data( $section, $is_full = false ) {
210 try {
211 // Dynamic Translation Reloader for user locale (to support REST API requests loaded under user locale).
212 if ( function_exists( 'get_user_locale' ) ) {
213 $user_locale = get_user_locale();
214 if ( $user_locale && get_locale() !== $user_locale ) {
215 $mofile = WAWP_PLUGIN_DIR . 'languages/automation-web-platform-' . $user_locale . '.mo';
216 if ( file_exists( $mofile ) ) {
217 load_textdomain( 'automation-web-platform', $mofile );
218 } else {
219 $parts = explode( '_', $user_locale );
220 if ( count( $parts ) > 1 ) {
221 $fallback_mofile = WAWP_PLUGIN_DIR . 'languages/automation-web-platform-' . $parts[0] . '.mo';
222 if ( file_exists( $fallback_mofile ) ) {
223 load_textdomain( 'automation-web-platform', $fallback_mofile );
224 }
225 }
226 }
227 }
228 }
229
230 if ( ! function_exists( 'is_plugin_active' ) ) {
231 require_once ABSPATH . 'wp-admin/includes/plugin.php';
232 }
233
234 $user_data = array(
235 'user_email' => wp_get_current_user()->user_email,
236 'plan_name' => 'Pro Lifetime',
237 'is_lifetime' => true,
238 'avatar' => '',
239 );
240 $connector = wawp()->senders;
241 $is_connected = true;
242 $is_sso = true;
243 $icons_url = WAWP_PLUGIN_URL . 'assets/icons/';
244
245 // Sidebar logic.
246 $issues_count = 0;
247 if ( WAWP()->system_info ) {
248 $issues_count = WAWP()->system_info->get_cached_issue_count();
249 }
250
251 // Notices.
252 $notices = array();
253 if ( class_exists( 'WAWP_Admin_Notices' ) ) {
254 if ( method_exists( 'WAWP_Admin_Notices', 'is_wa_offline' ) && WAWP_Admin_Notices::is_wa_offline() ) {
255 $notices[] = array(
256 'type' => 'offline',
257 'url' => admin_url( 'admin.php?page=wawp&wawp_section=whatsapp-web-sender' ),
258 );
259 }
260 }
261
262 $country_options = WAWP()->get_wawp_setting( 'woo_intl_tel_options', array() );
263 $default_country = ! empty( $country_options['default_country_code'] ) ? strtoupper( $country_options['default_country_code'] ) : 'US';
264
265 // Detect if any caching plugin is active.
266 $cache_plugins = array(
267 'wp-rocket/wp-rocket.php' => 'WP Rocket',
268 'litespeed-cache/litespeed-cache.php' => 'LiteSpeed Cache',
269 'w3-total-cache/w3-total-cache.php' => 'W3 Total Cache',
270 'wp-super-cache/wp-cache.php' => 'WP Super Cache',
271 'wp-fastest-cache/wpFastestCache.php' => 'WP Fastest Cache',
272 'sg-cachepress/sg-cachepress.php' => 'SG Optimizer',
273 'breeze/breeze.php' => 'Breeze',
274 'autoptimize/autoptimize.php' => 'Autoptimize',
275 'cache-enabler/cache-enabler.php' => 'Cache Enabler',
276 'wp-hummingbird/wp-hummingbird.php' => 'Hummingbird',
277 'hummingbird-performance/wp-hummingbird.php' => 'Hummingbird',
278 'swift-performance-lite/swift-performance-lite.php' => 'Swift Performance',
279 'flying-press/flying-press.php' => 'FlyingPress',
280 'nitropack/nitropack.php' => 'NitroPack',
281 );
282 $active_cache_plugins = array();
283 foreach ( $cache_plugins as $plugin_path => $plugin_name ) {
284 if ( is_plugin_active( $plugin_path ) ) {
285 $active_cache_plugins[] = $plugin_name;
286 }
287 }
288 $has_active_cache = ! empty( $active_cache_plugins );
289
290 // 1. Base Global Data (Always present)
291 $data = array(
292 'notices' => $notices,
293 'global' => array(
294 'wpRestNonce' => wp_create_nonce( 'wp_rest' ),
295 'apiRestUrl' => get_rest_url( null, 'wawp/v1' ),
296 'restUrl' => get_rest_url( null, 'wawp/v1' ),
297 'settingsRestUrl' => get_rest_url( null, 'wawp/v1/settings' ),
298 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
299 'nonce' => wp_create_nonce( 'wawp_nonce' ),
300 'isRtl' => is_rtl() || ( function_exists( 'get_user_locale' ) && 'ar' === substr( get_user_locale(), 0, 2 ) ),
301 'version' => WAWP_PLUGIN_VERSION,
302 'pluginUrl' => WAWP_PLUGIN_URL,
303 'popupStep' => (int) filter_input( INPUT_GET, 'wawp_popup_step', FILTER_SANITIZE_NUMBER_INT ),
304 'section' => $section,
305 'country' => $default_country,
306 'adminEmail' => wp_get_current_user()->user_email,
307 'currentLocale' => get_locale(),
308 'currentUserLocale' => get_user_locale(),
309 'availableLanguages' => self::get_site_available_languages(),
310 'languageSwitchNonce' => wp_create_nonce( 'wawp_switch_language' ),
311 'flushCacheNonce' => wp_create_nonce( 'wawp_flush_cache' ),
312 'hasActiveCache' => $has_active_cache,
313 'activeCachePlugins' => $active_cache_plugins,
314 ),
315 'nonce' => wp_create_nonce( 'wawp_nonce' ),
316 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
317 'rtl' => is_rtl() || ( function_exists( 'get_user_locale' ) && 'ar' === substr( get_user_locale(), 0, 2 ) ),
318 'i18n' => array(),
319 );
320
321 // 2. Full Sidebar and Stats (If full load or dashboard)
322 if ( $is_full || 'dashboard' === $section ) {
323 $dashboard = WAWP_Dashboard::get_instance();
324 $dashboard_data = $dashboard->get_dashboard_data();
325 $data['sidebarData'] = array(
326 'issuesCount' => $issues_count,
327 'enabledSections' => $dashboard_data['features'] ?? array(),
328 'enabledSenders' => array(
329 'email' => $connector->is_email_sender_enabled(),
330 'wa' => $connector->is_whatsapp_sender_enabled(),
331 'block' => $connector->is_block_manager_active(),
332 ),
333 'allowedSenders' => array(
334 'email' => true,
335 'wa' => true,
336 'block' => true,
337 ),
338 );
339 $data['i18n'] = $dashboard_data['i18n'] ?? array();
340 $data['urls'] = $dashboard_data['urls'] ?? array();
341 $data['features'] = $dashboard_data['features'] ?? array();
342 $data['isGuest'] = $dashboard_data['isGuest'] ?? false;
343 $data['stats'] = $dashboard_data['stats'] ?? array();
344 $data['wooStatus'] = $dashboard_data['wooStatus'] ?? array();
345 $data['icons'] = array(
346 'baseUrl' => $icons_url,
347 'successGif' => WAWP_PLUGIN_URL . 'assets/img/success.gif',
348 'errorGif' => WAWP_PLUGIN_URL . 'assets/img/error.gif',
349 );
350 }
351
352 // 3. Section Specific Data (Always include if mapped)
353 $mapped_section = $section;
354 if ( 'activity_hub' === $section ) {
355 $mapped_section = 'activity-hub';
356 } elseif ( 'email_templates' === $section ) {
357 $mapped_section = 'email-templates';
358 } elseif ( 'otp_messages' === $section ) {
359 $mapped_section = 'passwordless-login';
360 }
361
362 // 3. Section Specific Data Dispatch (Registry Pattern - Point 4)
363 $handlers = array(
364 'senders' => array( 'WAWP_Senders', 'get_instance' ),
365 'whatsapp-web-sender' => array( 'WAWP_Senders', 'get_instance' ),
366 'registration-form' => array( 'WAWP_Signup', 'get_instance' ),
367 'passwordless-login' => array( 'WAWP_Otp_Login', 'get_instance' ),
368 'system_info' => array( 'WAWP_System_Info', 'get_instance' ),
369 'notifications' => array( 'WAWP_Flow_Builder', 'get_instance' ),
370 'activity-hub' => array( 'WAWP_Unified_Log', 'get_instance' ),
371 'google_recaptcha' => array( 'WAWP_Google_Recaptcha', 'get_instance' ),
372 'chat_widget' => array( 'WAWP_Chat_Widget', 'get_instance' ),
373 'smtp-sender' => array( 'WAWP_Email_Sender', 'get_instance' ),
374 'block-manager' => array( 'WAWP_Block_Manager', 'get_instance' ),
375 'email-templates' => array( 'WAWP_Email_Templates', 'get_instance' ),
376 'authentication-pages' => array( 'WAWP_Custom_Pages_Settings', 'get_instance' ),
377 'country-code' => array( 'WAWP_CountryCode', 'get_instance' ),
378 );
379
380 // Key Mapping for the localized object.
381 $key_map = array(
382 'senders' => 'sendersData',
383 'whatsapp-web-sender' => 'instancesData',
384 'registration-form' => 'registrationForm',
385 'passwordless-login' => 'passwordlessLoginSettings',
386 'system_info' => 'systemInfo',
387 'notifications' => 'notificationsData',
388 'activity-hub' => 'logsData',
389 'google_recaptcha' => 'recaptchaData',
390 'chat_widget' => 'chatWidgetData',
391 'smtp-sender' => 'smtpSenderData',
392 'block-manager' => 'blockManagerData',
393 'email-templates' => 'emailTemplatesData',
394 'authentication-pages' => 'authPages',
395 'country-code' => 'phoneFieldData',
396 );
397
398 $sections_to_load = $is_full ? array_keys( $handlers ) : array( $mapped_section );
399
400 foreach ( $sections_to_load as $current_section ) {
401 if ( ! isset( $handlers[ $current_section ] ) ) {
402 continue;
403 }
404
405 $class = $handlers[ $current_section ][0];
406 $instance = null;
407
408 // Point 1: Get existing singleton instance instead of 'new Class()'.
409 switch ( $class ) {
410 case 'WAWP_Senders':
411 $instance = WAWP()->senders;
412 break;
413 case 'WAWP_System_Info':
414 $instance = WAWP()->system_info;
415 break;
416 case 'WAWP_Flow_Builder':
417 $instance = WAWP()->flow_builder;
418 break;
419 case 'WAWP_Unified_Log':
420 $instance = WAWP()->unified_log;
421 break;
422 case 'WAWP_Chat_Widget':
423 $instance = WAWP()->chat_widget;
424 break;
425 case 'WAWP_Block_Manager':
426 $instance = WAWP()->block_manager;
427 break;
428 default:
429 if ( method_exists( $class, 'get_instance' ) ) {
430 $instance = call_user_func( array( $class, 'get_instance' ) );
431 }
432 break;
433 }
434
435 if ( $instance ) {
436 $method = 'get_react_settings_data';
437 // Handle special method names for legacy support.
438 if ( 'whatsapp-web-sender' === $current_section ) {
439 $method = 'get_whatsapp_react_settings_data';
440 } elseif ( 'system_info' === $current_section ) {
441 $method = 'get_react_system_info_data';
442 } elseif ( 'block-manager' === $current_section ) {
443 $method = 'get_data';
444 }
445
446 if ( method_exists( $instance, $method ) ) {
447 $key = $key_map[ $current_section ] ?? $current_section;
448 $section_data = $instance->$method();
449 $data[ $key ] = $section_data;
450
451 // Merge translations if provided.
452 // During a full load, section-specific pageTitle/pageSubtitle must NOT
453 // overwrite the dashboard's own pageTitle/pageSubtitle that was already set.
454 if ( isset( $section_data['i18n'] ) ) {
455 $preserved_page_title = $data['i18n']['pageTitle'] ?? '';
456 $preserved_page_subtitle = $data['i18n']['pageSubtitle'] ?? '';
457 $data['i18n'] = array_merge( $data['i18n'], $section_data['i18n'] );
458 // Restore the dashboard-level page title/subtitle so no section overwrites them.
459 if ( '' !== $preserved_page_title ) {
460 $data['i18n']['pageTitle'] = $preserved_page_title;
461 }
462 if ( '' !== $preserved_page_subtitle ) {
463 $data['i18n']['pageSubtitle'] = $preserved_page_subtitle;
464 }
465 }
466
467 // Merge status messages if provided.
468 if ( isset( $section_data['status_messages'] ) ) {
469 $data['status_messages'] = array_merge( $data['status_messages'] ?? array(), $section_data['status_messages'] );
470 }
471 }
472 }
473
474 // Special additions for specific sections.
475 if ( 'whatsapp-web-sender' === $current_section ) {
476 // OTP Settings & Status.
477 $otp_settings = WAWP()->from_db( WAWP()->get_wawp_setting( WAWP_Settings_Registry::OTP_SETTINGS, array() ) );
478 $otp_settings['enabled'] = (int) WAWP()->get_wawp_setting( WAWP_Settings_Registry::OTP_ENABLED, 0 );
479 $otp_settings['login_enabled'] = (int) WAWP()->get_wawp_setting( WAWP_Settings_Registry::PASSWORDLESS_LOGIN_ENABLED, 0 );
480 $otp_settings['signup_enabled'] = (int) WAWP()->get_wawp_setting( WAWP_Settings_Registry::SIGNUP_ENABLED, 0 );
481 $data['otp'] = $otp_settings;
482
483 // Signup Settings.
484 $signup_settings = WAWP()->from_db( WAWP()->get_wawp_setting( WAWP_Settings_Registry::REGISTRATION_FORM_SETTINGS, array() ) );
485 $signup_settings['enabled'] = (int) WAWP()->get_wawp_setting( WAWP_Settings_Registry::SIGNUP_ENABLED, 0 );
486 $data['signup'] = $signup_settings;
487
488 // Notifications Settings.
489 $notif_settings = WAWP()->from_db( WAWP()->get_wawp_setting( WAWP_Settings_Registry::NOTIFICATIONS_RULES, array() ) );
490 $notif_settings['enabled'] = (int) WAWP()->get_wawp_setting( WAWP_Settings_Registry::NOTIFICATIONS_ENABLED, 0 );
491 $notif_settings['selected_instance_ids'] = WAWP()->get_wawp_setting( WAWP_Settings_Registry::NOTIF_SELECTED_INSTANCE_IDS, '' );
492 $notif_settings['admin_selected_instance_ids'] = WAWP()->get_wawp_setting( WAWP_Settings_Registry::NOTIF_ADMIN_SELECTED_INSTANCE_IDS, '' );
493 $data['notif'] = $notif_settings;
494
495 $data['general_instance'] = WAWP()->get_wawp_setting( WAWP_Settings_Registry::GENERAL_INSTANCE, '' );
496 }
497
498 // Activity Hub: inject Meta templates, placeholders, instances & sender status for the Send Message drawer.
499 if ( 'activity-hub' === $current_section ) {
500 $data['metaTemplates'] = array();
501 $data['placeholders'] = ( class_exists( 'WAWP_Flow_Builder' ) && method_exists( 'WAWP_Flow_Builder', 'get_available_placeholders' ) )
502 ? WAWP_Flow_Builder::get_instance()->get_available_placeholders()
503 : array();
504 $data['placeholderGroups'] = ( class_exists( 'WAWP_Flow_Builder' ) && method_exists( 'WAWP_Flow_Builder', 'get_grouped_placeholders' ) )
505 ? WAWP_Flow_Builder::get_instance()->get_grouped_placeholders()
506 : array();
507
508 // Online Wawp instances for instance picker.
509 $raw_instances = class_exists( 'WAWP_Instance_Manager' ) ? WAWP()->instance_manager->get_online_instances_cached() : array();
510 $online_instances_clean = array();
511 if ( ! empty( $raw_instances ) ) {
512 foreach ( $raw_instances as $inst ) {
513 $online_instances_clean[] = array(
514 'instance_id' => is_object( $inst ) ? $inst->instance_id : ( $inst['instance_id'] ?? '' ),
515 'name' => is_object( $inst ) ? $inst->name : ( $inst['name'] ?? '' ),
516 );
517 }
518 }
519 $data['onlineInstances'] = $online_instances_clean;
520
521 // Enabled senders for showing active status.
522 $data['enabledSenders'] = array(
523 'wa' => $connector->is_whatsapp_sender_enabled(),
524 'email' => $connector->is_email_sender_enabled(),
525 );
526 }
527 }
528
529 $data = self::decode_translations_recursive( $data );
530 return $data;
531 } catch ( \Exception $e ) {
532 return array(
533 'error' => true,
534 'message' => $e->getMessage(),
535 'global' => array( 'section' => $section ),
536 );
537 }
538 }
539
540 /**
541 * Decode HTML entities recursively in translation arrays.
542 *
543 * @param array $data_array Input array.
544 * @return array Decoded array.
545 */
546 public static function decode_translations_recursive( $data_array ) {
547 if ( ! is_array( $data_array ) ) {
548 return $data_array;
549 }
550 foreach ( $data_array as $key => $value ) {
551 if ( 'i18n' === $key || 'status_messages' === $key ) {
552 $data_array[ $key ] = self::decode_all_strings_recursive( $value );
553 } elseif ( is_array( $value ) ) {
554 $data_array[ $key ] = self::decode_translations_recursive( $value );
555 }
556 }
557 return $data_array;
558 }
559
560 /**
561 * Decode HTML entities recursively for all string values in an array.
562 *
563 * @param mixed $value Input value.
564 * @return mixed Decoded value.
565 */
566 private static function decode_all_strings_recursive( $value ) {
567 if ( is_array( $value ) ) {
568 foreach ( $value as $k => $v ) {
569 $value[ $k ] = self::decode_all_strings_recursive( $v );
570 }
571 } elseif ( is_string( $value ) ) {
572 $value = html_entity_decode( $value, ENT_QUOTES, 'UTF-8' );
573 }
574 return $value;
575 }
576
577
578
579 /**
580 * Find a Vite-hashed asset file in the dist/assets directory.
581 *
582 * Vite generates filenames like index-AbCdEfGh.js. This method
583 * scans the directory for a file matching the pattern [name]-[hash].[ext]
584 * so PHP doesn't need to hardcode the hash.
585 *
586 * Falls back to [name].[ext] (legacy builds without hash) if not found.
587 *
588 * @param string $dir Absolute path to the dist/assets directory (with trailing slash).
589 * @param string $name Base name without hash (e.g. 'index').
590 * @param string $ext File extension without dot (e.g. 'js').
591 * @return string The matched filename (basename only), or fallback.
592 */
593 private static function find_hashed_asset( $dir, $name, $ext ) {
594 if ( is_dir( $dir ) ) {
595 $pattern = $dir . $name . '-*.' . $ext;
596 $files = glob( $pattern );
597 if ( ! empty( $files ) ) {
598 // Sort by modification time DESC so the newest build wins.
599 usort(
600 $files,
601 function ( $a, $b ) {
602 return filemtime( $b ) - filemtime( $a );
603 }
604 );
605 return basename( $files[0] );
606 }
607 }
608 // Fallback: legacy build without hash in filename.
609 return $name . '.' . $ext;
610 }
611
612
613
614
615
616 /**
617 * Handle REST API requests for dashboard data fetching.
618 *
619 * @param \WP_REST_Request $request REST request object.
620 * @return \WP_REST_Response REST response.
621 */
622 public static function handle_rest_fetch_data( $request ) {
623 $section = $request->get_param( 'section' );
624 $data = self::prepare_dashboard_data( $section, false );
625 $data['nonce'] = wp_create_nonce( 'wawp_nonce' );
626 return new WP_REST_Response( $data, 200 );
627 }
628
629 /**
630 * Get the list of languages available/installed on this WordPress site.
631 * Returns English (default) plus every language whose .mo file is present.
632 *
633 * @return array<string, array{locale: string, name: string, nativeName: string, isRtl: bool}>
634 */
635 public static function get_site_available_languages() {
636 // WordPress RTL locales (partial list covering the most common ones).
637 $rtl_locales = array( 'ar', 'ar_EG', 'ar_MA', 'ar_SA', 'fa_IR', 'he_IL', 'ug', 'ps', 'sd_PK', 'ku_IQ', 'ckb' );
638
639 $languages = array();
640
641 // Always include English as the default fallback.
642 $languages['en_US'] = array(
643 'locale' => 'en_US',
644 'name' => 'English (US)',
645 'nativeName' => 'English',
646 'isRtl' => false,
647 );
648
649 // get_available_languages() returns locale slugs that have a .mo file installed.
650 if ( function_exists( 'get_available_languages' ) ) {
651 $installed = get_available_languages( WP_LANG_DIR );
652
653 // Load the WordPress translation installation API if not already loaded.
654 if ( ! function_exists( 'wp_get_available_translations' ) && file_exists( ABSPATH . 'wp-admin/includes/translation-install.php' ) ) {
655 require_once ABSPATH . 'wp-admin/includes/translation-install.php';
656 }
657
658 // Try to enrich with native names from the translations API cache.
659 $translations = array();
660 if ( function_exists( 'wp_get_available_translations' ) ) {
661 $translations = wp_get_available_translations();
662 }
663
664 // Standard common locales fallback.
665 $common_locales = array(
666 'ar' => array(
667 'name' => 'Arabic',
668 'nativeName' => 'العربية',
669 ),
670 'ar_EG' => array(
671 'name' => 'Arabic (Egypt)',
672 'nativeName' => 'العربية (�
673 صر)',
674 ),
675 'ar_SA' => array(
676 'name' => 'Arabic (Saudi Arabia)',
677 'nativeName' => 'العربية (السعودية)',
678 ),
679 'fr_FR' => array(
680 'name' => 'French (France)',
681 'nativeName' => 'Français',
682 ),
683 'fr' => array(
684 'name' => 'French',
685 'nativeName' => 'Français',
686 ),
687 'es_ES' => array(
688 'name' => 'Spanish (Spain)',
689 'nativeName' => 'Español',
690 ),
691 'es' => array(
692 'name' => 'Spanish',
693 'nativeName' => 'Español',
694 ),
695 'de_DE' => array(
696 'name' => 'German',
697 'nativeName' => 'Deutsch',
698 ),
699 'de' => array(
700 'name' => 'German',
701 'nativeName' => 'Deutsch',
702 ),
703 'it_IT' => array(
704 'name' => 'Italian',
705 'nativeName' => 'Italiano',
706 ),
707 'it' => array(
708 'name' => 'Italian',
709 'nativeName' => 'Italiano',
710 ),
711 'pt_BR' => array(
712 'name' => 'Portuguese (Brazil)',
713 'nativeName' => 'Português do Brasil',
714 ),
715 'pt' => array(
716 'name' => 'Portuguese',
717 'nativeName' => 'Português',
718 ),
719 'hi' => array(
720 'name' => 'Hindi',
721 'nativeName' => 'हिन्दी',
722 ),
723 'ja' => array(
724 'name' => 'Japanese',
725 'nativeName' => '日本語',
726 ),
727 'ko_KR' => array(
728 'name' => 'Korean',
729 'nativeName' => '한국어',
730 ),
731 'tr_TR' => array(
732 'name' => 'Turkish',
733 'nativeName' => 'Türkçe',
734 ),
735 'tr' => array(
736 'name' => 'Turkish',
737 'nativeName' => 'Türkçe',
738 ),
739 'ru_RU' => array(
740 'name' => 'Russian',
741 'nativeName' => 'Русский',
742 ),
743 'ru' => array(
744 'name' => 'Russian',
745 'nativeName' => 'Русский',
746 ),
747 'zh_CN' => array(
748 'name' => 'Chinese (China)',
749 'nativeName' => '简体中文',
750 ),
751 'zh_TW' => array(
752 'name' => 'Chinese (Taiwan)',
753 'nativeName' => '繁體中文',
754 ),
755 'nl_NL' => array(
756 'name' => 'Dutch',
757 'nativeName' => 'Nederlands',
758 ),
759 'nl' => array(
760 'name' => 'Dutch',
761 'nativeName' => 'Nederlands',
762 ),
763 );
764
765 foreach ( $installed as $locale ) {
766 if ( 'en_US' === $locale ) {
767 continue; // Already added.
768 }
769
770 $lang_prefix = substr( $locale, 0, 2 );
771 $is_rtl = in_array( $locale, $rtl_locales, true ) || in_array( $lang_prefix, array( 'ar', 'fa', 'he', 'ur' ), true );
772
773 if ( isset( $translations[ $locale ] ) ) {
774 $t = $translations[ $locale ];
775 $native_name = ! empty( $t['native_name'] ) ? $t['native_name'] : $locale;
776 $name = ! empty( $t['english_name'] ) ? $t['english_name'] : $locale;
777 } elseif ( isset( $common_locales[ $locale ] ) ) {
778 $native_name = $common_locales[ $locale ]['nativeName'];
779 $name = $common_locales[ $locale ]['name'];
780 } elseif ( isset( $common_locales[ $lang_prefix ] ) ) {
781 $native_name = $common_locales[ $lang_prefix ]['nativeName'];
782 $name = $common_locales[ $lang_prefix ]['name'];
783 } else {
784 // Fallback: pretty-print the locale slug.
785 $name = str_replace( '_', ' ', $locale );
786 $native_name = $name;
787 }
788
789 $languages[ $locale ] = array(
790 'locale' => $locale,
791 'name' => $name,
792 'nativeName' => $native_name,
793 'isRtl' => $is_rtl,
794 );
795 }
796 }
797
798 return array_values( $languages );
799 }
800
801 /**
802 * AJAX handler: switch the current WordPress admin UI language for this user.
803 */
804 public static function handle_switch_language() {
805 check_ajax_referer( 'wawp_switch_language', 'nonce' );
806
807 if ( ! current_user_can( 'manage_options' ) && ! current_user_can( WAWP_CAPABILITY ) ) {
808 wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 );
809 return;
810 }
811
812 $locale = isset( $_POST['locale'] ) ? sanitize_text_field( wp_unslash( $_POST['locale'] ) ) : '';
813
814 if ( empty( $locale ) ) {
815 wp_send_json_error( array( 'message' => 'Invalid locale.' ), 400 );
816 return;
817 }
818
819 // Validate: make sure the locale is actually installed.
820 $installed = function_exists( 'get_available_languages' ) ? get_available_languages( WP_LANG_DIR ) : array();
821 if ( 'en_US' !== $locale && 'en' !== $locale && ! in_array( $locale, $installed, true ) ) {
822 wp_send_json_error( array( 'message' => 'Locale not available.' ), 400 );
823 return;
824 }
825
826 $user_id = get_current_user_id();
827
828 if ( 'en' === $locale ) {
829 $locale = 'en_US';
830 }
831
832 // Store the chosen locale in the user meta.
833 update_user_meta( $user_id, 'locale', $locale );
834
835 // Clear WordPress internal user locale cache.
836 wp_cache_delete( $user_id, 'users' );
837
838 // Bust the WAWP dashboard transient for this user so the next page load
839 // fetches fresh data (with the correct i18n strings for the new locale).
840 $cache_key = 'wawp_dashboard_data_full_' . $user_id;
841 delete_transient( $cache_key );
842 wp_cache_delete( $cache_key, 'wawp_database' );
843
844 // Also clear any tracked transient keys for this user.
845 $keys = get_option( 'wawp_dashboard_transient_keys', array() );
846 if ( is_array( $keys ) ) {
847 foreach ( $keys as $key ) {
848 if ( false !== strpos( $key, (string) $user_id ) ) {
849 delete_transient( $key );
850 wp_cache_delete( $key, 'wawp_database' );
851 }
852 }
853 }
854
855 wp_send_json_success( array( 'locale' => $locale ) );
856 }
857
858 /**
859 * AJAX handler: flush WordPress and React assets cache.
860 */
861 public static function handle_flush_cache() {
862 check_ajax_referer( 'wawp_flush_cache', 'nonce' );
863
864 if ( ! current_user_can( 'manage_options' ) && ! current_user_can( WAWP_CAPABILITY ) ) {
865 wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 );
866 return;
867 }
868
869 // 1. Force bust React cache (updates asset version query args).
870 update_option( 'wawp_force_cache_bust_version', time(), false );
871
872 // 2. Clear WordPress Transients & Object Cache.
873 wp_cache_flush();
874 if ( function_exists( 'delete_transient' ) ) {
875 // Delete specific transients.
876 delete_transient( 'wawp_last_db_check' );
877 delete_transient( 'wawp_online_instances' );
878 delete_transient( 'wawp_dashboard_transients' );
879
880 $user_id = get_current_user_id();
881 delete_transient( 'wawp_dashboard_data_full_' . $user_id );
882
883 // Also delete any other cached dashboard transient keys.
884 $keys = get_option( 'wawp_dashboard_transient_keys', array() );
885 if ( is_array( $keys ) ) {
886 foreach ( $keys as $key ) {
887 delete_transient( $key );
888 wp_cache_delete( $key, 'wawp_database' );
889 }
890 update_option( 'wawp_dashboard_transient_keys', array(), false );
891 }
892 }
893
894 // 3. OPcache reset if available.
895 if ( function_exists( 'opcache_reset' ) ) {
896 opcache_reset();
897 }
898
899 // Flush rewrites just in case.
900 flush_rewrite_rules( false );
901
902 wp_send_json_success( array( 'message' => 'Cache cleared successfully.' ) );
903 }
904 }
905
906 add_action( 'admin_enqueue_scripts', array( 'WAWP_Enqueue_Scripts', 'enqueue_admin_styles_scripts' ), 999 );
907 add_action( 'wp_enqueue_scripts', array( 'WAWP_Enqueue_Scripts', 'enqueue_frontend_styles_scripts' ) );
908 add_action( 'wp_ajax_wawp_switch_language', array( 'WAWP_Enqueue_Scripts', 'handle_switch_language' ) );
909 add_action( 'wp_ajax_wawp_flush_cache', array( 'WAWP_Enqueue_Scripts', 'handle_flush_cache' ) );
910