get_settings());
// Additional processing after save.
$this->process_settings_update();
// Check if we should create a coupon from settings.
$this->maybe_create_coupon_from_settings();
}
/**
* Get settings fields
*
* @return array
*/
public function get_settings() {
// Switch to plugin's language setting and reload text domain for translations.
// This must be done BEFORE creating the settings array so translations are applied.
$this->switch_to_plugin_language();
// Check if we're on the export tab.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Navigation only, no data processing
$current_tab = isset($_GET['section']) ? sanitize_text_field(wp_unslash($_GET['section'])) : '';
if ( 'export' === $current_tab ) {
return $this->get_export_settings();
}
// Get current settings from our unified settings system.
$current_settings = ActiveTrail_Settings::get_all();
$settings = array(
array(
'name' => esc_html__('ActiveTrail Integration Settings', 'activetrail-for-woocommerce'),
'type' => 'title',
'desc' => esc_html__('Configure your ActiveTrail integration settings below.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_settings'
),
// Close the table opened by the global title above so subsequent sections
// are at the top level and our wrapper
injections produce valid HTML.
array(
'type' => 'sectionend',
'id' => 'activetrail_settings',
),
// Language Settings Section.
array(
'name' => esc_html__('Language Settings', 'activetrail-for-woocommerce'),
'type' => 'title',
'id' => 'activetrail_language_settings'
),
array(
'name' => esc_html__('Plugin Language', 'activetrail-for-woocommerce'),
'type' => 'select',
'desc' => esc_html__('Select the language for the ActiveTrail plugin interface. After selecting your preferred language, click the blue "Save Changes" button at the bottom of the page. Then refresh your browser for the changes to take effect properly.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_language',
'options' => ActiveTrail_Settings::get_available_languages(),
'default' => $current_settings['language'],
'css' => 'min-width:200px;',
'class' => 'activetrail-language-select'
),
array(
'type' => 'sectionend',
'id' => 'activetrail_language_settings'
),
// API Configuration Section.
array(
'name' => esc_html__('API Configuration', 'activetrail-for-woocommerce'),
'type' => 'title',
'id' => 'activetrail_api_settings'
),
array(
'name' => esc_html__('API Key', 'activetrail-for-woocommerce'),
'type' => 'activetrail_api_key',
'desc' => esc_html__('Enter your ActiveTrail API key. You can find this in your ActiveTrail account settings.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_api_key',
'css' => 'min-width:300px;',
'default' => $current_settings['api_key'],
'custom_attributes' => array(
'autocomplete' => 'off',
'required' => 'required'
),
'required' => true
),
array(
'type' => 'sectionend',
'id' => 'activetrail_api_settings'
),
// Group Section.
array(
'name' => esc_html__('Group', 'activetrail-for-woocommerce'),
'type' => 'title',
'id' => 'activetrail_group_settings'
),
array(
'name' => esc_html__('Group ID', 'activetrail-for-woocommerce'),
'type' => 'activetrail_group_select',
'desc' => esc_html__('Select the ActiveTrail group where customers will be added.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_group_id',
'options' => $this->get_groups_options(),
'default' => $current_settings['group_id'],
'css' => 'min-width:300px;',
'required' => true
),
array(
'type' => 'sectionend',
'id' => 'activetrail_group_settings'
),
// Order Sync Section.
array(
'name' => esc_html__('Order Synchronization', 'activetrail-for-woocommerce'),
'type' => 'title',
'id' => 'activetrail_order_settings'
),
array(
'name' => esc_html__('Synchronized Statuses', 'activetrail-for-woocommerce'),
'type' => 'activetrail_status_table',
'desc' => esc_html__('Select which order statuses should trigger events to ActiveTrail. If no statuses are selected, all statuses will be synchronized (backward compatible).', 'activetrail-for-woocommerce'),
'id' => 'activetrail_status_mapping',
'default' => ActiveTrail_Settings::get_status_mapping(),
),
array(
'type' => 'sectionend',
'id' => 'activetrail_order_settings'
),
// Checkout Section.
array(
'name' => esc_html__('Checkout Settings', 'activetrail-for-woocommerce'),
'type' => 'title',
'id' => 'activetrail_checkout_settings'
),
array(
'name' => esc_html__('Enable Consent Checkbox', 'activetrail-for-woocommerce'),
'type' => 'checkbox',
'desc' => esc_html__('Add a marketing consent checkbox to the checkout form', 'activetrail-for-woocommerce'),
'id' => 'activetrail_enable_checkout_consent',
'default' => $current_settings['enable_checkout_consent'] ? 'yes' : 'no'
),
array(
'name' => esc_html__('Enable Registration Consent', 'activetrail-for-woocommerce'),
'type' => 'checkbox',
'desc' => esc_html__('Add a marketing consent checkbox to the user registration form', 'activetrail-for-woocommerce'),
'id' => 'activetrail_enable_registration_consent',
'default' => $current_settings['enable_registration_consent'] ? 'yes' : 'no'
),
array(
'name' => esc_html__('Consent Text', 'activetrail-for-woocommerce'),
'type' => 'textarea',
'desc' => esc_html__('Text displayed next to the consent checkbox.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_consent_text',
'default' => $current_settings['consent_text'],
'css' => 'min-width:300px; height:60px;'
),
// If enabled, the marketing consent checkbox on frontend is checked by default.
array(
'name' => esc_html__('Marketing consent checked by default', 'activetrail-for-woocommerce'),
'type' => 'checkbox',
'desc' => esc_html__('When enabled, the marketing consent checkbox is checked by default on checkout and registration.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_consent_checked_by_default',
'default' => ! empty( $current_settings['consent_checked_by_default'] ) ? 'yes' : 'no'
),
array(
'type' => 'sectionend',
'id' => 'activetrail_checkout_settings'
),
// Cart Tracking Section.
array(
'name' => esc_html__('Cart Tracking', 'activetrail-for-woocommerce'),
'type' => 'title',
'id' => 'activetrail_cart_settings'
),
array(
'name' => esc_html__('Enable Cart Tracking', 'activetrail-for-woocommerce'),
'type' => 'checkbox',
'desc' => esc_html__('Track cart changes and send data to ActiveTrail', 'activetrail-for-woocommerce'),
'id' => 'activetrail_enable_cart_tracking',
'default' => $current_settings['enable_cart_tracking'] ? 'yes' : 'no'
),
array(
'type' => 'sectionend',
'id' => 'activetrail_cart_settings'
),
// Product Feed Section.
array(
'name' => esc_html__('Product Feed', 'activetrail-for-woocommerce'),
'type' => 'title',
'id' => 'activetrail_feed_settings'
),
array(
'name' => esc_html__('Feed Title', 'activetrail-for-woocommerce'),
'type' => 'text',
'desc' => esc_html__( 'Product feed title.', 'activetrail-for-woocommerce' ),
'id' => 'activetrail_feed_title',
'default' => $current_settings['feed_title'],
'css' => 'min-width:300px;'
),
array(
'name' => esc_html__('Feed Description', 'activetrail-for-woocommerce'),
'type' => 'textarea',
'desc' => esc_html__( 'Product feed description.', 'activetrail-for-woocommerce' ),
'id' => 'activetrail_feed_description',
'default' => $current_settings['feed_description'],
'css' => 'min-width:300px; height:60px;'
),
array(
'type' => 'sectionend',
'id' => 'activetrail_feed_settings'
),
// Historical Sync Section.
array(
'name' => esc_html__('Historical Contacts and Orders Sync', 'activetrail-for-woocommerce'),
'type' => 'title',
'desc' => esc_html__('Sync existing customers from your WooCommerce store to ActiveTrail. This is useful when connecting to a new ActiveTrail account or when you need to sync historical data.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_historical_sync_settings'
),
array(
'name' => '',
'type' => 'activetrail_historical_sync',
'id' => 'activetrail_historical_sync',
),
array(
'type' => 'sectionend',
'id' => 'activetrail_historical_sync_settings'
),
// Coupon Management Section - HIDDEN FROM UI.
// array(.
// 'name' => esc_html__('Coupon Management', 'activetrail-for-woocommerce'),.
// 'type' => 'title',.
// 'desc' => esc_html__('Create and manage WooCommerce coupons directly from the ActiveTrail plugin.', 'activetrail-for-woocommerce'),.
// 'id' => 'activetrail_coupon_settings'.
// ),.
// array(.
// 'name' => '',.
// 'type' => 'activetrail_coupon_manager',.
// 'id' => 'activetrail_coupon_manager',.
// ),.
// array(.
// 'type' => 'sectionend',.
// 'id' => 'activetrail_coupon_settings'.
// ),.
);
return apply_filters('activetrail_settings_fields', $settings);
}
/**
* Get export settings fields
*
* @return array
*/
public function get_export_settings() {
// Switch to plugin's language setting and reload text domain for translations.
// This must be done BEFORE creating the settings array so translations are applied.
$this->switch_to_plugin_language();
// Generate secure keys if they don't exist.
$google_key = get_option('activetrail_google_feed_key', '');
$facebook_key = get_option('activetrail_facebook_feed_key', '');
if (empty($google_key)) {
$google_key = $this->generate_secure_feed_key();
update_option('activetrail_google_feed_key', $google_key);
}
if (empty($facebook_key)) {
$facebook_key = $this->generate_secure_feed_key();
update_option('activetrail_facebook_feed_key', $facebook_key);
}
// Get product count.
$product_count = wp_count_posts('product');
$total_products = isset($product_count->publish) ? (int) $product_count->publish : 0;
// Get last feed update time for display.
$last_update = get_option('activetrail_feed_last_update', '');
if (!empty($last_update)) {
$timestamp = strtotime($last_update);
$last_update_display = $timestamp
? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $timestamp)
: $last_update;
} else {
$last_update_display = esc_html__('Feed has not been generated yet.', 'activetrail-for-woocommerce');
}
$settings = array(
array(
'name' => esc_html__('Product Catalog Export', 'activetrail-for-woocommerce'),
'type' => 'title',
'desc' => esc_html__('Export your product catalog for Google Merchant Center and Facebook Catalog. The feeds update automatically when products change.', 'activetrail-for-woocommerce'),
'id' => 'activetrail_export_settings'
),
array(
'name' => '',
'type' => 'activetrail_export_feeds',
'id' => 'activetrail_export_feeds',
'google_url' => home_url('?activetrail_export=google&key=' . $google_key),
'facebook_url' => home_url('?activetrail_export=facebook&key=' . $facebook_key),
'product_count' => $total_products,
'last_update' => $last_update_display,
'google_key' => $google_key,
'facebook_key' => $facebook_key
),
array(
'type' => 'sectionend',
'id' => 'activetrail_export_settings'
),
);
return $settings;
}
/**
* Generate secure feed key
*
* @return string
*/
private function generate_secure_feed_key() {
return wp_generate_password(32, false, false);
}
/**
* Get groups options for select field
*
* @return array
*/
private function get_groups_options() {
// Get current settings to check if API key is available.
$current_settings = ActiveTrail_Settings::get_all();
// If no API key, return default option only.
if (empty($current_settings['api_key'])) {
return array('' => esc_html__('Enter API key first to load groups', 'activetrail-for-woocommerce'));
}
// Try to get groups from settings.
$groups_options = ActiveTrail_Settings::get_groups_for_select();
// If we only have the default option, try to force refresh the groups.
if (count($groups_options) <= 1) {
// Force refresh groups from API.
$groups_result = ActiveTrail_Settings::get_activetrail_groups(true);
if ($groups_result['success'] && !empty($groups_result['data'])) {
// Manually build the options array.
$groups_options = array('' => esc_html__('Select a group...', 'activetrail-for-woocommerce'));
foreach ($groups_result['data'] as $group) {
$id = isset($group['Id']) ? $group['Id'] : (isset($group['id']) ? $group['id'] : '');
$name = isset($group['Name']) ? $group['Name'] : (isset($group['name']) ? $group['name'] : '');
if (!empty($id) && !empty($name)) {
$groups_options[$id] = $name;
}
}
} else {
// API call failed, show error message.
$error_message = isset($groups_result['message']) ? $groups_result['message'] : esc_html__('Failed to load groups', 'activetrail-for-woocommerce');
return array('' => $error_message);
}
}
// Always append "Load more" as the last option so it is visible even before JS runs.
$groups_options['__load_more__'] = esc_html__( '── Load more... ──', 'activetrail-for-woocommerce' );
return $groups_options;
}
/**
* Get order statuses options
*
* @return array
*/
private function get_order_statuses_options() {
$statuses = wc_get_order_statuses();
$options = array();
foreach ($statuses as $key => $label) {
$clean_key = str_replace('wc-', '', $key);
$options[$clean_key] = $label;
}
return $options;
}
/**
* Process settings update
*/
private function process_settings_update() {
// Verify nonce for security.
if (!isset($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'woocommerce-settings')) {
return;
}
// Get current settings first to preserve any existing data.
$current_settings = ActiveTrail_Settings::get_all();
// Get selected statuses from checkboxes.
$detailed_statuses = ActiveTrail_Settings::get_order_statuses_detailed();
$status_mapping = array();
$enabled_statuses = array();
// Process status checkboxes from POST data.
foreach ($detailed_statuses as $status_key => $status_info) {
// Get description from POST if available, otherwise use default.
$description = $status_info['description'];
if (isset($_POST['activetrail_status_mapping'][$status_key]['description'])) {
$description = sanitize_text_field(wp_unslash($_POST['activetrail_status_mapping'][$status_key]['description']));
}
// Check if checkbox is checked.
$is_enabled = isset($_POST['activetrail_status_mapping'][$status_key]['sync']) &&
sanitize_text_field(wp_unslash($_POST['activetrail_status_mapping'][$status_key]['sync'])) === '1';
$status_mapping[$status_key] = array(
'sync' => $is_enabled,
'description' => $description
);
if ($is_enabled) {
$enabled_statuses[] = $status_key;
}
}
// Save the status mapping - all statuses always enabled.
ActiveTrail_Settings::update_status_mapping($status_mapping);
// Validate required fields.
$api_key = get_option('activetrail_api_key', '');
$group_id = get_option('activetrail_group_id', '');
if (empty($api_key)) {
WC_Admin_Settings::add_error(esc_html__('API Key is required. Please enter your ActiveTrail API key.', 'activetrail-for-woocommerce'));
}
if (empty($group_id)) {
WC_Admin_Settings::add_error(esc_html__('Group ID is required. Please select a group.', 'activetrail-for-woocommerce'));
}
// Update plugin settings using our settings class.
$new_settings = array(
'api_key' => $api_key,
'group_id' => $group_id,
'order_statuses' => $enabled_statuses,
'enable_checkout_consent' => get_option('activetrail_enable_checkout_consent', 'yes') === 'yes',
'consent_text' => get_option('activetrail_consent_text', ''),
'consent_checked_by_default' => get_option('activetrail_consent_checked_by_default', 'no') === 'yes',
'consent_required' => false,
'enable_registration_consent' => get_option('activetrail_enable_registration_consent', 'no') === 'yes',
'sync_without_consent' => get_option('activetrail_sync_without_consent', 'no') === 'yes',
'enable_cart_tracking' => get_option('activetrail_enable_cart_tracking', 'yes') === 'yes',
'enable_product_feed' => true, // Always enabled
'feed_title' => get_option('activetrail_feed_title', ''),
'feed_description' => get_option('activetrail_feed_description', ''),
'debug_mode' => get_option('activetrail_debug_mode', 'no') === 'yes',
'language' => get_option('activetrail_language', 'en_US'),
);
// Merge with current settings to preserve cache and other data.
$merged_settings = array_merge($current_settings, $new_settings);
// Update unified settings.
$update_result = ActiveTrail_Settings::update_all($merged_settings);
// Debug logging if enabled.
if ($merged_settings['debug_mode'] && function_exists('activetrail_logger')) {
$logger = activetrail_logger();
$logger->debug('Settings update - Group ID: ' . ($merged_settings['group_id'] ? $merged_settings['group_id'] : 'empty'), 'SETTINGS');
$logger->debug('Settings update - Enabled statuses: ' . implode(', ', $enabled_statuses), 'SETTINGS');
$logger->debug('Settings update result: ' . ($update_result ? 'success' : 'failed'), 'SETTINGS');
}
// Clear groups cache if group_id changed.
if ($current_settings['group_id'] !== $merged_settings['group_id']) {
ActiveTrail_Settings::clear_groups_cache();
}
// Flush rewrite rules if feed settings changed.
if ($merged_settings['enable_product_feed']) {
flush_rewrite_rules();
}
// Clean up temporary WooCommerce options.
delete_option('activetrail_status_mapping');
}
/**
* Enqueue admin scripts and styles
*
* @param string $hook Current admin page hook.
*/
public function enqueue_admin_scripts($hook) {
// Debug logging.
if (function_exists('activetrail_logger')) {
$logger = activetrail_logger();
$logger->debug('enqueue_admin_scripts called with hook: ' . $hook, 'GENERAL');
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Debug logging only
$get_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'not set';
$logger->debug('GET tab: ' . $get_tab, 'GENERAL');
}
// Only load on WooCommerce settings page.
if ( 'woocommerce_page_wc-settings' !== $hook ) {
if (function_exists('activetrail_logger')) {
activetrail_logger()->debug('Not WooCommerce settings page, returning', 'GENERAL');
}
return;
}
// Only load on our tab.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Debug logging only
$get_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : '';
if ( 'activetrail' !== $get_tab ) {
if (function_exists('activetrail_logger')) {
activetrail_logger()->debug('Not ActiveTrail tab, returning', 'GENERAL');
}
return;
}
if (function_exists('activetrail_logger')) {
activetrail_logger()->debug('Loading admin scripts...', 'GENERAL');
}
wp_enqueue_script(
'activetrail-admin',
ACTIVETRAIL_WC_PLUGIN_URL . 'assets/js/admin-script.js',
array('jquery', 'jquery-ui-tooltip'),
ACTIVETRAIL_WC_VERSION,
true
);
wp_enqueue_style(
'activetrail-admin',
ACTIVETRAIL_WC_PLUGIN_URL . 'assets/css/admin-style.css',
array(),
ACTIVETRAIL_WC_VERSION
);
// Get current section to conditionally add inline styles/scripts.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Navigation only, no data processing
$current_section = isset($_GET['section']) ? sanitize_text_field(wp_unslash($_GET['section'])) : '';
// Add inline styles for sub-navigation (always needed).
$sub_nav_css = $this->get_sub_nav_css();
wp_add_inline_style('activetrail-admin', $sub_nav_css);
// Add inline styles and scripts based on section.
if ($current_section === 'export') {
$export_css = $this->get_export_css();
$export_js = $this->get_export_js();
wp_add_inline_style('activetrail-admin', $export_css);
wp_add_inline_script('activetrail-admin', $export_js);
} elseif ($current_section === 'coupons') {
$coupon_css = $this->get_coupon_css();
$coupon_js = $this->get_coupon_js();
wp_add_inline_style('activetrail-admin', $coupon_css);
wp_add_inline_script('activetrail-admin', $coupon_js);
} elseif ($current_section === 'sync') {
$status_css = $this->get_status_css();
wp_add_inline_style('activetrail-admin', $status_css);
}
// Always add historical sync JS/CSS (needed on main settings page where the field is displayed)
// This ensures the buttons work even when not on the historical-sync section
$historical_css = $this->get_historical_sync_css();
$historical_js = $this->get_historical_sync_js();
wp_add_inline_style('activetrail-admin', $historical_css);
wp_add_inline_script('activetrail-admin', $historical_js);
// Add required field indicators script (always needed on settings page).
$required_field_js = $this->get_required_field_indicators_js();
wp_add_inline_script('activetrail-admin', $required_field_js);
// Add API key and group select scripts (always needed on settings page).
$api_key_js = $this->get_api_key_field_js();
$group_select_js = $this->get_group_select_field_js();
wp_add_inline_script('activetrail-admin', $api_key_js);
wp_add_inline_script('activetrail-admin', $group_select_js);
// Localize script.
wp_localize_script('activetrail-admin', 'activetrail_admin', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('activetrail_admin_nonce'),
'strings' => array(
'testing_connection' => esc_html__('Testing connection...', 'activetrail-for-woocommerce'),
'connection_successful' => esc_html__('Connection successful!', 'activetrail-for-woocommerce'),
'connection_failed' => esc_html__('Connection failed:', 'activetrail-for-woocommerce'),
'loading_groups' => esc_html__('Loading groups...', 'activetrail-for-woocommerce'),
'groups_loaded' => esc_html__('Groups loaded successfully', 'activetrail-for-woocommerce'),
'groups_failed' => esc_html__('Failed to load groups:', 'activetrail-for-woocommerce'),
'refresh_groups' => esc_html__('Refresh Groups', 'activetrail-for-woocommerce'),
'copy' => esc_html__('Copy', 'activetrail-for-woocommerce'),
'product_feed_urls' => esc_html__('Product Feed URLs:', 'activetrail-for-woocommerce'),
'xml_feed' => esc_html__('XML Feed:', 'activetrail-for-woocommerce'),
'json_feed' => esc_html__('JSON Feed:', 'activetrail-for-woocommerce'),
'use_these_urls' => esc_html__('Use these URLs in your ActiveTrail campaigns or external integrations.', 'activetrail-for-woocommerce'),
'settings_saved' => esc_html__('Settings saved successfully', 'activetrail-for-woocommerce'),
'error_saving' => esc_html__('Error saving settings', 'activetrail-for-woocommerce'),
'plugin_language_change_hebrew' => esc_html__('Plugin language will change to Hebrew after saving settings', 'activetrail-for-woocommerce'),
'plugin_language_change_spanish' => esc_html__('Plugin language will change to Spanish after saving settings', 'activetrail-for-woocommerce'),
'plugin_language_change_french' => esc_html__('Plugin language will change to French after saving settings', 'activetrail-for-woocommerce'),
'plugin_language_change_english' => esc_html__('Plugin language will change to English after saving settings', 'activetrail-for-woocommerce'),
'saving_and_switching' => esc_html__('Saving settings and switching language', 'activetrail-for-woocommerce'),
'saving' => esc_html__('Saving...', 'activetrail-for-woocommerce'),
// Additional button translations for different languages.
'refresh_groups_hebrew' => esc_html__('רענן קבוצות', 'activetrail-for-woocommerce'),
'refresh_groups_spanish' => esc_html__('Actualizar grupos', 'activetrail-for-woocommerce'),
'refresh_groups_french' => esc_html__('Actualiser les groupes', 'activetrail-for-woocommerce'),
'save_group_hebrew' => esc_html__('שמור קבוצה', 'activetrail-for-woocommerce'),
'save_group_spanish' => esc_html__('Guardar grupo', 'activetrail-for-woocommerce'),
'save_group_french' => esc_html__('Enregistrer le groupe', 'activetrail-for-woocommerce'),
'connect_store' => esc_html__('Connect Store', 'activetrail-for-woocommerce'),
'connect_store_hebrew' => esc_html__('חבר חנות', 'activetrail-for-woocommerce'),
'connect_store_spanish' => esc_html__('Conectar tienda', 'activetrail-for-woocommerce'),
'connect_store_french' => esc_html__('Connecter la boutique', 'activetrail-for-woocommerce'),
'connecting_store' => esc_html__('Connecting...', 'activetrail-for-woocommerce'),
'store_connected' => esc_html__('Store connected successfully!', 'activetrail-for-woocommerce'),
'store_connected_hebrew' => 'החנות מחוברת בהצלחה!',
'store_connected_spanish' => '¡Tienda conectada exitosamente!',
'store_connected_french' => 'Boutique connectée avec succès !',
),
'store_is_connected' => ( get_option('activetrail_store_status') === 'connected' ),
));
}
/**
* Get sub-navigation CSS
*
* @return string CSS content
*/
private function get_sub_nav_css() {
return '.activetrail-sub-nav-wrapper {
margin: 0 0 30px 0;
background: linear-gradient(135deg, #262e3e 0%, #262e3e 100%);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(38, 46, 62, 0.2);
overflow: hidden;
position: relative;
}
.activetrail-sub-nav-wrapper::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
pointer-events: none;
}
.activetrail-sub-nav {
display: flex;
align-items: center;
gap: 24px;
margin: 0;
padding: 24px 40px 24px 40px;
position: relative;
z-index: 1;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.activetrail-sub-nav::-webkit-scrollbar {
display: none;
}
.activetrail-sub-nav-item {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 14px 24px;
text-decoration: none;
color: rgba(255, 255, 255, 0.8);
border-radius: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-weight: 600;
font-size: 14px;
white-space: nowrap;
position: relative;
backdrop-filter: blur(10px);
min-width: fit-content;
flex-shrink: 0;
}
.activetrail-sub-nav-item::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
opacity: 0;
transition: opacity 0.3s ease;
}
.activetrail-sub-nav-item:hover {
color: #fff;
text-decoration: none;
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.activetrail-sub-nav-item:hover::before {
opacity: 1;
}
.activetrail-sub-nav-item.active {
color: #fff;
background: #ef4b66;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
.activetrail-sub-nav-item:focus {
outline: 2px solid rgba(255, 255, 255, 0.6);
outline-offset: 2px;
}
.activetrail-sub-nav-item.active:focus {
outline: 2px solid rgba(255, 255, 255, 0.8);
outline-offset: 2px;
}
.activetrail-sub-nav-item[href*="activetrail.com"] {
transition: none !important;
transform: none !important;
box-shadow: none !important;
background: rgba(255, 255, 255, 0.1) !important;
color: rgba(255, 255, 255, 0.8) !important;
}
.activetrail-sub-nav-item[href*="activetrail.com"]:hover {
color: rgba(255, 255, 255, 0.8) !important;
transform: none !important;
box-shadow: none !important;
background: rgba(255, 255, 255, 0.1) !important;
}
.activetrail-sub-nav-item[href*="activetrail.com"]:focus {
outline: none !important;
background: rgba(255, 255, 255, 0.1) !important;
color: rgba(255, 255, 255, 0.8) !important;
}
.activetrail-sub-nav-item[href*="activetrail.com"]:active {
background: rgba(255, 255, 255, 0.1) !important;
color: rgba(255, 255, 255, 0.8) !important;
transform: none !important;
}
.activetrail-sub-nav-item[href*="activetrail.com"]::before {
display: none !important;
}
.activetrail-sub-nav-item[href*="activetrail.com"]:hover::before {
display: none !important;
}
.activetrail-sub-nav-item:not([href*="activetrail.com"]) {
color: rgba(255, 255, 255, 0.8) !important;
}
.activetrail-sub-nav-item:not([href*="activetrail.com"]):hover {
color: #fff !important;
}
.activetrail-sub-nav-item:not([href*="activetrail.com"]):focus {
color: #fff !important;
}
.activetrail-sub-nav-item:not([href*="activetrail.com"]):active {
color: #fff !important;
}
.activetrail-sub-nav-item.active {
color: #fff !important;
}
.activetrail-sub-nav-item.active:hover {
color: #fff !important;
}
.activetrail-sub-nav-item.active:focus {
color: #fff !important;
}
.activetrail-sub-nav-item.active:active {
color: #fff !important;
}
.activetrail-sub-nav-item .dashicons {
font-size: 18px;
width: 18px;
height: 18px;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
}
.activetrail-logo-container {
margin-right: auto;
display: flex;
align-items: center;
justify-content: center;
padding: 0 20px;
}
.activetrail-logo-link {
display: inline-block;
text-decoration: none;
border-radius: 6px;
padding: 4px;
transition: all 0.3s ease;
}
.activetrail-logo-link:hover {
background-color: rgba(255, 255, 255, 0.1);
transform: scale(1.05);
}
.activetrail-logo-link:focus {
outline: 2px solid rgba(255, 255, 255, 0.5);
outline-offset: 2px;
}
.activetrail-logo {
height: 48px;
width: auto;
opacity: 0.9;
transition: opacity 0.3s ease, transform 0.3s ease;
display: block;
}
.activetrail-logo:hover {
opacity: 1;
}
@media (max-width: 1024px) {
.activetrail-sub-nav-item {
padding: 12px 20px;
font-size: 13px;
align-items: center;
justify-content: center;
}
.activetrail-sub-nav-item .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
}
.activetrail-logo {
height: 42px;
}
}
@media (max-width: 768px) {
.activetrail-sub-nav-wrapper {
margin: 0 0 25px 0;
border-radius: 8px;
}
.activetrail-sub-nav {
padding: 16px 24px 16px 24px;
gap: 16px;
align-items: center;
}
.activetrail-sub-nav-item {
padding: 10px 16px;
font-size: 12px;
}
.activetrail-sub-nav-item .dashicons {
font-size: 14px;
width: 14px;
height: 14px;
}
.activetrail-logo {
height: 36px;
}
.activetrail-logo-container {
padding: 0 10px;
}
}';
}
/**
* Get status sync CSS
*
* @return string CSS content
*/
private function get_status_css() {
return '.activetrail-status-table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
}
.activetrail-status-table th,
.activetrail-status-table td {
padding: 12px 15px;
border: 1px solid #ddd;
vertical-align: middle;
word-wrap: break-word;
}
.activetrail-status-table th {
background: #f1f1f1;
font-weight: 600;
text-align: left;
}
.activetrail-status-table th:first-child {
width: 120px;
}
.activetrail-status-table th:nth-child(2) {
width: 80px;
text-align: center;
}
.activetrail-status-table th:nth-child(3) {
width: auto;
}
.activetrail-status-table th:nth-child(4) {
width: 150px;
text-align: left;
}
.activetrail-status-table .custom-status {
background: #fff9e6;
}
.activetrail-status-table .sync-enabled {
background: #e8f5e8;
}
.activetrail-category-header td {
background: #f9f9f9 !important;
font-weight: bold;
border-top: 2px solid #0073aa;
}
.activetrail-status-table td {
text-align: left;
}
.activetrail-status-table td:nth-child(2) {
text-align: center;
}
.custom-badge {
background: #ff9800;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
margin-left: 5px;
}
.recommended-badge {
background: #4caf50;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
margin-left: 5px;
}
.category-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
}
.category-pending { background: #fff3cd; color: #856404; }
.category-active { background: #d1ecf1; color: #0c5460; }
.category-completed { background: #d4edda; color: #155724; }
.category-cancelled { background: #f8d7da; color: #721c24; }
.category-custom { background: #e2e3e5; color: #383d41; }
.summary-stats {
display: flex;
gap: 20px;
margin-bottom: 10px;
}
.summary-stats .stat {
color: #666;
}';
}
/**
* Get export section CSS
*
* @return string CSS content
*/
private function get_export_css() {
return '.feed-url-box {
display: flex;
gap: 10px;
align-items: center;
}
.feed-url-input {
flex: 1;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f9f9f9;
font-family: monospace;
font-size: 13px;
color: #333;
}
.copy-url-btn, .refresh-key-btn {
white-space: nowrap;
display: flex;
align-items: center;
gap: 5px;
}
.copy-url-btn.copied {
background: #46b450;
color: white;
border-color: #46b450;
}
.refresh-key-notice {
margin-top: 15px;
padding: 12px;
background: #f0f6fc;
border-left: 4px solid #2271b1;
color: #1e1e1e;
font-size: 13px;
line-height: 1.6;
border-radius: 4px;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.refresh-key-btn .spin {
animation: spin 1s linear infinite;
}
@media (max-width: 768px) {
.feed-url-box {
flex-direction: column;
align-items: stretch;
}
.copy-url-btn, .refresh-key-btn {
width: 100%;
justify-content: center;
}
}';
}
/**
* Get export section JavaScript
*
* @return string JavaScript content
*/
private function get_export_js() {
$base_url = esc_js( home_url() );
$ajax_url = esc_js( admin_url( 'admin-ajax.php' ) );
$nonce = esc_js( wp_create_nonce( 'activetrail_admin_nonce' ) );
$no_url = esc_js( esc_html__( 'No URL available.', 'activetrail-for-woocommerce' ) );
$copied = esc_js( esc_html__( 'Copied!', 'activetrail-for-woocommerce' ) );
$failed_copy = esc_js( esc_html__( 'Failed to copy. Please select and copy manually.', 'activetrail-for-woocommerce' ) );
$invalid_feed = esc_js( esc_html__( 'Invalid feed type.', 'activetrail-for-woocommerce' ) );
$key_refreshed = esc_js( esc_html__( 'Security key refreshed successfully!', 'activetrail-for-woocommerce' ) );
$failed_refresh = esc_js( esc_html__( 'Failed to refresh key:', 'activetrail-for-woocommerce' ) );
$unknown_error = esc_js( esc_html__( 'Unknown error', 'activetrail-for-woocommerce' ) );
$network_error = esc_js( esc_html__( 'Network error occurred', 'activetrail-for-woocommerce' ) );
$refreshing = esc_js( esc_html__( 'Refreshing...', 'activetrail-for-woocommerce' ) );
return "jQuery(document).ready(function($) {
var baseUrl = '{$base_url}';
// Copy URL functionality.
$('.copy-url-btn').on('click', function(e) {
e.preventDefault();
var \$button = $(this);
var targetId = \$button.data('clipboard-target');
var \$input = $(targetId);
var textToCopy = \$input.val();
if (!textToCopy) {
alert('{$no_url}');
return;
}
// Try modern Clipboard API first.
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(textToCopy).then(function() {
var originalText = \$button.html();
\$button.addClass('copied').html('{$copied}');
setTimeout(function() {
\$button.removeClass('copied').html(originalText);
}, 2000);
}).catch(function(err) {
console.error('Failed to copy:', err);
fallbackCopyText(textToCopy, \$button);
});
} else {
fallbackCopyText(textToCopy, \$button, \$input);
}
});
// Fallback copy function.
function fallbackCopyText(text, \$button, \$input) {
var textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.left = '-999999px';
textarea.style.top = '-999999px';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
var originalText = \$button.html();
\$button.addClass('copied').html('{$copied}');
setTimeout(function() {
\$button.removeClass('copied').html(originalText);
}, 2000);
} else {
alert('{$failed_copy}');
}
} catch (err) {
console.error('Fallback copy failed:', err);
alert('{$failed_copy}');
} finally {
document.body.removeChild(textarea);
}
}
// Refresh key functionality.
$('.refresh-key-btn').on('click', function(e) {
e.preventDefault();
var \$button = $(this);
var feedType = \$button.data('feed-type');
var originalText = \$button.html();
if (!feedType) {
alert('{$invalid_feed}');
return;
}
// Show notice before refresh.
if (feedType === 'google') {
$('#google-refresh-notice').show();
} else if (feedType === 'facebook') {
$('#facebook-refresh-notice').show();
}
var ajaxUrl = (typeof activetrail_admin !== 'undefined' && activetrail_admin.ajax_url)
? activetrail_admin.ajax_url
: '{$ajax_url}';
var nonce = (typeof activetrail_admin !== 'undefined' && activetrail_admin.nonce)
? activetrail_admin.nonce
: '{$nonce}';
\$button.prop('disabled', true).html('{$refreshing}');
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
action: 'activetrail_refresh_feed_key',
feed_type: feedType,
nonce: nonce
},
success: function(response) {
if (response.success) {
// Update the hidden key input.
if (feedType === 'google') {
$('#google-feed-key').val(response.data.new_key);
var newUrl = baseUrl + '/?activetrail_export=google&key=' + response.data.new_key;
$('#google-feed-url').val(newUrl);
} else if (feedType === 'facebook') {
$('#facebook-feed-key').val(response.data.new_key);
var newUrl = baseUrl + '/?activetrail_export=facebook&key=' + response.data.new_key;
$('#facebook-feed-url').val(newUrl);
}
// Show success message.
$('
');
}
});
} else {
alert('{$failed_delete} ' + (response.data || '{$unknown_error}'));
}
},
error: function() {
alert('{$error_deleting}');
},
complete: function() {
\$button.prop('disabled', false);
}
});
});
});";
}
/**
* Get historical sync CSS
*
* @return string CSS content
*/
private function get_historical_sync_css() {
return '.activetrail-historical-sync-container {
max-width: 100%;
margin: 20px 0;
}
.historical-sync-main {
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
}
.sync-header h3 {
margin: 0 0 10px 0;
font-size: 18px;
display: flex;
align-items: center;
}
.sync-header .description {
margin: 0 0 20px 0;
color: #666;
line-height: 1.5;
}
.sync-configuration {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.config-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.config-item {
display: flex;
flex-direction: column;
gap: 5px;
}
.config-item label {
font-weight: 600;
color: #333;
}
.config-item input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.sync-actions {
display: flex;
gap: 10px;
align-items: center;
}
.sync-actions .button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
cursor: pointer;
position: relative;
z-index: 1;
}
.sync-actions .button:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.sync-actions .button:not(:disabled):hover {
opacity: 0.9;
}
.sync-actions .button .dashicons.spin {
display: inline-block;
animation: spin 1s linear infinite;
}
.sync-progress {
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.sync-progress h4 {
margin: 0 0 15px 0;
color: #333;
}
.progress-messages {
border-top: 1px solid #e9ecef;
padding-top: 15px;
}
#sync-status-message {
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
#sync-status-message.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
#sync-status-message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
#sync-status-message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
#sync-status-message.warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
#sync-errors h5 {
margin: 0 0 10px 0;
color: #721c24;
}
#error-list {
margin: 0;
padding-left: 20px;
max-height: 150px;
overflow-y: auto;
}
#error-list li {
margin: 5px 0;
color: #721c24;
font-size: 13px;
}
@media (max-width: 768px) {
.config-grid {
grid-template-columns: 1fr;
}
.sync-actions {
flex-direction: column;
align-items: stretch;
}
.sync-actions .button {
margin-bottom: 10px;
}
}
.spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}';
}
/**
* Get historical sync JavaScript
*
* @return string JavaScript content
*/
private function get_historical_sync_js() {
$ajax_url = esc_js( admin_url( 'admin-ajax.php' ) );
$nonce = esc_js( wp_create_nonce( 'activetrail_admin_nonce' ) );
$sync_completed = esc_js( esc_html__( 'Sync completed successfully!', 'activetrail-for-woocommerce' ) );
$contact = esc_js( esc_html__( 'contact', 'activetrail-for-woocommerce' ) );
$contacts = esc_js( esc_html__( 'contacts', 'activetrail-for-woocommerce' ) );
$failed = esc_js( esc_html__( 'failed', 'activetrail-for-woocommerce' ) );
return "jQuery(document).ready(function($) {
console.log('[ActiveTrail Historical Sync] JavaScript loaded');
var syncInterval = null;
var isPolling = false;
// Handler function for sync button clicks
function handleSyncClick(e) {
e.preventDefault();
e.stopPropagation();
var \$button = $(this);
console.log('[ActiveTrail Historical Sync] Button clicked:', \$button.attr('id'));
// Check if button is disabled
if (\$button.prop('disabled')) {
console.log('[ActiveTrail Historical Sync] Button is disabled');
return false;
}
var syncType = \$button.data('sync-type');
var ajaxUrl = (typeof activetrail_admin !== 'undefined' && activetrail_admin.ajax_url)
? activetrail_admin.ajax_url
: '{$ajax_url}';
var nonce = (typeof activetrail_admin !== 'undefined' && activetrail_admin.nonce)
? activetrail_admin.nonce
: '{$nonce}';
// Get date range from form inputs
var dateFrom = $('#sync-date-from').val() || '';
var dateTo = $('#sync-date-to').val() || '';
// Determine sync options based on sync type
var syncContacts = (syncType === 'contacts' || syncType === 'all');
var syncOrders = (syncType === 'all');
// Disable buttons and show loading state
\$button.prop('disabled', true);
var originalText = \$button.html();
\$button.html(' ' + (syncType === 'contacts' ? 'Starting Contacts Sync...' : 'Starting Full Sync...'));
console.log('[ActiveTrail Historical Sync] Starting sync:', {
syncType: syncType,
dateFrom: dateFrom,
dateTo: dateTo,
syncContacts: syncContacts,
syncOrders: syncOrders
});
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
action: 'activetrail_start_historical_sync',
sync_type: syncType,
date_from: dateFrom,
date_to: dateTo,
sync_contacts: syncContacts,
sync_orders: syncOrders,
nonce: nonce
},
success: function(response) {
console.log('[ActiveTrail Historical Sync] Response:', response);
if (response.success) {
// Show status message container
$('#sync-progress-container').show();
$('#start-sync-contacts-only, #start-sync-contacts-orders').hide();
$('#cancel-historical-sync').show();
// Show starting message
var \$messageDiv = $('#sync-status-message');
\$messageDiv.removeClass('info success error warning').addClass('info');
\$messageDiv.html('מתחיל...').show();
// Check sync status immediately to update from starting to in process
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
action: 'activetrail_get_sync_progress',
nonce: nonce
},
success: function(statusResponse) {
if (statusResponse && statusResponse.success && statusResponse.data) {
if (typeof ActiveTrailAdmin !== 'undefined' && typeof ActiveTrailAdmin.updateSyncProgress === 'function') {
ActiveTrailAdmin.updateSyncProgress(statusResponse.data);
} else if (typeof updateProgress === 'function') {
updateProgress(statusResponse.data);
}
}
}
});
// Start progress polling only when button is clicked
if (typeof window.activetrailStartProgressPolling === 'function') {
window.activetrailStartProgressPolling();
} else if (typeof ActiveTrailAdmin !== 'undefined' && typeof ActiveTrailAdmin.startProgressPolling === 'function') {
ActiveTrailAdmin.startProgressPolling();
} else if (typeof startProgressPolling === 'function') {
startProgressPolling();
}
} else {
// Show error message
var errorMsg = response.data && response.data.message ? response.data.message : 'Failed to start sync';
alert('Error: ' + errorMsg);
// Re-enable buttons
\$button.prop('disabled', false).html(originalText);
}
},
error: function(xhr, status, error) {
console.error('[ActiveTrail Historical Sync] AJAX error:', {xhr: xhr, status: status, error: error});
alert('Network error: ' + error);
// Re-enable buttons
\$button.prop('disabled', false).html(originalText);
}
});
return false;
}
// Use event delegation - this works even if buttons are added dynamically
$(document).on('click', '#start-sync-contacts-only, #start-sync-contacts-orders', handleSyncClick);
// Also listen for the .sync namespace trigger from onclick
$(document).on('click.sync', '#start-sync-contacts-only, #start-sync-contacts-orders', handleSyncClick);
// Also try direct binding after page loads
function initDirectHandlers() {
var \$contactsBtn = $('#start-sync-contacts-only');
var \$ordersBtn = $('#start-sync-contacts-orders');
console.log('[ActiveTrail Historical Sync] Initializing handlers:', {
contactsExists: \$contactsBtn.length > 0,
ordersExists: \$ordersBtn.length > 0
});
if (\$contactsBtn.length > 0) {
\$contactsBtn.off('click.directSync').on('click.directSync', handleSyncClick);
console.log('[ActiveTrail Historical Sync] Direct handler attached to contacts button');
}
if (\$ordersBtn.length > 0) {
\$ordersBtn.off('click.directSync').on('click.directSync', handleSyncClick);
console.log('[ActiveTrail Historical Sync] Direct handler attached to orders button');
}
}
// Initialize immediately and after delays
initDirectHandlers();
setTimeout(initDirectHandlers, 500);
setTimeout(initDirectHandlers, 1000);
setTimeout(initDirectHandlers, 2000);
// Check if sync is already running when page loads
function checkSyncStatus() {
var ajaxUrl = (typeof activetrail_admin !== 'undefined' && activetrail_admin.ajax_url)
? activetrail_admin.ajax_url
: '{$ajax_url}';
var nonce = (typeof activetrail_admin !== 'undefined' && activetrail_admin.nonce)
? activetrail_admin.nonce
: '{$nonce}';
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
action: 'activetrail_get_sync_progress',
nonce: nonce
},
success: function(response) {
if (response.success && response.data) {
var status = response.data.status;
if (status === 'running') {
// Sync is running - show status message and start polling
$('#sync-progress-container').show();
$('#start-sync-contacts-only, #start-sync-contacts-orders').hide();
$('#cancel-historical-sync').show();
updateProgress(response.data);
startProgressPolling();
} else if (status === 'completed' || status === 'cancelled') {
// Show last status message
$('#sync-progress-container').show();
$('#start-sync-contacts-only, #start-sync-contacts-orders').show();
$('#cancel-historical-sync').hide();
updateProgress(response.data);
} else {
// No sync running - hide progress container
$('#sync-progress-container').hide();
}
} else {
// No sync data - hide progress container
$('#sync-progress-container').hide();
}
},
error: function(xhr, status, error) {
console.error('[ActiveTrail] Error checking sync status:', error);
// Hide progress container on error
$('#sync-progress-container').hide();
}
});
}
// Don't check sync status on page load - only when button is clicked
// Cancel sync - use event delegation.
$(document).on('click', '#cancel-historical-sync', function(e) {
e.preventDefault();
e.stopPropagation();
var \$button = $(this);
var ajaxUrl = (typeof activetrail_admin !== 'undefined' && activetrail_admin.ajax_url)
? activetrail_admin.ajax_url
: '{$ajax_url}';
var nonce = (typeof activetrail_admin !== 'undefined' && activetrail_admin.nonce)
? activetrail_admin.nonce
: '{$nonce}';
// Disable button and show loading
\$button.prop('disabled', true);
var originalText = \$button.html();
\$button.html(' Cancelling...');
console.log('Cancelling historical sync');
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
action: 'activetrail_cancel_historical_sync',
nonce: nonce
},
success: function(response) {
console.log('Cancel sync response:', response);
// Stop progress polling
if (typeof window.activetrailStopProgressPolling === 'function') {
window.activetrailStopProgressPolling();
} else if (typeof stopProgressPolling === 'function') {
stopProgressPolling();
} else if (typeof ActiveTrailAdmin !== 'undefined' && typeof ActiveTrailAdmin.stopProgressPolling === 'function') {
ActiveTrailAdmin.stopProgressPolling();
}
// Reset sync interface
if (typeof resetSyncInterface === 'function') {
resetSyncInterface();
} else {
// Manual reset if function doesn't exist
$('#start-sync-contacts-only, #start-sync-contacts-orders').show();
$('#cancel-historical-sync').hide();
$('#sync-progress-container').hide();
}
\$button.prop('disabled', false).html(originalText);
},
error: function(xhr, status, error) {
console.error('AJAX error cancelling sync:', {xhr: xhr, status: status, error: error});
alert('Error cancelling sync: ' + error);
\$button.prop('disabled', false).html(originalText);
}
});
return false;
});
// Start progress polling.
function startProgressPolling() {
if (isPolling) return;
isPolling = true;
var ajaxUrl = (typeof activetrail_admin !== 'undefined' && activetrail_admin.ajax_url)
? activetrail_admin.ajax_url
: '{$ajax_url}';
var nonce = (typeof activetrail_admin !== 'undefined' && activetrail_admin.nonce)
? activetrail_admin.nonce
: '{$nonce}';
// Make functions globally accessible
window.activetrailStartProgressPolling = startProgressPolling;
window.activetrailStopProgressPolling = stopProgressPolling;
window.activetrailResetSyncInterface = resetSyncInterface;
// Start polling every 2 seconds
syncInterval = setInterval(function() {
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
action: 'activetrail_get_sync_progress',
nonce: nonce
},
success: function(response) {
if (response && response.success) {
if (response.data) {
updateProgress(response.data);
if (response.data.status === 'completed' || response.data.status === 'cancelled') {
stopProgressPolling();
if (response.data.status === 'completed') {
showSyncMessage('{$sync_completed}', 'success');
setTimeout(function() {
location.reload();
}, 2000);
}
resetSyncInterface();
}
} else {
console.warn('[ActiveTrail] Progress response missing data:', response);
}
} else {
console.warn('[ActiveTrail] Progress response not successful:', response);
}
},
error: function(xhr, status, error) {
console.error('[ActiveTrail] Error fetching sync progress:', {xhr: xhr, status: status, error: error});
// Don't stop polling on error - might be temporary network issue
}
});
}, 2000);
}
// Stop progress polling.
function stopProgressPolling() {
if (syncInterval) {
clearInterval(syncInterval);
syncInterval = null;
}
isPolling = false;
}
// Update progress display - simplified to show only status messages
function updateProgress(data) {
if (!data) {
console.warn('[ActiveTrail] updateProgress called with no data');
return;
}
var status = data.status || '';
var \$messageDiv = $('#sync-status-message');
var message = '';
var messageType = 'info';
// Show simple status messages based on sync status
if (status === 'running') {
message = 'בתהליך...';
messageType = 'info';
} else if (status === 'completed') {
message = 'הסתיים בהצלחה';
messageType = 'success';
} else if (status === 'cancelled') {
message = 'בוטל';
messageType = 'warning';
} else if (status === 'error' || status === 'failed') {
message = 'נכשל';
messageType = 'error';
}
// Update message display
\$messageDiv.removeClass('info success error warning').addClass(messageType);
\$messageDiv.html(message).show();
// Show errors if any
if (data.errors && data.errors.length > 0) {
var errorList = $('#error-list');
errorList.empty();
data.errors.forEach(function(error) {
var errorText = error.email ? (error.email + ': ') : '';
errorText += error.error || error.message || 'Unknown error';
errorList.append('
' + errorText + '
');
});
$('#sync-errors').show();
} else {
$('#sync-errors').hide();
}
// Show failed contacts CSV if available
if (data.failed_contacts_csv && data.failed_contacts_csv.url) {
$('#download-failed-csv').attr('href', data.failed_contacts_csv.url);
if (data.failed_contacts_csv.count) {
var contactText = data.failed_contacts_csv.count === 1 ? '{$contact}' : '{$contacts}';
$('#failed-csv-count').text('(' + data.failed_contacts_csv.count + ' ' + contactText + ' ' + '{$failed}' + ')');
}
$('#failed-contacts-csv').show();
} else {
$('#failed-contacts-csv').hide();
}
}
// Show sync message.
function showSyncMessage(message, type) {
var \$messageDiv = $('#sync-status-message');
\$messageDiv.removeClass('info success error warning').addClass(type);
\$messageDiv.html(message).show();
}
// Format number with commas.
function formatNumber(num) {
if (num === null || num === undefined || num === '' || num === '-') return '-';
var number = parseInt(num, 10);
if (isNaN(number)) return '-';
return number.toLocaleString();
}
// Reset sync interface.
function resetSyncInterface() {
$('#start-sync-contacts-only, #start-sync-contacts-orders').show();
$('#cancel-historical-sync').hide();
$('#sync-progress-container').hide();
}
});";
}
/**
* Get API key field JavaScript
*
* @return string JavaScript content
*/
private function get_api_key_field_js() {
$ajax_url = esc_js( admin_url( 'admin-ajax.php' ) );
$nonce = esc_js( wp_create_nonce( 'activetrail_admin_nonce' ) );
$field_key = 'activetrail_api_key';
$save_key_label = esc_js( esc_html__( 'Save API Key', 'activetrail-for-woocommerce' ) );
$api_key_required = esc_js( esc_html__( 'API Key is required. Please enter your API key.', 'activetrail-for-woocommerce' ) );
$saving_api_key = esc_js( esc_html__( 'Saving API key...', 'activetrail-for-woocommerce' ) );
$api_key_saved = esc_js( esc_html__( 'API key saved successfully!', 'activetrail-for-woocommerce' ) );
$network_error = esc_js( esc_html__( 'Network error occurred', 'activetrail-for-woocommerce' ) );
$timeout_error = esc_js( esc_html__( 'Request timeout. The API key test took too long. Please check your connection and try again.', 'activetrail-for-woocommerce' ) );
$select_group = esc_js( esc_html__( 'Select a group...', 'activetrail-for-woocommerce' ) );
return "jQuery(document).ready(function($) {
console.log('[ActiveTrail] API Key Save JavaScript loaded');
var ajaxUrl = (typeof activetrail_admin !== 'undefined' && activetrail_admin.ajax_url)
? activetrail_admin.ajax_url
: '{$ajax_url}';
var nonce = (typeof activetrail_admin !== 'undefined' && activetrail_admin.nonce)
? activetrail_admin.nonce
: '{$nonce}';
var \$saveKeyBtn = $('#activetrail-save-api-key-btn');
var saveKeyOriginalText = \$saveKeyBtn.length ? \$saveKeyBtn.text() : '{$save_key_label}';
if (\$saveKeyBtn.length) {
console.log('[ActiveTrail] Save API key button found in DOM');
} else {
console.warn('[ActiveTrail] Save API key button NOT found in DOM');
}
function refreshGroupsDropdown() {
var \$groupSelect = $('#activetrail_group_id');
if (\$groupSelect.length === 0) {
console.log('[ActiveTrail] Group select not found, skipping refresh');
return;
}
console.log('[ActiveTrail] Refreshing groups dropdown...');
var originalValue = \$groupSelect.val();
\$groupSelect.prop('disabled', true);
var savedApiKey = $('#activetrail_api_key').val();
if (!savedApiKey) {
console.log('[ActiveTrail] No API key found, cannot refresh groups');
\$groupSelect.prop('disabled', false);
return;
}
$.ajax({
url: ajaxUrl,
type: 'POST',
timeout: 15000,
data: {
action: 'activetrail_get_groups',
api_key: savedApiKey,
nonce: nonce
},
success: function(response) {
if (response && response.success && response.data && response.data.groups) {
\$groupSelect.empty();
var selectGroupText = '{$select_group}';
\$groupSelect.append(jQuery('').attr('value', '').text(selectGroupText));
jQuery.each(response.data.groups, function(index, group) {
var option = jQuery('').attr('value', group.id).text(group.name);
if (group.id == originalValue) {
option.attr('selected', 'selected');
}
\$groupSelect.append(option);
});
// Always append Load-more so user can page through groups.
\$groupSelect.data('at-page', 1);
\$groupSelect.append(jQuery('').attr('value', '__load_more__').text('\u2500\u2500 Load more... \u2500\u2500'));
console.log('[ActiveTrail] Groups dropdown refreshed successfully');
} else {
console.error('[ActiveTrail] Failed to refresh groups:', response);
}
},
error: function(xhr, status, error) {
console.error('[ActiveTrail] Error refreshing groups:', status, error);
},
complete: function() {
\$groupSelect.prop('disabled', false);
}
});
}
// Use event delegation like the test connection button (works even if button is added dynamically)
$(document).on('click', '#activetrail-save-api-key-btn', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('[ActiveTrail] Save API key handler triggered');
var \$button = $(this);
var \$input = $('#activetrail_api_key');
var \$status = $('#activetrail-api-key-status');
var apiKeyValue = \$input.val();
console.log('[ActiveTrail] Save API key button clicked');
if (!apiKeyValue || apiKeyValue.trim() === '') {
\$status.html('
{$api_key_required}
');
\$input.focus();
return false;
}
// Check if button is disabled
if (\$button.prop('disabled')) {
console.log('[ActiveTrail] Save button is disabled');
return false;
}
\$button.prop('disabled', true);
var originalText = \$button.text();
\$button.text('{$saving_api_key}');
\$status.html('
{$saving_api_key}
');
console.log('[ActiveTrail] Starting API key save request...');
var ajaxRequest = $.ajax({
url: ajaxUrl,
type: 'POST',
timeout: 35000,
data: {
action: 'activetrail_save_api_key',
api_key: apiKeyValue,
nonce: nonce
},
success: function(response) {
console.log('[ActiveTrail] API key save response:', response);
if (response && response.success) {
\$status.html('
✅ {$api_key_saved}
');
refreshGroupsDropdown();
// Unlock the Group ID section now that the API key is saved
\$('#activetrail-group-section-wrapper').removeClass('at-group-locked');
} else {
var errorMessage = (response && response.data) ? response.data : 'Unknown error';
if (typeof errorMessage === 'object' && errorMessage.message) {
errorMessage = errorMessage.message;
}
\$status.html('
❌ ' + errorMessage + '
');
}
},
error: function(xhr, status, error) {
console.error('[ActiveTrail] API key save error:', status, error, xhr);
var errorMessage = '{$network_error}';
if (status === 'timeout') {
errorMessage = '{$timeout_error}';
} else {
if (xhr.responseJSON && xhr.responseJSON.data) {
errorMessage = xhr.responseJSON.data;
} else if (xhr.responseText) {
try {
var response = JSON.parse(xhr.responseText);
if (response.data) {
errorMessage = response.data;
}
} catch (e) {
console.error('[ActiveTrail] Failed to parse error response:', e);
}
}
}
\$status.html('
');
updateDebugInfo();
console.log('[ActiveTrail] Group saved:', {
group_id: selectedValue,
group_name: selectedText,
timestamp: new Date().toISOString()
});
// Show Connect Store section now that a group is saved
\$('#activetrail-connect-store-section-wrapper').slideDown(300);
// Enable Connect Store button now that a group is saved
var \$connectBtn = \$('#activetrail-connect-store-btn');
if (!\$connectBtn.hasClass('at-connect-ready')) {
\$connectBtn.prop('disabled', false).addClass('at-connect-ready');
}
} else {
\$status.html('
' + (response.data || 'Unknown error') + '
');
}
},
error: function() {
\$status.html('
❌ {$network_error}
');
},
complete: function() {
\$button.prop('disabled', false).text('{$save_group}');
}
});
});
function updateDebugInfo() {
var \$debug = $('#activetrail-group-debug');
var selectedValue = $('#activetrail_group_id').val();
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
action: 'activetrail_get_debug_info',
nonce: nonce
},
success: function(response) {
if (response.success && response.data) {
var debugHtml = '{$debug_info} ';
debugHtml += 'Current WC Option: ' + (response.data.wc_option || 'empty') + ' ';
debugHtml += 'Unified Settings: ' + (response.data.unified_setting || 'empty') + ' ';
debugHtml += 'Selected Value: ' + (selectedValue || 'empty') + ' ';
debugHtml += 'Sync Status: ' + (response.data.sync_status || 'unknown') + ' ';
debugHtml += 'Last Updated: ' + response.data.timestamp + ' ';
\$debug.html(debugHtml);
}
}
});
}
});";
}
/**
* Get required field indicators JavaScript
*
* @return string JavaScript content
*/
private function get_required_field_indicators_js() {
return "jQuery(document).ready(function($) {
// Add asterisk to API Key label.
var \$apiKeyLabel = $('label[for=\"activetrail_api_key\"]');
if (\$apiKeyLabel.length && \$apiKeyLabel.find('.required').length === 0) {
\$apiKeyLabel.append(' *');
}
// Add required attribute to API Key field.
var \$apiKeyField = $('#activetrail_api_key');
if (\$apiKeyField.length && !\$apiKeyField.attr('required')) {
\$apiKeyField.attr('required', 'required');
\$apiKeyField.attr('aria-required', 'true');
}
});";
}
/**
* Display admin notices
*/
public function admin_notices() {
// Only show on our settings page.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Navigation only, no data processing
if (!isset($_GET['page']) || sanitize_text_field(wp_unslash($_GET['page'])) !== 'wc-settings') {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Navigation only, no data processing
if (!isset($_GET['tab']) || sanitize_text_field(wp_unslash($_GET['tab'])) !== 'activetrail') {
return;
}
// Check configuration status.
$status = ActiveTrail_Settings::get_configuration_status();
if (!$status['fully_configured']) {
?>
';
}
/**
* Output closing wrapper div for a collapsible section.
*
* @param array $value Field configuration.
*/
public function output_section_wrapper_end( $value ) {
echo '
';
}
/**
* Open the Group section wrapper div (fires after API settings table closes).
*/
public function output_group_section_wrapper_open() {
echo '
';
}
/**
* Close Group wrapper and open Connect Store wrapper (fires after Group settings table closes).
*/
public function output_group_section_wrapper_close_connect_open() {
echo '
';
echo '
';
}
/**
* Close the Connect Store section wrapper div (fires after Connect Store settings table closes).
*/
public function output_connect_store_section_wrapper_close() {
echo '
';
}
/**
* Output Connect Store button field.
*
* @param array $value Field configuration.
*/
public function output_connect_store_button_field( $value ) {
// Button has been moved to the API Key field row.
}
/**
* Output group select field with save button and debug
*
* @param array $value Field configuration.
*/
public function output_group_select_field($value) {
$field_key = $value['id'];
$field_name = $value['name'];
$description = $value['desc'];
$options = $value['options'];
$default_value = $value['default'];
$css = isset($value['css']) ? $value['css'] : '';
// Get current value.
$current_value = get_option($field_key, $default_value);
?>
post_type !== 'shop_coupon') {
wp_send_json_error(esc_html__('Coupon not found.', 'activetrail-for-woocommerce'));
}
try {
// Delete the coupon.
$result = wp_delete_post($coupon_id, true);
if ($result) {
wp_send_json_success(array(
/* translators: %s: Coupon code */
'message' => sprintf(esc_html__('Coupon "%s" deleted successfully!', 'activetrail-for-woocommerce'), $coupon_post->post_title)
));
} else {
wp_send_json_error(esc_html__('Failed to delete coupon.', 'activetrail-for-woocommerce'));
}
} catch (Exception $e) {
/* translators: %s: Error message */
wp_send_json_error(sprintf(esc_html__('Error deleting coupon: %s', 'activetrail-for-woocommerce'), $e->getMessage()));
}
}
/**
* Handle coupon form submission (deprecated - now handled via REST API)
*/
private function handle_coupon_form_submission() {
// This method is deprecated - coupon creation is now handled via REST API.
// Kept for backward compatibility but does nothing.
return;
}
/**
* Maybe create coupon from settings form submission (deprecated)
*/
private function maybe_create_coupon_from_settings() {
// This method is deprecated - coupon creation is now handled via REST API.
// Kept for backward compatibility but does nothing.
return;
}
/**
* Output historical sync field
*
* @param array $value Field configuration.
*/
public function output_historical_sync_field($value) {
// Switch to plugin's language setting and reload text domain for translations.
$this->switch_to_plugin_language();
// Get current sync status.
$sync_status = get_option('activetrail_historical_sync_status', array());
// Check if API is configured.
$api_configured = ActiveTrail_Settings::is_api_configured();
?>
id !== 'woocommerce_page_wc-settings') {
return;
}
// Only run on ActiveTrail tab.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Navigation only, no data processing
if (!isset($_GET['tab']) || $_GET['tab'] !== 'activetrail') {
return;
}
// JavaScript moved to wp_add_inline_script in enqueue_admin_scripts()
}
/**
* Switch to plugin's configured language and reload text domain
*
* @return bool True if locale/textdomain was loaded, false otherwise
*/
private function switch_to_plugin_language() {
if (!class_exists('ActiveTrail_Settings')) {
require_once ACTIVETRAIL_WC_PLUGIN_DIR . 'includes/settings/class-settings.php';
}
$current_language = ActiveTrail_Settings::get_current_language();
if (empty($current_language)) {
return false;
}
$domain = 'activetrail-for-woocommerce';
// Find the correct MO file.
// Note: MO files are named activetrail-woocommerce-{locale}.mo.
$mofile = ACTIVETRAIL_WC_PLUGIN_DIR . 'languages/activetrail-woocommerce-' . $current_language . '.mo';
if (!file_exists($mofile)) {
// Fallback to WordPress languages directory.
$mofile = WP_LANG_DIR . '/plugins/activetrail-woocommerce-' . $current_language . '.mo';
}
if (file_exists($mofile)) {
// Completely remove existing text domain from all globals.
unload_textdomain($domain);
if (isset($GLOBALS['l10n'][$domain])) {
unset($GLOBALS['l10n'][$domain]);
}
if (isset($GLOBALS['l10n_unloaded'])) {
unset($GLOBALS['l10n_unloaded'][$domain]);
}
// Use override_load_textdomain filter to force loading our MO file.
$override_filter = function($override, $domain_to_load, $mofile_path) use ($domain, $mofile) {
if ($domain_to_load === $domain) {
// Load our specific MO file.
if (!class_exists('MO')) {
require_once ABSPATH . WPINC . '/pomo/mo.php';
}
$mo = new MO();
if ($mo->import_from_file($mofile)) {
$GLOBALS['l10n'][$domain] = &$mo;
return true; // Indicate we handled it
}
}
return $override;
};
// Add filter with high priority.
add_filter('override_load_textdomain', $override_filter, 999, 3);
// Load text domain - this will trigger our filter.
load_textdomain($domain, $mofile);
// Remove filter to avoid affecting other translations.
remove_filter('override_load_textdomain', $override_filter, 999);
// Also directly set in globals as backup.
if (!class_exists('MO')) {
require_once ABSPATH . WPINC . '/pomo/mo.php';
}
if (class_exists('MO')) {
$mo = new MO();
if ($mo->import_from_file($mofile)) {
$GLOBALS['l10n'][$domain] = &$mo;
}
}
// Hardcoded Hebrew fallback for feed labels, order statuses desc, and status table category labels when .mo lookup fails (ensures RTL admin shows Hebrew).
$feed_label_fallbacks = array(
'Product feed title.' => 'כותרת פיד המוצרים',
'Product feed description.' => 'תיאור פיד המוצרים',
'Select which order statuses should trigger events to ActiveTrail. If no statuses are selected, all statuses will be synchronized (backward compatible).' => 'בחר אילו סטטוסי הזמנות יפעילו אירועים ל-ActiveTrail. אם לא נבחרו סטטוסים, כל הסטטוסים יסונכרנו (תאימות לאחור).',
// Status table category column (קטגוריה).
'Pending' => 'ממתין',
'Processing' => 'בטיפול',
'On Hold' => 'בהשהייה',
'Completed' => 'הושלם',
'Cancelled' => 'בוטל',
'Refunded' => 'הוחזר',
'Failed' => 'נכשל',
'Custom Statuses' => 'סטטוסים מותאמים',
);
if ( 'he_IL' === $current_language && ! empty( $feed_label_fallbacks ) ) {
add_filter( 'gettext', function( $translated_text, $text, $text_domain ) use ( $domain, $feed_label_fallbacks ) {
if ( $text_domain !== $domain ) {
return $translated_text;
}
if ( isset( $feed_label_fallbacks[ $text ] ) && $translated_text === $text ) {
return $feed_label_fallbacks[ $text ];
}
return $translated_text;
}, 10000, 3 );
}
// Add gettext filter as additional backup to override translations at runtime.
// Use the MO object that we just loaded into globals.
if (isset($GLOBALS['l10n'][$domain]) && is_object($GLOBALS['l10n'][$domain])) {
$mo_object = $GLOBALS['l10n'][$domain];
// Add filter to use our loaded MO object directly.
add_filter('gettext', function($translated_text, $text, $text_domain) use ($domain, $mo_object) {
// Only process our domain.
if ($text_domain !== $domain) {
return $translated_text;
}
// Use translate method from MO object.
if (method_exists($mo_object, 'translate')) {
$translation = $mo_object->translate($text);
if ($translation !== $text) {
return $translation;
}
} elseif (isset($mo_object->entries[$text])) {
$entry = $mo_object->entries[$text];
if (is_object($entry) && isset($entry->translations) && is_array($entry->translations) && !empty($entry->translations)) {
return $entry->translations[0];
}
}
return $translated_text;
}, 9999, 3);
} else {
// Fallback: load translations map.
$translations_map = $this->load_translations_from_mo($mofile);
if (!empty($translations_map)) {
// Store in class property so filter can access it.
$this->cached_translations = $translations_map;
$this->cached_translations_domain = $domain;
add_filter('gettext', function($translated_text, $text, $text_domain) use ($domain, $translations_map) {
// Only process our domain.
if ($text_domain !== $domain) {
return $translated_text;
}
// Check if we have a translation for this text.
if (isset($translations_map[$text]) && !empty($translations_map[$text])) {
return $translations_map[$text];
}
return $translated_text;
}, 9999, 3);
}
}
}
// Support WPML if installed.
if (function_exists('icl_object_id') && function_exists('do_action')) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WPML core global variable
global $sitepress;
if ($sitepress) {
// Convert language code format if needed (e.g., en_US -> en).
$wpml_lang = strstr($current_language, '_', true) ?: $current_language;
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML hook
do_action('wpml_switch_language', $wpml_lang);
}
}
// Support Polylang if installed.
if (function_exists('pll_current_language') && function_exists('PLL')) {
$polylang = PLL();
if ($polylang) {
// Convert language code format if needed.
$pll_lang = strstr($current_language, '_', true) ?: $current_language;
$polylang->curlang = $polylang->model->get_language($pll_lang);
if (!$polylang->curlang) {
// Try full code.
$polylang->curlang = $polylang->model->get_language($current_language);
}
}
}
return true;
}
/**
* Restore original locale/textdomain (optional - textdomain reload is usually sufficient)
*/
private function restore_plugin_language() {
// For textdomain-based approach, we don't need to restore anything.
// The textdomain will be reloaded on next page load with default locale.
// WPML and Polylang don't need explicit restoration as they handle it automatically.
}
/**
* Load translations from MO file and return translation map
*
* @param string $mofile Path to MO file.
* @return array Translation map (original => translated)
*/
private function load_translations_from_mo($mofile) {
$translations = array();
if (!file_exists($mofile)) {
return $translations;
}
if (!class_exists('MO')) {
require_once ABSPATH . WPINC . '/pomo/mo.php';
}
if (class_exists('MO')) {
$mo = new MO();
if ($mo->import_from_file($mofile)) {
// Extract translations from MO object.
// MO class stores entries as: key (singular) => Translation_Entry object.
foreach ($mo->entries as $key => $entry) {
if (is_object($entry) && isset($entry->translations) && is_array($entry->translations) && !empty($entry->translations)) {
// Use the key (original text) as the key, and first translation as the value.
$translations[$key] = $entry->translations[0];
} elseif (is_string($entry)) {
// Sometimes entries might be stored as strings.
$translations[$key] = $entry;
}
}
}
}
return $translations;
}
}