PluginProbe
Texty – SMS Notification for WordPress, WooCommerce, Dokan and more / trunk
Texty – SMS Notification for WordPress, WooCommerce, Dokan and more vtrunk
2.0.2 trunk 0.2 1.0 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 2.0.0 2.0.1
texty / includes / Notifications / WC / Base.php

Base.php in Texty – SMS Notification for WordPress, WooCommerce, Dokan and more trunk, at includes/Notifications/WC/Base.php

241 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Texty\Notifications\WC;
4
5 use Texty\Notifications\Notification;
6 use WC_Order_Item_Product;
7
8 class Base extends Notification {
9
10 /**
11 * @var object
12 */
13 protected $order;
14
15 /**
16 * Set the user ID
17 *
18 * @param mixed $order
19 *
20 * @return self
21 */
22 public function set_order( $order ) {
23 $this->order = $order;
24
25 return $this;
26 }
27
28 /**
29 * Return the message
30 *
31 * @return string
32 */
33 public function get_message() {
34 $message = parent::get_message_raw();
35
36 if ( ! $this->order ) {
37 return $message;
38 }
39
40 foreach ( $this->replacement_keys() as $search => $method ) {
41 $value = method_exists( $this->order, $method ) ? $this->order->$method() : '';
42
43 // WC accessors like `get_date_paid()` return null on unpaid orders
44 // and `WC_DateTime` objects on paid ones — normalize both to a
45 // string before any string handling. PHP 8.1 deprecates passing
46 // null to str_replace's $replace argument.
47 if ( null === $value ) {
48 $value = '';
49 } elseif ( ! is_scalar( $value ) ) {
50 $value = (string) $value;
51 }
52
53 if ( 'order_total' === $search ) {
54 $value = wp_strip_all_tags( html_entity_decode( (string) $value ) );
55 }
56
57 if ( 'items' === $search ) {
58 $value = $this->get_items();
59 }
60
61 $message = str_replace( '{' . $search . '}', (string) $value, $message );
62 }
63
64 $message = $this->replace_global_keys( $message );
65
66 return $message;
67 }
68
69 /**
70 * Get product items from the order
71 *
72 * @return string
73 */
74 protected function get_items() {
75 $products = [];
76
77 foreach ( $this->order->get_items() as $item ) {
78 if ( ! $item instanceof WC_Order_Item_Product ) {
79 continue;
80 }
81
82 $product = $item->get_product();
83
84 // A product may have been deleted after the order was placed —
85 // `get_product()` then returns false. Fall back to the line-item
86 // name stored on the order so building the message doesn't fatal.
87 $name = $product ? $product->get_name() : $item->get_name();
88
89 $products[] = sprintf( '%s x %d', $name, $item->get_quantity() );
90 }
91
92 $names = implode( "\n", $products );
93
94 return $names;
95 }
96
97 /**
98 * Return recipients
99 *
100 * @return array
101 */
102 public function get_recipients() {
103 return $this->get_numbers_by_roles();
104 }
105
106 /**
107 * Get replacement keys
108 *
109 * @return array
110 */
111 public function replacement_keys() {
112 return [
113 'order_id' => 'get_id',
114 'items' => 'products',
115 'date' => 'get_date_paid',
116 'status' => 'get_status',
117 'payment_method' => 'get_payment_method_title',
118 'shipping_method' => 'get_shipping_method',
119 'transaction_id' => 'get_transaction_id',
120 'billing_name' => 'get_formatted_billing_full_name',
121 'billing_email' => 'get_billing_email',
122 'order_total' => 'get_formatted_order_total',
123 'shipping_total' => 'get_shipping_total',
124 'tax_total' => 'get_total_tax',
125 'discount' => 'get_discount_total',
126 ];
127 }
128
129 public function send(): bool {
130 if ( ! $this->enabled() ) {
131 return false;
132 }
133
134 $meta_key = '_texty_' . $this->get_id();
135 $has_sent = $this->order->get_meta( $meta_key, true );
136
137 // if we've already sent the message, don't send again
138 if ( $has_sent ) {
139 return false;
140 }
141
142 // Claim the send up-front so concurrent status changes stay at-most-once.
143 // update_meta_data (not add_meta_data) keeps exactly one row per order/notification.
144 $this->order->update_meta_data( $meta_key, 1 );
145 $this->order->save_meta_data();
146
147 if ( 'user' === $this->get_type() ) {
148 $number = $this->order->get_billing_phone();
149
150 $recipients = $number ? [ $number ] : [];
151 } else {
152 $recipients = $this->get_recipients();
153 }
154
155 /**
156 * Filter the recipients for a notification.
157 *
158 * @param array $recipients The recipient phone numbers
159 * @param Notification $notification The notification instance
160 */
161 $recipients = apply_filters( 'texty_notification_recipients', $recipients, $this );
162
163 // Drop nulls / empty strings — a stale `texty_phone` meta value or a
164 // third-party filter can leave them in the array and crash the
165 // gateway send (which expects a string).
166 $recipients = is_array( $recipients ) ? array_values( array_filter( $recipients ) ) : [];
167
168 if ( ! $recipients ) {
169 return false;
170 }
171
172 $content = $this->get_message();
173
174 /**
175 * Filter the notification message content.
176 *
177 * @param string $content The message content
178 * @param Notification $notification The notification instance
179 */
180 $content = apply_filters( 'texty_notification_message', $content, $this );
181
182 /**
183 * Filter the message for a specific notification type.
184 *
185 * @param string $content The message content
186 * @param Notification $notification The notification instance
187 */
188 $content = apply_filters( 'texty_notification_message_' . $this->get_id(), $content, $this );
189
190 /**
191 * Fires before the notification send loop.
192 *
193 * @param Notification $notification The notification instance
194 * @param array $recipients The recipient phone numbers
195 * @param string $content The message content
196 */
197 do_action( 'texty_before_notification', $this, $recipients, $content );
198
199 $gateway = texty()->gateways();
200 $any_sent = false;
201
202 // Stash the active notification so the after-send logger can attach
203 // notification_id / notification_group to each SmsStat row without
204 // threading them through the gateway pipeline.
205 texty()->notifications()->set_active( $this );
206
207 try {
208 foreach ( $recipients as $number ) {
209 $result = $gateway->send( $number, $content );
210
211 if ( ! is_wp_error( $result ) && false !== $result ) {
212 $any_sent = true;
213 }
214 }
215 } finally {
216 texty()->notifications()->clear_active();
217 }
218
219 /**
220 * Fires after the notification send loop.
221 *
222 * @param Notification $notification The notification instance
223 * @param array $recipients The recipient phone numbers
224 * @param string $content The message content
225 */
226 do_action( 'texty_after_notification', $this, $recipients, $content );
227
228 // Every recipient failed — release the claim so the next matching status
229 // change can retry. A partial success keeps the flag so the recipients
230 // who already received the SMS are not messaged again.
231 if ( ! $any_sent ) {
232 $this->order->delete_meta_data( $meta_key );
233 $this->order->save_meta_data();
234
235 return false;
236 }
237
238 return true;
239 }
240 }
241