PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.63
Timetics – Appointment Booking Calendar & Scheduling v1.0.63
1.0.62 1.0.63 1.0.61 1.0.60 1.0.59 1.0.58 1.0.57 1.0.56 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 All 64 releases
timetics / vendor / themewinter / email-notification-sdk / src / Whatsapp / MetaCloudProvider.php

MetaCloudProvider.php in Timetics – Appointment Booking Calendar & Scheduling 1.0.63, at vendor/themewinter/email-notification-sdk/src/Whatsapp/MetaCloudProvider.php

273 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Ens\Whatsapp;
3
4 use Ens\Utils\Helpers;
5
6 /**
7 * Class MetaCloudProvider
8 *
9 * Built-in WhatsApp transport using Meta Graph Cloud API.
10 * Auto-registers on `notification_sdk_send_whatsapp` action.
11 *
12 * @package Ens\Whatsapp
13 *
14 * @since 1.0.0
15 */
16 class MetaCloudProvider {
17
18 const DEFAULT_API_VERSION = 'v25.0';
19 const TEXT_BODY_LIMIT = 4096;
20
21 protected $identifier;
22
23 public function __construct( $identifier ) {
24 $this->identifier = $identifier;
25 }
26
27 /**
28 * Register the dispatcher on the SDK send action.
29 *
30 * @since 1.0.0
31 */
32 public function register() {
33 add_action( 'notification_sdk_send_whatsapp', [ $this, 'dispatch' ], 10, 8 );
34 }
35
36 /**
37 * Dispatch the WhatsApp message to Meta Cloud API.
38 *
39 * Fires three prefixed hooks consumers can subscribe to for logging,
40 * metrics, or alerting:
41 * - `{hook_prefix}_ens_whatsapp_request` before the HTTP call
42 * - `{hook_prefix}_ens_whatsapp_send_success` on 2xx response
43 * - `{hook_prefix}_ens_whatsapp_send_error` on any failure path
44 *
45 * @since 1.0.0
46 *
47 * @param string $to Recipient phone number.
48 * @param string $message Body for text messages (ignored for templates).
49 * @param array $action_data Hook payload.
50 * @param string $action_name Triggering action name.
51 * @param string $receiver_type Receiver type key.
52 * @param int $count Iteration count.
53 * @param string $message_type `text` (default) or `template`.
54 * @param array|null $template Template config when $message_type=template.
55 */
56 public function dispatch( $to, $message, $action_data, $action_name, $receiver_type, $count, $message_type = 'text', $template = null ) {
57 $handled = apply_filters(
58 'ens_whatsapp_provider',
59 false,
60 $to,
61 $message,
62 $action_data,
63 $action_name,
64 $receiver_type,
65 $count,
66 $message_type,
67 $template
68 );
69 if ( true === $handled ) {
70 return;
71 }
72
73 $creds = $this->get_credentials();
74 if ( empty( $creds['access_token'] ) || empty( $creds['phone_number_id'] ) ) {
75 $this->fire_error( 'missing_credentials', $to, [], 0 );
76 return;
77 }
78
79 $to = $this->normalize_number( $to );
80 if ( '' === $to ) {
81 $this->fire_error( 'invalid_recipient', $to, [], 0 );
82 return;
83 }
84
85 if ( 'template' === $message_type ) {
86 $payload = $this->build_template_payload( $to, $template );
87 if ( null === $payload ) {
88 $this->fire_error( 'invalid_template_config', $to, is_array( $template ) ? $template : [], 0 );
89 return;
90 }
91 } else {
92 $body = trim( wp_strip_all_tags( $message ) );
93 if ( '' === $body ) {
94 return;
95 }
96 if ( strlen( $body ) > self::TEXT_BODY_LIMIT ) {
97 $body = substr( $body, 0, self::TEXT_BODY_LIMIT );
98 }
99 $payload = [
100 'messaging_product' => 'whatsapp',
101 'recipient_type' => 'individual',
102 'to' => $to,
103 'type' => 'text',
104 'text' => [ 'body' => $body ],
105 ];
106 }
107
108 $payload = apply_filters(
109 'ens_whatsapp_payload',
110 $payload,
111 $to,
112 $message,
113 $action_data,
114 $action_name,
115 $receiver_type,
116 $count,
117 $message_type,
118 $template
119 );
120
121 $version = apply_filters( 'ens_whatsapp_api_version', self::DEFAULT_API_VERSION, $this->identifier );
122 $endpoint = 'https://graph.facebook.com/' . rawurlencode( $version ) . '/' . rawurlencode( $creds['phone_number_id'] ) . '/messages';
123
124 do_action( Helpers::get_hook_name( $this->identifier, 'ens_whatsapp_request' ), $endpoint, $payload, $message_type, $to );
125
126 $response = wp_remote_post( $endpoint, [
127 'timeout' => 15,
128 'headers' => [
129 'Authorization' => 'Bearer ' . $creds['access_token'],
130 'Content-Type' => 'application/json',
131 ],
132 'body' => wp_json_encode( $payload ),
133 ] );
134
135 if ( is_wp_error( $response ) ) {
136 $this->fire_error( $response->get_error_message(), $to, $payload, 0 );
137 return;
138 }
139
140 $code = (int) wp_remote_retrieve_response_code( $response );
141 $response_body = wp_remote_retrieve_body( $response );
142
143 if ( $code >= 400 ) {
144 $this->fire_error( $response_body, $to, $payload, $code );
145 return;
146 }
147
148 do_action( Helpers::get_hook_name( $this->identifier, 'ens_whatsapp_send_success' ), $response_body, $to, $payload, $code );
149 }
150
151 /**
152 * Fire the prefixed send-error action with a uniform signature.
153 *
154 * @since 1.0.0
155 *
156 * @param mixed $error_body Raw response body or error string.
157 * @param string $to Recipient (possibly empty when normalization fails).
158 * @param array $payload Payload that would have been sent.
159 * @param int $http_code HTTP status (0 for setup/transport errors).
160 */
161 protected function fire_error( $error_body, $to, $payload, $http_code ) {
162 do_action(
163 Helpers::get_hook_name( $this->identifier, 'ens_whatsapp_send_error' ),
164 $error_body,
165 $to,
166 $payload,
167 (int) $http_code
168 );
169 }
170
171 /**
172 * Build a Meta Cloud API `type: template` payload.
173 *
174 * Returns null when the template config is missing required fields so the
175 * caller can fail closed and surface a diagnostic error.
176 *
177 * @since 1.0.0
178 *
179 * @param string $to Normalized recipient.
180 * @param array|null $template Template config (name, language, body_params).
181 *
182 * @return array|null
183 */
184 protected function build_template_payload( $to, $template ) {
185 if ( ! is_array( $template ) || empty( $template['name'] ) ) {
186 return null;
187 }
188
189 $payload = [
190 'messaging_product' => 'whatsapp',
191 'to' => $to,
192 'type' => 'template',
193 'template' => [
194 'name' => (string) $template['name'],
195 'language' => [
196 'code' => '' !== trim( (string) ( $template['language'] ?? '' ) )
197 ? (string) $template['language']
198 : 'en_US',
199 ],
200 ],
201 ];
202
203 if ( ! empty( $template['body_params'] ) && is_array( $template['body_params'] ) ) {
204 $parameters = array_values( array_filter( array_map(
205 function ( $value ) {
206 $value = trim( (string) $value );
207 return '' === $value ? null : [
208 'type' => 'text',
209 'text' => $value,
210 ];
211 },
212 $template['body_params']
213 ) ) );
214
215 if ( ! empty( $parameters ) ) {
216 $payload['template']['components'] = [
217 [
218 'type' => 'body',
219 'parameters' => $parameters,
220 ],
221 ];
222 }
223 }
224
225 return $payload;
226 }
227
228 /**
229 * Read credentials from the option store with consumer override filter.
230 *
231 * @since 1.0.0
232 *
233 * @return array
234 */
235 protected function get_credentials() {
236 $stored = get_option( $this->identifier . '_ens_whatsapp_settings', [] );
237 if ( ! is_array( $stored ) ) {
238 $stored = [];
239 }
240 $defaults = [
241 'access_token' => '',
242 'phone_number_id' => '',
243 ];
244 $creds = wp_parse_args( $stored, $defaults );
245
246 return apply_filters(
247 Helpers::get_hook_name( $this->identifier, 'ens_whatsapp_credentials' ),
248 $creds,
249 $this->identifier
250 );
251 }
252
253 /**
254 * Normalize phone number to E.164 digits-only (Meta Cloud API requirement).
255 *
256 * @since 1.0.0
257 *
258 * @param mixed $number
259 *
260 * @return string
261 */
262 protected function normalize_number( $number ) {
263 if ( ! is_scalar( $number ) ) {
264 return '';
265 }
266 $number = preg_replace( '/\D/', '', (string) $number );
267 if ( strlen( $number ) < 7 ) {
268 return '';
269 }
270 return $number;
271 }
272 }
273