Plugin Directory

Changeset 3765041


Ignore:
Timestamp:
10/25/2026 06:01:43 PM (29 hours ago)
Author:
devrayhan
Message:

Version 1.1.0

Location:
nexoy-order-tracker-for-woocommerce
Files:
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • nexoy-order-tracker-for-woocommerce/tags/1.1.0/assets/js/script.js

    r3467879 r3765041  
    11jQuery(document).ready(function($) {
     2    const i18n = nexoy_ot_params.i18n || {};
     3
     4    // Escape any text before it is placed into HTML
     5    function esc(value) {
     6        return $('<div>').text(value == null ? '' : String(value)).html();
     7    }
     8
    29    // Tab Switching Logic
    310    const $tabs = $('input[name="tracker_type"]');
    … …  
    2532    $form.on('submit', function(e) {
    2633        e.preventDefault();
    27        
    28         $resultContainer.html('<p style="text-align:center; padding:20px; font-weight:500;">Fetching order details...</p>').fadeIn();
     34
     35        $resultContainer.html('<p style="text-align:center; padding:20px; font-weight:500;">' + esc(i18n.fetching) + '</p>').fadeIn();
    2936
    3037        const trackerType = $('input[name="tracker_type"]:checked').val();
    … …  
    4350                renderNexoyTimeline(response.data);
    4451            } else {
    45                 $resultContainer.html(`<p style="color:#ff4d4d; text-align:center; padding:20px; font-weight:500;">${response.data.message}</p>`);
     52                const message = response.data && response.data.message ? response.data.message : i18n.conn_error;
     53                $resultContainer.html('<p style="color:#ff4d4d; text-align:center; padding:20px; font-weight:500;">' + esc(message) + '</p>');
    4654            }
    4755        }).fail(function() {
    48             $resultContainer.html('<p style="color:#ff4d4d; text-align:center; padding:20px;">Connection error. Please try again.</p>');
     56            $resultContainer.html('<p style="color:#ff4d4d; text-align:center; padding:20px;">' + esc(i18n.conn_error) + '</p>');
    4957        });
    5058    });
    … …  
    5361    function renderNexoyTimeline(data) {
    5462        let html = '<div class="nexoy-ot-timeline-container">';
    55        
     63
    5664        if (data.courier && data.courier.name) {
     65            const url = data.courier.url && /^https?:\/\//i.test(data.courier.url) ? data.courier.url : '';
    5766            html += `
    5867                <div class="nexoy-ot-courier-card">
    5968                    <div class="nexoy-ot-courier-info">
    60                         <span class="nexoy-ot-label">Carrier:</span>
    61                         <span class="nexoy-ot-val">${data.courier.name}</span>
     69                        <span class="nexoy-ot-label">${esc(i18n.carrier)}</span>
     70                        <span class="nexoy-ot-val">${esc(data.courier.name)}</span>
    6271                    </div>
    63                     ${data.courier.url ? `<a href="${data.courier.url}" target="_blank" class="nexoy-ot-track-btn">Track Order</a>` : ''}
     72                    ${url ? `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer" class="nexoy-ot-track-btn">${esc(i18n.track_order)}</a>` : ''}
    6473                </div>
    6574            `;
    … …  
    6877        if (data.history && data.history.length > 0) {
    6978            html += '<div class="nexoy-ot-v-timeline">';
    70             const history = data.history.reverse();
    71             history.forEach((item, index) => {
    72                 const date = new Date(item.timestamp * 1000);
    73                 const timeStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' +
    74                               date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
    75                 const statusTitle = item.new_status.replace(/-/g, ' ').toUpperCase();
     79            data.history.forEach((item, index) => {
     80                const message = (i18n.updated_to || '%s').replace('%s', item.status);
    7681                html += `
    7782                    <div class="nexoy-ot-t-item" style="animation-delay: ${index * 0.1}s">
    78                         <div class="nexoy-ot-t-meta">${timeStr}</div>
     83                        <div class="nexoy-ot-t-meta">${esc(item.date)}</div>
    7984                        <div class="nexoy-ot-t-content">
    8085                            <div class="nexoy-ot-t-marker"></div>
    8186                            <div class="nexoy-ot-t-text">
    82                                 <h5>${statusTitle}</h5>
    83                                 <p>Your order has been updated to ${item.new_status}.</p>
     87                                <h5>${esc(item.status)}</h5>
     88                                <p>${esc(message)}</p>
    8489                            </div>
    8590                        </div>
    … …  
    8893            });
    8994            html += '</div>';
     95        } else if (data.order_status) {
     96            html += `<p class="nexoy-ot-current-status">${esc(i18n.current)} <strong>${esc(data.order_status)}</strong></p>`;
    9097        }
    9198        html += '</div>';
  • nexoy-order-tracker-for-woocommerce/tags/1.1.0/includes/admin-settings.php

    r3467879 r3765041  
    88    add_submenu_page(
    99        'woocommerce',
    10         'Nexoy Order Tracker',
    11         'Nexoy Order Tracker',
     10        __( 'Nexoy Order Tracker', 'nexoy-order-tracker-for-woocommerce' ),
     11        __( 'Nexoy Order Tracker', 'nexoy-order-tracker-for-woocommerce' ),
    1212        'manage_woocommerce',
    1313        'nexoy-order-tracker',
    … …  
    3838        <div class="nexoy-header">
    3939            <div class="nexoy-brand"><h1>Nexoy <span>Order Tracker</span></h1></div>
    40             <div class="nexoy-version"><span style="background: #e2e8f0; padding: 5px 12px; border-radius: 20px; font-size: 12px; font-weight: 700;">v 1.0.0</span></div>
     40            <div class="nexoy-version"><span style="background: #e2e8f0; padding: 5px 12px; border-radius: 20px; font-size: 12px; font-weight: 700;">v <?php echo esc_html( NEXOY_OT_VERSION ); ?></span></div>
    4141        </div>
    4242        <div class="nexoy-grid">
    … …  
    4545                    <?php settings_fields( 'nexoy_ot_settings_group' ); ?>
    4646                    <div class="nexoy-card">
    47                         <div class="nexoy-card-header"><h3>🎨 Visual Customization</h3></div>
     47                        <div class="nexoy-card-header"><h3><span class="dashicons dashicons-admin-appearance"></span> <?php esc_html_e( 'Visual Customization', 'nexoy-order-tracker-for-woocommerce' ); ?></h3></div>
    4848                        <div class="nexoy-card-body">
    49                             <p style="margin-top:0; color: #64748b;">Choose the base theme for your tracking interface.</p>
     49                            <p style="margin-top:0; color: #64748b;"><?php esc_html_e( 'Choose the base theme for your tracking interface.', 'nexoy-order-tracker-for-woocommerce' ); ?></p>
    5050                            <?php do_settings_sections( 'nexoy-order-tracker' ); ?>
    5151                        </div>
    5252                    </div>
    5353                    <div class="nexoy-card">
    54                         <div class="nexoy-card-header"><h3>🚀 Ready to Launch?</h3></div>
     54                        <div class="nexoy-card-header"><h3><span class="dashicons dashicons-shortcode"></span> <?php esc_html_e( 'Ready to Launch?', 'nexoy-order-tracker-for-woocommerce' ); ?></h3></div>
    5555                        <div class="nexoy-card-body">
    5656                            <div class="nexoy-guide-box">
    57                                 <strong style="color: #0c4a6e;">Integration Shortcode</strong>
    58                                 <p style="font-size: 13px; color: #334155; margin: 5px 0 15px;">Copy and paste this shortcode to any page where you want the tracker to appear.</p>
     57                                <strong style="color: #0c4a6e;"><?php esc_html_e( 'Integration Shortcode', 'nexoy-order-tracker-for-woocommerce' ); ?></strong>
     58                                <p style="font-size: 13px; color: #334155; margin: 5px 0 15px;"><?php esc_html_e( 'Copy and paste this shortcode to any page where you want the tracker to appear.', 'nexoy-order-tracker-for-woocommerce' ); ?></p>
    5959                                <div class="nexoy-shortcode-display"><span>[nexoy_order_tracker]</span></div>
    6060                            </div>
    61                             <?php submit_button( 'Save All Changes', 'nexoy-btn-save' ); ?>
     61                            <?php submit_button( __( 'Save All Changes', 'nexoy-order-tracker-for-woocommerce' ), 'nexoy-btn-save' ); ?>
    6262                        </div>
    6363                    </div>
    … …  
    6666            <div class="nexoy-sidebar">
    6767                <div class="nexoy-card">
    68                     <div class="nexoy-card-header"><h3>ℹ️ Plugin Info</h3></div>
     68                    <div class="nexoy-card-header"><h3><span class="dashicons dashicons-info-outline"></span> <?php esc_html_e( 'Plugin Info', 'nexoy-order-tracker-for-woocommerce' ); ?></h3></div>
    6969                    <div class="nexoy-card-body">
    70                         <p style="font-size: 13px; line-height: 1.6; color: #64748b;"><strong>Nexoy Order Tracker</strong> is built for high-performance WooCommerce stores. If you need help, contact our support team at <strong>thenexoy.com</strong>.</p>
     70                        <p style="font-size: 13px; line-height: 1.6; color: #64748b;"><?php echo wp_kses( __( '<strong>Nexoy Order Tracker</strong> is built for high-performance WooCommerce stores. If you need help, contact our support team at <strong>thenexoy.com</strong>.', 'nexoy-order-tracker-for-woocommerce' ), array( 'strong' => array() ) ); ?></p>
    7171                    </div>
    7272                </div>
    … …  
    8080add_action( 'admin_init', 'nexoy_ot_register_settings' );
    8181function nexoy_ot_register_settings() {
    82     register_setting( 'nexoy_ot_settings_group', 'nexoy_ot_theme_preference', 'nexoy_ot_sanitize_theme_choice' );
    83     add_settings_section( 'nexoy_ot_main_section', 'Display Settings', '__return_empty_string', 'nexoy-order-tracker' );
    84     add_settings_field( 'nexoy_ot_theme_preference', 'Tracker Theme', 'nexoy_ot_theme_radio_callback', 'nexoy-order-tracker', 'nexoy_ot_main_section' );
     82    register_setting( 'nexoy_ot_settings_group', 'nexoy_ot_theme_preference', array( 'type' => 'string', 'sanitize_callback' => 'nexoy_ot_sanitize_theme_choice', 'default' => 'light' ) );
     83    add_settings_section( 'nexoy_ot_main_section', __( 'Display Settings', 'nexoy-order-tracker-for-woocommerce' ), '__return_empty_string', 'nexoy-order-tracker' );
     84    add_settings_field( 'nexoy_ot_theme_preference', __( 'Tracker Theme', 'nexoy-order-tracker-for-woocommerce' ), 'nexoy_ot_theme_radio_callback', 'nexoy-order-tracker', 'nexoy_ot_main_section' );
    8585}
    8686
    … …  
    9292    $theme = get_option( 'nexoy_ot_theme_preference', 'light' );
    9393    ?>
    94     <label><input type="radio" name="nexoy_ot_theme_preference" value="light" <?php checked( $theme, 'light' ); ?> /> Light Theme (Default)</label><br><br>
    95     <label><input type="radio" name="nexoy_ot_theme_preference" value="dark" <?php checked( $theme, 'dark' ); ?> /> Dark Theme (Premium Look)</label>
     94    <label><input type="radio" name="nexoy_ot_theme_preference" value="light" <?php checked( $theme, 'light' ); ?> /> <?php esc_html_e( 'Light Theme (Default)', 'nexoy-order-tracker-for-woocommerce' ); ?></label><br><br>
     95    <label><input type="radio" name="nexoy_ot_theme_preference" value="dark" <?php checked( $theme, 'dark' ); ?> /> <?php esc_html_e( 'Dark Theme (Premium Look)', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
    9696    <?php
    9797}
  • nexoy-order-tracker-for-woocommerce/tags/1.1.0/includes/ajax-handler.php

    r3467879 r3765041  
    66add_action( 'wp_ajax_nopriv_nexoy_ot_search_order', 'nexoy_ot_search_order_handler' );
    77
     8/**
     9 * Rate limit: max failed lookups per visitor IP within the window.
     10 */
     11define( 'NEXOY_OT_MAX_ATTEMPTS', 10 );
     12define( 'NEXOY_OT_ATTEMPT_WINDOW', 15 * MINUTE_IN_SECONDS );
     13
     14function nexoy_ot_attempts_key() {
     15    $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : 'unknown';
     16    return 'nexoy_ot_fail_' . md5( $ip );
     17}
     18
     19function nexoy_ot_register_failed_attempt() {
     20    $key      = nexoy_ot_attempts_key();
     21    $attempts = (int) get_transient( $key );
     22    set_transient( $key, $attempts + 1, NEXOY_OT_ATTEMPT_WINDOW );
     23}
     24
    825function nexoy_ot_search_order_handler() {
    926    check_ajax_referer( 'nexoy_ot_search_nonce', 'security' );
     27
     28    if ( (int) get_transient( nexoy_ot_attempts_key() ) >= NEXOY_OT_MAX_ATTEMPTS ) {
     29        wp_send_json_error( array( 'message' => __( 'Too many attempts. Please try again in a few minutes.', 'nexoy-order-tracker-for-woocommerce' ) ) );
     30    }
    1031
    1132    $order_id      = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
    … …  
    1435    $billing_phone = isset( $_POST['billing_phone'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_phone'] ) ) : '';
    1536
    16     if ( ! $order_id ) wp_send_json_error( array( 'message' => 'Please provide a valid Order ID.' ) );
     37    if ( ! $order_id ) wp_send_json_error( array( 'message' => __( 'Please provide a valid Order ID.', 'nexoy-order-tracker-for-woocommerce' ) ) );
     38
     39    // Same message for "not found" and "no match", so order IDs cannot be probed
     40    $no_match = __( 'Order ID and credentials do not match.', 'nexoy-order-tracker-for-woocommerce' );
    1741
    1842    $order = wc_get_order( $order_id );
    19     if ( ! $order || $order->get_id() !== $order_id ) wp_send_json_error( array( 'message' => 'Order not found.' ) );
     43    if ( ! $order || $order->get_id() !== $order_id ) {
     44        nexoy_ot_register_failed_attempt();
     45        wp_send_json_error( array( 'message' => $no_match ) );
     46    }
    2047
    2148    $is_valid_user = false;
    2249    if ( 'email' === $tracker_type ) {
    23         if ( strtolower( $order->get_billing_email() ) === strtolower( $billing_email ) ) $is_valid_user = true;
     50        if ( ! empty( $billing_email ) && strtolower( $order->get_billing_email() ) === strtolower( $billing_email ) ) $is_valid_user = true;
    2451    } else {
    25         $saved_phone   = preg_replace( '/[^0-9]/', '', $order->get_billing_phone() );
     52        $saved_phone    = preg_replace( '/[^0-9]/', '', $order->get_billing_phone() );
    2653        $provided_phone = preg_replace( '/[^0-9]/', '', $billing_phone );
    2754        if ( ! empty( $provided_phone ) && $saved_phone === $provided_phone ) $is_valid_user = true;
    2855    }
    2956
    30     if ( ! $is_valid_user ) wp_send_json_error( array( 'message' => 'Order ID and credentials do not match.' ) );
     57    if ( ! $is_valid_user ) {
     58        nexoy_ot_register_failed_attempt();
     59        wp_send_json_error( array( 'message' => $no_match ) );
     60    }
    3161
    3262    $history      = $order->get_meta( '_nexoy_ot_activity_log', true );
    … …  
    3464    $tracking_url = $order->get_meta( '_nexoy_ot_tracking_url', true );
    3565
     66    // Send ready-to-display labels and dates (site timezone, site date format)
     67    $timeline = array();
     68    if ( is_array( $history ) ) {
     69        foreach ( $history as $log ) {
     70            if ( ! isset( $log['event_type'], $log['new_status'], $log['timestamp'] ) || 'status_change' !== $log['event_type'] ) {
     71                continue;
     72            }
     73            $timeline[] = array(
     74                'status' => wc_get_order_status_name( $log['new_status'] ),
     75                'date'   => date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $log['timestamp'] ),
     76            );
     77        }
     78    }
     79
    3680    $response_data = array(
    3781        'order_status' => wc_get_order_status_name( $order->get_status() ),
    38         'history'      => $history,
    39         'courier'      => array( 'name' => $courier_name, 'url'  => $tracking_url )
     82        'history'      => array_reverse( $timeline ),
     83        'courier'      => array(
     84            'name' => $courier_name,
     85            'url'  => $tracking_url ? esc_url( $tracking_url, array( 'http', 'https' ) ) : '',
     86        ),
    4087    );
    4188
  • nexoy-order-tracker-for-woocommerce/tags/1.1.0/includes/frontend-shortcode.php

    r3467879 r3765041  
    66
    77function nexoy_ot_render_search_form() {
     8    nexoy_ot_enqueue_assets();
     9
    810    ob_start();
    9    
     11
    1012    $current_user = wp_get_current_user();
    1113    $user_email   = $current_user->exists() ? $current_user->user_email : '';
    … …  
    1517    <div class="nexoy-ot-wrapper <?php echo esc_attr( $theme_class ); ?>">
    1618        <div class="nexoy-ot-toggle-area">
    17             <p class="nexoy-ot-label-main">How would you like to track your order?</p>
     19            <p class="nexoy-ot-label-main"><?php esc_html_e( 'How would you like to track your order?', 'nexoy-order-tracker-for-woocommerce' ); ?></p>
    1820            <div class="nexoy-ot-tabs">
    1921                <label class="nexoy-ot-tab">
    2022                    <input type="radio" name="tracker_type" value="email" checked>
    21                     <span>Track with Email</span>
     23                    <span><?php esc_html_e( 'Track with Email', 'nexoy-order-tracker-for-woocommerce' ); ?></span>
    2224                </label>
    2325                <label class="nexoy-ot-tab">
    2426                    <input type="radio" name="tracker_type" value="phone">
    25                     <span>Track with Phone</span>
     27                    <span><?php esc_html_e( 'Track with Phone', 'nexoy-order-tracker-for-woocommerce' ); ?></span>
    2628                </label>
    2729            </div>
    … …  
    3032        <form id="nexoy-ot-form" class="nexoy-ot-form">
    3133            <div class="nexoy-ot-input-group">
    32                 <label>Order ID</label>
    33                 <input type="text" name="order_id" placeholder="e.g. 12345" required>
     34                <label><?php esc_html_e( 'Order ID', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
     35                <input type="text" name="order_id" placeholder="<?php esc_attr_e( 'e.g. 12345', 'nexoy-order-tracker-for-woocommerce' ); ?>" required>
    3436            </div>
    3537            <div id="nexoy-ot-email-field" class="nexoy-ot-input-group">
    36                 <label>Billing Email</label>
     38                <label><?php esc_html_e( 'Billing Email', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
    3739                <input type="email" name="billing_email" value="<?php echo esc_attr( $user_email ); ?>" placeholder="you@example.com">
    3840            </div>
    3941            <div id="nexoy-ot-phone-field" class="nexoy-ot-input-group" style="display:none;">
    40                 <label>Billing Phone</label>
    41                 <input type="text" name="billing_phone" value="<?php echo esc_attr( $user_phone ); ?>" placeholder="e.g. +880123456789">
     42                <label><?php esc_html_e( 'Billing Phone', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
     43                <input type="text" name="billing_phone" value="<?php echo esc_attr( $user_phone ); ?>" placeholder="<?php esc_attr_e( 'e.g. +880123456789', 'nexoy-order-tracker-for-woocommerce' ); ?>">
    4244            </div>
    43             <button type="submit" class="nexoy-ot-submit">Track Order Now</button>
     45            <button type="submit" class="nexoy-ot-submit"><?php esc_html_e( 'Track Order Now', 'nexoy-order-tracker-for-woocommerce' ); ?></button>
    4446        </form>
    4547
  • nexoy-order-tracker-for-woocommerce/tags/1.1.0/includes/order-meta.php

    r3467879 r3765041  
    2727function nexoy_ot_add_order_meta_box() {
    2828    $screen = class_exists( '\Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController' ) && wc_get_container()->get( \Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ? wc_get_page_screen_id( 'shop-order' ) : 'shop_order';
    29     add_meta_box( 'nexoy_ot_tracker_meta_box', 'Nexoy Order Tracker', 'nexoy_ot_tracker_meta_box_callback', $screen, 'side', 'default' );
     29    add_meta_box( 'nexoy_ot_tracker_meta_box', __( 'Nexoy Order Tracker', 'nexoy-order-tracker-for-woocommerce' ), 'nexoy_ot_tracker_meta_box_callback', $screen, 'side', 'default' );
    3030}
    3131
    … …  
    4040    wp_nonce_field( 'nexoy_ot_save_meta_box', 'nexoy_ot_meta_box_nonce' );
    4141
    42     echo '<p><strong>Courier Name:</strong><br><input type="text" name="nexoy_ot_courier_name" value="' . esc_attr( $courier_name ) . '" style="width:100%;" placeholder="e.g. FedEx, DHL" /></p>';
    43     echo '<p><strong>Tracking URL:</strong><br><input type="url" name="nexoy_ot_tracking_url" value="' . esc_attr( $tracking_url ) . '" style="width:100%;" placeholder="https://..." /></p>';
     42    echo '<p><strong>' . esc_html__( 'Courier Name:', 'nexoy-order-tracker-for-woocommerce' ) . '</strong><br><input type="text" name="nexoy_ot_courier_name" value="' . esc_attr( $courier_name ) . '" style="width:100%;" placeholder="' . esc_attr__( 'e.g. FedEx, DHL', 'nexoy-order-tracker-for-woocommerce' ) . '" /></p>';
     43    echo '<p><strong>' . esc_html__( 'Tracking URL:', 'nexoy-order-tracker-for-woocommerce' ) . '</strong><br><input type="url" name="nexoy_ot_tracking_url" value="' . esc_attr( $tracking_url ) . '" style="width:100%;" placeholder="https://..." /></p>';
    4444
    45     echo '<hr><p><strong>Activity Log:</strong></p>';
     45    echo '<hr><p><strong>' . esc_html__( 'Activity Log:', 'nexoy-order-tracker-for-woocommerce' ) . '</strong></p>';
    4646    if ( is_array( $history ) && ! empty( $history ) ) {
    4747        echo '<ul style="margin:0; padding-left:15px; font-size:12px;">';
    4848        foreach ( $history as $log ) {
    4949            if ( isset( $log['event_type'] ) && 'status_change' === $log['event_type'] ) {
    50                 $time = date_i18n( 'M d, Y h:i A', $log['timestamp'] );
     50                $time = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $log['timestamp'] );
    5151                $status_name = wc_get_order_status_name( $log['new_status'] );
    5252                echo '<li>' . esc_html( $status_name ) . ' - <em>' . esc_html( $time ) . '</em></li>';
    … …  
    5555        echo '</ul>';
    5656    } else {
    57         echo '<p style="color:#777;">No activity recorded yet.</p>';
     57        echo '<p style="color:#777;">' . esc_html__( 'No activity recorded yet.', 'nexoy-order-tracker-for-woocommerce' ) . '</p>';
    5858    }
    5959}
  • nexoy-order-tracker-for-woocommerce/tags/1.1.0/nexoy-order-tracker.php

    r3467879 r3765041  
    44 * Plugin URI:        https://thenexoy.com/
    55 * Description:       A premium, high-performance order tracking system for WooCommerce. Allow customers to track their orders via a luxury timeline UI using their Order ID and Email/Phone.
    6  * Version:           1.0.0
     6 * Version:           1.1.0
    77 * Requires at least: 5.8
    88 * Requires PHP:      7.4
    9  * Author:            Rayhan Chowdhury
    10  * Author URI:        https://prorayhan.com
     9 * Requires Plugins:  woocommerce
     10 * WC requires at least: 6.0
     11 * WC tested up to:   11.0
     12 * Author:            The Nexoy
     13 * Author URI:        https://thenexoy.com/
    1114 * License:           GPL v2 or later
    1215 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2023
    2124// Define Plugin Constants
    22 define( 'NEXOY_OT_VERSION', '1.0.1' );
     25define( 'NEXOY_OT_VERSION', '1.1.0' );
    2326define( 'NEXOY_OT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    2427define( 'NEXOY_OT_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     28
     29/**
     30 * Declare compatibility with WooCommerce HPOS and Cart/Checkout blocks.
     31 */
     32add_action( 'before_woocommerce_init', 'nexoy_ot_declare_wc_compatibility' );
     33
     34function nexoy_ot_declare_wc_compatibility() {
     35    if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) {
     36        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
     37        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true );
     38    }
     39}
    2540
    2641/**
    … …  
    3853    require_once NEXOY_OT_PLUGIN_DIR . 'includes/frontend-shortcode.php';
    3954}
    40 // FIXED ISSUE 1: Priority 99 ensures WooCommerce loads before our plugin
     55// Priority 99 ensures WooCommerce loads before our plugin
    4156add_action( 'plugins_loaded', 'nexoy_ot_init_plugin', 99 );
    4257
    4358/**
    44  * Enqueue Frontend Scripts and Styles unconditionally for Page Builders
     59 * Register frontend assets. They are enqueued only where the tracker is shown.
    4560 */
    46 function nexoy_ot_enqueue_scripts() {
    47     // FIXED ISSUE 2: Removed strict shortcode check to ensure CSS loads in Elementor/Gutenberg perfectly
    48     wp_enqueue_style( 'nexoy-ot-style', NEXOY_OT_PLUGIN_URL . 'assets/css/style.css', array(), NEXOY_OT_VERSION );
    49     wp_enqueue_script( 'nexoy-ot-script', NEXOY_OT_PLUGIN_URL . 'assets/js/script.js', array( 'jquery' ), NEXOY_OT_VERSION, true );
    50    
     61function nexoy_ot_register_scripts() {
     62    wp_register_style( 'nexoy-ot-style', NEXOY_OT_PLUGIN_URL . 'assets/css/style.css', array(), NEXOY_OT_VERSION );
     63    wp_register_script( 'nexoy-ot-script', NEXOY_OT_PLUGIN_URL . 'assets/js/script.js', array( 'jquery' ), NEXOY_OT_VERSION, true );
     64
    5165    wp_localize_script( 'nexoy-ot-script', 'nexoy_ot_params', array(
    5266        'ajax_url' => admin_url( 'admin-ajax.php' ),
    53         'nonce'    => wp_create_nonce( 'nexoy_ot_search_nonce' )
     67        'nonce'    => wp_create_nonce( 'nexoy_ot_search_nonce' ),
     68        'i18n'     => array(
     69            'fetching'     => __( 'Fetching order details...', 'nexoy-order-tracker-for-woocommerce' ),
     70            'conn_error'   => __( 'Connection error. Please try again.', 'nexoy-order-tracker-for-woocommerce' ),
     71            'carrier'      => __( 'Carrier:', 'nexoy-order-tracker-for-woocommerce' ),
     72            'track_order'  => __( 'Track Order', 'nexoy-order-tracker-for-woocommerce' ),
     73            'current'      => __( 'Current status:', 'nexoy-order-tracker-for-woocommerce' ),
     74            /* translators: %s: order status name */
     75            'updated_to'   => __( 'Your order has been updated to %s.', 'nexoy-order-tracker-for-woocommerce' ),
     76        ),
    5477    ) );
     78
     79    // Load early on regular pages that contain the shortcode (avoids a flash of unstyled content)
     80    if ( is_singular() ) {
     81        $post = get_post();
     82        if ( $post && has_shortcode( $post->post_content, 'nexoy_order_tracker' ) ) {
     83            nexoy_ot_enqueue_assets();
     84        }
     85    }
    5586}
    56 add_action( 'wp_enqueue_scripts', 'nexoy_ot_enqueue_scripts' );
     87add_action( 'wp_enqueue_scripts', 'nexoy_ot_register_scripts' );
     88
     89/**
     90 * Enqueue frontend assets (also called from the shortcode, for page builders).
     91 */
     92function nexoy_ot_enqueue_assets() {
     93    wp_enqueue_style( 'nexoy-ot-style' );
     94    wp_enqueue_script( 'nexoy-ot-script' );
     95}
    5796
    5897
    … …  
    63102
    64103function nexoy_ot_add_settings_link( $links ) {
    65     // Create the settings link
    66     $settings_url = admin_url( 'admin.php?page=nexoy-order-tracker' );
    67     $settings_link = '<a href="' . esc_url( $settings_url ) . '" style="font-weight: bold; color: #2271b1;">Settings</a>';
    68    
    69     // Add it to the beginning of the links array
     104    $settings_url  = admin_url( 'admin.php?page=nexoy-order-tracker' );
     105    $settings_link = '<a href="' . esc_url( $settings_url ) . '" style="font-weight: bold; color: #2271b1;">' . esc_html__( 'Settings', 'nexoy-order-tracker-for-woocommerce' ) . '</a>';
     106
    70107    array_unshift( $links, $settings_link );
    71    
     108
    72109    return $links;
    73110}
  • nexoy-order-tracker-for-woocommerce/tags/1.1.0/readme.txt

    r3467879 r3765041  
    44Tags: woocommerce, order tracker, order tracking, shipment tracking, tracking
    55Requires at least: 5.8
    6 Tested up to: 6.9
    7 Stable tag: 1.0.0
     6Tested up to: 7.1
     7Stable tag: 1.1.0
    88Requires PHP: 7.4
    99License: GPLv2 or later
    … …  
    5959== Changelog ==
    6060
     61= 1.1.0 =
     62* Security: Courier details are now escaped before display.
     63* Security: Failed lookups are rate limited per visitor to stop order probing.
     64* Improved: Timeline shows proper status names and dates in your site's timezone and date format.
     65* Improved: CSS/JS load only on pages that show the tracker.
     66* Improved: Declared compatibility with WooCommerce HPOS and Cart/Checkout blocks.
     67* Improved: All texts are now translatable; admin icons use Dashicons.
     68* Fixed: Version number mismatch on the settings page.
     69* Tested with WordPress 7.1.
     70
    6171= 1.0.0 =
    6272* Initial Release: The grand launch of Nexoy Order Tracker for WooCommerce.
     73
     74== Upgrade Notice ==
     75
     76= 1.1.0 =
     77Security and display improvements. Recommended update for all users.
  • nexoy-order-tracker-for-woocommerce/trunk/assets/js/script.js

    r3467879 r3765041  
    11jQuery(document).ready(function($) {
     2    const i18n = nexoy_ot_params.i18n || {};
     3
     4    // Escape any text before it is placed into HTML
     5    function esc(value) {
     6        return $('<div>').text(value == null ? '' : String(value)).html();
     7    }
     8
    29    // Tab Switching Logic
    310    const $tabs = $('input[name="tracker_type"]');
    … …  
    2532    $form.on('submit', function(e) {
    2633        e.preventDefault();
    27        
    28         $resultContainer.html('<p style="text-align:center; padding:20px; font-weight:500;">Fetching order details...</p>').fadeIn();
     34
     35        $resultContainer.html('<p style="text-align:center; padding:20px; font-weight:500;">' + esc(i18n.fetching) + '</p>').fadeIn();
    2936
    3037        const trackerType = $('input[name="tracker_type"]:checked').val();
    … …  
    4350                renderNexoyTimeline(response.data);
    4451            } else {
    45                 $resultContainer.html(`<p style="color:#ff4d4d; text-align:center; padding:20px; font-weight:500;">${response.data.message}</p>`);
     52                const message = response.data && response.data.message ? response.data.message : i18n.conn_error;
     53                $resultContainer.html('<p style="color:#ff4d4d; text-align:center; padding:20px; font-weight:500;">' + esc(message) + '</p>');
    4654            }
    4755        }).fail(function() {
    48             $resultContainer.html('<p style="color:#ff4d4d; text-align:center; padding:20px;">Connection error. Please try again.</p>');
     56            $resultContainer.html('<p style="color:#ff4d4d; text-align:center; padding:20px;">' + esc(i18n.conn_error) + '</p>');
    4957        });
    5058    });
    … …  
    5361    function renderNexoyTimeline(data) {
    5462        let html = '<div class="nexoy-ot-timeline-container">';
    55        
     63
    5664        if (data.courier && data.courier.name) {
     65            const url = data.courier.url && /^https?:\/\//i.test(data.courier.url) ? data.courier.url : '';
    5766            html += `
    5867                <div class="nexoy-ot-courier-card">
    5968                    <div class="nexoy-ot-courier-info">
    60                         <span class="nexoy-ot-label">Carrier:</span>
    61                         <span class="nexoy-ot-val">${data.courier.name}</span>
     69                        <span class="nexoy-ot-label">${esc(i18n.carrier)}</span>
     70                        <span class="nexoy-ot-val">${esc(data.courier.name)}</span>
    6271                    </div>
    63                     ${data.courier.url ? `<a href="${data.courier.url}" target="_blank" class="nexoy-ot-track-btn">Track Order</a>` : ''}
     72                    ${url ? `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer" class="nexoy-ot-track-btn">${esc(i18n.track_order)}</a>` : ''}
    6473                </div>
    6574            `;
    … …  
    6877        if (data.history && data.history.length > 0) {
    6978            html += '<div class="nexoy-ot-v-timeline">';
    70             const history = data.history.reverse();
    71             history.forEach((item, index) => {
    72                 const date = new Date(item.timestamp * 1000);
    73                 const timeStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' +
    74                               date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
    75                 const statusTitle = item.new_status.replace(/-/g, ' ').toUpperCase();
     79            data.history.forEach((item, index) => {
     80                const message = (i18n.updated_to || '%s').replace('%s', item.status);
    7681                html += `
    7782                    <div class="nexoy-ot-t-item" style="animation-delay: ${index * 0.1}s">
    78                         <div class="nexoy-ot-t-meta">${timeStr}</div>
     83                        <div class="nexoy-ot-t-meta">${esc(item.date)}</div>
    7984                        <div class="nexoy-ot-t-content">
    8085                            <div class="nexoy-ot-t-marker"></div>
    8186                            <div class="nexoy-ot-t-text">
    82                                 <h5>${statusTitle}</h5>
    83                                 <p>Your order has been updated to ${item.new_status}.</p>
     87                                <h5>${esc(item.status)}</h5>
     88                                <p>${esc(message)}</p>
    8489                            </div>
    8590                        </div>
    … …  
    8893            });
    8994            html += '</div>';
     95        } else if (data.order_status) {
     96            html += `<p class="nexoy-ot-current-status">${esc(i18n.current)} <strong>${esc(data.order_status)}</strong></p>`;
    9097        }
    9198        html += '</div>';
  • nexoy-order-tracker-for-woocommerce/trunk/includes/admin-settings.php

    r3467879 r3765041  
    88    add_submenu_page(
    99        'woocommerce',
    10         'Nexoy Order Tracker',
    11         'Nexoy Order Tracker',
     10        __( 'Nexoy Order Tracker', 'nexoy-order-tracker-for-woocommerce' ),
     11        __( 'Nexoy Order Tracker', 'nexoy-order-tracker-for-woocommerce' ),
    1212        'manage_woocommerce',
    1313        'nexoy-order-tracker',
    … …  
    3838        <div class="nexoy-header">
    3939            <div class="nexoy-brand"><h1>Nexoy <span>Order Tracker</span></h1></div>
    40             <div class="nexoy-version"><span style="background: #e2e8f0; padding: 5px 12px; border-radius: 20px; font-size: 12px; font-weight: 700;">v 1.0.0</span></div>
     40            <div class="nexoy-version"><span style="background: #e2e8f0; padding: 5px 12px; border-radius: 20px; font-size: 12px; font-weight: 700;">v <?php echo esc_html( NEXOY_OT_VERSION ); ?></span></div>
    4141        </div>
    4242        <div class="nexoy-grid">
    … …  
    4545                    <?php settings_fields( 'nexoy_ot_settings_group' ); ?>
    4646                    <div class="nexoy-card">
    47                         <div class="nexoy-card-header"><h3>🎨 Visual Customization</h3></div>
     47                        <div class="nexoy-card-header"><h3><span class="dashicons dashicons-admin-appearance"></span> <?php esc_html_e( 'Visual Customization', 'nexoy-order-tracker-for-woocommerce' ); ?></h3></div>
    4848                        <div class="nexoy-card-body">
    49                             <p style="margin-top:0; color: #64748b;">Choose the base theme for your tracking interface.</p>
     49                            <p style="margin-top:0; color: #64748b;"><?php esc_html_e( 'Choose the base theme for your tracking interface.', 'nexoy-order-tracker-for-woocommerce' ); ?></p>
    5050                            <?php do_settings_sections( 'nexoy-order-tracker' ); ?>
    5151                        </div>
    5252                    </div>
    5353                    <div class="nexoy-card">
    54                         <div class="nexoy-card-header"><h3>🚀 Ready to Launch?</h3></div>
     54                        <div class="nexoy-card-header"><h3><span class="dashicons dashicons-shortcode"></span> <?php esc_html_e( 'Ready to Launch?', 'nexoy-order-tracker-for-woocommerce' ); ?></h3></div>
    5555                        <div class="nexoy-card-body">
    5656                            <div class="nexoy-guide-box">
    57                                 <strong style="color: #0c4a6e;">Integration Shortcode</strong>
    58                                 <p style="font-size: 13px; color: #334155; margin: 5px 0 15px;">Copy and paste this shortcode to any page where you want the tracker to appear.</p>
     57                                <strong style="color: #0c4a6e;"><?php esc_html_e( 'Integration Shortcode', 'nexoy-order-tracker-for-woocommerce' ); ?></strong>
     58                                <p style="font-size: 13px; color: #334155; margin: 5px 0 15px;"><?php esc_html_e( 'Copy and paste this shortcode to any page where you want the tracker to appear.', 'nexoy-order-tracker-for-woocommerce' ); ?></p>
    5959                                <div class="nexoy-shortcode-display"><span>[nexoy_order_tracker]</span></div>
    6060                            </div>
    61                             <?php submit_button( 'Save All Changes', 'nexoy-btn-save' ); ?>
     61                            <?php submit_button( __( 'Save All Changes', 'nexoy-order-tracker-for-woocommerce' ), 'nexoy-btn-save' ); ?>
    6262                        </div>
    6363                    </div>
    … …  
    6666            <div class="nexoy-sidebar">
    6767                <div class="nexoy-card">
    68                     <div class="nexoy-card-header"><h3>ℹ️ Plugin Info</h3></div>
     68                    <div class="nexoy-card-header"><h3><span class="dashicons dashicons-info-outline"></span> <?php esc_html_e( 'Plugin Info', 'nexoy-order-tracker-for-woocommerce' ); ?></h3></div>
    6969                    <div class="nexoy-card-body">
    70                         <p style="font-size: 13px; line-height: 1.6; color: #64748b;"><strong>Nexoy Order Tracker</strong> is built for high-performance WooCommerce stores. If you need help, contact our support team at <strong>thenexoy.com</strong>.</p>
     70                        <p style="font-size: 13px; line-height: 1.6; color: #64748b;"><?php echo wp_kses( __( '<strong>Nexoy Order Tracker</strong> is built for high-performance WooCommerce stores. If you need help, contact our support team at <strong>thenexoy.com</strong>.', 'nexoy-order-tracker-for-woocommerce' ), array( 'strong' => array() ) ); ?></p>
    7171                    </div>
    7272                </div>
    … …  
    8080add_action( 'admin_init', 'nexoy_ot_register_settings' );
    8181function nexoy_ot_register_settings() {
    82     register_setting( 'nexoy_ot_settings_group', 'nexoy_ot_theme_preference', 'nexoy_ot_sanitize_theme_choice' );
    83     add_settings_section( 'nexoy_ot_main_section', 'Display Settings', '__return_empty_string', 'nexoy-order-tracker' );
    84     add_settings_field( 'nexoy_ot_theme_preference', 'Tracker Theme', 'nexoy_ot_theme_radio_callback', 'nexoy-order-tracker', 'nexoy_ot_main_section' );
     82    register_setting( 'nexoy_ot_settings_group', 'nexoy_ot_theme_preference', array( 'type' => 'string', 'sanitize_callback' => 'nexoy_ot_sanitize_theme_choice', 'default' => 'light' ) );
     83    add_settings_section( 'nexoy_ot_main_section', __( 'Display Settings', 'nexoy-order-tracker-for-woocommerce' ), '__return_empty_string', 'nexoy-order-tracker' );
     84    add_settings_field( 'nexoy_ot_theme_preference', __( 'Tracker Theme', 'nexoy-order-tracker-for-woocommerce' ), 'nexoy_ot_theme_radio_callback', 'nexoy-order-tracker', 'nexoy_ot_main_section' );
    8585}
    8686
    … …  
    9292    $theme = get_option( 'nexoy_ot_theme_preference', 'light' );
    9393    ?>
    94     <label><input type="radio" name="nexoy_ot_theme_preference" value="light" <?php checked( $theme, 'light' ); ?> /> Light Theme (Default)</label><br><br>
    95     <label><input type="radio" name="nexoy_ot_theme_preference" value="dark" <?php checked( $theme, 'dark' ); ?> /> Dark Theme (Premium Look)</label>
     94    <label><input type="radio" name="nexoy_ot_theme_preference" value="light" <?php checked( $theme, 'light' ); ?> /> <?php esc_html_e( 'Light Theme (Default)', 'nexoy-order-tracker-for-woocommerce' ); ?></label><br><br>
     95    <label><input type="radio" name="nexoy_ot_theme_preference" value="dark" <?php checked( $theme, 'dark' ); ?> /> <?php esc_html_e( 'Dark Theme (Premium Look)', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
    9696    <?php
    9797}
  • nexoy-order-tracker-for-woocommerce/trunk/includes/ajax-handler.php

    r3467879 r3765041  
    66add_action( 'wp_ajax_nopriv_nexoy_ot_search_order', 'nexoy_ot_search_order_handler' );
    77
     8/**
     9 * Rate limit: max failed lookups per visitor IP within the window.
     10 */
     11define( 'NEXOY_OT_MAX_ATTEMPTS', 10 );
     12define( 'NEXOY_OT_ATTEMPT_WINDOW', 15 * MINUTE_IN_SECONDS );
     13
     14function nexoy_ot_attempts_key() {
     15    $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : 'unknown';
     16    return 'nexoy_ot_fail_' . md5( $ip );
     17}
     18
     19function nexoy_ot_register_failed_attempt() {
     20    $key      = nexoy_ot_attempts_key();
     21    $attempts = (int) get_transient( $key );
     22    set_transient( $key, $attempts + 1, NEXOY_OT_ATTEMPT_WINDOW );
     23}
     24
    825function nexoy_ot_search_order_handler() {
    926    check_ajax_referer( 'nexoy_ot_search_nonce', 'security' );
     27
     28    if ( (int) get_transient( nexoy_ot_attempts_key() ) >= NEXOY_OT_MAX_ATTEMPTS ) {
     29        wp_send_json_error( array( 'message' => __( 'Too many attempts. Please try again in a few minutes.', 'nexoy-order-tracker-for-woocommerce' ) ) );
     30    }
    1031
    1132    $order_id      = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
    … …  
    1435    $billing_phone = isset( $_POST['billing_phone'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_phone'] ) ) : '';
    1536
    16     if ( ! $order_id ) wp_send_json_error( array( 'message' => 'Please provide a valid Order ID.' ) );
     37    if ( ! $order_id ) wp_send_json_error( array( 'message' => __( 'Please provide a valid Order ID.', 'nexoy-order-tracker-for-woocommerce' ) ) );
     38
     39    // Same message for "not found" and "no match", so order IDs cannot be probed
     40    $no_match = __( 'Order ID and credentials do not match.', 'nexoy-order-tracker-for-woocommerce' );
    1741
    1842    $order = wc_get_order( $order_id );
    19     if ( ! $order || $order->get_id() !== $order_id ) wp_send_json_error( array( 'message' => 'Order not found.' ) );
     43    if ( ! $order || $order->get_id() !== $order_id ) {
     44        nexoy_ot_register_failed_attempt();
     45        wp_send_json_error( array( 'message' => $no_match ) );
     46    }
    2047
    2148    $is_valid_user = false;
    2249    if ( 'email' === $tracker_type ) {
    23         if ( strtolower( $order->get_billing_email() ) === strtolower( $billing_email ) ) $is_valid_user = true;
     50        if ( ! empty( $billing_email ) && strtolower( $order->get_billing_email() ) === strtolower( $billing_email ) ) $is_valid_user = true;
    2451    } else {
    25         $saved_phone   = preg_replace( '/[^0-9]/', '', $order->get_billing_phone() );
     52        $saved_phone    = preg_replace( '/[^0-9]/', '', $order->get_billing_phone() );
    2653        $provided_phone = preg_replace( '/[^0-9]/', '', $billing_phone );
    2754        if ( ! empty( $provided_phone ) && $saved_phone === $provided_phone ) $is_valid_user = true;
    2855    }
    2956
    30     if ( ! $is_valid_user ) wp_send_json_error( array( 'message' => 'Order ID and credentials do not match.' ) );
     57    if ( ! $is_valid_user ) {
     58        nexoy_ot_register_failed_attempt();
     59        wp_send_json_error( array( 'message' => $no_match ) );
     60    }
    3161
    3262    $history      = $order->get_meta( '_nexoy_ot_activity_log', true );
    … …  
    3464    $tracking_url = $order->get_meta( '_nexoy_ot_tracking_url', true );
    3565
     66    // Send ready-to-display labels and dates (site timezone, site date format)
     67    $timeline = array();
     68    if ( is_array( $history ) ) {
     69        foreach ( $history as $log ) {
     70            if ( ! isset( $log['event_type'], $log['new_status'], $log['timestamp'] ) || 'status_change' !== $log['event_type'] ) {
     71                continue;
     72            }
     73            $timeline[] = array(
     74                'status' => wc_get_order_status_name( $log['new_status'] ),
     75                'date'   => date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $log['timestamp'] ),
     76            );
     77        }
     78    }
     79
    3680    $response_data = array(
    3781        'order_status' => wc_get_order_status_name( $order->get_status() ),
    38         'history'      => $history,
    39         'courier'      => array( 'name' => $courier_name, 'url'  => $tracking_url )
     82        'history'      => array_reverse( $timeline ),
     83        'courier'      => array(
     84            'name' => $courier_name,
     85            'url'  => $tracking_url ? esc_url( $tracking_url, array( 'http', 'https' ) ) : '',
     86        ),
    4087    );
    4188
  • nexoy-order-tracker-for-woocommerce/trunk/includes/frontend-shortcode.php

    r3467879 r3765041  
    66
    77function nexoy_ot_render_search_form() {
     8    nexoy_ot_enqueue_assets();
     9
    810    ob_start();
    9    
     11
    1012    $current_user = wp_get_current_user();
    1113    $user_email   = $current_user->exists() ? $current_user->user_email : '';
    … …  
    1517    <div class="nexoy-ot-wrapper <?php echo esc_attr( $theme_class ); ?>">
    1618        <div class="nexoy-ot-toggle-area">
    17             <p class="nexoy-ot-label-main">How would you like to track your order?</p>
     19            <p class="nexoy-ot-label-main"><?php esc_html_e( 'How would you like to track your order?', 'nexoy-order-tracker-for-woocommerce' ); ?></p>
    1820            <div class="nexoy-ot-tabs">
    1921                <label class="nexoy-ot-tab">
    2022                    <input type="radio" name="tracker_type" value="email" checked>
    21                     <span>Track with Email</span>
     23                    <span><?php esc_html_e( 'Track with Email', 'nexoy-order-tracker-for-woocommerce' ); ?></span>
    2224                </label>
    2325                <label class="nexoy-ot-tab">
    2426                    <input type="radio" name="tracker_type" value="phone">
    25                     <span>Track with Phone</span>
     27                    <span><?php esc_html_e( 'Track with Phone', 'nexoy-order-tracker-for-woocommerce' ); ?></span>
    2628                </label>
    2729            </div>
    … …  
    3032        <form id="nexoy-ot-form" class="nexoy-ot-form">
    3133            <div class="nexoy-ot-input-group">
    32                 <label>Order ID</label>
    33                 <input type="text" name="order_id" placeholder="e.g. 12345" required>
     34                <label><?php esc_html_e( 'Order ID', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
     35                <input type="text" name="order_id" placeholder="<?php esc_attr_e( 'e.g. 12345', 'nexoy-order-tracker-for-woocommerce' ); ?>" required>
    3436            </div>
    3537            <div id="nexoy-ot-email-field" class="nexoy-ot-input-group">
    36                 <label>Billing Email</label>
     38                <label><?php esc_html_e( 'Billing Email', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
    3739                <input type="email" name="billing_email" value="<?php echo esc_attr( $user_email ); ?>" placeholder="you@example.com">
    3840            </div>
    3941            <div id="nexoy-ot-phone-field" class="nexoy-ot-input-group" style="display:none;">
    40                 <label>Billing Phone</label>
    41                 <input type="text" name="billing_phone" value="<?php echo esc_attr( $user_phone ); ?>" placeholder="e.g. +880123456789">
     42                <label><?php esc_html_e( 'Billing Phone', 'nexoy-order-tracker-for-woocommerce' ); ?></label>
     43                <input type="text" name="billing_phone" value="<?php echo esc_attr( $user_phone ); ?>" placeholder="<?php esc_attr_e( 'e.g. +880123456789', 'nexoy-order-tracker-for-woocommerce' ); ?>">
    4244            </div>
    43             <button type="submit" class="nexoy-ot-submit">Track Order Now</button>
     45            <button type="submit" class="nexoy-ot-submit"><?php esc_html_e( 'Track Order Now', 'nexoy-order-tracker-for-woocommerce' ); ?></button>
    4446        </form>
    4547
  • nexoy-order-tracker-for-woocommerce/trunk/includes/order-meta.php

    r3467879 r3765041  
    2727function nexoy_ot_add_order_meta_box() {
    2828    $screen = class_exists( '\Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController' ) && wc_get_container()->get( \Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ? wc_get_page_screen_id( 'shop-order' ) : 'shop_order';
    29     add_meta_box( 'nexoy_ot_tracker_meta_box', 'Nexoy Order Tracker', 'nexoy_ot_tracker_meta_box_callback', $screen, 'side', 'default' );
     29    add_meta_box( 'nexoy_ot_tracker_meta_box', __( 'Nexoy Order Tracker', 'nexoy-order-tracker-for-woocommerce' ), 'nexoy_ot_tracker_meta_box_callback', $screen, 'side', 'default' );
    3030}
    3131
    … …  
    4040    wp_nonce_field( 'nexoy_ot_save_meta_box', 'nexoy_ot_meta_box_nonce' );
    4141
    42     echo '<p><strong>Courier Name:</strong><br><input type="text" name="nexoy_ot_courier_name" value="' . esc_attr( $courier_name ) . '" style="width:100%;" placeholder="e.g. FedEx, DHL" /></p>';
    43     echo '<p><strong>Tracking URL:</strong><br><input type="url" name="nexoy_ot_tracking_url" value="' . esc_attr( $tracking_url ) . '" style="width:100%;" placeholder="https://..." /></p>';
     42    echo '<p><strong>' . esc_html__( 'Courier Name:', 'nexoy-order-tracker-for-woocommerce' ) . '</strong><br><input type="text" name="nexoy_ot_courier_name" value="' . esc_attr( $courier_name ) . '" style="width:100%;" placeholder="' . esc_attr__( 'e.g. FedEx, DHL', 'nexoy-order-tracker-for-woocommerce' ) . '" /></p>';
     43    echo '<p><strong>' . esc_html__( 'Tracking URL:', 'nexoy-order-tracker-for-woocommerce' ) . '</strong><br><input type="url" name="nexoy_ot_tracking_url" value="' . esc_attr( $tracking_url ) . '" style="width:100%;" placeholder="https://..." /></p>';
    4444
    45     echo '<hr><p><strong>Activity Log:</strong></p>';
     45    echo '<hr><p><strong>' . esc_html__( 'Activity Log:', 'nexoy-order-tracker-for-woocommerce' ) . '</strong></p>';
    4646    if ( is_array( $history ) && ! empty( $history ) ) {
    4747        echo '<ul style="margin:0; padding-left:15px; font-size:12px;">';
    4848        foreach ( $history as $log ) {
    4949            if ( isset( $log['event_type'] ) && 'status_change' === $log['event_type'] ) {
    50                 $time = date_i18n( 'M d, Y h:i A', $log['timestamp'] );
     50                $time = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $log['timestamp'] );
    5151                $status_name = wc_get_order_status_name( $log['new_status'] );
    5252                echo '<li>' . esc_html( $status_name ) . ' - <em>' . esc_html( $time ) . '</em></li>';
    … …  
    5555        echo '</ul>';
    5656    } else {
    57         echo '<p style="color:#777;">No activity recorded yet.</p>';
     57        echo '<p style="color:#777;">' . esc_html__( 'No activity recorded yet.', 'nexoy-order-tracker-for-woocommerce' ) . '</p>';
    5858    }
    5959}
  • nexoy-order-tracker-for-woocommerce/trunk/nexoy-order-tracker.php

    r3467879 r3765041  
    44 * Plugin URI:        https://thenexoy.com/
    55 * Description:       A premium, high-performance order tracking system for WooCommerce. Allow customers to track their orders via a luxury timeline UI using their Order ID and Email/Phone.
    6  * Version:           1.0.0
     6 * Version:           1.1.0
    77 * Requires at least: 5.8
    88 * Requires PHP:      7.4
    9  * Author:            Rayhan Chowdhury
    10  * Author URI:        https://prorayhan.com
     9 * Requires Plugins:  woocommerce
     10 * WC requires at least: 6.0
     11 * WC tested up to:   11.0
     12 * Author:            The Nexoy
     13 * Author URI:        https://thenexoy.com/
    1114 * License:           GPL v2 or later
    1215 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2023
    2124// Define Plugin Constants
    22 define( 'NEXOY_OT_VERSION', '1.0.1' );
     25define( 'NEXOY_OT_VERSION', '1.1.0' );
    2326define( 'NEXOY_OT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    2427define( 'NEXOY_OT_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     28
     29/**
     30 * Declare compatibility with WooCommerce HPOS and Cart/Checkout blocks.
     31 */
     32add_action( 'before_woocommerce_init', 'nexoy_ot_declare_wc_compatibility' );
     33
     34function nexoy_ot_declare_wc_compatibility() {
     35    if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) {
     36        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
     37        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true );
     38    }
     39}
    2540
    2641/**
    … …  
    3853    require_once NEXOY_OT_PLUGIN_DIR . 'includes/frontend-shortcode.php';
    3954}
    40 // FIXED ISSUE 1: Priority 99 ensures WooCommerce loads before our plugin
     55// Priority 99 ensures WooCommerce loads before our plugin
    4156add_action( 'plugins_loaded', 'nexoy_ot_init_plugin', 99 );
    4257
    4358/**
    44  * Enqueue Frontend Scripts and Styles unconditionally for Page Builders
     59 * Register frontend assets. They are enqueued only where the tracker is shown.
    4560 */
    46 function nexoy_ot_enqueue_scripts() {
    47     // FIXED ISSUE 2: Removed strict shortcode check to ensure CSS loads in Elementor/Gutenberg perfectly
    48     wp_enqueue_style( 'nexoy-ot-style', NEXOY_OT_PLUGIN_URL . 'assets/css/style.css', array(), NEXOY_OT_VERSION );
    49     wp_enqueue_script( 'nexoy-ot-script', NEXOY_OT_PLUGIN_URL . 'assets/js/script.js', array( 'jquery' ), NEXOY_OT_VERSION, true );
    50    
     61function nexoy_ot_register_scripts() {
     62    wp_register_style( 'nexoy-ot-style', NEXOY_OT_PLUGIN_URL . 'assets/css/style.css', array(), NEXOY_OT_VERSION );
     63    wp_register_script( 'nexoy-ot-script', NEXOY_OT_PLUGIN_URL . 'assets/js/script.js', array( 'jquery' ), NEXOY_OT_VERSION, true );
     64
    5165    wp_localize_script( 'nexoy-ot-script', 'nexoy_ot_params', array(
    5266        'ajax_url' => admin_url( 'admin-ajax.php' ),
    53         'nonce'    => wp_create_nonce( 'nexoy_ot_search_nonce' )
     67        'nonce'    => wp_create_nonce( 'nexoy_ot_search_nonce' ),
     68        'i18n'     => array(
     69            'fetching'     => __( 'Fetching order details...', 'nexoy-order-tracker-for-woocommerce' ),
     70            'conn_error'   => __( 'Connection error. Please try again.', 'nexoy-order-tracker-for-woocommerce' ),
     71            'carrier'      => __( 'Carrier:', 'nexoy-order-tracker-for-woocommerce' ),
     72            'track_order'  => __( 'Track Order', 'nexoy-order-tracker-for-woocommerce' ),
     73            'current'      => __( 'Current status:', 'nexoy-order-tracker-for-woocommerce' ),
     74            /* translators: %s: order status name */
     75            'updated_to'   => __( 'Your order has been updated to %s.', 'nexoy-order-tracker-for-woocommerce' ),
     76        ),
    5477    ) );
     78
     79    // Load early on regular pages that contain the shortcode (avoids a flash of unstyled content)
     80    if ( is_singular() ) {
     81        $post = get_post();
     82        if ( $post && has_shortcode( $post->post_content, 'nexoy_order_tracker' ) ) {
     83            nexoy_ot_enqueue_assets();
     84        }
     85    }
    5586}
    56 add_action( 'wp_enqueue_scripts', 'nexoy_ot_enqueue_scripts' );
     87add_action( 'wp_enqueue_scripts', 'nexoy_ot_register_scripts' );
     88
     89/**
     90 * Enqueue frontend assets (also called from the shortcode, for page builders).
     91 */
     92function nexoy_ot_enqueue_assets() {
     93    wp_enqueue_style( 'nexoy-ot-style' );
     94    wp_enqueue_script( 'nexoy-ot-script' );
     95}
    5796
    5897
    … …  
    63102
    64103function nexoy_ot_add_settings_link( $links ) {
    65     // Create the settings link
    66     $settings_url = admin_url( 'admin.php?page=nexoy-order-tracker' );
    67     $settings_link = '<a href="' . esc_url( $settings_url ) . '" style="font-weight: bold; color: #2271b1;">Settings</a>';
    68    
    69     // Add it to the beginning of the links array
     104    $settings_url  = admin_url( 'admin.php?page=nexoy-order-tracker' );
     105    $settings_link = '<a href="' . esc_url( $settings_url ) . '" style="font-weight: bold; color: #2271b1;">' . esc_html__( 'Settings', 'nexoy-order-tracker-for-woocommerce' ) . '</a>';
     106
    70107    array_unshift( $links, $settings_link );
    71    
     108
    72109    return $links;
    73110}
  • nexoy-order-tracker-for-woocommerce/trunk/readme.txt

    r3467879 r3765041  
    44Tags: woocommerce, order tracker, order tracking, shipment tracking, tracking
    55Requires at least: 5.8
    6 Tested up to: 6.9
    7 Stable tag: 1.0.0
     6Tested up to: 7.1
     7Stable tag: 1.1.0
    88Requires PHP: 7.4
    99License: GPLv2 or later
    … …  
    5959== Changelog ==
    6060
     61= 1.1.0 =
     62* Security: Courier details are now escaped before display.
     63* Security: Failed lookups are rate limited per visitor to stop order probing.
     64* Improved: Timeline shows proper status names and dates in your site's timezone and date format.
     65* Improved: CSS/JS load only on pages that show the tracker.
     66* Improved: Declared compatibility with WooCommerce HPOS and Cart/Checkout blocks.
     67* Improved: All texts are now translatable; admin icons use Dashicons.
     68* Fixed: Version number mismatch on the settings page.
     69* Tested with WordPress 7.1.
     70
    6171= 1.0.0 =
    6272* Initial Release: The grand launch of Nexoy Order Tracker for WooCommerce.
     73
     74== Upgrade Notice ==
     75
     76= 1.1.0 =
     77Security and display improvements. Recommended update for all users.
Note: See TracChangeset for help on using the changeset viewer.