__( 'Canada Post', 'aisp-order-tracking-notification' ), 'purolator' => __( 'Purolator', 'aisp-order-tracking-notification' ), 'gls' => __( 'GLS', 'aisp-order-tracking-notification' ), 'canpar' => __( 'Canpar', 'aisp-order-tracking-notification' ), 'ups' => __( 'UPS', 'aisp-order-tracking-notification' ), 'delivro' => __( 'Delivro', 'aisp-order-tracking-notification' ), 'loomis' => __( 'Loomis', 'aisp-order-tracking-notification' ), 'nationex' => __( 'Nationex', 'aisp-order-tracking-notification' ), 'livrapide' => __( 'LivRapide', 'aisp-order-tracking-notification' ), 'uniuni' => __( 'Uni Uni', 'aisp-order-tracking-notification' ), ); // New filter. $carriers = apply_filters( 'aisp_order_tracking_notification_carriers', $carriers ); // Backward compatibility – intentional typo preserved from v1.x. $carriers = apply_filters( 'aisp_simple_traking_carriers', $carriers ); // Sort alphabetically by carrier name. asort( $carriers ); return $carriers; } // ------------------------------------------------------------------------- // Carrier normalization // ------------------------------------------------------------------------- public static function aisp_normalize_carrier( $carrier_raw ) { if ( empty( $carrier_raw ) ) { return ''; } $carrier = strtolower( trim( $carrier_raw ) ); if ( strpos( $carrier, 'canadapost' ) !== false ) { return 'canada_post'; } if ( strpos( $carrier, 'canada post' ) !== false ) { return 'canada_post'; } if ( strpos( $carrier, 'poste canada' ) !== false ) { return 'canada_post'; } if ( strpos( $carrier, 'loomisexpress' ) !== false ) { return 'loomis'; } if ( strpos( $carrier, 'purolator' ) !== false ) { return 'purolator'; } if ( strpos( $carrier, 'gls' ) !== false ) { return 'gls'; } if ( strpos( $carrier, 'ups' ) !== false ) { return 'ups'; } if ( strpos( $carrier, 'canpar' ) !== false ) { return 'canpar'; } if ( strpos( $carrier, 'nationex' ) !== false ) { return 'nationex'; } if ( strpos( $carrier, 'delivro' ) !== false ) { return 'delivro'; } if ( strpos( $carrier, 'livrapide' ) !== false ) { return 'livrapide'; } if ( strpos( $carrier, 'uniuni' ) !== false ) { return 'uniuni'; } if ( strpos( $carrier, 'uni' ) !== false ) { return 'uniuni'; } return $carrier; } // ------------------------------------------------------------------------- // Tracking URL // ------------------------------------------------------------------------- public static function aisp_order_tracking_notification_get_tracking_url( $carrier, $tracking ) { $carrier = self::aisp_normalize_carrier( $carrier ); $tracking = trim( (string) $tracking ); if ( '' === $tracking ) { return false; } switch ( $carrier ) { case 'canada_post': $url = 'https://www.canadapost-postescanada.ca/track-reperage/en#/details/%1$s'; break; case 'purolator': $url = 'https://www.purolator.com/en/shipping/tracker?pin=%1$s'; break; case 'gls': $url = 'https://ship.gls-canada.com/tracking/%1$s'; break; case 'canpar': $url = 'https://www.canpar.com/en/tracking/track.htm?barcode=%1$s'; break; case 'ups': $url = 'https://www.ups.com/track?track=yes&trackNums=%1$s'; break; case 'delivro': $url = 'https://delivro.dsapp.io/Customer/%1$s'; break; case 'loomis': $url = 'https://www.loomis-express.com/loomship/Track/TrackResults?t=WAYBILL&s=%1$s'; break; case 'nationex': $url = 'https://nationex.com/en/tracking/%1$s'; break; case 'livrapide': $url = 'https://plus-dispatch.shiptrackapp.com/Tracking/View?lng=&tracking=%1$s'; break; case 'uniuni': $url = 'https://www.uniuni.com/fr/suivi/#tracking-detail?no=%1$s'; break; default: return false; } // New filter. $url = apply_filters( 'aisp_order_tracking_notification_tracking_url_template', $url, $carrier, $tracking ); // Backward compatibility – intentional typo preserved from v1.x. $url = apply_filters( 'aisp_simple_traking_tracking_url_template', $url, $carrier ); return sprintf( $url, rawurlencode( $tracking ) ); } // ------------------------------------------------------------------------- // Tracking value normalization (v1 string → v2 array) // ------------------------------------------------------------------------- /** * Normalize a raw meta value to a clean array of tracking numbers. * * Handles three cases: * - v2 JSON array : '["P001","P002"]' → ['P001', 'P002'] * - v1 plain string: 'P001' → ['P001'] * - Empty : '' → [] * * @param mixed $raw Raw value from get_meta(). * @return array Array of non-empty tracking number strings. */ public static function aisp_normalize_tracking_value( $raw ) { if ( empty( $raw ) ) { return array(); } // Try JSON first (v2 format). $decoded = json_decode( $raw, true ); if ( is_array( $decoded ) ) { return array_values( array_filter( array_map( 'trim', $decoded ), function( $v ) { return '' !== $v; } ) ); } // Legacy v1 plain string. $trimmed = trim( (string) $raw ); return $trimmed !== '' ? array( $trimmed ) : array(); } // ------------------------------------------------------------------------- // Order tracking data // ------------------------------------------------------------------------- /** * Return normalized tracking data for an order. * * Return shape: * [ * 'carrier' => string (normalized carrier key) * 'tracking' => string[] (array of tracking numbers — may be empty) * 'source' => 'manual'|'external'|'' * ] * * @param WC_Order $order * @return array */ public static function aisp_get_order_tracking_data( $order ) { $empty = array( 'carrier' => '', 'tracking' => array(), 'source' => '', ); if ( ! $order instanceof WC_Order ) { return $empty; } // --- Manual (v2 JSON array or v1 plain string) --- $manual_carrier = $order->get_meta( self::META_CARRIER, true ); $manual_tracking = self::aisp_normalize_tracking_value( $order->get_meta( self::META_TRACKING, true ) ); $manual_carrier = self::aisp_normalize_carrier( $manual_carrier ); if ( ! empty( $manual_carrier ) && ! empty( $manual_tracking ) ) { return array( 'carrier' => $manual_carrier, 'tracking' => $manual_tracking, 'source' => 'manual', ); } // --- External integrations (Machool, Ogalo, ClickShip, etc.) --- $external_carrier = $order->get_meta( 'shipping_provider', true ); if ( empty( $external_carrier ) ) { $external_carrier = $order->get_meta( 'carrier', true ); } $external_carrier = self::aisp_normalize_carrier( $external_carrier ); // External integrations currently send a single tracking number. $external_tracking_raw = trim( (string) $order->get_meta( 'tracking_number', true ) ); $external_tracking = $external_tracking_raw !== '' ? array( $external_tracking_raw ) : array(); if ( ! empty( $external_carrier ) && ! empty( $external_tracking ) ) { return array( 'carrier' => $external_carrier, 'tracking' => $external_tracking, 'source' => 'external', ); } return $empty; } // ------------------------------------------------------------------------- // Order notes (internal only — never sent to customer) // ------------------------------------------------------------------------- /** * Add an internal order note. * * @param WC_Order $order * @param string $message Plain text note content. */ public static function aisp_add_order_note( WC_Order $order, $message ) { $order->add_order_note( sanitize_text_field( $message ), 0, // 0 = internal note (not sent to customer). false // false = not added by customer. ); } /** * Build a human-readable summary of tracking numbers for use in order notes. * * @param string $carrier Normalized carrier key. * @param string[] $trackings Array of tracking numbers. * @return string */ public static function aisp_build_tracking_note_summary( $carrier, array $trackings ) { $carriers = self::aisp_order_tracking_notification_get_carriers(); $carrier_label = isset( $carriers[ $carrier ] ) ? $carriers[ $carrier ] : strtoupper( $carrier ); $numbers = implode( ', ', $trackings ); return sprintf( // translators: %1$s carrier name, %2$s list of tracking numbers. __( '%1$s: %2$s', 'aisp-order-tracking-notification' ), $carrier_label, $numbers ); } }