PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.86
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.86
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
← All changes | includes/widgets/Form_Builder/helpers/Create_Submission.php +213 -37 51.1.65 → 51.1.86 View file →
@@ -5,11 +5,28 @@
5 5 if (!defined('ABSPATH')) {
6 6 exit;
7 7 }
8 8
9 +/**
10 + * Stores Form Builder submissions from the public AJAX endpoint.
11 + */
9 12 class Create_Submission
10 13 {
14 + /**
15 + * HMAC of the one-time access secret for this submission.
16 + */
17 + public const META_ACCESS_SECRET = 'king_addons_fb_access_secret';
11 18
19 + /**
20 + * Plain secrets issued in this request, keyed by submission ID.
21 + *
22 + * @var array<int,string>
23 + */
24 + private static array $issued_secrets = [];
25 +
26 + /**
27 + * Registers submission AJAX hooks and admin meta updates.
28 + */
12 29 public function __construct()
13 30 {
14 31 add_action('wp_ajax_king_addons_form_builder_submissions', [$this, 'add_to_submissions']);
15 32 add_action('wp_ajax_nopriv_king_addons_form_builder_submissions', [$this, 'add_to_submissions']);
@@ -15,8 +32,16 @@
15 32 add_action('wp_ajax_nopriv_king_addons_form_builder_submissions', [$this, 'add_to_submissions']);
16 33 add_action('save_post', [$this, 'update_submissions_post_meta']);
17 34 }
18 35
36 + /**
37 + * Creates a submission post from a public form request.
38 + *
39 + * Guests are allowed when the nonce is valid and the submitted page ID
40 + * belongs to a published Form Builder source page.
41 + *
42 + * @return void
43 + */
19 44 public function add_to_submissions()
20 45 {
21 46
22 47 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
@@ -26,61 +51,37 @@
26 51 'message' => esc_html__('Security check failed.', 'king-addons'),
27 52 ));
28 53 }
29 54
30 - // Add capability check
31 - if (!current_user_can('read')) {
55 + Form_Builder_Security::guard_spam();
56 +
57 + $sanitized_form_page_id = absint($_POST['form_page_id'] ?? 0);
58 + if (!Form_Builder_Security::is_valid_submission_page($sanitized_form_page_id)) {
32 59 wp_send_json_error(array(
33 60 'message' => esc_html__('Insufficient permissions.', 'king-addons'),
34 61 ));
35 62 }
36 63
37 - $new = [
38 - 'post_status' => 'publish',
39 - 'post_type' => 'king-addons-fb-sub'
40 - ];
41 -
42 - $post_id = wp_insert_post($new);
43 -
44 - // Security fix: Validate and sanitize form_content before saving to database
64 + $sanitized_form_name = sanitize_text_field(wp_unslash($_POST['form_name'] ?? ''));
45 65 $form_content = isset($_POST['form_content']) && is_array($_POST['form_content']) ? wp_unslash($_POST['form_content']) : [];
46 -
47 - foreach ($form_content as $key => $value) {
48 - if (!is_array($value) || count($value) < 3) {
49 - continue; // Skip malformed fields
50 - }
51 -
52 - // Sanitize all form field data before saving
53 - $sanitized_key = sanitize_key($key);
54 - $sanitized_value = [
55 - sanitize_text_field($value[0]), // field type
56 - is_array($value[1]) ? array_map('sanitize_text_field', $value[1]) : sanitize_text_field($value[1]), // field value
57 - sanitize_text_field($value[2]) // field label
58 - ];
59 -
60 - update_post_meta($post_id, $sanitized_key, $sanitized_value);
61 - }
62 -
63 - $sanitized_form_name = sanitize_text_field(wp_unslash($_POST['form_name'] ?? ''));
64 66 $sanitized_form_id = sanitize_key(wp_unslash($_POST['form_id'] ?? ''));
65 67 $sanitized_form_page = sanitize_text_field(wp_unslash($_POST['form_page'] ?? ''));
66 - $sanitized_form_page_id = absint($_POST['form_page_id'] ?? 0);
67 68
68 - update_post_meta($post_id, 'king_addons_form_name', $sanitized_form_name);
69 - update_post_meta($post_id, 'king_addons_form_id', $sanitized_form_id);
70 - update_post_meta($post_id, 'king_addons_form_page', $sanitized_form_page);
71 - update_post_meta($post_id, 'king_addons_form_page_id', $sanitized_form_page_id);
72 - $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_textarea_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
73 - update_post_meta($post_id, 'king_addons_user_agent', $user_agent);
74 - update_post_meta($post_id, 'king_addons_user_ip', Core::getClientIP());
69 + $post_id = self::insert_submission([
70 + 'form_name' => $sanitized_form_name,
71 + 'form_id' => $sanitized_form_id,
72 + 'form_page' => $sanitized_form_page,
73 + 'form_page_id' => $sanitized_form_page_id,
74 + 'form_content' => $form_content,
75 + ]);
75 76
76 77 if ($post_id) {
77 78 wp_send_json_success(array(
78 79 'action' => 'king_addons_form_builder_submissions',
79 80 'post_id' => $post_id,
81 + 'access_secret' => self::issued_access_secret($post_id),
80 82 'message' => esc_html__('Submission created successfully', 'king-addons'),
81 83 'status' => 'success'
82 - // Security fix: Removed unsanitized form_content from response to prevent XSS
83 84 ));
84 85 } else {
85 86 wp_send_json_success(array(
86 87 'action' => 'king_addons_form_builder_submissions',
@@ -90,8 +91,147 @@
90 91 ));
91 92 }
92 93 }
93 94
95 + /**
96 + * Insert a Form Builder submission from already-sanitized request pieces.
97 + *
98 + * @param array<string,mixed> $args {
99 + * @type string $form_name
100 + * @type string $form_id
101 + * @type string $form_page
102 + * @type int $form_page_id
103 + * @type array $form_content
104 + * }
105 + *
106 + * @return int Submission post ID, or 0.
107 + */
108 + public static function insert_submission(array $args): int
109 + {
110 + $form_name = sanitize_text_field((string) ($args['form_name'] ?? ''));
111 + $form_id = sanitize_key((string) ($args['form_id'] ?? ''));
112 + $form_page = sanitize_text_field((string) ($args['form_page'] ?? ''));
113 + $form_page_id = absint($args['form_page_id'] ?? 0);
114 + $form_content = isset($args['form_content']) && is_array($args['form_content']) ? $args['form_content'] : [];
115 +
116 + $post_id = wp_insert_post([
117 + 'post_status' => 'publish',
118 + 'post_type' => 'king-addons-fb-sub',
119 + 'post_title' => $form_name
120 + ? $form_name . ' - ' . current_time('mysql')
121 + : current_time('mysql'),
122 + ]);
123 +
124 + if (!$post_id || is_wp_error($post_id)) {
125 + return 0;
126 + }
127 +
128 + $post_id = (int) $post_id;
129 +
130 + foreach ($form_content as $key => $value) {
131 + if (!is_array($value) || count($value) < 3) {
132 + continue;
133 + }
134 +
135 + $sanitized_key = sanitize_key((string) $key);
136 + if ('' === $sanitized_key) {
137 + continue;
138 + }
139 +
140 + update_post_meta($post_id, $sanitized_key, [
141 + sanitize_text_field((string) $value[0]),
142 + self::sanitize_field_value($value[1]),
143 + sanitize_text_field((string) $value[2]),
144 + ]);
145 + }
146 +
147 + update_post_meta($post_id, 'king_addons_form_name', $form_name);
148 + update_post_meta($post_id, 'king_addons_form_id', $form_id);
149 + update_post_meta($post_id, 'king_addons_form_page', $form_page);
150 + update_post_meta($post_id, 'king_addons_form_page_id', $form_page_id);
151 + $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_textarea_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
152 + update_post_meta($post_id, 'king_addons_user_agent', $user_agent);
153 + update_post_meta($post_id, 'king_addons_user_ip', Core::getClientIP());
154 + self::issue_access_secret($post_id);
155 +
156 + return $post_id;
157 + }
158 +
159 + /**
160 + * Create a secret that later public requests must present to touch this submission.
161 + *
162 + * Only the HMAC is stored. The plaintext is kept for this request so the
163 + * creator can send it with the payment call.
164 + *
165 + * @param int $submission_id Submission post ID.
166 + * @return string Plaintext secret.
167 + */
168 + public static function issue_access_secret(int $submission_id): string
169 + {
170 + if ($submission_id < 1) {
171 + return '';
172 + }
173 +
174 + try {
175 + $secret = bin2hex(random_bytes(32));
176 + } catch (\Exception $e) {
177 + $secret = wp_generate_password(64, false, false);
178 + }
179 +
180 + update_post_meta($submission_id, self::META_ACCESS_SECRET, hash_hmac('sha256', $secret, self::access_secret_key()));
181 + self::$issued_secrets[$submission_id] = $secret;
182 +
183 + return $secret;
184 + }
185 +
186 + /**
187 + * Plaintext secret issued for this submission in the current request.
188 + *
189 + * @param int $submission_id Submission post ID.
190 + * @return string
191 + */
192 + public static function issued_access_secret(int $submission_id): string
193 + {
194 + return self::$issued_secrets[$submission_id] ?? '';
195 + }
196 +
197 + /**
198 + * Whether the posted secret matches the one stored for this submission.
199 + *
200 + * @param int $submission_id Submission post ID.
201 + * @param string $secret Plaintext from the request.
202 + * @return bool
203 + */
204 + public static function verify_access_secret(int $submission_id, string $secret): bool
205 + {
206 + if ($submission_id < 1 || '' === $secret) {
207 + return false;
208 + }
209 +
210 + $stored = (string) get_post_meta($submission_id, self::META_ACCESS_SECRET, true);
211 + if ('' === $stored) {
212 + return false;
213 + }
214 +
215 + return hash_equals($stored, hash_hmac('sha256', $secret, self::access_secret_key()));
216 + }
217 +
218 + /**
219 + * Key used to HMAC submission access secrets.
220 + *
221 + * @return string
222 + */
223 + private static function access_secret_key(): string
224 + {
225 + return 'king-addons-fb-sub|' . wp_salt('auth');
226 + }
227 +
228 + /**
229 + * Saves admin edits to an existing submission.
230 + *
231 + * @param int $post_id Submission post ID.
232 + * @return void
233 + */
94 234 public function update_submissions_post_meta($post_id)
95 235 {
96 236 // Security fix: Validate nonce and capabilities
97 237 if (!current_user_can('edit_post', $post_id)) {
@@ -123,8 +263,44 @@
123 263
124 264 update_post_meta($post_id, $sanitized_key, $sanitized_value);
125 265 }
126 266 }
267 + }
268 +
269 + /**
270 + * Sanitize a submitted field value.
271 + *
272 + * Radio and checkbox groups arrive as rows of [value, checked, name, id].
273 + * A flat array_map(sanitize_text_field) turns each row into an empty
274 + * string, so the saved submission lost what the visitor actually picked.
275 + *
276 + * @param mixed $raw Raw value from form_content.
277 + *
278 + * @return string|array<int,string>
279 + */
280 + private static function sanitize_field_value($raw)
281 + {
282 + if (!is_array($raw)) {
283 + return sanitize_text_field((string) $raw);
284 + }
285 +
286 + if (isset($raw[0]) && is_array($raw[0])) {
287 + $picked = [];
288 + foreach ($raw as $row) {
289 + if (!is_array($row)) {
290 + continue;
291 + }
292 + $option = sanitize_text_field((string) ($row[0] ?? ''));
293 + $checked = !empty($row[1]) && 'false' !== (string) $row[1] && '0' !== (string) $row[1];
294 + if ($checked && '' !== $option) {
295 + $picked[] = $option;
296 + }
297 + }
298 +
299 + return implode(', ', $picked);
300 + }
301 +
302 + return array_values(array_map('sanitize_text_field', $raw));
127 303 }
128 304 }
129 305
130 306 new Create_Submission();