PluginProbe
DPD SK for WooCommerce / trunk
DPD SK for WooCommerce vtrunk
9.2.1 9.1.0 9.2.0 9.0.0 trunk 1.0.0 1.0.1 2.0.0 2.0.1 3.0.0 3.0.1 3.0.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 5.0.0 5.0.1 5.0.2 5.0.3 6.0.0 6.0.1 7.0.0 7.0.1 All 37 releases
wc-dpd / includes / AutoExport.php

AutoExport.php in DPD SK for WooCommerce trunk, at includes/AutoExport.php

209 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WcDPD;
4
5 defined('ABSPATH') || exit;
6
7 /**
8 * AutoExport
9 *
10 * Watches WooCommerce order status transitions. When the configured trigger
11 * status is reached and auto-export is enabled, the order is exported to DPD
12 * via Order::export() (the data-layer entry, NOT an admin action -- so no
13 * capability check sits between this hook and the API call).
14 *
15 * Recursion is guarded by a short-lived order meta key -- if Order::export()
16 * triggers any update that re-fires the same status hook, the second pass
17 * exits immediately and the guard is removed in the `finally` block.
18 */
19 class AutoExport
20 {
21 public const ENABLED_OPTION_KEY = 'wc_dpd_auto_export_enabled';
22 public const STATUS_OPTION_KEY = 'wc_dpd_auto_export_status';
23
24 public const RECURSION_META_KEY = '_wc_dpd_auto_exported';
25
26 /**
27 * Statuses for which we register a transition hook.
28 *
29 * Each value feeds `woocommerce_order_status_{status}` (with hyphens replaced
30 * by underscores, which is the WP convention).
31 */
32 public const ALLOWED_STATUSES = [
33 'pending',
34 'processing',
35 'on-hold',
36 'completed',
37 'cancelled',
38 'refunded',
39 'failed',
40 ];
41
42 /**
43 * Register transition hooks and the related actions.
44 */
45 public static function init(): void
46 {
47 foreach (self::ALLOWED_STATUSES as $status) {
48 add_action(
49 'woocommerce_order_status_' . str_replace('-', '_', $status),
50 [__CLASS__, 'maybeHandle'],
51 10,
52 2
53 );
54 }
55
56 add_action('wc_dpd_auto_export_attempt', [__CLASS__, 'maybeNotifyAdmin'], 10, 2);
57 add_action('admin_notices', [__CLASS__, 'maybeShowAutoExportConfigNotice']);
58 }
59
60 /**
61 * Run the export if all preconditions are met.
62 *
63 * @param int $order_id
64 * @param \WC_Order|null $order
65 */
66 /**
67 * Read auto-export settings from the WC shipping-method options bag
68 * (`woocommerce_dpd_export_settings`), not from top-level options.
69 *
70 * @return array{enabled: bool, status: string}
71 */
72 public static function getSettings(): array
73 {
74 $settings = get_option(DpdExportSettings::SETTINGS_OPTION_KEY, []);
75 if (!is_array($settings)) {
76 $settings = [];
77 }
78
79 $enabled_raw = $settings[self::ENABLED_OPTION_KEY] ?? 'no';
80 $status = (string) ($settings[self::STATUS_OPTION_KEY] ?? 'processing');
81
82 return [
83 // WC checkboxes store 'yes' / 'no' strings. Never cast the string
84 // with (bool) - (bool)'no' is true in PHP.
85 'enabled' => $enabled_raw === 'yes',
86 'status' => $status !== '' ? $status : 'processing',
87 ];
88 }
89
90 public static function maybeHandle(int $order_id, $order = null): void
91 {
92 if (!$order instanceof \WC_Order) {
93 $order = wc_get_order($order_id);
94 }
95
96 if (!$order instanceof \WC_Order) {
97 return;
98 }
99
100 $cfg = self::getSettings();
101 $enabled = $cfg['enabled'];
102 $status = $cfg['status'];
103
104 if (!$enabled || $order->get_status() !== $status) {
105 return;
106 }
107
108 // Auto-export only orders shipped with a DPD shipping method --
109 // orders using other carriers must never hit the DPD API.
110 if (!Order::hasParcelShpping($order)) {
111 return;
112 }
113
114 // Recursion guard -- if Order::export() somehow re-fires this hook,
115 // the second pass exits before touching the DPD API.
116 if ($order->get_meta(self::RECURSION_META_KEY)) {
117 return;
118 }
119
120 $order->update_meta_data(self::RECURSION_META_KEY, (string) time());
121 $order->save_meta_data();
122
123 try {
124 $result = (bool) Order::export($order->get_id());
125
126 do_action('wc_dpd_auto_export_attempt', $order, $result);
127
128 $verb = $result ? __('succeeded', 'wc-dpd') : __('failed', 'wc-dpd');
129 $order->add_order_note(
130 sprintf(
131 /* translators: %s: succeeded or failed */
132 Notice::PREFIX . __('Auto-export %s.', 'wc-dpd'),
133 $verb
134 )
135 );
136 } finally {
137 $order->delete_meta_data(self::RECURSION_META_KEY);
138 $order->save_meta_data();
139 }
140 }
141
142 /**
143 * Notify the site admin of an auto-export failure.
144 *
145 * @param \WC_Order $order
146 * @param bool $result
147 */
148 public static function maybeNotifyAdmin($order, bool $result): void
149 {
150 if ($result) {
151 return;
152 }
153
154 if (!$order instanceof \WC_Order) {
155 return;
156 }
157
158 $admin_email = (string) get_option('admin_email');
159
160 if (!$admin_email) {
161 return;
162 }
163
164 $blog_name = wp_specialchars_decode((string) get_option('blogname'), ENT_QUOTES);
165
166 wp_mail(
167 $admin_email,
168 sprintf(
169 /* translators: 1: site name, 2: order id */
170 __('[%1$s] DPD auto-export failed for order #%2$d', 'wc-dpd'),
171 $blog_name,
172 $order->get_id()
173 ),
174 sprintf(
175 /* translators: %d: order id */
176 __("DPD auto-export for order #%d failed. Open the order in admin to see details and retry manually.", 'wc-dpd'),
177 $order->get_id()
178 )
179 );
180 }
181
182 /**
183 * Render an admin notice if auto-export is enabled but the configured
184 * trigger status is missing/empty.
185 */
186 public static function maybeShowAutoExportConfigNotice(): void
187 {
188 if (!current_user_can('manage_woocommerce')) {
189 return;
190 }
191
192 $cfg = self::getSettings();
193
194 if (!$cfg['enabled']) {
195 return;
196 }
197
198 $status = $cfg['status'];
199
200 if (in_array($status, self::ALLOWED_STATUSES, true)) {
201 return;
202 }
203
204 echo '<div class="notice notice-warning is-dismissible"><p>';
205 echo esc_html__('DPD: auto-export is enabled but the configured trigger status is not a valid WooCommerce order status. Falling back to "processing".', 'wc-dpd');
206 echo '</p></div>';
207 }
208 }
209