PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.2
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.2
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-forms / src / service / class-mailpoet-integration.php
jetpack / jetpack_vendor / automattic / jetpack-forms / src / service Last commit date
class-form-webhooks.php 1 month ago class-google-drive.php 9 months ago class-hostinger-reach-integration.php 9 months ago class-mailpoet-integration.php 1 month ago class-post-to-url.php 1 month ago
class-mailpoet-integration.php
340 lines
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 // Try and get the form from any of the fields
165 $form = null;
166 foreach ( $fields as $field ) {
167 if ( ! empty( $field->form ) ) {
168 $form = $field->form;
169 break;
170 }
171 }
172 if ( ! $form || ! is_a( $form, 'Automattic\Jetpack\Forms\ContactForm\Contact_Form' ) ) {
173 return array();
174 }
175
176 $subscriber_data = array();
177 foreach ( $form->fields as $field ) {
178 $type = strtolower( (string) $field->get_attribute( 'type' ) );
179 $id = strtolower( str_replace( array( ' ', '_' ), '', (string) $field->get_attribute( 'id' ) ) );
180 $label = strtolower( str_replace( array( ' ', '_' ), '', (string) $field->get_attribute( 'label' ) ) );
181
182 // If value is not a string, we already know it's not a valid name or email.
183 if ( ! is_string( $field->value ) ) {
184 continue;
185 }
186
187 $value = trim( $field->value );
188
189 if ( ( $type === 'email' || $id === 'email' || $label === 'email' ) && ! empty( $value ) ) {
190 $subscriber_data['email'] = $value;
191 } elseif ( ( $id === 'firstname' || $label === 'firstname' ) && ! empty( $value ) ) {
192 $subscriber_data['first_name'] = $value;
193 } elseif ( ( $id === 'lastname' || $label === 'lastname' ) && ! empty( $value ) ) {
194 $subscriber_data['last_name'] = $value;
195 }
196 }
197
198 if ( empty( $subscriber_data['email'] ) ) {
199 return array();
200 }
201
202 return $subscriber_data;
203 }
204
205 /**
206 * Extract subscriber data (email, first_name, last_name) from form fields.
207 *
208 * @param Feedback $feedback Feedback object for the submission.
209 * @return array Associative array with at least 'email', optionally 'first_name', 'last_name'. Empty array if no email found.
210 */
211 protected static function get_subscriber_data( $feedback ) {
212 if ( ! $feedback->get_author_email() ) {
213 return array();
214 }
215
216 // Get email using new Feedback API.
217 $subscriber_data = array();
218 $subscriber_data['email'] = $feedback->get_author_email();
219
220 // Try getting first and name from Feedback API.
221 if ( $feedback->get_field_value_by_label( 'First Name' ) ) {
222 $subscriber_data['first_name'] = $feedback->get_field_value_by_label( 'First Name' );
223 } elseif ( $feedback->get_field_value_by_form_field_id( 'firstname' ) ) {
224 $subscriber_data['first_name'] = $feedback->get_field_value_by_form_field_id( 'firstname' );
225 } elseif ( $feedback->get_field_value_by_form_field_id( 'first-name' ) ) {
226 $subscriber_data['first_name'] = $feedback->get_field_value_by_form_field_id( 'first-name' );
227 }
228 if ( $feedback->get_field_value_by_label( 'Last Name' ) ) {
229 $subscriber_data['last_name'] = $feedback->get_field_value_by_label( 'Last Name' );
230 } elseif ( $feedback->get_field_value_by_form_field_id( 'lastname' ) ) {
231 $subscriber_data['last_name'] = $feedback->get_field_value_by_form_field_id( 'lastname' );
232 } elseif ( $feedback->get_field_value_by_form_field_id( 'last-name' ) ) {
233 $subscriber_data['last_name'] = $feedback->get_field_value_by_form_field_id( 'last-name' );
234 }
235
236 return $subscriber_data;
237 }
238
239 /**
240 * Handle MailPoet integration after feedback post is inserted.
241 *
242 * @param int $post_id The post ID for the feedback CPT.
243 * @param array $fields Collection of Contact_Form_Field instances.
244 * @param bool $is_spam Whether the submission is spam.
245 */
246 public static function handle_mailpoet_integration( $post_id, $fields, $is_spam ) {
247 if ( $is_spam ) {
248 return;
249 }
250
251 // Try and get the form from any of the fields
252 $form = null;
253 foreach ( $fields as $field ) {
254 if ( ! empty( $field->form ) ) {
255 $form = $field->form;
256 break;
257 }
258 }
259 if ( ! $form || ! is_a( $form, 'Automattic\Jetpack\Forms\ContactForm\Contact_Form' ) ) {
260 return;
261 }
262
263 if ( empty( $form->attributes['mailpoet']['enabledForForm'] ?? null ) ) {
264 return;
265 }
266
267 $feedback = Feedback::get( $post_id );
268 if ( ! $feedback ) {
269 return;
270 }
271
272 $post = get_post( $post_id );
273 $is_v2_data = ( $post && $post->post_mime_type === 'v2' );
274
275 if ( $is_v2_data ) {
276 if ( $feedback->has_field_type( 'consent' ) && ! $feedback->has_consent() ) {
277 return;
278 }
279 } else {
280 $consent_field = null;
281 if ( is_array( $form->fields ) ) {
282 foreach ( $form->fields as $form_field ) {
283 if ( 'consent' === $form_field->get_attribute( 'type' ) ) {
284 $consent_field = $form_field;
285 break;
286 }
287 }
288 }
289 if ( $consent_field ) {
290 $consent_type = strtolower( (string) $consent_field->get_attribute( 'consenttype' ) );
291 if ( 'explicit' === $consent_type && ! $consent_field->value ) {
292 return;
293 }
294 }
295 }
296
297 $mailpoet_api = self::get_api();
298 if ( ! $mailpoet_api ) {
299 // MailPoet is not active or not loaded.
300 return;
301 }
302
303 // Get listId and listName from the mailpoet attribute
304 $mailpoet_attr = is_array( $form->attributes['mailpoet'] ) ? $form->attributes['mailpoet'] : array();
305 $list_id = $mailpoet_attr['listId'] ?? null;
306 $list_name = $mailpoet_attr['listName'] ?? null;
307
308 $list_id = self::get_or_create_list_id( $mailpoet_api, $list_id, $list_name );
309 if ( ! $list_id ) {
310 // Could not get or create the list; bail out.
311 return;
312 }
313
314 $subscriber_data = $is_v2_data ? self::get_subscriber_data( $feedback ) : self::get_subscriber_data_from_fields( $fields );
315 if ( empty( $subscriber_data ) ) {
316 // Email is required for MailPoet subscribers.
317 return;
318 }
319
320 self::add_subscriber_to_list( $mailpoet_api, $list_id, $subscriber_data );
321 }
322
323 /**
324 * Get all MailPoet lists.
325 *
326 * @return array List of MailPoet lists, or empty array on failure.
327 */
328 public static function get_all_lists() {
329 $mailpoet_api = self::get_api();
330 if ( ! $mailpoet_api ) {
331 return array();
332 }
333 try {
334 return $mailpoet_api->getLists();
335 } catch ( \Exception $e ) {
336 return array();
337 }
338 }
339 }
340