PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
jetpack / jetpack_vendor / automattic / jetpack-forms / src / service / class-mailpoet-integration.php

class-mailpoet-integration.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3, at jetpack_vendor/automattic/jetpack-forms/src/service/class-mailpoet-integration.php

327 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MailPoet Integration for Jetpack Contact Forms.
4 *
5 * @package automattic/jetpack
6 */
7
8 namespace Automattic\Jetpack\Forms\Service;
9
10 use Automattic\Jetpack\Forms\ContactForm\Feedback;
11
12 /**
13 * Class MailPoet_Integration
14 *
15 * Handles integration with MailPoet for Jetpack Contact Forms.
16 */
17 class MailPoet_Integration {
18 /**
19 * MailPoet API instance
20 *
21 * @var mixed
22 */
23 protected static $mailpoet_api = null;
24
25 /**
26 * Get the MailPoet API instance (v1), instantiating if necessary.
27 *
28 * @return mixed
29 */
30 protected static function get_api() {
31 if ( null === self::$mailpoet_api && class_exists( '\MailPoet\API\API' ) ) {
32 // @phan-suppress-next-line PhanUndeclaredClassMethod
33 self::$mailpoet_api = \MailPoet\API\API::MP( 'v1' );
34 }
35 return self::$mailpoet_api;
36 }
37
38 /**
39 * Get or create a MailPoet list for Jetpack Forms.
40 *
41 * @param mixed $mailpoet_api The MailPoet API instance.
42 * @param string|null $list_id Optional. The ID of the list to use if it exists.
43 * @param string|null $list_name Optional. The name of the list to create if no ID is provided. Defaults to 'Jetpack Form Subscribers'.
44 * @return string|null List ID or null on failure.
45 */
46 protected static function get_or_create_list_id( $mailpoet_api, $list_id = null, $list_name = null ) {
47 // 1. If listId is provided, check if it exists
48 if ( $list_id ) {
49 try {
50 $lists = $mailpoet_api->getLists();
51 foreach ( $lists as $list ) {
52 if ( (string) $list['id'] === (string) $list_id && empty( $list['deleted_at'] ) ) {
53 return $list['id'];
54 }
55 }
56 } catch ( \Exception $e ) { // phpcs:ignore Squiz.PHP.EmptyCatchComment,Generic.CodeAnalysis.EmptyStatement.DetectedCatch
57 // Intentionally empty: fall through to next step
58 }
59 }
60
61 // 2. If listName is provided, create a new list
62 if ( $list_name ) {
63 try {
64 $new_list = $mailpoet_api->addList(
65 array(
66 'name' => $list_name,
67 'description' => 'Created by Jetpack Forms',
68 )
69 );
70 return $new_list['id'];
71 } catch ( \Exception $e ) { // phpcs:ignore Squiz.PHP.EmptyCatchComment,Generic.CodeAnalysis.EmptyStatement.DetectedCatch
72 // Intentionally empty: fall through to default
73 }
74 }
75
76 // 3. Fallback: use or create the default list
77 $default_list_name = 'Jetpack Forms';
78 $default_list_description = __( 'Subscribers from Jetpack Forms', 'jetpack-forms' );
79 try {
80 $lists = $mailpoet_api->getLists();
81 foreach ( $lists as $list ) {
82 if ( $list['name'] === $default_list_name && empty( $list['deleted_at'] ) ) {
83 return $list['id'];
84 }
85 }
86 $new_list = $mailpoet_api->addList(
87 array(
88 'name' => $default_list_name,
89 'description' => $default_list_description,
90 )
91 );
92 return $new_list['id'];
93 } catch ( \Exception $e ) {
94 return null;
95 }
96 }
97
98 /**
99 * Add a subscriber to a MailPoet list.
100 *
101 * @param mixed $mailpoet_api The MailPoet API instance.
102 * @param string $list_id The MailPoet list ID.
103 * @param array $subscriber_data Associative array with at least 'email', optionally 'first_name', 'last_name'.
104 * @return array|null Subscriber data on success, or null on failure.
105 */
106 protected static function add_subscriber_to_list( $mailpoet_api, $list_id, $subscriber_data ) {
107 $email = $subscriber_data['email'];
108 try {
109 $existing = $mailpoet_api->getSubscriber( $email );
110
111 // Normalize "subscribed" status using MailPoet constant when available.
112 $status_subscribed = class_exists( '\MailPoet\Entities\SubscriberEntity' )
113 // @phan-suppress-next-line PhanUndeclaredClassConstant
114 ? \MailPoet\Entities\SubscriberEntity::STATUS_SUBSCRIBED
115 : 'subscribed';
116
117 // If already subscribed to list, do nothing.
118 if ( ! empty( $existing['subscriptions'] ) && is_array( $existing['subscriptions'] ) ) {
119 foreach ( $existing['subscriptions'] as $subscription ) {
120 if (
121 isset( $subscription['segment_id'] ) && isset( $subscription['status'] ) &&
122 (string) $subscription['segment_id'] === (string) $list_id &&
123 $status_subscribed === $subscription['status']
124 ) {
125 return $existing;
126 }
127 }
128 }
129
130 // Subscriber exists but is not on the target list, so add to list.
131 // If subscriber already confirmed ('subscribed'), do not resend confirmation.
132 $options = array();
133 if ( isset( $existing['status'] ) && $existing['status'] === $status_subscribed ) {
134 $options['send_confirmation_email'] = false;
135 }
136
137 return $mailpoet_api->subscribeToLists( $email, array( $list_id ), $options );
138 } catch ( \Exception $e ) {
139 // MailPoet returns APIException code 4 if "subscriber does not exist".
140 // In that case, take no action and next try statement will add subscriber.
141 // For other exceptions, return null.
142 $not_found_code = 4;
143 if ( method_exists( $e, 'getCode' ) && (int) $e->getCode() !== $not_found_code ) {
144 return null;
145 }
146 }
147
148 // Subscriber does not exist, so add new subscriber to list and send confirmation email.
149 try {
150 return $mailpoet_api->addSubscriber( $subscriber_data, array( $list_id ) );
151 } catch ( \Exception $e ) {
152 return null;
153 }
154 }
155
156 /**
157 * Extract subscriber data (email, first_name, last_name) from form fields.
158 * Once refactored form storage is in place, use get_subscriber_data() instead.
159 *
160 * @param array $fields Collection of Contact_Form_Field instances.
161 * @return array Associative array with at least 'email', optionally 'first_name', 'last_name'. Empty array if no email found.
162 */
163 protected static function get_subscriber_data_from_fields( $fields ) {
164 $subscriber_data = array();
165 foreach ( $fields as $field ) {
166 $type = strtolower( (string) $field->get_attribute( 'type' ) );
167 $id = strtolower( str_replace( array( ' ', '_' ), '', (string) $field->get_attribute( 'id' ) ) );
168 $label = strtolower( str_replace( array( ' ', '_' ), '', (string) $field->get_attribute( 'label' ) ) );
169
170 // If value is not a string, we already know it's not a valid name or email.
171 if ( ! is_string( $field->value ) ) {
172 continue;
173 }
174
175 $value = trim( $field->value );
176
177 if ( ( $type === 'email' || $id === 'email' || $label === 'email' ) && ! empty( $value ) ) {
178 $subscriber_data['email'] = $value;
179 } elseif ( ( $id === 'firstname' || $label === 'firstname' ) && ! empty( $value ) ) {
180 $subscriber_data['first_name'] = $value;
181 } elseif ( ( $id === 'lastname' || $label === 'lastname' ) && ! empty( $value ) ) {
182 $subscriber_data['last_name'] = $value;
183 }
184 }
185
186 if ( empty( $subscriber_data['email'] ) ) {
187 return array();
188 }
189
190 return $subscriber_data;
191 }
192
193 /**
194 * Extract subscriber data (email, first_name, last_name) from form fields.
195 *
196 * @param Feedback $feedback Feedback object for the submission.
197 * @return array Associative array with at least 'email', optionally 'first_name', 'last_name'. Empty array if no email found.
198 */
199 protected static function get_subscriber_data( $feedback ) {
200 if ( ! $feedback->get_author_email() ) {
201 return array();
202 }
203
204 // Get email using new Feedback API.
205 $subscriber_data = array();
206 $subscriber_data['email'] = $feedback->get_author_email();
207
208 // Try getting first and name from Feedback API.
209 if ( $feedback->get_field_value_by_label( 'First Name' ) ) {
210 $subscriber_data['first_name'] = $feedback->get_field_value_by_label( 'First Name' );
211 } elseif ( $feedback->get_field_value_by_form_field_id( 'firstname' ) ) {
212 $subscriber_data['first_name'] = $feedback->get_field_value_by_form_field_id( 'firstname' );
213 } elseif ( $feedback->get_field_value_by_form_field_id( 'first-name' ) ) {
214 $subscriber_data['first_name'] = $feedback->get_field_value_by_form_field_id( 'first-name' );
215 }
216 if ( $feedback->get_field_value_by_label( 'Last Name' ) ) {
217 $subscriber_data['last_name'] = $feedback->get_field_value_by_label( 'Last Name' );
218 } elseif ( $feedback->get_field_value_by_form_field_id( 'lastname' ) ) {
219 $subscriber_data['last_name'] = $feedback->get_field_value_by_form_field_id( 'lastname' );
220 } elseif ( $feedback->get_field_value_by_form_field_id( 'last-name' ) ) {
221 $subscriber_data['last_name'] = $feedback->get_field_value_by_form_field_id( 'last-name' );
222 }
223
224 return $subscriber_data;
225 }
226
227 /**
228 * Handle MailPoet integration after feedback post is inserted.
229 *
230 * @param int $post_id The post ID for the feedback CPT.
231 * @param array $fields Collection of Contact_Form_Field instances.
232 * @param bool $is_spam Whether the submission is spam.
233 */
234 public static function handle_mailpoet_integration( $post_id, $fields, $is_spam ) {
235 if ( $is_spam ) {
236 return;
237 }
238
239 // Try and get the form from any of the fields
240 $form = null;
241 foreach ( $fields as $field ) {
242 if ( ! empty( $field->form ) ) {
243 $form = $field->form;
244 break;
245 }
246 }
247 if ( ! $form || ! is_a( $form, 'Automattic\Jetpack\Forms\ContactForm\Contact_Form' ) ) {
248 return;
249 }
250
251 if ( empty( $form->attributes['mailpoet']['enabledForForm'] ?? null ) ) {
252 return;
253 }
254
255 $feedback = Feedback::get( $post_id );
256 if ( ! $feedback ) {
257 return;
258 }
259
260 $uses_feedback_api = $feedback->uses_structured_fields();
261
262 if ( $uses_feedback_api ) {
263 if ( $feedback->has_field_type( 'consent' ) && ! $feedback->has_consent() ) {
264 return;
265 }
266 } else {
267 $consent_field = null;
268 if ( is_array( $fields ) ) {
269 foreach ( $fields as $form_field ) {
270 if ( 'consent' === $form_field->get_attribute( 'type' ) ) {
271 $consent_field = $form_field;
272 break;
273 }
274 }
275 }
276 if ( $consent_field ) {
277 $consent_type = strtolower( (string) $consent_field->get_attribute( 'consenttype' ) );
278 if ( 'explicit' === $consent_type && ! $consent_field->value ) {
279 return;
280 }
281 }
282 }
283
284 $mailpoet_api = self::get_api();
285 if ( ! $mailpoet_api ) {
286 // MailPoet is not active or not loaded.
287 return;
288 }
289
290 // Get listId and listName from the mailpoet attribute
291 $mailpoet_attr = is_array( $form->attributes['mailpoet'] ) ? $form->attributes['mailpoet'] : array();
292 $list_id = $mailpoet_attr['listId'] ?? null;
293 $list_name = $mailpoet_attr['listName'] ?? null;
294
295 $list_id = self::get_or_create_list_id( $mailpoet_api, $list_id, $list_name );
296 if ( ! $list_id ) {
297 // Could not get or create the list; bail out.
298 return;
299 }
300
301 $subscriber_data = $uses_feedback_api ? self::get_subscriber_data( $feedback ) : self::get_subscriber_data_from_fields( $fields );
302 if ( empty( $subscriber_data ) ) {
303 // Email is required for MailPoet subscribers.
304 return;
305 }
306
307 self::add_subscriber_to_list( $mailpoet_api, $list_id, $subscriber_data );
308 }
309
310 /**
311 * Get all MailPoet lists.
312 *
313 * @return array List of MailPoet lists, or empty array on failure.
314 */
315 public static function get_all_lists() {
316 $mailpoet_api = self::get_api();
317 if ( ! $mailpoet_api ) {
318 return array();
319 }
320 try {
321 return $mailpoet_api->getLists();
322 } catch ( \Exception $e ) {
323 return array();
324 }
325 }
326 }
327