PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
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 13.7.2 All 507 releases
jetpack / jetpack_vendor / automattic / jetpack-forms / src / service / class-post-to-url.php

class-post-to-url.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-forms/src/service/class-post-to-url.php

196 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Post to URL using Jetpack Contact Forms.
4 *
5 * @package automattic/jetpack
6 */
7
8 namespace Automattic\Jetpack\Forms\Service;
9
10 use WP_Error;
11
12 /**
13 * Class Post_To_Url
14 *
15 * Hooks on Jetpack's Contact form to post form data to some URL.
16 */
17 class Post_To_Url {
18 /**
19 * Singleton instance
20 *
21 * @var Post_To_Url
22 */
23 private static $instance = null;
24
25 /**
26 * Initialize and return singleton instance.
27 *
28 * @return Post_To_Url
29 */
30 public static function init() {
31 if ( null === self::$instance ) {
32 self::$instance = new self();
33 }
34
35 return self::$instance;
36 }
37
38 /**
39 * Post_To_Url class constructor.
40 * Hooks on `grunion_after_feedback_post_inserted` action to send form data to specified URL.
41 * NOTE: As a singleton, this constructor is private and only callable from ::init, which will return the singleton instance,
42 * effectively preventing multiple instances of this class (hence, multiple hooks triggering the POST request).
43 */
44 private function __construct() {
45 add_action( 'grunion_after_feedback_post_inserted', array( $this, 'feedback_post_hook' ), 10, 4 );
46 }
47
48 /**
49 * Get the setup for the post to URL.
50 *
51 * Salesforce-only: posts to the fixed Salesforce Web-to-Lead endpoint when
52 * the form has a salesforceData.organizationId attribute. The legacy
53 * postToUrl override is intentionally NOT honored here — postToUrl is
54 * deprecated and the new pipeline (Form_Webhooks) already handles it with
55 * proper URL validation. Honoring it here too would let an Editor with
56 * Salesforce enabled override the destination to an arbitrary URL,
57 * including internal/cloud-metadata endpoints (SSRF).
58 *
59 * @param array $attributes - the attributes of the contact form.
60 * @return array|bool Array setup, or false if Salesforce isn't configured.
61 */
62 private function get_setup( $attributes = array() ) {
63 if ( empty( $attributes['salesforceData']['organizationId'] ) ) {
64 return false;
65 }
66
67 return array(
68 'url' => 'https://webto.salesforce.com/servlet/servlet.WebToLead?encoding=UTF-8',
69 'format' => 'urlencoded',
70 );
71 }
72
73 /**
74 * Hook on `grunion_after_feedback_post_inserted` action to send form data to specified URL.
75 *
76 * @param int $post_id - the post_id for the CPT that is created.
77 * @param array $fields - a collection of Automattic\Jetpack\Forms\ContactForm\Contact_Form_Field instances.
78 * @param bool $is_spam - marked as spam by Akismet(?).
79 * @param array $entry_values - extra fields added to from the contact form.
80 *
81 * @return null|void
82 */
83 public function feedback_post_hook( $post_id, $fields, $is_spam, $entry_values ) {
84 // Try and get the form from any of the fields
85 $form = null;
86 foreach ( $fields as $field ) {
87 if ( ! empty( $field->form ) ) {
88 $form = $field->form;
89 break;
90 }
91 }
92 if ( ! $form || ! is_a( $form, 'Automattic\Jetpack\Forms\ContactForm\Contact_Form' ) ) {
93 return;
94 }
95
96 // if spam (hinted by akismet?), don't process
97 if ( $is_spam ) {
98 return;
99 }
100
101 $setup = $this->get_setup( $form->attributes );
102
103 if ( ! $setup ) {
104 return;
105 }
106
107 $form_data = $this->get_form_data( $form, $fields, $entry_values );
108
109 $result = $this->post_to_url( $form_data, $setup );
110
111 if ( is_wp_error( $result ) ) {
112 // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- figuring out what to do with the error.
113 $message = sprintf(
114 'JETPACK %s - Jetpack Forms: POSTing to URL failed: "%s" at %s',
115 constant( 'JETPACK__VERSION' ),
116 $result->get_error_message(),
117 $entry_values['entry_permalink']
118 );
119 // TODO: not sure what to do with the error. Is not useful at frontend and it would be difficult to
120 // solve for a non tech-savvy user. We should log it somewhere, but it could turn messy.
121 // Maybe email the owner?
122 }
123 }
124
125 /**
126 * POST to URL
127 *
128 * @param array $data The data key/value pairs to send in POST.
129 * @param array $options Options for POST.
130 *
131 * @return array|WP_Error The result value from wp_safe_remote_post
132 *
133 * TODO: do complex fields (MC, etc) need to be handled differently? JSON should be fine, but URLencoded might need to be serialized.
134 */
135 private function post_to_url( $data, $options = array() ) {
136 global $wp_version;
137
138 $user_agent = "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ) . '; ' . get_bloginfo( 'url' );
139 $format = $options['format'] === 'urlencoded' ? 'application/x-www-form-urlencoded' : 'application/json';
140 $args = array(
141 'body' => $data,
142 'headers' => array(
143 'Content-Type' => $format,
144 'user-agent' => $user_agent,
145 ),
146 );
147 return wp_safe_remote_post( $options['url'], $args );
148 }
149
150 /**
151 * Gather fields key/value pairs from the form
152 * Sanitizes the hidden fields values
153 *
154 * @param \Automattic\Jetpack\Forms\ContactForm\Contact_Form $form The form instance being processed/submitted.
155 * @param array $visible_fields Visible submitted fields.
156 * @param array $entry_values The feedback entry values.
157 */
158 private function get_form_data( $form, $visible_fields, $entry_values ) {
159 $fields = array();
160 foreach ( $visible_fields as $field ) {
161 $fields[ $field->get_attribute( 'id' ) ] = $field->value;
162 }
163
164 // Right in the middle, backwards compatibility for salesforceData implementation.
165 $salesforce_data = (array) ( $form->attributes['salesforceData'] ?? array() );
166 if ( ! empty( $salesforce_data['organizationId'] ) ) {
167 $fields['oid'] = sanitize_text_field( $salesforce_data['organizationId'] );
168 $fields['lead_source'] = $entry_values['entry_permalink'];
169 }
170
171 // `hiddenFields` is a legacy attribute that may appear in a few shapes on forms
172 // in the wild: an array of `{ name, value }` objects (its original design), an
173 // associative `name => value` map, or a JSON-encoded string. Iterating it blindly
174 // and accessing `['name']`/`['value']` on a non-array element fatals on PHP 8 with
175 // "Cannot access offset of type string on string", so normalize defensively.
176 $hidden_fields = $form->attributes['hiddenFields'] ?? array();
177 if ( is_string( $hidden_fields ) ) {
178 $decoded = json_decode( $hidden_fields, true );
179 $hidden_fields = is_array( $decoded ) ? $decoded : array();
180 }
181 foreach ( (array) $hidden_fields as $key => $hidden_field ) {
182 if ( is_array( $hidden_field ) ) {
183 // Original `{ name, value }` object shape.
184 if ( isset( $hidden_field['name'] ) ) {
185 $fields[ $hidden_field['name'] ] = sanitize_text_field( $hidden_field['value'] ?? '' );
186 }
187 } elseif ( ! is_int( $key ) ) {
188 // Associative `name => value` shape.
189 $fields[ $key ] = sanitize_text_field( (string) $hidden_field );
190 }
191 }
192
193 return $fields;
194 }
195 }
196