PluginProbe
HTML Forms – Simple WordPress Forms Plugin / 1.7.0
HTML Forms – Simple WordPress Forms Plugin v1.7.0
1.7.0 trunk 1.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0 1.3.0 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 All 67 releases
← All changes | src/class-forms.php +77 -58 1.3.161.7.0 View file →
@@ -27,11 +27,13 @@
27 27 }
28 28
29 29 public function hook() {
30 30 add_action( 'init', array( $this, 'register' ) );
31 - add_action( 'init', array( $this, 'listen_for_submit' ) );
31 + add_action( 'wp_ajax_hf_form_submit', array( $this, 'listen_for_submit' ) );
32 + add_action( 'wp_ajax_nopriv_hf_form_submit', array( $this, 'listen_for_submit' ) );
33 + add_action( 'init', array( $this, 'register_assets' ) );
34 + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
32 35 add_action( 'parse_request', array( $this, 'listen_for_preview' ) );
33 - add_action( 'wp_enqueue_scripts', array( $this, 'assets' ) );
34 36 add_filter( 'hf_form_markup', 'hf_template' );
35 37 }
36 38
37 39 public function register() {
@@ -59,27 +61,41 @@
59 61
60 62 add_shortcode( 'hf_form', array( $this, 'shortcode' ) );
61 63 }
62 64
63 - public function assets() {
64 - $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
65 + public function register_assets() {
65 66 $assets_url = plugins_url( 'assets/', $this->plugin_file );
66 67
67 - wp_register_script( 'html-forms', $assets_url . "js/public{$suffix}.js", array(), HTML_FORMS_VERSION, true );
68 + wp_register_script( 'html-forms', $assets_url . 'js/public.js', array(), HTML_FORMS_VERSION, true );
68 69 wp_localize_script(
69 70 'html-forms',
70 71 'hf_js_vars',
71 72 array(
72 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
73 + 'ajax_url' => admin_url( 'admin-ajax.php?action=hf_form_submit' ),
73 74 )
74 75 );
75 76
77 + wp_register_style( 'html-forms', $assets_url . 'css/forms.css', array(), HTML_FORMS_VERSION );
78 + add_filter( 'script_loader_tag', array( $this, 'add_defer_attribute' ), 10, 2 );
79 + }
80 +
81 + public function enqueue_assets() {
76 82 if ( $this->settings['load_stylesheet'] ) {
77 - wp_enqueue_style( 'html-forms', $assets_url . "css/forms{$suffix}.css", array(), HTML_FORMS_VERSION );
83 + wp_enqueue_style( 'html-forms' );
78 84 }
79 85 }
80 86
81 87 /**
88 + * Adds defer attribute to our <script> element
89 + */
90 + public function add_defer_attribute( $tag, $handle ) {
91 + if ( $handle !== 'html-forms' ) {
92 + return $tag;
93 + }
94 +
95 + return str_replace( ' src=', ' defer src=', $tag );
96 + }
97 + /**
82 98 * @param Form $form
83 99 * @param array $data
84 100 * @return string
85 101 */
@@ -145,13 +161,13 @@
145 161 return '';
146 162 }
147 163
148 164 /**
149 - * Sanitize array with values before saving. Can be called recursively.
150 - *
151 - * @param mixed $value
152 - * @return mixed
153 - */
165 + * Sanitize array with values before saving. Can be called recursively.
166 + *
167 + * @param mixed $value
168 + * @return mixed
169 + */
154 170 public function sanitize( $value ) {
155 171 if ( is_string( $value ) ) {
156 172 // do nothing if empty string
157 173 if ( $value === '' ) {
@@ -189,15 +205,14 @@
189 205 return $value;
190 206 }
191 207
192 208 /**
193 - * @return array
194 - */
209 + * @return array
210 + */
195 211 public function get_request_data() {
196 212 $data = $_POST;
197 213
198 214 if ( ! empty( $_FILES ) ) {
199 -
200 215 foreach ( $_FILES as $field_name => $file ) {
201 216 // only add non-empty files so that required field validation works as expected
202 217 // upload could still have errored at this point
203 218 if ( $file['error'] !== UPLOAD_ERR_NO_FILE ) {
@@ -209,29 +224,43 @@
209 224 return $data;
210 225 }
211 226
212 227 public function listen_for_submit() {
228 + // Check nonce only if enabled in settings
229 + $nonce_check_failed = false;
230 + if ( $this->settings['enable_nonce'] ) {
231 + $nonce_check_failed = ! check_ajax_referer( 'html_forms_submit', '_wpnonce', false );
232 + }
233 +
234 + if ( $nonce_check_failed || empty( $_POST['_hf_form_id'] ) ) {
235 + wp_send_json(
236 + array(
237 + 'message' => array(
238 + 'type' => 'warning',
239 + 'text' => __( 'Something went wrong. Please reload the page and try again.', 'html-forms' ),
240 + ),
241 + 'error' => 'error',
242 + ),
243 + 200 );
244 + }
213 245
214 - // only respond to AJAX requests with _hf_form_id set.
215 - if ( empty( $_POST['_hf_form_id'] )
216 - || empty( $_SERVER['HTTP_X_REQUESTED_WITH'] )
217 - || strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) !== strtolower( 'XMLHttpRequest' ) ) {
218 - return;
219 - }
220 -
221 246 $data = $this->get_request_data();
222 247 $form_id = (int) $data['_hf_form_id'];
223 - $form = hf_get_form( $form_id );
248 + try {
249 + $form = hf_get_form( $form_id );
250 + } catch ( \Exception $e ) {
251 + return;
252 + }
224 253 $error_code = $this->validate_form( $form, $data );
254 + $submission = null;
225 255
226 256 if ( empty( $error_code ) ) {
227 -
228 257 /**
229 - * Filters the field names that should be ignored on the Submission object.
230 - * Fields starting with an underscore (_) are ignored by default.
231 - *
232 - * @param array $names
233 - */
258 + * Filters the field names that should be ignored on the Submission object.
259 + * Fields starting with an underscore (_) are ignored by default.
260 + *
261 + * @param array $names
262 + */
234 263 $ignored_field_names = apply_filters( 'hf_ignored_field_names', array() );
235 264
236 265 // filter out ignored field names
237 266 foreach ( $data as $key => $value ) {
@@ -241,9 +270,9 @@
241 270 }
242 271
243 272 // this detects the WPBruiser token field to ensure it isn't stored
244 273 // CAVEAT: this will detect any non-uppercase string with 2 dashes in the field name and no whitespace in the field value
245 - if ( class_exists('GoodByeCaptcha') && is_string( $key ) && is_string( $value ) && strtoupper( $key ) !== $key && substr_count( $key, '-' ) >= 2 && substr_count( trim( $value ), ' ' ) === 0 ) {
274 + if ( class_exists( 'GoodByeCaptcha' ) && is_string( $key ) && is_string( $value ) && strtoupper( $key ) !== $key && substr_count( $key, '-' ) >= 2 && substr_count( trim( $value ), ' ' ) === 0 ) {
246 275 unset( $data[ $key ] );
247 276 continue;
248 277 }
249 278 }
@@ -256,24 +285,24 @@
256 285 $submission->form_id = $form_id;
257 286 $submission->data = $data;
258 287 $submission->ip_address = ! empty( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : '';
259 288 $submission->user_agent = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) : '';
260 - $submission->referer_url = ! empty( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '';
289 + $submission->referer_url = ! empty( $_SERVER['HTTP_REFERER'] ) ? sanitize_url( $_SERVER['HTTP_REFERER'] ) : '';
261 290 $submission->submitted_at = gmdate( 'Y-m-d H:i:s' );
262 291
263 292 // save submission object so that other form processor have an insert ID to work with (eg file upload)
264 293 if ( $form->settings['save_submissions'] ) {
265 - $submission->save();
294 + $submission->save();
266 295 }
267 296
268 297 /**
269 - * General purpose hook that runs before all form actions, so we can still modify the submission object that is passed to actions.
270 - */
298 + * General purpose hook that runs before all form actions, so we can still modify the submission object that is passed to actions.
299 + */
271 300 do_action( 'hf_process_form', $form, $submission );
272 301
273 302 // re-save submission object for convenience in form processors hooked into hf_process_form
274 303 if ( $form->settings['save_submissions'] ) {
275 - $submission->save();
304 + $submission->save();
276 305 }
277 306
278 307 // process form actions
279 308 if ( isset( $form->settings['actions'] ) ) {
@@ -315,26 +344,10 @@
315 344 do_action( 'hf_form_error', $error_code, $form, $data );
316 345 }
317 346
318 347 // Delay response until "wp_loaded" hook to give other plugins a chance to process stuff.
319 - add_action(
320 - 'wp_loaded',
321 - function() use ( $error_code, $form, $data ) {
322 - $response = $this->get_response_for_error_code( $error_code, $form, $data );
323 -
324 - // clear output, some plugin or hooked code might have thrown errors by now.
325 - if ( ob_get_level() > 0 ) {
326 - ob_end_clean();
327 - }
328 -
329 - send_origin_headers();
330 - send_nosniff_header();
331 - nocache_headers();
332 -
333 - wp_send_json( $response, 200 );
334 - exit;
335 - }
336 - );
348 + $response = $this->get_response_for_error_code( $error_code, $form, $data, $submission );
349 + wp_send_json( $response, 200 );
337 350 }
338 351
339 352 public function listen_for_preview() {
340 353 if ( empty( $_GET['hf_preview_form'] ) || ! current_user_can( 'edit_forms' ) ) {
@@ -364,9 +377,9 @@
364 377 }
365 378 );
366 379 }
367 380
368 - private function get_response_for_error_code( $error_code, Form $form, $data = array() ) {
381 + private function get_response_for_error_code( $error_code, Form $form, $data = array(), ?Submission $submission = null ) {
369 382 // return success response for empty error code string or spam (to trick bots)
370 383 if ( $error_code === '' || $error_code === 'spam' ) {
371 384 $response = array(
372 385 'message' => array(
@@ -375,10 +388,16 @@
375 388 ),
376 389 'hide_form' => (bool) $form->settings['hide_after_success'],
377 390 );
378 391
379 - if ( ! empty( $form->settings['redirect_url'] ) ) {
380 - $response['redirect_url'] = hf_replace_data_variables( $form->settings['redirect_url'], $data, 'urlencode' );
392 + if ( ! empty( $form->settings['redirect_url'] ) && $submission !== null ) {
393 + $url = hf_replace_data_variables( $form->settings['redirect_url'], $submission, 'urlencode' );
394 +
395 + // Validate the scheme again to prevent javascript: XSS
396 + $scheme = wp_parse_url( $url, PHP_URL_SCHEME );
397 + if ( $scheme === null || in_array( strtolower( $scheme ), array( 'http', 'https' ), true ) ) {
398 + $response['redirect_url'] = $url;
399 + }
381 400 }
382 401
383 402 return apply_filters( 'hf_form_response', $response, $form, $data );
384 403 }
@@ -403,9 +422,9 @@
403 422 if ( empty( $attributes['slug'] ) && empty( $attributes['id'] ) ) {
404 423 return '';
405 424 }
406 425
407 - $slug_or_id = empty( $attributes['id'] ) ? $attributes['slug'] : $attributes['id'];
426 + $slug_or_id = esc_attr( empty( $attributes['id'] ) ? $attributes['slug'] : $attributes['id'] );
408 427 try {
409 428 $form = hf_get_form( $slug_or_id );
410 429 } catch ( \Exception $e ) {
411 430 if ( ! current_user_can( 'manage_options' ) ) {
@@ -411,9 +430,9 @@
411 430 if ( ! current_user_can( 'manage_options' ) ) {
412 431 return $content;
413 432 }
414 433
415 - return sprintf( '<p><strong>%s</strong> %s</p>', __( 'Error:', 'html-forms' ), sprintf( __( 'No form found with slug %s', 'html-forms' ), $attributes['slug'] ) );
434 + return sprintf( '<p><strong>%s</strong> %s</p>', __( 'Error:', 'html-forms' ), sprintf( __( 'No form found with slug %s', 'html-forms' ), esc_attr( $attributes['slug'] ) ) );
416 435 }
417 436
418 437 return $form . $content;
419 438 }