');
}
});
} 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) {
\$groupSelect.empty();
var selectGroupText = '{$select_group}';
\$groupSelect.append(jQuery('').attr('value', '').text(selectGroupText));
jQuery.each(response.data, function(index, group) {
var option = jQuery('').attr('value', group.id).text(group.name);
if (group.id == originalValue) {
option.attr('selected', 'selected');
}
\$groupSelect.append(option);
});
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('
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;
}
}