Plugin Directory

Changeset 3789014


Ignore:
Timestamp:
11/07/2026 05:31:48 AM (less than one hour ago)
Author:
yoohw
Message:

Release 0.4.0 with updated readme, full changelog and approved screenshots

Location:
yoohw-advanced-notifications
Files:
22 added
20 edited
1 copied

Legend:

Unmodified
Added
Removed
  • yoohw-advanced-notifications/tags/0.4.0/assets/admin.css

    r3771948 r3789014  
    810810
    811811.yoohw-an-workspace .an-preview-device .dashicons { display: block; width: 20px; height: 20px; margin: 0; font-size: 20px; line-height: 20px; }
     812
     813/* Automation authoring shares the composer steps, editor and preview layout. */
     814.an-automation-rule { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 16px 0; border-top: 1px solid #dcdcde; }
     815.an-automation-rule p { margin: 6px 0 0; color: #50575e; }
     816.an-automation-state { display: inline-block; margin-left: 12px; padding: 2px 8px; border: 1px solid #c3c4c7; border-radius: 3px; font-size: 12px; }
     817.an-automation-rule-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
     818.an-automation-form details { margin-top: 16px; }
     819.an-automation-form .an-field > input[type="number"] { max-width: 240px; }
     820.an-automation-form .an-sample-stale { padding: 12px; border-left: 4px solid #dba617; background: #fcf9e8; }
     821.an-automation-form .an-automation-sample[hidden], .an-automation-form .an-sample-stale[hidden] { display: none !important; }
     822@media (max-width: 782px) { .an-automation-rule { align-items: flex-start; flex-direction: column; gap: 12px; } }
  • yoohw-advanced-notifications/tags/0.4.0/assets/admin.js

    r3771948 r3789014  
    2727                        $row.find(':input').prop('disabled', false);
    2828                }
     29                revealApplicable('.an-selected-orders', source === 'selected_orders');
    2930                revealApplicable('#yoohw-advanced-notifications-products-row', product);
    3031                revealApplicable('#yoohw-advanced-notifications-roles-row', registered);
    3132                revealApplicable('#yoohw-advanced-notifications-manual-row', manual);
    32                 revealApplicable('.an-order-filters', product || source === yoohwAN.customersSource);
    33                 revealApplicable('.an-country-filter', !manual);
     33                revealApplicable('.an-order-filters', product || source === yoohwAN.customersSource || source === 'selected_orders');
     34                revealApplicable('.an-country-filter', !manual && source !== 'subscribed_emails');
    3435                $('#yoohw-advanced-notifications-customers-row').toggle(source === yoohwAN.customersSource);
    3536                $('.an-incompatible-help').prop('hidden', !$('.an-inapplicable-selection').length);
    … …  
    143144
    144145                editor.on('keyup change input SetContent NodeChange', updateEmailPreview);
     146                editor.on('change input keyup', markAutomationPreviewStale);
    145147                editor.yoohwEmailNotifyPreviewBound = true;
    146148        }
    … …  
    209211                        if (panel) { showStep(names.indexOf(panel.dataset.anPanel), false); }
    210212                }, true);
    211                 showStep(0, false);
     213                var initial = names.indexOf($form.attr('data-an-initial-step'));
     214                showStep(initial < 0 ? 0 : initial, false);
    212215        }
    213216
    … …  
    282285                updateEmailPreview();
    283286        });
     287
     288        function markAutomationPreviewStale() {
     289                $('.an-automation-form .an-automation-sample').prop('hidden', true);
     290                $('.an-automation-form .an-sample-stale').prop('hidden', false);
     291        }
     292        $(document).on('input change', '.an-automation-form input:not([type="hidden"]), .an-automation-form select, .an-automation-form textarea', function() {
     293                if (this.name !== 'test_email' && this.name !== 'name') { markAutomationPreviewStale(); }
     294        });
    284295        $(document).on('submit', 'form', function() { if (window.tinymce) { window.tinymce.triggerSave(); } });
    285296        $(document).on('change', '#recipient_source, .an-order-filters :input, .an-country-filter :input, #yoohw-advanced-notifications-roles-row :input, #yoohw-advanced-notifications-products-row :input, #yoohw-advanced-notifications-manual-row :input', toggleRecipientFields);
  • yoohw-advanced-notifications/tags/0.4.0/docs/operator-features.md

    r3771948 r3789014  
    1 # Tính năng vận hành campaign (chưa phát hành)
     1# Tính năng vận hành campaign (0.4.0)
    22
    33Nền đối chiếu: `74353181835147d8e68d9bca87a3d801c3e772dd` trên local và `origin/main`. Giữ schema recipient 3, Action Scheduler, manifest xác nhận và durable opt-out hiện có. Không thêm service hoặc kênh gửi khác.
    … …  
    7878
    7979Các selection field dùng SelectWoo có sẵn của WooCommerce (giao diện Select2), bao gồm các bộ lọc và người nhận mẫu; giá trị gốc và validation vẫn giữ nguyên. Hai nút icon Desktop/Mobile điều chỉnh bố cục preview trong cả bước soạn và preview người nhận, có tooltip/nhãn hỗ trợ đọc màn hình và trạng thái lựa chọn rõ ràng; không tạo thao tác gửi.
     80
     81### AN-5: Liên hệ từ Products và Orders (0.4.0)
     82
     83- Products có **Email purchasers** tại danh sách và trang sửa. Sản phẩm cha gồm các variation; từng variation có liên kết riêng chỉ chọn người mua variation đó. Orders có bulk action **Email selected orders** trên cả HPOS và lưu trữ cũ; trang chi tiết có **Email this customer**. Các thao tác chỉ lưu bản nháp, chưa tạo job hay gửi email.
     84- Nguồn **Selected orders** giữ tập ID rõ ràng, sửa được trong composer. Mặc định xét mọi trạng thái trong tập đã chọn để hỗ trợ thông báo cho cả đơn đang chờ/hủy; bộ lọc trạng thái có thể thu hẹp. Product customers tiếp tục mặc định Processing/Completed. Guest và tài khoản được gộp theo billing email đã chuẩn hóa, mỗi email nhận một thư cho campaign. Bộ lọc đơn vẫn khớp cùng một đơn; ID mất/quyền không đủ bị từ chối, không mở rộng tập chọn.
     85- Subject, heading và body của campaign mới hỗ trợ `{customer_name}`, `{store_name}`, `{product_name}`, `{order_number}`, `{ordered_products}`, `{order_url}`. `{product_name}` vẫn là nhãn chung của campaign. Thông tin riêng lấy từ **đơn khớp có ID lớn nhất**, gồm toàn bộ tên dòng sản phẩm của đơn đó; preview ghi số đơn đại diện và số đơn khớp. Không lấy đơn mới hơn nằm ngoài phạm vi. Thiếu tên dùng **Customer**; thiếu ngữ cảnh đơn thì các biến đơn rỗng.
     86- `{order_url}` chỉ có khi email tài khoản sở hữu khớp billing email, dùng trang My Account và cần đăng nhập đúng chủ đơn. Guest hoặc email khác tài khoản không có liên kết; không lưu khóa order/guest. Không chèn liên kết vào mẫu mặc định khi thiếu ngữ cảnh.
     87- Live editor hiển thị nội dung template; **Preview Recipients** là preview cá nhân hóa thật, chọn được tối đa 20 người mẫu. Email thử dùng người đủ điều kiện đầu tiên, hoặc Test Customer nếu chưa có người nhận, và luôn gửi tới địa chỉ thử đã nhập. Ba mẫu có hướng dẫn điền thay đổi/ngày hiệu lực/hành động/liên hệ và vẫn sửa tự do.
     88- Lưu/mở lại/duplicate/saved audience giữ tiêu chí ID; phải preview dữ liệu hiện tại khi dùng lại. Xác nhận giữ nguyên context, tên cửa hàng, nội dung và recipient; dispatcher/recovery đọc snapshot, không truy vấn lại đơn để gửi. Campaign đã xác nhận trước AN-5 giữ subject/heading literal và cách thay body cũ. Durable opt-out, outcome unknown, privacy/retention hiện hành tiếp tục có hiệu lực; export bổ sung context của đúng email và erasure xóa context khỏi snapshot/preview/args.
     89
     90Kiểm chứng mới: `tests/contextual-campaigns.php` và `tests/contextual-entrypoints.php`, được chạy trong CI regression với database reliability riêng và mail bị chặn. Bằng chứng theo candidate ghi trong PR. Không chứng minh SMTP/inbox thật.
     91
     92
     93### AN-6: Đăng ký trực tiếp và lịch sử liên hệ (0.4.0)
     94
     95- Chèn shortcode `[yoohw_an_subscribe]` vào trang. Khách nhập email, chủ động chọn đồng ý rồi nhận thư xác nhận. Classic Checkout và Checkout Blocks có checkbox tùy chọn, không chọn sẵn, chỉ gửi yêu cầu xác nhận cho billing email; My Account quản lý đúng email hiện tại của tài khoản. Không suy diễn consent từ đơn hàng/tài khoản. Blocks dùng Additional Checkout Fields API của WooCommerce, không lưu lựa chọn mặc định trên hồ sơ khách.
     96- Link email chỉ mở trang xác nhận; cần bấm **Confirm subscription** để thay đổi trạng thái, tránh scanner email tự đăng ký. Token ngẫu nhiên, chỉ lưu hash, hết hạn sau 24 giờ, dùng một lần. Trước xác nhận, trạng thái có hiệu lực vẫn là unknown hoặc unsubscribed; My Account và tìm kiếm subscriber thể hiện yêu cầu đang chờ.
     97- Đăng ký lại sau unsubscribe cần yêu cầu mới và xác nhận mailbox. Record subscription xác nhận ghi nhận đúng thế hệ suppression trước yêu cầu; unsubscribe mới tạo thế hệ mới nên chặn cả link cũ lẫn quyền xác nhận cũ. Không xóa option suppression legacy hoặc bản ghi bảo vệ hàng loạt. Import không có thẩm quyền xác nhận này và không gỡ opt-out.
     98- Nguồn **Subscribed email addresses** dùng record hiện có, bao gồm guest không tài khoản/đơn hàng; chỉ lấy subscribed còn hiệu lực. Không áp dụng product/order/country/roles/manual criteria của nguồn khác. Draft/saved audience/preview/duplicate/lịch/controls dùng đường hiện có. Worker kiểm tra lại đăng ký trước handoff; unsubscribe hoặc trạng thái unknown sau preview không được gửi trong nguồn này. Campaign đã xác nhận không lấy thêm subscriber mới.
     99- **Subscribers → Contact history by email** tra cứu chính xác email trên bảng recipient hiện có, 50 dòng/trang: campaign, subject/context còn được lưu, due/updated time UTC, outcome và lý do, liên kết mở campaign. Không query đơn live để khôi phục context. Retention chỉ còn outcome sẽ báo nội dung không còn; tombstone erasure chặn toàn bộ lịch sử plaintext.
     100- Anti-abuse dùng nonce/honeypot trên form và quota nguyên tử dưới mutex privacy hiện có: một yêu cầu/email mỗi 10 phút, tối đa 5/client mỗi 30 phút, 100/site mỗi giờ. Client chỉ dùng HMAC của REMOTE_ADDR (không tin X-Forwarded-For, không lưu IP gốc); quota chung NAT có thể giới hạn nhiều khách cùng mạng. Phản hồi công khai không cho biết email đã tồn tại, unsubscribed hay erased. Pending request/counter hết hạn được dọn qua reconciliation hàng giờ; không tự retry confirmation mail khi transport mơ hồ.
     101- Confirmation mail là thư xác minh theo yêu cầu, có thể gửi tới địa chỉ đang unsubscribed để chính chủ đăng ký lại; không dùng đường này cho campaign. Nó dùng WooCommerce template/sender của shop và transport WordPress, không được trình bày như campaign handoff trong contact history. Kiểm thử chặn toàn bộ mail ra ngoài, không xác minh SMTP/inbox thật.
     102- Erasure giữ nguyên protective hash và không cho signup mới phục hồi dữ liệu/subscriber đã erase. Pending request được scrub cùng dữ liệu cá nhân hiện có; export bổ sung nguồn/thời gian yêu cầu, không xuất token. Việc đăng ký lại sau unsubscribe chỉ thay lựa chọn tương lai, không dựng lại history, không replay campaign cũ, không tự thay outcome unknown.
     103
     104Bộ kiểm chứng mới: `subscriptions.php`, `checkout-subscription.php`, `contact-history.php` và concurrency cho request/confirm/unsubscribe/erasure. Bằng chứng theo candidate và giới hạn runtime ghi trong PR; không thêm service, bảng CRM hoặc cơ chế tracking mở/nhấp email.
     105
     106
     107## Purchase history and Completed-order guides (AN-7)
     108
     109Compose and saved audiences support `purchase_frequency` (`first`/`repeat`) and `inactive_days` (1–36500). These criteria scan valid purchases across the whole store, separately from same-order product/category/date/status/country conditions. Identity is normalized billing email, including guest/account orders; different emails are never inferred to be the same customer. Valid purchases are Processing/Completed orders excluding full refunds; a partial refund counts while value remains, and free orders without refunds count. Inactivity uses the latest valid order creation instant and X × 24 hours. Never-buyers and orders with unavailable dates do not qualify.
     110
     111Automations supports one fixed guide per rule/order after a new Completed transition. Save disabled, inspect conditions/content/delay, preview or test with an accessible order, then explicitly Enable. Test mail goes only to its entered address and never saves/enables a rule or creates a delivery job. Products/variations and category descendants reuse the existing same-order matching behavior. Each eligible order can receive its own guide even when another order shares the email.
     112
     113The event stores an immutable campaign manifest using a deterministic rule/order identity. Repeated events, items and workers reuse the same identity. Delay is an exact UTC offset from capture; queue delays never roll the due time to another day. Edits affect future events only. Disable rotates the enabled generation, durably blocks pending campaigns and cancels their unstarted rows. Re-enable cannot revive them. Work already admitted may finish. History and contact lookup retain the exact triggering order and result.
     114
     115Before handoff the rule generation, fresh order, billing email and saved product/category conditions are rechecked. An order must still be Completed and not fully refunded; other states are skipped, partial refunds remain eligible while value remains. Active subscription is required and checked again after email rendering. Unknown never becomes subscribed through purchase. Existing suppression, erasure, claim, rate, recovery and unknown-handoff protections apply.
     116
     117Only new events are admitted; there is no historical backfill. Captured manifests interrupted before their history row are registered by existing reconciliation and deactivation. A database/process failure before durable capture cannot be reconstructed without a new transition: the WooCommerce logger records a non-personal capture error. Operators must investigate storage/history and any mail-provider ambiguity before manually changing order status; the plugin never automatically replays an unknown handoff. No external event service or general automation builder is added.
     118
     119
     120Automation authoring follows the same four-step layout as Compose: **Conditions → Content → Timing → Review**. Content uses the shared WooCommerce live preview, Visual/Code editor and Desktop/Mobile controls. Live preview retains placeholders; Review refreshes the exact order sample through the existing server renderer. Preview/test returns to Review and preserves the unsaved form. Changing content, conditions, timing or sample order hides the old personal preview and asks for a refresh. Saving and enabling remain separate operations; list controls always use the saved configuration. The campaign-history link opens the campaign list so pending guides are discoverable.
  • yoohw-advanced-notifications/tags/0.4.0/includes/class-customer-email-notify.php

    r3771948 r3789014  
    1010require_once __DIR__ . '/trait-campaign-audiences.php';
    1111require_once __DIR__ . '/trait-campaign-privacy.php';
     12require_once __DIR__ . '/trait-campaign-entrypoints.php';
     13require_once __DIR__ . '/trait-campaign-personalization.php';
     14require_once __DIR__ . '/trait-campaign-subscriptions.php';
     15require_once __DIR__ . '/trait-campaign-checkout.php';
     16require_once __DIR__ . '/trait-campaign-contact-history.php';
     17require_once __DIR__ . '/trait-campaign-purchase-history.php';
     18require_once __DIR__ . '/trait-campaign-automations.php';
     19require_once __DIR__ . '/trait-campaign-automation-ui.php';
    1220
    1321class YOOHW_AN_Customer_Email_Notify {
    … …  
    1725        use YOOHW_AN_Campaign_Audiences;
    1826        use YOOHW_AN_Campaign_Privacy;
     27        use YOOHW_AN_Campaign_Entrypoints;
     28        use YOOHW_AN_Campaign_Personalization;
     29        use YOOHW_AN_Campaign_Subscriptions;
     30        use YOOHW_AN_Campaign_Checkout;
     31        use YOOHW_AN_Campaign_Contact_History;
     32        use YOOHW_AN_Campaign_Purchase_History;
     33        use YOOHW_AN_Campaign_Automations;
     34        use YOOHW_AN_Campaign_Automation_UI;
    1935
    2036        const ACTION_SEND_SINGLE_EMAIL = 'yoohw_an_send_single_email';
    … …  
    5066        const SOURCE_REGISTERED_USERS    = 'registered_users';
    5167        const SOURCE_WOOCOMMERCE_CUSTOMERS = 'woocommerce_customers';
     68        const SOURCE_SUBSCRIBED = 'subscribed_emails';
     69        const SOURCE_SELECTED_ORDERS = 'selected_orders';
    5270        const SOURCE_MANUAL_EMAILS        = 'manual_emails';
    5371
    … …  
    5674        private $last_mail_error    = '';
    5775        private $mail_handoff_started = false;
     76        private $last_automation_outcome = '';
    5877        private $current_action_id = 0;
    5978        private $recovery_enabled = true;
    … …  
    6382        public function __construct() {
    6483                $this->register_operator_features();
     84                $this->register_contextual_entrypoints();
     85                $this->register_subscriptions();
     86                $this->register_automations();
    6587                // Register after WooCommerce sorts Marketing at priority 99 to preserve our position.
    6688                add_action( 'admin_menu', [ $this, 'add_submenu' ], 100 );
    … …  
    143165        public function deactivate() {
    144166                $this->recovery_enabled = false;
     167                $this->register_automation_manifests();
    145168                $this->block_campaigns_on_deactivation();
    146169                wp_unschedule_hook( self::ACTION_RECONCILE_CAMPAIGNS );
    … …  
    176199
    177200        public function reconcile_open_campaigns( $campaign_id = '' ) {
     201                $this->reconcile_automation_campaigns();
    178202                $campaigns = $this->get_campaign_history();
    179203                $campaign_id = sanitize_text_field( $campaign_id );
    … …  
    781805
    782806                $active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'campaign'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin navigation, filters and notices; no state is changed.
     807
     808                if ( 'automations' === $active_tab ) { $this->render_automations_page(); return; }
    783809
    784810                if ( 'library' === $active_tab ) {
    … …  
    11551181                                                                );
    11561182                                                                ?>
    1157                                                                 <p><?php /* translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders. */ esc_html_e( 'Body variables: {customer_name} uses the saved recipient name, falling back to email; {product_name} is the shared campaign product label, never a personal purchase. With no product selection it uses the audience label. Subject and heading are literal.', 'yoohw-advanced-notifications' ); ?></p>
     1183                                                                <p><?php /* translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders. */ esc_html_e( 'Variables in subject, heading and body: {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products}, {order_url}. Order variables use the matching order with the highest order ID; preview shows its number and the matching order count. Without order context they are empty; missing names use Customer. {product_name} remains the shared campaign label. Order links require the matching account login; guests have no order link. The live editor shows template text; Preview Recipients and test email use the first eligible recipient.', 'yoohw-advanced-notifications' ); ?></p>
    11581184                                                                <p><?php esc_html_e( 'An unsubscribe link is added automatically to campaign emails.', 'yoohw-advanced-notifications' ); ?></p>
    11591185                                                        </div>
    … …  
    12961322                        'campaigns' => [ 'label' => __( 'Campaigns', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'campaigns' ] ) ],
    12971323                        'campaign' => [ 'label' => __( 'Compose', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url() ],
     1324                        'automations' => [ 'label' => __( 'Automations', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'automations' ] ) ],
    12981325                        'library' => [ 'label' => __( 'Library', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'library' ] ) ],
    12991326                        'subscribers' => [ 'label' => __( 'Subscribers', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'subscribers' ] ) ],
    … …  
    23252352                        );
    23262353
    2327                         $test_message = str_replace(
    2328                                 [
    2329                                         '{customer_name}',
    2330                                         '{product_name}',
    2331                                 ],
    2332                                 [
    2333                                         __( 'Test Customer', 'yoohw-advanced-notifications' ),
    2334                                         $payload['products_label'],
    2335                                 ],
    2336                                 $payload['message']
    2337                         );
     2354                        $test_recipients = $this->get_recipients_by_payload( $payload );
     2355                        $personal = $this->render_personalized_email( $payload, $test_recipients[0] ?? [ 'name' => __( 'Test Customer', 'yoohw-advanced-notifications' ), 'email' => $test_email ] );
     2356                        $test_message = $personal['message'];
    23382357                        $test_message = $this->append_unsubscribe_footer( $test_message, $test_email, $payload['campaign_id'] );
    23392358
    23402359                        $sent = $this->send_woocommerce_email(
    23412360                                $test_email,
    2342                                 '[TEST] ' . $payload['subject'],
    2343                                 $payload['heading'],
     2361                                '[TEST] ' . $personal['subject'],
     2362                                $personal['heading'],
    23442363                                $test_message,
    23452364                                $payload['from_email'],
    … …  
    24882507                }
    24892508
     2509                if ( self::SOURCE_SUBSCRIBED === ( $payload['recipient_source'] ?? '' ) || ! empty( $payload['automation'] ) ) { $email_payload['subscription_required'] = true; }
     2510                if ( ! empty( $payload['automation'] ) ) { $email_payload['automation'] = $payload['automation']; }
     2511                if ( ! empty( $payload['personalization_version'] ) ) { $email_payload['personalization_version'] = absint( $payload['personalization_version'] ); $email_payload['store_name'] = sanitize_text_field( $payload['store_name'] ?? get_bloginfo( 'name' ) ); }
     2512
    24902513                $payload_hash = hash( 'sha256', wp_json_encode( $email_payload ) );
    24912514                $payload_key  = $this->get_campaign_payload_option_name( $campaign_id, $delivery_id );
    … …  
    25652588                $this->campaign_payload_cache[ $payload_key ] = $record;
    25662589
    2567                 foreach ( [ 'subject', 'heading', 'message', 'products_label', 'from_email', 'from_name', 'reply_to_email' ] as $field ) {
     2590                foreach ( [ 'subject', 'heading', 'message', 'products_label', 'from_email', 'from_name', 'reply_to_email', 'personalization_version', 'store_name', 'subscription_required', 'automation' ] as $field ) {
    25682591                        $args[ $field ] = isset( $record['payload'][ $field ] ) ? $record['payload'][ $field ] : '';
    25692592                }
    25702593
     2594                if ( ! empty( $args['personalization_version'] ) ) {
     2595                        $manifest = $this->read_durable_option( $this->campaign_manifest_key( $campaign_id ) );
     2596                        $found = false;
     2597                        foreach ( (array) ( $manifest['recipients'] ?? [] ) as $recipient ) {
     2598                                if ( strtolower( $recipient['email'] ?? '' ) === strtolower( $args['to'] ?? '' ) && ! empty( $recipient['email'] ) ) {
     2599                                        $args['order_context'] = $this->sanitize_order_context( $recipient['order_context'] ?? [] );
     2600                                        $found = true; break;
     2601                                }
     2602                        }
     2603                        if ( ! $found ) { return new WP_Error( 'missing_recipient_context', __( 'Confirmed recipient context is unavailable. No email was handed off.', 'yoohw-advanced-notifications' ) ); }
     2604                }
    25712605                return $args;
    25722606        }
    … …  
    27772811                }
    27782812                $options_valid = $this->validate_recipient_source_options( [
     2813                        'order_ids' => $this->parse_selected_order_ids( wp_unslash( $_POST['order_ids'] ?? '' ) ),
    27792814                        'recipient_source' => $recipient_source, 'user_roles' => $user_roles,
    27802815                        'manual_emails' => $manual_emails, 'test_mode' => $test_mode,
    … …  
    28292864
    28302865        private function generate_campaign_hash( $payload ) {
    2831                 $hash_payload = [ 'filters' => $payload['filters'] ?? [], 'schedule_policy' => $payload['schedule_policy'] ?? '', 'store_timezone' => $payload['store_timezone'] ?? '',
     2866                $hash_payload = [ 'order_ids' => $payload['order_ids'] ?? [], 'personalization_version' => $payload['personalization_version'] ?? 0, 'store_name' => $payload['store_name'] ?? '', 'filters' => $payload['filters'] ?? [], 'schedule_policy' => $payload['schedule_policy'] ?? '', 'store_timezone' => $payload['store_timezone'] ?? '',
    28322867                        'recipient_source' => isset( $payload['recipient_source'] ) ? (string) $payload['recipient_source'] : self::SOURCE_PRODUCT_CUSTOMERS,
    28332868                        'product_ids'      => isset( $payload['product_ids'] ) ? array_map( 'absint', (array) $payload['product_ids'] ) : [],
    … …  
    30753110                );
    30763111
    3077                 foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone', 'schedule_summary' ] as $field ) { if ( isset( $payload[ $field ] ) ) { $entry[ $field ] = $payload[ $field ]; } }
     3112                foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone', 'schedule_summary', 'order_ids', 'personalization_version', 'store_name', 'automation' ] as $field ) { if ( isset( $payload[ $field ] ) ) { $entry[ $field ] = $payload[ $field ]; } }
    30783113                $entry['status_counts'] = isset( $existing['status_counts'] ) && is_array( $existing['status_counts'] )
    30793114                        ? $this->sanitize_count_map( $existing['status_counts'] )
    … …  
    33273362                ];
    33283363
    3329                 foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone' ] as $field ) { if ( isset( $campaign[ $field ] ) ) { $payload[ $field ] = $campaign[ $field ]; } }
     3364                foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone', 'order_ids', 'personalization_version', 'store_name', 'automation' ] as $field ) { if ( isset( $campaign[ $field ] ) ) { $payload[ $field ] = $campaign[ $field ]; } }
    33303365                if ( empty( $payload['products_label'] ) ) {
    33313366                        $payload['products_label'] = ! empty( $payload['product_ids'] )
    … …  
    34293464        private function sanitize_recipient_context( $recipient ) {
    34303465                return [
     3466                        'order_context' => $this->sanitize_order_context( $recipient['order_context'] ?? [] ),
    34313467                        'user_id'  => isset( $recipient['user_id'] ) ? absint( $recipient['user_id'] ) : 0,
    34323468                        'email'    => isset( $recipient['email'] ) ? sanitize_email( $recipient['email'] ) : '',
    … …  
    38993935
    39003936        private function get_recipient_scheduled_delivery( $payload, $recipient, $roll_forward = true, $reference_time = null ) {
     3937                if ( ! empty( $payload['automation']['due'] ) ) { return [ 'timestamp' => (int) $payload['automation']['due'], 'timezone' => 'UTC', 'fallback' => false, 'country' => '', 'state' => '', 'city' => '', 'rolled_forward_days' => 0 ]; }
    39013938                if ( isset( $payload['schedule_policy'] ) ) { return $this->operator_schedule( $payload, $recipient ); }
    39023939                $reference_time = null === $reference_time ? time() : absint( $reference_time );
    … …  
    42514288        private function get_recipient_sources() {
    42524289                $sources = [
     4290                        self::SOURCE_SUBSCRIBED => __( 'Subscribed email addresses', 'yoohw-advanced-notifications' ),
     4291                        self::SOURCE_SELECTED_ORDERS => __( 'Selected orders', 'yoohw-advanced-notifications' ),
    42534292                        self::SOURCE_PRODUCT_CUSTOMERS   => __( 'Product customers', 'yoohw-advanced-notifications' ),
    42544293                        self::SOURCE_WOOCOMMERCE_CUSTOMERS => __( 'WooCommerce customers', 'yoohw-advanced-notifications' ),
    … …  
    42834322
    42844323        private function validate_recipient_source_options( $payload ) {
     4324                $selected = $this->validate_selected_orders( $payload );
     4325                if ( is_wp_error( $selected ) ) { return $selected; }
    42854326                $valid = $this->validate_audience_filters( $payload );
    42864327                if ( is_wp_error( $valid ) ) { return $valid; }
    … …  
    43514392
    43524393                switch ( $recipient_source ) {
     4394                        case self::SOURCE_SUBSCRIBED:
     4395                                return $this->get_subscribed_recipients();
     4396
     4397                        case self::SOURCE_SELECTED_ORDERS:
     4398                                return $this->get_order_customer_recipients( null, $payload['filters'] ?? [], $payload['order_ids'] ?? [] );
     4399
    43534400                        case self::SOURCE_REGISTERED_USERS:
    43544401                                return $this->get_registered_user_recipients( [], isset( $payload['user_roles'] ) ? $payload['user_roles'] : [] );
    … …  
    45024549                $record = $this->read_durable_option( 'yoohw_an_optout_' . hash( 'sha256', $email ) );
    45034550                if ( $this->read_durable_option( 'yoohw_an_erased_' . hash( 'sha256', $email ) ) ) { return true; }
    4504                 if ( is_array( $record ) && ( ! empty( $record['suppressed'] ) || $email === ( $record['email'] ?? '' ) ) ) { return true; }
     4551                if ( is_array( $record ) && ( ! empty( $record['suppressed'] ) || $email === ( $record['email'] ?? '' ) ) ) { return ! $this->confirmed_suppression_override( $email ); }
    45054552                $legacy = $this->normalize_unsubscribed_emails( $this->read_durable_option( self::OPTION_UNSUBSCRIBED_EMAILS ) );
    4506                 return isset( $legacy[ $email ] );
     4553                return isset( $legacy[ $email ] ) && ! $this->confirmed_suppression_override( $email );
    45074554        }
    45084555
    … …  
    45154562                        }
    45164563                }
    4517                 return $this->normalize_unsubscribed_emails( $stored );
     4564                return array_filter( $this->normalize_unsubscribed_emails( $stored ), function ( $record, $email ) { return ! $this->confirmed_suppression_override( $email ); }, ARRAY_FILTER_USE_BOTH );
    45184565        }
    45194566
    … …  
    45594606                try {
    45604607                $key = 'yoohw_an_optout_' . hash( 'sha256', $email );
    4561                 $data = $this->read_durable_option( 'yoohw_an_erased_' . hash( 'sha256', $email ) ) ? [ 'suppressed' => true ] : [ 'email' => $email, 'unsubscribed_at' => time(), 'campaign_id' => sanitize_text_field( $campaign_id ) ];
     4608                $data = $this->read_durable_option( 'yoohw_an_erased_' . hash( 'sha256', $email ) ) ? [ 'suppressed' => true ] : [ 'email' => $email, 'unsubscribed_at' => time(), 'generation' => wp_generate_uuid4(), 'campaign_id' => sanitize_text_field( $campaign_id ) ];
    45624609                $data = $this->scrub_erased_references( $data );
    4563                 $this->write_durable_option( $key, $data, true );
     4610                if ( false === $this->write_durable_option( $key, $data ) ) { return false; }
    45644611                $saved = $this->read_durable_option( $key );
    4565                 return is_array( $saved ) && ( ! empty( $saved['suppressed'] ) || $email === ( $saved['email'] ?? '' ) );
     4612                return $data === $saved;
    45664613                } finally { $this->release_campaign_lock( '__privacy__' ); }
    45674614        }
    … …  
    48864933                }
    48874934                try {
    4888                         $message = str_replace( [ '{customer_name}', '{product_name}' ], [ $args['name'] ?? $to, $args['products_label'] ?? '' ], $args['message'] );
     4935                        $automation_check = $this->automation_delivery_check( $args );
     4936                        if ( is_wp_error( $automation_check ) ) {
     4937                                $outcome = 'automation_cancelled' === $automation_check->get_error_code() ? 'cancelled' : ( 'automation_ineligible' === $automation_check->get_error_code() ? 'skipped' : 'failed' );
     4938                                $this->finish_delivery( $campaign_id, $delivery_id, $to, $claim, $outcome, $automation_check->get_error_message(), 'failed' === $outcome ? 'configuration' : '' ); return;
     4939                        }
     4940                        if ( ! empty( $args['subscription_required'] ) && 'subscribed' !== $this->subscription_record( $to )['status'] ) {
     4941                                $this->finish_delivery( $campaign_id, $delivery_id, $to, $claim, 'skipped_unsubscribed', __( 'No active subscription before send.', 'yoohw-advanced-notifications' ) );
     4942                                return;
     4943                        }
     4944                        $personal = $this->render_personalized_email( $args, array_merge( $args, [ 'email' => $to ] ) );
     4945                        $message = $personal['message'];
    48894946                        $message = $this->append_unsubscribe_footer( $message, $to, $campaign_id );
    48904947                        // Read the durable opt-out immediately before entering the sending path.
    … …  
    48944951                        }
    48954952                        $this->mail_handoff_started = false;
    4896                         $sent = $this->send_woocommerce_email( $to, $args['subject'], $args['heading'], $message, $args['from_email'] ?? '', $args['from_name'] ?? '', $args['reply_to_email'] ?? '' );
    4897                         $status = $sent ? 'sent' : ( $this->mail_handoff_started ? 'unknown' : ( $this->is_email_unsubscribed( $to ) ? 'skipped_unsubscribed' : 'failed' ) );
     4953                        $sent = $this->send_woocommerce_email( $to, $personal['subject'], $personal['heading'], $message, $args['from_email'] ?? '', $args['from_name'] ?? '', $args['reply_to_email'] ?? '', ! empty( $args['subscription_required'] ), $args['automation'] ?? [] );
     4954                        $status = $sent ? 'sent' : ( $this->mail_handoff_started ? 'unknown' : ( ( $this->is_email_unsubscribed( $to ) || ( ! empty( $args['subscription_required'] ) && 'subscribed' !== $this->subscription_record( $to )['status'] ) ) ? 'skipped_unsubscribed' : 'failed' ) );
     4955                        if ( ! $sent && ! $this->mail_handoff_started && $this->last_automation_outcome ) { $status = $this->last_automation_outcome; }
    48984956                        $this->finish_delivery( $campaign_id, $delivery_id, $to, $claim, $status,
    48994957                                $sent ? __( 'Mail system accepted the request; inbox delivery is not verified.', 'yoohw-advanced-notifications' ) : ( $this->last_mail_error ?: __( 'Mail transport returned no confirmation. Check the mail provider before sending again.', 'yoohw-advanced-notifications' ) ),
    … …  
    49675025        }
    49685026
    4969         private function get_order_customer_recipients( $product_ids = null, $filters = [] ) {
     5027        private function get_order_customer_recipients( $product_ids = null, $filters = [], $order_ids = null ) {
     5028                if ( null !== $order_ids && ! $order_ids ) { return []; }
    49705029                $all_products = null === $product_ids;
    49715030                $product_ids = $all_products ? [] : $product_ids;
    … …  
    50245083                        $orders = wc_get_orders(
    50255084                                [
     5085                                        'post__in' => null === $order_ids ? [] : $order_ids,
    50265086                                        'limit'   => self::ORDER_QUERY_LIMIT,
    50275087                                        'paged'   => $page,
    5028                                         'status'  => ! empty( $filters['order_statuses'] ) ? $filters['order_statuses'] : [ 'processing', 'completed' ],
     5088                                        'status'  => ! empty( $filters['order_statuses'] ) ? $filters['order_statuses'] : ( null === $order_ids ? [ 'processing', 'completed' ] : array_keys( wc_get_order_statuses() ) ),
    50295089                                        'return'  => 'objects',
    50305090                                        'orderby' => 'ID',
    … …  
    50425102                                }
    50435103
     5104                                if ( null !== $order_ids && ! in_array( (int) $order->get_id(), $order_ids, true ) ) { continue; }
    50445105                                if ( ! $this->order_matches_filters( $order, $filters ) ) { continue; }
    50455106                                $has_product = $all_products;
    … …  
    50765137                                $email = strtolower( sanitize_email( $order->get_billing_email() ) );
    50775138
    5078                                 if ( ! is_email( $email ) || isset( $customers[ $email ] ) ) {
    5079                                         continue;
    5080                                 }
     5139                                if ( ! is_email( $email ) ) { continue; }
     5140                                if ( isset( $customers[ $email ] ) ) { $customers[ $email ]['order_context']['count']++; continue; }
    50815141
    50825142                                $name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
    … …  
    50865146                                        'email'   => $email,
    50875147                                        'name'    => $name ?: $email,
     5148                                        'order_context' => $this->context_from_order( $order ),
    50885149                                        'country' => $this->normalize_country_code( $order->get_billing_country() ),
    50895150                                        'state'   => $this->normalize_location_part( $order->get_billing_state() ),
    … …  
    52355296        }
    52365297
    5237         private function send_woocommerce_email( $to, $subject, $heading, $message, $from_email = '', $from_name = '', $reply_to_email = '' ) {
     5298        private function send_woocommerce_email( $to, $subject, $heading, $message, $from_email = '', $from_name = '', $reply_to_email = '', $require_subscription = false, $automation = [], $sample_email_hash = '' ) {
     5299                $this->last_automation_outcome = '';
    52385300                $email_body = $this->build_woocommerce_email_body( $heading, $message );
    52395301
    52405302                if ( false === $email_body ) {
    52415303                        $this->last_mail_error = __( 'WooCommerce mailer is unavailable.', 'yoohw-advanced-notifications' );
     5304                        return false;
     5305                }
     5306
     5307                if ( $sample_email_hash && $this->read_durable_option( 'yoohw_an_erased_' . $sample_email_hash ) ) {
     5308                        $this->last_mail_error = __( 'The sample was erased before test mail handoff.', 'yoohw-advanced-notifications' ); return false;
     5309                }
     5310
     5311                $check = $this->automation_delivery_check( [ 'automation' => $automation, 'to' => $to ] );
     5312                if ( is_wp_error( $check ) ) {
     5313                        $this->last_automation_outcome = 'automation_cancelled' === $check->get_error_code() ? 'cancelled' : ( 'automation_ineligible' === $check->get_error_code() ? 'skipped' : 'failed' );
     5314                        $this->last_mail_error = $check->get_error_message(); return false;
     5315                }
     5316
     5317                if ( $require_subscription && 'subscribed' !== $this->subscription_record( $to )['status'] ) {
     5318                        $this->last_mail_error = __( 'No active subscription before mail handoff.', 'yoohw-advanced-notifications' );
    52425319                        return false;
    52435320                }
  • yoohw-advanced-notifications/tags/0.4.0/includes/trait-campaign-audiences.php

    r3771948 r3789014  
    77                $raw = wp_unslash( $_POST['audience_filters'] ?? [] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each scalar/list is type checked, sanitized below and strictly validated by validate_audience_filters().
    88                $filters = [];
    9                 $scalar_keys = [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode' ];
     9                $scalar_keys = [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'purchase_frequency', 'inactive_days' ];
    1010                $list_keys = [ 'category_ids', 'order_statuses' ];
    1111                if ( ! is_array( $raw ) || array_diff( array_keys( $raw ), array_merge( $scalar_keys, $list_keys ) ) ) { return [ 'invalid_input' => true ]; }
    1212                foreach ( $scalar_keys as $key ) { if ( isset( $raw[ $key ] ) && ! is_string( $raw[ $key ] ) ) { return [ 'invalid_input' => true ]; } }
    1313                foreach ( $list_keys as $key ) { if ( isset( $raw[ $key ] ) && ( ! is_array( $raw[ $key ] ) || array_filter( $raw[ $key ], static function ( $value ) { return ! is_string( $value ); } ) ) ) { return [ 'invalid_input' => true ]; } }
    14                 foreach ( [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode' ] as $key ) { $filters[ $key ] = sanitize_text_field( $raw[ $key ] ?? '' ); }
     14                foreach ( [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'purchase_frequency', 'inactive_days' ] as $key ) { $filters[ $key ] = sanitize_text_field( $raw[ $key ] ?? '' ); }
    1515                foreach ( [ 'category_ids', 'order_statuses' ] as $key ) { $filters[ $key ] = array_map( 'sanitize_text_field', (array) ( $raw[ $key ] ?? [] ) ); }
    1616                return $filters;
    … …  
    1919        private function validate_audience_filters( $p ) {
    2020                $f = $p['filters'] ?? [];
    21                 $invalid = new WP_Error( 'invalid_filters', __( 'Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country and campaign history; no condition has been ignored.', 'yoohw-advanced-notifications' ) );
    22                 if ( ! is_array( $f ) || array_diff( array_keys( $f ), [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'category_ids', 'order_statuses' ] ) ) { return $invalid; }
     21                $invalid = new WP_Error( 'invalid_filters', __( 'Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country, purchase history and campaign history; no condition has been ignored.', 'yoohw-advanced-notifications' ) );
     22                if ( ! is_array( $f ) || array_diff( array_keys( $f ), [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'category_ids', 'order_statuses', 'purchase_frequency', 'inactive_days' ] ) ) { return $invalid; }
    2323                foreach ( [ 'category_ids', 'order_statuses' ] as $key ) { if ( isset( $f[ $key ] ) && ! is_array( $f[ $key ] ) ) { return $invalid; } }
     24                if ( ! in_array( $f['purchase_frequency'] ?? '', [ '', 'first', 'repeat' ], true ) ) { return $invalid; }
     25                if ( isset( $f['inactive_days'] ) && '' !== $f['inactive_days'] && ( ( ! is_string( $f['inactive_days'] ) && ! is_int( $f['inactive_days'] ) ) || ! ctype_digit( (string) $f['inactive_days'] ) || (int) $f['inactive_days'] < 1 || (int) $f['inactive_days'] > 36500 ) ) { return $invalid; }
    2426                $source = $p['recipient_source'] ?? '';
    25                 $order_source = in_array( $source, [ self::SOURCE_PRODUCT_CUSTOMERS, self::SOURCE_WOOCOMMERCE_CUSTOMERS ], true );
     27                if ( self::SOURCE_SUBSCRIBED === $source && ( ! empty( $p['product_ids'] ) || ! empty( $p['user_roles'] ) || ! empty( $p['manual_emails'] ) ) ) { return $invalid; }
     28                $order_source = in_array( $source, [ self::SOURCE_PRODUCT_CUSTOMERS, self::SOURCE_WOOCOMMERCE_CUSTOMERS, self::SOURCE_SELECTED_ORDERS ], true );
    2629                foreach ( [ 'date_from', 'date_to', 'last_days', 'category_ids', 'order_statuses' ] as $field ) { if ( ! $order_source && ! empty( $f[ $field ] ) ) { return $invalid; } }
    27                 if ( self::SOURCE_MANUAL_EMAILS === $source && ! empty( $f['country'] ) ) { return $invalid; }
     30                if ( in_array( $source, [ self::SOURCE_MANUAL_EMAILS, self::SOURCE_SUBSCRIBED ], true ) && ! empty( $f['country'] ) ) { return $invalid; }
    2831                foreach ( [ 'date_from', 'date_to' ] as $field ) {
    2932                        if ( isset( $f[ $field ] ) && '' !== $f[ $field ] ) {
    … …  
    6669        }
    6770        private function subscription_record( $email ) {
    68                 if ( $this->is_email_unsubscribed( $email ) ) { return [ 'status' => 'unsubscribed', 'source' => 'suppression' ]; }
    69                 return $this->read_durable_option( 'yoohw_an_subscription_' . hash( 'sha256', strtolower( $email ) ) ) ?: [ 'status' => 'unknown', 'source' => '', 'recorded_at' => 0 ];
     71                $email = strtolower( $email );
     72                $record = $this->is_email_unsubscribed( $email ) ? [ 'status' => 'unsubscribed', 'source' => 'suppression' ] : ( $this->read_durable_option( 'yoohw_an_subscription_' . hash( 'sha256', $email ) ) ?: [ 'status' => 'unknown', 'source' => '', 'recorded_at' => 0 ] );
     73                $pending = $this->read_durable_option( 'yoohw_an_signup_' . hash( 'sha256', $email ) );
     74                if ( is_array( $pending ) && ( $pending['email'] ?? '' ) === $email && ! empty( $pending['token_hash'] ) && ( $pending['expires'] ?? 0 ) > time() ) { $record['confirmation_pending'] = true; $record['request_source'] = $pending['source']; $record['requested_at'] = $pending['requested_at']; }
     75                return $record;
    7076        }
    7177        private function apply_audience_filters( $recipients, $p ) {
    7278                if ( is_wp_error( $this->validate_audience_filters( $p ) ) ) { return []; }
    7379                $f = $p['filters'] ?? [];
     80                $recipients = $this->apply_purchase_history_filters( $recipients, $f );
    7481                $result = [];
    7582                $history = [];
    … …  
    129136        }
    130137        private function render_audience_fields( $p ) {
     138                $this->render_selected_orders_field( $p );
    131139                $f = $p['filters'] ?? [];
    132140                $invalid = is_wp_error( $this->validate_audience_filters( $p ) );
     141                $this->render_purchase_history_fields( $f, $invalid );
    133142                if ( $invalid ) { echo '<p role="alert">' . esc_html__( 'Saved conditions are invalid or unavailable. They remain selected; correct or explicitly remove them before previewing.', 'yoohw-advanced-notifications' ) . '</p>'; }
    134143                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Audience conditions', 'yoohw-advanced-notifications' ) . '</h2><div class="an-order-filters"><div class="an-form-grid an-form-grid-three">';
    135144                foreach ( [ 'date_from' => [ 'date', __( 'Order created from (store timezone)', 'yoohw-advanced-notifications' ) ], 'date_to' => [ 'date', __( 'Through date (inclusive)', 'yoohw-advanced-notifications' ) ], 'last_days' => [ 'number', __( 'Or last N × 24 hours', 'yoohw-advanced-notifications' ) ] ] as $key => $field ) { echo '<div class="an-field"><label>' . esc_html( $field[1] ) . ' <input type="' . esc_attr( $invalid ? 'text' : $field[0] ) . '" name="audience_filters[' . esc_attr( $key ) . ']" value="' . esc_attr( $f[ $key ] ?? '' ) . '"></label></div>'; }
    136                 echo '</div><div class="an-form-grid"><div class="an-field"><label>' . esc_html__( 'Matching order statuses (default processing/completed)', 'yoohw-advanced-notifications' ) . '<select multiple name="audience_filters[order_statuses][]">';
     145                echo '</div><div class="an-form-grid"><div class="an-field"><label>' . esc_html__( 'Matching order statuses (selected orders: all; other sources: processing/completed)', 'yoohw-advanced-notifications' ) . '<select multiple name="audience_filters[order_statuses][]">';
    137146                foreach ( $this->get_order_status_options() as $key => $label ) { echo '<option value="' . esc_attr( $key ) . '" ' . selected( in_array( $key, $f['order_statuses'] ?? [], true ), true, false ) . '>' . esc_html( $label ) . '</option>'; }
    138147                $this->render_unavailable_options( $f['order_statuses'] ?? [], array_keys( $this->get_order_status_options() ) );
    … …  
    156165                $f = array_filter( $p['filters'] ?? [], static function ( $value ) { return '' !== $value && [] !== $value; } );
    157166                if ( ! $f ) { return; }
    158                 $labels = [ 'date_from' => __( 'Order created from', 'yoohw-advanced-notifications' ), 'date_to' => __( 'Through date', 'yoohw-advanced-notifications' ), 'last_days' => __( 'Last N days', 'yoohw-advanced-notifications' ), 'country' => __( 'Billing country', 'yoohw-advanced-notifications' ), 'subscription' => __( 'Subscription status', 'yoohw-advanced-notifications' ), 'history_campaign' => __( 'History campaign', 'yoohw-advanced-notifications' ), 'history_mode' => __( 'Handoff condition', 'yoohw-advanced-notifications' ), 'category_ids' => __( 'Categories including descendants', 'yoohw-advanced-notifications' ), 'order_statuses' => __( 'Matching order statuses', 'yoohw-advanced-notifications' ) ];
     167                $labels = [ 'purchase_frequency' => __( 'Store-wide purchase frequency', 'yoohw-advanced-notifications' ), 'inactive_days' => __( 'Days without a valid store-wide purchase', 'yoohw-advanced-notifications' ), 'date_from' => __( 'Order created from', 'yoohw-advanced-notifications' ), 'date_to' => __( 'Through date', 'yoohw-advanced-notifications' ), 'last_days' => __( 'Last N days', 'yoohw-advanced-notifications' ), 'country' => __( 'Billing country', 'yoohw-advanced-notifications' ), 'subscription' => __( 'Subscription status', 'yoohw-advanced-notifications' ), 'history_campaign' => __( 'History campaign', 'yoohw-advanced-notifications' ), 'history_mode' => __( 'Handoff condition', 'yoohw-advanced-notifications' ), 'category_ids' => __( 'Categories including descendants', 'yoohw-advanced-notifications' ), 'order_statuses' => __( 'Matching order statuses', 'yoohw-advanced-notifications' ) ];
    159168                echo '<h3>' . esc_html__( 'Saved audience conditions', 'yoohw-advanced-notifications' ) . '</h3><table class="widefat striped">';
    160169                foreach ( $f as $key => $value ) {
    161170                        $values = (array) $value;
     171                        if ( 'purchase_frequency' === $key ) { $options = [ 'first' => __( 'First-time buyer (one valid order)', 'yoohw-advanced-notifications' ), 'repeat' => __( 'Repeat buyer (at least two valid orders)', 'yoohw-advanced-notifications' ) ]; $values = [ $options[ $value ] ?? $value ]; }
    162172                        if ( 'order_statuses' === $key ) { $options = $this->get_order_status_options(); $values = array_map( static function ( $status ) use ( $options ) { return $options[ $status ] ?? $status; }, $values ); }
    163173                        if ( 'category_ids' === $key ) { $values = array_map( static function ( $id ) { $term = get_term( (int) $id, 'product_cat' ); return $term && ! is_wp_error( $term ) ? $term->name . ' (#' . $id . ')' : '#' . $id; }, $values ); }
    … …  
    177187                $reason = __( 'Not in the selected source or product purchasers.', 'yoohw-advanced-notifications' );
    178188                if ( in_array( $email, array_column( $baseline, 'email' ), true ) ) { $reason = __( 'No single order matches all order conditions, or billing country is missing/different.', 'yoohw-advanced-notifications' ); }
    179                 if ( in_array( $email, array_column( $filtered, 'email' ), true ) ) { $reason = __( 'Subscription status or known campaign handoff condition does not match.', 'yoohw-advanced-notifications' ); }
     189                if ( in_array( $email, array_column( $filtered, 'email' ), true ) ) { $reason = __( 'Store-wide purchase history, subscription status or known campaign handoff condition does not match.', 'yoohw-advanced-notifications' ); }
    180190                if ( in_array( $email, array_column( $filtered_common, 'email' ), true ) ) { $reason = __( 'Matched source and all conditions; removed by an explicit exclusion.', 'yoohw-advanced-notifications' ); }
    181191                if ( in_array( $email, array_column( $this->get_recipients_by_payload( $p ), 'email' ), true ) ) { $reason = __( 'Included: matches source and every applicable condition; no exclusion matched.', 'yoohw-advanced-notifications' ); }
  • yoohw-advanced-notifications/tags/0.4.0/includes/trait-campaign-authoring.php

    r3771948 r3789014  
    1212        }
    1313        private function draft_fields() {
    14                 return [ 'internal_name', 'subject', 'heading', 'message', 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'schedule_mode', 'scheduled_at_input', 'filters', 'test_email' ];
    15         }
    16         private function criteria_fields() { return [ 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'filters' ]; }
     14                return [ 'order_ids', 'personalization_version', 'internal_name', 'subject', 'heading', 'message', 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'schedule_mode', 'scheduled_at_input', 'filters', 'test_email' ];
     15        }
     16        private function criteria_fields() { return [ 'order_ids', 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'filters' ]; }
    1717        private function save_draft( $payload, $id = '' ) {
    1818                $id = $id ?: ( $payload['draft_id'] ?? wp_generate_uuid4() );
    … …  
    3737                $p['exclude_emails_raw'] = sanitize_textarea_field( $post['exclude_emails'] ?? '' );
    3838                $p['exclusions'] = [ 'product_ids' => array_map( 'absint', (array) ( $post['exclude_product_ids'] ?? [] ) ), 'order_statuses' => array_map( 'sanitize_key', (array) ( $post['exclude_order_statuses'] ?? [] ) ), 'emails' => $this->parse_email_list( $p['exclude_emails_raw'] ) ];
     39                $p['order_ids'] = $this->parse_selected_order_ids( $post['order_ids'] ?? '' );
     40                if ( is_wp_error( $p['order_ids'] ) ) { wp_die( esc_html( $p['order_ids']->get_error_message() ) ); }
     41                $p['personalization_version'] = 1;
     42                $p['store_name'] = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
    3943                $p['filters'] = $this->filters_from_request();
    4044                return $p;
    … …  
    6165                $p['internal_name'] = sanitize_text_field( wp_unslash( $_POST['internal_name'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Caller verifies nonce.
    6266                $p['filters'] = $this->filters_from_request();
     67                $p['order_ids'] = $this->parse_selected_order_ids( wp_unslash( $_POST['order_ids'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Caller verifies nonce.
     68                if ( is_wp_error( $p['order_ids'] ) ) { return $p['order_ids']; }
     69                $p['personalization_version'] = 1;
     70                $p['store_name'] = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
     71                $valid = $this->validate_selected_orders( $p );
     72                if ( is_wp_error( $valid ) ) { return $valid; }
    6373                $p['schedule_policy'] = 'strict_v1';
    6474                $p['store_timezone'] = wp_timezone_string();
    … …  
    120130                if ( ! $drafts ) { echo '<p>' . esc_html__( 'No saved drafts yet.', 'yoohw-advanced-notifications' ) . '</p>'; }
    121131                foreach ( $drafts as $draft ) {
    122                         echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'draft_id' => $draft['draft_id'] ] ) ) . '">' . esc_html( $draft['internal_name'] ?: ( $draft['subject'] ?: $draft['draft_id'] ) ) . '</a>';
     132                        echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'draft_id' => $draft['draft_id'] ] ) ) . '">' . esc_html( ( $draft['internal_name'] ?? '' ) ?: ( ( $draft['subject'] ?? '' ) ?: $draft['draft_id'] ) ) . '</a>';
    123133                        $this->library_button( $draft['draft_id'], 'delete_draft', __( 'Delete draft', 'yoohw-advanced-notifications' ) );
    124134                        echo '</div>';
    … …  
    128138                if ( ! $audiences ) { echo '<p>' . esc_html__( 'No saved audiences yet.', 'yoohw-advanced-notifications' ) . '</p>'; }
    129139                foreach ( $audiences as $audience ) {
    130                         echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ) . '</a>';
     140                        echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ?? $audience['id'] ) . '</a>';
    131141                        $this->library_button( $audience['id'], 'delete_audience', __( 'Delete audience', 'yoohw-advanced-notifications' ) );
    132142                        echo '</div>';
    … …  
    140150                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Saved audience criteria', 'yoohw-advanced-notifications' ) . '</h2><p>';
    141151                foreach ( $this->read_durable_options( 'yoohw_an_audience_' ) as $audience ) {
    142                         echo '<a class="button" href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ) . '</a> ';
     152                        echo '<a class="button" href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ?? $audience['id'] ) . '</a> ';
    143153                }
    144154                echo '<a href="' . esc_url( $this->get_admin_page_url( [ 'tab' => 'library' ] ) ) . '">' . esc_html__( 'Open Library', 'yoohw-advanced-notifications' ) . '</a></p><p class="description">' . esc_html__( 'Opening saved criteria replaces this form. Save your draft first to keep current edits.', 'yoohw-advanced-notifications' ) . '</p><input type="hidden" name="audience_id" value="' . esc_attr( $p['audience_id'] ?? '' ) . '"><div class="an-form-row"><label class="an-field">' . esc_html__( 'Audience name', 'yoohw-advanced-notifications' ) . ' <input name="audience_name" value="' . esc_attr( $p['audience_name'] ?? '' ) . '"></label> <button class="button" formnovalidate name="authoring_operation" value="save_audience">' . esc_html__( 'Save audience criteria', 'yoohw-advanced-notifications' ) . '</button></div></section>';
    … …  
    147157                echo '<section class="yoohw-advanced-notifications-panel an-template-field"><label for="an-template">' . esc_html__( 'Insert editable content template', 'yoohw-advanced-notifications' ) . '</label><p><select id="an-template"><option value="">—</option>';
    148158                $templates = [
    149                         'update' => [ __( 'Product or documentation update', 'yoohw-advanced-notifications' ), __( 'An update for you', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>We have updated {product_name}. Add the details and documentation link here.</p>', 'yoohw-advanced-notifications' ) ],
    150                         'guide' => [ __( 'After-purchase guide', 'yoohw-advanced-notifications' ), __( 'Getting started', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>Thank you for your purchase. Add setup steps and support details here.</p>', 'yoohw-advanced-notifications' ) ],
    151                         'service' => [ __( 'Service or order change', 'yoohw-advanced-notifications' ), __( 'Service update', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>Add the change, effective date and any action needed here.</p>', 'yoohw-advanced-notifications' ) ],
     159                        'update' => [ __( 'Product or documentation update', 'yoohw-advanced-notifications' ), __( 'An update for you', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>We have updated {product_name}. Describe what changed, when it applies, and add the documentation link and any action needed. Contact our support team if you need help.</p>', 'yoohw-advanced-notifications' ) ],
     160                        'guide' => [ __( 'After-purchase guide', 'yoohw-advanced-notifications' ), __( 'Getting started', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>Thank you for your purchase of {ordered_products} (order {order_number}). Add setup or care steps, when to follow them, and a support contact here.</p>', 'yoohw-advanced-notifications' ) ],
     161                        'service' => [ __( 'Service or order change', 'yoohw-advanced-notifications' ), __( 'Service update', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>An update about order {order_number}: describe the change and the new delivery date or effective time. Explain any action needed and add a support contact here.</p>', 'yoohw-advanced-notifications' ) ],
    152162                ];
    153163                foreach ( $templates as $key => $template ) { echo '<option value="' . esc_attr( $key ) . '" data-subject="' . esc_attr( $template[1] ) . '" data-body="' . esc_attr( $template[2] ) . '">' . esc_html( $template[0] ) . '</option>'; }
    … …  
    217227                if ( ! empty( $_GET['audience_changed'] ) ) { echo '<p class="notice notice-warning" role="alert">' . esc_html__( 'Audience changed since preview. Review this updated preview and confirm again.', 'yoohw-advanced-notifications' ) . '</p>'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    218228                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Recipients and schedule to confirm', 'yoohw-advanced-notifications' ) . '</h2><dl class="an-preview-counts"><div><dt>' . esc_html__( 'Selected', 'yoohw-advanced-notifications' ) . '</dt><dd>' . esc_html( number_format_i18n( $p['recipient_count'] ?? 0 ) ) . '</dd></div><div><dt>' . esc_html__( 'Excluded', 'yoohw-advanced-notifications' ) . '</dt><dd>' . esc_html( number_format_i18n( $p['excluded_count'] ?? 0 ) ) . '</dd></div></dl>';
    219                 $reason_labels = [ 'order_conditions' => __( 'Removed by order conditions', 'yoohw-advanced-notifications' ), 'subscription_or_history' => __( 'Removed by subscription or campaign history', 'yoohw-advanced-notifications' ), 'explicit_exclusions_or_suppression' => __( 'Removed by exclusions or suppression', 'yoohw-advanced-notifications' ) ];
     229                $reason_labels = [ 'order_conditions' => __( 'Removed by order conditions', 'yoohw-advanced-notifications' ), 'subscription_or_history' => __( 'Removed by purchase history, subscription or campaign history', 'yoohw-advanced-notifications' ), 'explicit_exclusions_or_suppression' => __( 'Removed by exclusions or suppression', 'yoohw-advanced-notifications' ) ];
    220230                echo '<dl class="an-review-facts">';
    221231                foreach ( $p['audience_audit'] ?? [] as $reason => $count ) { echo '<div><dt>' . esc_html( $reason_labels[ $reason ] ?? $reason ) . '</dt><dd>' . esc_html( number_format_i18n( $count ) ) . '</dd></div>'; }
    … …  
    239249                        echo '</select></div><button class="button">' . esc_html__( 'Show sample', 'yoohw-advanced-notifications' ) . '</button></form>';
    240250                        $this->render_preview_device_controls();
    241                         $message = str_replace( [ '{customer_name}', '{product_name}' ], [ $sample['name'] ?: $sample['email'], $p['products_label'] ?? '' ], $p['message'] );
     251                        $personal = $this->render_personalized_email( $p, $sample );
     252                        echo '<p>' . esc_html( $personal['subject'] ) . '</p>';
     253                        $this->render_order_context_summary( $sample );
     254                        $message = $personal['message'];
    242255                        $message = $this->append_unsubscribe_footer( $message, $sample['email'], $p['campaign_id'] );
    243                         $html = $this->build_woocommerce_email_body( $p['heading'], $message );
     256                        $html = $this->build_woocommerce_email_body( $personal['heading'], $message );
    244257                        echo '<iframe class="an-personal-preview" sandbox="" title="' . esc_attr__( 'Personalized email preview', 'yoohw-advanced-notifications' ) . '" srcdoc="' . esc_attr( $html ) . '"></iframe></section>';
    245258                }
  • yoohw-advanced-notifications/tags/0.4.0/includes/trait-campaign-privacy.php

    r3771948 r3789014  
    121121                echo '<div class="wrap yoohw-an-workspace"><h1>' . esc_html__( 'Email campaigns', 'yoohw-advanced-notifications' ) . '</h1>';
    122122                $this->render_page_tabs( 'subscribers' );
     123                $this->render_contact_history();
    123124                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Subscribers and suppression', 'yoohw-advanced-notifications' ) . '</h2><p>' . esc_html__( 'An order, account or pasted email is not opt-in evidence. Unknown remains unknown. Imports never remove an unsubscribe. For subscribed records, enter the actual evidence source; the recorded time is the import time, not an inferred original consent time.', 'yoohw-advanced-notifications' ) . '</p>';
    124125                $search = strtolower( sanitize_text_field( wp_unslash( $_GET['subscription_search'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    … …  
    130131                foreach ( $this->read_durable_options( 'yoohw_an_subscription_' ) as $record ) { if ( ! empty( $record['email'] ) ) { $records[ $record['email'] ] = $record; } }
    131132                foreach ( $this->get_unsubscribed_emails() as $email => $record ) { $records[ $email ] = array_merge( (array) $record, $records[ $email ] ?? [], [ 'status' => 'unsubscribed' ] ); }
    132                 if ( is_email( $search ) && ! isset( $records[ $search ] ) ) { $records[ $search ] = $this->subscription_record( $search ); }
     133                if ( is_email( $search ) ) { $records[ $search ] = $this->subscription_record( $search ); }
    133134                $count = 0;
    134135                foreach ( $records as $email => $record ) {
    … …  
    138139                        $timestamp = $record['recorded_at'] ?? ( $record['unsubscribed_at'] ?? 0 );
    139140                        echo '<tr><td>' . esc_html( $email . ' / ' . $record['status'] ) . '</td><td>' . esc_html( ( $record['source'] ?? 'legacy/unsubscribe' ) . ' / ' . ( $timestamp ? gmdate( 'Y-m-d H:i:s', is_numeric( $timestamp ) ? (int) $timestamp : strtotime( $timestamp ) ) : '—' ) ) . '</td></tr>';
     141                        if ( ! empty( $record['confirmation_pending'] ) ) { echo '<tr><td colspan="2">' . esc_html__( 'Confirmation pending; current subscription unchanged.', 'yoohw-advanced-notifications' ) . ' ' . esc_html( $record['request_source'] . ' / ' . gmdate( 'Y-m-d H:i:s', $record['requested_at'] ) ) . '</td></tr>'; }
    140142                } echo '</table><p>' . esc_html__( 'Up to 200 matches. Erased addresses are represented only by protective hashes and are not listed as plaintext.', 'yoohw-advanced-notifications' ) . '</p>';
    141143                echo '</section><section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Import records', 'yoohw-advanced-notifications' ) . '</h2><p>' . esc_html__( '1. Paste → 2. Preview → 3. Apply and review result', 'yoohw-advanced-notifications' ) . '</p><form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '"><input type="hidden" name="action" value="yoohw_an_data"><input type="hidden" name="operation" value="preview_import">'; wp_nonce_field( 'yoohw_an_data' );
    … …  
    179181                        $fields = [];
    180182                        foreach ( $row as $name => $value ) { $fields[] = [ 'name' => $name, 'value' => $value ]; }
     183                        $manifest = $this->read_durable_option( $this->campaign_manifest_key( $row['campaign_id'] ) );
     184                        foreach ( (array) ( $manifest['recipients'] ?? [] ) as $recipient ) {
     185                                if ( strtolower( $recipient['email'] ?? '' ) === $email && ! empty( $recipient['order_context'] ) ) { $fields[] = [ 'name' => __( 'Saved order context', 'yoohw-advanced-notifications' ), 'value' => wp_json_encode( $this->sanitize_order_context( $recipient['order_context'] ) ) ]; }
     186                        }
    181187                        $data[] = [ 'group_id' => 'yoohw-an', 'group_label' => __( 'Email campaigns', 'yoohw-advanced-notifications' ), 'item_id' => 'yoohw-an-' . $row['campaign_id'] . '-' . $page . '-' . $index, 'data' => $fields ];
    182188                }
    183189                if ( 1 === (int) $page ) {
    184190                        $record = $this->subscription_record( $email );
     191                        $pending = $this->read_durable_option( 'yoohw_an_signup_' . $hash );
     192                        if ( is_array( $pending ) && ( $pending['email'] ?? '' ) === $email ) { $record['pending_request'] = array_intersect_key( $pending, array_flip( [ 'source', 'requested_at', 'expires', 'consumed_at' ] ) ); }
    185193                        $record['suppression_evidence'] = $this->read_durable_option( 'yoohw_an_optout_' . $hash ) ?: ( $this->get_unsubscribed_emails()[ $email ] ?? [] );
    186194                        $record['protective_erasure_hash'] = (bool) $this->read_durable_option( 'yoohw_an_erased_' . $hash );
    … …  
    203211                if ( is_array( $value ) ) {
    204212                        if ( strtolower( $value['email'] ?? $value['to'] ?? '' ) === $email ) {
    205                                 foreach ( [ 'email', 'to', 'name', 'recipient_name', 'country', 'state', 'city', 'timezone', 'user_id' ] as $field ) { if ( isset( $value[ $field ] ) ) { $value[ $field ] = ''; } }
     213                                foreach ( [ 'email', 'to', 'name', 'recipient_name', 'country', 'state', 'city', 'timezone', 'user_id', 'order_context' ] as $field ) { if ( isset( $value[ $field ] ) ) { $value[ $field ] = ''; } }
    206214                        }
    207215                        $result = [];
  • yoohw-advanced-notifications/tags/0.4.0/languages/yoohw-advanced-notifications.pot

    r3771948 r3789014  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Advanced Notifications for WooCommerce 0.3.0\n"
     5"Project-Id-Version: Advanced Notifications for WooCommerce 0.4.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/yoohw-advanced-notifications\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-29T02:04:10+00:00\n"
     12"POT-Creation-Date: 2026-11-07T03:47:04+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.9.0\n"
    … …  
    3535msgstr ""
    3636
    37 #: includes/class-customer-email-notify.php:161
     37#: includes/class-customer-email-notify.php:184
    3838msgid "Plugin deactivated before delivery."
    3939msgstr ""
    4040
    41 #: includes/class-customer-email-notify.php:365
     41#: includes/class-customer-email-notify.php:389
    4242msgid "Privacy cleanup is busy. Campaign preparation will resume."
    4343msgstr ""
    4444
    45 #: includes/class-customer-email-notify.php:372
     45#: includes/class-customer-email-notify.php:396
    4646msgid "Campaign recipients could not be prepared for background dispatch."
    4747msgstr ""
    4848
    49 #: includes/class-customer-email-notify.php:378
     49#: includes/class-customer-email-notify.php:402
    5050msgid "Recipient tracking storage is unavailable."
    5151msgstr ""
    5252
    53 #: includes/class-customer-email-notify.php:413
     53#: includes/class-customer-email-notify.php:437
    5454msgid "Invalid scheduled date/time. Create a new campaign with a valid time."
    5555msgstr ""
    5656
    57 #: includes/class-customer-email-notify.php:475
     57#: includes/class-customer-email-notify.php:499
    5858msgid "Recipient dispatch storage failed."
    5959msgstr ""
    6060
    61 #: includes/class-customer-email-notify.php:712
     61#: includes/class-customer-email-notify.php:736
    6262msgid "Recipient preparation reached the limit of 3 queue attempts. Check Scheduled Actions for jobs saved before the interruption. Existing jobs may finish; recovery will not create further jobs for these recipients."
    6363msgstr ""
    6464
    65 #: includes/class-customer-email-notify.php:718
     65#: includes/class-customer-email-notify.php:742
    6666msgid "Dispatcher could not be queued. Check Action Scheduler, then use Recover / retry eligible recipients."
    6767msgstr ""
    6868
    69 #: includes/class-customer-email-notify.php:727
     69#: includes/class-customer-email-notify.php:751
    7070msgid "Recipient snapshot is incomplete. Recovery uses only saved recipients; it cannot recreate a lost historical audience. Review the stored records before creating a new campaign."
    7171msgstr ""
    7272
    73 #: includes/class-customer-email-notify.php:731
     73#: includes/class-customer-email-notify.php:755
    7474msgid "Delivery jobs are overdue. Check WordPress cron and WooCommerce Scheduled Actions. Existing pending jobs are retained; recovery will not create another send while they remain pending."
    7575msgstr ""
    7676
    77 #: includes/class-customer-email-notify.php:735
     77#: includes/class-customer-email-notify.php:759
    7878msgid "Some outcomes are unknown. Check the mail provider logs before creating another campaign for those addresses. Recovery never resends unknown outcomes."
    7979msgstr ""
    8080
    81 #: includes/class-customer-email-notify.php:737
     81#: includes/class-customer-email-notify.php:761
    8282msgid "Review recipient reasons below. Queue failures retry at least 5 minutes apart, up to 3 queue attempts. Correct configuration failures before recovery. Cancelled recipients require a new campaign."
    8383msgstr ""
    8484
    85 #: includes/class-customer-email-notify.php:742
     85#: includes/class-customer-email-notify.php:766
    8686msgid "Paused. Resume to continue remaining recipients."
    8787msgstr ""
    8888
    89 #: includes/class-customer-email-notify.php:763
    90 #: includes/class-customer-email-notify.php:764
    91 #: includes/class-customer-email-notify.php:848
    92 #: includes/class-customer-email-notify.php:1356
    93 #: includes/class-customer-email-notify.php:1839
    94 #: includes/trait-campaign-authoring.php:112
     89#: includes/class-customer-email-notify.php:787
     90#: includes/class-customer-email-notify.php:788
     91#: includes/class-customer-email-notify.php:874
     92#: includes/class-customer-email-notify.php:1383
     93#: includes/class-customer-email-notify.php:1866
     94#: includes/trait-campaign-authoring.php:122
     95#: includes/trait-campaign-automation-ui.php:64
    9596#: includes/trait-campaign-privacy.php:121
    96 #: includes/trait-campaign-privacy.php:181
    9797#: includes/trait-campaign-privacy.php:187
    98 #: includes/trait-campaign-privacy.php:198
     98#: includes/trait-campaign-privacy.php:195
     99#: includes/trait-campaign-privacy.php:206
    99100msgid "Email campaigns"
    100101msgstr ""
    101102
    102 #: includes/class-customer-email-notify.php:854
    103 #: includes/class-customer-email-notify.php:2879
     103#: includes/class-customer-email-notify.php:880
     104#: includes/class-customer-email-notify.php:2914
    104105msgid "The email preview expired. Please review the form and try again."
    105106msgstr ""
    106107
    107 #: includes/class-customer-email-notify.php:860
     108#: includes/class-customer-email-notify.php:886
    108109msgid "The shared campaign content could not be stored safely, so no email jobs were created. Review the campaign log or server error log before retrying."
    109110msgstr ""
    110111
    111 #: includes/class-customer-email-notify.php:866
     112#: includes/class-customer-email-notify.php:892
    112113msgid "Draft restored. You can edit it and preview recipients again."
    113114msgstr ""
    114115
    115 #: includes/class-customer-email-notify.php:870
     116#: includes/class-customer-email-notify.php:896
    116117msgid "The draft preview expired, so the form could not be restored."
    117118msgstr ""
    118119
    119 #: includes/class-customer-email-notify.php:876
     120#: includes/class-customer-email-notify.php:902
    120121msgid "Recipients could not be prepared for background delivery, so no new email jobs were created."
    121122msgstr ""
    122123
    123124#. translators: %d: Number of recipients.
    124 #: includes/class-customer-email-notify.php:886
     125#: includes/class-customer-email-notify.php:912
    125126msgid "%d recipient(s) are being prepared in the background. You can leave this page; campaign status will update automatically."
    126127msgstr ""
    127128
    128129#. translators: %d: Number of recipients.
    129 #: includes/class-customer-email-notify.php:901
     130#: includes/class-customer-email-notify.php:927
    130131msgid "Email queued for %d recipient(s). Emails will be sent in the background."
    131132msgstr ""
    132133
    133134#. translators: 1: Number of recipients. 2: Scheduled local date and time.
    134 #: includes/class-customer-email-notify.php:917
     135#: includes/class-customer-email-notify.php:943
    135136msgid "Email scheduled for %1$d recipient(s). Local send time: %2$s. Timezone is derived from each recipient billing country; missing country uses UTC."
    136137msgstr ""
    137138
    138 #: includes/class-customer-email-notify.php:919
     139#: includes/class-customer-email-notify.php:945
    139140msgid "the selected time"
    140141msgstr ""
    141142
    142143#. translators: %d: Number of recipients.
    143 #: includes/class-customer-email-notify.php:933
     144#: includes/class-customer-email-notify.php:959
    144145msgid "This campaign was already queued recently for %d recipient(s), so the duplicate submission was blocked."
    145146msgstr ""
    146147
    147 #: includes/class-customer-email-notify.php:947
     148#: includes/class-customer-email-notify.php:973
    148149msgid "Test email handed to the mail system. Inbox delivery is not verified."
    149150msgstr ""
    150151
    151 #: includes/class-customer-email-notify.php:948
     152#: includes/class-customer-email-notify.php:974
    152153msgid "Test email could not be sent."
    153154msgstr ""
    154155
    155156#. translators: %d: Number of recipients.
    156 #: includes/class-customer-email-notify.php:960
     157#: includes/class-customer-email-notify.php:986
    157158msgid "Email sent to %d recipient(s)."
    158159msgstr ""
    159160
    160 #: includes/class-customer-email-notify.php:971
    161 #: includes/class-customer-email-notify.php:1581
    162 #: includes/class-customer-email-notify.php:1854
     161#: includes/class-customer-email-notify.php:997
     162#: includes/class-customer-email-notify.php:1608
     163#: includes/class-customer-email-notify.php:1881
    163164msgid "Sender"
    164165msgstr ""
    165166
    166 #: includes/class-customer-email-notify.php:975
    167 #: includes/class-customer-email-notify.php:1595
    168 #: includes/class-customer-email-notify.php:2027
     167#: includes/class-customer-email-notify.php:1001
     168#: includes/class-customer-email-notify.php:1622
     169#: includes/class-customer-email-notify.php:2054
    169170msgid "Reply-To"
    170171msgstr ""
    171172
    172173#. translators: %s: effective no-reply address.
    173 #: includes/class-customer-email-notify.php:982
    174 #: includes/class-customer-email-notify.php:1603
    175 #: includes/class-customer-email-notify.php:2035
     174#: includes/class-customer-email-notify.php:1008
     175#: includes/class-customer-email-notify.php:1630
     176#: includes/class-customer-email-notify.php:2062
    176177msgid "%s (automatic replies suppressed)"
    177178msgstr ""
    178179
    179 #: includes/class-customer-email-notify.php:991
     180#: includes/class-customer-email-notify.php:1017
    180181msgid "Edit Settings"
    181182msgstr ""
    182183
    183 #: includes/class-customer-email-notify.php:1001
     184#: includes/class-customer-email-notify.php:1027
    184185msgid "Compose steps"
    185186msgstr ""
    186187
    187 #: includes/class-customer-email-notify.php:1002
     188#: includes/class-customer-email-notify.php:1028
    188189msgid "1. Audience"
    189190msgstr ""
    190191
    191 #: includes/class-customer-email-notify.php:1002
     192#: includes/class-customer-email-notify.php:1028
     193#: includes/trait-campaign-automation-ui.php:94
    192194msgid "2. Content"
    193195msgstr ""
    194196
    195 #: includes/class-customer-email-notify.php:1002
     197#: includes/class-customer-email-notify.php:1028
    196198msgid "3. Schedule"
    197199msgstr ""
    198200
    199 #: includes/class-customer-email-notify.php:1002
     201#: includes/class-customer-email-notify.php:1028
     202#: includes/trait-campaign-automation-ui.php:94
    200203msgid "4. Review"
    201204msgstr ""
    202205
    203 #: includes/class-customer-email-notify.php:1012
    204 #: includes/class-customer-email-notify.php:1419
    205 #: includes/class-customer-email-notify.php:1431
    206 #: includes/class-customer-email-notify.php:1549
     206#: includes/class-customer-email-notify.php:1038
     207#: includes/class-customer-email-notify.php:1446
     208#: includes/class-customer-email-notify.php:1458
     209#: includes/class-customer-email-notify.php:1576
    207210msgid "Audience"
    208211msgstr ""
    209212
    210 #: includes/class-customer-email-notify.php:1013
     213#: includes/class-customer-email-notify.php:1039
    211214msgid "Choose who should receive this campaign."
    212215msgstr ""
    213216
    214 #: includes/class-customer-email-notify.php:1017
     217#: includes/class-customer-email-notify.php:1043
    215218msgid "Some selections do not apply to this recipient source. Clear the highlighted selections explicitly or choose a compatible source before previewing."
    216219msgstr ""
    217220
    218 #: includes/class-customer-email-notify.php:1020
    219 #: includes/class-customer-email-notify.php:1974
     221#: includes/class-customer-email-notify.php:1046
     222#: includes/class-customer-email-notify.php:2001
    220223msgid "Recipient source"
    221224msgstr ""
    222225
    223 #: includes/class-customer-email-notify.php:1028
     226#: includes/class-customer-email-notify.php:1054
    224227msgid "Product selection is only required for product customers."
    225228msgstr ""
    226229
    227 #: includes/class-customer-email-notify.php:1031
    228 #: includes/class-customer-email-notify.php:1979
     230#: includes/class-customer-email-notify.php:1057
     231#: includes/class-customer-email-notify.php:2006
    229232msgid "User roles"
    230233msgstr ""
    231234
    232 #: includes/class-customer-email-notify.php:1032
    233 #: includes/class-customer-email-notify.php:4264
     235#: includes/class-customer-email-notify.php:1058
     236#: includes/class-customer-email-notify.php:4303
    234237msgid "All roles"
    235238msgstr ""
    236239
    237 #: includes/class-customer-email-notify.php:1037
     240#: includes/class-customer-email-notify.php:1063
    238241msgid "Match any selected role. Leave empty for all registered users, including administrators."
    239242msgstr ""
    240243
    241 #: includes/class-customer-email-notify.php:1040
     244#: includes/class-customer-email-notify.php:1066
    242245msgid "Recipient email addresses"
    243246msgstr ""
    244247
    245 #: includes/class-customer-email-notify.php:1042
     248#: includes/class-customer-email-notify.php:1068
    246249msgid "Enter email addresses separated by commas, semicolons, spaces, or line breaks. Duplicates are removed. Invalid addresses must be corrected. Recipient-local scheduling uses UTC for this list; store-time scheduling uses the store timezone."
    247250msgstr ""
    248251
    249 #: includes/class-customer-email-notify.php:1044
     252#: includes/class-customer-email-notify.php:1070
    250253msgid "Includes billing email addresses from matching orders, including guests (processing/completed by default). Each email address is included once."
    251254msgstr ""
    252255
    253 #: includes/class-customer-email-notify.php:1047
    254 #: includes/class-customer-email-notify.php:1985
     256#. translators: %d: Number of hours after the Completed event.
     257#: includes/class-customer-email-notify.php:1073
     258#: includes/class-customer-email-notify.php:2012
     259#: includes/trait-campaign-automation-ui.php:84
    255260msgid "Products"
    256261msgstr ""
    257262
    258 #: includes/class-customer-email-notify.php:1054
     263#: includes/class-customer-email-notify.php:1080
     264#: includes/class-customer-email-notify.php:1125
     265#: includes/trait-campaign-automation-ui.php:99
     266msgid "Search products or variations"
     267msgstr ""
     268
     269#: includes/class-customer-email-notify.php:1085
     270msgid "Include matching purchases of selected products or variations."
     271msgstr ""
     272
     273#: includes/class-customer-email-notify.php:1093
     274#: includes/class-customer-email-notify.php:1592
     275#: includes/class-customer-email-notify.php:2031
     276msgid "Exclusions"
     277msgstr ""
     278
     279#: includes/class-customer-email-notify.php:1094
     280msgid "Remove matched recipients from the final list."
     281msgstr ""
     282
    259283#: includes/class-customer-email-notify.php:1099
    260 msgid "Search products or variations"
    261 msgstr ""
    262 
    263 #: includes/class-customer-email-notify.php:1059
    264 msgid "Include matching purchases of selected products or variations."
    265 msgstr ""
    266 
    267 #: includes/class-customer-email-notify.php:1067
    268 #: includes/class-customer-email-notify.php:1565
    269 #: includes/class-customer-email-notify.php:2004
    270 msgid "Exclusions"
    271 msgstr ""
    272 
    273 #: includes/class-customer-email-notify.php:1068
    274 msgid "Remove matched recipients from the final list."
    275 msgstr ""
    276 
    277 #: includes/class-customer-email-notify.php:1073
    278284msgid "Order statuses"
    279285msgstr ""
    280286
    281 #: includes/class-customer-email-notify.php:1080
     287#: includes/class-customer-email-notify.php:1106
    282288msgid "No order statuses excluded"
    283289msgstr ""
    284290
    285 #: includes/class-customer-email-notify.php:1088
     291#: includes/class-customer-email-notify.php:1114
    286292msgid "Example: exclude users with refunded orders."
    287293msgstr ""
    288294
    289 #: includes/class-customer-email-notify.php:1092
     295#: includes/class-customer-email-notify.php:1118
    290296msgid "Product purchasers"
    291297msgstr ""
    292298
    293 #: includes/class-customer-email-notify.php:1104
     299#: includes/class-customer-email-notify.php:1130
    294300msgid "Exclude people who already bought selected products."
    295301msgstr ""
    296302
    297 #: includes/class-customer-email-notify.php:1108
     303#: includes/class-customer-email-notify.php:1134
    298304msgid "Manual emails"
    299305msgstr ""
    300306
    301 #: includes/class-customer-email-notify.php:1114
     307#: includes/class-customer-email-notify.php:1140
    302308msgid "one@example.com, two@example.com"
    303309msgstr ""
    304310
    305 #: includes/class-customer-email-notify.php:1115
     311#: includes/class-customer-email-notify.php:1141
    306312msgid "Comma, semicolon, space, or line break separated."
    307313msgstr ""
    308314
    309 #: includes/class-customer-email-notify.php:1128
     315#: includes/class-customer-email-notify.php:1154
     316#: includes/trait-campaign-automation-ui.php:106
    310317msgid "Content"
    311318msgstr ""
    312319
    313 #: includes/class-customer-email-notify.php:1129
     320#: includes/class-customer-email-notify.php:1155
    314321msgid "Compose the email shown inside the WooCommerce template."
    315322msgstr ""
    316323
    317 #: includes/class-customer-email-notify.php:1135
    318 #: includes/class-customer-email-notify.php:1202
    319 #: includes/class-customer-email-notify.php:1545
    320 #: includes/class-customer-email-notify.php:2044
     324#: includes/class-customer-email-notify.php:1161
     325#: includes/class-customer-email-notify.php:1228
     326#: includes/class-customer-email-notify.php:1572
     327#: includes/class-customer-email-notify.php:2071
     328#: includes/trait-campaign-automation-ui.php:107
     329#: includes/trait-campaign-automation-ui.php:132
     330#: includes/trait-campaign-automation-ui.php:135
    321331msgid "Subject"
    322332msgstr ""
    323333
    324 #: includes/class-customer-email-notify.php:1140
    325 #: includes/class-customer-email-notify.php:5160
    326 #: includes/class-customer-email-notify.php:5389
     334#: includes/class-customer-email-notify.php:1166
     335#: includes/class-customer-email-notify.php:5221
     336#: includes/class-customer-email-notify.php:5466
     337#: includes/trait-campaign-automation-ui.php:107
    327338msgid "Email heading"
    328339msgstr ""
    329340
    330 #: includes/class-customer-email-notify.php:1145
    331 #: includes/class-customer-email-notify.php:5147
    332 #: includes/class-customer-email-notify.php:5390
     341#: includes/class-customer-email-notify.php:1171
     342#: includes/class-customer-email-notify.php:5208
     343#: includes/class-customer-email-notify.php:5467
     344#: includes/trait-campaign-automation-ui.php:108
    333345msgid "Message body"
    334346msgstr ""
    335347
    336348#. translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders.
    337 #: includes/class-customer-email-notify.php:1157
    338 msgid "Body variables: {customer_name} uses the saved recipient name, falling back to email; {product_name} is the shared campaign product label, never a personal purchase. With no product selection it uses the audience label. Subject and heading are literal."
     349#: includes/class-customer-email-notify.php:1183
     350msgid "Variables in subject, heading and body: {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products}, {order_url}. Order variables use the matching order with the highest order ID; preview shows its number and the matching order count. Without order context they are empty; missing names use Customer. {product_name} remains the shared campaign label. Order links require the matching account login; guests have no order link. The live editor shows template text; Preview Recipients and test email use the first eligible recipient."
    339351msgstr ""
    340352
    341353#. translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders.
    342 #: includes/class-customer-email-notify.php:1158
     354#: includes/class-customer-email-notify.php:1184
    343355msgid "An unsubscribe link is added automatically to campaign emails."
    344356msgstr ""
    345357
    346 #: includes/class-customer-email-notify.php:1166
     358#: includes/class-customer-email-notify.php:1192
    347359msgid "Test"
    348360msgstr ""
    349361
    350 #: includes/class-customer-email-notify.php:1167
     362#: includes/class-customer-email-notify.php:1193
    351363msgid "Send a single email without building the campaign queue."
    352364msgstr ""
    353365
    354 #: includes/class-customer-email-notify.php:1174
     366#: includes/class-customer-email-notify.php:1200
    355367msgid "Enable test mode"
    356368msgstr ""
    357369
    358 #: includes/class-customer-email-notify.php:1175
     370#: includes/class-customer-email-notify.php:1201
    359371msgid "Only the test recipient will receive this email."
    360372msgstr ""
    361373
    362 #: includes/class-customer-email-notify.php:1180
     374#: includes/class-customer-email-notify.php:1206
     375#: includes/trait-campaign-automation-ui.php:118
    363376msgid "Test email address"
    364377msgstr ""
    365378
    366 #: includes/class-customer-email-notify.php:1189
    367 #: includes/class-customer-email-notify.php:1278
     379#: includes/class-customer-email-notify.php:1215
     380#: includes/class-customer-email-notify.php:1304
    368381msgid "Send Test Email"
    369382msgstr ""
    370383
    371 #: includes/class-customer-email-notify.php:1191
    372 #: includes/class-customer-email-notify.php:1194
     384#: includes/class-customer-email-notify.php:1217
     385#: includes/class-customer-email-notify.php:1220
     386#: includes/trait-campaign-automation-ui.php:128
     387#: includes/trait-campaign-automation-ui.php:129
    373388msgid "Email preview"
    374389msgstr ""
    375390
    376 #: includes/class-customer-email-notify.php:1195
     391#: includes/class-customer-email-notify.php:1221
     392#: includes/trait-campaign-automation-ui.php:129
    377393msgid "Rendered with the active WooCommerce email template."
    378394msgstr ""
    379395
    380 #: includes/class-customer-email-notify.php:1197
     396#: includes/class-customer-email-notify.php:1223
     397#: includes/trait-campaign-automation-ui.php:129
    381398msgid "Live"
    382399msgstr ""
    383400
    384 #: includes/class-customer-email-notify.php:1203
    385 #: includes/class-customer-email-notify.php:5388
     401#: includes/class-customer-email-notify.php:1229
     402#: includes/class-customer-email-notify.php:5465
    386403msgid "Email subject"
    387404msgstr ""
    388405
    389 #: includes/class-customer-email-notify.php:1210
     406#: includes/class-customer-email-notify.php:1236
     407#: includes/trait-campaign-automation-ui.php:132
    390408msgid "WooCommerce email preview"
    391409msgstr ""
    392410
    393 #: includes/class-customer-email-notify.php:1219
     411#: includes/class-customer-email-notify.php:1245
    394412msgid "Schedule"
    395413msgstr ""
    396414
    397 #: includes/class-customer-email-notify.php:1220
     415#: includes/class-customer-email-notify.php:1246
    398416msgid "Choose when the campaign should be sent."
    399417msgstr ""
    400418
    401 #: includes/class-customer-email-notify.php:1228
     419#: includes/class-customer-email-notify.php:1254
    402420msgid "Send after confirmation"
    403421msgstr ""
    404422
    405 #: includes/class-customer-email-notify.php:1229
     423#: includes/class-customer-email-notify.php:1255
    406424msgid "Queue emails as soon as the preview is confirmed."
    407425msgstr ""
    408426
    409 #: includes/class-customer-email-notify.php:1235
     427#: includes/class-customer-email-notify.php:1261
    410428msgid "Schedule for later"
    411429msgstr ""
    412430
    413 #: includes/class-customer-email-notify.php:1236
     431#: includes/class-customer-email-notify.php:1262
    414432msgid "Legacy schedule restored. Choose an explicit store or local mode for this new campaign."
    415433msgstr ""
    416434
    417 #: includes/class-customer-email-notify.php:1241
     435#: includes/class-customer-email-notify.php:1267
    418436msgid "One time in store timezone:"
    419437msgstr ""
    420438
    421 #: includes/class-customer-email-notify.php:1241
     439#: includes/class-customer-email-notify.php:1267
    422440msgid "All recipients share one UTC due time."
    423441msgstr ""
    424442
    425 #: includes/class-customer-email-notify.php:1242
    426 #: includes/trait-campaign-authoring.php:222
     443#: includes/class-customer-email-notify.php:1268
     444#: includes/trait-campaign-authoring.php:232
    427445msgid "Same local time per recipient (location estimate; UTC fallback)"
    428446msgstr ""
    429447
    430 #: includes/class-customer-email-notify.php:1242
     448#: includes/class-customer-email-notify.php:1268
    431449msgid "Only unambiguous billing countries use a location estimate. Other countries, missing data and manual lists use UTC."
    432450msgstr ""
    433451
    434 #: includes/class-customer-email-notify.php:1244
     452#: includes/class-customer-email-notify.php:1270
    435453msgid "Scheduled date/time"
    436454msgstr ""
    437455
    438 #: includes/class-customer-email-notify.php:1251
     456#: includes/class-customer-email-notify.php:1277
    439457msgid "Store mode uses the displayed store timezone. Local mode uses a billing-location estimate only when unambiguous, otherwise UTC. Past, nonexistent or repeated local times are rejected; choose another time. No automatic next-day rollover."
    440458msgstr ""
    441459
    442 #: includes/class-customer-email-notify.php:1257
     460#: includes/class-customer-email-notify.php:1283
    443461msgid "Review before confirmation"
    444462msgstr ""
    445463
    446 #: includes/class-customer-email-notify.php:1258
     464#: includes/class-customer-email-notify.php:1284
    447465msgid "Preview Recipients opens the saved recipient preview with selected and excluded counts, reasons, personalized samples and confirmed due times. No audience estimate is calculated while you edit."
    448466msgstr ""
    449467
    450 #: includes/class-customer-email-notify.php:1259
     468#: includes/class-customer-email-notify.php:1285
    451469msgid "Review that preview, then confirm there. Changes require another preview. Emails already handed to the mail system cannot be recalled; inbox delivery is not verified."
    452470msgstr ""
    453471
    454 #: includes/class-customer-email-notify.php:1260
     472#: includes/class-customer-email-notify.php:1286
    455473msgid "Test mode sends only to the test address. Turn it off in Content to preview a campaign audience."
    456474msgstr ""
    457475
    458 #: includes/class-customer-email-notify.php:1265
    459 #: includes/class-customer-email-notify.php:1267
     476#: includes/class-customer-email-notify.php:1291
     477#: includes/class-customer-email-notify.php:1293
    460478msgid "Next step: preview recipients and confirm before sending."
    461479msgstr ""
    462480
    463 #: includes/class-customer-email-notify.php:1266
     481#: includes/class-customer-email-notify.php:1292
    464482msgid "Test mode sends one email to the test recipient and keeps this draft for further edits."
    465483msgstr ""
    466484
    467 #: includes/class-customer-email-notify.php:1271
    468 #: includes/class-customer-email-notify.php:1277
     485#: includes/class-customer-email-notify.php:1297
     486#: includes/class-customer-email-notify.php:1303
    469487msgid "Preview Recipients"
    470488msgstr ""
    471489
    472 #: includes/class-customer-email-notify.php:1285
     490#: includes/class-customer-email-notify.php:1311
     491#: includes/trait-campaign-automation-ui.php:122
    473492msgid "Back"
    474493msgstr ""
    475494
    476 #: includes/class-customer-email-notify.php:1286
     495#: includes/class-customer-email-notify.php:1312
     496#: includes/trait-campaign-automation-ui.php:122
    477497msgid "Continue"
    478498msgstr ""
    479499
    480 #: includes/class-customer-email-notify.php:1296
     500#: includes/class-customer-email-notify.php:1322
    481501msgid "Campaigns"
    482502msgstr ""
    483503
    484 #: includes/class-customer-email-notify.php:1297
     504#: includes/class-customer-email-notify.php:1323
    485505msgid "Compose"
    486506msgstr ""
    487507
    488 #: includes/class-customer-email-notify.php:1298
     508#: includes/class-customer-email-notify.php:1324
     509#: includes/trait-campaign-automation-ui.php:80
     510msgid "Automations"
     511msgstr ""
     512
     513#: includes/class-customer-email-notify.php:1325
    489514msgid "Library"
    490515msgstr ""
    491516
    492 #: includes/class-customer-email-notify.php:1299
     517#: includes/class-customer-email-notify.php:1326
    493518msgid "Subscribers"
    494519msgstr ""
    495520
    496 #: includes/class-customer-email-notify.php:1300
     521#: includes/class-customer-email-notify.php:1327
    497522msgid "Settings"
    498523msgstr ""
    499524
    500 #: includes/class-customer-email-notify.php:1344
    501 #: includes/class-customer-email-notify.php:1380
     525#: includes/class-customer-email-notify.php:1371
     526#: includes/class-customer-email-notify.php:1407
    502527msgid "Campaign list"
    503528msgstr ""
    504529
    505530#. translators: %s: Campaign ID.
    506 #: includes/class-customer-email-notify.php:1349
    507 #: includes/class-customer-email-notify.php:1670
     531#: includes/class-customer-email-notify.php:1376
     532#: includes/class-customer-email-notify.php:1697
    508533msgid "Recent campaign log"
    509534msgstr ""
    510535
    511 #: includes/class-customer-email-notify.php:1360
     536#: includes/class-customer-email-notify.php:1387
    512537msgid "Handed to mail system means the sending request was accepted, not that the email reached an inbox. Queue failures retry automatically up to 3 attempts, at least 5 minutes apart. Known pre-transport failures require correcting the reported data or configuration, then recovery. Unknown outcomes require checking the mail provider before deciding to create a new campaign."
    513538msgstr ""
    514539
    515 #: includes/class-customer-email-notify.php:1381
     540#: includes/class-customer-email-notify.php:1408
    516541msgid "Recent campaign previews, tests, queues, schedules, and send results are grouped by campaign."
    517542msgstr ""
    518543
    519544#. translators: %d: Number of campaigns.
    520 #: includes/class-customer-email-notify.php:1387
     545#: includes/class-customer-email-notify.php:1414
    521546msgid "%d campaign(s) stored"
    522547msgstr ""
    523548
    524 #: includes/class-customer-email-notify.php:1397
     549#: includes/class-customer-email-notify.php:1424
    525550msgid "No campaign history has been created yet. Preview or send a campaign to start tracking it here."
    526551msgstr ""
    527552
    528 #: includes/class-customer-email-notify.php:1416
    529 #: includes/class-customer-email-notify.php:1428
    530 #: includes/class-customer-email-notify.php:1711
    531 #: includes/class-customer-email-notify.php:1721
     553#: includes/class-customer-email-notify.php:1443
     554#: includes/class-customer-email-notify.php:1455
     555#: includes/class-customer-email-notify.php:1738
     556#: includes/class-customer-email-notify.php:1748
    532557msgid "Campaign"
    533558msgstr ""
    534559
    535 #: includes/class-customer-email-notify.php:1417
    536 #: includes/class-customer-email-notify.php:1429
    537 #: includes/class-customer-email-notify.php:1713
    538 #: includes/class-customer-email-notify.php:1723
    539 #: includes/trait-campaign-privacy.php:142
     560#: includes/class-customer-email-notify.php:1444
     561#: includes/class-customer-email-notify.php:1456
     562#: includes/class-customer-email-notify.php:1740
     563#: includes/class-customer-email-notify.php:1750
     564#: includes/trait-campaign-privacy.php:144
    540565msgid "Status"
    541566msgstr ""
    542567
    543 #: includes/class-customer-email-notify.php:1418
    544 #: includes/class-customer-email-notify.php:1430
    545 #: includes/class-customer-email-notify.php:1715
    546 #: includes/class-customer-email-notify.php:1725
     568#: includes/class-customer-email-notify.php:1445
     569#: includes/class-customer-email-notify.php:1457
     570#: includes/class-customer-email-notify.php:1742
     571#: includes/class-customer-email-notify.php:1752
    547572msgid "Message"
    548573msgstr ""
    549574
    550 #: includes/class-customer-email-notify.php:1420
    551 #: includes/class-customer-email-notify.php:1432
    552 #: includes/class-customer-email-notify.php:1577
    553 #: includes/class-customer-email-notify.php:2052
     575#: includes/class-customer-email-notify.php:1447
     576#: includes/class-customer-email-notify.php:1459
     577#: includes/class-customer-email-notify.php:1604
     578#: includes/class-customer-email-notify.php:2079
    554579msgid "Delivery"
    555580msgstr ""
    556581
    557 #: includes/class-customer-email-notify.php:1421
    558 #: includes/class-customer-email-notify.php:1433
     582#: includes/class-customer-email-notify.php:1448
     583#: includes/class-customer-email-notify.php:1460
    559584msgid "Activity"
    560585msgstr ""
    561586
    562 #: includes/class-customer-email-notify.php:1422
    563 #: includes/class-customer-email-notify.php:1434
     587#: includes/class-customer-email-notify.php:1449
     588#: includes/class-customer-email-notify.php:1461
     589#: includes/trait-campaign-contact-history.php:81
    564590msgid "Updated"
    565591msgstr ""
    566592
    567 #: includes/class-customer-email-notify.php:1423
    568 #: includes/class-customer-email-notify.php:1435
     593#: includes/class-customer-email-notify.php:1450
     594#: includes/class-customer-email-notify.php:1462
    569595msgid "Actions"
    570596msgstr ""
    571597
    572 #: includes/class-customer-email-notify.php:1459
     598#: includes/class-customer-email-notify.php:1486
    573599msgid "Test campaign"
    574600msgstr ""
    575601
    576 #: includes/class-customer-email-notify.php:1463
    577 #: includes/class-customer-email-notify.php:1506
     602#: includes/class-customer-email-notify.php:1490
     603#: includes/class-customer-email-notify.php:1533
    578604msgid "View log"
    579605msgstr ""
    580606
    581 #: includes/class-customer-email-notify.php:1473
     607#: includes/class-customer-email-notify.php:1500
    582608msgid "(No subject)"
    583609msgstr ""
    584610
    585611#. translators: %s: Formatted creation date and time.
    586 #: includes/class-customer-email-notify.php:1496
     612#: includes/class-customer-email-notify.php:1523
    587613msgid "Created %s"
    588614msgstr ""
    589615
    590 #: includes/class-customer-email-notify.php:1530
     616#: includes/class-customer-email-notify.php:1557
    591617msgid "The selected campaign could not be found in stored history."
    592618msgstr ""
    593619
    594 #: includes/class-customer-email-notify.php:1537
     620#: includes/class-customer-email-notify.php:1564
    595621msgid "Campaign details"
    596622msgstr ""
    597623
    598 #: includes/class-customer-email-notify.php:1541
    599 #: includes/class-customer-email-notify.php:1970
     624#: includes/class-customer-email-notify.php:1568
     625#: includes/class-customer-email-notify.php:1997
    600626msgid "Campaign ID"
    601627msgstr ""
    602628
    603 #: includes/class-customer-email-notify.php:1561
     629#: includes/class-customer-email-notify.php:1588
    604630msgid "Scope"
    605631msgstr ""
    606632
    607 #: includes/class-customer-email-notify.php:1571
     633#: includes/class-customer-email-notify.php:1598
    608634msgid "None"
    609635msgstr ""
    610636
    611 #: includes/class-customer-email-notify.php:1634
     637#: includes/class-customer-email-notify.php:1661
    612638msgid "Recover / retry eligible recipients"
    613639msgstr ""
    614640
    615641#. translators: %d: Number of scheduling failures.
    616 #: includes/class-customer-email-notify.php:1644
     642#: includes/class-customer-email-notify.php:1671
    617643msgid "%d eligible recipient(s). Recovery continues preparation and retries queue failures or known pre-transport failures, up to 3 attempts. Unknown outcomes are never retried. Unsubscribes are checked again."
    618644msgstr ""
    619645
    620 #: includes/class-customer-email-notify.php:1655
     646#: includes/class-customer-email-notify.php:1682
    621647msgid "Historical schedule failures are recorded, but their recipient-level records are no longer complete. Automatic retry is disabled to prevent sending to the wrong or already handled recipients."
    622648msgstr ""
    623649
    624650#. translators: %s: Campaign ID.
    625 #: includes/class-customer-email-notify.php:1669
     651#: includes/class-customer-email-notify.php:1696
    626652msgid "Log for campaign %s"
    627653msgstr ""
    628654
    629 #: includes/class-customer-email-notify.php:1674
     655#: includes/class-customer-email-notify.php:1701
    630656msgid "Latest stored send, queue, schedule, failure, skipped, and unsubscribe events."
    631657msgstr ""
    632658
    633 #: includes/class-customer-email-notify.php:1678
     659#: includes/class-customer-email-notify.php:1705
    634660msgid "View all logs"
    635661msgstr ""
    636662
    637 #: includes/class-customer-email-notify.php:1685
     663#: includes/class-customer-email-notify.php:1712
    638664msgid "No log entries found yet."
    639665msgstr ""
    640666
    641 #: includes/class-customer-email-notify.php:1710
    642 #: includes/class-customer-email-notify.php:1720
     667#: includes/class-customer-email-notify.php:1737
     668#: includes/class-customer-email-notify.php:1747
    643669msgid "Time"
    644670msgstr ""
    645671
    646 #: includes/class-customer-email-notify.php:1712
    647 #: includes/class-customer-email-notify.php:1722
    648 #: includes/class-customer-email-notify.php:2075
     672#: includes/class-customer-email-notify.php:1739
     673#: includes/class-customer-email-notify.php:1749
     674#: includes/class-customer-email-notify.php:2102
    649675#: includes/trait-campaign-controls.php:179
    650676msgid "Email"
    651677msgstr ""
    652678
    653 #: includes/class-customer-email-notify.php:1714
    654 #: includes/class-customer-email-notify.php:1724
     679#: includes/class-customer-email-notify.php:1741
     680#: includes/class-customer-email-notify.php:1751
    655681msgid "Action ID"
    656682msgstr ""
    657683
    658 #: includes/class-customer-email-notify.php:1741
     684#: includes/class-customer-email-notify.php:1768
    659685msgid "unknown"
    660686msgstr ""
    661687
    662 #: includes/class-customer-email-notify.php:1778
     688#: includes/class-customer-email-notify.php:1805
    663689msgid "The selected campaign could not be found."
    664690msgstr ""
    665691
    666 #: includes/class-customer-email-notify.php:1779
     692#: includes/class-customer-email-notify.php:1806
    667693msgid "This campaign does not have enough stored email content to reschedule safely. For older campaigns, keep at least one pending scheduled action so Email campaigns can recover the message payload."
    668694msgstr ""
    669695
    670 #: includes/class-customer-email-notify.php:1780
     696#: includes/class-customer-email-notify.php:1807
    671697msgid "Recipient-level failure records are incomplete, so automatic retry was blocked to prevent duplicate or incorrect delivery."
    672698msgstr ""
    673699
    674 #: includes/class-customer-email-notify.php:1781
     700#: includes/class-customer-email-notify.php:1808
    675701msgid "The campaign content could not be stored safely, so no retry jobs were created."
    676702msgstr ""
    677703
    678 #: includes/class-customer-email-notify.php:1782
     704#: includes/class-customer-email-notify.php:1809
    679705msgid "Failed recipients could not be prepared for background retry."
    680706msgstr ""
    681707
    682 #: includes/class-customer-email-notify.php:1783
     708#: includes/class-customer-email-notify.php:1810
    683709msgid "The background retry dispatcher could not be queued."
    684710msgstr ""
    685711
    686 #: includes/class-customer-email-notify.php:1784
     712#: includes/class-customer-email-notify.php:1811
    687713msgid "No stuck schedule-failed recipients were found for this campaign."
    688714msgstr ""
    689715
    690 #: includes/class-customer-email-notify.php:1788
     716#: includes/class-customer-email-notify.php:1815
    691717msgid "The failed schedule retry could not be completed."
    692718msgstr ""
    693719
    694720#. translators: 1: Number of emails successfully rescheduled. 2: Number of failed retries.
    695 #: includes/class-customer-email-notify.php:1807
     721#: includes/class-customer-email-notify.php:1834
    696722msgid "Rescheduled %1$d failed email(s). %2$d retry attempt(s) failed."
    697723msgstr ""
    698724
    699725#. translators: %d: Number of recipients.
    700 #: includes/class-customer-email-notify.php:1825
     726#: includes/class-customer-email-notify.php:1852
    701727msgid "%d failed recipient(s) are being prepared for retry in the background."
    702728msgstr ""
    703729
    704 #: includes/class-customer-email-notify.php:1845
     730#: includes/class-customer-email-notify.php:1872
    705731msgid "Email campaigns settings saved."
    706732msgstr ""
    707733
    708 #: includes/class-customer-email-notify.php:1856
     734#: includes/class-customer-email-notify.php:1883
    709735msgid "From email"
    710736msgstr ""
    711737
    712 #: includes/class-customer-email-notify.php:1866
     738#: includes/class-customer-email-notify.php:1893
    713739msgid "Used as the sender email for every campaign."
    714740msgstr ""
    715741
    716 #: includes/class-customer-email-notify.php:1868
     742#: includes/class-customer-email-notify.php:1895
    717743msgid "From name"
    718744msgstr ""
    719745
    720 #: includes/class-customer-email-notify.php:1877
     746#: includes/class-customer-email-notify.php:1904
    721747msgid "Reply-To email"
    722748msgstr ""
    723749
    724 #: includes/class-customer-email-notify.php:1887
     750#: includes/class-customer-email-notify.php:1914
    725751msgid "Used when automated-reply suppression is disabled."
    726752msgstr ""
    727753
    728 #: includes/class-customer-email-notify.php:1889
     754#: includes/class-customer-email-notify.php:1916
    729755msgid "No-reply email"
    730756msgstr ""
    731757
    732 #: includes/class-customer-email-notify.php:1898
     758#: includes/class-customer-email-notify.php:1925
    733759msgid "Used as Reply-To while suppression is enabled, so non-compliant automatic responders do not reach the support inbox."
    734760msgstr ""
    735761
    736 #: includes/class-customer-email-notify.php:1900
     762#: includes/class-customer-email-notify.php:1927
    737763msgid "Automated replies"
    738764msgstr ""
    739765
    740 #: includes/class-customer-email-notify.php:1909
     766#: includes/class-customer-email-notify.php:1936
    741767msgid "Suppress out-of-office and other automatic replies"
    742768msgstr ""
    743769
    744 #: includes/class-customer-email-notify.php:1912
     770#: includes/class-customer-email-notify.php:1939
    745771msgid "Adds standards-based bulk/automatic message headers while preserving delivery failure notifications."
    746772msgstr ""
    747773
    748 #: includes/class-customer-email-notify.php:1915
     774#: includes/class-customer-email-notify.php:1942
    749775msgid "Save Settings"
    750776msgstr ""
    751777
    752 #: includes/class-customer-email-notify.php:1928
    753 #: includes/class-customer-email-notify.php:1963
     778#: includes/class-customer-email-notify.php:1955
     779#: includes/class-customer-email-notify.php:1990
    754780msgid "Preview email campaign"
    755781msgstr ""
    756782
    757 #: includes/class-customer-email-notify.php:1934
    758 #: includes/class-customer-email-notify.php:2133
     783#: includes/class-customer-email-notify.php:1961
     784#: includes/class-customer-email-notify.php:2160
    759785msgid "Back to email form"
    760786msgstr ""
    761787
    762 #: includes/class-customer-email-notify.php:1948
    763 #: includes/class-customer-email-notify.php:4253
     788#: includes/class-customer-email-notify.php:1975
     789#: includes/class-customer-email-notify.php:4292
    764790msgid "Product customers"
    765791msgstr ""
    766792
    767 #: includes/class-customer-email-notify.php:1951
    768 #: includes/class-customer-email-notify.php:5115
     793#: includes/class-customer-email-notify.php:1978
     794#: includes/class-customer-email-notify.php:5176
    769795msgid "selected product(s)"
    770796msgstr ""
    771797
    772 #: includes/class-customer-email-notify.php:1990
     798#: includes/class-customer-email-notify.php:2017
    773799#: includes/trait-campaign-controls.php:172
    774800msgid "Recipients"
    … …  
    776802
    777803#. translators: %d: Number of recipients.
    778 #: includes/class-customer-email-notify.php:1995
     804#: includes/class-customer-email-notify.php:2022
    779805msgid "%d recipient"
    780806msgid_plural "%d recipients"
    … …  
    782808msgstr[1] ""
    783809
    784 #: includes/class-customer-email-notify.php:2008
    785 #: includes/trait-campaign-authoring.php:218
     810#: includes/class-customer-email-notify.php:2035
     811#: includes/trait-campaign-authoring.php:228
    786812msgid "Excluded"
    787813msgstr ""
    788814
    789815#. translators: 1: Number of excluded recipients. 2: Initial number of recipients.
    790 #: includes/class-customer-email-notify.php:2013
     816#: includes/class-customer-email-notify.php:2040
    791817msgid "%1$d removed from %2$d initial recipient(s)."
    792818msgstr ""
    793819
    794 #: includes/class-customer-email-notify.php:2023
     820#: includes/class-customer-email-notify.php:2050
    795821msgid "From"
    796822msgstr ""
    797823
    798 #: includes/class-customer-email-notify.php:2048
     824#: includes/class-customer-email-notify.php:2075
    799825msgid "Heading"
    800826msgstr ""
    801827
    802828#. translators: %s: Scheduled local date and time.
    803 #: includes/class-customer-email-notify.php:2058
     829#: includes/class-customer-email-notify.php:2085
    804830msgid "Scheduled for %s. See the confirmed mode, timezone and UTC range below."
    805831msgstr ""
    806832
    807 #: includes/class-customer-email-notify.php:2061
     833#: includes/class-customer-email-notify.php:2088
    808834msgid "Send as soon as possible after confirmation"
    809835msgstr ""
    810836
    811 #: includes/class-customer-email-notify.php:2070
     837#: includes/class-customer-email-notify.php:2097
    812838msgid "Saved recipient sample and due times"
    813839msgstr ""
    814840
    815 #: includes/class-customer-email-notify.php:2074
     841#: includes/class-customer-email-notify.php:2101
    816842msgid "Name"
    817843msgstr ""
    818844
    819 #: includes/class-customer-email-notify.php:2076
    820 #: includes/trait-campaign-audiences.php:143
    821 #: includes/trait-campaign-audiences.php:158
     845#: includes/class-customer-email-notify.php:2103
     846#: includes/trait-campaign-audiences.php:152
     847#: includes/trait-campaign-audiences.php:167
    822848msgid "Billing country"
    823849msgstr ""
    824850
    825 #: includes/class-customer-email-notify.php:2077
     851#: includes/class-customer-email-notify.php:2104
    826852msgid "Billing state/city"
    827853msgstr ""
    828854
    829 #: includes/class-customer-email-notify.php:2079
     855#: includes/class-customer-email-notify.php:2106
    830856msgid "Scheduled timezone"
    831857msgstr ""
    832858
    833 #: includes/class-customer-email-notify.php:2080
     859#: includes/class-customer-email-notify.php:2107
    834860msgid "Scheduled send time"
    835861msgstr ""
    836862
    837 #: includes/class-customer-email-notify.php:2094
     863#: includes/class-customer-email-notify.php:2121
    838864msgid "UTC fallback"
    839865msgstr ""
    840866
    841 #: includes/class-customer-email-notify.php:2101
     867#: includes/class-customer-email-notify.php:2128
    842868msgid "Not set"
    843869msgstr ""
    844870
    845 #: includes/class-customer-email-notify.php:2112
     871#: includes/class-customer-email-notify.php:2139
    846872msgid "Invalid scheduled time"
    847873msgstr ""
    848874
    849 #: includes/class-customer-email-notify.php:2125
     875#: includes/class-customer-email-notify.php:2152
    850876msgid "Message preview"
    851877msgstr ""
    852878
    853 #: includes/class-customer-email-notify.php:2141
     879#: includes/class-customer-email-notify.php:2168
    854880msgid "Confirm this saved preview"
    855881msgstr ""
    856882
    857 #: includes/class-customer-email-notify.php:2142
     883#: includes/class-customer-email-notify.php:2169
    858884msgid "Confirmation creates this campaign with its saved recipients and content. Audience changes require a new preview. Emails already handed to the mail system cannot be recalled; inbox delivery is not verified."
    859885msgstr ""
    860886
    861887#. translators: %d: Number of recipients.
    862 #: includes/class-customer-email-notify.php:2149
     888#: includes/class-customer-email-notify.php:2176
    863889msgid "Schedule Email for %d Recipient(s)"
    864890msgstr ""
    865891
    866892#. translators: %d: Number of recipients.
    867 #: includes/class-customer-email-notify.php:2150
     893#: includes/class-customer-email-notify.php:2177
    868894msgid "Queue Email for %d Recipient(s)"
    869895msgstr ""
    870896
    871 #: includes/class-customer-email-notify.php:2158
     897#: includes/class-customer-email-notify.php:2185
    872898msgid "No matching customers were found. Nothing will be queued."
    873899msgstr ""
    874900
    875 #: includes/class-customer-email-notify.php:2173
     901#: includes/class-customer-email-notify.php:2200
    876902msgid "This unsubscribe link is invalid or expired."
    877903msgstr ""
    878904
    879 #: includes/class-customer-email-notify.php:2174
     905#: includes/class-customer-email-notify.php:2201
    880906msgid "Invalid unsubscribe link"
    881907msgstr ""
    882908
    883 #: includes/class-customer-email-notify.php:2180
     909#: includes/class-customer-email-notify.php:2207
    884910msgid "Your unsubscribe could not be saved. Please try this link again."
    885911msgstr ""
    886912
    887 #: includes/class-customer-email-notify.php:2189
     913#: includes/class-customer-email-notify.php:2216
    888914msgid "You have been unsubscribed."
    889915msgstr ""
    890916
    891 #: includes/class-customer-email-notify.php:2190
     917#: includes/class-customer-email-notify.php:2217
    892918msgid "This email address will be excluded from future email campaigns."
    893919msgstr ""
    894920
    895 #: includes/class-customer-email-notify.php:2193
    896 #: includes/class-customer-email-notify.php:3600
     921#: includes/class-customer-email-notify.php:2220
     922#: includes/class-customer-email-notify.php:3636
     923#: includes/trait-campaign-contact-history.php:66
    897924#: includes/trait-campaign-controls.php:173
    898 #: includes/trait-campaign-privacy.php:127
    899 #: includes/trait-campaign-privacy.php:143
     925#: includes/trait-campaign-privacy.php:128
     926#: includes/trait-campaign-privacy.php:145
    900927msgid "Unsubscribed"
    901928msgstr ""
    902929
    903 #: includes/class-customer-email-notify.php:2200
    904 #: includes/class-customer-email-notify.php:2253
    905 #: includes/class-customer-email-notify.php:2270
    906 #: includes/trait-campaign-authoring.php:91
     930#: includes/class-customer-email-notify.php:2227
     931#: includes/class-customer-email-notify.php:2280
     932#: includes/class-customer-email-notify.php:2297
     933#: includes/trait-campaign-authoring.php:101
     934#: includes/trait-campaign-automation-ui.php:16
     935#: includes/trait-campaign-contact-history.php:6
    907936#: includes/trait-campaign-controls.php:111
     937#: includes/trait-campaign-entrypoints.php:61
     938#: includes/trait-campaign-entrypoints.php:83
    908939#: includes/trait-campaign-privacy.php:80
     940#: includes/trait-campaign-subscriptions.php:128
    909941msgid "Permission denied."
    910942msgstr ""
    911943
    912 #: includes/class-customer-email-notify.php:2215
    913 #: includes/class-customer-email-notify.php:2756
     944#: includes/class-customer-email-notify.php:2242
     945#: includes/class-customer-email-notify.php:2790
    914946msgid "Please enter a valid sender email address."
    915947msgstr ""
    916948
    917 #: includes/class-customer-email-notify.php:2219
    918 #: includes/class-customer-email-notify.php:2760
     949#: includes/class-customer-email-notify.php:2246
     950#: includes/class-customer-email-notify.php:2794
    919951msgid "Please enter a sender name."
    920952msgstr ""
    921953
    922 #: includes/class-customer-email-notify.php:2223
    923 #: includes/class-customer-email-notify.php:2764
     954#: includes/class-customer-email-notify.php:2250
     955#: includes/class-customer-email-notify.php:2798
    924956msgid "Please enter a valid Reply-To email address."
    925957msgstr ""
    926958
    927 #: includes/class-customer-email-notify.php:2227
     959#: includes/class-customer-email-notify.php:2254
    928960msgid "Please enter a valid no-reply email address when automated replies are suppressed."
    929961msgstr ""
    930962
    931 #: includes/class-customer-email-notify.php:2315
     963#: includes/class-customer-email-notify.php:2342
    932964msgid "Please enter a valid test email address."
    933965msgstr ""
    934966
    935 #: includes/class-customer-email-notify.php:2333
     967#: includes/class-customer-email-notify.php:2355
    936968msgid "Test Customer"
    937969msgstr ""
    938970
    939 #: includes/class-customer-email-notify.php:2464
     971#: includes/class-customer-email-notify.php:2483
    940972msgid "Missing campaign payload identity."
    941973msgstr ""
    942974
    943 #: includes/class-customer-email-notify.php:2487
     975#: includes/class-customer-email-notify.php:2506
    944976msgid "Campaign content or sender details are incomplete."
    945977msgstr ""
    946978
    947 #: includes/class-customer-email-notify.php:2512
     979#: includes/class-customer-email-notify.php:2535
    948980msgid "Stored campaign content does not match this campaign delivery."
    949981msgstr ""
    950982
    951 #: includes/class-customer-email-notify.php:2536
     983#: includes/class-customer-email-notify.php:2559
    952984msgid "Invalid shared campaign payload reference."
    953985msgstr ""
    954986
    955 #: includes/class-customer-email-notify.php:2551
     987#: includes/class-customer-email-notify.php:2574
    956988msgid "Shared campaign content is missing or invalid."
    957989msgstr ""
    958990
    959 #: includes/class-customer-email-notify.php:2562
     991#: includes/class-customer-email-notify.php:2585
    960992msgid "Shared campaign content failed its integrity check."
    961993msgstr ""
    962994
    963 #: includes/class-customer-email-notify.php:2591
     995#: includes/class-customer-email-notify.php:2603
     996msgid "Confirmed recipient context is unavailable. No email was handed off."
     997msgstr ""
     998
     999#: includes/class-customer-email-notify.php:2625
    9641000msgid "Email job metadata is too large for Action Scheduler. Campaign content must use shared payload storage."
    9651001msgstr ""
    9661002
    967 #: includes/class-customer-email-notify.php:2602
     1003#: includes/class-customer-email-notify.php:2636
    9681004msgid "Scheduled timestamp is not in the future."
    9691005msgstr ""
    9701006
    971 #: includes/class-customer-email-notify.php:2622
     1007#: includes/class-customer-email-notify.php:2656
    9721008msgid "as_schedule_single_action returned an empty action id"
    9731009msgstr ""
    9741010
    975 #: includes/class-customer-email-notify.php:2633
     1011#: includes/class-customer-email-notify.php:2667
    9761012msgid "wp_schedule_single_event returned false"
    9771013msgstr ""
    9781014
    979 #: includes/class-customer-email-notify.php:2644
     1015#: includes/class-customer-email-notify.php:2678
    9801016msgid "Action Scheduler is unavailable."
    9811017msgstr ""
    9821018
    983 #: includes/class-customer-email-notify.php:2656
     1019#: includes/class-customer-email-notify.php:2690
    9841020msgid "as_enqueue_async_action returned an empty action id"
    9851021msgstr ""
    9861022
    987 #: includes/class-customer-email-notify.php:2698
     1023#: includes/class-customer-email-notify.php:2732
    9881024msgid "The exclusion list contains an invalid email address. Correct it before previewing."
    9891025msgstr ""
    9901026
    991 #: includes/class-customer-email-notify.php:2702
     1027#: includes/class-customer-email-notify.php:2736
    9921028msgid "A selected product is invalid or unavailable. Review the selection."
    9931029msgstr ""
    9941030
    995 #: includes/class-customer-email-notify.php:2705
     1031#: includes/class-customer-email-notify.php:2739
    9961032msgid "An excluded order status is invalid."
    9971033msgstr ""
    9981034
    999 #: includes/class-customer-email-notify.php:2724
    1000 #: includes/class-customer-email-notify.php:4289
     1035#: includes/class-customer-email-notify.php:2758
     1036#: includes/class-customer-email-notify.php:4330
    10011037msgid "Please choose a valid recipient source."
    10021038msgstr ""
    10031039
    1004 #: includes/class-customer-email-notify.php:2728
     1040#: includes/class-customer-email-notify.php:2762
    10051041msgid "Please choose a valid send time option."
    10061042msgstr ""
    10071043
    1008 #: includes/class-customer-email-notify.php:2732
     1044#: includes/class-customer-email-notify.php:2766
    10091045msgid "Selected products apply only to Product customers. Clear the products or choose that recipient source before previewing."
    10101046msgstr ""
    10111047
    1012 #: includes/class-customer-email-notify.php:2735
     1048#: includes/class-customer-email-notify.php:2769
    10131049msgid "Selected roles apply only to registered users. Clear the roles or choose that recipient source before previewing."
    10141050msgstr ""
    10151051
    1016 #: includes/class-customer-email-notify.php:2738
     1052#: includes/class-customer-email-notify.php:2772
    10171053msgid "The pasted email list applies only to Manual email list. Clear the list or choose that recipient source before previewing."
    10181054msgstr ""
    10191055
    1020 #: includes/class-customer-email-notify.php:2747
     1056#: includes/class-customer-email-notify.php:2781
    10211057msgid "Please enter a valid scheduled date/time."
    10221058msgstr ""
    10231059
    1024 #: includes/class-customer-email-notify.php:2752
     1060#: includes/class-customer-email-notify.php:2786
    10251061msgid "Missing required fields."
    10261062msgstr ""
    10271063
    1028 #: includes/class-customer-email-notify.php:2768
     1064#: includes/class-customer-email-notify.php:2802
    10291065msgid "Please select at least one product."
    10301066msgstr ""
    10311067
    1032 #: includes/class-customer-email-notify.php:2873
     1068#: includes/class-customer-email-notify.php:2908
    10331069msgid "Missing email preview token."
    10341070msgstr ""
    10351071
    10361072#. translators: %s: Formatted number of items.
    1037 #: includes/class-customer-email-notify.php:2946
     1073#: includes/class-customer-email-notify.php:2981
    10381074msgid "%s item"
    10391075msgid_plural "%s items"
    … …  
    10411077msgstr[1] ""
    10421078
    1043 #: includes/class-customer-email-notify.php:2956
     1079#: includes/class-customer-email-notify.php:2991
    10441080msgid "First page"
    10451081msgstr ""
    10461082
    1047 #: includes/class-customer-email-notify.php:2957
     1083#: includes/class-customer-email-notify.php:2992
    10481084msgid "Previous page"
    10491085msgstr ""
    10501086
    1051 #: includes/class-customer-email-notify.php:2961
     1087#: includes/class-customer-email-notify.php:2996
    10521088msgid "Current page"
    10531089msgstr ""
    10541090
    1055 #: includes/class-customer-email-notify.php:2973
     1091#: includes/class-customer-email-notify.php:3008
    10561092msgid "of"
    10571093msgstr ""
    10581094
    1059 #: includes/class-customer-email-notify.php:2978
     1095#: includes/class-customer-email-notify.php:3013
    10601096msgid "Next page"
    10611097msgstr ""
    10621098
    1063 #: includes/class-customer-email-notify.php:2979
     1099#: includes/class-customer-email-notify.php:3014
    10641100msgid "Last page"
    10651101msgstr ""
    10661102
    1067 #: includes/class-customer-email-notify.php:3098
     1103#: includes/class-customer-email-notify.php:3133
    10681104msgid "Recovered campaign content. Only stored recipients and existing jobs can continue. The original audience may be incomplete; review recipient records before creating another campaign."
    10691105msgstr ""
    10701106
    1071 #: includes/class-customer-email-notify.php:3289
     1107#: includes/class-customer-email-notify.php:3324
    10721108msgid "Missing campaign ID."
    10731109msgstr ""
    10741110
    1075 #: includes/class-customer-email-notify.php:3358
     1111#: includes/class-customer-email-notify.php:3393
    10761112msgid "Missing campaign content needed for rescheduling."
    10771113msgstr ""
    10781114
    1079 #: includes/class-customer-email-notify.php:3575
    1080 #: includes/class-customer-email-notify.php:3576
     1115#: includes/class-customer-email-notify.php:3611
     1116#: includes/class-customer-email-notify.php:3612
    10811117#: includes/trait-campaign-controls.php:173
    10821118msgid "Paused"
    10831119msgstr ""
    10841120
    1085 #: includes/class-customer-email-notify.php:3577
     1121#: includes/class-customer-email-notify.php:3613
    10861122msgid "Waiting for site limit"
    10871123msgstr ""
    10881124
    1089 #: includes/class-customer-email-notify.php:3578
     1125#: includes/class-customer-email-notify.php:3614
    10901126msgid "Saving recipient snapshot"
    10911127msgstr ""
    10921128
    1093 #: includes/class-customer-email-notify.php:3579
     1129#: includes/class-customer-email-notify.php:3615
    10941130msgid "Needs attention"
    10951131msgstr ""
    10961132
    1097 #: includes/class-customer-email-notify.php:3580
     1133#: includes/class-customer-email-notify.php:3616
    10981134msgid "Previewed"
    10991135msgstr ""
    11001136
    1101 #: includes/class-customer-email-notify.php:3581
     1137#: includes/class-customer-email-notify.php:3617
    11021138msgid "Testing"
    11031139msgstr ""
    11041140
    1105 #: includes/class-customer-email-notify.php:3582
     1141#: includes/class-customer-email-notify.php:3618
    11061142msgid "Test handed to mail system"
    11071143msgstr ""
    11081144
    1109 #: includes/class-customer-email-notify.php:3583
     1145#: includes/class-customer-email-notify.php:3619
    11101146msgid "Test failed"
    11111147msgstr ""
    11121148
    1113 #: includes/class-customer-email-notify.php:3584
     1149#: includes/class-customer-email-notify.php:3620
    11141150msgid "Queueing"
    11151151msgstr ""
    11161152
    1117 #: includes/class-customer-email-notify.php:3585
     1153#: includes/class-customer-email-notify.php:3621
    11181154msgid "Preparing in background"
    11191155msgstr ""
    11201156
    1121 #: includes/class-customer-email-notify.php:3586
     1157#: includes/class-customer-email-notify.php:3622
    11221158msgid "Preparing"
    11231159msgstr ""
    11241160
    1125 #: includes/class-customer-email-notify.php:3587
     1161#: includes/class-customer-email-notify.php:3623
    11261162#: includes/trait-campaign-controls.php:173
    11271163msgid "Queued"
    11281164msgstr ""
    11291165
    1130 #: includes/class-customer-email-notify.php:3588
     1166#: includes/class-customer-email-notify.php:3624
    11311167#: includes/trait-campaign-controls.php:173
    11321168msgid "Queue failed"
    11331169msgstr ""
    11341170
    1135 #: includes/class-customer-email-notify.php:3589
     1171#: includes/class-customer-email-notify.php:3625
    11361172msgid "Scheduling"
    11371173msgstr ""
    11381174
    1139 #: includes/class-customer-email-notify.php:3590
     1175#: includes/class-customer-email-notify.php:3626
    11401176#: includes/trait-campaign-controls.php:173
    11411177msgid "Scheduled"
    11421178msgstr ""
    11431179
    1144 #: includes/class-customer-email-notify.php:3591
     1180#: includes/class-customer-email-notify.php:3627
    11451181#: includes/trait-campaign-controls.php:173
    11461182msgid "Schedule failed"
    11471183msgstr ""
    11481184
    1149 #: includes/class-customer-email-notify.php:3592
    1150 #: includes/class-customer-email-notify.php:4627
     1185#: includes/class-customer-email-notify.php:3628
     1186#: includes/class-customer-email-notify.php:4674
    11511187#: includes/trait-campaign-controls.php:140
    11521188#: includes/trait-campaign-controls.php:173
    … …  
    11541190msgstr ""
    11551191
    1156 #: includes/class-customer-email-notify.php:3593
    1157 #: includes/class-customer-email-notify.php:4629
     1192#: includes/class-customer-email-notify.php:3629
     1193#: includes/class-customer-email-notify.php:4676
    11581194msgid "Completed"
    11591195msgstr ""
    11601196
    1161 #: includes/class-customer-email-notify.php:3594
     1197#: includes/class-customer-email-notify.php:3630
    11621198msgid "Completed with issues"
    11631199msgstr ""
    11641200
    1165 #: includes/class-customer-email-notify.php:3595
    1166 #: includes/trait-campaign-audiences.php:152
     1201#: includes/class-customer-email-notify.php:3631
     1202#: includes/trait-campaign-audiences.php:161
    11671203#: includes/trait-campaign-controls.php:138
    11681204#: includes/trait-campaign-controls.php:173
    … …  
    11701206msgstr ""
    11711207
    1172 #: includes/class-customer-email-notify.php:3596
    1173 #: includes/class-customer-email-notify.php:4632
     1208#: includes/class-customer-email-notify.php:3632
     1209#: includes/class-customer-email-notify.php:4679
    11741210#: includes/trait-campaign-controls.php:141
    11751211#: includes/trait-campaign-controls.php:173
    … …  
    11771213msgstr ""
    11781214
    1179 #: includes/class-customer-email-notify.php:3597
     1215#: includes/class-customer-email-notify.php:3633
    11801216msgid "Skipped"
    11811217msgstr ""
    11821218
    1183 #: includes/class-customer-email-notify.php:3598
     1219#: includes/class-customer-email-notify.php:3634
    11841220msgid "Skipped unsubscribed"
    11851221msgstr ""
    11861222
    1187 #: includes/class-customer-email-notify.php:3599
     1223#: includes/class-customer-email-notify.php:3635
    11881224msgid "Skipped duplicate"
    11891225msgstr ""
    11901226
    1191 #: includes/class-customer-email-notify.php:3601
     1227#: includes/class-customer-email-notify.php:3637
    11921228msgid "Duplicate blocked"
    11931229msgstr ""
    11941230
    1195 #: includes/class-customer-email-notify.php:3602
     1231#: includes/class-customer-email-notify.php:3638
    11961232msgid "No recipients"
    11971233msgstr ""
    11981234
    1199 #: includes/class-customer-email-notify.php:3603
    1200 #: includes/class-customer-email-notify.php:4630
     1235#: includes/class-customer-email-notify.php:3639
     1236#: includes/class-customer-email-notify.php:4677
    12011237#: includes/trait-campaign-controls.php:173
    12021238msgid "Cancelled"
    12031239msgstr ""
    12041240
    1205 #: includes/class-customer-email-notify.php:3604
     1241#: includes/class-customer-email-notify.php:3640
    12061242msgid "Unresolved"
    12071243msgstr ""
    12081244
    1209 #: includes/class-customer-email-notify.php:3605
     1245#: includes/class-customer-email-notify.php:3641
    12101246msgid "Outcome unknown"
    12111247msgstr ""
    12121248
    12131249#. translators: 1: Final number of recipients. 2: Excluded recipients. 3: Initial recipients.
    1214 #: includes/class-customer-email-notify.php:3640
     1250#: includes/class-customer-email-notify.php:3676
    12151251msgid "%1$d recipient(s), %2$d excluded from %3$d"
    12161252msgstr ""
    12171253
    12181254#. translators: %d: Number of recipients.
    1219 #: includes/class-customer-email-notify.php:3648
     1255#: includes/class-customer-email-notify.php:3684
    12201256msgid "%d recipient(s)"
    12211257msgstr ""
    12221258
    1223 #: includes/class-customer-email-notify.php:3654
     1259#: includes/class-customer-email-notify.php:3690
    12241260msgid "Recipient location estimate / UTC fallback"
    12251261msgstr ""
    12261262
    1227 #: includes/class-customer-email-notify.php:3656
     1263#: includes/class-customer-email-notify.php:3692
    12281264msgid "Test email only"
    12291265msgstr ""
    12301266
    12311267#. translators: %s: Scheduled local date and time.
    1232 #: includes/class-customer-email-notify.php:3665
     1268#: includes/class-customer-email-notify.php:3701
    12331269msgid "Scheduled: %s by recipient timezone"
    12341270msgstr ""
    12351271
    12361272#. translators: %s: Scheduled local date and time.
    1237 #: includes/class-customer-email-notify.php:3666
     1273#: includes/class-customer-email-notify.php:3702
    12381274msgid "selected time"
    12391275msgstr ""
    12401276
    1241 #: includes/class-customer-email-notify.php:3670
    1242 #: includes/trait-campaign-authoring.php:222
     1277#: includes/class-customer-email-notify.php:3706
     1278#: includes/trait-campaign-authoring.php:232
    12431279msgid "After confirmation"
    12441280msgstr ""
    12451281
    1246 #: includes/class-customer-email-notify.php:3729
     1282#: includes/class-customer-email-notify.php:3765
    12471283msgid "No activity yet"
    12481284msgstr ""
    12491285
    1250 #: includes/class-customer-email-notify.php:4254
     1286#: includes/class-customer-email-notify.php:4290
     1287msgid "Subscribed email addresses"
     1288msgstr ""
     1289
     1290#: includes/class-customer-email-notify.php:4291
     1291#: includes/trait-campaign-entrypoints.php:72
     1292msgid "Selected orders"
     1293msgstr ""
     1294
     1295#: includes/class-customer-email-notify.php:4293
    12511296msgid "WooCommerce customers"
    12521297msgstr ""
    12531298
    1254 #: includes/class-customer-email-notify.php:4255
     1299#: includes/class-customer-email-notify.php:4294
    12551300msgid "All registered users"
    12561301msgstr ""
    12571302
    1258 #: includes/class-customer-email-notify.php:4256
     1303#: includes/class-customer-email-notify.php:4295
    12591304msgid "Manual email list"
    12601305msgstr ""
    12611306
    1262 #: includes/class-customer-email-notify.php:4271
    12631307#: includes/class-customer-email-notify.php:4310
     1308#: includes/class-customer-email-notify.php:4351
    12641309msgid "Please enter a list of valid email addresses."
    12651310msgstr ""
    12661311
    1267 #: includes/class-customer-email-notify.php:4276
     1312#: includes/class-customer-email-notify.php:4315
    12681313msgid "The recipient list contains an invalid email address. Correct the list before continuing."
    12691314msgstr ""
    12701315
    1271 #: includes/class-customer-email-notify.php:4294
     1316#: includes/class-customer-email-notify.php:4335
    12721317msgid "Please choose valid user roles."
    12731318msgstr ""
    12741319
    1275 #: includes/class-customer-email-notify.php:4299
     1320#: includes/class-customer-email-notify.php:4340
    12761321msgid "A selected user role is unavailable. Review the recipient selection before continuing."
    12771322msgstr ""
    12781323
    1279 #: includes/class-customer-email-notify.php:4306
     1324#: includes/class-customer-email-notify.php:4347
    12801325msgid "Please enter at least one recipient email address."
    12811326msgstr ""
    12821327
    1283 #: includes/class-customer-email-notify.php:4331
     1328#: includes/class-customer-email-notify.php:4372
    12841329msgid "Previously unsubscribed email addresses"
    12851330msgstr ""
    12861331
    1287 #: includes/class-customer-email-notify.php:4610
     1332#: includes/class-customer-email-notify.php:4657
    12881333msgid "Do not want to receive these campaign emails?"
    12891334msgstr ""
    12901335
    1291 #: includes/class-customer-email-notify.php:4612
     1336#: includes/class-customer-email-notify.php:4659
     1337#: includes/trait-campaign-subscriptions.php:125
    12921338msgid "Unsubscribe"
    12931339msgstr ""
    12941340
    1295 #: includes/class-customer-email-notify.php:4626
     1341#: includes/class-customer-email-notify.php:4673
    12961342msgid "Pending payment"
    12971343msgstr ""
    12981344
    1299 #: includes/class-customer-email-notify.php:4628
     1345#: includes/class-customer-email-notify.php:4675
    13001346msgid "On hold"
    13011347msgstr ""
    13021348
    1303 #: includes/class-customer-email-notify.php:4631
     1349#: includes/class-customer-email-notify.php:4678
    13041350msgid "Refunded"
    13051351msgstr ""
    13061352
    13071353#. translators: %s: Comma-separated order status labels.
    1308 #: includes/class-customer-email-notify.php:4693
     1354#: includes/class-customer-email-notify.php:4740
    13091355msgid "Order statuses: %s"
    13101356msgstr ""
    13111357
    13121358#. translators: %s: Comma-separated product names.
    1313 #: includes/class-customer-email-notify.php:4702
     1359#: includes/class-customer-email-notify.php:4749
    13141360msgid "Purchased products: %s"
    13151361msgstr ""
    13161362
    13171363#. translators: %d: Number of excluded email addresses.
    1318 #: includes/class-customer-email-notify.php:4711
     1364#: includes/class-customer-email-notify.php:4758
    13191365msgid "Manual emails: %d address"
    13201366msgid_plural "Manual emails: %d addresses"
    … …  
    13221368msgstr[1] ""
    13231369
    1324 #: includes/class-customer-email-notify.php:4884
     1370#: includes/class-customer-email-notify.php:4931
    13251371msgid "Missing required email content. Repair campaign data before retrying."
    13261372msgstr ""
    13271373
    1328 #: includes/class-customer-email-notify.php:4892
     1374#: includes/class-customer-email-notify.php:4941
     1375msgid "No active subscription before send."
     1376msgstr ""
     1377
     1378#: includes/class-customer-email-notify.php:4949
    13291379msgid "Recipient unsubscribed before send."
    13301380msgstr ""
    13311381
    1332 #: includes/class-customer-email-notify.php:4899
     1382#: includes/class-customer-email-notify.php:4957
    13331383msgid "Mail system accepted the request; inbox delivery is not verified."
    13341384msgstr ""
    13351385
    1336 #: includes/class-customer-email-notify.php:4899
     1386#: includes/class-customer-email-notify.php:4957
    13371387msgid "Mail transport returned no confirmation. Check the mail provider before sending again."
    13381388msgstr ""
    13391389
    1340 #: includes/class-customer-email-notify.php:4919
     1390#: includes/class-customer-email-notify.php:4977
    13411391msgid "Preserved a pre-upgrade delivery claim. Check the mail provider for unknown outcomes."
    13421392msgstr ""
    13431393
    13441394#. translators: %d: Product ID.
    1345 #: includes/class-customer-email-notify.php:5131
     1395#: includes/class-customer-email-notify.php:5192
    13461396msgid "Product #%d"
    13471397msgstr ""
    13481398
    1349 #: includes/class-customer-email-notify.php:5155
     1399#: includes/class-customer-email-notify.php:5216
    13501400msgid "WooCommerce email template is unavailable."
    13511401msgstr ""
    13521402
    1353 #: includes/class-customer-email-notify.php:5241
     1403#: includes/class-customer-email-notify.php:5303
    13541404msgid "WooCommerce mailer is unavailable."
    13551405msgstr ""
    13561406
    1357 #: includes/class-customer-email-notify.php:5246
     1407#: includes/class-customer-email-notify.php:5308
     1408msgid "The sample was erased before test mail handoff."
     1409msgstr ""
     1410
     1411#: includes/class-customer-email-notify.php:5318
     1412msgid "No active subscription before mail handoff."
     1413msgstr ""
     1414
     1415#: includes/class-customer-email-notify.php:5323
    13581416msgid "Recipient unsubscribed before mail handoff."
    13591417msgstr ""
    13601418
    13611419#: includes/trait-campaign-audiences.php:21
    1362 msgid "Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country and campaign history; no condition has been ignored."
    1363 msgstr ""
    1364 
    1365 #: includes/trait-campaign-audiences.php:127
     1420msgid "Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country, purchase history and campaign history; no condition has been ignored."
     1421msgstr ""
     1422
     1423#: includes/trait-campaign-audiences.php:134
    13661424msgid "Unavailable selection; remove or replace:"
    13671425msgstr ""
    13681426
    1369 #: includes/trait-campaign-audiences.php:133
     1427#: includes/trait-campaign-audiences.php:142
    13701428msgid "Saved conditions are invalid or unavailable. They remain selected; correct or explicitly remove them before previewing."
    13711429msgstr ""
    13721430
    1373 #: includes/trait-campaign-audiences.php:134
     1431#: includes/trait-campaign-audiences.php:143
    13741432msgid "Audience conditions"
    13751433msgstr ""
    13761434
    1377 #: includes/trait-campaign-audiences.php:135
     1435#: includes/trait-campaign-audiences.php:144
    13781436msgid "Order created from (store timezone)"
    13791437msgstr ""
    13801438
    1381 #: includes/trait-campaign-audiences.php:135
     1439#: includes/trait-campaign-audiences.php:144
    13821440msgid "Through date (inclusive)"
    13831441msgstr ""
    13841442
    1385 #: includes/trait-campaign-audiences.php:135
     1443#: includes/trait-campaign-audiences.php:144
    13861444msgid "Or last N × 24 hours"
    13871445msgstr ""
    13881446
    1389 #: includes/trait-campaign-audiences.php:136
    1390 msgid "Matching order statuses (default processing/completed)"
    1391 msgstr ""
    1392 
    1393 #: includes/trait-campaign-audiences.php:139
     1447#: includes/trait-campaign-audiences.php:145
     1448msgid "Matching order statuses (selected orders: all; other sources: processing/completed)"
     1449msgstr ""
     1450
     1451#: includes/trait-campaign-audiences.php:148
    13941452msgid "Categories (includes all descendants)"
    13951453msgstr ""
    13961454
    1397 #: includes/trait-campaign-audiences.php:146
    1398 #: includes/trait-campaign-audiences.php:158
     1455#: includes/trait-campaign-audiences.php:155
     1456#: includes/trait-campaign-audiences.php:167
    13991457msgid "Subscription status"
    14001458msgstr ""
    14011459
    1402 #: includes/trait-campaign-audiences.php:147
     1460#: includes/trait-campaign-audiences.php:156
    14031461msgid "Any (suppression still applies)"
    14041462msgstr ""
    14051463
    1406 #: includes/trait-campaign-audiences.php:147
    1407 #: includes/trait-campaign-privacy.php:127
     1464#: includes/trait-campaign-audiences.php:156
     1465#: includes/trait-campaign-contact-history.php:66
     1466#: includes/trait-campaign-privacy.php:128
     1467#: includes/trait-campaign-subscriptions.php:118
    14081468msgid "Subscribed"
    14091469msgstr ""
    14101470
    1411 #: includes/trait-campaign-audiences.php:147
     1471#: includes/trait-campaign-audiences.php:156
    14121472msgid "No confirmation recorded"
    14131473msgstr ""
    14141474
    1415 #: includes/trait-campaign-audiences.php:147
     1475#: includes/trait-campaign-audiences.php:156
    14161476msgid "Unsubscribed (excluded from sending)"
    14171477msgstr ""
    14181478
    1419 #: includes/trait-campaign-audiences.php:148
     1479#: includes/trait-campaign-audiences.php:157
    14201480msgid "Campaign history"
    14211481msgstr ""
    14221482
    1423 #: includes/trait-campaign-audiences.php:151
     1483#: includes/trait-campaign-audiences.php:160
    14241484msgid "History outcome"
    14251485msgstr ""
    14261486
    1427 #: includes/trait-campaign-audiences.php:152
     1487#: includes/trait-campaign-audiences.php:161
    14281488msgid "Known unsent terminal outcome"
    14291489msgstr ""
    14301490
    1431 #: includes/trait-campaign-audiences.php:153
     1491#: includes/trait-campaign-audiences.php:162
    14321492msgid "Durable opt-outs always apply. Orders and accounts do not establish opt-in."
    14331493msgstr ""
    14341494
    1435 #: includes/trait-campaign-audiences.php:153
     1495#: includes/trait-campaign-audiences.php:162
    14361496msgid "How audience conditions match"
    14371497msgstr ""
    14381498
    1439 #: includes/trait-campaign-audiences.php:153
     1499#: includes/trait-campaign-audiences.php:162
    14401500msgid "Order filters use order creation time in the store timezone and match on one order, including product/variation, status and billing country. Categories include descendants and use current product categories. Registered users use profile billing country; manual lists have no country. Missing data fails the condition. History uses handoff only: unknown, pending and missing history never qualify as known unsent. Existing exclusions and durable opt-outs apply last."
    14411501msgstr ""
    14421502
    1443 #: includes/trait-campaign-audiences.php:158
     1503#: includes/trait-campaign-audiences.php:167
     1504msgid "Store-wide purchase frequency"
     1505msgstr ""
     1506
     1507#: includes/trait-campaign-audiences.php:167
     1508msgid "Days without a valid store-wide purchase"
     1509msgstr ""
     1510
     1511#: includes/trait-campaign-audiences.php:167
    14441512msgid "Order created from"
    14451513msgstr ""
    14461514
    1447 #: includes/trait-campaign-audiences.php:158
     1515#: includes/trait-campaign-audiences.php:167
    14481516msgid "Through date"
    14491517msgstr ""
    14501518
    1451 #: includes/trait-campaign-audiences.php:158
     1519#: includes/trait-campaign-audiences.php:167
    14521520msgid "Last N days"
    14531521msgstr ""
    14541522
    1455 #: includes/trait-campaign-audiences.php:158
     1523#: includes/trait-campaign-audiences.php:167
    14561524msgid "History campaign"
    14571525msgstr ""
    14581526
    1459 #: includes/trait-campaign-audiences.php:158
     1527#: includes/trait-campaign-audiences.php:167
    14601528msgid "Handoff condition"
    14611529msgstr ""
    14621530
    1463 #: includes/trait-campaign-audiences.php:158
     1531#: includes/trait-campaign-audiences.php:167
    14641532msgid "Categories including descendants"
    14651533msgstr ""
    14661534
    1467 #: includes/trait-campaign-audiences.php:158
     1535#: includes/trait-campaign-audiences.php:167
    14681536msgid "Matching order statuses"
    14691537msgstr ""
    14701538
    1471 #: includes/trait-campaign-audiences.php:159
     1539#: includes/trait-campaign-audiences.php:168
     1540#: includes/trait-campaign-authoring.php:238
     1541msgid "Saved audience conditions"
     1542msgstr ""
     1543
     1544#: includes/trait-campaign-audiences.php:171
     1545#: includes/trait-campaign-purchase-history.php:54
     1546msgid "First-time buyer (one valid order)"
     1547msgstr ""
     1548
     1549#: includes/trait-campaign-audiences.php:171
     1550#: includes/trait-campaign-purchase-history.php:54
     1551msgid "Repeat buyer (at least two valid orders)"
     1552msgstr ""
     1553
     1554#: includes/trait-campaign-audiences.php:181
     1555msgid "Inspect an address against current criteria"
     1556msgstr ""
     1557
     1558#: includes/trait-campaign-audiences.php:181
     1559msgid "Inspect"
     1560msgstr ""
     1561
     1562#: includes/trait-campaign-audiences.php:187
     1563msgid "Not in the selected source or product purchasers."
     1564msgstr ""
     1565
     1566#: includes/trait-campaign-audiences.php:188
     1567msgid "No single order matches all order conditions, or billing country is missing/different."
     1568msgstr ""
     1569
     1570#: includes/trait-campaign-audiences.php:189
     1571msgid "Store-wide purchase history, subscription status or known campaign handoff condition does not match."
     1572msgstr ""
     1573
     1574#: includes/trait-campaign-audiences.php:190
     1575msgid "Matched source and all conditions; removed by an explicit exclusion."
     1576msgstr ""
     1577
     1578#: includes/trait-campaign-audiences.php:191
     1579msgid "Included: matches source and every applicable condition; no exclusion matched."
     1580msgstr ""
     1581
     1582#: includes/trait-campaign-audiences.php:192
     1583msgid "Excluded: durable unsubscribe/suppression."
     1584msgstr ""
     1585
     1586#: includes/trait-campaign-authoring.php:57
     1587msgid "Enter an audience name."
     1588msgstr ""
     1589
     1590#: includes/trait-campaign-authoring.php:60
     1591msgid "Audience could not be saved."
     1592msgstr ""
     1593
     1594#: includes/trait-campaign-authoring.php:86
     1595msgid "This draft changed or was deleted. Open it and create a new preview."
     1596msgstr ""
     1597
     1598#: includes/trait-campaign-authoring.php:107
     1599msgid "Campaign content unavailable."
     1600msgstr ""
     1601
     1602#: includes/trait-campaign-authoring.php:120
     1603msgid "Duplicate as new draft"
     1604msgstr ""
     1605
     1606#: includes/trait-campaign-authoring.php:128
     1607msgid "Drafts"
     1608msgstr ""
     1609
     1610#: includes/trait-campaign-authoring.php:128
     1611msgid "Drafts do not expire and never create delivery jobs."
     1612msgstr ""
     1613
     1614#: includes/trait-campaign-authoring.php:130
     1615msgid "No saved drafts yet."
     1616msgstr ""
     1617
     1618#: includes/trait-campaign-authoring.php:133
     1619msgid "Delete draft"
     1620msgstr ""
     1621
     1622#: includes/trait-campaign-authoring.php:136
     1623#: includes/trait-campaign-authoring.php:150
     1624msgid "Saved audience criteria"
     1625msgstr ""
     1626
     1627#: includes/trait-campaign-authoring.php:136
     1628msgid "Reusable rules, not a frozen recipient list. A new preview is required when reused."
     1629msgstr ""
     1630
     1631#: includes/trait-campaign-authoring.php:138
     1632msgid "No saved audiences yet."
     1633msgstr ""
     1634
     1635#: includes/trait-campaign-authoring.php:141
     1636msgid "Delete audience"
     1637msgstr ""
     1638
     1639#: includes/trait-campaign-authoring.php:147
     1640msgid "Draft name (internal only)"
     1641msgstr ""
     1642
     1643#: includes/trait-campaign-authoring.php:147
     1644msgid "Save draft"
     1645msgstr ""
     1646
     1647#: includes/trait-campaign-authoring.php:147
     1648msgid "Drafts create no delivery jobs. Changes require a new preview."
     1649msgstr ""
     1650
     1651#: includes/trait-campaign-authoring.php:154
     1652msgid "Open Library"
     1653msgstr ""
     1654
     1655#: includes/trait-campaign-authoring.php:154
     1656msgid "Opening saved criteria replaces this form. Save your draft first to keep current edits."
     1657msgstr ""
     1658
     1659#: includes/trait-campaign-authoring.php:154
     1660msgid "Audience name"
     1661msgstr ""
     1662
     1663#: includes/trait-campaign-authoring.php:154
     1664msgid "Save audience criteria"
     1665msgstr ""
     1666
     1667#: includes/trait-campaign-authoring.php:157
     1668msgid "Insert editable content template"
     1669msgstr ""
     1670
     1671#: includes/trait-campaign-authoring.php:159
     1672msgid "Product or documentation update"
     1673msgstr ""
     1674
     1675#: includes/trait-campaign-authoring.php:159
     1676msgid "An update for you"
     1677msgstr ""
     1678
     1679#: includes/trait-campaign-authoring.php:159
     1680msgid "<p>Hello {customer_name},</p><p>We have updated {product_name}. Describe what changed, when it applies, and add the documentation link and any action needed. Contact our support team if you need help.</p>"
     1681msgstr ""
     1682
     1683#: includes/trait-campaign-authoring.php:160
     1684msgid "After-purchase guide"
     1685msgstr ""
     1686
     1687#: includes/trait-campaign-authoring.php:160
     1688msgid "Getting started"
     1689msgstr ""
     1690
     1691#: includes/trait-campaign-authoring.php:160
     1692msgid "<p>Hello {customer_name},</p><p>Thank you for your purchase of {ordered_products} (order {order_number}). Add setup or care steps, when to follow them, and a support contact here.</p>"
     1693msgstr ""
     1694
     1695#: includes/trait-campaign-authoring.php:161
     1696msgid "Service or order change"
     1697msgstr ""
     1698
     1699#: includes/trait-campaign-authoring.php:161
     1700msgid "Service update"
     1701msgstr ""
     1702
     1703#: includes/trait-campaign-authoring.php:161
     1704msgid "<p>Hello {customer_name},</p><p>An update about order {order_number}: describe the change and the new delivery date or effective time. Explain any action needed and add a support contact here.</p>"
     1705msgstr ""
     1706
     1707#: includes/trait-campaign-authoring.php:197
     1708msgid "At least one recipient has a past, nonexistent or repeated local time. Choose a later, unambiguous time and preview again. No date was shifted."
     1709msgstr ""
     1710
     1711#: includes/trait-campaign-authoring.php:220
     1712msgid "Preview width"
     1713msgstr ""
     1714
     1715#: includes/trait-campaign-authoring.php:221
     1716msgid "Desktop"
     1717msgstr ""
     1718
     1719#: includes/trait-campaign-authoring.php:221
     1720msgid "Mobile"
     1721msgstr ""
     1722
     1723#: includes/trait-campaign-authoring.php:227
     1724msgid "Audience changed since preview. Review this updated preview and confirm again."
     1725msgstr ""
     1726
    14721727#: includes/trait-campaign-authoring.php:228
    1473 msgid "Saved audience conditions"
    1474 msgstr ""
    1475 
    1476 #: includes/trait-campaign-audiences.php:171
    1477 msgid "Inspect an address against current criteria"
    1478 msgstr ""
    1479 
    1480 #: includes/trait-campaign-audiences.php:171
    1481 msgid "Inspect"
    1482 msgstr ""
    1483 
    1484 #: includes/trait-campaign-audiences.php:177
    1485 msgid "Not in the selected source or product purchasers."
    1486 msgstr ""
    1487 
    1488 #: includes/trait-campaign-audiences.php:178
    1489 msgid "No single order matches all order conditions, or billing country is missing/different."
    1490 msgstr ""
    1491 
    1492 #: includes/trait-campaign-audiences.php:179
    1493 msgid "Subscription status or known campaign handoff condition does not match."
    1494 msgstr ""
    1495 
    1496 #: includes/trait-campaign-audiences.php:180
    1497 msgid "Matched source and all conditions; removed by an explicit exclusion."
    1498 msgstr ""
    1499 
    1500 #: includes/trait-campaign-audiences.php:181
    1501 msgid "Included: matches source and every applicable condition; no exclusion matched."
    1502 msgstr ""
    1503 
    1504 #: includes/trait-campaign-audiences.php:182
    1505 msgid "Excluded: durable unsubscribe/suppression."
    1506 msgstr ""
    1507 
    1508 #: includes/trait-campaign-authoring.php:53
    1509 msgid "Enter an audience name."
    1510 msgstr ""
    1511 
    1512 #: includes/trait-campaign-authoring.php:56
    1513 msgid "Audience could not be saved."
    1514 msgstr ""
    1515 
    1516 #: includes/trait-campaign-authoring.php:76
    1517 msgid "This draft changed or was deleted. Open it and create a new preview."
    1518 msgstr ""
    1519 
    1520 #: includes/trait-campaign-authoring.php:97
    1521 msgid "Campaign content unavailable."
    1522 msgstr ""
    1523 
    1524 #: includes/trait-campaign-authoring.php:110
    1525 msgid "Duplicate as new draft"
    1526 msgstr ""
    1527 
    1528 #: includes/trait-campaign-authoring.php:118
    1529 msgid "Drafts"
    1530 msgstr ""
    1531 
    1532 #: includes/trait-campaign-authoring.php:118
    1533 msgid "Drafts do not expire and never create delivery jobs."
    1534 msgstr ""
    1535 
    1536 #: includes/trait-campaign-authoring.php:120
    1537 msgid "No saved drafts yet."
    1538 msgstr ""
    1539 
    1540 #: includes/trait-campaign-authoring.php:123
    1541 msgid "Delete draft"
    1542 msgstr ""
    1543 
    1544 #: includes/trait-campaign-authoring.php:126
    1545 #: includes/trait-campaign-authoring.php:140
    1546 msgid "Saved audience criteria"
    1547 msgstr ""
    1548 
    1549 #: includes/trait-campaign-authoring.php:126
    1550 msgid "Reusable rules, not a frozen recipient list. A new preview is required when reused."
    1551 msgstr ""
    1552 
    1553 #: includes/trait-campaign-authoring.php:128
    1554 msgid "No saved audiences yet."
    1555 msgstr ""
    1556 
    1557 #: includes/trait-campaign-authoring.php:131
    1558 msgid "Delete audience"
    1559 msgstr ""
    1560 
    1561 #: includes/trait-campaign-authoring.php:137
    1562 msgid "Draft name (internal only)"
    1563 msgstr ""
    1564 
    1565 #: includes/trait-campaign-authoring.php:137
    1566 msgid "Save draft"
    1567 msgstr ""
    1568 
    1569 #: includes/trait-campaign-authoring.php:137
    1570 msgid "Drafts create no delivery jobs. Changes require a new preview."
    1571 msgstr ""
    1572 
    1573 #: includes/trait-campaign-authoring.php:144
    1574 msgid "Open Library"
    1575 msgstr ""
    1576 
    1577 #: includes/trait-campaign-authoring.php:144
    1578 msgid "Opening saved criteria replaces this form. Save your draft first to keep current edits."
    1579 msgstr ""
    1580 
    1581 #: includes/trait-campaign-authoring.php:144
    1582 msgid "Audience name"
    1583 msgstr ""
    1584 
    1585 #: includes/trait-campaign-authoring.php:144
    1586 msgid "Save audience criteria"
    1587 msgstr ""
    1588 
    1589 #: includes/trait-campaign-authoring.php:147
    1590 msgid "Insert editable content template"
    1591 msgstr ""
    1592 
    1593 #: includes/trait-campaign-authoring.php:149
    1594 msgid "Product or documentation update"
    1595 msgstr ""
    1596 
    1597 #: includes/trait-campaign-authoring.php:149
    1598 msgid "An update for you"
    1599 msgstr ""
    1600 
    1601 #: includes/trait-campaign-authoring.php:149
    1602 msgid "<p>Hello {customer_name},</p><p>We have updated {product_name}. Add the details and documentation link here.</p>"
    1603 msgstr ""
    1604 
    1605 #: includes/trait-campaign-authoring.php:150
    1606 msgid "After-purchase guide"
    1607 msgstr ""
    1608 
    1609 #: includes/trait-campaign-authoring.php:150
    1610 msgid "Getting started"
    1611 msgstr ""
    1612 
    1613 #: includes/trait-campaign-authoring.php:150
    1614 msgid "<p>Hello {customer_name},</p><p>Thank you for your purchase. Add setup steps and support details here.</p>"
    1615 msgstr ""
    1616 
    1617 #: includes/trait-campaign-authoring.php:151
    1618 msgid "Service or order change"
    1619 msgstr ""
    1620 
    1621 #: includes/trait-campaign-authoring.php:151
    1622 msgid "Service update"
    1623 msgstr ""
    1624 
    1625 #: includes/trait-campaign-authoring.php:151
    1626 msgid "<p>Hello {customer_name},</p><p>Add the change, effective date and any action needed here.</p>"
    1627 msgstr ""
    1628 
    1629 #: includes/trait-campaign-authoring.php:187
    1630 msgid "At least one recipient has a past, nonexistent or repeated local time. Choose a later, unambiguous time and preview again. No date was shifted."
    1631 msgstr ""
    1632 
    1633 #: includes/trait-campaign-authoring.php:210
    1634 msgid "Preview width"
    1635 msgstr ""
    1636 
    1637 #: includes/trait-campaign-authoring.php:211
    1638 msgid "Desktop"
    1639 msgstr ""
    1640 
    1641 #: includes/trait-campaign-authoring.php:211
    1642 msgid "Mobile"
    1643 msgstr ""
    1644 
    1645 #: includes/trait-campaign-authoring.php:217
    1646 msgid "Audience changed since preview. Review this updated preview and confirm again."
    1647 msgstr ""
    1648 
    1649 #: includes/trait-campaign-authoring.php:218
    16501728msgid "Recipients and schedule to confirm"
    16511729msgstr ""
    16521730
    1653 #: includes/trait-campaign-authoring.php:218
     1731#: includes/trait-campaign-authoring.php:228
    16541732msgid "Selected"
    16551733msgstr ""
    16561734
    1657 #: includes/trait-campaign-authoring.php:219
     1735#: includes/trait-campaign-authoring.php:229
    16581736msgid "Removed by order conditions"
    16591737msgstr ""
    16601738
    1661 #: includes/trait-campaign-authoring.php:219
    1662 msgid "Removed by subscription or campaign history"
    1663 msgstr ""
    1664 
    1665 #: includes/trait-campaign-authoring.php:219
     1739#: includes/trait-campaign-authoring.php:229
     1740msgid "Removed by purchase history, subscription or campaign history"
     1741msgstr ""
     1742
     1743#: includes/trait-campaign-authoring.php:229
    16661744msgid "Removed by exclusions or suppression"
    16671745msgstr ""
    16681746
    1669 #: includes/trait-campaign-authoring.php:222
     1747#: includes/trait-campaign-authoring.php:232
    16701748msgid "One time in store timezone"
    16711749msgstr ""
    16721750
    1673 #: includes/trait-campaign-authoring.php:222
     1751#: includes/trait-campaign-authoring.php:232
    16741752msgid "Legacy schedule"
    16751753msgstr ""
    16761754
    1677 #: includes/trait-campaign-authoring.php:223
     1755#: includes/trait-campaign-authoring.php:233
    16781756msgid "Schedule mode / store timezone"
    16791757msgstr ""
    16801758
    1681 #: includes/trait-campaign-authoring.php:226
     1759#: includes/trait-campaign-authoring.php:236
    16821760msgid "UTC fallback count"
    16831761msgstr ""
    16841762
    1685 #: includes/trait-campaign-authoring.php:226
     1763#: includes/trait-campaign-authoring.php:236
    16861764msgid "Earliest and latest due (UTC)"
    16871765msgstr ""
    16881766
    1689 #: includes/trait-campaign-authoring.php:230
     1767#: includes/trait-campaign-authoring.php:240
    16901768msgid "Inspect an address"
    16911769msgstr ""
    16921770
    1693 #: includes/trait-campaign-authoring.php:237
    1694 #: includes/trait-campaign-authoring.php:244
     1771#: includes/trait-campaign-authoring.php:247
     1772#: includes/trait-campaign-authoring.php:257
    16951773msgid "Personalized email preview"
    16961774msgstr ""
    16971775
    1698 #: includes/trait-campaign-authoring.php:237
     1776#: includes/trait-campaign-authoring.php:247
    16991777msgid "Sample recipient (up to 20)"
    17001778msgstr ""
    17011779
    1702 #: includes/trait-campaign-authoring.php:239
     1780#: includes/trait-campaign-authoring.php:249
    17031781msgid "Show sample"
     1782msgstr ""
     1783
     1784#: includes/trait-campaign-automation-ui.php:17
     1785msgid "Use the automation form."
     1786msgstr ""
     1787
     1788#: includes/trait-campaign-automation-ui.php:19
     1789msgid "Choose a valid automation rule."
     1790msgstr ""
     1791
     1792#: includes/trait-campaign-automation-ui.php:32
     1793msgid "Choose an available order ID for the sample."
     1794msgstr ""
     1795
     1796#: includes/trait-campaign-automation-ui.php:38
     1797msgid "The test email could not be handed off. Check mail settings and try again."
     1798msgstr ""
     1799
     1800#: includes/trait-campaign-automation-ui.php:47
     1801msgid "Choose a valid automation action."
     1802msgstr ""
     1803
     1804#: includes/trait-campaign-automation-ui.php:66
     1805msgid "This automation rule is unavailable."
     1806msgstr ""
     1807
     1808#: includes/trait-campaign-automation-ui.php:67
     1809msgid "Rule saved. New rules stay disabled until you choose Enable. Changes to enabled rules apply to new events only."
     1810msgstr ""
     1811
     1812#: includes/trait-campaign-automation-ui.php:67
     1813msgid "Rule enabled for new Completed events. Historical orders were not queued."
     1814msgstr ""
     1815
     1816#: includes/trait-campaign-automation-ui.php:67
     1817msgid "Rule disabled. Unstarted emails are cancelled; work already in flight may finish."
     1818msgstr ""
     1819
     1820#: includes/trait-campaign-automation-ui.php:67
     1821msgid "Test mail handed to the mail system. No rule was saved or enabled. Inbox delivery is not verified."
     1822msgstr ""
     1823
     1824#: includes/trait-campaign-automation-ui.php:67
     1825msgid "Sample refreshed. No rule was saved or enabled and no delivery job was created."
     1826msgstr ""
     1827
     1828#: includes/trait-campaign-automation-ui.php:80
     1829msgid "Send a helpful guide after an order is completed. Enable uses the saved configuration."
     1830msgstr ""
     1831
     1832#: includes/trait-campaign-automation-ui.php:80
     1833msgid "New rule"
     1834msgstr ""
     1835
     1836#: includes/trait-campaign-automation-ui.php:81
     1837msgid "No rules yet. Build your first guide below, then enable it when you are ready."
     1838msgstr ""
     1839
     1840#: includes/trait-campaign-automation-ui.php:84
     1841msgid "Enabled"
     1842msgstr ""
     1843
     1844#: includes/trait-campaign-automation-ui.php:84
     1845msgid "Disabled"
     1846msgstr ""
     1847
     1848#. translators: %d: Number of hours after the Completed event.
     1849#: includes/trait-campaign-automation-ui.php:84
     1850msgid "Completed → wait %d hours → send one guide"
     1851msgstr ""
     1852
     1853#. translators: %d: Number of hours after the Completed event.
     1854#: includes/trait-campaign-automation-ui.php:84
     1855msgid "Saved configuration"
     1856msgstr ""
     1857
     1858#. translators: %d: Number of hours after the Completed event.
     1859#: includes/trait-campaign-automation-ui.php:84
     1860msgid "All products"
     1861msgstr ""
     1862
     1863#. translators: %d: Number of hours after the Completed event.
     1864#: includes/trait-campaign-automation-ui.php:84
     1865msgid "Categories"
     1866msgstr ""
     1867
     1868#. translators: %d: Number of hours after the Completed event.
     1869#: includes/trait-campaign-automation-ui.php:84
     1870msgid "All categories"
     1871msgstr ""
     1872
     1873#. translators: %d: Number of hours after the Completed event.
     1874#: includes/trait-campaign-automation-ui.php:85
     1875msgid "Edit rule"
     1876msgstr ""
     1877
     1878#. translators: %d: Number of hours after the Completed event.
     1879#: includes/trait-campaign-automation-ui.php:85
     1880msgid "Disable and cancel waiting emails"
     1881msgstr ""
     1882
     1883#. translators: %d: Number of hours after the Completed event.
     1884#: includes/trait-campaign-automation-ui.php:85
     1885msgid "Enable"
     1886msgstr ""
     1887
     1888#: includes/trait-campaign-automation-ui.php:88
     1889msgid "View campaigns and delivery status"
     1890msgstr ""
     1891
     1892#: includes/trait-campaign-automation-ui.php:88
     1893msgid "Look up a contact"
     1894msgstr ""
     1895
     1896#: includes/trait-campaign-automation-ui.php:92
     1897msgid "Rule name (internal only)"
     1898msgstr ""
     1899
     1900#: includes/trait-campaign-automation-ui.php:92
     1901#: includes/trait-campaign-automation-ui.php:119
     1902msgid "Save changes for new events"
     1903msgstr ""
     1904
     1905#: includes/trait-campaign-automation-ui.php:92
     1906#: includes/trait-campaign-automation-ui.php:119
     1907msgid "Save rule"
     1908msgstr ""
     1909
     1910#: includes/trait-campaign-automation-ui.php:92
     1911msgid "Waiting emails keep their confirmed content, order context and due time."
     1912msgstr ""
     1913
     1914#: includes/trait-campaign-automation-ui.php:92
     1915msgid "Save your rule, then explicitly Enable it above. Saving alone never enables a new rule."
     1916msgstr ""
     1917
     1918#: includes/trait-campaign-automation-ui.php:93
     1919msgid "Automation steps"
     1920msgstr ""
     1921
     1922#: includes/trait-campaign-automation-ui.php:94
     1923msgid "1. Conditions"
     1924msgstr ""
     1925
     1926#: includes/trait-campaign-automation-ui.php:94
     1927msgid "3. Timing"
     1928msgstr ""
     1929
     1930#: includes/trait-campaign-automation-ui.php:97
     1931msgid "Conditions"
     1932msgstr ""
     1933
     1934#: includes/trait-campaign-automation-ui.php:97
     1935msgid "Choose which completed orders should receive this guide."
     1936msgstr ""
     1937
     1938#: includes/trait-campaign-automation-ui.php:98
     1939msgid "Trigger: order changes to Completed"
     1940msgstr ""
     1941
     1942#: includes/trait-campaign-automation-ui.php:98
     1943msgid "Only new events after Enable are captured. There is no historical backfill. A customer with several qualifying orders can receive one guide for each order."
     1944msgstr ""
     1945
     1946#: includes/trait-campaign-automation-ui.php:99
     1947msgid "Products or variations (empty means any)"
     1948msgstr ""
     1949
     1950#: includes/trait-campaign-automation-ui.php:100
     1951msgid "Categories including descendants (empty means any)"
     1952msgstr ""
     1953
     1954#: includes/trait-campaign-automation-ui.php:102
     1955msgid "Product and category conditions match the same order. Parent products include their variations; a selected variation matches only that variation."
     1956msgstr ""
     1957
     1958#: includes/trait-campaign-automation-ui.php:103
     1959msgid "Eligibility and subscription"
     1960msgstr ""
     1961
     1962#: includes/trait-campaign-automation-ui.php:103
     1963msgid "An active subscription is required at sending. Unknown is not consent. Unsubscribe and erasure protection always apply. The order must still be Completed, match its saved conditions and keep the same billing email. Cancelled, failed and fully refunded orders are skipped; partial refunds remain eligible while an amount remains."
     1964msgstr ""
     1965
     1966#: includes/trait-campaign-automation-ui.php:106
     1967msgid "Write useful setup or care instructions for the purchased items."
     1968msgstr ""
     1969
     1970#: includes/trait-campaign-automation-ui.php:107
     1971msgid "A guide for order {order_number}"
     1972msgstr ""
     1973
     1974#: includes/trait-campaign-automation-ui.php:107
     1975msgid "Getting started with your purchase"
     1976msgstr ""
     1977
     1978#: includes/trait-campaign-automation-ui.php:108
     1979msgid "<p>Hello {customer_name},</p><p>Thank you for purchasing {ordered_products}.</p><p>Before you start, check the setup instructions included with your order. Keep this email handy and contact our store if you need help.</p><p>Order: {order_number}</p>"
     1980msgstr ""
     1981
     1982#: includes/trait-campaign-automation-ui.php:109
     1983msgid "An unsubscribe link is added automatically. The live preview shows template text; Review uses the exact sample order."
     1984msgstr ""
     1985
     1986#: includes/trait-campaign-automation-ui.php:109
     1987msgid "Personalization variables"
     1988msgstr ""
     1989
     1990#: includes/trait-campaign-automation-ui.php:109
     1991msgid "Use {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products} and {order_url} in the subject, heading or body. Order values come from the triggering order and are frozen when captured. Missing names use Customer. Guest order links are empty; account links require the owning account login."
     1992msgstr ""
     1993
     1994#: includes/trait-campaign-automation-ui.php:112
     1995msgid "Timing"
     1996msgstr ""
     1997
     1998#: includes/trait-campaign-automation-ui.php:112
     1999msgid "Set how long to wait after the order becomes Completed."
     2000msgstr ""
     2001
     2002#: includes/trait-campaign-automation-ui.php:113
     2003msgid "Delay after Completed (hours)"
     2004msgstr ""
     2005
     2006#: includes/trait-campaign-automation-ui.php:113
     2007msgid "Use 0 to queue immediately, or 24 to wait one day. Sending limits and queue delays can make actual delivery later."
     2008msgstr ""
     2009
     2010#: includes/trait-campaign-automation-ui.php:113
     2011msgid "Editing the delay affects new events only. Waiting emails keep their original due time."
     2012msgstr ""
     2013
     2014#: includes/trait-campaign-automation-ui.php:116
     2015msgid "Preview and test"
     2016msgstr ""
     2017
     2018#: includes/trait-campaign-automation-ui.php:116
     2019msgid "Check the personalized email using an order you can access."
     2020msgstr ""
     2021
     2022#: includes/trait-campaign-automation-ui.php:117
     2023msgid "Sample order ID"
     2024msgstr ""
     2025
     2026#: includes/trait-campaign-automation-ui.php:117
     2027msgid "Preview guide"
     2028msgstr ""
     2029
     2030#: includes/trait-campaign-automation-ui.php:118
     2031msgid "Send test email"
     2032msgstr ""
     2033
     2034#: includes/trait-campaign-automation-ui.php:118
     2035msgid "Test mail goes only to this address. Preview and test create no campaign or delivery job, and never save or enable the rule."
     2036msgstr ""
     2037
     2038#: includes/trait-campaign-automation-ui.php:119
     2039msgid "Ready to save?"
     2040msgstr ""
     2041
     2042#: includes/trait-campaign-automation-ui.php:119
     2043msgid "Save the form, then Enable the saved rule above. If it is already enabled, your changes apply to new events only."
     2044msgstr ""
     2045
     2046#: includes/trait-campaign-automation-ui.php:119
     2047msgid "What happens when I disable a rule?"
     2048msgstr ""
     2049
     2050#: includes/trait-campaign-automation-ui.php:119
     2051msgid "New events stop and unstarted emails are cancelled. Work already in flight may finish. Re-enabling never restores cancelled mail. History is preserved."
     2052msgstr ""
     2053
     2054#: includes/trait-campaign-automation-ui.php:128
     2055#: includes/trait-campaign-automation-ui.php:129
     2056#: includes/trait-campaign-automation-ui.php:135
     2057msgid "Personalized guide preview"
     2058msgstr ""
     2059
     2060#: includes/trait-campaign-automation-ui.php:129
     2061msgid "Based on the form at the last refresh."
     2062msgstr ""
     2063
     2064#: includes/trait-campaign-automation-ui.php:129
     2065msgid "Sample"
     2066msgstr ""
     2067
     2068#: includes/trait-campaign-automation-ui.php:135
     2069msgid "The form has changed. Choose Preview guide to refresh this sample before saving or testing."
     2070msgstr ""
     2071
     2072#: includes/trait-campaign-automation-ui.php:135
     2073msgid "Sample recipient"
     2074msgstr ""
     2075
     2076#: includes/trait-campaign-automation-ui.php:136
     2077msgid "Choose a sample order and select Preview guide to see its customer name, products and order details here."
     2078msgstr ""
     2079
     2080#: includes/trait-campaign-automations.php:16
     2081msgid "Review the rule name, products, categories, delay and required email content."
     2082msgstr ""
     2083
     2084#: includes/trait-campaign-automations.php:37
     2085#: includes/trait-campaign-automations.php:48
     2086msgid "Another rule change is in progress. Please try again."
     2087msgstr ""
     2088
     2089#: includes/trait-campaign-automations.php:40
     2090msgid "Rule not found. Open Automations again."
     2091msgstr ""
     2092
     2093#: includes/trait-campaign-automations.php:44
     2094msgid "The rule could not be saved. Please try again after checking storage."
     2095msgstr ""
     2096
     2097#: includes/trait-campaign-automations.php:51
     2098msgid "Rule not found."
     2099msgstr ""
     2100
     2101#: includes/trait-campaign-automations.php:56
     2102msgid "The rule change could not be verified. Please check its current state and try again."
     2103msgstr ""
     2104
     2105#: includes/trait-campaign-automations.php:60
     2106msgid "The rule is disabled and its previous generation cannot send. Some campaign cancellations still need cleanup; open Campaigns again after checking storage."
     2107msgstr ""
     2108
     2109#: includes/trait-campaign-automations.php:77
     2110#: includes/trait-campaign-automations.php:157
     2111msgid "Completed-order guide"
     2112msgstr ""
     2113
     2114#: includes/trait-campaign-automations.php:90
     2115msgid "The triggering order has no valid billing email. No guide was queued."
     2116msgstr ""
     2117
     2118#: includes/trait-campaign-automations.php:144
     2119msgid "Guide rule disabled or this pending email belongs to an earlier enabled generation."
     2120msgstr ""
     2121
     2122#: includes/trait-campaign-automations.php:146
     2123msgid "The triggering order is no longer Completed, has been fully refunded, changed billing email or no longer matches its saved product/category conditions."
     2124msgstr ""
     2125
     2126#: includes/trait-campaign-automations.php:148
     2127msgid "The triggering order or rule could not be checked. No guide was handed off."
     2128msgstr ""
     2129
     2130#: includes/trait-campaign-automations.php:152
     2131msgid "Choose a valid sample order ID."
     2132msgstr ""
     2133
     2134#: includes/trait-campaign-automations.php:154
     2135msgid "Choose an available order that you can access for preview."
     2136msgstr ""
     2137
     2138#: includes/trait-campaign-automations.php:155
     2139msgid "This sample is protected by erasure. Its previous personal context cannot be previewed or tested."
     2140msgstr ""
     2141
     2142#: includes/trait-campaign-automations.php:159
     2143msgid "This sample order does not currently match the rule. Preview only; it will not trigger a guide in this state."
     2144msgstr ""
     2145
     2146#: includes/trait-campaign-automations.php:159
     2147msgid "This sample has no active subscription. A real guide would be skipped unless subscribed at send time."
     2148msgstr ""
     2149
     2150#: includes/trait-campaign-automations.php:159
     2151msgid "This sample matches now. Enabling never sends to historical orders; only a new Completed event can trigger a guide."
     2152msgstr ""
     2153
     2154#: includes/trait-campaign-automations.php:163
     2155msgid "Enter a valid test email address."
     2156msgstr ""
     2157
     2158#: includes/trait-campaign-checkout.php:16
     2159#: includes/trait-campaign-checkout.php:26
     2160msgid "Email me store news and offers. I will confirm my email address before subscribing."
     2161msgstr ""
     2162
     2163#: includes/trait-campaign-contact-history.php:7
     2164msgid "Enter one valid email address to look up its contact history."
     2165msgstr ""
     2166
     2167#: includes/trait-campaign-contact-history.php:19
     2168#: includes/trait-campaign-contact-history.php:24
     2169#: includes/trait-campaign-contact-history.php:51
     2170msgid "Contact history could not be read. Please try again after checking storage."
     2171msgstr ""
     2172
     2173#: includes/trait-campaign-contact-history.php:62
     2174msgid "Contact history by email"
     2175msgstr ""
     2176
     2177#: includes/trait-campaign-contact-history.php:62
     2178msgid "Only contacts recorded by Advanced Notifications are shown. A mail-system handoff does not confirm inbox delivery or that an email was read."
     2179msgstr ""
     2180
     2181#: includes/trait-campaign-contact-history.php:62
     2182msgid "Email for contact history"
     2183msgstr ""
     2184
     2185#: includes/trait-campaign-contact-history.php:62
     2186msgid "Look up contacts"
     2187msgstr ""
     2188
     2189#: includes/trait-campaign-contact-history.php:65
     2190msgid "This address is protected by erasure. Its previous contact details are not shown or reconstructed."
     2191msgstr ""
     2192
     2193#: includes/trait-campaign-contact-history.php:66
     2194#: includes/trait-campaign-controls.php:143
     2195#: includes/trait-campaign-controls.php:173
     2196#: includes/trait-campaign-privacy.php:128
     2197#: includes/trait-campaign-privacy.php:145
     2198msgid "Unknown"
     2199msgstr ""
     2200
     2201#: includes/trait-campaign-contact-history.php:68
     2202msgid "Current subscription"
     2203msgstr ""
     2204
     2205#: includes/trait-campaign-contact-history.php:69
     2206msgid "Recorded source"
     2207msgstr ""
     2208
     2209#: includes/trait-campaign-contact-history.php:70
     2210msgid "No campaign contacts are currently retained for this email."
     2211msgstr ""
     2212
     2213#: includes/trait-campaign-contact-history.php:71
     2214msgid "Campaign / saved context"
     2215msgstr ""
     2216
     2217#: includes/trait-campaign-contact-history.php:71
     2218msgid "Recorded times (UTC)"
     2219msgstr ""
     2220
     2221#: includes/trait-campaign-contact-history.php:71
     2222msgid "Outcome / reason"
     2223msgstr ""
     2224
     2225#: includes/trait-campaign-contact-history.php:79
     2226msgid "Saved matching order"
     2227msgstr ""
     2228
     2229#: includes/trait-campaign-contact-history.php:80
     2230msgid "Campaign content is no longer available. Only the retained outcome is shown."
     2231msgstr ""
     2232
     2233#: includes/trait-campaign-contact-history.php:81
     2234msgid "Created"
     2235msgstr ""
     2236
     2237#: includes/trait-campaign-contact-history.php:81
     2238msgid "Due"
     2239msgstr ""
     2240
     2241#: includes/trait-campaign-contact-history.php:87
     2242msgid "Previous contacts"
     2243msgstr ""
     2244
     2245#. translators: 1: Current page, 2: Total pages.
     2246#: includes/trait-campaign-contact-history.php:89
     2247msgid "Page %1$d of %2$d"
     2248msgstr ""
     2249
     2250#: includes/trait-campaign-contact-history.php:90
     2251msgid "Next contacts"
    17042252msgstr ""
    17052253
    … …  
    17732321msgstr ""
    17742322
    1775 #: includes/trait-campaign-controls.php:143
    1776 #: includes/trait-campaign-controls.php:173
    1777 #: includes/trait-campaign-privacy.php:127
    1778 #: includes/trait-campaign-privacy.php:143
    1779 msgid "Unknown"
    1780 msgstr ""
    1781 
    17822323#: includes/trait-campaign-controls.php:151
    17832324msgid "Handed off does not verify inbox delivery. Unknown outcomes are never retried automatically. Retry only eligible known failures after correcting the reported problem."
    … …  
    18922433msgstr ""
    18932434
     2435#: includes/trait-campaign-entrypoints.php:24
     2436#: includes/trait-campaign-entrypoints.php:32
     2437msgid "Email purchasers"
     2438msgstr ""
     2439
     2440#: includes/trait-campaign-entrypoints.php:32
     2441msgid "A parent product includes all its variations. Opening a draft sends no email."
     2442msgstr ""
     2443
     2444#: includes/trait-campaign-entrypoints.php:38
     2445msgid "Email purchasers of this variation"
     2446msgstr ""
     2447
     2448#: includes/trait-campaign-entrypoints.php:44
     2449msgid "Email this customer"
     2450msgstr ""
     2451
     2452#: includes/trait-campaign-entrypoints.php:49
     2453msgid "Email selected orders"
     2454msgstr ""
     2455
     2456#: includes/trait-campaign-entrypoints.php:62
     2457msgid "Select at least one available order."
     2458msgstr ""
     2459
     2460#: includes/trait-campaign-entrypoints.php:65
     2461#: includes/trait-campaign-entrypoints.php:67
     2462msgid "A selected order is invalid or unavailable."
     2463msgstr ""
     2464
     2465#: includes/trait-campaign-entrypoints.php:91
     2466msgid "A selected product is invalid or unavailable."
     2467msgstr ""
     2468
     2469#: includes/trait-campaign-entrypoints.php:94
     2470msgid "Invalid draft source."
     2471msgstr ""
     2472
     2473#: includes/trait-campaign-personalization.php:6
     2474msgid "Choose valid orders. No order selection has been ignored."
     2475msgstr ""
     2476
     2477#: includes/trait-campaign-personalization.php:16
     2478msgid "Clear selected orders or choose Selected orders before previewing."
     2479msgstr ""
     2480
     2481#: includes/trait-campaign-personalization.php:18
     2482msgid "Select at least one order."
     2483msgstr ""
     2484
     2485#: includes/trait-campaign-personalization.php:21
     2486msgid "A selected order is unavailable or cannot be accessed. Review the selection."
     2487msgstr ""
     2488
     2489#: includes/trait-campaign-personalization.php:26
     2490msgid "Selected order IDs (exact scope)"
     2491msgstr ""
     2492
     2493#: includes/trait-campaign-personalization.php:26
     2494msgid "Used only with Selected orders. These orders do not expand into other purchases. All selected order statuses are included unless filtered below; invalid emails and suppression are excluded. Product customers includes purchases of the selected parent and its variations; a selected variation matches only that variation."
     2495msgstr ""
     2496
     2497#: includes/trait-campaign-personalization.php:47
     2498msgid "Customer"
     2499msgstr ""
     2500
     2501#: includes/trait-campaign-personalization.php:60
     2502msgid "Order context: highest matching order ID"
     2503msgstr ""
     2504
     2505#: includes/trait-campaign-personalization.php:60
     2506msgid "Matching orders"
     2507msgstr ""
     2508
    18942509#: includes/trait-campaign-privacy.php:57
    18952510msgid "Invalid email; use one plain email address per line."
    … …  
    19842599msgstr ""
    19852600
    1986 #: includes/trait-campaign-privacy.php:123
     2601#: includes/trait-campaign-privacy.php:124
    19872602msgid "Subscribers and suppression"
    19882603msgstr ""
    19892604
    1990 #: includes/trait-campaign-privacy.php:123
     2605#: includes/trait-campaign-privacy.php:124
    19912606msgid "An order, account or pasted email is not opt-in evidence. Unknown remains unknown. Imports never remove an unsubscribe. For subscribed records, enter the actual evidence source; the recorded time is the import time, not an inferred original consent time."
    19922607msgstr ""
    19932608
    1994 #: includes/trait-campaign-privacy.php:125
     2609#: includes/trait-campaign-privacy.php:126
    19952610msgid "Find subscription or suppression"
    19962611msgstr ""
    19972612
    1998 #: includes/trait-campaign-privacy.php:125
     2613#: includes/trait-campaign-privacy.php:126
    19992614msgid "State"
    20002615msgstr ""
    20012616
    2002 #: includes/trait-campaign-privacy.php:127
     2617#: includes/trait-campaign-privacy.php:128
    20032618msgid "All states"
    20042619msgstr ""
    20052620
    2006 #: includes/trait-campaign-privacy.php:128
     2621#: includes/trait-campaign-privacy.php:129
    20072622msgid "Search"
    20082623msgstr ""
    20092624
    2010 #: includes/trait-campaign-privacy.php:128
     2625#: includes/trait-campaign-privacy.php:129
    20112626msgid "Email / state"
    20122627msgstr ""
    20132628
    2014 #: includes/trait-campaign-privacy.php:128
     2629#: includes/trait-campaign-privacy.php:129
    20152630msgid "Source / recorded time (UTC)"
    20162631msgstr ""
    20172632
    2018 #: includes/trait-campaign-privacy.php:140
     2633#: includes/trait-campaign-privacy.php:141
     2634msgid "Confirmation pending; current subscription unchanged."
     2635msgstr ""
     2636
     2637#: includes/trait-campaign-privacy.php:142
    20192638msgid "Up to 200 matches. Erased addresses are represented only by protective hashes and are not listed as plaintext."
    20202639msgstr ""
    20212640
    2022 #: includes/trait-campaign-privacy.php:141
     2641#: includes/trait-campaign-privacy.php:143
    20232642msgid "Import records"
    20242643msgstr ""
    20252644
    2026 #: includes/trait-campaign-privacy.php:141
     2645#: includes/trait-campaign-privacy.php:143
    20272646msgid "1. Paste → 2. Preview → 3. Apply and review result"
    20282647msgstr ""
    20292648
    2030 #: includes/trait-campaign-privacy.php:142
     2649#: includes/trait-campaign-privacy.php:144
    20312650msgid "One email per line"
    20322651msgstr ""
    20332652
    2034 #: includes/trait-campaign-privacy.php:143
     2653#: includes/trait-campaign-privacy.php:145
    20352654msgid "Subscribed (evidence required)"
    20362655msgstr ""
    20372656
    2038 #: includes/trait-campaign-privacy.php:144
     2657#: includes/trait-campaign-privacy.php:146
    20392658msgid "Evidence/source"
    20402659msgstr ""
    20412660
    2042 #: includes/trait-campaign-privacy.php:144
     2661#: includes/trait-campaign-privacy.php:146
    20432662msgid "Preview import"
    20442663msgstr ""
    20452664
    2046 #: includes/trait-campaign-privacy.php:147
     2665#: includes/trait-campaign-privacy.php:149
    20472666msgid "Previewed import"
    20482667msgstr ""
    20492668
    2050 #: includes/trait-campaign-privacy.php:147
     2669#: includes/trait-campaign-privacy.php:149
    20512670msgid "State / evidence source:"
    20522671msgstr ""
    20532672
    2054 #: includes/trait-campaign-privacy.php:147
     2673#: includes/trait-campaign-privacy.php:149
    20552674msgid "Apply uses this saved preview. If you edit the input above, preview it again before applying."
    20562675msgstr ""
    20572676
    2058 #: includes/trait-campaign-privacy.php:148
     2677#: includes/trait-campaign-privacy.php:150
    20592678msgid "Valid / duplicate or already protected / rejected:"
    20602679msgstr ""
    20612680
    2062 #: includes/trait-campaign-privacy.php:150
     2681#: includes/trait-campaign-privacy.php:152
    20632682msgid "Apply previewed import"
    20642683msgstr ""
    20652684
    2066 #: includes/trait-campaign-privacy.php:153
     2685#: includes/trait-campaign-privacy.php:155
    20672686msgid "Applied / not applied (protected or storage failure):"
    20682687msgstr ""
    20692688
    2070 #: includes/trait-campaign-privacy.php:167
    2071 #: includes/trait-campaign-privacy.php:170
     2689#: includes/trait-campaign-privacy.php:169
     2690#: includes/trait-campaign-privacy.php:172
    20722691msgid "YoOhw campaign data"
    20732692msgstr ""
    20742693
    2075 #: includes/trait-campaign-privacy.php:187
     2694#: includes/trait-campaign-privacy.php:185
     2695msgid "Saved order context"
     2696msgstr ""
     2697
     2698#: includes/trait-campaign-privacy.php:195
    20762699msgid "Subscription and protection"
    20772700msgstr ""
    20782701
    2079 #: includes/trait-campaign-privacy.php:198
     2702#: includes/trait-campaign-privacy.php:206
    20802703msgid "Saved list references and legacy logs"
    20812704msgstr ""
    20822705
    2083 #: includes/trait-campaign-privacy.php:215
     2706#: includes/trait-campaign-privacy.php:223
    20842707msgid "Campaign storage is busy or unavailable. Personal data was not fully erased; run erasure again after checking storage and workers."
    20852708msgstr ""
    20862709
    2087 #: includes/trait-campaign-privacy.php:227
     2710#: includes/trait-campaign-privacy.php:235
    20882711msgid "A send is in flight. Run erasure again after the worker finishes. A protective hash already blocks future admission."
    20892712msgstr ""
    20902713
    2091 #: includes/trait-campaign-privacy.php:263
     2714#: includes/trait-campaign-privacy.php:271
    20922715msgid "Personal fields removed. Active campaign shared content is retained until completion; run erasure again if it contains personal text. Minimal recipient/campaign hashes, outcomes and suppression/erasure protection remain to prevent duplicate sends and data reconstruction."
     2716msgstr ""
     2717
     2718#: includes/trait-campaign-purchase-history.php:53
     2719msgid "Purchase history across the store"
     2720msgstr ""
     2721
     2722#: includes/trait-campaign-purchase-history.php:53
     2723msgid "Purchase frequency"
     2724msgstr ""
     2725
     2726#: includes/trait-campaign-purchase-history.php:54
     2727msgid "Any purchase history"
     2728msgstr ""
     2729
     2730#: includes/trait-campaign-purchase-history.php:57
     2731msgid "No repeat purchase in the last X days"
     2732msgstr ""
     2733
     2734#: includes/trait-campaign-purchase-history.php:57
     2735msgid "These conditions use all valid purchases across the entire store, including orders outside the dates and products selected below. Product/category conditions still match one order; they do not change this into repeat purchases of only that product."
     2736msgstr ""
     2737
     2738#: includes/trait-campaign-purchase-history.php:57
     2739msgid "Which purchases count?"
     2740msgstr ""
     2741
     2742#: includes/trait-campaign-purchase-history.php:57
     2743msgid "Processing and Completed orders count by normalized billing email, including guest orders and orders before an account existed. Cancelled, Failed, Refunded and fully refunded orders do not count. Partial refunds count while an amount remains; free orders without refunds count. Inactivity uses the latest valid order creation time and X times 24 hours. A customer with no valid purchases never qualifies as inactive. Different emails are not merged."
    20932744msgstr ""
    20942745
    … …  
    21132764msgstr ""
    21142765
     2766#: includes/trait-campaign-subscriptions.php:42
     2767msgid "Enter a valid email address and try again."
     2768msgstr ""
     2769
     2770#: includes/trait-campaign-subscriptions.php:43
     2771#: includes/trait-campaign-subscriptions.php:74
     2772msgid "Subscription storage is busy. Please try again."
     2773msgstr ""
     2774
     2775#: includes/trait-campaign-subscriptions.php:57
     2776msgid "The request could not be completed. Please try again later."
     2777msgstr ""
     2778
     2779#: includes/trait-campaign-subscriptions.php:61
     2780#: includes/trait-campaign-subscriptions.php:113
     2781msgid "Confirm your email subscription"
     2782msgstr ""
     2783
     2784#: includes/trait-campaign-subscriptions.php:62
     2785msgid "Someone requested campaign emails from this store for this address. To subscribe, open the link below and confirm. It expires in 24 hours. If you did not request this, ignore this email; your preference has not changed."
     2786msgstr ""
     2787
     2788#: includes/trait-campaign-subscriptions.php:72
     2789msgid "This confirmation is invalid, expired or already used. Submit a new subscription request."
     2790msgstr ""
     2791
     2792#: includes/trait-campaign-subscriptions.php:85
     2793msgid "The subscription could not be saved. Submit a new request and try again."
     2794msgstr ""
     2795
     2796#: includes/trait-campaign-subscriptions.php:92
     2797msgid "Email address"
     2798msgstr ""
     2799
     2800#: includes/trait-campaign-subscriptions.php:92
     2801msgid "I would like to receive campaign emails from this store. I can unsubscribe at any time."
     2802msgstr ""
     2803
     2804#: includes/trait-campaign-subscriptions.php:92
     2805msgid "Leave this field empty"
     2806msgstr ""
     2807
     2808#: includes/trait-campaign-subscriptions.php:92
     2809msgid "Request subscription"
     2810msgstr ""
     2811
     2812#: includes/trait-campaign-subscriptions.php:92
     2813msgid "Check your email to confirm. Submitting this form does not subscribe you."
     2814msgstr ""
     2815
     2816#: includes/trait-campaign-subscriptions.php:96
     2817msgid "Use the subscription form."
     2818msgstr ""
     2819
     2820#: includes/trait-campaign-subscriptions.php:99
     2821msgid "Choose whether you want to request campaign emails."
     2822msgstr ""
     2823
     2824#: includes/trait-campaign-subscriptions.php:103
     2825msgid "If this address is eligible, a confirmation email has been requested. Check your inbox and spam folder. Requests are limited; you can try again later."
     2826msgstr ""
     2827
     2828#: includes/trait-campaign-subscriptions.php:103
     2829msgid "Check your email"
     2830msgstr ""
     2831
     2832#: includes/trait-campaign-subscriptions.php:112
     2833msgid "Invalid confirmation link."
     2834msgstr ""
     2835
     2836#: includes/trait-campaign-subscriptions.php:113
     2837msgid "Confirm that you want campaign emails from this store. This also replaces an earlier unsubscribe for this address. You can unsubscribe again at any time."
     2838msgstr ""
     2839
     2840#: includes/trait-campaign-subscriptions.php:113
     2841#: includes/trait-campaign-subscriptions.php:114
     2842msgid "Confirm subscription"
     2843msgstr ""
     2844
     2845#: includes/trait-campaign-subscriptions.php:118
     2846msgid "Your email subscription is confirmed. You can unsubscribe from any campaign email."
     2847msgstr ""
     2848
     2849#: includes/trait-campaign-subscriptions.php:123
     2850msgid "Email confirmation is pending. Your current preference remains in effect until you confirm."
     2851msgstr ""
     2852
     2853#: includes/trait-campaign-subscriptions.php:124
     2854msgid "Campaign email preference"
     2855msgstr ""
     2856
     2857#: includes/trait-campaign-subscriptions.php:124
     2858msgid "Preference for your current account email:"
     2859msgstr ""
     2860
     2861#: includes/trait-campaign-subscriptions.php:125
     2862msgid "Request subscription by email"
     2863msgstr ""
     2864
     2865#: includes/trait-campaign-subscriptions.php:125
     2866msgid "A subscription request requires email confirmation. Unsubscribe applies immediately."
     2867msgstr ""
     2868
     2869#: includes/trait-campaign-subscriptions.php:133
     2870msgid "The preference could not be saved. Try again later."
     2871msgstr ""
     2872
    21152873#: yoohw-advanced-notifications.php:43
    21162874msgid "Advanced Notifications for WooCommerce requires WooCommerce to be active."
  • yoohw-advanced-notifications/tags/0.4.0/readme.txt

    r3771948 r3789014  
    66Requires PHP: 8.1
    77Requires Plugins: woocommerce
    8 Stable tag: 0.3.0
     8Stable tag: 0.4.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2020= Choose and review your audience =
    2121
    22 * Select product or variation purchasers, WooCommerce customers with matching orders, registered users or a pasted email list. Order-based sources include guest customers.
     22* Select product or variation purchasers, selected orders, WooCommerce customers with matching orders, registered users, subscribed email addresses or a pasted email list. Order-based sources include guest customers.
     23* Start from Email purchasers on Products or Email selected orders / Email this customer on Orders. These actions create a draft with an explicit scope; preview and confirmation are still required before sending.
    2324* Filter matching orders by product, category (including child categories), creation date, order status and billing country. These order conditions must match the same order.
    2425* Filter registered users by role and profile billing country. Only filters supported by the selected source can be used.
    2526* Select by subscription status or a previous campaign's recorded handoff outcome. Missing or unknown delivery history is not treated as proof that an email was never sent.
    2627* Apply exclusions, remove duplicate email addresses and respect saved unsubscribes across every recipient source.
     28* Choose first-time buyers (one valid order), repeat buyers (two or more), or customers with no valid purchase in the last X days. History spans the entire store, outside campaign product/date filters. Processing and Completed orders count by normalized billing email; cancelled, failed and fully refunded orders do not. Partial refunds count while value remains; free orders without refunds count. Inactivity uses order creation time and X times 24 hours; customers with no valid purchases are excluded.
    2729* Save audience criteria for reuse. Each use requires a fresh recipient preview and confirmation.
    2830* Review selection and exclusion counts and inspect why an email address is included or excluded.
    … …  
    3537* Use WooCommerce HTML email styling, the rich-text editor and desktop/mobile previews for a sample recipient.
    3638* Send a test email and continue editing the draft before confirming recipients.
    37 * Personalize the message body with {customer_name} and {product_name}. A missing customer name falls back to the email address. The product variable is a campaign-wide label, not a record of each recipient's purchases. Subjects and headings do not expand these variables.
     39* New campaigns personalize subject, heading and body with {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products} and {order_url}. Missing names use Customer. {product_name} remains a campaign-wide label; order details come from the matching order with the highest order ID and are empty without order context. Guest order links are empty; account order links require the owning account login. Already-confirmed older campaigns keep their original rendering contract.
    3840
    3941= Schedule and control sending =
    … …  
    4648* Recover interrupted work using confirmed recipients and bounded retries for eligible known failures. Unknown handoff outcomes are not automatically retried.
    4749
     50= One guide after Completed =
     51
     52* Follow Conditions → Content → Timing → Review in Automations. Compose with a live WooCommerce preview and Desktop/Mobile controls, then refresh the exact order sample in Review. Save a guide with optional products/categories and a delay of 0–8760 hours. Preview an accessible sample order or send a test to your chosen address, then explicitly enable the saved rule.
     53* Only new transitions to Completed are captured. Enabling never scans historical orders. Each rule/order can create at most one email, including repeated transitions and multiple matching items; different orders for the same email can each receive a guide.
     54* An active subscription is required at sending. Unknown is not consent. Orders must still be Completed, have the same valid billing email, match saved product/category conditions and not be fully refunded. Partial refunds remain eligible while value remains. Subscription, suppression and erasure protections are checked before handoff.
     55* Content, triggering-order context and UTC due time are fixed when captured. Editing a rule changes future events. Disabling stops new events and cancels unstarted mail; started work may finish. Re-enabling does not restore cancelled mail.
     56* Use Campaigns & Logs or the contact lookup to inspect the guide, exact order and recorded result. Existing limits, recovery and unknown-outcome protections apply. A working queue is required; delivery may be later than the due time.
     57
    4858= Subscription and privacy tools =
     59
     60* Place [yoohw_an_subscribe] on a page for guest signup. Optional, unchecked Classic Checkout and Checkout Blocks fields (Blocks requires WooCommerce 8.9 or later) request a confirmation email; buying or creating an account never establishes consent.
     61* Confirm mailbox ownership using the emailed link and confirmation button within 24 hours. A new verified request can replace an earlier unsubscribe; imports cannot. Erasure protection remains in effect.
     62* View or change the preference for the current email in My Account. Choose Subscribed email addresses in the composer to include subscribers without an account or order.
     63* Look up one email under Subscribers to review its retained campaign outcomes, reasons and campaign links. Handoff does not prove inbox delivery or reading.
    4964
    5065* Keep subscribed, unsubscribed and unknown states separate, with the recorded source and time where available.
    … …  
    6075Only this plugin's campaign emails are subject to its send controls and admission limits. WooCommerce transactional emails and unrelated scheduled actions keep their existing behavior. Test emails are outside the campaign admission limits.
    6176
    62 This plugin does not include recurring marketing automation, WooCommerce Subscriptions audiences, CRM synchronization or additional messaging channels.
     77This plugin does not include multi-step or recurring marketing automation, WooCommerce Subscriptions audiences, CRM synchronization or additional messaging channels.
    6378
    6479== Installation ==
    … …  
    97112= What data does the plugin store and erase? =
    98113
    99 The plugin stores campaign content, confirmed recipients, scheduling and delivery records, drafts, saved audience criteria, subscription evidence and opt-outs in your WordPress database. Emails are sent through the mail transport configured on your site.
     114The plugin stores campaign content, confirmed recipients, scheduling and delivery records, drafts, saved audience criteria, subscription evidence and opt-outs in your WordPress database. Emails are sent through the mail transport configured on your site. Subscription requests store the email, source, request/expiry time and a hashed confirmation token for at most 24 hours. Rate counters store hashes, not raw IP addresses. Confirmation requests are limited per address, client and site. A protected erased address is not re-enrolled or reconstructed by a new request.
    100115
    101116WordPress Tools > Export Personal Data and Erase Personal Data include this plugin's records for the requested email address, including guests. Erasure retains protective hashes and opt-out evidence to prevent reconstruction or another send. In-flight work or shared content in an active campaign may require another erasure pass after completion; the erasure result reports retained data.
    … …  
    107122Deactivation cancels pending campaign work and removes the reconciliation event while retaining campaign records and opt-outs. Reactivation does not automatically restart cancelled work.
    108123
    109 When upgrading, stop background workers that have already loaded the old plugin code before replacing files. Version 0.3.0 keeps the existing plugin slug, text domain, settings, campaigns and opt-outs. Do not downgrade directly to 0.2.2, which does not understand the new cancellation and erasure protections. Data from the separate YoOhw Extra Code plugin is not imported automatically.
     124When upgrading, stop background workers that have already loaded the old plugin code before replacing files. Version 0.4.0 keeps the existing plugin slug, text domain, settings, campaigns and opt-outs. Do not downgrade directly to 0.2.2, which does not understand the new cancellation and erasure protections. Data from the separate YoOhw Extra Code plugin is not imported automatically.
    110125
    111126= Can I translate the interface? =
    … …  
    115130== Changelog ==
    116131
    117 = 0.3.0 =
    118 * Rename the plugin to Advanced Notifications for WooCommerce; keep the existing slug, text domain and stored data.
    119 * Add individual campaign controls and shared sending admission limits.
    120 * Add durable drafts, duplication, content templates and personalized previews.
    121 * Add source-aware saved audiences and explicit timezone scheduling.
    122 * Add subscription/suppression administration, privacy callbacks and terminal retention.
    123 * Preserve erased data boundaries, known-unsent history and cancellation during concurrent recovery.
    124 * Unify campaign forms with the admin color scheme, SelectWoo fields and desktop/mobile preview controls.
    125 * Harden confirmed recipient snapshots, duplicate protection, bounded retries and interrupted-work recovery.
     132= 0.4.0 =
     133* Add product and order email entry points that create scoped drafts, with HPOS and posts order storage support.
     134* Personalize new campaign subjects, headings and bodies with frozen customer, store and representative-order context; preserve already-confirmed campaigns.
     135* Add verified subscriptions through a signup shortcode, Classic Checkout, Checkout Blocks and My Account without treating purchase or account creation as consent.
     136* Add per-email contact history lookup with retained outcomes, reasons and campaign links.
     137* Add store-wide first-time, repeat-buyer and purchase-inactivity filters for campaigns and saved audiences.
     138* Add explicitly enabled Completed-order guides with product/category conditions, a fixed delay and one immutable delivery per rule/order.
     139* Recheck rule, order, subscription, suppression and erasure before guide handoff; preserve cancellation, duplicate protection and unknown-outcome boundaries.
     140* Align automation authoring with the four-step composer, live Desktop/Mobile previews and exact-order preview/test; hide stale samples after edits.
     141* Fix Library warnings when optional draft or saved-audience names are missing.
    126142
    127 = 0.2.2 =
    128 * Align the translation domain with the WordPress.org slug yoohw-advanced-notifications.
    129 * Resolve Plugin Check findings for SQL identifiers, input handling, test fixtures and plugin metadata.
    130 * Load admin JavaScript and CSS from separate files with WordPress dependencies.
    131 
    132 = 0.2.1 =
    133 * Register the languages directory on init and include a POT translation template.
    134 * Make remaining delivery messages and status labels translatable.
    135 * Add translator notes and translation setup instructions.
    136 
    137 = 0.2.0 =
    138 * Add WooCommerce customers and Manual email list recipient sources.
    139 * Add multi-role filtering for registered users, including preview and campaign history.
    140 * Preserve recipient selections when restoring drafts and retrying failed schedules.
    141 * Use native JavaScript trim() for the live preview.
    142 
    143 = 0.1.0 =
    144 * Extract standalone customer email campaigns from YoOhw Extra Code.
    145 * Remove WooCommerce Subscriptions audiences and exclusions, and Audience sync integration.
    146 * Isolate plugin storage/hooks and use the current store's sender defaults.
     143See changelog.txt for the full version history.
    147144
    148145== Upgrade Notice ==
    149146
     147= 0.4.0 =
     148Adds scoped product/order campaigns, verified subscriptions, contact history, purchase-history filters and Completed-order guides. Existing data and confirmed work are retained. New automation rules stay disabled until explicitly enabled; there is no historical backfill. Stop workers using the old code before replacing plugin files. Do not downgrade to an older version without reviewing pending jobs and subscription/privacy compatibility.
     149
    150150= 0.3.0 =
    151151Campaign controls, reusable drafts, audience filters, scheduling and privacy tools. Existing data is retained. Stop old background workers before upgrading. Do not downgrade to 0.2.2 because it lacks the new cancellation and erasure protections.
  • yoohw-advanced-notifications/tags/0.4.0/yoohw-advanced-notifications.php

    r3771948 r3789014  
    44 * Plugin URI: https://wordpress.org/plugins/yoohw-advanced-notifications
    55 * Description: Compose, preview, schedule and track WooCommerce customer email campaigns.
    6  * Version: 0.3.0
     6 * Version: 0.4.0
    77 * Author: YoOhw Studio
    88 * Author URI: https://yoohw.com/
    … …  
    2020}
    2121
    22 define( 'YOOHW_AN_VERSION', '0.3.0' );
     22define( 'YOOHW_AN_VERSION', '0.4.0' );
    2323
    2424function yoohw_an_load_textdomain() {
  • yoohw-advanced-notifications/trunk/assets/admin.css

    r3771948 r3789014  
    810810
    811811.yoohw-an-workspace .an-preview-device .dashicons { display: block; width: 20px; height: 20px; margin: 0; font-size: 20px; line-height: 20px; }
     812
     813/* Automation authoring shares the composer steps, editor and preview layout. */
     814.an-automation-rule { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 16px 0; border-top: 1px solid #dcdcde; }
     815.an-automation-rule p { margin: 6px 0 0; color: #50575e; }
     816.an-automation-state { display: inline-block; margin-left: 12px; padding: 2px 8px; border: 1px solid #c3c4c7; border-radius: 3px; font-size: 12px; }
     817.an-automation-rule-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
     818.an-automation-form details { margin-top: 16px; }
     819.an-automation-form .an-field > input[type="number"] { max-width: 240px; }
     820.an-automation-form .an-sample-stale { padding: 12px; border-left: 4px solid #dba617; background: #fcf9e8; }
     821.an-automation-form .an-automation-sample[hidden], .an-automation-form .an-sample-stale[hidden] { display: none !important; }
     822@media (max-width: 782px) { .an-automation-rule { align-items: flex-start; flex-direction: column; gap: 12px; } }
  • yoohw-advanced-notifications/trunk/assets/admin.js

    r3771948 r3789014  
    2727                        $row.find(':input').prop('disabled', false);
    2828                }
     29                revealApplicable('.an-selected-orders', source === 'selected_orders');
    2930                revealApplicable('#yoohw-advanced-notifications-products-row', product);
    3031                revealApplicable('#yoohw-advanced-notifications-roles-row', registered);
    3132                revealApplicable('#yoohw-advanced-notifications-manual-row', manual);
    32                 revealApplicable('.an-order-filters', product || source === yoohwAN.customersSource);
    33                 revealApplicable('.an-country-filter', !manual);
     33                revealApplicable('.an-order-filters', product || source === yoohwAN.customersSource || source === 'selected_orders');
     34                revealApplicable('.an-country-filter', !manual && source !== 'subscribed_emails');
    3435                $('#yoohw-advanced-notifications-customers-row').toggle(source === yoohwAN.customersSource);
    3536                $('.an-incompatible-help').prop('hidden', !$('.an-inapplicable-selection').length);
    … …  
    143144
    144145                editor.on('keyup change input SetContent NodeChange', updateEmailPreview);
     146                editor.on('change input keyup', markAutomationPreviewStale);
    145147                editor.yoohwEmailNotifyPreviewBound = true;
    146148        }
    … …  
    209211                        if (panel) { showStep(names.indexOf(panel.dataset.anPanel), false); }
    210212                }, true);
    211                 showStep(0, false);
     213                var initial = names.indexOf($form.attr('data-an-initial-step'));
     214                showStep(initial < 0 ? 0 : initial, false);
    212215        }
    213216
    … …  
    282285                updateEmailPreview();
    283286        });
     287
     288        function markAutomationPreviewStale() {
     289                $('.an-automation-form .an-automation-sample').prop('hidden', true);
     290                $('.an-automation-form .an-sample-stale').prop('hidden', false);
     291        }
     292        $(document).on('input change', '.an-automation-form input:not([type="hidden"]), .an-automation-form select, .an-automation-form textarea', function() {
     293                if (this.name !== 'test_email' && this.name !== 'name') { markAutomationPreviewStale(); }
     294        });
    284295        $(document).on('submit', 'form', function() { if (window.tinymce) { window.tinymce.triggerSave(); } });
    285296        $(document).on('change', '#recipient_source, .an-order-filters :input, .an-country-filter :input, #yoohw-advanced-notifications-roles-row :input, #yoohw-advanced-notifications-products-row :input, #yoohw-advanced-notifications-manual-row :input', toggleRecipientFields);
  • yoohw-advanced-notifications/trunk/docs/operator-features.md

    r3771948 r3789014  
    1 # Tính năng vận hành campaign (chưa phát hành)
     1# Tính năng vận hành campaign (0.4.0)
    22
    33Nền đối chiếu: `74353181835147d8e68d9bca87a3d801c3e772dd` trên local và `origin/main`. Giữ schema recipient 3, Action Scheduler, manifest xác nhận và durable opt-out hiện có. Không thêm service hoặc kênh gửi khác.
    … …  
    7878
    7979Các selection field dùng SelectWoo có sẵn của WooCommerce (giao diện Select2), bao gồm các bộ lọc và người nhận mẫu; giá trị gốc và validation vẫn giữ nguyên. Hai nút icon Desktop/Mobile điều chỉnh bố cục preview trong cả bước soạn và preview người nhận, có tooltip/nhãn hỗ trợ đọc màn hình và trạng thái lựa chọn rõ ràng; không tạo thao tác gửi.
     80
     81### AN-5: Liên hệ từ Products và Orders (0.4.0)
     82
     83- Products có **Email purchasers** tại danh sách và trang sửa. Sản phẩm cha gồm các variation; từng variation có liên kết riêng chỉ chọn người mua variation đó. Orders có bulk action **Email selected orders** trên cả HPOS và lưu trữ cũ; trang chi tiết có **Email this customer**. Các thao tác chỉ lưu bản nháp, chưa tạo job hay gửi email.
     84- Nguồn **Selected orders** giữ tập ID rõ ràng, sửa được trong composer. Mặc định xét mọi trạng thái trong tập đã chọn để hỗ trợ thông báo cho cả đơn đang chờ/hủy; bộ lọc trạng thái có thể thu hẹp. Product customers tiếp tục mặc định Processing/Completed. Guest và tài khoản được gộp theo billing email đã chuẩn hóa, mỗi email nhận một thư cho campaign. Bộ lọc đơn vẫn khớp cùng một đơn; ID mất/quyền không đủ bị từ chối, không mở rộng tập chọn.
     85- Subject, heading và body của campaign mới hỗ trợ `{customer_name}`, `{store_name}`, `{product_name}`, `{order_number}`, `{ordered_products}`, `{order_url}`. `{product_name}` vẫn là nhãn chung của campaign. Thông tin riêng lấy từ **đơn khớp có ID lớn nhất**, gồm toàn bộ tên dòng sản phẩm của đơn đó; preview ghi số đơn đại diện và số đơn khớp. Không lấy đơn mới hơn nằm ngoài phạm vi. Thiếu tên dùng **Customer**; thiếu ngữ cảnh đơn thì các biến đơn rỗng.
     86- `{order_url}` chỉ có khi email tài khoản sở hữu khớp billing email, dùng trang My Account và cần đăng nhập đúng chủ đơn. Guest hoặc email khác tài khoản không có liên kết; không lưu khóa order/guest. Không chèn liên kết vào mẫu mặc định khi thiếu ngữ cảnh.
     87- Live editor hiển thị nội dung template; **Preview Recipients** là preview cá nhân hóa thật, chọn được tối đa 20 người mẫu. Email thử dùng người đủ điều kiện đầu tiên, hoặc Test Customer nếu chưa có người nhận, và luôn gửi tới địa chỉ thử đã nhập. Ba mẫu có hướng dẫn điền thay đổi/ngày hiệu lực/hành động/liên hệ và vẫn sửa tự do.
     88- Lưu/mở lại/duplicate/saved audience giữ tiêu chí ID; phải preview dữ liệu hiện tại khi dùng lại. Xác nhận giữ nguyên context, tên cửa hàng, nội dung và recipient; dispatcher/recovery đọc snapshot, không truy vấn lại đơn để gửi. Campaign đã xác nhận trước AN-5 giữ subject/heading literal và cách thay body cũ. Durable opt-out, outcome unknown, privacy/retention hiện hành tiếp tục có hiệu lực; export bổ sung context của đúng email và erasure xóa context khỏi snapshot/preview/args.
     89
     90Kiểm chứng mới: `tests/contextual-campaigns.php` và `tests/contextual-entrypoints.php`, được chạy trong CI regression với database reliability riêng và mail bị chặn. Bằng chứng theo candidate ghi trong PR. Không chứng minh SMTP/inbox thật.
     91
     92
     93### AN-6: Đăng ký trực tiếp và lịch sử liên hệ (0.4.0)
     94
     95- Chèn shortcode `[yoohw_an_subscribe]` vào trang. Khách nhập email, chủ động chọn đồng ý rồi nhận thư xác nhận. Classic Checkout và Checkout Blocks có checkbox tùy chọn, không chọn sẵn, chỉ gửi yêu cầu xác nhận cho billing email; My Account quản lý đúng email hiện tại của tài khoản. Không suy diễn consent từ đơn hàng/tài khoản. Blocks dùng Additional Checkout Fields API của WooCommerce, không lưu lựa chọn mặc định trên hồ sơ khách.
     96- Link email chỉ mở trang xác nhận; cần bấm **Confirm subscription** để thay đổi trạng thái, tránh scanner email tự đăng ký. Token ngẫu nhiên, chỉ lưu hash, hết hạn sau 24 giờ, dùng một lần. Trước xác nhận, trạng thái có hiệu lực vẫn là unknown hoặc unsubscribed; My Account và tìm kiếm subscriber thể hiện yêu cầu đang chờ.
     97- Đăng ký lại sau unsubscribe cần yêu cầu mới và xác nhận mailbox. Record subscription xác nhận ghi nhận đúng thế hệ suppression trước yêu cầu; unsubscribe mới tạo thế hệ mới nên chặn cả link cũ lẫn quyền xác nhận cũ. Không xóa option suppression legacy hoặc bản ghi bảo vệ hàng loạt. Import không có thẩm quyền xác nhận này và không gỡ opt-out.
     98- Nguồn **Subscribed email addresses** dùng record hiện có, bao gồm guest không tài khoản/đơn hàng; chỉ lấy subscribed còn hiệu lực. Không áp dụng product/order/country/roles/manual criteria của nguồn khác. Draft/saved audience/preview/duplicate/lịch/controls dùng đường hiện có. Worker kiểm tra lại đăng ký trước handoff; unsubscribe hoặc trạng thái unknown sau preview không được gửi trong nguồn này. Campaign đã xác nhận không lấy thêm subscriber mới.
     99- **Subscribers → Contact history by email** tra cứu chính xác email trên bảng recipient hiện có, 50 dòng/trang: campaign, subject/context còn được lưu, due/updated time UTC, outcome và lý do, liên kết mở campaign. Không query đơn live để khôi phục context. Retention chỉ còn outcome sẽ báo nội dung không còn; tombstone erasure chặn toàn bộ lịch sử plaintext.
     100- Anti-abuse dùng nonce/honeypot trên form và quota nguyên tử dưới mutex privacy hiện có: một yêu cầu/email mỗi 10 phút, tối đa 5/client mỗi 30 phút, 100/site mỗi giờ. Client chỉ dùng HMAC của REMOTE_ADDR (không tin X-Forwarded-For, không lưu IP gốc); quota chung NAT có thể giới hạn nhiều khách cùng mạng. Phản hồi công khai không cho biết email đã tồn tại, unsubscribed hay erased. Pending request/counter hết hạn được dọn qua reconciliation hàng giờ; không tự retry confirmation mail khi transport mơ hồ.
     101- Confirmation mail là thư xác minh theo yêu cầu, có thể gửi tới địa chỉ đang unsubscribed để chính chủ đăng ký lại; không dùng đường này cho campaign. Nó dùng WooCommerce template/sender của shop và transport WordPress, không được trình bày như campaign handoff trong contact history. Kiểm thử chặn toàn bộ mail ra ngoài, không xác minh SMTP/inbox thật.
     102- Erasure giữ nguyên protective hash và không cho signup mới phục hồi dữ liệu/subscriber đã erase. Pending request được scrub cùng dữ liệu cá nhân hiện có; export bổ sung nguồn/thời gian yêu cầu, không xuất token. Việc đăng ký lại sau unsubscribe chỉ thay lựa chọn tương lai, không dựng lại history, không replay campaign cũ, không tự thay outcome unknown.
     103
     104Bộ kiểm chứng mới: `subscriptions.php`, `checkout-subscription.php`, `contact-history.php` và concurrency cho request/confirm/unsubscribe/erasure. Bằng chứng theo candidate và giới hạn runtime ghi trong PR; không thêm service, bảng CRM hoặc cơ chế tracking mở/nhấp email.
     105
     106
     107## Purchase history and Completed-order guides (AN-7)
     108
     109Compose and saved audiences support `purchase_frequency` (`first`/`repeat`) and `inactive_days` (1–36500). These criteria scan valid purchases across the whole store, separately from same-order product/category/date/status/country conditions. Identity is normalized billing email, including guest/account orders; different emails are never inferred to be the same customer. Valid purchases are Processing/Completed orders excluding full refunds; a partial refund counts while value remains, and free orders without refunds count. Inactivity uses the latest valid order creation instant and X × 24 hours. Never-buyers and orders with unavailable dates do not qualify.
     110
     111Automations supports one fixed guide per rule/order after a new Completed transition. Save disabled, inspect conditions/content/delay, preview or test with an accessible order, then explicitly Enable. Test mail goes only to its entered address and never saves/enables a rule or creates a delivery job. Products/variations and category descendants reuse the existing same-order matching behavior. Each eligible order can receive its own guide even when another order shares the email.
     112
     113The event stores an immutable campaign manifest using a deterministic rule/order identity. Repeated events, items and workers reuse the same identity. Delay is an exact UTC offset from capture; queue delays never roll the due time to another day. Edits affect future events only. Disable rotates the enabled generation, durably blocks pending campaigns and cancels their unstarted rows. Re-enable cannot revive them. Work already admitted may finish. History and contact lookup retain the exact triggering order and result.
     114
     115Before handoff the rule generation, fresh order, billing email and saved product/category conditions are rechecked. An order must still be Completed and not fully refunded; other states are skipped, partial refunds remain eligible while value remains. Active subscription is required and checked again after email rendering. Unknown never becomes subscribed through purchase. Existing suppression, erasure, claim, rate, recovery and unknown-handoff protections apply.
     116
     117Only new events are admitted; there is no historical backfill. Captured manifests interrupted before their history row are registered by existing reconciliation and deactivation. A database/process failure before durable capture cannot be reconstructed without a new transition: the WooCommerce logger records a non-personal capture error. Operators must investigate storage/history and any mail-provider ambiguity before manually changing order status; the plugin never automatically replays an unknown handoff. No external event service or general automation builder is added.
     118
     119
     120Automation authoring follows the same four-step layout as Compose: **Conditions → Content → Timing → Review**. Content uses the shared WooCommerce live preview, Visual/Code editor and Desktop/Mobile controls. Live preview retains placeholders; Review refreshes the exact order sample through the existing server renderer. Preview/test returns to Review and preserves the unsaved form. Changing content, conditions, timing or sample order hides the old personal preview and asks for a refresh. Saving and enabling remain separate operations; list controls always use the saved configuration. The campaign-history link opens the campaign list so pending guides are discoverable.
  • yoohw-advanced-notifications/trunk/includes/class-customer-email-notify.php

    r3771948 r3789014  
    1010require_once __DIR__ . '/trait-campaign-audiences.php';
    1111require_once __DIR__ . '/trait-campaign-privacy.php';
     12require_once __DIR__ . '/trait-campaign-entrypoints.php';
     13require_once __DIR__ . '/trait-campaign-personalization.php';
     14require_once __DIR__ . '/trait-campaign-subscriptions.php';
     15require_once __DIR__ . '/trait-campaign-checkout.php';
     16require_once __DIR__ . '/trait-campaign-contact-history.php';
     17require_once __DIR__ . '/trait-campaign-purchase-history.php';
     18require_once __DIR__ . '/trait-campaign-automations.php';
     19require_once __DIR__ . '/trait-campaign-automation-ui.php';
    1220
    1321class YOOHW_AN_Customer_Email_Notify {
    … …  
    1725        use YOOHW_AN_Campaign_Audiences;
    1826        use YOOHW_AN_Campaign_Privacy;
     27        use YOOHW_AN_Campaign_Entrypoints;
     28        use YOOHW_AN_Campaign_Personalization;
     29        use YOOHW_AN_Campaign_Subscriptions;
     30        use YOOHW_AN_Campaign_Checkout;
     31        use YOOHW_AN_Campaign_Contact_History;
     32        use YOOHW_AN_Campaign_Purchase_History;
     33        use YOOHW_AN_Campaign_Automations;
     34        use YOOHW_AN_Campaign_Automation_UI;
    1935
    2036        const ACTION_SEND_SINGLE_EMAIL = 'yoohw_an_send_single_email';
    … …  
    5066        const SOURCE_REGISTERED_USERS    = 'registered_users';
    5167        const SOURCE_WOOCOMMERCE_CUSTOMERS = 'woocommerce_customers';
     68        const SOURCE_SUBSCRIBED = 'subscribed_emails';
     69        const SOURCE_SELECTED_ORDERS = 'selected_orders';
    5270        const SOURCE_MANUAL_EMAILS        = 'manual_emails';
    5371
    … …  
    5674        private $last_mail_error    = '';
    5775        private $mail_handoff_started = false;
     76        private $last_automation_outcome = '';
    5877        private $current_action_id = 0;
    5978        private $recovery_enabled = true;
    … …  
    6382        public function __construct() {
    6483                $this->register_operator_features();
     84                $this->register_contextual_entrypoints();
     85                $this->register_subscriptions();
     86                $this->register_automations();
    6587                // Register after WooCommerce sorts Marketing at priority 99 to preserve our position.
    6688                add_action( 'admin_menu', [ $this, 'add_submenu' ], 100 );
    … …  
    143165        public function deactivate() {
    144166                $this->recovery_enabled = false;
     167                $this->register_automation_manifests();
    145168                $this->block_campaigns_on_deactivation();
    146169                wp_unschedule_hook( self::ACTION_RECONCILE_CAMPAIGNS );
    … …  
    176199
    177200        public function reconcile_open_campaigns( $campaign_id = '' ) {
     201                $this->reconcile_automation_campaigns();
    178202                $campaigns = $this->get_campaign_history();
    179203                $campaign_id = sanitize_text_field( $campaign_id );
    … …  
    781805
    782806                $active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'campaign'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin navigation, filters and notices; no state is changed.
     807
     808                if ( 'automations' === $active_tab ) { $this->render_automations_page(); return; }
    783809
    784810                if ( 'library' === $active_tab ) {
    … …  
    11551181                                                                );
    11561182                                                                ?>
    1157                                                                 <p><?php /* translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders. */ esc_html_e( 'Body variables: {customer_name} uses the saved recipient name, falling back to email; {product_name} is the shared campaign product label, never a personal purchase. With no product selection it uses the audience label. Subject and heading are literal.', 'yoohw-advanced-notifications' ); ?></p>
     1183                                                                <p><?php /* translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders. */ esc_html_e( 'Variables in subject, heading and body: {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products}, {order_url}. Order variables use the matching order with the highest order ID; preview shows its number and the matching order count. Without order context they are empty; missing names use Customer. {product_name} remains the shared campaign label. Order links require the matching account login; guests have no order link. The live editor shows template text; Preview Recipients and test email use the first eligible recipient.', 'yoohw-advanced-notifications' ); ?></p>
    11581184                                                                <p><?php esc_html_e( 'An unsubscribe link is added automatically to campaign emails.', 'yoohw-advanced-notifications' ); ?></p>
    11591185                                                        </div>
    … …  
    12961322                        'campaigns' => [ 'label' => __( 'Campaigns', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'campaigns' ] ) ],
    12971323                        'campaign' => [ 'label' => __( 'Compose', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url() ],
     1324                        'automations' => [ 'label' => __( 'Automations', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'automations' ] ) ],
    12981325                        'library' => [ 'label' => __( 'Library', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'library' ] ) ],
    12991326                        'subscribers' => [ 'label' => __( 'Subscribers', 'yoohw-advanced-notifications' ), 'url' => $this->get_admin_page_url( [ 'tab' => 'subscribers' ] ) ],
    … …  
    23252352                        );
    23262353
    2327                         $test_message = str_replace(
    2328                                 [
    2329                                         '{customer_name}',
    2330                                         '{product_name}',
    2331                                 ],
    2332                                 [
    2333                                         __( 'Test Customer', 'yoohw-advanced-notifications' ),
    2334                                         $payload['products_label'],
    2335                                 ],
    2336                                 $payload['message']
    2337                         );
     2354                        $test_recipients = $this->get_recipients_by_payload( $payload );
     2355                        $personal = $this->render_personalized_email( $payload, $test_recipients[0] ?? [ 'name' => __( 'Test Customer', 'yoohw-advanced-notifications' ), 'email' => $test_email ] );
     2356                        $test_message = $personal['message'];
    23382357                        $test_message = $this->append_unsubscribe_footer( $test_message, $test_email, $payload['campaign_id'] );
    23392358
    23402359                        $sent = $this->send_woocommerce_email(
    23412360                                $test_email,
    2342                                 '[TEST] ' . $payload['subject'],
    2343                                 $payload['heading'],
     2361                                '[TEST] ' . $personal['subject'],
     2362                                $personal['heading'],
    23442363                                $test_message,
    23452364                                $payload['from_email'],
    … …  
    24882507                }
    24892508
     2509                if ( self::SOURCE_SUBSCRIBED === ( $payload['recipient_source'] ?? '' ) || ! empty( $payload['automation'] ) ) { $email_payload['subscription_required'] = true; }
     2510                if ( ! empty( $payload['automation'] ) ) { $email_payload['automation'] = $payload['automation']; }
     2511                if ( ! empty( $payload['personalization_version'] ) ) { $email_payload['personalization_version'] = absint( $payload['personalization_version'] ); $email_payload['store_name'] = sanitize_text_field( $payload['store_name'] ?? get_bloginfo( 'name' ) ); }
     2512
    24902513                $payload_hash = hash( 'sha256', wp_json_encode( $email_payload ) );
    24912514                $payload_key  = $this->get_campaign_payload_option_name( $campaign_id, $delivery_id );
    … …  
    25652588                $this->campaign_payload_cache[ $payload_key ] = $record;
    25662589
    2567                 foreach ( [ 'subject', 'heading', 'message', 'products_label', 'from_email', 'from_name', 'reply_to_email' ] as $field ) {
     2590                foreach ( [ 'subject', 'heading', 'message', 'products_label', 'from_email', 'from_name', 'reply_to_email', 'personalization_version', 'store_name', 'subscription_required', 'automation' ] as $field ) {
    25682591                        $args[ $field ] = isset( $record['payload'][ $field ] ) ? $record['payload'][ $field ] : '';
    25692592                }
    25702593
     2594                if ( ! empty( $args['personalization_version'] ) ) {
     2595                        $manifest = $this->read_durable_option( $this->campaign_manifest_key( $campaign_id ) );
     2596                        $found = false;
     2597                        foreach ( (array) ( $manifest['recipients'] ?? [] ) as $recipient ) {
     2598                                if ( strtolower( $recipient['email'] ?? '' ) === strtolower( $args['to'] ?? '' ) && ! empty( $recipient['email'] ) ) {
     2599                                        $args['order_context'] = $this->sanitize_order_context( $recipient['order_context'] ?? [] );
     2600                                        $found = true; break;
     2601                                }
     2602                        }
     2603                        if ( ! $found ) { return new WP_Error( 'missing_recipient_context', __( 'Confirmed recipient context is unavailable. No email was handed off.', 'yoohw-advanced-notifications' ) ); }
     2604                }
    25712605                return $args;
    25722606        }
    … …  
    27772811                }
    27782812                $options_valid = $this->validate_recipient_source_options( [
     2813                        'order_ids' => $this->parse_selected_order_ids( wp_unslash( $_POST['order_ids'] ?? '' ) ),
    27792814                        'recipient_source' => $recipient_source, 'user_roles' => $user_roles,
    27802815                        'manual_emails' => $manual_emails, 'test_mode' => $test_mode,
    … …  
    28292864
    28302865        private function generate_campaign_hash( $payload ) {
    2831                 $hash_payload = [ 'filters' => $payload['filters'] ?? [], 'schedule_policy' => $payload['schedule_policy'] ?? '', 'store_timezone' => $payload['store_timezone'] ?? '',
     2866                $hash_payload = [ 'order_ids' => $payload['order_ids'] ?? [], 'personalization_version' => $payload['personalization_version'] ?? 0, 'store_name' => $payload['store_name'] ?? '', 'filters' => $payload['filters'] ?? [], 'schedule_policy' => $payload['schedule_policy'] ?? '', 'store_timezone' => $payload['store_timezone'] ?? '',
    28322867                        'recipient_source' => isset( $payload['recipient_source'] ) ? (string) $payload['recipient_source'] : self::SOURCE_PRODUCT_CUSTOMERS,
    28332868                        'product_ids'      => isset( $payload['product_ids'] ) ? array_map( 'absint', (array) $payload['product_ids'] ) : [],
    … …  
    30753110                );
    30763111
    3077                 foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone', 'schedule_summary' ] as $field ) { if ( isset( $payload[ $field ] ) ) { $entry[ $field ] = $payload[ $field ]; } }
     3112                foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone', 'schedule_summary', 'order_ids', 'personalization_version', 'store_name', 'automation' ] as $field ) { if ( isset( $payload[ $field ] ) ) { $entry[ $field ] = $payload[ $field ]; } }
    30783113                $entry['status_counts'] = isset( $existing['status_counts'] ) && is_array( $existing['status_counts'] )
    30793114                        ? $this->sanitize_count_map( $existing['status_counts'] )
    … …  
    33273362                ];
    33283363
    3329                 foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone' ] as $field ) { if ( isset( $campaign[ $field ] ) ) { $payload[ $field ] = $campaign[ $field ]; } }
     3364                foreach ( [ 'internal_name', 'filters', 'schedule_policy', 'store_timezone', 'order_ids', 'personalization_version', 'store_name', 'automation' ] as $field ) { if ( isset( $campaign[ $field ] ) ) { $payload[ $field ] = $campaign[ $field ]; } }
    33303365                if ( empty( $payload['products_label'] ) ) {
    33313366                        $payload['products_label'] = ! empty( $payload['product_ids'] )
    … …  
    34293464        private function sanitize_recipient_context( $recipient ) {
    34303465                return [
     3466                        'order_context' => $this->sanitize_order_context( $recipient['order_context'] ?? [] ),
    34313467                        'user_id'  => isset( $recipient['user_id'] ) ? absint( $recipient['user_id'] ) : 0,
    34323468                        'email'    => isset( $recipient['email'] ) ? sanitize_email( $recipient['email'] ) : '',
    … …  
    38993935
    39003936        private function get_recipient_scheduled_delivery( $payload, $recipient, $roll_forward = true, $reference_time = null ) {
     3937                if ( ! empty( $payload['automation']['due'] ) ) { return [ 'timestamp' => (int) $payload['automation']['due'], 'timezone' => 'UTC', 'fallback' => false, 'country' => '', 'state' => '', 'city' => '', 'rolled_forward_days' => 0 ]; }
    39013938                if ( isset( $payload['schedule_policy'] ) ) { return $this->operator_schedule( $payload, $recipient ); }
    39023939                $reference_time = null === $reference_time ? time() : absint( $reference_time );
    … …  
    42514288        private function get_recipient_sources() {
    42524289                $sources = [
     4290                        self::SOURCE_SUBSCRIBED => __( 'Subscribed email addresses', 'yoohw-advanced-notifications' ),
     4291                        self::SOURCE_SELECTED_ORDERS => __( 'Selected orders', 'yoohw-advanced-notifications' ),
    42534292                        self::SOURCE_PRODUCT_CUSTOMERS   => __( 'Product customers', 'yoohw-advanced-notifications' ),
    42544293                        self::SOURCE_WOOCOMMERCE_CUSTOMERS => __( 'WooCommerce customers', 'yoohw-advanced-notifications' ),
    … …  
    42834322
    42844323        private function validate_recipient_source_options( $payload ) {
     4324                $selected = $this->validate_selected_orders( $payload );
     4325                if ( is_wp_error( $selected ) ) { return $selected; }
    42854326                $valid = $this->validate_audience_filters( $payload );
    42864327                if ( is_wp_error( $valid ) ) { return $valid; }
    … …  
    43514392
    43524393                switch ( $recipient_source ) {
     4394                        case self::SOURCE_SUBSCRIBED:
     4395                                return $this->get_subscribed_recipients();
     4396
     4397                        case self::SOURCE_SELECTED_ORDERS:
     4398                                return $this->get_order_customer_recipients( null, $payload['filters'] ?? [], $payload['order_ids'] ?? [] );
     4399
    43534400                        case self::SOURCE_REGISTERED_USERS:
    43544401                                return $this->get_registered_user_recipients( [], isset( $payload['user_roles'] ) ? $payload['user_roles'] : [] );
    … …  
    45024549                $record = $this->read_durable_option( 'yoohw_an_optout_' . hash( 'sha256', $email ) );
    45034550                if ( $this->read_durable_option( 'yoohw_an_erased_' . hash( 'sha256', $email ) ) ) { return true; }
    4504                 if ( is_array( $record ) && ( ! empty( $record['suppressed'] ) || $email === ( $record['email'] ?? '' ) ) ) { return true; }
     4551                if ( is_array( $record ) && ( ! empty( $record['suppressed'] ) || $email === ( $record['email'] ?? '' ) ) ) { return ! $this->confirmed_suppression_override( $email ); }
    45054552                $legacy = $this->normalize_unsubscribed_emails( $this->read_durable_option( self::OPTION_UNSUBSCRIBED_EMAILS ) );
    4506                 return isset( $legacy[ $email ] );
     4553                return isset( $legacy[ $email ] ) && ! $this->confirmed_suppression_override( $email );
    45074554        }
    45084555
    … …  
    45154562                        }
    45164563                }
    4517                 return $this->normalize_unsubscribed_emails( $stored );
     4564                return array_filter( $this->normalize_unsubscribed_emails( $stored ), function ( $record, $email ) { return ! $this->confirmed_suppression_override( $email ); }, ARRAY_FILTER_USE_BOTH );
    45184565        }
    45194566
    … …  
    45594606                try {
    45604607                $key = 'yoohw_an_optout_' . hash( 'sha256', $email );
    4561                 $data = $this->read_durable_option( 'yoohw_an_erased_' . hash( 'sha256', $email ) ) ? [ 'suppressed' => true ] : [ 'email' => $email, 'unsubscribed_at' => time(), 'campaign_id' => sanitize_text_field( $campaign_id ) ];
     4608                $data = $this->read_durable_option( 'yoohw_an_erased_' . hash( 'sha256', $email ) ) ? [ 'suppressed' => true ] : [ 'email' => $email, 'unsubscribed_at' => time(), 'generation' => wp_generate_uuid4(), 'campaign_id' => sanitize_text_field( $campaign_id ) ];
    45624609                $data = $this->scrub_erased_references( $data );
    4563                 $this->write_durable_option( $key, $data, true );
     4610                if ( false === $this->write_durable_option( $key, $data ) ) { return false; }
    45644611                $saved = $this->read_durable_option( $key );
    4565                 return is_array( $saved ) && ( ! empty( $saved['suppressed'] ) || $email === ( $saved['email'] ?? '' ) );
     4612                return $data === $saved;
    45664613                } finally { $this->release_campaign_lock( '__privacy__' ); }
    45674614        }
    … …  
    48864933                }
    48874934                try {
    4888                         $message = str_replace( [ '{customer_name}', '{product_name}' ], [ $args['name'] ?? $to, $args['products_label'] ?? '' ], $args['message'] );
     4935                        $automation_check = $this->automation_delivery_check( $args );
     4936                        if ( is_wp_error( $automation_check ) ) {
     4937                                $outcome = 'automation_cancelled' === $automation_check->get_error_code() ? 'cancelled' : ( 'automation_ineligible' === $automation_check->get_error_code() ? 'skipped' : 'failed' );
     4938                                $this->finish_delivery( $campaign_id, $delivery_id, $to, $claim, $outcome, $automation_check->get_error_message(), 'failed' === $outcome ? 'configuration' : '' ); return;
     4939                        }
     4940                        if ( ! empty( $args['subscription_required'] ) && 'subscribed' !== $this->subscription_record( $to )['status'] ) {
     4941                                $this->finish_delivery( $campaign_id, $delivery_id, $to, $claim, 'skipped_unsubscribed', __( 'No active subscription before send.', 'yoohw-advanced-notifications' ) );
     4942                                return;
     4943                        }
     4944                        $personal = $this->render_personalized_email( $args, array_merge( $args, [ 'email' => $to ] ) );
     4945                        $message = $personal['message'];
    48894946                        $message = $this->append_unsubscribe_footer( $message, $to, $campaign_id );
    48904947                        // Read the durable opt-out immediately before entering the sending path.
    … …  
    48944951                        }
    48954952                        $this->mail_handoff_started = false;
    4896                         $sent = $this->send_woocommerce_email( $to, $args['subject'], $args['heading'], $message, $args['from_email'] ?? '', $args['from_name'] ?? '', $args['reply_to_email'] ?? '' );
    4897                         $status = $sent ? 'sent' : ( $this->mail_handoff_started ? 'unknown' : ( $this->is_email_unsubscribed( $to ) ? 'skipped_unsubscribed' : 'failed' ) );
     4953                        $sent = $this->send_woocommerce_email( $to, $personal['subject'], $personal['heading'], $message, $args['from_email'] ?? '', $args['from_name'] ?? '', $args['reply_to_email'] ?? '', ! empty( $args['subscription_required'] ), $args['automation'] ?? [] );
     4954                        $status = $sent ? 'sent' : ( $this->mail_handoff_started ? 'unknown' : ( ( $this->is_email_unsubscribed( $to ) || ( ! empty( $args['subscription_required'] ) && 'subscribed' !== $this->subscription_record( $to )['status'] ) ) ? 'skipped_unsubscribed' : 'failed' ) );
     4955                        if ( ! $sent && ! $this->mail_handoff_started && $this->last_automation_outcome ) { $status = $this->last_automation_outcome; }
    48984956                        $this->finish_delivery( $campaign_id, $delivery_id, $to, $claim, $status,
    48994957                                $sent ? __( 'Mail system accepted the request; inbox delivery is not verified.', 'yoohw-advanced-notifications' ) : ( $this->last_mail_error ?: __( 'Mail transport returned no confirmation. Check the mail provider before sending again.', 'yoohw-advanced-notifications' ) ),
    … …  
    49675025        }
    49685026
    4969         private function get_order_customer_recipients( $product_ids = null, $filters = [] ) {
     5027        private function get_order_customer_recipients( $product_ids = null, $filters = [], $order_ids = null ) {
     5028                if ( null !== $order_ids && ! $order_ids ) { return []; }
    49705029                $all_products = null === $product_ids;
    49715030                $product_ids = $all_products ? [] : $product_ids;
    … …  
    50245083                        $orders = wc_get_orders(
    50255084                                [
     5085                                        'post__in' => null === $order_ids ? [] : $order_ids,
    50265086                                        'limit'   => self::ORDER_QUERY_LIMIT,
    50275087                                        'paged'   => $page,
    5028                                         'status'  => ! empty( $filters['order_statuses'] ) ? $filters['order_statuses'] : [ 'processing', 'completed' ],
     5088                                        'status'  => ! empty( $filters['order_statuses'] ) ? $filters['order_statuses'] : ( null === $order_ids ? [ 'processing', 'completed' ] : array_keys( wc_get_order_statuses() ) ),
    50295089                                        'return'  => 'objects',
    50305090                                        'orderby' => 'ID',
    … …  
    50425102                                }
    50435103
     5104                                if ( null !== $order_ids && ! in_array( (int) $order->get_id(), $order_ids, true ) ) { continue; }
    50445105                                if ( ! $this->order_matches_filters( $order, $filters ) ) { continue; }
    50455106                                $has_product = $all_products;
    … …  
    50765137                                $email = strtolower( sanitize_email( $order->get_billing_email() ) );
    50775138
    5078                                 if ( ! is_email( $email ) || isset( $customers[ $email ] ) ) {
    5079                                         continue;
    5080                                 }
     5139                                if ( ! is_email( $email ) ) { continue; }
     5140                                if ( isset( $customers[ $email ] ) ) { $customers[ $email ]['order_context']['count']++; continue; }
    50815141
    50825142                                $name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
    … …  
    50865146                                        'email'   => $email,
    50875147                                        'name'    => $name ?: $email,
     5148                                        'order_context' => $this->context_from_order( $order ),
    50885149                                        'country' => $this->normalize_country_code( $order->get_billing_country() ),
    50895150                                        'state'   => $this->normalize_location_part( $order->get_billing_state() ),
    … …  
    52355296        }
    52365297
    5237         private function send_woocommerce_email( $to, $subject, $heading, $message, $from_email = '', $from_name = '', $reply_to_email = '' ) {
     5298        private function send_woocommerce_email( $to, $subject, $heading, $message, $from_email = '', $from_name = '', $reply_to_email = '', $require_subscription = false, $automation = [], $sample_email_hash = '' ) {
     5299                $this->last_automation_outcome = '';
    52385300                $email_body = $this->build_woocommerce_email_body( $heading, $message );
    52395301
    52405302                if ( false === $email_body ) {
    52415303                        $this->last_mail_error = __( 'WooCommerce mailer is unavailable.', 'yoohw-advanced-notifications' );
     5304                        return false;
     5305                }
     5306
     5307                if ( $sample_email_hash && $this->read_durable_option( 'yoohw_an_erased_' . $sample_email_hash ) ) {
     5308                        $this->last_mail_error = __( 'The sample was erased before test mail handoff.', 'yoohw-advanced-notifications' ); return false;
     5309                }
     5310
     5311                $check = $this->automation_delivery_check( [ 'automation' => $automation, 'to' => $to ] );
     5312                if ( is_wp_error( $check ) ) {
     5313                        $this->last_automation_outcome = 'automation_cancelled' === $check->get_error_code() ? 'cancelled' : ( 'automation_ineligible' === $check->get_error_code() ? 'skipped' : 'failed' );
     5314                        $this->last_mail_error = $check->get_error_message(); return false;
     5315                }
     5316
     5317                if ( $require_subscription && 'subscribed' !== $this->subscription_record( $to )['status'] ) {
     5318                        $this->last_mail_error = __( 'No active subscription before mail handoff.', 'yoohw-advanced-notifications' );
    52425319                        return false;
    52435320                }
  • yoohw-advanced-notifications/trunk/includes/trait-campaign-audiences.php

    r3771948 r3789014  
    77                $raw = wp_unslash( $_POST['audience_filters'] ?? [] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each scalar/list is type checked, sanitized below and strictly validated by validate_audience_filters().
    88                $filters = [];
    9                 $scalar_keys = [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode' ];
     9                $scalar_keys = [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'purchase_frequency', 'inactive_days' ];
    1010                $list_keys = [ 'category_ids', 'order_statuses' ];
    1111                if ( ! is_array( $raw ) || array_diff( array_keys( $raw ), array_merge( $scalar_keys, $list_keys ) ) ) { return [ 'invalid_input' => true ]; }
    1212                foreach ( $scalar_keys as $key ) { if ( isset( $raw[ $key ] ) && ! is_string( $raw[ $key ] ) ) { return [ 'invalid_input' => true ]; } }
    1313                foreach ( $list_keys as $key ) { if ( isset( $raw[ $key ] ) && ( ! is_array( $raw[ $key ] ) || array_filter( $raw[ $key ], static function ( $value ) { return ! is_string( $value ); } ) ) ) { return [ 'invalid_input' => true ]; } }
    14                 foreach ( [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode' ] as $key ) { $filters[ $key ] = sanitize_text_field( $raw[ $key ] ?? '' ); }
     14                foreach ( [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'purchase_frequency', 'inactive_days' ] as $key ) { $filters[ $key ] = sanitize_text_field( $raw[ $key ] ?? '' ); }
    1515                foreach ( [ 'category_ids', 'order_statuses' ] as $key ) { $filters[ $key ] = array_map( 'sanitize_text_field', (array) ( $raw[ $key ] ?? [] ) ); }
    1616                return $filters;
    … …  
    1919        private function validate_audience_filters( $p ) {
    2020                $f = $p['filters'] ?? [];
    21                 $invalid = new WP_Error( 'invalid_filters', __( 'Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country and campaign history; no condition has been ignored.', 'yoohw-advanced-notifications' ) );
    22                 if ( ! is_array( $f ) || array_diff( array_keys( $f ), [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'category_ids', 'order_statuses' ] ) ) { return $invalid; }
     21                $invalid = new WP_Error( 'invalid_filters', __( 'Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country, purchase history and campaign history; no condition has been ignored.', 'yoohw-advanced-notifications' ) );
     22                if ( ! is_array( $f ) || array_diff( array_keys( $f ), [ 'date_from', 'date_to', 'last_days', 'country', 'subscription', 'history_campaign', 'history_mode', 'category_ids', 'order_statuses', 'purchase_frequency', 'inactive_days' ] ) ) { return $invalid; }
    2323                foreach ( [ 'category_ids', 'order_statuses' ] as $key ) { if ( isset( $f[ $key ] ) && ! is_array( $f[ $key ] ) ) { return $invalid; } }
     24                if ( ! in_array( $f['purchase_frequency'] ?? '', [ '', 'first', 'repeat' ], true ) ) { return $invalid; }
     25                if ( isset( $f['inactive_days'] ) && '' !== $f['inactive_days'] && ( ( ! is_string( $f['inactive_days'] ) && ! is_int( $f['inactive_days'] ) ) || ! ctype_digit( (string) $f['inactive_days'] ) || (int) $f['inactive_days'] < 1 || (int) $f['inactive_days'] > 36500 ) ) { return $invalid; }
    2426                $source = $p['recipient_source'] ?? '';
    25                 $order_source = in_array( $source, [ self::SOURCE_PRODUCT_CUSTOMERS, self::SOURCE_WOOCOMMERCE_CUSTOMERS ], true );
     27                if ( self::SOURCE_SUBSCRIBED === $source && ( ! empty( $p['product_ids'] ) || ! empty( $p['user_roles'] ) || ! empty( $p['manual_emails'] ) ) ) { return $invalid; }
     28                $order_source = in_array( $source, [ self::SOURCE_PRODUCT_CUSTOMERS, self::SOURCE_WOOCOMMERCE_CUSTOMERS, self::SOURCE_SELECTED_ORDERS ], true );
    2629                foreach ( [ 'date_from', 'date_to', 'last_days', 'category_ids', 'order_statuses' ] as $field ) { if ( ! $order_source && ! empty( $f[ $field ] ) ) { return $invalid; } }
    27                 if ( self::SOURCE_MANUAL_EMAILS === $source && ! empty( $f['country'] ) ) { return $invalid; }
     30                if ( in_array( $source, [ self::SOURCE_MANUAL_EMAILS, self::SOURCE_SUBSCRIBED ], true ) && ! empty( $f['country'] ) ) { return $invalid; }
    2831                foreach ( [ 'date_from', 'date_to' ] as $field ) {
    2932                        if ( isset( $f[ $field ] ) && '' !== $f[ $field ] ) {
    … …  
    6669        }
    6770        private function subscription_record( $email ) {
    68                 if ( $this->is_email_unsubscribed( $email ) ) { return [ 'status' => 'unsubscribed', 'source' => 'suppression' ]; }
    69                 return $this->read_durable_option( 'yoohw_an_subscription_' . hash( 'sha256', strtolower( $email ) ) ) ?: [ 'status' => 'unknown', 'source' => '', 'recorded_at' => 0 ];
     71                $email = strtolower( $email );
     72                $record = $this->is_email_unsubscribed( $email ) ? [ 'status' => 'unsubscribed', 'source' => 'suppression' ] : ( $this->read_durable_option( 'yoohw_an_subscription_' . hash( 'sha256', $email ) ) ?: [ 'status' => 'unknown', 'source' => '', 'recorded_at' => 0 ] );
     73                $pending = $this->read_durable_option( 'yoohw_an_signup_' . hash( 'sha256', $email ) );
     74                if ( is_array( $pending ) && ( $pending['email'] ?? '' ) === $email && ! empty( $pending['token_hash'] ) && ( $pending['expires'] ?? 0 ) > time() ) { $record['confirmation_pending'] = true; $record['request_source'] = $pending['source']; $record['requested_at'] = $pending['requested_at']; }
     75                return $record;
    7076        }
    7177        private function apply_audience_filters( $recipients, $p ) {
    7278                if ( is_wp_error( $this->validate_audience_filters( $p ) ) ) { return []; }
    7379                $f = $p['filters'] ?? [];
     80                $recipients = $this->apply_purchase_history_filters( $recipients, $f );
    7481                $result = [];
    7582                $history = [];
    … …  
    129136        }
    130137        private function render_audience_fields( $p ) {
     138                $this->render_selected_orders_field( $p );
    131139                $f = $p['filters'] ?? [];
    132140                $invalid = is_wp_error( $this->validate_audience_filters( $p ) );
     141                $this->render_purchase_history_fields( $f, $invalid );
    133142                if ( $invalid ) { echo '<p role="alert">' . esc_html__( 'Saved conditions are invalid or unavailable. They remain selected; correct or explicitly remove them before previewing.', 'yoohw-advanced-notifications' ) . '</p>'; }
    134143                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Audience conditions', 'yoohw-advanced-notifications' ) . '</h2><div class="an-order-filters"><div class="an-form-grid an-form-grid-three">';
    135144                foreach ( [ 'date_from' => [ 'date', __( 'Order created from (store timezone)', 'yoohw-advanced-notifications' ) ], 'date_to' => [ 'date', __( 'Through date (inclusive)', 'yoohw-advanced-notifications' ) ], 'last_days' => [ 'number', __( 'Or last N × 24 hours', 'yoohw-advanced-notifications' ) ] ] as $key => $field ) { echo '<div class="an-field"><label>' . esc_html( $field[1] ) . ' <input type="' . esc_attr( $invalid ? 'text' : $field[0] ) . '" name="audience_filters[' . esc_attr( $key ) . ']" value="' . esc_attr( $f[ $key ] ?? '' ) . '"></label></div>'; }
    136                 echo '</div><div class="an-form-grid"><div class="an-field"><label>' . esc_html__( 'Matching order statuses (default processing/completed)', 'yoohw-advanced-notifications' ) . '<select multiple name="audience_filters[order_statuses][]">';
     145                echo '</div><div class="an-form-grid"><div class="an-field"><label>' . esc_html__( 'Matching order statuses (selected orders: all; other sources: processing/completed)', 'yoohw-advanced-notifications' ) . '<select multiple name="audience_filters[order_statuses][]">';
    137146                foreach ( $this->get_order_status_options() as $key => $label ) { echo '<option value="' . esc_attr( $key ) . '" ' . selected( in_array( $key, $f['order_statuses'] ?? [], true ), true, false ) . '>' . esc_html( $label ) . '</option>'; }
    138147                $this->render_unavailable_options( $f['order_statuses'] ?? [], array_keys( $this->get_order_status_options() ) );
    … …  
    156165                $f = array_filter( $p['filters'] ?? [], static function ( $value ) { return '' !== $value && [] !== $value; } );
    157166                if ( ! $f ) { return; }
    158                 $labels = [ 'date_from' => __( 'Order created from', 'yoohw-advanced-notifications' ), 'date_to' => __( 'Through date', 'yoohw-advanced-notifications' ), 'last_days' => __( 'Last N days', 'yoohw-advanced-notifications' ), 'country' => __( 'Billing country', 'yoohw-advanced-notifications' ), 'subscription' => __( 'Subscription status', 'yoohw-advanced-notifications' ), 'history_campaign' => __( 'History campaign', 'yoohw-advanced-notifications' ), 'history_mode' => __( 'Handoff condition', 'yoohw-advanced-notifications' ), 'category_ids' => __( 'Categories including descendants', 'yoohw-advanced-notifications' ), 'order_statuses' => __( 'Matching order statuses', 'yoohw-advanced-notifications' ) ];
     167                $labels = [ 'purchase_frequency' => __( 'Store-wide purchase frequency', 'yoohw-advanced-notifications' ), 'inactive_days' => __( 'Days without a valid store-wide purchase', 'yoohw-advanced-notifications' ), 'date_from' => __( 'Order created from', 'yoohw-advanced-notifications' ), 'date_to' => __( 'Through date', 'yoohw-advanced-notifications' ), 'last_days' => __( 'Last N days', 'yoohw-advanced-notifications' ), 'country' => __( 'Billing country', 'yoohw-advanced-notifications' ), 'subscription' => __( 'Subscription status', 'yoohw-advanced-notifications' ), 'history_campaign' => __( 'History campaign', 'yoohw-advanced-notifications' ), 'history_mode' => __( 'Handoff condition', 'yoohw-advanced-notifications' ), 'category_ids' => __( 'Categories including descendants', 'yoohw-advanced-notifications' ), 'order_statuses' => __( 'Matching order statuses', 'yoohw-advanced-notifications' ) ];
    159168                echo '<h3>' . esc_html__( 'Saved audience conditions', 'yoohw-advanced-notifications' ) . '</h3><table class="widefat striped">';
    160169                foreach ( $f as $key => $value ) {
    161170                        $values = (array) $value;
     171                        if ( 'purchase_frequency' === $key ) { $options = [ 'first' => __( 'First-time buyer (one valid order)', 'yoohw-advanced-notifications' ), 'repeat' => __( 'Repeat buyer (at least two valid orders)', 'yoohw-advanced-notifications' ) ]; $values = [ $options[ $value ] ?? $value ]; }
    162172                        if ( 'order_statuses' === $key ) { $options = $this->get_order_status_options(); $values = array_map( static function ( $status ) use ( $options ) { return $options[ $status ] ?? $status; }, $values ); }
    163173                        if ( 'category_ids' === $key ) { $values = array_map( static function ( $id ) { $term = get_term( (int) $id, 'product_cat' ); return $term && ! is_wp_error( $term ) ? $term->name . ' (#' . $id . ')' : '#' . $id; }, $values ); }
    … …  
    177187                $reason = __( 'Not in the selected source or product purchasers.', 'yoohw-advanced-notifications' );
    178188                if ( in_array( $email, array_column( $baseline, 'email' ), true ) ) { $reason = __( 'No single order matches all order conditions, or billing country is missing/different.', 'yoohw-advanced-notifications' ); }
    179                 if ( in_array( $email, array_column( $filtered, 'email' ), true ) ) { $reason = __( 'Subscription status or known campaign handoff condition does not match.', 'yoohw-advanced-notifications' ); }
     189                if ( in_array( $email, array_column( $filtered, 'email' ), true ) ) { $reason = __( 'Store-wide purchase history, subscription status or known campaign handoff condition does not match.', 'yoohw-advanced-notifications' ); }
    180190                if ( in_array( $email, array_column( $filtered_common, 'email' ), true ) ) { $reason = __( 'Matched source and all conditions; removed by an explicit exclusion.', 'yoohw-advanced-notifications' ); }
    181191                if ( in_array( $email, array_column( $this->get_recipients_by_payload( $p ), 'email' ), true ) ) { $reason = __( 'Included: matches source and every applicable condition; no exclusion matched.', 'yoohw-advanced-notifications' ); }
  • yoohw-advanced-notifications/trunk/includes/trait-campaign-authoring.php

    r3771948 r3789014  
    1212        }
    1313        private function draft_fields() {
    14                 return [ 'internal_name', 'subject', 'heading', 'message', 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'schedule_mode', 'scheduled_at_input', 'filters', 'test_email' ];
    15         }
    16         private function criteria_fields() { return [ 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'filters' ]; }
     14                return [ 'order_ids', 'personalization_version', 'internal_name', 'subject', 'heading', 'message', 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'schedule_mode', 'scheduled_at_input', 'filters', 'test_email' ];
     15        }
     16        private function criteria_fields() { return [ 'order_ids', 'recipient_source', 'product_ids', 'user_roles', 'manual_emails', 'manual_emails_raw', 'exclusions', 'exclude_emails_raw', 'filters' ]; }
    1717        private function save_draft( $payload, $id = '' ) {
    1818                $id = $id ?: ( $payload['draft_id'] ?? wp_generate_uuid4() );
    … …  
    3737                $p['exclude_emails_raw'] = sanitize_textarea_field( $post['exclude_emails'] ?? '' );
    3838                $p['exclusions'] = [ 'product_ids' => array_map( 'absint', (array) ( $post['exclude_product_ids'] ?? [] ) ), 'order_statuses' => array_map( 'sanitize_key', (array) ( $post['exclude_order_statuses'] ?? [] ) ), 'emails' => $this->parse_email_list( $p['exclude_emails_raw'] ) ];
     39                $p['order_ids'] = $this->parse_selected_order_ids( $post['order_ids'] ?? '' );
     40                if ( is_wp_error( $p['order_ids'] ) ) { wp_die( esc_html( $p['order_ids']->get_error_message() ) ); }
     41                $p['personalization_version'] = 1;
     42                $p['store_name'] = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
    3943                $p['filters'] = $this->filters_from_request();
    4044                return $p;
    … …  
    6165                $p['internal_name'] = sanitize_text_field( wp_unslash( $_POST['internal_name'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Caller verifies nonce.
    6266                $p['filters'] = $this->filters_from_request();
     67                $p['order_ids'] = $this->parse_selected_order_ids( wp_unslash( $_POST['order_ids'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Caller verifies nonce.
     68                if ( is_wp_error( $p['order_ids'] ) ) { return $p['order_ids']; }
     69                $p['personalization_version'] = 1;
     70                $p['store_name'] = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
     71                $valid = $this->validate_selected_orders( $p );
     72                if ( is_wp_error( $valid ) ) { return $valid; }
    6373                $p['schedule_policy'] = 'strict_v1';
    6474                $p['store_timezone'] = wp_timezone_string();
    … …  
    120130                if ( ! $drafts ) { echo '<p>' . esc_html__( 'No saved drafts yet.', 'yoohw-advanced-notifications' ) . '</p>'; }
    121131                foreach ( $drafts as $draft ) {
    122                         echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'draft_id' => $draft['draft_id'] ] ) ) . '">' . esc_html( $draft['internal_name'] ?: ( $draft['subject'] ?: $draft['draft_id'] ) ) . '</a>';
     132                        echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'draft_id' => $draft['draft_id'] ] ) ) . '">' . esc_html( ( $draft['internal_name'] ?? '' ) ?: ( ( $draft['subject'] ?? '' ) ?: $draft['draft_id'] ) ) . '</a>';
    123133                        $this->library_button( $draft['draft_id'], 'delete_draft', __( 'Delete draft', 'yoohw-advanced-notifications' ) );
    124134                        echo '</div>';
    … …  
    128138                if ( ! $audiences ) { echo '<p>' . esc_html__( 'No saved audiences yet.', 'yoohw-advanced-notifications' ) . '</p>'; }
    129139                foreach ( $audiences as $audience ) {
    130                         echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ) . '</a>';
     140                        echo '<div class="an-library-item"><a href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ?? $audience['id'] ) . '</a>';
    131141                        $this->library_button( $audience['id'], 'delete_audience', __( 'Delete audience', 'yoohw-advanced-notifications' ) );
    132142                        echo '</div>';
    … …  
    140150                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Saved audience criteria', 'yoohw-advanced-notifications' ) . '</h2><p>';
    141151                foreach ( $this->read_durable_options( 'yoohw_an_audience_' ) as $audience ) {
    142                         echo '<a class="button" href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ) . '</a> ';
     152                        echo '<a class="button" href="' . esc_url( $this->get_admin_page_url( [ 'audience_id' => $audience['id'] ] ) ) . '">' . esc_html( $audience['name'] ?? $audience['id'] ) . '</a> ';
    143153                }
    144154                echo '<a href="' . esc_url( $this->get_admin_page_url( [ 'tab' => 'library' ] ) ) . '">' . esc_html__( 'Open Library', 'yoohw-advanced-notifications' ) . '</a></p><p class="description">' . esc_html__( 'Opening saved criteria replaces this form. Save your draft first to keep current edits.', 'yoohw-advanced-notifications' ) . '</p><input type="hidden" name="audience_id" value="' . esc_attr( $p['audience_id'] ?? '' ) . '"><div class="an-form-row"><label class="an-field">' . esc_html__( 'Audience name', 'yoohw-advanced-notifications' ) . ' <input name="audience_name" value="' . esc_attr( $p['audience_name'] ?? '' ) . '"></label> <button class="button" formnovalidate name="authoring_operation" value="save_audience">' . esc_html__( 'Save audience criteria', 'yoohw-advanced-notifications' ) . '</button></div></section>';
    … …  
    147157                echo '<section class="yoohw-advanced-notifications-panel an-template-field"><label for="an-template">' . esc_html__( 'Insert editable content template', 'yoohw-advanced-notifications' ) . '</label><p><select id="an-template"><option value="">—</option>';
    148158                $templates = [
    149                         'update' => [ __( 'Product or documentation update', 'yoohw-advanced-notifications' ), __( 'An update for you', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>We have updated {product_name}. Add the details and documentation link here.</p>', 'yoohw-advanced-notifications' ) ],
    150                         'guide' => [ __( 'After-purchase guide', 'yoohw-advanced-notifications' ), __( 'Getting started', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>Thank you for your purchase. Add setup steps and support details here.</p>', 'yoohw-advanced-notifications' ) ],
    151                         'service' => [ __( 'Service or order change', 'yoohw-advanced-notifications' ), __( 'Service update', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>Add the change, effective date and any action needed here.</p>', 'yoohw-advanced-notifications' ) ],
     159                        'update' => [ __( 'Product or documentation update', 'yoohw-advanced-notifications' ), __( 'An update for you', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>We have updated {product_name}. Describe what changed, when it applies, and add the documentation link and any action needed. Contact our support team if you need help.</p>', 'yoohw-advanced-notifications' ) ],
     160                        'guide' => [ __( 'After-purchase guide', 'yoohw-advanced-notifications' ), __( 'Getting started', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>Thank you for your purchase of {ordered_products} (order {order_number}). Add setup or care steps, when to follow them, and a support contact here.</p>', 'yoohw-advanced-notifications' ) ],
     161                        'service' => [ __( 'Service or order change', 'yoohw-advanced-notifications' ), __( 'Service update', 'yoohw-advanced-notifications' ), __( '<p>Hello {customer_name},</p><p>An update about order {order_number}: describe the change and the new delivery date or effective time. Explain any action needed and add a support contact here.</p>', 'yoohw-advanced-notifications' ) ],
    152162                ];
    153163                foreach ( $templates as $key => $template ) { echo '<option value="' . esc_attr( $key ) . '" data-subject="' . esc_attr( $template[1] ) . '" data-body="' . esc_attr( $template[2] ) . '">' . esc_html( $template[0] ) . '</option>'; }
    … …  
    217227                if ( ! empty( $_GET['audience_changed'] ) ) { echo '<p class="notice notice-warning" role="alert">' . esc_html__( 'Audience changed since preview. Review this updated preview and confirm again.', 'yoohw-advanced-notifications' ) . '</p>'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    218228                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Recipients and schedule to confirm', 'yoohw-advanced-notifications' ) . '</h2><dl class="an-preview-counts"><div><dt>' . esc_html__( 'Selected', 'yoohw-advanced-notifications' ) . '</dt><dd>' . esc_html( number_format_i18n( $p['recipient_count'] ?? 0 ) ) . '</dd></div><div><dt>' . esc_html__( 'Excluded', 'yoohw-advanced-notifications' ) . '</dt><dd>' . esc_html( number_format_i18n( $p['excluded_count'] ?? 0 ) ) . '</dd></div></dl>';
    219                 $reason_labels = [ 'order_conditions' => __( 'Removed by order conditions', 'yoohw-advanced-notifications' ), 'subscription_or_history' => __( 'Removed by subscription or campaign history', 'yoohw-advanced-notifications' ), 'explicit_exclusions_or_suppression' => __( 'Removed by exclusions or suppression', 'yoohw-advanced-notifications' ) ];
     229                $reason_labels = [ 'order_conditions' => __( 'Removed by order conditions', 'yoohw-advanced-notifications' ), 'subscription_or_history' => __( 'Removed by purchase history, subscription or campaign history', 'yoohw-advanced-notifications' ), 'explicit_exclusions_or_suppression' => __( 'Removed by exclusions or suppression', 'yoohw-advanced-notifications' ) ];
    220230                echo '<dl class="an-review-facts">';
    221231                foreach ( $p['audience_audit'] ?? [] as $reason => $count ) { echo '<div><dt>' . esc_html( $reason_labels[ $reason ] ?? $reason ) . '</dt><dd>' . esc_html( number_format_i18n( $count ) ) . '</dd></div>'; }
    … …  
    239249                        echo '</select></div><button class="button">' . esc_html__( 'Show sample', 'yoohw-advanced-notifications' ) . '</button></form>';
    240250                        $this->render_preview_device_controls();
    241                         $message = str_replace( [ '{customer_name}', '{product_name}' ], [ $sample['name'] ?: $sample['email'], $p['products_label'] ?? '' ], $p['message'] );
     251                        $personal = $this->render_personalized_email( $p, $sample );
     252                        echo '<p>' . esc_html( $personal['subject'] ) . '</p>';
     253                        $this->render_order_context_summary( $sample );
     254                        $message = $personal['message'];
    242255                        $message = $this->append_unsubscribe_footer( $message, $sample['email'], $p['campaign_id'] );
    243                         $html = $this->build_woocommerce_email_body( $p['heading'], $message );
     256                        $html = $this->build_woocommerce_email_body( $personal['heading'], $message );
    244257                        echo '<iframe class="an-personal-preview" sandbox="" title="' . esc_attr__( 'Personalized email preview', 'yoohw-advanced-notifications' ) . '" srcdoc="' . esc_attr( $html ) . '"></iframe></section>';
    245258                }
  • yoohw-advanced-notifications/trunk/includes/trait-campaign-privacy.php

    r3771948 r3789014  
    121121                echo '<div class="wrap yoohw-an-workspace"><h1>' . esc_html__( 'Email campaigns', 'yoohw-advanced-notifications' ) . '</h1>';
    122122                $this->render_page_tabs( 'subscribers' );
     123                $this->render_contact_history();
    123124                echo '<section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Subscribers and suppression', 'yoohw-advanced-notifications' ) . '</h2><p>' . esc_html__( 'An order, account or pasted email is not opt-in evidence. Unknown remains unknown. Imports never remove an unsubscribe. For subscribed records, enter the actual evidence source; the recorded time is the import time, not an inferred original consent time.', 'yoohw-advanced-notifications' ) . '</p>';
    124125                $search = strtolower( sanitize_text_field( wp_unslash( $_GET['subscription_search'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    … …  
    130131                foreach ( $this->read_durable_options( 'yoohw_an_subscription_' ) as $record ) { if ( ! empty( $record['email'] ) ) { $records[ $record['email'] ] = $record; } }
    131132                foreach ( $this->get_unsubscribed_emails() as $email => $record ) { $records[ $email ] = array_merge( (array) $record, $records[ $email ] ?? [], [ 'status' => 'unsubscribed' ] ); }
    132                 if ( is_email( $search ) && ! isset( $records[ $search ] ) ) { $records[ $search ] = $this->subscription_record( $search ); }
     133                if ( is_email( $search ) ) { $records[ $search ] = $this->subscription_record( $search ); }
    133134                $count = 0;
    134135                foreach ( $records as $email => $record ) {
    … …  
    138139                        $timestamp = $record['recorded_at'] ?? ( $record['unsubscribed_at'] ?? 0 );
    139140                        echo '<tr><td>' . esc_html( $email . ' / ' . $record['status'] ) . '</td><td>' . esc_html( ( $record['source'] ?? 'legacy/unsubscribe' ) . ' / ' . ( $timestamp ? gmdate( 'Y-m-d H:i:s', is_numeric( $timestamp ) ? (int) $timestamp : strtotime( $timestamp ) ) : '—' ) ) . '</td></tr>';
     141                        if ( ! empty( $record['confirmation_pending'] ) ) { echo '<tr><td colspan="2">' . esc_html__( 'Confirmation pending; current subscription unchanged.', 'yoohw-advanced-notifications' ) . ' ' . esc_html( $record['request_source'] . ' / ' . gmdate( 'Y-m-d H:i:s', $record['requested_at'] ) ) . '</td></tr>'; }
    140142                } echo '</table><p>' . esc_html__( 'Up to 200 matches. Erased addresses are represented only by protective hashes and are not listed as plaintext.', 'yoohw-advanced-notifications' ) . '</p>';
    141143                echo '</section><section class="yoohw-advanced-notifications-panel"><h2>' . esc_html__( 'Import records', 'yoohw-advanced-notifications' ) . '</h2><p>' . esc_html__( '1. Paste → 2. Preview → 3. Apply and review result', 'yoohw-advanced-notifications' ) . '</p><form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '"><input type="hidden" name="action" value="yoohw_an_data"><input type="hidden" name="operation" value="preview_import">'; wp_nonce_field( 'yoohw_an_data' );
    … …  
    179181                        $fields = [];
    180182                        foreach ( $row as $name => $value ) { $fields[] = [ 'name' => $name, 'value' => $value ]; }
     183                        $manifest = $this->read_durable_option( $this->campaign_manifest_key( $row['campaign_id'] ) );
     184                        foreach ( (array) ( $manifest['recipients'] ?? [] ) as $recipient ) {
     185                                if ( strtolower( $recipient['email'] ?? '' ) === $email && ! empty( $recipient['order_context'] ) ) { $fields[] = [ 'name' => __( 'Saved order context', 'yoohw-advanced-notifications' ), 'value' => wp_json_encode( $this->sanitize_order_context( $recipient['order_context'] ) ) ]; }
     186                        }
    181187                        $data[] = [ 'group_id' => 'yoohw-an', 'group_label' => __( 'Email campaigns', 'yoohw-advanced-notifications' ), 'item_id' => 'yoohw-an-' . $row['campaign_id'] . '-' . $page . '-' . $index, 'data' => $fields ];
    182188                }
    183189                if ( 1 === (int) $page ) {
    184190                        $record = $this->subscription_record( $email );
     191                        $pending = $this->read_durable_option( 'yoohw_an_signup_' . $hash );
     192                        if ( is_array( $pending ) && ( $pending['email'] ?? '' ) === $email ) { $record['pending_request'] = array_intersect_key( $pending, array_flip( [ 'source', 'requested_at', 'expires', 'consumed_at' ] ) ); }
    185193                        $record['suppression_evidence'] = $this->read_durable_option( 'yoohw_an_optout_' . $hash ) ?: ( $this->get_unsubscribed_emails()[ $email ] ?? [] );
    186194                        $record['protective_erasure_hash'] = (bool) $this->read_durable_option( 'yoohw_an_erased_' . $hash );
    … …  
    203211                if ( is_array( $value ) ) {
    204212                        if ( strtolower( $value['email'] ?? $value['to'] ?? '' ) === $email ) {
    205                                 foreach ( [ 'email', 'to', 'name', 'recipient_name', 'country', 'state', 'city', 'timezone', 'user_id' ] as $field ) { if ( isset( $value[ $field ] ) ) { $value[ $field ] = ''; } }
     213                                foreach ( [ 'email', 'to', 'name', 'recipient_name', 'country', 'state', 'city', 'timezone', 'user_id', 'order_context' ] as $field ) { if ( isset( $value[ $field ] ) ) { $value[ $field ] = ''; } }
    206214                        }
    207215                        $result = [];
  • yoohw-advanced-notifications/trunk/languages/yoohw-advanced-notifications.pot

    r3771948 r3789014  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Advanced Notifications for WooCommerce 0.3.0\n"
     5"Project-Id-Version: Advanced Notifications for WooCommerce 0.4.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/yoohw-advanced-notifications\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-29T02:04:10+00:00\n"
     12"POT-Creation-Date: 2026-11-07T03:47:04+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.9.0\n"
    … …  
    3535msgstr ""
    3636
    37 #: includes/class-customer-email-notify.php:161
     37#: includes/class-customer-email-notify.php:184
    3838msgid "Plugin deactivated before delivery."
    3939msgstr ""
    4040
    41 #: includes/class-customer-email-notify.php:365
     41#: includes/class-customer-email-notify.php:389
    4242msgid "Privacy cleanup is busy. Campaign preparation will resume."
    4343msgstr ""
    4444
    45 #: includes/class-customer-email-notify.php:372
     45#: includes/class-customer-email-notify.php:396
    4646msgid "Campaign recipients could not be prepared for background dispatch."
    4747msgstr ""
    4848
    49 #: includes/class-customer-email-notify.php:378
     49#: includes/class-customer-email-notify.php:402
    5050msgid "Recipient tracking storage is unavailable."
    5151msgstr ""
    5252
    53 #: includes/class-customer-email-notify.php:413
     53#: includes/class-customer-email-notify.php:437
    5454msgid "Invalid scheduled date/time. Create a new campaign with a valid time."
    5555msgstr ""
    5656
    57 #: includes/class-customer-email-notify.php:475
     57#: includes/class-customer-email-notify.php:499
    5858msgid "Recipient dispatch storage failed."
    5959msgstr ""
    6060
    61 #: includes/class-customer-email-notify.php:712
     61#: includes/class-customer-email-notify.php:736
    6262msgid "Recipient preparation reached the limit of 3 queue attempts. Check Scheduled Actions for jobs saved before the interruption. Existing jobs may finish; recovery will not create further jobs for these recipients."
    6363msgstr ""
    6464
    65 #: includes/class-customer-email-notify.php:718
     65#: includes/class-customer-email-notify.php:742
    6666msgid "Dispatcher could not be queued. Check Action Scheduler, then use Recover / retry eligible recipients."
    6767msgstr ""
    6868
    69 #: includes/class-customer-email-notify.php:727
     69#: includes/class-customer-email-notify.php:751
    7070msgid "Recipient snapshot is incomplete. Recovery uses only saved recipients; it cannot recreate a lost historical audience. Review the stored records before creating a new campaign."
    7171msgstr ""
    7272
    73 #: includes/class-customer-email-notify.php:731
     73#: includes/class-customer-email-notify.php:755
    7474msgid "Delivery jobs are overdue. Check WordPress cron and WooCommerce Scheduled Actions. Existing pending jobs are retained; recovery will not create another send while they remain pending."
    7575msgstr ""
    7676
    77 #: includes/class-customer-email-notify.php:735
     77#: includes/class-customer-email-notify.php:759
    7878msgid "Some outcomes are unknown. Check the mail provider logs before creating another campaign for those addresses. Recovery never resends unknown outcomes."
    7979msgstr ""
    8080
    81 #: includes/class-customer-email-notify.php:737
     81#: includes/class-customer-email-notify.php:761
    8282msgid "Review recipient reasons below. Queue failures retry at least 5 minutes apart, up to 3 queue attempts. Correct configuration failures before recovery. Cancelled recipients require a new campaign."
    8383msgstr ""
    8484
    85 #: includes/class-customer-email-notify.php:742
     85#: includes/class-customer-email-notify.php:766
    8686msgid "Paused. Resume to continue remaining recipients."
    8787msgstr ""
    8888
    89 #: includes/class-customer-email-notify.php:763
    90 #: includes/class-customer-email-notify.php:764
    91 #: includes/class-customer-email-notify.php:848
    92 #: includes/class-customer-email-notify.php:1356
    93 #: includes/class-customer-email-notify.php:1839
    94 #: includes/trait-campaign-authoring.php:112
     89#: includes/class-customer-email-notify.php:787
     90#: includes/class-customer-email-notify.php:788
     91#: includes/class-customer-email-notify.php:874
     92#: includes/class-customer-email-notify.php:1383
     93#: includes/class-customer-email-notify.php:1866
     94#: includes/trait-campaign-authoring.php:122
     95#: includes/trait-campaign-automation-ui.php:64
    9596#: includes/trait-campaign-privacy.php:121
    96 #: includes/trait-campaign-privacy.php:181
    9797#: includes/trait-campaign-privacy.php:187
    98 #: includes/trait-campaign-privacy.php:198
     98#: includes/trait-campaign-privacy.php:195
     99#: includes/trait-campaign-privacy.php:206
    99100msgid "Email campaigns"
    100101msgstr ""
    101102
    102 #: includes/class-customer-email-notify.php:854
    103 #: includes/class-customer-email-notify.php:2879
     103#: includes/class-customer-email-notify.php:880
     104#: includes/class-customer-email-notify.php:2914
    104105msgid "The email preview expired. Please review the form and try again."
    105106msgstr ""
    106107
    107 #: includes/class-customer-email-notify.php:860
     108#: includes/class-customer-email-notify.php:886
    108109msgid "The shared campaign content could not be stored safely, so no email jobs were created. Review the campaign log or server error log before retrying."
    109110msgstr ""
    110111
    111 #: includes/class-customer-email-notify.php:866
     112#: includes/class-customer-email-notify.php:892
    112113msgid "Draft restored. You can edit it and preview recipients again."
    113114msgstr ""
    114115
    115 #: includes/class-customer-email-notify.php:870
     116#: includes/class-customer-email-notify.php:896
    116117msgid "The draft preview expired, so the form could not be restored."
    117118msgstr ""
    118119
    119 #: includes/class-customer-email-notify.php:876
     120#: includes/class-customer-email-notify.php:902
    120121msgid "Recipients could not be prepared for background delivery, so no new email jobs were created."
    121122msgstr ""
    122123
    123124#. translators: %d: Number of recipients.
    124 #: includes/class-customer-email-notify.php:886
     125#: includes/class-customer-email-notify.php:912
    125126msgid "%d recipient(s) are being prepared in the background. You can leave this page; campaign status will update automatically."
    126127msgstr ""
    127128
    128129#. translators: %d: Number of recipients.
    129 #: includes/class-customer-email-notify.php:901
     130#: includes/class-customer-email-notify.php:927
    130131msgid "Email queued for %d recipient(s). Emails will be sent in the background."
    131132msgstr ""
    132133
    133134#. translators: 1: Number of recipients. 2: Scheduled local date and time.
    134 #: includes/class-customer-email-notify.php:917
     135#: includes/class-customer-email-notify.php:943
    135136msgid "Email scheduled for %1$d recipient(s). Local send time: %2$s. Timezone is derived from each recipient billing country; missing country uses UTC."
    136137msgstr ""
    137138
    138 #: includes/class-customer-email-notify.php:919
     139#: includes/class-customer-email-notify.php:945
    139140msgid "the selected time"
    140141msgstr ""
    141142
    142143#. translators: %d: Number of recipients.
    143 #: includes/class-customer-email-notify.php:933
     144#: includes/class-customer-email-notify.php:959
    144145msgid "This campaign was already queued recently for %d recipient(s), so the duplicate submission was blocked."
    145146msgstr ""
    146147
    147 #: includes/class-customer-email-notify.php:947
     148#: includes/class-customer-email-notify.php:973
    148149msgid "Test email handed to the mail system. Inbox delivery is not verified."
    149150msgstr ""
    150151
    151 #: includes/class-customer-email-notify.php:948
     152#: includes/class-customer-email-notify.php:974
    152153msgid "Test email could not be sent."
    153154msgstr ""
    154155
    155156#. translators: %d: Number of recipients.
    156 #: includes/class-customer-email-notify.php:960
     157#: includes/class-customer-email-notify.php:986
    157158msgid "Email sent to %d recipient(s)."
    158159msgstr ""
    159160
    160 #: includes/class-customer-email-notify.php:971
    161 #: includes/class-customer-email-notify.php:1581
    162 #: includes/class-customer-email-notify.php:1854
     161#: includes/class-customer-email-notify.php:997
     162#: includes/class-customer-email-notify.php:1608
     163#: includes/class-customer-email-notify.php:1881
    163164msgid "Sender"
    164165msgstr ""
    165166
    166 #: includes/class-customer-email-notify.php:975
    167 #: includes/class-customer-email-notify.php:1595
    168 #: includes/class-customer-email-notify.php:2027
     167#: includes/class-customer-email-notify.php:1001
     168#: includes/class-customer-email-notify.php:1622
     169#: includes/class-customer-email-notify.php:2054
    169170msgid "Reply-To"
    170171msgstr ""
    171172
    172173#. translators: %s: effective no-reply address.
    173 #: includes/class-customer-email-notify.php:982
    174 #: includes/class-customer-email-notify.php:1603
    175 #: includes/class-customer-email-notify.php:2035
     174#: includes/class-customer-email-notify.php:1008
     175#: includes/class-customer-email-notify.php:1630
     176#: includes/class-customer-email-notify.php:2062
    176177msgid "%s (automatic replies suppressed)"
    177178msgstr ""
    178179
    179 #: includes/class-customer-email-notify.php:991
     180#: includes/class-customer-email-notify.php:1017
    180181msgid "Edit Settings"
    181182msgstr ""
    182183
    183 #: includes/class-customer-email-notify.php:1001
     184#: includes/class-customer-email-notify.php:1027
    184185msgid "Compose steps"
    185186msgstr ""
    186187
    187 #: includes/class-customer-email-notify.php:1002
     188#: includes/class-customer-email-notify.php:1028
    188189msgid "1. Audience"
    189190msgstr ""
    190191
    191 #: includes/class-customer-email-notify.php:1002
     192#: includes/class-customer-email-notify.php:1028
     193#: includes/trait-campaign-automation-ui.php:94
    192194msgid "2. Content"
    193195msgstr ""
    194196
    195 #: includes/class-customer-email-notify.php:1002
     197#: includes/class-customer-email-notify.php:1028
    196198msgid "3. Schedule"
    197199msgstr ""
    198200
    199 #: includes/class-customer-email-notify.php:1002
     201#: includes/class-customer-email-notify.php:1028
     202#: includes/trait-campaign-automation-ui.php:94
    200203msgid "4. Review"
    201204msgstr ""
    202205
    203 #: includes/class-customer-email-notify.php:1012
    204 #: includes/class-customer-email-notify.php:1419
    205 #: includes/class-customer-email-notify.php:1431
    206 #: includes/class-customer-email-notify.php:1549
     206#: includes/class-customer-email-notify.php:1038
     207#: includes/class-customer-email-notify.php:1446
     208#: includes/class-customer-email-notify.php:1458
     209#: includes/class-customer-email-notify.php:1576
    207210msgid "Audience"
    208211msgstr ""
    209212
    210 #: includes/class-customer-email-notify.php:1013
     213#: includes/class-customer-email-notify.php:1039
    211214msgid "Choose who should receive this campaign."
    212215msgstr ""
    213216
    214 #: includes/class-customer-email-notify.php:1017
     217#: includes/class-customer-email-notify.php:1043
    215218msgid "Some selections do not apply to this recipient source. Clear the highlighted selections explicitly or choose a compatible source before previewing."
    216219msgstr ""
    217220
    218 #: includes/class-customer-email-notify.php:1020
    219 #: includes/class-customer-email-notify.php:1974
     221#: includes/class-customer-email-notify.php:1046
     222#: includes/class-customer-email-notify.php:2001
    220223msgid "Recipient source"
    221224msgstr ""
    222225
    223 #: includes/class-customer-email-notify.php:1028
     226#: includes/class-customer-email-notify.php:1054
    224227msgid "Product selection is only required for product customers."
    225228msgstr ""
    226229
    227 #: includes/class-customer-email-notify.php:1031
    228 #: includes/class-customer-email-notify.php:1979
     230#: includes/class-customer-email-notify.php:1057
     231#: includes/class-customer-email-notify.php:2006
    229232msgid "User roles"
    230233msgstr ""
    231234
    232 #: includes/class-customer-email-notify.php:1032
    233 #: includes/class-customer-email-notify.php:4264
     235#: includes/class-customer-email-notify.php:1058
     236#: includes/class-customer-email-notify.php:4303
    234237msgid "All roles"
    235238msgstr ""
    236239
    237 #: includes/class-customer-email-notify.php:1037
     240#: includes/class-customer-email-notify.php:1063
    238241msgid "Match any selected role. Leave empty for all registered users, including administrators."
    239242msgstr ""
    240243
    241 #: includes/class-customer-email-notify.php:1040
     244#: includes/class-customer-email-notify.php:1066
    242245msgid "Recipient email addresses"
    243246msgstr ""
    244247
    245 #: includes/class-customer-email-notify.php:1042
     248#: includes/class-customer-email-notify.php:1068
    246249msgid "Enter email addresses separated by commas, semicolons, spaces, or line breaks. Duplicates are removed. Invalid addresses must be corrected. Recipient-local scheduling uses UTC for this list; store-time scheduling uses the store timezone."
    247250msgstr ""
    248251
    249 #: includes/class-customer-email-notify.php:1044
     252#: includes/class-customer-email-notify.php:1070
    250253msgid "Includes billing email addresses from matching orders, including guests (processing/completed by default). Each email address is included once."
    251254msgstr ""
    252255
    253 #: includes/class-customer-email-notify.php:1047
    254 #: includes/class-customer-email-notify.php:1985
     256#. translators: %d: Number of hours after the Completed event.
     257#: includes/class-customer-email-notify.php:1073
     258#: includes/class-customer-email-notify.php:2012
     259#: includes/trait-campaign-automation-ui.php:84
    255260msgid "Products"
    256261msgstr ""
    257262
    258 #: includes/class-customer-email-notify.php:1054
     263#: includes/class-customer-email-notify.php:1080
     264#: includes/class-customer-email-notify.php:1125
     265#: includes/trait-campaign-automation-ui.php:99
     266msgid "Search products or variations"
     267msgstr ""
     268
     269#: includes/class-customer-email-notify.php:1085
     270msgid "Include matching purchases of selected products or variations."
     271msgstr ""
     272
     273#: includes/class-customer-email-notify.php:1093
     274#: includes/class-customer-email-notify.php:1592
     275#: includes/class-customer-email-notify.php:2031
     276msgid "Exclusions"
     277msgstr ""
     278
     279#: includes/class-customer-email-notify.php:1094
     280msgid "Remove matched recipients from the final list."
     281msgstr ""
     282
    259283#: includes/class-customer-email-notify.php:1099
    260 msgid "Search products or variations"
    261 msgstr ""
    262 
    263 #: includes/class-customer-email-notify.php:1059
    264 msgid "Include matching purchases of selected products or variations."
    265 msgstr ""
    266 
    267 #: includes/class-customer-email-notify.php:1067
    268 #: includes/class-customer-email-notify.php:1565
    269 #: includes/class-customer-email-notify.php:2004
    270 msgid "Exclusions"
    271 msgstr ""
    272 
    273 #: includes/class-customer-email-notify.php:1068
    274 msgid "Remove matched recipients from the final list."
    275 msgstr ""
    276 
    277 #: includes/class-customer-email-notify.php:1073
    278284msgid "Order statuses"
    279285msgstr ""
    280286
    281 #: includes/class-customer-email-notify.php:1080
     287#: includes/class-customer-email-notify.php:1106
    282288msgid "No order statuses excluded"
    283289msgstr ""
    284290
    285 #: includes/class-customer-email-notify.php:1088
     291#: includes/class-customer-email-notify.php:1114
    286292msgid "Example: exclude users with refunded orders."
    287293msgstr ""
    288294
    289 #: includes/class-customer-email-notify.php:1092
     295#: includes/class-customer-email-notify.php:1118
    290296msgid "Product purchasers"
    291297msgstr ""
    292298
    293 #: includes/class-customer-email-notify.php:1104
     299#: includes/class-customer-email-notify.php:1130
    294300msgid "Exclude people who already bought selected products."
    295301msgstr ""
    296302
    297 #: includes/class-customer-email-notify.php:1108
     303#: includes/class-customer-email-notify.php:1134
    298304msgid "Manual emails"
    299305msgstr ""
    300306
    301 #: includes/class-customer-email-notify.php:1114
     307#: includes/class-customer-email-notify.php:1140
    302308msgid "one@example.com, two@example.com"
    303309msgstr ""
    304310
    305 #: includes/class-customer-email-notify.php:1115
     311#: includes/class-customer-email-notify.php:1141
    306312msgid "Comma, semicolon, space, or line break separated."
    307313msgstr ""
    308314
    309 #: includes/class-customer-email-notify.php:1128
     315#: includes/class-customer-email-notify.php:1154
     316#: includes/trait-campaign-automation-ui.php:106
    310317msgid "Content"
    311318msgstr ""
    312319
    313 #: includes/class-customer-email-notify.php:1129
     320#: includes/class-customer-email-notify.php:1155
    314321msgid "Compose the email shown inside the WooCommerce template."
    315322msgstr ""
    316323
    317 #: includes/class-customer-email-notify.php:1135
    318 #: includes/class-customer-email-notify.php:1202
    319 #: includes/class-customer-email-notify.php:1545
    320 #: includes/class-customer-email-notify.php:2044
     324#: includes/class-customer-email-notify.php:1161
     325#: includes/class-customer-email-notify.php:1228
     326#: includes/class-customer-email-notify.php:1572
     327#: includes/class-customer-email-notify.php:2071
     328#: includes/trait-campaign-automation-ui.php:107
     329#: includes/trait-campaign-automation-ui.php:132
     330#: includes/trait-campaign-automation-ui.php:135
    321331msgid "Subject"
    322332msgstr ""
    323333
    324 #: includes/class-customer-email-notify.php:1140
    325 #: includes/class-customer-email-notify.php:5160
    326 #: includes/class-customer-email-notify.php:5389
     334#: includes/class-customer-email-notify.php:1166
     335#: includes/class-customer-email-notify.php:5221
     336#: includes/class-customer-email-notify.php:5466
     337#: includes/trait-campaign-automation-ui.php:107
    327338msgid "Email heading"
    328339msgstr ""
    329340
    330 #: includes/class-customer-email-notify.php:1145
    331 #: includes/class-customer-email-notify.php:5147
    332 #: includes/class-customer-email-notify.php:5390
     341#: includes/class-customer-email-notify.php:1171
     342#: includes/class-customer-email-notify.php:5208
     343#: includes/class-customer-email-notify.php:5467
     344#: includes/trait-campaign-automation-ui.php:108
    333345msgid "Message body"
    334346msgstr ""
    335347
    336348#. translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders.
    337 #: includes/class-customer-email-notify.php:1157
    338 msgid "Body variables: {customer_name} uses the saved recipient name, falling back to email; {product_name} is the shared campaign product label, never a personal purchase. With no product selection it uses the audience label. Subject and heading are literal."
     349#: includes/class-customer-email-notify.php:1183
     350msgid "Variables in subject, heading and body: {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products}, {order_url}. Order variables use the matching order with the highest order ID; preview shows its number and the matching order count. Without order context they are empty; missing names use Customer. {product_name} remains the shared campaign label. Order links require the matching account login; guests have no order link. The live editor shows template text; Preview Recipients and test email use the first eligible recipient."
    339351msgstr ""
    340352
    341353#. translators: Keep {customer_name} and {product_name} unchanged. They are email placeholders.
    342 #: includes/class-customer-email-notify.php:1158
     354#: includes/class-customer-email-notify.php:1184
    343355msgid "An unsubscribe link is added automatically to campaign emails."
    344356msgstr ""
    345357
    346 #: includes/class-customer-email-notify.php:1166
     358#: includes/class-customer-email-notify.php:1192
    347359msgid "Test"
    348360msgstr ""
    349361
    350 #: includes/class-customer-email-notify.php:1167
     362#: includes/class-customer-email-notify.php:1193
    351363msgid "Send a single email without building the campaign queue."
    352364msgstr ""
    353365
    354 #: includes/class-customer-email-notify.php:1174
     366#: includes/class-customer-email-notify.php:1200
    355367msgid "Enable test mode"
    356368msgstr ""
    357369
    358 #: includes/class-customer-email-notify.php:1175
     370#: includes/class-customer-email-notify.php:1201
    359371msgid "Only the test recipient will receive this email."
    360372msgstr ""
    361373
    362 #: includes/class-customer-email-notify.php:1180
     374#: includes/class-customer-email-notify.php:1206
     375#: includes/trait-campaign-automation-ui.php:118
    363376msgid "Test email address"
    364377msgstr ""
    365378
    366 #: includes/class-customer-email-notify.php:1189
    367 #: includes/class-customer-email-notify.php:1278
     379#: includes/class-customer-email-notify.php:1215
     380#: includes/class-customer-email-notify.php:1304
    368381msgid "Send Test Email"
    369382msgstr ""
    370383
    371 #: includes/class-customer-email-notify.php:1191
    372 #: includes/class-customer-email-notify.php:1194
     384#: includes/class-customer-email-notify.php:1217
     385#: includes/class-customer-email-notify.php:1220
     386#: includes/trait-campaign-automation-ui.php:128
     387#: includes/trait-campaign-automation-ui.php:129
    373388msgid "Email preview"
    374389msgstr ""
    375390
    376 #: includes/class-customer-email-notify.php:1195
     391#: includes/class-customer-email-notify.php:1221
     392#: includes/trait-campaign-automation-ui.php:129
    377393msgid "Rendered with the active WooCommerce email template."
    378394msgstr ""
    379395
    380 #: includes/class-customer-email-notify.php:1197
     396#: includes/class-customer-email-notify.php:1223
     397#: includes/trait-campaign-automation-ui.php:129
    381398msgid "Live"
    382399msgstr ""
    383400
    384 #: includes/class-customer-email-notify.php:1203
    385 #: includes/class-customer-email-notify.php:5388
     401#: includes/class-customer-email-notify.php:1229
     402#: includes/class-customer-email-notify.php:5465
    386403msgid "Email subject"
    387404msgstr ""
    388405
    389 #: includes/class-customer-email-notify.php:1210
     406#: includes/class-customer-email-notify.php:1236
     407#: includes/trait-campaign-automation-ui.php:132
    390408msgid "WooCommerce email preview"
    391409msgstr ""
    392410
    393 #: includes/class-customer-email-notify.php:1219
     411#: includes/class-customer-email-notify.php:1245
    394412msgid "Schedule"
    395413msgstr ""
    396414
    397 #: includes/class-customer-email-notify.php:1220
     415#: includes/class-customer-email-notify.php:1246
    398416msgid "Choose when the campaign should be sent."
    399417msgstr ""
    400418
    401 #: includes/class-customer-email-notify.php:1228
     419#: includes/class-customer-email-notify.php:1254
    402420msgid "Send after confirmation"
    403421msgstr ""
    404422
    405 #: includes/class-customer-email-notify.php:1229
     423#: includes/class-customer-email-notify.php:1255
    406424msgid "Queue emails as soon as the preview is confirmed."
    407425msgstr ""
    408426
    409 #: includes/class-customer-email-notify.php:1235
     427#: includes/class-customer-email-notify.php:1261
    410428msgid "Schedule for later"
    411429msgstr ""
    412430
    413 #: includes/class-customer-email-notify.php:1236
     431#: includes/class-customer-email-notify.php:1262
    414432msgid "Legacy schedule restored. Choose an explicit store or local mode for this new campaign."
    415433msgstr ""
    416434
    417 #: includes/class-customer-email-notify.php:1241
     435#: includes/class-customer-email-notify.php:1267
    418436msgid "One time in store timezone:"
    419437msgstr ""
    420438
    421 #: includes/class-customer-email-notify.php:1241
     439#: includes/class-customer-email-notify.php:1267
    422440msgid "All recipients share one UTC due time."
    423441msgstr ""
    424442
    425 #: includes/class-customer-email-notify.php:1242
    426 #: includes/trait-campaign-authoring.php:222
     443#: includes/class-customer-email-notify.php:1268
     444#: includes/trait-campaign-authoring.php:232
    427445msgid "Same local time per recipient (location estimate; UTC fallback)"
    428446msgstr ""
    429447
    430 #: includes/class-customer-email-notify.php:1242
     448#: includes/class-customer-email-notify.php:1268
    431449msgid "Only unambiguous billing countries use a location estimate. Other countries, missing data and manual lists use UTC."
    432450msgstr ""
    433451
    434 #: includes/class-customer-email-notify.php:1244
     452#: includes/class-customer-email-notify.php:1270
    435453msgid "Scheduled date/time"
    436454msgstr ""
    437455
    438 #: includes/class-customer-email-notify.php:1251
     456#: includes/class-customer-email-notify.php:1277
    439457msgid "Store mode uses the displayed store timezone. Local mode uses a billing-location estimate only when unambiguous, otherwise UTC. Past, nonexistent or repeated local times are rejected; choose another time. No automatic next-day rollover."
    440458msgstr ""
    441459
    442 #: includes/class-customer-email-notify.php:1257
     460#: includes/class-customer-email-notify.php:1283
    443461msgid "Review before confirmation"
    444462msgstr ""
    445463
    446 #: includes/class-customer-email-notify.php:1258
     464#: includes/class-customer-email-notify.php:1284
    447465msgid "Preview Recipients opens the saved recipient preview with selected and excluded counts, reasons, personalized samples and confirmed due times. No audience estimate is calculated while you edit."
    448466msgstr ""
    449467
    450 #: includes/class-customer-email-notify.php:1259
     468#: includes/class-customer-email-notify.php:1285
    451469msgid "Review that preview, then confirm there. Changes require another preview. Emails already handed to the mail system cannot be recalled; inbox delivery is not verified."
    452470msgstr ""
    453471
    454 #: includes/class-customer-email-notify.php:1260
     472#: includes/class-customer-email-notify.php:1286
    455473msgid "Test mode sends only to the test address. Turn it off in Content to preview a campaign audience."
    456474msgstr ""
    457475
    458 #: includes/class-customer-email-notify.php:1265
    459 #: includes/class-customer-email-notify.php:1267
     476#: includes/class-customer-email-notify.php:1291
     477#: includes/class-customer-email-notify.php:1293
    460478msgid "Next step: preview recipients and confirm before sending."
    461479msgstr ""
    462480
    463 #: includes/class-customer-email-notify.php:1266
     481#: includes/class-customer-email-notify.php:1292
    464482msgid "Test mode sends one email to the test recipient and keeps this draft for further edits."
    465483msgstr ""
    466484
    467 #: includes/class-customer-email-notify.php:1271
    468 #: includes/class-customer-email-notify.php:1277
     485#: includes/class-customer-email-notify.php:1297
     486#: includes/class-customer-email-notify.php:1303
    469487msgid "Preview Recipients"
    470488msgstr ""
    471489
    472 #: includes/class-customer-email-notify.php:1285
     490#: includes/class-customer-email-notify.php:1311
     491#: includes/trait-campaign-automation-ui.php:122
    473492msgid "Back"
    474493msgstr ""
    475494
    476 #: includes/class-customer-email-notify.php:1286
     495#: includes/class-customer-email-notify.php:1312
     496#: includes/trait-campaign-automation-ui.php:122
    477497msgid "Continue"
    478498msgstr ""
    479499
    480 #: includes/class-customer-email-notify.php:1296
     500#: includes/class-customer-email-notify.php:1322
    481501msgid "Campaigns"
    482502msgstr ""
    483503
    484 #: includes/class-customer-email-notify.php:1297
     504#: includes/class-customer-email-notify.php:1323
    485505msgid "Compose"
    486506msgstr ""
    487507
    488 #: includes/class-customer-email-notify.php:1298
     508#: includes/class-customer-email-notify.php:1324
     509#: includes/trait-campaign-automation-ui.php:80
     510msgid "Automations"
     511msgstr ""
     512
     513#: includes/class-customer-email-notify.php:1325
    489514msgid "Library"
    490515msgstr ""
    491516
    492 #: includes/class-customer-email-notify.php:1299
     517#: includes/class-customer-email-notify.php:1326
    493518msgid "Subscribers"
    494519msgstr ""
    495520
    496 #: includes/class-customer-email-notify.php:1300
     521#: includes/class-customer-email-notify.php:1327
    497522msgid "Settings"
    498523msgstr ""
    499524
    500 #: includes/class-customer-email-notify.php:1344
    501 #: includes/class-customer-email-notify.php:1380
     525#: includes/class-customer-email-notify.php:1371
     526#: includes/class-customer-email-notify.php:1407
    502527msgid "Campaign list"
    503528msgstr ""
    504529
    505530#. translators: %s: Campaign ID.
    506 #: includes/class-customer-email-notify.php:1349
    507 #: includes/class-customer-email-notify.php:1670
     531#: includes/class-customer-email-notify.php:1376
     532#: includes/class-customer-email-notify.php:1697
    508533msgid "Recent campaign log"
    509534msgstr ""
    510535
    511 #: includes/class-customer-email-notify.php:1360
     536#: includes/class-customer-email-notify.php:1387
    512537msgid "Handed to mail system means the sending request was accepted, not that the email reached an inbox. Queue failures retry automatically up to 3 attempts, at least 5 minutes apart. Known pre-transport failures require correcting the reported data or configuration, then recovery. Unknown outcomes require checking the mail provider before deciding to create a new campaign."
    513538msgstr ""
    514539
    515 #: includes/class-customer-email-notify.php:1381
     540#: includes/class-customer-email-notify.php:1408
    516541msgid "Recent campaign previews, tests, queues, schedules, and send results are grouped by campaign."
    517542msgstr ""
    518543
    519544#. translators: %d: Number of campaigns.
    520 #: includes/class-customer-email-notify.php:1387
     545#: includes/class-customer-email-notify.php:1414
    521546msgid "%d campaign(s) stored"
    522547msgstr ""
    523548
    524 #: includes/class-customer-email-notify.php:1397
     549#: includes/class-customer-email-notify.php:1424
    525550msgid "No campaign history has been created yet. Preview or send a campaign to start tracking it here."
    526551msgstr ""
    527552
    528 #: includes/class-customer-email-notify.php:1416
    529 #: includes/class-customer-email-notify.php:1428
    530 #: includes/class-customer-email-notify.php:1711
    531 #: includes/class-customer-email-notify.php:1721
     553#: includes/class-customer-email-notify.php:1443
     554#: includes/class-customer-email-notify.php:1455
     555#: includes/class-customer-email-notify.php:1738
     556#: includes/class-customer-email-notify.php:1748
    532557msgid "Campaign"
    533558msgstr ""
    534559
    535 #: includes/class-customer-email-notify.php:1417
    536 #: includes/class-customer-email-notify.php:1429
    537 #: includes/class-customer-email-notify.php:1713
    538 #: includes/class-customer-email-notify.php:1723
    539 #: includes/trait-campaign-privacy.php:142
     560#: includes/class-customer-email-notify.php:1444
     561#: includes/class-customer-email-notify.php:1456
     562#: includes/class-customer-email-notify.php:1740
     563#: includes/class-customer-email-notify.php:1750
     564#: includes/trait-campaign-privacy.php:144
    540565msgid "Status"
    541566msgstr ""
    542567
    543 #: includes/class-customer-email-notify.php:1418
    544 #: includes/class-customer-email-notify.php:1430
    545 #: includes/class-customer-email-notify.php:1715
    546 #: includes/class-customer-email-notify.php:1725
     568#: includes/class-customer-email-notify.php:1445
     569#: includes/class-customer-email-notify.php:1457
     570#: includes/class-customer-email-notify.php:1742
     571#: includes/class-customer-email-notify.php:1752
    547572msgid "Message"
    548573msgstr ""
    549574
    550 #: includes/class-customer-email-notify.php:1420
    551 #: includes/class-customer-email-notify.php:1432
    552 #: includes/class-customer-email-notify.php:1577
    553 #: includes/class-customer-email-notify.php:2052
     575#: includes/class-customer-email-notify.php:1447
     576#: includes/class-customer-email-notify.php:1459
     577#: includes/class-customer-email-notify.php:1604
     578#: includes/class-customer-email-notify.php:2079
    554579msgid "Delivery"
    555580msgstr ""
    556581
    557 #: includes/class-customer-email-notify.php:1421
    558 #: includes/class-customer-email-notify.php:1433
     582#: includes/class-customer-email-notify.php:1448
     583#: includes/class-customer-email-notify.php:1460
    559584msgid "Activity"
    560585msgstr ""
    561586
    562 #: includes/class-customer-email-notify.php:1422
    563 #: includes/class-customer-email-notify.php:1434
     587#: includes/class-customer-email-notify.php:1449
     588#: includes/class-customer-email-notify.php:1461
     589#: includes/trait-campaign-contact-history.php:81
    564590msgid "Updated"
    565591msgstr ""
    566592
    567 #: includes/class-customer-email-notify.php:1423
    568 #: includes/class-customer-email-notify.php:1435
     593#: includes/class-customer-email-notify.php:1450
     594#: includes/class-customer-email-notify.php:1462
    569595msgid "Actions"
    570596msgstr ""
    571597
    572 #: includes/class-customer-email-notify.php:1459
     598#: includes/class-customer-email-notify.php:1486
    573599msgid "Test campaign"
    574600msgstr ""
    575601
    576 #: includes/class-customer-email-notify.php:1463
    577 #: includes/class-customer-email-notify.php:1506
     602#: includes/class-customer-email-notify.php:1490
     603#: includes/class-customer-email-notify.php:1533
    578604msgid "View log"
    579605msgstr ""
    580606
    581 #: includes/class-customer-email-notify.php:1473
     607#: includes/class-customer-email-notify.php:1500
    582608msgid "(No subject)"
    583609msgstr ""
    584610
    585611#. translators: %s: Formatted creation date and time.
    586 #: includes/class-customer-email-notify.php:1496
     612#: includes/class-customer-email-notify.php:1523
    587613msgid "Created %s"
    588614msgstr ""
    589615
    590 #: includes/class-customer-email-notify.php:1530
     616#: includes/class-customer-email-notify.php:1557
    591617msgid "The selected campaign could not be found in stored history."
    592618msgstr ""
    593619
    594 #: includes/class-customer-email-notify.php:1537
     620#: includes/class-customer-email-notify.php:1564
    595621msgid "Campaign details"
    596622msgstr ""
    597623
    598 #: includes/class-customer-email-notify.php:1541
    599 #: includes/class-customer-email-notify.php:1970
     624#: includes/class-customer-email-notify.php:1568
     625#: includes/class-customer-email-notify.php:1997
    600626msgid "Campaign ID"
    601627msgstr ""
    602628
    603 #: includes/class-customer-email-notify.php:1561
     629#: includes/class-customer-email-notify.php:1588
    604630msgid "Scope"
    605631msgstr ""
    606632
    607 #: includes/class-customer-email-notify.php:1571
     633#: includes/class-customer-email-notify.php:1598
    608634msgid "None"
    609635msgstr ""
    610636
    611 #: includes/class-customer-email-notify.php:1634
     637#: includes/class-customer-email-notify.php:1661
    612638msgid "Recover / retry eligible recipients"
    613639msgstr ""
    614640
    615641#. translators: %d: Number of scheduling failures.
    616 #: includes/class-customer-email-notify.php:1644
     642#: includes/class-customer-email-notify.php:1671
    617643msgid "%d eligible recipient(s). Recovery continues preparation and retries queue failures or known pre-transport failures, up to 3 attempts. Unknown outcomes are never retried. Unsubscribes are checked again."
    618644msgstr ""
    619645
    620 #: includes/class-customer-email-notify.php:1655
     646#: includes/class-customer-email-notify.php:1682
    621647msgid "Historical schedule failures are recorded, but their recipient-level records are no longer complete. Automatic retry is disabled to prevent sending to the wrong or already handled recipients."
    622648msgstr ""
    623649
    624650#. translators: %s: Campaign ID.
    625 #: includes/class-customer-email-notify.php:1669
     651#: includes/class-customer-email-notify.php:1696
    626652msgid "Log for campaign %s"
    627653msgstr ""
    628654
    629 #: includes/class-customer-email-notify.php:1674
     655#: includes/class-customer-email-notify.php:1701
    630656msgid "Latest stored send, queue, schedule, failure, skipped, and unsubscribe events."
    631657msgstr ""
    632658
    633 #: includes/class-customer-email-notify.php:1678
     659#: includes/class-customer-email-notify.php:1705
    634660msgid "View all logs"
    635661msgstr ""
    636662
    637 #: includes/class-customer-email-notify.php:1685
     663#: includes/class-customer-email-notify.php:1712
    638664msgid "No log entries found yet."
    639665msgstr ""
    640666
    641 #: includes/class-customer-email-notify.php:1710
    642 #: includes/class-customer-email-notify.php:1720
     667#: includes/class-customer-email-notify.php:1737
     668#: includes/class-customer-email-notify.php:1747
    643669msgid "Time"
    644670msgstr ""
    645671
    646 #: includes/class-customer-email-notify.php:1712
    647 #: includes/class-customer-email-notify.php:1722
    648 #: includes/class-customer-email-notify.php:2075
     672#: includes/class-customer-email-notify.php:1739
     673#: includes/class-customer-email-notify.php:1749
     674#: includes/class-customer-email-notify.php:2102
    649675#: includes/trait-campaign-controls.php:179
    650676msgid "Email"
    651677msgstr ""
    652678
    653 #: includes/class-customer-email-notify.php:1714
    654 #: includes/class-customer-email-notify.php:1724
     679#: includes/class-customer-email-notify.php:1741
     680#: includes/class-customer-email-notify.php:1751
    655681msgid "Action ID"
    656682msgstr ""
    657683
    658 #: includes/class-customer-email-notify.php:1741
     684#: includes/class-customer-email-notify.php:1768
    659685msgid "unknown"
    660686msgstr ""
    661687
    662 #: includes/class-customer-email-notify.php:1778
     688#: includes/class-customer-email-notify.php:1805
    663689msgid "The selected campaign could not be found."
    664690msgstr ""
    665691
    666 #: includes/class-customer-email-notify.php:1779
     692#: includes/class-customer-email-notify.php:1806
    667693msgid "This campaign does not have enough stored email content to reschedule safely. For older campaigns, keep at least one pending scheduled action so Email campaigns can recover the message payload."
    668694msgstr ""
    669695
    670 #: includes/class-customer-email-notify.php:1780
     696#: includes/class-customer-email-notify.php:1807
    671697msgid "Recipient-level failure records are incomplete, so automatic retry was blocked to prevent duplicate or incorrect delivery."
    672698msgstr ""
    673699
    674 #: includes/class-customer-email-notify.php:1781
     700#: includes/class-customer-email-notify.php:1808
    675701msgid "The campaign content could not be stored safely, so no retry jobs were created."
    676702msgstr ""
    677703
    678 #: includes/class-customer-email-notify.php:1782
     704#: includes/class-customer-email-notify.php:1809
    679705msgid "Failed recipients could not be prepared for background retry."
    680706msgstr ""
    681707
    682 #: includes/class-customer-email-notify.php:1783
     708#: includes/class-customer-email-notify.php:1810
    683709msgid "The background retry dispatcher could not be queued."
    684710msgstr ""
    685711
    686 #: includes/class-customer-email-notify.php:1784
     712#: includes/class-customer-email-notify.php:1811
    687713msgid "No stuck schedule-failed recipients were found for this campaign."
    688714msgstr ""
    689715
    690 #: includes/class-customer-email-notify.php:1788
     716#: includes/class-customer-email-notify.php:1815
    691717msgid "The failed schedule retry could not be completed."
    692718msgstr ""
    693719
    694720#. translators: 1: Number of emails successfully rescheduled. 2: Number of failed retries.
    695 #: includes/class-customer-email-notify.php:1807
     721#: includes/class-customer-email-notify.php:1834
    696722msgid "Rescheduled %1$d failed email(s). %2$d retry attempt(s) failed."
    697723msgstr ""
    698724
    699725#. translators: %d: Number of recipients.
    700 #: includes/class-customer-email-notify.php:1825
     726#: includes/class-customer-email-notify.php:1852
    701727msgid "%d failed recipient(s) are being prepared for retry in the background."
    702728msgstr ""
    703729
    704 #: includes/class-customer-email-notify.php:1845
     730#: includes/class-customer-email-notify.php:1872
    705731msgid "Email campaigns settings saved."
    706732msgstr ""
    707733
    708 #: includes/class-customer-email-notify.php:1856
     734#: includes/class-customer-email-notify.php:1883
    709735msgid "From email"
    710736msgstr ""
    711737
    712 #: includes/class-customer-email-notify.php:1866
     738#: includes/class-customer-email-notify.php:1893
    713739msgid "Used as the sender email for every campaign."
    714740msgstr ""
    715741
    716 #: includes/class-customer-email-notify.php:1868
     742#: includes/class-customer-email-notify.php:1895
    717743msgid "From name"
    718744msgstr ""
    719745
    720 #: includes/class-customer-email-notify.php:1877
     746#: includes/class-customer-email-notify.php:1904
    721747msgid "Reply-To email"
    722748msgstr ""
    723749
    724 #: includes/class-customer-email-notify.php:1887
     750#: includes/class-customer-email-notify.php:1914
    725751msgid "Used when automated-reply suppression is disabled."
    726752msgstr ""
    727753
    728 #: includes/class-customer-email-notify.php:1889
     754#: includes/class-customer-email-notify.php:1916
    729755msgid "No-reply email"
    730756msgstr ""
    731757
    732 #: includes/class-customer-email-notify.php:1898
     758#: includes/class-customer-email-notify.php:1925
    733759msgid "Used as Reply-To while suppression is enabled, so non-compliant automatic responders do not reach the support inbox."
    734760msgstr ""
    735761
    736 #: includes/class-customer-email-notify.php:1900
     762#: includes/class-customer-email-notify.php:1927
    737763msgid "Automated replies"
    738764msgstr ""
    739765
    740 #: includes/class-customer-email-notify.php:1909
     766#: includes/class-customer-email-notify.php:1936
    741767msgid "Suppress out-of-office and other automatic replies"
    742768msgstr ""
    743769
    744 #: includes/class-customer-email-notify.php:1912
     770#: includes/class-customer-email-notify.php:1939
    745771msgid "Adds standards-based bulk/automatic message headers while preserving delivery failure notifications."
    746772msgstr ""
    747773
    748 #: includes/class-customer-email-notify.php:1915
     774#: includes/class-customer-email-notify.php:1942
    749775msgid "Save Settings"
    750776msgstr ""
    751777
    752 #: includes/class-customer-email-notify.php:1928
    753 #: includes/class-customer-email-notify.php:1963
     778#: includes/class-customer-email-notify.php:1955
     779#: includes/class-customer-email-notify.php:1990
    754780msgid "Preview email campaign"
    755781msgstr ""
    756782
    757 #: includes/class-customer-email-notify.php:1934
    758 #: includes/class-customer-email-notify.php:2133
     783#: includes/class-customer-email-notify.php:1961
     784#: includes/class-customer-email-notify.php:2160
    759785msgid "Back to email form"
    760786msgstr ""
    761787
    762 #: includes/class-customer-email-notify.php:1948
    763 #: includes/class-customer-email-notify.php:4253
     788#: includes/class-customer-email-notify.php:1975
     789#: includes/class-customer-email-notify.php:4292
    764790msgid "Product customers"
    765791msgstr ""
    766792
    767 #: includes/class-customer-email-notify.php:1951
    768 #: includes/class-customer-email-notify.php:5115
     793#: includes/class-customer-email-notify.php:1978
     794#: includes/class-customer-email-notify.php:5176
    769795msgid "selected product(s)"
    770796msgstr ""
    771797
    772 #: includes/class-customer-email-notify.php:1990
     798#: includes/class-customer-email-notify.php:2017
    773799#: includes/trait-campaign-controls.php:172
    774800msgid "Recipients"
    … …  
    776802
    777803#. translators: %d: Number of recipients.
    778 #: includes/class-customer-email-notify.php:1995
     804#: includes/class-customer-email-notify.php:2022
    779805msgid "%d recipient"
    780806msgid_plural "%d recipients"
    … …  
    782808msgstr[1] ""
    783809
    784 #: includes/class-customer-email-notify.php:2008
    785 #: includes/trait-campaign-authoring.php:218
     810#: includes/class-customer-email-notify.php:2035
     811#: includes/trait-campaign-authoring.php:228
    786812msgid "Excluded"
    787813msgstr ""
    788814
    789815#. translators: 1: Number of excluded recipients. 2: Initial number of recipients.
    790 #: includes/class-customer-email-notify.php:2013
     816#: includes/class-customer-email-notify.php:2040
    791817msgid "%1$d removed from %2$d initial recipient(s)."
    792818msgstr ""
    793819
    794 #: includes/class-customer-email-notify.php:2023
     820#: includes/class-customer-email-notify.php:2050
    795821msgid "From"
    796822msgstr ""
    797823
    798 #: includes/class-customer-email-notify.php:2048
     824#: includes/class-customer-email-notify.php:2075
    799825msgid "Heading"
    800826msgstr ""
    801827
    802828#. translators: %s: Scheduled local date and time.
    803 #: includes/class-customer-email-notify.php:2058
     829#: includes/class-customer-email-notify.php:2085
    804830msgid "Scheduled for %s. See the confirmed mode, timezone and UTC range below."
    805831msgstr ""
    806832
    807 #: includes/class-customer-email-notify.php:2061
     833#: includes/class-customer-email-notify.php:2088
    808834msgid "Send as soon as possible after confirmation"
    809835msgstr ""
    810836
    811 #: includes/class-customer-email-notify.php:2070
     837#: includes/class-customer-email-notify.php:2097
    812838msgid "Saved recipient sample and due times"
    813839msgstr ""
    814840
    815 #: includes/class-customer-email-notify.php:2074
     841#: includes/class-customer-email-notify.php:2101
    816842msgid "Name"
    817843msgstr ""
    818844
    819 #: includes/class-customer-email-notify.php:2076
    820 #: includes/trait-campaign-audiences.php:143
    821 #: includes/trait-campaign-audiences.php:158
     845#: includes/class-customer-email-notify.php:2103
     846#: includes/trait-campaign-audiences.php:152
     847#: includes/trait-campaign-audiences.php:167
    822848msgid "Billing country"
    823849msgstr ""
    824850
    825 #: includes/class-customer-email-notify.php:2077
     851#: includes/class-customer-email-notify.php:2104
    826852msgid "Billing state/city"
    827853msgstr ""
    828854
    829 #: includes/class-customer-email-notify.php:2079
     855#: includes/class-customer-email-notify.php:2106
    830856msgid "Scheduled timezone"
    831857msgstr ""
    832858
    833 #: includes/class-customer-email-notify.php:2080
     859#: includes/class-customer-email-notify.php:2107
    834860msgid "Scheduled send time"
    835861msgstr ""
    836862
    837 #: includes/class-customer-email-notify.php:2094
     863#: includes/class-customer-email-notify.php:2121
    838864msgid "UTC fallback"
    839865msgstr ""
    840866
    841 #: includes/class-customer-email-notify.php:2101
     867#: includes/class-customer-email-notify.php:2128
    842868msgid "Not set"
    843869msgstr ""
    844870
    845 #: includes/class-customer-email-notify.php:2112
     871#: includes/class-customer-email-notify.php:2139
    846872msgid "Invalid scheduled time"
    847873msgstr ""
    848874
    849 #: includes/class-customer-email-notify.php:2125
     875#: includes/class-customer-email-notify.php:2152
    850876msgid "Message preview"
    851877msgstr ""
    852878
    853 #: includes/class-customer-email-notify.php:2141
     879#: includes/class-customer-email-notify.php:2168
    854880msgid "Confirm this saved preview"
    855881msgstr ""
    856882
    857 #: includes/class-customer-email-notify.php:2142
     883#: includes/class-customer-email-notify.php:2169
    858884msgid "Confirmation creates this campaign with its saved recipients and content. Audience changes require a new preview. Emails already handed to the mail system cannot be recalled; inbox delivery is not verified."
    859885msgstr ""
    860886
    861887#. translators: %d: Number of recipients.
    862 #: includes/class-customer-email-notify.php:2149
     888#: includes/class-customer-email-notify.php:2176
    863889msgid "Schedule Email for %d Recipient(s)"
    864890msgstr ""
    865891
    866892#. translators: %d: Number of recipients.
    867 #: includes/class-customer-email-notify.php:2150
     893#: includes/class-customer-email-notify.php:2177
    868894msgid "Queue Email for %d Recipient(s)"
    869895msgstr ""
    870896
    871 #: includes/class-customer-email-notify.php:2158
     897#: includes/class-customer-email-notify.php:2185
    872898msgid "No matching customers were found. Nothing will be queued."
    873899msgstr ""
    874900
    875 #: includes/class-customer-email-notify.php:2173
     901#: includes/class-customer-email-notify.php:2200
    876902msgid "This unsubscribe link is invalid or expired."
    877903msgstr ""
    878904
    879 #: includes/class-customer-email-notify.php:2174
     905#: includes/class-customer-email-notify.php:2201
    880906msgid "Invalid unsubscribe link"
    881907msgstr ""
    882908
    883 #: includes/class-customer-email-notify.php:2180
     909#: includes/class-customer-email-notify.php:2207
    884910msgid "Your unsubscribe could not be saved. Please try this link again."
    885911msgstr ""
    886912
    887 #: includes/class-customer-email-notify.php:2189
     913#: includes/class-customer-email-notify.php:2216
    888914msgid "You have been unsubscribed."
    889915msgstr ""
    890916
    891 #: includes/class-customer-email-notify.php:2190
     917#: includes/class-customer-email-notify.php:2217
    892918msgid "This email address will be excluded from future email campaigns."
    893919msgstr ""
    894920
    895 #: includes/class-customer-email-notify.php:2193
    896 #: includes/class-customer-email-notify.php:3600
     921#: includes/class-customer-email-notify.php:2220
     922#: includes/class-customer-email-notify.php:3636
     923#: includes/trait-campaign-contact-history.php:66
    897924#: includes/trait-campaign-controls.php:173
    898 #: includes/trait-campaign-privacy.php:127
    899 #: includes/trait-campaign-privacy.php:143
     925#: includes/trait-campaign-privacy.php:128
     926#: includes/trait-campaign-privacy.php:145
    900927msgid "Unsubscribed"
    901928msgstr ""
    902929
    903 #: includes/class-customer-email-notify.php:2200
    904 #: includes/class-customer-email-notify.php:2253
    905 #: includes/class-customer-email-notify.php:2270
    906 #: includes/trait-campaign-authoring.php:91
     930#: includes/class-customer-email-notify.php:2227
     931#: includes/class-customer-email-notify.php:2280
     932#: includes/class-customer-email-notify.php:2297
     933#: includes/trait-campaign-authoring.php:101
     934#: includes/trait-campaign-automation-ui.php:16
     935#: includes/trait-campaign-contact-history.php:6
    907936#: includes/trait-campaign-controls.php:111
     937#: includes/trait-campaign-entrypoints.php:61
     938#: includes/trait-campaign-entrypoints.php:83
    908939#: includes/trait-campaign-privacy.php:80
     940#: includes/trait-campaign-subscriptions.php:128
    909941msgid "Permission denied."
    910942msgstr ""
    911943
    912 #: includes/class-customer-email-notify.php:2215
    913 #: includes/class-customer-email-notify.php:2756
     944#: includes/class-customer-email-notify.php:2242
     945#: includes/class-customer-email-notify.php:2790
    914946msgid "Please enter a valid sender email address."
    915947msgstr ""
    916948
    917 #: includes/class-customer-email-notify.php:2219
    918 #: includes/class-customer-email-notify.php:2760
     949#: includes/class-customer-email-notify.php:2246
     950#: includes/class-customer-email-notify.php:2794
    919951msgid "Please enter a sender name."
    920952msgstr ""
    921953
    922 #: includes/class-customer-email-notify.php:2223
    923 #: includes/class-customer-email-notify.php:2764
     954#: includes/class-customer-email-notify.php:2250
     955#: includes/class-customer-email-notify.php:2798
    924956msgid "Please enter a valid Reply-To email address."
    925957msgstr ""
    926958
    927 #: includes/class-customer-email-notify.php:2227
     959#: includes/class-customer-email-notify.php:2254
    928960msgid "Please enter a valid no-reply email address when automated replies are suppressed."
    929961msgstr ""
    930962
    931 #: includes/class-customer-email-notify.php:2315
     963#: includes/class-customer-email-notify.php:2342
    932964msgid "Please enter a valid test email address."
    933965msgstr ""
    934966
    935 #: includes/class-customer-email-notify.php:2333
     967#: includes/class-customer-email-notify.php:2355
    936968msgid "Test Customer"
    937969msgstr ""
    938970
    939 #: includes/class-customer-email-notify.php:2464
     971#: includes/class-customer-email-notify.php:2483
    940972msgid "Missing campaign payload identity."
    941973msgstr ""
    942974
    943 #: includes/class-customer-email-notify.php:2487
     975#: includes/class-customer-email-notify.php:2506
    944976msgid "Campaign content or sender details are incomplete."
    945977msgstr ""
    946978
    947 #: includes/class-customer-email-notify.php:2512
     979#: includes/class-customer-email-notify.php:2535
    948980msgid "Stored campaign content does not match this campaign delivery."
    949981msgstr ""
    950982
    951 #: includes/class-customer-email-notify.php:2536
     983#: includes/class-customer-email-notify.php:2559
    952984msgid "Invalid shared campaign payload reference."
    953985msgstr ""
    954986
    955 #: includes/class-customer-email-notify.php:2551
     987#: includes/class-customer-email-notify.php:2574
    956988msgid "Shared campaign content is missing or invalid."
    957989msgstr ""
    958990
    959 #: includes/class-customer-email-notify.php:2562
     991#: includes/class-customer-email-notify.php:2585
    960992msgid "Shared campaign content failed its integrity check."
    961993msgstr ""
    962994
    963 #: includes/class-customer-email-notify.php:2591
     995#: includes/class-customer-email-notify.php:2603
     996msgid "Confirmed recipient context is unavailable. No email was handed off."
     997msgstr ""
     998
     999#: includes/class-customer-email-notify.php:2625
    9641000msgid "Email job metadata is too large for Action Scheduler. Campaign content must use shared payload storage."
    9651001msgstr ""
    9661002
    967 #: includes/class-customer-email-notify.php:2602
     1003#: includes/class-customer-email-notify.php:2636
    9681004msgid "Scheduled timestamp is not in the future."
    9691005msgstr ""
    9701006
    971 #: includes/class-customer-email-notify.php:2622
     1007#: includes/class-customer-email-notify.php:2656
    9721008msgid "as_schedule_single_action returned an empty action id"
    9731009msgstr ""
    9741010
    975 #: includes/class-customer-email-notify.php:2633
     1011#: includes/class-customer-email-notify.php:2667
    9761012msgid "wp_schedule_single_event returned false"
    9771013msgstr ""
    9781014
    979 #: includes/class-customer-email-notify.php:2644
     1015#: includes/class-customer-email-notify.php:2678
    9801016msgid "Action Scheduler is unavailable."
    9811017msgstr ""
    9821018
    983 #: includes/class-customer-email-notify.php:2656
     1019#: includes/class-customer-email-notify.php:2690
    9841020msgid "as_enqueue_async_action returned an empty action id"
    9851021msgstr ""
    9861022
    987 #: includes/class-customer-email-notify.php:2698
     1023#: includes/class-customer-email-notify.php:2732
    9881024msgid "The exclusion list contains an invalid email address. Correct it before previewing."
    9891025msgstr ""
    9901026
    991 #: includes/class-customer-email-notify.php:2702
     1027#: includes/class-customer-email-notify.php:2736
    9921028msgid "A selected product is invalid or unavailable. Review the selection."
    9931029msgstr ""
    9941030
    995 #: includes/class-customer-email-notify.php:2705
     1031#: includes/class-customer-email-notify.php:2739
    9961032msgid "An excluded order status is invalid."
    9971033msgstr ""
    9981034
    999 #: includes/class-customer-email-notify.php:2724
    1000 #: includes/class-customer-email-notify.php:4289
     1035#: includes/class-customer-email-notify.php:2758
     1036#: includes/class-customer-email-notify.php:4330
    10011037msgid "Please choose a valid recipient source."
    10021038msgstr ""
    10031039
    1004 #: includes/class-customer-email-notify.php:2728
     1040#: includes/class-customer-email-notify.php:2762
    10051041msgid "Please choose a valid send time option."
    10061042msgstr ""
    10071043
    1008 #: includes/class-customer-email-notify.php:2732
     1044#: includes/class-customer-email-notify.php:2766
    10091045msgid "Selected products apply only to Product customers. Clear the products or choose that recipient source before previewing."
    10101046msgstr ""
    10111047
    1012 #: includes/class-customer-email-notify.php:2735
     1048#: includes/class-customer-email-notify.php:2769
    10131049msgid "Selected roles apply only to registered users. Clear the roles or choose that recipient source before previewing."
    10141050msgstr ""
    10151051
    1016 #: includes/class-customer-email-notify.php:2738
     1052#: includes/class-customer-email-notify.php:2772
    10171053msgid "The pasted email list applies only to Manual email list. Clear the list or choose that recipient source before previewing."
    10181054msgstr ""
    10191055
    1020 #: includes/class-customer-email-notify.php:2747
     1056#: includes/class-customer-email-notify.php:2781
    10211057msgid "Please enter a valid scheduled date/time."
    10221058msgstr ""
    10231059
    1024 #: includes/class-customer-email-notify.php:2752
     1060#: includes/class-customer-email-notify.php:2786
    10251061msgid "Missing required fields."
    10261062msgstr ""
    10271063
    1028 #: includes/class-customer-email-notify.php:2768
     1064#: includes/class-customer-email-notify.php:2802
    10291065msgid "Please select at least one product."
    10301066msgstr ""
    10311067
    1032 #: includes/class-customer-email-notify.php:2873
     1068#: includes/class-customer-email-notify.php:2908
    10331069msgid "Missing email preview token."
    10341070msgstr ""
    10351071
    10361072#. translators: %s: Formatted number of items.
    1037 #: includes/class-customer-email-notify.php:2946
     1073#: includes/class-customer-email-notify.php:2981
    10381074msgid "%s item"
    10391075msgid_plural "%s items"
    … …  
    10411077msgstr[1] ""
    10421078
    1043 #: includes/class-customer-email-notify.php:2956
     1079#: includes/class-customer-email-notify.php:2991
    10441080msgid "First page"
    10451081msgstr ""
    10461082
    1047 #: includes/class-customer-email-notify.php:2957
     1083#: includes/class-customer-email-notify.php:2992
    10481084msgid "Previous page"
    10491085msgstr ""
    10501086
    1051 #: includes/class-customer-email-notify.php:2961
     1087#: includes/class-customer-email-notify.php:2996
    10521088msgid "Current page"
    10531089msgstr ""
    10541090
    1055 #: includes/class-customer-email-notify.php:2973
     1091#: includes/class-customer-email-notify.php:3008
    10561092msgid "of"
    10571093msgstr ""
    10581094
    1059 #: includes/class-customer-email-notify.php:2978
     1095#: includes/class-customer-email-notify.php:3013
    10601096msgid "Next page"
    10611097msgstr ""
    10621098
    1063 #: includes/class-customer-email-notify.php:2979
     1099#: includes/class-customer-email-notify.php:3014
    10641100msgid "Last page"
    10651101msgstr ""
    10661102
    1067 #: includes/class-customer-email-notify.php:3098
     1103#: includes/class-customer-email-notify.php:3133
    10681104msgid "Recovered campaign content. Only stored recipients and existing jobs can continue. The original audience may be incomplete; review recipient records before creating another campaign."
    10691105msgstr ""
    10701106
    1071 #: includes/class-customer-email-notify.php:3289
     1107#: includes/class-customer-email-notify.php:3324
    10721108msgid "Missing campaign ID."
    10731109msgstr ""
    10741110
    1075 #: includes/class-customer-email-notify.php:3358
     1111#: includes/class-customer-email-notify.php:3393
    10761112msgid "Missing campaign content needed for rescheduling."
    10771113msgstr ""
    10781114
    1079 #: includes/class-customer-email-notify.php:3575
    1080 #: includes/class-customer-email-notify.php:3576
     1115#: includes/class-customer-email-notify.php:3611
     1116#: includes/class-customer-email-notify.php:3612
    10811117#: includes/trait-campaign-controls.php:173
    10821118msgid "Paused"
    10831119msgstr ""
    10841120
    1085 #: includes/class-customer-email-notify.php:3577
     1121#: includes/class-customer-email-notify.php:3613
    10861122msgid "Waiting for site limit"
    10871123msgstr ""
    10881124
    1089 #: includes/class-customer-email-notify.php:3578
     1125#: includes/class-customer-email-notify.php:3614
    10901126msgid "Saving recipient snapshot"
    10911127msgstr ""
    10921128
    1093 #: includes/class-customer-email-notify.php:3579
     1129#: includes/class-customer-email-notify.php:3615
    10941130msgid "Needs attention"
    10951131msgstr ""
    10961132
    1097 #: includes/class-customer-email-notify.php:3580
     1133#: includes/class-customer-email-notify.php:3616
    10981134msgid "Previewed"
    10991135msgstr ""
    11001136
    1101 #: includes/class-customer-email-notify.php:3581
     1137#: includes/class-customer-email-notify.php:3617
    11021138msgid "Testing"
    11031139msgstr ""
    11041140
    1105 #: includes/class-customer-email-notify.php:3582
     1141#: includes/class-customer-email-notify.php:3618
    11061142msgid "Test handed to mail system"
    11071143msgstr ""
    11081144
    1109 #: includes/class-customer-email-notify.php:3583
     1145#: includes/class-customer-email-notify.php:3619
    11101146msgid "Test failed"
    11111147msgstr ""
    11121148
    1113 #: includes/class-customer-email-notify.php:3584
     1149#: includes/class-customer-email-notify.php:3620
    11141150msgid "Queueing"
    11151151msgstr ""
    11161152
    1117 #: includes/class-customer-email-notify.php:3585
     1153#: includes/class-customer-email-notify.php:3621
    11181154msgid "Preparing in background"
    11191155msgstr ""
    11201156
    1121 #: includes/class-customer-email-notify.php:3586
     1157#: includes/class-customer-email-notify.php:3622
    11221158msgid "Preparing"
    11231159msgstr ""
    11241160
    1125 #: includes/class-customer-email-notify.php:3587
     1161#: includes/class-customer-email-notify.php:3623
    11261162#: includes/trait-campaign-controls.php:173
    11271163msgid "Queued"
    11281164msgstr ""
    11291165
    1130 #: includes/class-customer-email-notify.php:3588
     1166#: includes/class-customer-email-notify.php:3624
    11311167#: includes/trait-campaign-controls.php:173
    11321168msgid "Queue failed"
    11331169msgstr ""
    11341170
    1135 #: includes/class-customer-email-notify.php:3589
     1171#: includes/class-customer-email-notify.php:3625
    11361172msgid "Scheduling"
    11371173msgstr ""
    11381174
    1139 #: includes/class-customer-email-notify.php:3590
     1175#: includes/class-customer-email-notify.php:3626
    11401176#: includes/trait-campaign-controls.php:173
    11411177msgid "Scheduled"
    11421178msgstr ""
    11431179
    1144 #: includes/class-customer-email-notify.php:3591
     1180#: includes/class-customer-email-notify.php:3627
    11451181#: includes/trait-campaign-controls.php:173
    11461182msgid "Schedule failed"
    11471183msgstr ""
    11481184
    1149 #: includes/class-customer-email-notify.php:3592
    1150 #: includes/class-customer-email-notify.php:4627
     1185#: includes/class-customer-email-notify.php:3628
     1186#: includes/class-customer-email-notify.php:4674
    11511187#: includes/trait-campaign-controls.php:140
    11521188#: includes/trait-campaign-controls.php:173
    … …  
    11541190msgstr ""
    11551191
    1156 #: includes/class-customer-email-notify.php:3593
    1157 #: includes/class-customer-email-notify.php:4629
     1192#: includes/class-customer-email-notify.php:3629
     1193#: includes/class-customer-email-notify.php:4676
    11581194msgid "Completed"
    11591195msgstr ""
    11601196
    1161 #: includes/class-customer-email-notify.php:3594
     1197#: includes/class-customer-email-notify.php:3630
    11621198msgid "Completed with issues"
    11631199msgstr ""
    11641200
    1165 #: includes/class-customer-email-notify.php:3595
    1166 #: includes/trait-campaign-audiences.php:152
     1201#: includes/class-customer-email-notify.php:3631
     1202#: includes/trait-campaign-audiences.php:161
    11671203#: includes/trait-campaign-controls.php:138
    11681204#: includes/trait-campaign-controls.php:173
    … …  
    11701206msgstr ""
    11711207
    1172 #: includes/class-customer-email-notify.php:3596
    1173 #: includes/class-customer-email-notify.php:4632
     1208#: includes/class-customer-email-notify.php:3632
     1209#: includes/class-customer-email-notify.php:4679
    11741210#: includes/trait-campaign-controls.php:141
    11751211#: includes/trait-campaign-controls.php:173
    … …  
    11771213msgstr ""
    11781214
    1179 #: includes/class-customer-email-notify.php:3597
     1215#: includes/class-customer-email-notify.php:3633
    11801216msgid "Skipped"
    11811217msgstr ""
    11821218
    1183 #: includes/class-customer-email-notify.php:3598
     1219#: includes/class-customer-email-notify.php:3634
    11841220msgid "Skipped unsubscribed"
    11851221msgstr ""
    11861222
    1187 #: includes/class-customer-email-notify.php:3599
     1223#: includes/class-customer-email-notify.php:3635
    11881224msgid "Skipped duplicate"
    11891225msgstr ""
    11901226
    1191 #: includes/class-customer-email-notify.php:3601
     1227#: includes/class-customer-email-notify.php:3637
    11921228msgid "Duplicate blocked"
    11931229msgstr ""
    11941230
    1195 #: includes/class-customer-email-notify.php:3602
     1231#: includes/class-customer-email-notify.php:3638
    11961232msgid "No recipients"
    11971233msgstr ""
    11981234
    1199 #: includes/class-customer-email-notify.php:3603
    1200 #: includes/class-customer-email-notify.php:4630
     1235#: includes/class-customer-email-notify.php:3639
     1236#: includes/class-customer-email-notify.php:4677
    12011237#: includes/trait-campaign-controls.php:173
    12021238msgid "Cancelled"
    12031239msgstr ""
    12041240
    1205 #: includes/class-customer-email-notify.php:3604
     1241#: includes/class-customer-email-notify.php:3640
    12061242msgid "Unresolved"
    12071243msgstr ""
    12081244
    1209 #: includes/class-customer-email-notify.php:3605
     1245#: includes/class-customer-email-notify.php:3641
    12101246msgid "Outcome unknown"
    12111247msgstr ""
    12121248
    12131249#. translators: 1: Final number of recipients. 2: Excluded recipients. 3: Initial recipients.
    1214 #: includes/class-customer-email-notify.php:3640
     1250#: includes/class-customer-email-notify.php:3676
    12151251msgid "%1$d recipient(s), %2$d excluded from %3$d"
    12161252msgstr ""
    12171253
    12181254#. translators: %d: Number of recipients.
    1219 #: includes/class-customer-email-notify.php:3648
     1255#: includes/class-customer-email-notify.php:3684
    12201256msgid "%d recipient(s)"
    12211257msgstr ""
    12221258
    1223 #: includes/class-customer-email-notify.php:3654
     1259#: includes/class-customer-email-notify.php:3690
    12241260msgid "Recipient location estimate / UTC fallback"
    12251261msgstr ""
    12261262
    1227 #: includes/class-customer-email-notify.php:3656
     1263#: includes/class-customer-email-notify.php:3692
    12281264msgid "Test email only"
    12291265msgstr ""
    12301266
    12311267#. translators: %s: Scheduled local date and time.
    1232 #: includes/class-customer-email-notify.php:3665
     1268#: includes/class-customer-email-notify.php:3701
    12331269msgid "Scheduled: %s by recipient timezone"
    12341270msgstr ""
    12351271
    12361272#. translators: %s: Scheduled local date and time.
    1237 #: includes/class-customer-email-notify.php:3666
     1273#: includes/class-customer-email-notify.php:3702
    12381274msgid "selected time"
    12391275msgstr ""
    12401276
    1241 #: includes/class-customer-email-notify.php:3670
    1242 #: includes/trait-campaign-authoring.php:222
     1277#: includes/class-customer-email-notify.php:3706
     1278#: includes/trait-campaign-authoring.php:232
    12431279msgid "After confirmation"
    12441280msgstr ""
    12451281
    1246 #: includes/class-customer-email-notify.php:3729
     1282#: includes/class-customer-email-notify.php:3765
    12471283msgid "No activity yet"
    12481284msgstr ""
    12491285
    1250 #: includes/class-customer-email-notify.php:4254
     1286#: includes/class-customer-email-notify.php:4290
     1287msgid "Subscribed email addresses"
     1288msgstr ""
     1289
     1290#: includes/class-customer-email-notify.php:4291
     1291#: includes/trait-campaign-entrypoints.php:72
     1292msgid "Selected orders"
     1293msgstr ""
     1294
     1295#: includes/class-customer-email-notify.php:4293
    12511296msgid "WooCommerce customers"
    12521297msgstr ""
    12531298
    1254 #: includes/class-customer-email-notify.php:4255
     1299#: includes/class-customer-email-notify.php:4294
    12551300msgid "All registered users"
    12561301msgstr ""
    12571302
    1258 #: includes/class-customer-email-notify.php:4256
     1303#: includes/class-customer-email-notify.php:4295
    12591304msgid "Manual email list"
    12601305msgstr ""
    12611306
    1262 #: includes/class-customer-email-notify.php:4271
    12631307#: includes/class-customer-email-notify.php:4310
     1308#: includes/class-customer-email-notify.php:4351
    12641309msgid "Please enter a list of valid email addresses."
    12651310msgstr ""
    12661311
    1267 #: includes/class-customer-email-notify.php:4276
     1312#: includes/class-customer-email-notify.php:4315
    12681313msgid "The recipient list contains an invalid email address. Correct the list before continuing."
    12691314msgstr ""
    12701315
    1271 #: includes/class-customer-email-notify.php:4294
     1316#: includes/class-customer-email-notify.php:4335
    12721317msgid "Please choose valid user roles."
    12731318msgstr ""
    12741319
    1275 #: includes/class-customer-email-notify.php:4299
     1320#: includes/class-customer-email-notify.php:4340
    12761321msgid "A selected user role is unavailable. Review the recipient selection before continuing."
    12771322msgstr ""
    12781323
    1279 #: includes/class-customer-email-notify.php:4306
     1324#: includes/class-customer-email-notify.php:4347
    12801325msgid "Please enter at least one recipient email address."
    12811326msgstr ""
    12821327
    1283 #: includes/class-customer-email-notify.php:4331
     1328#: includes/class-customer-email-notify.php:4372
    12841329msgid "Previously unsubscribed email addresses"
    12851330msgstr ""
    12861331
    1287 #: includes/class-customer-email-notify.php:4610
     1332#: includes/class-customer-email-notify.php:4657
    12881333msgid "Do not want to receive these campaign emails?"
    12891334msgstr ""
    12901335
    1291 #: includes/class-customer-email-notify.php:4612
     1336#: includes/class-customer-email-notify.php:4659
     1337#: includes/trait-campaign-subscriptions.php:125
    12921338msgid "Unsubscribe"
    12931339msgstr ""
    12941340
    1295 #: includes/class-customer-email-notify.php:4626
     1341#: includes/class-customer-email-notify.php:4673
    12961342msgid "Pending payment"
    12971343msgstr ""
    12981344
    1299 #: includes/class-customer-email-notify.php:4628
     1345#: includes/class-customer-email-notify.php:4675
    13001346msgid "On hold"
    13011347msgstr ""
    13021348
    1303 #: includes/class-customer-email-notify.php:4631
     1349#: includes/class-customer-email-notify.php:4678
    13041350msgid "Refunded"
    13051351msgstr ""
    13061352
    13071353#. translators: %s: Comma-separated order status labels.
    1308 #: includes/class-customer-email-notify.php:4693
     1354#: includes/class-customer-email-notify.php:4740
    13091355msgid "Order statuses: %s"
    13101356msgstr ""
    13111357
    13121358#. translators: %s: Comma-separated product names.
    1313 #: includes/class-customer-email-notify.php:4702
     1359#: includes/class-customer-email-notify.php:4749
    13141360msgid "Purchased products: %s"
    13151361msgstr ""
    13161362
    13171363#. translators: %d: Number of excluded email addresses.
    1318 #: includes/class-customer-email-notify.php:4711
     1364#: includes/class-customer-email-notify.php:4758
    13191365msgid "Manual emails: %d address"
    13201366msgid_plural "Manual emails: %d addresses"
    … …  
    13221368msgstr[1] ""
    13231369
    1324 #: includes/class-customer-email-notify.php:4884
     1370#: includes/class-customer-email-notify.php:4931
    13251371msgid "Missing required email content. Repair campaign data before retrying."
    13261372msgstr ""
    13271373
    1328 #: includes/class-customer-email-notify.php:4892
     1374#: includes/class-customer-email-notify.php:4941
     1375msgid "No active subscription before send."
     1376msgstr ""
     1377
     1378#: includes/class-customer-email-notify.php:4949
    13291379msgid "Recipient unsubscribed before send."
    13301380msgstr ""
    13311381
    1332 #: includes/class-customer-email-notify.php:4899
     1382#: includes/class-customer-email-notify.php:4957
    13331383msgid "Mail system accepted the request; inbox delivery is not verified."
    13341384msgstr ""
    13351385
    1336 #: includes/class-customer-email-notify.php:4899
     1386#: includes/class-customer-email-notify.php:4957
    13371387msgid "Mail transport returned no confirmation. Check the mail provider before sending again."
    13381388msgstr ""
    13391389
    1340 #: includes/class-customer-email-notify.php:4919
     1390#: includes/class-customer-email-notify.php:4977
    13411391msgid "Preserved a pre-upgrade delivery claim. Check the mail provider for unknown outcomes."
    13421392msgstr ""
    13431393
    13441394#. translators: %d: Product ID.
    1345 #: includes/class-customer-email-notify.php:5131
     1395#: includes/class-customer-email-notify.php:5192
    13461396msgid "Product #%d"
    13471397msgstr ""
    13481398
    1349 #: includes/class-customer-email-notify.php:5155
     1399#: includes/class-customer-email-notify.php:5216
    13501400msgid "WooCommerce email template is unavailable."
    13511401msgstr ""
    13521402
    1353 #: includes/class-customer-email-notify.php:5241
     1403#: includes/class-customer-email-notify.php:5303
    13541404msgid "WooCommerce mailer is unavailable."
    13551405msgstr ""
    13561406
    1357 #: includes/class-customer-email-notify.php:5246
     1407#: includes/class-customer-email-notify.php:5308
     1408msgid "The sample was erased before test mail handoff."
     1409msgstr ""
     1410
     1411#: includes/class-customer-email-notify.php:5318
     1412msgid "No active subscription before mail handoff."
     1413msgstr ""
     1414
     1415#: includes/class-customer-email-notify.php:5323
    13581416msgid "Recipient unsubscribed before mail handoff."
    13591417msgstr ""
    13601418
    13611419#: includes/trait-campaign-audiences.php:21
    1362 msgid "Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country and campaign history; no condition has been ignored."
    1363 msgstr ""
    1364 
    1365 #: includes/trait-campaign-audiences.php:127
     1420msgid "Invalid or inapplicable audience conditions. Review source, dates, statuses, category, country, purchase history and campaign history; no condition has been ignored."
     1421msgstr ""
     1422
     1423#: includes/trait-campaign-audiences.php:134
    13661424msgid "Unavailable selection; remove or replace:"
    13671425msgstr ""
    13681426
    1369 #: includes/trait-campaign-audiences.php:133
     1427#: includes/trait-campaign-audiences.php:142
    13701428msgid "Saved conditions are invalid or unavailable. They remain selected; correct or explicitly remove them before previewing."
    13711429msgstr ""
    13721430
    1373 #: includes/trait-campaign-audiences.php:134
     1431#: includes/trait-campaign-audiences.php:143
    13741432msgid "Audience conditions"
    13751433msgstr ""
    13761434
    1377 #: includes/trait-campaign-audiences.php:135
     1435#: includes/trait-campaign-audiences.php:144
    13781436msgid "Order created from (store timezone)"
    13791437msgstr ""
    13801438
    1381 #: includes/trait-campaign-audiences.php:135
     1439#: includes/trait-campaign-audiences.php:144
    13821440msgid "Through date (inclusive)"
    13831441msgstr ""
    13841442
    1385 #: includes/trait-campaign-audiences.php:135
     1443#: includes/trait-campaign-audiences.php:144
    13861444msgid "Or last N × 24 hours"
    13871445msgstr ""
    13881446
    1389 #: includes/trait-campaign-audiences.php:136
    1390 msgid "Matching order statuses (default processing/completed)"
    1391 msgstr ""
    1392 
    1393 #: includes/trait-campaign-audiences.php:139
     1447#: includes/trait-campaign-audiences.php:145
     1448msgid "Matching order statuses (selected orders: all; other sources: processing/completed)"
     1449msgstr ""
     1450
     1451#: includes/trait-campaign-audiences.php:148
    13941452msgid "Categories (includes all descendants)"
    13951453msgstr ""
    13961454
    1397 #: includes/trait-campaign-audiences.php:146
    1398 #: includes/trait-campaign-audiences.php:158
     1455#: includes/trait-campaign-audiences.php:155
     1456#: includes/trait-campaign-audiences.php:167
    13991457msgid "Subscription status"
    14001458msgstr ""
    14011459
    1402 #: includes/trait-campaign-audiences.php:147
     1460#: includes/trait-campaign-audiences.php:156
    14031461msgid "Any (suppression still applies)"
    14041462msgstr ""
    14051463
    1406 #: includes/trait-campaign-audiences.php:147
    1407 #: includes/trait-campaign-privacy.php:127
     1464#: includes/trait-campaign-audiences.php:156
     1465#: includes/trait-campaign-contact-history.php:66
     1466#: includes/trait-campaign-privacy.php:128
     1467#: includes/trait-campaign-subscriptions.php:118
    14081468msgid "Subscribed"
    14091469msgstr ""
    14101470
    1411 #: includes/trait-campaign-audiences.php:147
     1471#: includes/trait-campaign-audiences.php:156
    14121472msgid "No confirmation recorded"
    14131473msgstr ""
    14141474
    1415 #: includes/trait-campaign-audiences.php:147
     1475#: includes/trait-campaign-audiences.php:156
    14161476msgid "Unsubscribed (excluded from sending)"
    14171477msgstr ""
    14181478
    1419 #: includes/trait-campaign-audiences.php:148
     1479#: includes/trait-campaign-audiences.php:157
    14201480msgid "Campaign history"
    14211481msgstr ""
    14221482
    1423 #: includes/trait-campaign-audiences.php:151
     1483#: includes/trait-campaign-audiences.php:160
    14241484msgid "History outcome"
    14251485msgstr ""
    14261486
    1427 #: includes/trait-campaign-audiences.php:152
     1487#: includes/trait-campaign-audiences.php:161
    14281488msgid "Known unsent terminal outcome"
    14291489msgstr ""
    14301490
    1431 #: includes/trait-campaign-audiences.php:153
     1491#: includes/trait-campaign-audiences.php:162
    14321492msgid "Durable opt-outs always apply. Orders and accounts do not establish opt-in."
    14331493msgstr ""
    14341494
    1435 #: includes/trait-campaign-audiences.php:153
     1495#: includes/trait-campaign-audiences.php:162
    14361496msgid "How audience conditions match"
    14371497msgstr ""
    14381498
    1439 #: includes/trait-campaign-audiences.php:153
     1499#: includes/trait-campaign-audiences.php:162
    14401500msgid "Order filters use order creation time in the store timezone and match on one order, including product/variation, status and billing country. Categories include descendants and use current product categories. Registered users use profile billing country; manual lists have no country. Missing data fails the condition. History uses handoff only: unknown, pending and missing history never qualify as known unsent. Existing exclusions and durable opt-outs apply last."
    14411501msgstr ""
    14421502
    1443 #: includes/trait-campaign-audiences.php:158
     1503#: includes/trait-campaign-audiences.php:167
     1504msgid "Store-wide purchase frequency"
     1505msgstr ""
     1506
     1507#: includes/trait-campaign-audiences.php:167
     1508msgid "Days without a valid store-wide purchase"
     1509msgstr ""
     1510
     1511#: includes/trait-campaign-audiences.php:167
    14441512msgid "Order created from"
    14451513msgstr ""
    14461514
    1447 #: includes/trait-campaign-audiences.php:158
     1515#: includes/trait-campaign-audiences.php:167
    14481516msgid "Through date"
    14491517msgstr ""
    14501518
    1451 #: includes/trait-campaign-audiences.php:158
     1519#: includes/trait-campaign-audiences.php:167
    14521520msgid "Last N days"
    14531521msgstr ""
    14541522
    1455 #: includes/trait-campaign-audiences.php:158
     1523#: includes/trait-campaign-audiences.php:167
    14561524msgid "History campaign"
    14571525msgstr ""
    14581526
    1459 #: includes/trait-campaign-audiences.php:158
     1527#: includes/trait-campaign-audiences.php:167
    14601528msgid "Handoff condition"
    14611529msgstr ""
    14621530
    1463 #: includes/trait-campaign-audiences.php:158
     1531#: includes/trait-campaign-audiences.php:167
    14641532msgid "Categories including descendants"
    14651533msgstr ""
    14661534
    1467 #: includes/trait-campaign-audiences.php:158
     1535#: includes/trait-campaign-audiences.php:167
    14681536msgid "Matching order statuses"
    14691537msgstr ""
    14701538
    1471 #: includes/trait-campaign-audiences.php:159
     1539#: includes/trait-campaign-audiences.php:168
     1540#: includes/trait-campaign-authoring.php:238
     1541msgid "Saved audience conditions"
     1542msgstr ""
     1543
     1544#: includes/trait-campaign-audiences.php:171
     1545#: includes/trait-campaign-purchase-history.php:54
     1546msgid "First-time buyer (one valid order)"
     1547msgstr ""
     1548
     1549#: includes/trait-campaign-audiences.php:171
     1550#: includes/trait-campaign-purchase-history.php:54
     1551msgid "Repeat buyer (at least two valid orders)"
     1552msgstr ""
     1553
     1554#: includes/trait-campaign-audiences.php:181
     1555msgid "Inspect an address against current criteria"
     1556msgstr ""
     1557
     1558#: includes/trait-campaign-audiences.php:181
     1559msgid "Inspect"
     1560msgstr ""
     1561
     1562#: includes/trait-campaign-audiences.php:187
     1563msgid "Not in the selected source or product purchasers."
     1564msgstr ""
     1565
     1566#: includes/trait-campaign-audiences.php:188
     1567msgid "No single order matches all order conditions, or billing country is missing/different."
     1568msgstr ""
     1569
     1570#: includes/trait-campaign-audiences.php:189
     1571msgid "Store-wide purchase history, subscription status or known campaign handoff condition does not match."
     1572msgstr ""
     1573
     1574#: includes/trait-campaign-audiences.php:190
     1575msgid "Matched source and all conditions; removed by an explicit exclusion."
     1576msgstr ""
     1577
     1578#: includes/trait-campaign-audiences.php:191
     1579msgid "Included: matches source and every applicable condition; no exclusion matched."
     1580msgstr ""
     1581
     1582#: includes/trait-campaign-audiences.php:192
     1583msgid "Excluded: durable unsubscribe/suppression."
     1584msgstr ""
     1585
     1586#: includes/trait-campaign-authoring.php:57
     1587msgid "Enter an audience name."
     1588msgstr ""
     1589
     1590#: includes/trait-campaign-authoring.php:60
     1591msgid "Audience could not be saved."
     1592msgstr ""
     1593
     1594#: includes/trait-campaign-authoring.php:86
     1595msgid "This draft changed or was deleted. Open it and create a new preview."
     1596msgstr ""
     1597
     1598#: includes/trait-campaign-authoring.php:107
     1599msgid "Campaign content unavailable."
     1600msgstr ""
     1601
     1602#: includes/trait-campaign-authoring.php:120
     1603msgid "Duplicate as new draft"
     1604msgstr ""
     1605
     1606#: includes/trait-campaign-authoring.php:128
     1607msgid "Drafts"
     1608msgstr ""
     1609
     1610#: includes/trait-campaign-authoring.php:128
     1611msgid "Drafts do not expire and never create delivery jobs."
     1612msgstr ""
     1613
     1614#: includes/trait-campaign-authoring.php:130
     1615msgid "No saved drafts yet."
     1616msgstr ""
     1617
     1618#: includes/trait-campaign-authoring.php:133
     1619msgid "Delete draft"
     1620msgstr ""
     1621
     1622#: includes/trait-campaign-authoring.php:136
     1623#: includes/trait-campaign-authoring.php:150
     1624msgid "Saved audience criteria"
     1625msgstr ""
     1626
     1627#: includes/trait-campaign-authoring.php:136
     1628msgid "Reusable rules, not a frozen recipient list. A new preview is required when reused."
     1629msgstr ""
     1630
     1631#: includes/trait-campaign-authoring.php:138
     1632msgid "No saved audiences yet."
     1633msgstr ""
     1634
     1635#: includes/trait-campaign-authoring.php:141
     1636msgid "Delete audience"
     1637msgstr ""
     1638
     1639#: includes/trait-campaign-authoring.php:147
     1640msgid "Draft name (internal only)"
     1641msgstr ""
     1642
     1643#: includes/trait-campaign-authoring.php:147
     1644msgid "Save draft"
     1645msgstr ""
     1646
     1647#: includes/trait-campaign-authoring.php:147
     1648msgid "Drafts create no delivery jobs. Changes require a new preview."
     1649msgstr ""
     1650
     1651#: includes/trait-campaign-authoring.php:154
     1652msgid "Open Library"
     1653msgstr ""
     1654
     1655#: includes/trait-campaign-authoring.php:154
     1656msgid "Opening saved criteria replaces this form. Save your draft first to keep current edits."
     1657msgstr ""
     1658
     1659#: includes/trait-campaign-authoring.php:154
     1660msgid "Audience name"
     1661msgstr ""
     1662
     1663#: includes/trait-campaign-authoring.php:154
     1664msgid "Save audience criteria"
     1665msgstr ""
     1666
     1667#: includes/trait-campaign-authoring.php:157
     1668msgid "Insert editable content template"
     1669msgstr ""
     1670
     1671#: includes/trait-campaign-authoring.php:159
     1672msgid "Product or documentation update"
     1673msgstr ""
     1674
     1675#: includes/trait-campaign-authoring.php:159
     1676msgid "An update for you"
     1677msgstr ""
     1678
     1679#: includes/trait-campaign-authoring.php:159
     1680msgid "<p>Hello {customer_name},</p><p>We have updated {product_name}. Describe what changed, when it applies, and add the documentation link and any action needed. Contact our support team if you need help.</p>"
     1681msgstr ""
     1682
     1683#: includes/trait-campaign-authoring.php:160
     1684msgid "After-purchase guide"
     1685msgstr ""
     1686
     1687#: includes/trait-campaign-authoring.php:160
     1688msgid "Getting started"
     1689msgstr ""
     1690
     1691#: includes/trait-campaign-authoring.php:160
     1692msgid "<p>Hello {customer_name},</p><p>Thank you for your purchase of {ordered_products} (order {order_number}). Add setup or care steps, when to follow them, and a support contact here.</p>"
     1693msgstr ""
     1694
     1695#: includes/trait-campaign-authoring.php:161
     1696msgid "Service or order change"
     1697msgstr ""
     1698
     1699#: includes/trait-campaign-authoring.php:161
     1700msgid "Service update"
     1701msgstr ""
     1702
     1703#: includes/trait-campaign-authoring.php:161
     1704msgid "<p>Hello {customer_name},</p><p>An update about order {order_number}: describe the change and the new delivery date or effective time. Explain any action needed and add a support contact here.</p>"
     1705msgstr ""
     1706
     1707#: includes/trait-campaign-authoring.php:197
     1708msgid "At least one recipient has a past, nonexistent or repeated local time. Choose a later, unambiguous time and preview again. No date was shifted."
     1709msgstr ""
     1710
     1711#: includes/trait-campaign-authoring.php:220
     1712msgid "Preview width"
     1713msgstr ""
     1714
     1715#: includes/trait-campaign-authoring.php:221
     1716msgid "Desktop"
     1717msgstr ""
     1718
     1719#: includes/trait-campaign-authoring.php:221
     1720msgid "Mobile"
     1721msgstr ""
     1722
     1723#: includes/trait-campaign-authoring.php:227
     1724msgid "Audience changed since preview. Review this updated preview and confirm again."
     1725msgstr ""
     1726
    14721727#: includes/trait-campaign-authoring.php:228
    1473 msgid "Saved audience conditions"
    1474 msgstr ""
    1475 
    1476 #: includes/trait-campaign-audiences.php:171
    1477 msgid "Inspect an address against current criteria"
    1478 msgstr ""
    1479 
    1480 #: includes/trait-campaign-audiences.php:171
    1481 msgid "Inspect"
    1482 msgstr ""
    1483 
    1484 #: includes/trait-campaign-audiences.php:177
    1485 msgid "Not in the selected source or product purchasers."
    1486 msgstr ""
    1487 
    1488 #: includes/trait-campaign-audiences.php:178
    1489 msgid "No single order matches all order conditions, or billing country is missing/different."
    1490 msgstr ""
    1491 
    1492 #: includes/trait-campaign-audiences.php:179
    1493 msgid "Subscription status or known campaign handoff condition does not match."
    1494 msgstr ""
    1495 
    1496 #: includes/trait-campaign-audiences.php:180
    1497 msgid "Matched source and all conditions; removed by an explicit exclusion."
    1498 msgstr ""
    1499 
    1500 #: includes/trait-campaign-audiences.php:181
    1501 msgid "Included: matches source and every applicable condition; no exclusion matched."
    1502 msgstr ""
    1503 
    1504 #: includes/trait-campaign-audiences.php:182
    1505 msgid "Excluded: durable unsubscribe/suppression."
    1506 msgstr ""
    1507 
    1508 #: includes/trait-campaign-authoring.php:53
    1509 msgid "Enter an audience name."
    1510 msgstr ""
    1511 
    1512 #: includes/trait-campaign-authoring.php:56
    1513 msgid "Audience could not be saved."
    1514 msgstr ""
    1515 
    1516 #: includes/trait-campaign-authoring.php:76
    1517 msgid "This draft changed or was deleted. Open it and create a new preview."
    1518 msgstr ""
    1519 
    1520 #: includes/trait-campaign-authoring.php:97
    1521 msgid "Campaign content unavailable."
    1522 msgstr ""
    1523 
    1524 #: includes/trait-campaign-authoring.php:110
    1525 msgid "Duplicate as new draft"
    1526 msgstr ""
    1527 
    1528 #: includes/trait-campaign-authoring.php:118
    1529 msgid "Drafts"
    1530 msgstr ""
    1531 
    1532 #: includes/trait-campaign-authoring.php:118
    1533 msgid "Drafts do not expire and never create delivery jobs."
    1534 msgstr ""
    1535 
    1536 #: includes/trait-campaign-authoring.php:120
    1537 msgid "No saved drafts yet."
    1538 msgstr ""
    1539 
    1540 #: includes/trait-campaign-authoring.php:123
    1541 msgid "Delete draft"
    1542 msgstr ""
    1543 
    1544 #: includes/trait-campaign-authoring.php:126
    1545 #: includes/trait-campaign-authoring.php:140
    1546 msgid "Saved audience criteria"
    1547 msgstr ""
    1548 
    1549 #: includes/trait-campaign-authoring.php:126
    1550 msgid "Reusable rules, not a frozen recipient list. A new preview is required when reused."
    1551 msgstr ""
    1552 
    1553 #: includes/trait-campaign-authoring.php:128
    1554 msgid "No saved audiences yet."
    1555 msgstr ""
    1556 
    1557 #: includes/trait-campaign-authoring.php:131
    1558 msgid "Delete audience"
    1559 msgstr ""
    1560 
    1561 #: includes/trait-campaign-authoring.php:137
    1562 msgid "Draft name (internal only)"
    1563 msgstr ""
    1564 
    1565 #: includes/trait-campaign-authoring.php:137
    1566 msgid "Save draft"
    1567 msgstr ""
    1568 
    1569 #: includes/trait-campaign-authoring.php:137
    1570 msgid "Drafts create no delivery jobs. Changes require a new preview."
    1571 msgstr ""
    1572 
    1573 #: includes/trait-campaign-authoring.php:144
    1574 msgid "Open Library"
    1575 msgstr ""
    1576 
    1577 #: includes/trait-campaign-authoring.php:144
    1578 msgid "Opening saved criteria replaces this form. Save your draft first to keep current edits."
    1579 msgstr ""
    1580 
    1581 #: includes/trait-campaign-authoring.php:144
    1582 msgid "Audience name"
    1583 msgstr ""
    1584 
    1585 #: includes/trait-campaign-authoring.php:144
    1586 msgid "Save audience criteria"
    1587 msgstr ""
    1588 
    1589 #: includes/trait-campaign-authoring.php:147
    1590 msgid "Insert editable content template"
    1591 msgstr ""
    1592 
    1593 #: includes/trait-campaign-authoring.php:149
    1594 msgid "Product or documentation update"
    1595 msgstr ""
    1596 
    1597 #: includes/trait-campaign-authoring.php:149
    1598 msgid "An update for you"
    1599 msgstr ""
    1600 
    1601 #: includes/trait-campaign-authoring.php:149
    1602 msgid "<p>Hello {customer_name},</p><p>We have updated {product_name}. Add the details and documentation link here.</p>"
    1603 msgstr ""
    1604 
    1605 #: includes/trait-campaign-authoring.php:150
    1606 msgid "After-purchase guide"
    1607 msgstr ""
    1608 
    1609 #: includes/trait-campaign-authoring.php:150
    1610 msgid "Getting started"
    1611 msgstr ""
    1612 
    1613 #: includes/trait-campaign-authoring.php:150
    1614 msgid "<p>Hello {customer_name},</p><p>Thank you for your purchase. Add setup steps and support details here.</p>"
    1615 msgstr ""
    1616 
    1617 #: includes/trait-campaign-authoring.php:151
    1618 msgid "Service or order change"
    1619 msgstr ""
    1620 
    1621 #: includes/trait-campaign-authoring.php:151
    1622 msgid "Service update"
    1623 msgstr ""
    1624 
    1625 #: includes/trait-campaign-authoring.php:151
    1626 msgid "<p>Hello {customer_name},</p><p>Add the change, effective date and any action needed here.</p>"
    1627 msgstr ""
    1628 
    1629 #: includes/trait-campaign-authoring.php:187
    1630 msgid "At least one recipient has a past, nonexistent or repeated local time. Choose a later, unambiguous time and preview again. No date was shifted."
    1631 msgstr ""
    1632 
    1633 #: includes/trait-campaign-authoring.php:210
    1634 msgid "Preview width"
    1635 msgstr ""
    1636 
    1637 #: includes/trait-campaign-authoring.php:211
    1638 msgid "Desktop"
    1639 msgstr ""
    1640 
    1641 #: includes/trait-campaign-authoring.php:211
    1642 msgid "Mobile"
    1643 msgstr ""
    1644 
    1645 #: includes/trait-campaign-authoring.php:217
    1646 msgid "Audience changed since preview. Review this updated preview and confirm again."
    1647 msgstr ""
    1648 
    1649 #: includes/trait-campaign-authoring.php:218
    16501728msgid "Recipients and schedule to confirm"
    16511729msgstr ""
    16521730
    1653 #: includes/trait-campaign-authoring.php:218
     1731#: includes/trait-campaign-authoring.php:228
    16541732msgid "Selected"
    16551733msgstr ""
    16561734
    1657 #: includes/trait-campaign-authoring.php:219
     1735#: includes/trait-campaign-authoring.php:229
    16581736msgid "Removed by order conditions"
    16591737msgstr ""
    16601738
    1661 #: includes/trait-campaign-authoring.php:219
    1662 msgid "Removed by subscription or campaign history"
    1663 msgstr ""
    1664 
    1665 #: includes/trait-campaign-authoring.php:219
     1739#: includes/trait-campaign-authoring.php:229
     1740msgid "Removed by purchase history, subscription or campaign history"
     1741msgstr ""
     1742
     1743#: includes/trait-campaign-authoring.php:229
    16661744msgid "Removed by exclusions or suppression"
    16671745msgstr ""
    16681746
    1669 #: includes/trait-campaign-authoring.php:222
     1747#: includes/trait-campaign-authoring.php:232
    16701748msgid "One time in store timezone"
    16711749msgstr ""
    16721750
    1673 #: includes/trait-campaign-authoring.php:222
     1751#: includes/trait-campaign-authoring.php:232
    16741752msgid "Legacy schedule"
    16751753msgstr ""
    16761754
    1677 #: includes/trait-campaign-authoring.php:223
     1755#: includes/trait-campaign-authoring.php:233
    16781756msgid "Schedule mode / store timezone"
    16791757msgstr ""
    16801758
    1681 #: includes/trait-campaign-authoring.php:226
     1759#: includes/trait-campaign-authoring.php:236
    16821760msgid "UTC fallback count"
    16831761msgstr ""
    16841762
    1685 #: includes/trait-campaign-authoring.php:226
     1763#: includes/trait-campaign-authoring.php:236
    16861764msgid "Earliest and latest due (UTC)"
    16871765msgstr ""
    16881766
    1689 #: includes/trait-campaign-authoring.php:230
     1767#: includes/trait-campaign-authoring.php:240
    16901768msgid "Inspect an address"
    16911769msgstr ""
    16921770
    1693 #: includes/trait-campaign-authoring.php:237
    1694 #: includes/trait-campaign-authoring.php:244
     1771#: includes/trait-campaign-authoring.php:247
     1772#: includes/trait-campaign-authoring.php:257
    16951773msgid "Personalized email preview"
    16961774msgstr ""
    16971775
    1698 #: includes/trait-campaign-authoring.php:237
     1776#: includes/trait-campaign-authoring.php:247
    16991777msgid "Sample recipient (up to 20)"
    17001778msgstr ""
    17011779
    1702 #: includes/trait-campaign-authoring.php:239
     1780#: includes/trait-campaign-authoring.php:249
    17031781msgid "Show sample"
     1782msgstr ""
     1783
     1784#: includes/trait-campaign-automation-ui.php:17
     1785msgid "Use the automation form."
     1786msgstr ""
     1787
     1788#: includes/trait-campaign-automation-ui.php:19
     1789msgid "Choose a valid automation rule."
     1790msgstr ""
     1791
     1792#: includes/trait-campaign-automation-ui.php:32
     1793msgid "Choose an available order ID for the sample."
     1794msgstr ""
     1795
     1796#: includes/trait-campaign-automation-ui.php:38
     1797msgid "The test email could not be handed off. Check mail settings and try again."
     1798msgstr ""
     1799
     1800#: includes/trait-campaign-automation-ui.php:47
     1801msgid "Choose a valid automation action."
     1802msgstr ""
     1803
     1804#: includes/trait-campaign-automation-ui.php:66
     1805msgid "This automation rule is unavailable."
     1806msgstr ""
     1807
     1808#: includes/trait-campaign-automation-ui.php:67
     1809msgid "Rule saved. New rules stay disabled until you choose Enable. Changes to enabled rules apply to new events only."
     1810msgstr ""
     1811
     1812#: includes/trait-campaign-automation-ui.php:67
     1813msgid "Rule enabled for new Completed events. Historical orders were not queued."
     1814msgstr ""
     1815
     1816#: includes/trait-campaign-automation-ui.php:67
     1817msgid "Rule disabled. Unstarted emails are cancelled; work already in flight may finish."
     1818msgstr ""
     1819
     1820#: includes/trait-campaign-automation-ui.php:67
     1821msgid "Test mail handed to the mail system. No rule was saved or enabled. Inbox delivery is not verified."
     1822msgstr ""
     1823
     1824#: includes/trait-campaign-automation-ui.php:67
     1825msgid "Sample refreshed. No rule was saved or enabled and no delivery job was created."
     1826msgstr ""
     1827
     1828#: includes/trait-campaign-automation-ui.php:80
     1829msgid "Send a helpful guide after an order is completed. Enable uses the saved configuration."
     1830msgstr ""
     1831
     1832#: includes/trait-campaign-automation-ui.php:80
     1833msgid "New rule"
     1834msgstr ""
     1835
     1836#: includes/trait-campaign-automation-ui.php:81
     1837msgid "No rules yet. Build your first guide below, then enable it when you are ready."
     1838msgstr ""
     1839
     1840#: includes/trait-campaign-automation-ui.php:84
     1841msgid "Enabled"
     1842msgstr ""
     1843
     1844#: includes/trait-campaign-automation-ui.php:84
     1845msgid "Disabled"
     1846msgstr ""
     1847
     1848#. translators: %d: Number of hours after the Completed event.
     1849#: includes/trait-campaign-automation-ui.php:84
     1850msgid "Completed → wait %d hours → send one guide"
     1851msgstr ""
     1852
     1853#. translators: %d: Number of hours after the Completed event.
     1854#: includes/trait-campaign-automation-ui.php:84
     1855msgid "Saved configuration"
     1856msgstr ""
     1857
     1858#. translators: %d: Number of hours after the Completed event.
     1859#: includes/trait-campaign-automation-ui.php:84
     1860msgid "All products"
     1861msgstr ""
     1862
     1863#. translators: %d: Number of hours after the Completed event.
     1864#: includes/trait-campaign-automation-ui.php:84
     1865msgid "Categories"
     1866msgstr ""
     1867
     1868#. translators: %d: Number of hours after the Completed event.
     1869#: includes/trait-campaign-automation-ui.php:84
     1870msgid "All categories"
     1871msgstr ""
     1872
     1873#. translators: %d: Number of hours after the Completed event.
     1874#: includes/trait-campaign-automation-ui.php:85
     1875msgid "Edit rule"
     1876msgstr ""
     1877
     1878#. translators: %d: Number of hours after the Completed event.
     1879#: includes/trait-campaign-automation-ui.php:85
     1880msgid "Disable and cancel waiting emails"
     1881msgstr ""
     1882
     1883#. translators: %d: Number of hours after the Completed event.
     1884#: includes/trait-campaign-automation-ui.php:85
     1885msgid "Enable"
     1886msgstr ""
     1887
     1888#: includes/trait-campaign-automation-ui.php:88
     1889msgid "View campaigns and delivery status"
     1890msgstr ""
     1891
     1892#: includes/trait-campaign-automation-ui.php:88
     1893msgid "Look up a contact"
     1894msgstr ""
     1895
     1896#: includes/trait-campaign-automation-ui.php:92
     1897msgid "Rule name (internal only)"
     1898msgstr ""
     1899
     1900#: includes/trait-campaign-automation-ui.php:92
     1901#: includes/trait-campaign-automation-ui.php:119
     1902msgid "Save changes for new events"
     1903msgstr ""
     1904
     1905#: includes/trait-campaign-automation-ui.php:92
     1906#: includes/trait-campaign-automation-ui.php:119
     1907msgid "Save rule"
     1908msgstr ""
     1909
     1910#: includes/trait-campaign-automation-ui.php:92
     1911msgid "Waiting emails keep their confirmed content, order context and due time."
     1912msgstr ""
     1913
     1914#: includes/trait-campaign-automation-ui.php:92
     1915msgid "Save your rule, then explicitly Enable it above. Saving alone never enables a new rule."
     1916msgstr ""
     1917
     1918#: includes/trait-campaign-automation-ui.php:93
     1919msgid "Automation steps"
     1920msgstr ""
     1921
     1922#: includes/trait-campaign-automation-ui.php:94
     1923msgid "1. Conditions"
     1924msgstr ""
     1925
     1926#: includes/trait-campaign-automation-ui.php:94
     1927msgid "3. Timing"
     1928msgstr ""
     1929
     1930#: includes/trait-campaign-automation-ui.php:97
     1931msgid "Conditions"
     1932msgstr ""
     1933
     1934#: includes/trait-campaign-automation-ui.php:97
     1935msgid "Choose which completed orders should receive this guide."
     1936msgstr ""
     1937
     1938#: includes/trait-campaign-automation-ui.php:98
     1939msgid "Trigger: order changes to Completed"
     1940msgstr ""
     1941
     1942#: includes/trait-campaign-automation-ui.php:98
     1943msgid "Only new events after Enable are captured. There is no historical backfill. A customer with several qualifying orders can receive one guide for each order."
     1944msgstr ""
     1945
     1946#: includes/trait-campaign-automation-ui.php:99
     1947msgid "Products or variations (empty means any)"
     1948msgstr ""
     1949
     1950#: includes/trait-campaign-automation-ui.php:100
     1951msgid "Categories including descendants (empty means any)"
     1952msgstr ""
     1953
     1954#: includes/trait-campaign-automation-ui.php:102
     1955msgid "Product and category conditions match the same order. Parent products include their variations; a selected variation matches only that variation."
     1956msgstr ""
     1957
     1958#: includes/trait-campaign-automation-ui.php:103
     1959msgid "Eligibility and subscription"
     1960msgstr ""
     1961
     1962#: includes/trait-campaign-automation-ui.php:103
     1963msgid "An active subscription is required at sending. Unknown is not consent. Unsubscribe and erasure protection always apply. The order must still be Completed, match its saved conditions and keep the same billing email. Cancelled, failed and fully refunded orders are skipped; partial refunds remain eligible while an amount remains."
     1964msgstr ""
     1965
     1966#: includes/trait-campaign-automation-ui.php:106
     1967msgid "Write useful setup or care instructions for the purchased items."
     1968msgstr ""
     1969
     1970#: includes/trait-campaign-automation-ui.php:107
     1971msgid "A guide for order {order_number}"
     1972msgstr ""
     1973
     1974#: includes/trait-campaign-automation-ui.php:107
     1975msgid "Getting started with your purchase"
     1976msgstr ""
     1977
     1978#: includes/trait-campaign-automation-ui.php:108
     1979msgid "<p>Hello {customer_name},</p><p>Thank you for purchasing {ordered_products}.</p><p>Before you start, check the setup instructions included with your order. Keep this email handy and contact our store if you need help.</p><p>Order: {order_number}</p>"
     1980msgstr ""
     1981
     1982#: includes/trait-campaign-automation-ui.php:109
     1983msgid "An unsubscribe link is added automatically. The live preview shows template text; Review uses the exact sample order."
     1984msgstr ""
     1985
     1986#: includes/trait-campaign-automation-ui.php:109
     1987msgid "Personalization variables"
     1988msgstr ""
     1989
     1990#: includes/trait-campaign-automation-ui.php:109
     1991msgid "Use {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products} and {order_url} in the subject, heading or body. Order values come from the triggering order and are frozen when captured. Missing names use Customer. Guest order links are empty; account links require the owning account login."
     1992msgstr ""
     1993
     1994#: includes/trait-campaign-automation-ui.php:112
     1995msgid "Timing"
     1996msgstr ""
     1997
     1998#: includes/trait-campaign-automation-ui.php:112
     1999msgid "Set how long to wait after the order becomes Completed."
     2000msgstr ""
     2001
     2002#: includes/trait-campaign-automation-ui.php:113
     2003msgid "Delay after Completed (hours)"
     2004msgstr ""
     2005
     2006#: includes/trait-campaign-automation-ui.php:113
     2007msgid "Use 0 to queue immediately, or 24 to wait one day. Sending limits and queue delays can make actual delivery later."
     2008msgstr ""
     2009
     2010#: includes/trait-campaign-automation-ui.php:113
     2011msgid "Editing the delay affects new events only. Waiting emails keep their original due time."
     2012msgstr ""
     2013
     2014#: includes/trait-campaign-automation-ui.php:116
     2015msgid "Preview and test"
     2016msgstr ""
     2017
     2018#: includes/trait-campaign-automation-ui.php:116
     2019msgid "Check the personalized email using an order you can access."
     2020msgstr ""
     2021
     2022#: includes/trait-campaign-automation-ui.php:117
     2023msgid "Sample order ID"
     2024msgstr ""
     2025
     2026#: includes/trait-campaign-automation-ui.php:117
     2027msgid "Preview guide"
     2028msgstr ""
     2029
     2030#: includes/trait-campaign-automation-ui.php:118
     2031msgid "Send test email"
     2032msgstr ""
     2033
     2034#: includes/trait-campaign-automation-ui.php:118
     2035msgid "Test mail goes only to this address. Preview and test create no campaign or delivery job, and never save or enable the rule."
     2036msgstr ""
     2037
     2038#: includes/trait-campaign-automation-ui.php:119
     2039msgid "Ready to save?"
     2040msgstr ""
     2041
     2042#: includes/trait-campaign-automation-ui.php:119
     2043msgid "Save the form, then Enable the saved rule above. If it is already enabled, your changes apply to new events only."
     2044msgstr ""
     2045
     2046#: includes/trait-campaign-automation-ui.php:119
     2047msgid "What happens when I disable a rule?"
     2048msgstr ""
     2049
     2050#: includes/trait-campaign-automation-ui.php:119
     2051msgid "New events stop and unstarted emails are cancelled. Work already in flight may finish. Re-enabling never restores cancelled mail. History is preserved."
     2052msgstr ""
     2053
     2054#: includes/trait-campaign-automation-ui.php:128
     2055#: includes/trait-campaign-automation-ui.php:129
     2056#: includes/trait-campaign-automation-ui.php:135
     2057msgid "Personalized guide preview"
     2058msgstr ""
     2059
     2060#: includes/trait-campaign-automation-ui.php:129
     2061msgid "Based on the form at the last refresh."
     2062msgstr ""
     2063
     2064#: includes/trait-campaign-automation-ui.php:129
     2065msgid "Sample"
     2066msgstr ""
     2067
     2068#: includes/trait-campaign-automation-ui.php:135
     2069msgid "The form has changed. Choose Preview guide to refresh this sample before saving or testing."
     2070msgstr ""
     2071
     2072#: includes/trait-campaign-automation-ui.php:135
     2073msgid "Sample recipient"
     2074msgstr ""
     2075
     2076#: includes/trait-campaign-automation-ui.php:136
     2077msgid "Choose a sample order and select Preview guide to see its customer name, products and order details here."
     2078msgstr ""
     2079
     2080#: includes/trait-campaign-automations.php:16
     2081msgid "Review the rule name, products, categories, delay and required email content."
     2082msgstr ""
     2083
     2084#: includes/trait-campaign-automations.php:37
     2085#: includes/trait-campaign-automations.php:48
     2086msgid "Another rule change is in progress. Please try again."
     2087msgstr ""
     2088
     2089#: includes/trait-campaign-automations.php:40
     2090msgid "Rule not found. Open Automations again."
     2091msgstr ""
     2092
     2093#: includes/trait-campaign-automations.php:44
     2094msgid "The rule could not be saved. Please try again after checking storage."
     2095msgstr ""
     2096
     2097#: includes/trait-campaign-automations.php:51
     2098msgid "Rule not found."
     2099msgstr ""
     2100
     2101#: includes/trait-campaign-automations.php:56
     2102msgid "The rule change could not be verified. Please check its current state and try again."
     2103msgstr ""
     2104
     2105#: includes/trait-campaign-automations.php:60
     2106msgid "The rule is disabled and its previous generation cannot send. Some campaign cancellations still need cleanup; open Campaigns again after checking storage."
     2107msgstr ""
     2108
     2109#: includes/trait-campaign-automations.php:77
     2110#: includes/trait-campaign-automations.php:157
     2111msgid "Completed-order guide"
     2112msgstr ""
     2113
     2114#: includes/trait-campaign-automations.php:90
     2115msgid "The triggering order has no valid billing email. No guide was queued."
     2116msgstr ""
     2117
     2118#: includes/trait-campaign-automations.php:144
     2119msgid "Guide rule disabled or this pending email belongs to an earlier enabled generation."
     2120msgstr ""
     2121
     2122#: includes/trait-campaign-automations.php:146
     2123msgid "The triggering order is no longer Completed, has been fully refunded, changed billing email or no longer matches its saved product/category conditions."
     2124msgstr ""
     2125
     2126#: includes/trait-campaign-automations.php:148
     2127msgid "The triggering order or rule could not be checked. No guide was handed off."
     2128msgstr ""
     2129
     2130#: includes/trait-campaign-automations.php:152
     2131msgid "Choose a valid sample order ID."
     2132msgstr ""
     2133
     2134#: includes/trait-campaign-automations.php:154
     2135msgid "Choose an available order that you can access for preview."
     2136msgstr ""
     2137
     2138#: includes/trait-campaign-automations.php:155
     2139msgid "This sample is protected by erasure. Its previous personal context cannot be previewed or tested."
     2140msgstr ""
     2141
     2142#: includes/trait-campaign-automations.php:159
     2143msgid "This sample order does not currently match the rule. Preview only; it will not trigger a guide in this state."
     2144msgstr ""
     2145
     2146#: includes/trait-campaign-automations.php:159
     2147msgid "This sample has no active subscription. A real guide would be skipped unless subscribed at send time."
     2148msgstr ""
     2149
     2150#: includes/trait-campaign-automations.php:159
     2151msgid "This sample matches now. Enabling never sends to historical orders; only a new Completed event can trigger a guide."
     2152msgstr ""
     2153
     2154#: includes/trait-campaign-automations.php:163
     2155msgid "Enter a valid test email address."
     2156msgstr ""
     2157
     2158#: includes/trait-campaign-checkout.php:16
     2159#: includes/trait-campaign-checkout.php:26
     2160msgid "Email me store news and offers. I will confirm my email address before subscribing."
     2161msgstr ""
     2162
     2163#: includes/trait-campaign-contact-history.php:7
     2164msgid "Enter one valid email address to look up its contact history."
     2165msgstr ""
     2166
     2167#: includes/trait-campaign-contact-history.php:19
     2168#: includes/trait-campaign-contact-history.php:24
     2169#: includes/trait-campaign-contact-history.php:51
     2170msgid "Contact history could not be read. Please try again after checking storage."
     2171msgstr ""
     2172
     2173#: includes/trait-campaign-contact-history.php:62
     2174msgid "Contact history by email"
     2175msgstr ""
     2176
     2177#: includes/trait-campaign-contact-history.php:62
     2178msgid "Only contacts recorded by Advanced Notifications are shown. A mail-system handoff does not confirm inbox delivery or that an email was read."
     2179msgstr ""
     2180
     2181#: includes/trait-campaign-contact-history.php:62
     2182msgid "Email for contact history"
     2183msgstr ""
     2184
     2185#: includes/trait-campaign-contact-history.php:62
     2186msgid "Look up contacts"
     2187msgstr ""
     2188
     2189#: includes/trait-campaign-contact-history.php:65
     2190msgid "This address is protected by erasure. Its previous contact details are not shown or reconstructed."
     2191msgstr ""
     2192
     2193#: includes/trait-campaign-contact-history.php:66
     2194#: includes/trait-campaign-controls.php:143
     2195#: includes/trait-campaign-controls.php:173
     2196#: includes/trait-campaign-privacy.php:128
     2197#: includes/trait-campaign-privacy.php:145
     2198msgid "Unknown"
     2199msgstr ""
     2200
     2201#: includes/trait-campaign-contact-history.php:68
     2202msgid "Current subscription"
     2203msgstr ""
     2204
     2205#: includes/trait-campaign-contact-history.php:69
     2206msgid "Recorded source"
     2207msgstr ""
     2208
     2209#: includes/trait-campaign-contact-history.php:70
     2210msgid "No campaign contacts are currently retained for this email."
     2211msgstr ""
     2212
     2213#: includes/trait-campaign-contact-history.php:71
     2214msgid "Campaign / saved context"
     2215msgstr ""
     2216
     2217#: includes/trait-campaign-contact-history.php:71
     2218msgid "Recorded times (UTC)"
     2219msgstr ""
     2220
     2221#: includes/trait-campaign-contact-history.php:71
     2222msgid "Outcome / reason"
     2223msgstr ""
     2224
     2225#: includes/trait-campaign-contact-history.php:79
     2226msgid "Saved matching order"
     2227msgstr ""
     2228
     2229#: includes/trait-campaign-contact-history.php:80
     2230msgid "Campaign content is no longer available. Only the retained outcome is shown."
     2231msgstr ""
     2232
     2233#: includes/trait-campaign-contact-history.php:81
     2234msgid "Created"
     2235msgstr ""
     2236
     2237#: includes/trait-campaign-contact-history.php:81
     2238msgid "Due"
     2239msgstr ""
     2240
     2241#: includes/trait-campaign-contact-history.php:87
     2242msgid "Previous contacts"
     2243msgstr ""
     2244
     2245#. translators: 1: Current page, 2: Total pages.
     2246#: includes/trait-campaign-contact-history.php:89
     2247msgid "Page %1$d of %2$d"
     2248msgstr ""
     2249
     2250#: includes/trait-campaign-contact-history.php:90
     2251msgid "Next contacts"
    17042252msgstr ""
    17052253
    … …  
    17732321msgstr ""
    17742322
    1775 #: includes/trait-campaign-controls.php:143
    1776 #: includes/trait-campaign-controls.php:173
    1777 #: includes/trait-campaign-privacy.php:127
    1778 #: includes/trait-campaign-privacy.php:143
    1779 msgid "Unknown"
    1780 msgstr ""
    1781 
    17822323#: includes/trait-campaign-controls.php:151
    17832324msgid "Handed off does not verify inbox delivery. Unknown outcomes are never retried automatically. Retry only eligible known failures after correcting the reported problem."
    … …  
    18922433msgstr ""
    18932434
     2435#: includes/trait-campaign-entrypoints.php:24
     2436#: includes/trait-campaign-entrypoints.php:32
     2437msgid "Email purchasers"
     2438msgstr ""
     2439
     2440#: includes/trait-campaign-entrypoints.php:32
     2441msgid "A parent product includes all its variations. Opening a draft sends no email."
     2442msgstr ""
     2443
     2444#: includes/trait-campaign-entrypoints.php:38
     2445msgid "Email purchasers of this variation"
     2446msgstr ""
     2447
     2448#: includes/trait-campaign-entrypoints.php:44
     2449msgid "Email this customer"
     2450msgstr ""
     2451
     2452#: includes/trait-campaign-entrypoints.php:49
     2453msgid "Email selected orders"
     2454msgstr ""
     2455
     2456#: includes/trait-campaign-entrypoints.php:62
     2457msgid "Select at least one available order."
     2458msgstr ""
     2459
     2460#: includes/trait-campaign-entrypoints.php:65
     2461#: includes/trait-campaign-entrypoints.php:67
     2462msgid "A selected order is invalid or unavailable."
     2463msgstr ""
     2464
     2465#: includes/trait-campaign-entrypoints.php:91
     2466msgid "A selected product is invalid or unavailable."
     2467msgstr ""
     2468
     2469#: includes/trait-campaign-entrypoints.php:94
     2470msgid "Invalid draft source."
     2471msgstr ""
     2472
     2473#: includes/trait-campaign-personalization.php:6
     2474msgid "Choose valid orders. No order selection has been ignored."
     2475msgstr ""
     2476
     2477#: includes/trait-campaign-personalization.php:16
     2478msgid "Clear selected orders or choose Selected orders before previewing."
     2479msgstr ""
     2480
     2481#: includes/trait-campaign-personalization.php:18
     2482msgid "Select at least one order."
     2483msgstr ""
     2484
     2485#: includes/trait-campaign-personalization.php:21
     2486msgid "A selected order is unavailable or cannot be accessed. Review the selection."
     2487msgstr ""
     2488
     2489#: includes/trait-campaign-personalization.php:26
     2490msgid "Selected order IDs (exact scope)"
     2491msgstr ""
     2492
     2493#: includes/trait-campaign-personalization.php:26
     2494msgid "Used only with Selected orders. These orders do not expand into other purchases. All selected order statuses are included unless filtered below; invalid emails and suppression are excluded. Product customers includes purchases of the selected parent and its variations; a selected variation matches only that variation."
     2495msgstr ""
     2496
     2497#: includes/trait-campaign-personalization.php:47
     2498msgid "Customer"
     2499msgstr ""
     2500
     2501#: includes/trait-campaign-personalization.php:60
     2502msgid "Order context: highest matching order ID"
     2503msgstr ""
     2504
     2505#: includes/trait-campaign-personalization.php:60
     2506msgid "Matching orders"
     2507msgstr ""
     2508
    18942509#: includes/trait-campaign-privacy.php:57
    18952510msgid "Invalid email; use one plain email address per line."
    … …  
    19842599msgstr ""
    19852600
    1986 #: includes/trait-campaign-privacy.php:123
     2601#: includes/trait-campaign-privacy.php:124
    19872602msgid "Subscribers and suppression"
    19882603msgstr ""
    19892604
    1990 #: includes/trait-campaign-privacy.php:123
     2605#: includes/trait-campaign-privacy.php:124
    19912606msgid "An order, account or pasted email is not opt-in evidence. Unknown remains unknown. Imports never remove an unsubscribe. For subscribed records, enter the actual evidence source; the recorded time is the import time, not an inferred original consent time."
    19922607msgstr ""
    19932608
    1994 #: includes/trait-campaign-privacy.php:125
     2609#: includes/trait-campaign-privacy.php:126
    19952610msgid "Find subscription or suppression"
    19962611msgstr ""
    19972612
    1998 #: includes/trait-campaign-privacy.php:125
     2613#: includes/trait-campaign-privacy.php:126
    19992614msgid "State"
    20002615msgstr ""
    20012616
    2002 #: includes/trait-campaign-privacy.php:127
     2617#: includes/trait-campaign-privacy.php:128
    20032618msgid "All states"
    20042619msgstr ""
    20052620
    2006 #: includes/trait-campaign-privacy.php:128
     2621#: includes/trait-campaign-privacy.php:129
    20072622msgid "Search"
    20082623msgstr ""
    20092624
    2010 #: includes/trait-campaign-privacy.php:128
     2625#: includes/trait-campaign-privacy.php:129
    20112626msgid "Email / state"
    20122627msgstr ""
    20132628
    2014 #: includes/trait-campaign-privacy.php:128
     2629#: includes/trait-campaign-privacy.php:129
    20152630msgid "Source / recorded time (UTC)"
    20162631msgstr ""
    20172632
    2018 #: includes/trait-campaign-privacy.php:140
     2633#: includes/trait-campaign-privacy.php:141
     2634msgid "Confirmation pending; current subscription unchanged."
     2635msgstr ""
     2636
     2637#: includes/trait-campaign-privacy.php:142
    20192638msgid "Up to 200 matches. Erased addresses are represented only by protective hashes and are not listed as plaintext."
    20202639msgstr ""
    20212640
    2022 #: includes/trait-campaign-privacy.php:141
     2641#: includes/trait-campaign-privacy.php:143
    20232642msgid "Import records"
    20242643msgstr ""
    20252644
    2026 #: includes/trait-campaign-privacy.php:141
     2645#: includes/trait-campaign-privacy.php:143
    20272646msgid "1. Paste → 2. Preview → 3. Apply and review result"
    20282647msgstr ""
    20292648
    2030 #: includes/trait-campaign-privacy.php:142
     2649#: includes/trait-campaign-privacy.php:144
    20312650msgid "One email per line"
    20322651msgstr ""
    20332652
    2034 #: includes/trait-campaign-privacy.php:143
     2653#: includes/trait-campaign-privacy.php:145
    20352654msgid "Subscribed (evidence required)"
    20362655msgstr ""
    20372656
    2038 #: includes/trait-campaign-privacy.php:144
     2657#: includes/trait-campaign-privacy.php:146
    20392658msgid "Evidence/source"
    20402659msgstr ""
    20412660
    2042 #: includes/trait-campaign-privacy.php:144
     2661#: includes/trait-campaign-privacy.php:146
    20432662msgid "Preview import"
    20442663msgstr ""
    20452664
    2046 #: includes/trait-campaign-privacy.php:147
     2665#: includes/trait-campaign-privacy.php:149
    20472666msgid "Previewed import"
    20482667msgstr ""
    20492668
    2050 #: includes/trait-campaign-privacy.php:147
     2669#: includes/trait-campaign-privacy.php:149
    20512670msgid "State / evidence source:"
    20522671msgstr ""
    20532672
    2054 #: includes/trait-campaign-privacy.php:147
     2673#: includes/trait-campaign-privacy.php:149
    20552674msgid "Apply uses this saved preview. If you edit the input above, preview it again before applying."
    20562675msgstr ""
    20572676
    2058 #: includes/trait-campaign-privacy.php:148
     2677#: includes/trait-campaign-privacy.php:150
    20592678msgid "Valid / duplicate or already protected / rejected:"
    20602679msgstr ""
    20612680
    2062 #: includes/trait-campaign-privacy.php:150
     2681#: includes/trait-campaign-privacy.php:152
    20632682msgid "Apply previewed import"
    20642683msgstr ""
    20652684
    2066 #: includes/trait-campaign-privacy.php:153
     2685#: includes/trait-campaign-privacy.php:155
    20672686msgid "Applied / not applied (protected or storage failure):"
    20682687msgstr ""
    20692688
    2070 #: includes/trait-campaign-privacy.php:167
    2071 #: includes/trait-campaign-privacy.php:170
     2689#: includes/trait-campaign-privacy.php:169
     2690#: includes/trait-campaign-privacy.php:172
    20722691msgid "YoOhw campaign data"
    20732692msgstr ""
    20742693
    2075 #: includes/trait-campaign-privacy.php:187
     2694#: includes/trait-campaign-privacy.php:185
     2695msgid "Saved order context"
     2696msgstr ""
     2697
     2698#: includes/trait-campaign-privacy.php:195
    20762699msgid "Subscription and protection"
    20772700msgstr ""
    20782701
    2079 #: includes/trait-campaign-privacy.php:198
     2702#: includes/trait-campaign-privacy.php:206
    20802703msgid "Saved list references and legacy logs"
    20812704msgstr ""
    20822705
    2083 #: includes/trait-campaign-privacy.php:215
     2706#: includes/trait-campaign-privacy.php:223
    20842707msgid "Campaign storage is busy or unavailable. Personal data was not fully erased; run erasure again after checking storage and workers."
    20852708msgstr ""
    20862709
    2087 #: includes/trait-campaign-privacy.php:227
     2710#: includes/trait-campaign-privacy.php:235
    20882711msgid "A send is in flight. Run erasure again after the worker finishes. A protective hash already blocks future admission."
    20892712msgstr ""
    20902713
    2091 #: includes/trait-campaign-privacy.php:263
     2714#: includes/trait-campaign-privacy.php:271
    20922715msgid "Personal fields removed. Active campaign shared content is retained until completion; run erasure again if it contains personal text. Minimal recipient/campaign hashes, outcomes and suppression/erasure protection remain to prevent duplicate sends and data reconstruction."
     2716msgstr ""
     2717
     2718#: includes/trait-campaign-purchase-history.php:53
     2719msgid "Purchase history across the store"
     2720msgstr ""
     2721
     2722#: includes/trait-campaign-purchase-history.php:53
     2723msgid "Purchase frequency"
     2724msgstr ""
     2725
     2726#: includes/trait-campaign-purchase-history.php:54
     2727msgid "Any purchase history"
     2728msgstr ""
     2729
     2730#: includes/trait-campaign-purchase-history.php:57
     2731msgid "No repeat purchase in the last X days"
     2732msgstr ""
     2733
     2734#: includes/trait-campaign-purchase-history.php:57
     2735msgid "These conditions use all valid purchases across the entire store, including orders outside the dates and products selected below. Product/category conditions still match one order; they do not change this into repeat purchases of only that product."
     2736msgstr ""
     2737
     2738#: includes/trait-campaign-purchase-history.php:57
     2739msgid "Which purchases count?"
     2740msgstr ""
     2741
     2742#: includes/trait-campaign-purchase-history.php:57
     2743msgid "Processing and Completed orders count by normalized billing email, including guest orders and orders before an account existed. Cancelled, Failed, Refunded and fully refunded orders do not count. Partial refunds count while an amount remains; free orders without refunds count. Inactivity uses the latest valid order creation time and X times 24 hours. A customer with no valid purchases never qualifies as inactive. Different emails are not merged."
    20932744msgstr ""
    20942745
    … …  
    21132764msgstr ""
    21142765
     2766#: includes/trait-campaign-subscriptions.php:42
     2767msgid "Enter a valid email address and try again."
     2768msgstr ""
     2769
     2770#: includes/trait-campaign-subscriptions.php:43
     2771#: includes/trait-campaign-subscriptions.php:74
     2772msgid "Subscription storage is busy. Please try again."
     2773msgstr ""
     2774
     2775#: includes/trait-campaign-subscriptions.php:57
     2776msgid "The request could not be completed. Please try again later."
     2777msgstr ""
     2778
     2779#: includes/trait-campaign-subscriptions.php:61
     2780#: includes/trait-campaign-subscriptions.php:113
     2781msgid "Confirm your email subscription"
     2782msgstr ""
     2783
     2784#: includes/trait-campaign-subscriptions.php:62
     2785msgid "Someone requested campaign emails from this store for this address. To subscribe, open the link below and confirm. It expires in 24 hours. If you did not request this, ignore this email; your preference has not changed."
     2786msgstr ""
     2787
     2788#: includes/trait-campaign-subscriptions.php:72
     2789msgid "This confirmation is invalid, expired or already used. Submit a new subscription request."
     2790msgstr ""
     2791
     2792#: includes/trait-campaign-subscriptions.php:85
     2793msgid "The subscription could not be saved. Submit a new request and try again."
     2794msgstr ""
     2795
     2796#: includes/trait-campaign-subscriptions.php:92
     2797msgid "Email address"
     2798msgstr ""
     2799
     2800#: includes/trait-campaign-subscriptions.php:92
     2801msgid "I would like to receive campaign emails from this store. I can unsubscribe at any time."
     2802msgstr ""
     2803
     2804#: includes/trait-campaign-subscriptions.php:92
     2805msgid "Leave this field empty"
     2806msgstr ""
     2807
     2808#: includes/trait-campaign-subscriptions.php:92
     2809msgid "Request subscription"
     2810msgstr ""
     2811
     2812#: includes/trait-campaign-subscriptions.php:92
     2813msgid "Check your email to confirm. Submitting this form does not subscribe you."
     2814msgstr ""
     2815
     2816#: includes/trait-campaign-subscriptions.php:96
     2817msgid "Use the subscription form."
     2818msgstr ""
     2819
     2820#: includes/trait-campaign-subscriptions.php:99
     2821msgid "Choose whether you want to request campaign emails."
     2822msgstr ""
     2823
     2824#: includes/trait-campaign-subscriptions.php:103
     2825msgid "If this address is eligible, a confirmation email has been requested. Check your inbox and spam folder. Requests are limited; you can try again later."
     2826msgstr ""
     2827
     2828#: includes/trait-campaign-subscriptions.php:103
     2829msgid "Check your email"
     2830msgstr ""
     2831
     2832#: includes/trait-campaign-subscriptions.php:112
     2833msgid "Invalid confirmation link."
     2834msgstr ""
     2835
     2836#: includes/trait-campaign-subscriptions.php:113
     2837msgid "Confirm that you want campaign emails from this store. This also replaces an earlier unsubscribe for this address. You can unsubscribe again at any time."
     2838msgstr ""
     2839
     2840#: includes/trait-campaign-subscriptions.php:113
     2841#: includes/trait-campaign-subscriptions.php:114
     2842msgid "Confirm subscription"
     2843msgstr ""
     2844
     2845#: includes/trait-campaign-subscriptions.php:118
     2846msgid "Your email subscription is confirmed. You can unsubscribe from any campaign email."
     2847msgstr ""
     2848
     2849#: includes/trait-campaign-subscriptions.php:123
     2850msgid "Email confirmation is pending. Your current preference remains in effect until you confirm."
     2851msgstr ""
     2852
     2853#: includes/trait-campaign-subscriptions.php:124
     2854msgid "Campaign email preference"
     2855msgstr ""
     2856
     2857#: includes/trait-campaign-subscriptions.php:124
     2858msgid "Preference for your current account email:"
     2859msgstr ""
     2860
     2861#: includes/trait-campaign-subscriptions.php:125
     2862msgid "Request subscription by email"
     2863msgstr ""
     2864
     2865#: includes/trait-campaign-subscriptions.php:125
     2866msgid "A subscription request requires email confirmation. Unsubscribe applies immediately."
     2867msgstr ""
     2868
     2869#: includes/trait-campaign-subscriptions.php:133
     2870msgid "The preference could not be saved. Try again later."
     2871msgstr ""
     2872
    21152873#: yoohw-advanced-notifications.php:43
    21162874msgid "Advanced Notifications for WooCommerce requires WooCommerce to be active."
  • yoohw-advanced-notifications/trunk/readme.txt

    r3771948 r3789014  
    66Requires PHP: 8.1
    77Requires Plugins: woocommerce
    8 Stable tag: 0.3.0
     8Stable tag: 0.4.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2020= Choose and review your audience =
    2121
    22 * Select product or variation purchasers, WooCommerce customers with matching orders, registered users or a pasted email list. Order-based sources include guest customers.
     22* Select product or variation purchasers, selected orders, WooCommerce customers with matching orders, registered users, subscribed email addresses or a pasted email list. Order-based sources include guest customers.
     23* Start from Email purchasers on Products or Email selected orders / Email this customer on Orders. These actions create a draft with an explicit scope; preview and confirmation are still required before sending.
    2324* Filter matching orders by product, category (including child categories), creation date, order status and billing country. These order conditions must match the same order.
    2425* Filter registered users by role and profile billing country. Only filters supported by the selected source can be used.
    2526* Select by subscription status or a previous campaign's recorded handoff outcome. Missing or unknown delivery history is not treated as proof that an email was never sent.
    2627* Apply exclusions, remove duplicate email addresses and respect saved unsubscribes across every recipient source.
     28* Choose first-time buyers (one valid order), repeat buyers (two or more), or customers with no valid purchase in the last X days. History spans the entire store, outside campaign product/date filters. Processing and Completed orders count by normalized billing email; cancelled, failed and fully refunded orders do not. Partial refunds count while value remains; free orders without refunds count. Inactivity uses order creation time and X times 24 hours; customers with no valid purchases are excluded.
    2729* Save audience criteria for reuse. Each use requires a fresh recipient preview and confirmation.
    2830* Review selection and exclusion counts and inspect why an email address is included or excluded.
    … …  
    3537* Use WooCommerce HTML email styling, the rich-text editor and desktop/mobile previews for a sample recipient.
    3638* Send a test email and continue editing the draft before confirming recipients.
    37 * Personalize the message body with {customer_name} and {product_name}. A missing customer name falls back to the email address. The product variable is a campaign-wide label, not a record of each recipient's purchases. Subjects and headings do not expand these variables.
     39* New campaigns personalize subject, heading and body with {customer_name}, {store_name}, {product_name}, {order_number}, {ordered_products} and {order_url}. Missing names use Customer. {product_name} remains a campaign-wide label; order details come from the matching order with the highest order ID and are empty without order context. Guest order links are empty; account order links require the owning account login. Already-confirmed older campaigns keep their original rendering contract.
    3840
    3941= Schedule and control sending =
    … …  
    4648* Recover interrupted work using confirmed recipients and bounded retries for eligible known failures. Unknown handoff outcomes are not automatically retried.
    4749
     50= One guide after Completed =
     51
     52* Follow Conditions → Content → Timing → Review in Automations. Compose with a live WooCommerce preview and Desktop/Mobile controls, then refresh the exact order sample in Review. Save a guide with optional products/categories and a delay of 0–8760 hours. Preview an accessible sample order or send a test to your chosen address, then explicitly enable the saved rule.
     53* Only new transitions to Completed are captured. Enabling never scans historical orders. Each rule/order can create at most one email, including repeated transitions and multiple matching items; different orders for the same email can each receive a guide.
     54* An active subscription is required at sending. Unknown is not consent. Orders must still be Completed, have the same valid billing email, match saved product/category conditions and not be fully refunded. Partial refunds remain eligible while value remains. Subscription, suppression and erasure protections are checked before handoff.
     55* Content, triggering-order context and UTC due time are fixed when captured. Editing a rule changes future events. Disabling stops new events and cancels unstarted mail; started work may finish. Re-enabling does not restore cancelled mail.
     56* Use Campaigns & Logs or the contact lookup to inspect the guide, exact order and recorded result. Existing limits, recovery and unknown-outcome protections apply. A working queue is required; delivery may be later than the due time.
     57
    4858= Subscription and privacy tools =
     59
     60* Place [yoohw_an_subscribe] on a page for guest signup. Optional, unchecked Classic Checkout and Checkout Blocks fields (Blocks requires WooCommerce 8.9 or later) request a confirmation email; buying or creating an account never establishes consent.
     61* Confirm mailbox ownership using the emailed link and confirmation button within 24 hours. A new verified request can replace an earlier unsubscribe; imports cannot. Erasure protection remains in effect.
     62* View or change the preference for the current email in My Account. Choose Subscribed email addresses in the composer to include subscribers without an account or order.
     63* Look up one email under Subscribers to review its retained campaign outcomes, reasons and campaign links. Handoff does not prove inbox delivery or reading.
    4964
    5065* Keep subscribed, unsubscribed and unknown states separate, with the recorded source and time where available.
    … …  
    6075Only this plugin's campaign emails are subject to its send controls and admission limits. WooCommerce transactional emails and unrelated scheduled actions keep their existing behavior. Test emails are outside the campaign admission limits.
    6176
    62 This plugin does not include recurring marketing automation, WooCommerce Subscriptions audiences, CRM synchronization or additional messaging channels.
     77This plugin does not include multi-step or recurring marketing automation, WooCommerce Subscriptions audiences, CRM synchronization or additional messaging channels.
    6378
    6479== Installation ==
    … …  
    97112= What data does the plugin store and erase? =
    98113
    99 The plugin stores campaign content, confirmed recipients, scheduling and delivery records, drafts, saved audience criteria, subscription evidence and opt-outs in your WordPress database. Emails are sent through the mail transport configured on your site.
     114The plugin stores campaign content, confirmed recipients, scheduling and delivery records, drafts, saved audience criteria, subscription evidence and opt-outs in your WordPress database. Emails are sent through the mail transport configured on your site. Subscription requests store the email, source, request/expiry time and a hashed confirmation token for at most 24 hours. Rate counters store hashes, not raw IP addresses. Confirmation requests are limited per address, client and site. A protected erased address is not re-enrolled or reconstructed by a new request.
    100115
    101116WordPress Tools > Export Personal Data and Erase Personal Data include this plugin's records for the requested email address, including guests. Erasure retains protective hashes and opt-out evidence to prevent reconstruction or another send. In-flight work or shared content in an active campaign may require another erasure pass after completion; the erasure result reports retained data.
    … …  
    107122Deactivation cancels pending campaign work and removes the reconciliation event while retaining campaign records and opt-outs. Reactivation does not automatically restart cancelled work.
    108123
    109 When upgrading, stop background workers that have already loaded the old plugin code before replacing files. Version 0.3.0 keeps the existing plugin slug, text domain, settings, campaigns and opt-outs. Do not downgrade directly to 0.2.2, which does not understand the new cancellation and erasure protections. Data from the separate YoOhw Extra Code plugin is not imported automatically.
     124When upgrading, stop background workers that have already loaded the old plugin code before replacing files. Version 0.4.0 keeps the existing plugin slug, text domain, settings, campaigns and opt-outs. Do not downgrade directly to 0.2.2, which does not understand the new cancellation and erasure protections. Data from the separate YoOhw Extra Code plugin is not imported automatically.
    110125
    111126= Can I translate the interface? =
    … …  
    115130== Changelog ==
    116131
    117 = 0.3.0 =
    118 * Rename the plugin to Advanced Notifications for WooCommerce; keep the existing slug, text domain and stored data.
    119 * Add individual campaign controls and shared sending admission limits.
    120 * Add durable drafts, duplication, content templates and personalized previews.
    121 * Add source-aware saved audiences and explicit timezone scheduling.
    122 * Add subscription/suppression administration, privacy callbacks and terminal retention.
    123 * Preserve erased data boundaries, known-unsent history and cancellation during concurrent recovery.
    124 * Unify campaign forms with the admin color scheme, SelectWoo fields and desktop/mobile preview controls.
    125 * Harden confirmed recipient snapshots, duplicate protection, bounded retries and interrupted-work recovery.
     132= 0.4.0 =
     133* Add product and order email entry points that create scoped drafts, with HPOS and posts order storage support.
     134* Personalize new campaign subjects, headings and bodies with frozen customer, store and representative-order context; preserve already-confirmed campaigns.
     135* Add verified subscriptions through a signup shortcode, Classic Checkout, Checkout Blocks and My Account without treating purchase or account creation as consent.
     136* Add per-email contact history lookup with retained outcomes, reasons and campaign links.
     137* Add store-wide first-time, repeat-buyer and purchase-inactivity filters for campaigns and saved audiences.
     138* Add explicitly enabled Completed-order guides with product/category conditions, a fixed delay and one immutable delivery per rule/order.
     139* Recheck rule, order, subscription, suppression and erasure before guide handoff; preserve cancellation, duplicate protection and unknown-outcome boundaries.
     140* Align automation authoring with the four-step composer, live Desktop/Mobile previews and exact-order preview/test; hide stale samples after edits.
     141* Fix Library warnings when optional draft or saved-audience names are missing.
    126142
    127 = 0.2.2 =
    128 * Align the translation domain with the WordPress.org slug yoohw-advanced-notifications.
    129 * Resolve Plugin Check findings for SQL identifiers, input handling, test fixtures and plugin metadata.
    130 * Load admin JavaScript and CSS from separate files with WordPress dependencies.
    131 
    132 = 0.2.1 =
    133 * Register the languages directory on init and include a POT translation template.
    134 * Make remaining delivery messages and status labels translatable.
    135 * Add translator notes and translation setup instructions.
    136 
    137 = 0.2.0 =
    138 * Add WooCommerce customers and Manual email list recipient sources.
    139 * Add multi-role filtering for registered users, including preview and campaign history.
    140 * Preserve recipient selections when restoring drafts and retrying failed schedules.
    141 * Use native JavaScript trim() for the live preview.
    142 
    143 = 0.1.0 =
    144 * Extract standalone customer email campaigns from YoOhw Extra Code.
    145 * Remove WooCommerce Subscriptions audiences and exclusions, and Audience sync integration.
    146 * Isolate plugin storage/hooks and use the current store's sender defaults.
     143See changelog.txt for the full version history.
    147144
    148145== Upgrade Notice ==
    149146
     147= 0.4.0 =
     148Adds scoped product/order campaigns, verified subscriptions, contact history, purchase-history filters and Completed-order guides. Existing data and confirmed work are retained. New automation rules stay disabled until explicitly enabled; there is no historical backfill. Stop workers using the old code before replacing plugin files. Do not downgrade to an older version without reviewing pending jobs and subscription/privacy compatibility.
     149
    150150= 0.3.0 =
    151151Campaign controls, reusable drafts, audience filters, scheduling and privacy tools. Existing data is retained. Stop old background workers before upgrading. Do not downgrade to 0.2.2 because it lacks the new cancellation and erasure protections.
  • yoohw-advanced-notifications/trunk/yoohw-advanced-notifications.php

    r3771948 r3789014  
    44 * Plugin URI: https://wordpress.org/plugins/yoohw-advanced-notifications
    55 * Description: Compose, preview, schedule and track WooCommerce customer email campaigns.
    6  * Version: 0.3.0
     6 * Version: 0.4.0
    77 * Author: YoOhw Studio
    88 * Author URI: https://yoohw.com/
    … …  
    2020}
    2121
    22 define( 'YOOHW_AN_VERSION', '0.3.0' );
     22define( 'YOOHW_AN_VERSION', '0.4.0' );
    2323
    2424function yoohw_an_load_textdomain() {
Note: See TracChangeset for help on using the changeset viewer.