· 6 years ago · Jan 08, 2020, 10:09 PM
1<?php
2/**
3 * WooCommerce Twilio SMS Notifications
4 *
5 * This source file is subject to the GNU General Public License v3.0
6 * that is bundled with this package in the file license.txt.
7 * It is also available through the world-wide-web at this URL:
8 * http://www.gnu.org/licenses/gpl-3.0.html
9 * If you did not receive a copy of the license and are unable to
10 * obtain it through the world-wide-web, please send an email
11 * to license@skyverge.com so we can send you a copy immediately.
12 *
13 * DISCLAIMER
14 *
15 * Do not edit or add to this file if you wish to upgrade WooCommerce Twilio SMS Notifications to newer
16 * versions in the future. If you wish to customize WooCommerce Twilio SMS Notifications for your
17 * needs please refer to http://docs.woocommerce.com/document/twilio-sms-notifications/ for more information.
18 *
19 * @package WC-Twilio-SMS-Notifications/Notification
20 * @author SkyVerge
21 * @copyright Copyright (c) 2013-2018, SkyVerge, Inc.
22 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23 */
24
25defined( 'ABSPATH' ) or exit;
26
27/**
28 * Twilio SMS Notification class
29 *
30 * Handle SMS sending Admin & Customer Notifications, as well as manual SMS messages from Order page
31 *
32 * @since 1.0
33 */
34class WC_Twilio_SMS_Notification {
35
36 /** @var \WC_Order order object for SMS sending */
37 private $order;
38
39
40 /**
41 * Load new order object
42 *
43 * @since 1.0
44 * @param int $order_id the order ID
45 */
46 public function __construct( $order_id ) {
47
48 $this->order = wc_get_order( $order_id );
49
50 }
51
52
53 /**
54 * Send admin new order SMS notifications
55 *
56 * @since 1.0
57 */
58 public function send_admin_notification() {
59
60 // Check if sending admin SMS updates for new orders
61 if ( 'yes' === get_option( 'wc_twilio_sms_enable_admin_sms' ) ) {
62
63 // get message template
64 $message = get_option( 'wc_twilio_sms_admin_sms_template', '' );
65
66 // replace template variables
67 $message = $this->replace_message_variables( $message );
68
69 // shorten URLs if enabled
70 if ( 'yes' === get_option( 'wc_twilio_sms_shorten_urls' ) ) {
71 $message = $this->shorten_urls( $message );
72 }
73
74 // get admin phone number (s)
75 $recipients = explode( ',', trim( get_option( 'wc_twilio_sms_admin_sms_recipients' ) ) );
76
77 // send the SMS to each recipient
78 if ( ! empty( $recipients ) ) {
79
80 foreach ( $recipients as $recipient ) {
81
82 try {
83
84 wc_twilio_sms()->get_api()->send( $recipient, $message, false );
85
86 } catch ( Exception $e ) {
87
88 wc_twilio_sms()->log( $e->getMessage() );
89 }
90 }
91 }
92 }
93 }
94
95
96 /**
97 * Sends customer SMS notifications on order status changes
98 *
99 * @since 1.0
100 */
101 public function send_automated_customer_notification() {
102
103 // get checkbox opt-in label
104 $optin = get_option( 'wc_twilio_sms_checkout_optin_checkbox_label', '' );
105
106 // check if opt-in checkbox is enabled
107 if ( ! empty( $optin ) ) {
108
109 // get opt-in meta for order
110 $optin = SV_WC_Order_Compatibility::get_meta( $this->order, '_wc_twilio_sms_optin', true );
111
112 // check if customer has opted-in
113 if ( empty( $optin ) ) {
114 // no meta set, so customer has not opted in
115 return;
116 }
117 }
118
119 // Check if sending SMS updates for this order's status
120 if ( in_array( 'wc-' . $this->order->get_status(), get_option( 'wc_twilio_sms_send_sms_order_statuses' ) ) ) {
121
122 // get message template
123 $message = get_option( 'wc_twilio_sms_' . $this->order->get_status() . '_sms_template', '' );
124
125 // use the default template if status-specific one is blank
126 if ( empty( $message ) ) {
127 $message = get_option( 'wc_twilio_sms_default_sms_template' );
128 }
129
130 // allow modification of message before variable replace (add additional variables, etc)
131 $message = apply_filters( 'wc_twilio_sms_customer_sms_before_variable_replace', $message, $this->order );
132
133 // replace template variables
134 $message = $this->replace_message_variables( $message );
135
136 // allow modification of message after variable replace
137 $message = apply_filters( 'wc_twilio_sms_customer_sms_after_variable_replace', $message, $this->order );
138
139 // allow modification of the "to" phone number
140 $phone = apply_filters( 'wc_twilio_sms_customer_phone', SV_WC_Order_Compatibility::get_prop( $this->order, 'billing_phone' ), $this->order );
141
142 // shorten URLs if enabled
143 if ( 'yes' === get_option( 'wc_twilio_sms_shorten_urls' ) ) {
144 $message = $this->shorten_urls( $message );
145 }
146
147 // send the SMS!
148 $this->send_sms( $phone, $message );
149 }
150 }
151
152
153 /**
154 * Sends SMS to customer from 'Send an SMS' metabox on Orders page
155 *
156 * @since 1.0
157 * @param string $message message to send customer
158 */
159 public function send_manual_customer_notification( $message ) {
160
161 // shorten URLs if enabled
162 if ( 'yes' === get_option( 'wc_twilio_sms_shorten_urls' ) ) {
163 $message = $this->shorten_urls( $message );
164 }
165
166 // send the SMS!
167 $this->send_sms( SV_WC_Order_Compatibility::get_prop( $this->order, 'billing_phone' ), $message );
168 }
169
170
171 /**
172 * Create and send SMS message
173 *
174 * @since 1.0
175 * @param string $to
176 * @param string $message
177 * @param bool $customer_notification order note is added if true
178 */
179 private function send_sms( $to, $message, $customer_notification = true ) {
180
181 // Default status for SMS message, on error this is replaced with error message
182 $status = __( 'Sent', 'woocommerce-twilio-sms-notifications' );
183
184 // Timestamp of SMS is current time
185 $sent_timestamp = time();
186
187 // error flag
188 $error = false;
189
190 try {
191
192 // send the SMS via API
193 $response = wc_twilio_sms()->get_api()->send( $to, $message, SV_WC_Order_Compatibility::get_prop( $this->order, 'billing_country' ) );
194
195 // use the timestamp from twilio if available
196 $sent_timestamp = ( isset( $response['date_created'] ) ) ? strtotime( $response['date_created'] ) : $sent_timestamp;
197
198 // use twilio formatted number if available
199 $to = ( isset( $response['to'] ) ) ? $response['to'] : $to;
200
201 } catch ( Exception $e ) {
202
203 // Set status to error message
204 $status = $e->getMessage();
205
206 // set error flag
207 $error = true;
208
209 // log to PHP error log
210 wc_twilio_sms()->log( $e->getMessage() );
211 }
212
213 // Add formatted order note
214 if ( $customer_notification ) {
215 $this->order->add_order_note( $this->format_order_note( $to, $sent_timestamp, $message, $status, $error ) );
216 }
217 }
218
219 /**
220 * Extract URLs from SMS message and replace them with shorten URLs via callback
221 *
222 * @since 1.0
223 * @param string $message SMS message
224 * @return string SMS message with URLs shortened
225 */
226 private function shorten_urls( $message ) {
227
228 // regex pattern source : http://daringfireball.net/2010/07/improved_regex_for_matching_urls
229 $pattern = "/(?i)\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»????]))/";
230
231 // find each URL and replacing using callback
232 $message = preg_replace_callback( $pattern, array( $this, 'shorten_url' ), $message );
233
234 // return message with shortened URLs
235 return $message;
236 }
237
238 /**
239 * Callback for shorten_urls() preg_replace
240 * By default, uses Google URL Shortener
241 *
242 * @since 1.0
243 * @param array $matches matches found via preg_replace
244 * @return string shortened url
245 */
246 private function shorten_url( $matches ) {
247
248 // get first match
249 $url = reset( $matches );
250
251 $api_key = get_option( 'wc_twilio_sms_shortener_api_key', '' );
252
253 if ( $api_key ) {
254 $shortened_url = $this->google_shorten_url( $url, $api_key );
255 } else {
256 $shortened_url = $url;
257 }
258
259 /**
260 * Filters the a shortened URL.
261 *
262 * @since 1.8.2
263 * @param string $shortened_url the shortened URL
264 * @param string $url the original URL
265 */
266 return apply_filters( 'wc_twilio_sms_shorten_url', $shortened_url, $url );
267 }
268
269
270 /**
271 * Shortens a given URL via Google URL Shortener
272 *
273 * @link : https://developers.google.com/url-shortener/v1/getting_started
274 * @since 1.0
275 * @param string $url URL to shorten
276 * @param string $api_key the API key
277 * @return string shortened URL
278 */
279 private function google_shorten_url( $url, $api_key ) {
280
281 $api_url = add_query_arg( 'key', $api_key, 'https://www.googleapis.com/urlshortener/v1/url' );
282
283 // set wp_safe_remote_post arguments
284 $args = array(
285 'method' => 'POST',
286 'timeout' => '10',
287 'redirection' => 0,
288 'httpversion' => '1.0',
289 'sslverify' => true,
290 'headers' => array(
291 'content-type' => 'application/json',
292 ),
293 'body' => json_encode( array(
294 'longUrl' => $url,
295 ) ),
296 );
297
298 // perform POST request
299 $response = wp_safe_remote_post( $api_url, $args );
300
301 $error_message = __( 'URL Shortener error', 'woocommerce-twilio-sms-notifications' );
302
303 // check for WP Error and bail
304 if ( is_wp_error( $response ) ) {
305
306 // log the error
307 wc_twilio_sms()->log( $error_message . ': ' . $response->get_error_message() );
308
309 return $url;
310 }
311
312 $data = json_decode( wp_remote_retrieve_body( $response ), true );
313
314 // check for an API error
315 if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
316
317 if ( ! empty( $data['error']['errors'] ) ) {
318
319 foreach ( $data['error']['errors'] as $error ) {
320
321 // append an error message if it was provided
322 if ( ! empty( $error['message'] ) ) {
323 $error_message .= ': ' . $error['message'];
324 }
325
326 // append the reason code if it was provided
327 if ( ! empty( $error['reason'] ) ) {
328 $error_message .= ' (' . $error['reason'] . ')';
329 }
330
331 wc_twilio_sms()->log( $error_message );
332 }
333
334 } else {
335
336 wc_twilio_sms()->log( $error_message );
337 }
338
339 return $url;
340 }
341
342 // if short url was decoded successfully, use it
343 if ( ! empty( $data['id'] ) ) {
344 $url = $data['id'];
345 }
346
347 return $url;
348 }
349
350
351 /**
352 * Replaces template variables in SMS message
353 *
354 * @since 1.0
355 * @param string $message raw SMS message to replace with variable info
356 * @return string message with variables replaced with indicated values
357 */
358 private function replace_message_variables( $message ) {
359
360 $replacements = array(
361 '%shop_name%' => SV_WC_Helper::get_site_name(),
362 '%order_id%' => $this->order->get_order_number(),
363 '%order_count%' => $this->order->get_item_count(),
364 '%order_amount%' => $this->order->get_total(),
365 '%order_status%' => ucfirst( $this->order->get_status() ),
366 '%billing_name%' => $this->order->get_formatted_billing_full_name(),
367 '%billing_phone%' => $this->order->get_billing_phone(),
368 '%shipping_name%' => $this->order->get_formatted_shipping_full_name(),
369 '%shipping_method%' => $this->order->get_shipping_method(),
370 '%billing_first%' => SV_WC_Order_Compatibility::get_prop( $this->order, 'billing_first_name' ),
371 '%billing_last%' => SV_WC_Order_Compatibility::get_prop( $this->order, 'billing_last_name' ),
372 );
373
374 /**
375 * Filter the notification placeholders and replacements.
376 *
377 * @since 1.6.0
378 * @param array $replacements {
379 * The replacements in 'placeholder' => 'replacement' format.
380 *
381 * @type string %shop_name% The site name.
382 * @type int %order_id% The order ID.
383 * @type int %order_count% The total number of items ordered.
384 * @type string %order_amount% The order total.
385 * @type string %order_status% The order status.
386 * @type string %billing_name% The billing first and last name.
387 * @type string %shipping_name% The shipping first and last name.
388 * @type string %shipping_method% The shipping method name.
389 * }
390 * @param WC_Twilio_SMS_Notification $notification The notification object.
391 */
392 $replacements = apply_filters( 'wc_twilio_sms_message_replacements', $replacements, $this );
393
394 return str_replace( array_keys( $replacements ), $replacements, $message );
395 }
396
397
398 /**
399 * Formats order note
400 *
401 * @since 1.0
402 * @param string $to number SMS message was sent to
403 * @param int $sent_timestamp integer timestamp for when message was sent
404 * @param string $message SMS message sent
405 * @param string $status order status
406 * @param bool $error true if there was an error sending SMS, false otherwise
407 * @return string HTML-formatted order note
408 */
409 private function format_order_note( $to, $sent_timestamp, $message, $status, $error ) {
410
411 try {
412
413 // get datetime object from unix timestamp
414 $datetime = new DateTime( "@{$sent_timestamp}", new DateTimeZone( 'UTC' ) );
415
416 // change timezone to site timezone
417 $datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
418
419 // return datetime localized to site date/time settings
420 $formatted_datetime = date_i18n( wc_date_format() . ' ' . wc_time_format(), $sent_timestamp + $datetime->getOffset() );
421
422 } catch ( Exception $e ) {
423
424 // log error and set datetime for SMS to 'N/A'
425 wc_twilio_sms()->log( $e->getMessage() );
426 $formatted_datetime = __( 'N/A', 'woocommerce-twilio-sms-notifications' );
427 }
428
429 ob_start();
430 ?>
431 <p><strong><?php esc_html_e( 'SMS Notification', 'woocommerce-twilio-sms-notifications' ); ?></strong></p>
432 <p><strong><?php esc_html_e( 'To', 'woocommerce-twilio-sms-notifications' ); ?>: </strong><?php echo esc_html( $to ); ?></p>
433 <p><strong><?php esc_html_e( 'Date Sent', 'woocommerce-twilio-sms-notifications' ); ?>: </strong><?php echo esc_html( $formatted_datetime ); ?></p>
434 <p><strong><?php esc_html_e( 'Message', 'woocommerce-twilio-sms-notifications' ); ?>: </strong><?php echo esc_html( $message ); ?></p>
435 <p><strong><?php esc_html_e( 'Status', 'woocommerce-twilio-sms-notifications' ); ?>: <span style="<?php echo ( $error ) ? 'color: red;' : 'color: green;'; ?>"><?php echo esc_html( $status ); ?></span></strong></p>
436 <?php
437
438 return ob_get_clean();
439 }
440
441
442}