PluginProbe
HTML Forms – Simple WordPress Forms Plugin / 1.3.21
HTML Forms – Simple WordPress Forms Plugin v1.3.21
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
html-forms / src / class-forms.php

class-forms.php in HTML Forms – Simple WordPress Forms Plugin 1.3.21, at src/class-forms.php

419 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace HTML_Forms;
4
5 class Forms {
6
7
8 /**
9 * @var string
10 */
11 private $plugin_file;
12
13 /**
14 * @var array
15 */
16 private $settings;
17
18 /**
19 * Forms constructor.
20 *
21 * @param string $plugin_file
22 * @param array $settings
23 */
24 public function __construct( $plugin_file, array $settings ) {
25 $this->plugin_file = $plugin_file;
26 $this->settings = $settings;
27 }
28
29 public function hook() {
30 add_action( 'init', array( $this, 'register' ) );
31 add_action( 'init', array( $this, 'listen_for_submit' ) );
32 add_action( 'parse_request', array( $this, 'listen_for_preview' ) );
33 add_action( 'wp_enqueue_scripts', array( $this, 'assets' ) );
34 add_filter( 'hf_form_markup', 'hf_template' );
35 }
36
37 public function register() {
38 // register post type
39 register_post_type(
40 'html-form',
41 array(
42 'labels' => array(
43 'name' => 'HTML Forms',
44 'singular_name' => 'HTML Form',
45 ),
46 'public' => false,
47 'capability_type' => 'form',
48 )
49 );
50
51 if ( function_exists( 'register_block_type' ) ) {
52 register_block_type(
53 'html-forms/form',
54 array(
55 'render_callback' => array( $this, 'shortcode' ),
56 )
57 );
58 }
59
60 add_shortcode( 'hf_form', array( $this, 'shortcode' ) );
61 }
62
63 public function assets() {
64 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
65 $assets_url = plugins_url( 'assets/', $this->plugin_file );
66
67 wp_register_script( 'html-forms', $assets_url . "js/public{$suffix}.js", array(), HTML_FORMS_VERSION, true );
68 wp_localize_script(
69 'html-forms',
70 'hf_js_vars',
71 array(
72 'ajax_url' => admin_url( 'admin-ajax.php' ),
73 )
74 );
75
76 if ( $this->settings['load_stylesheet'] ) {
77 wp_enqueue_style( 'html-forms', $assets_url . "css/forms{$suffix}.css", array(), HTML_FORMS_VERSION );
78 }
79 }
80
81 /**
82 * @param Form $form
83 * @param array $data
84 * @return string
85 */
86 public function validate_form( Form $form, array $data ) {
87 // validate honeypot field
88 $honeypot_key = sprintf( '_hf_h%d', $form->ID );
89 if ( ! isset( $data[ $honeypot_key ] ) || $data[ $honeypot_key ] !== '' ) {
90 return 'spam';
91 }
92
93 // validate size of POST array
94 if ( count( $data ) > $form->get_field_count() && apply_filters( 'hf_validate_form_request_size', true ) ) {
95 return 'spam';
96 }
97
98 $was_required = (array) hf_array_get( $data, '_was_required', array() );
99 $required_fields = $form->get_required_fields();
100 foreach ( $required_fields as $field_name ) {
101 $value = hf_array_get( $data, $field_name );
102 if ( empty( $value ) && ! in_array( $field_name, $was_required ) ) {
103 return 'required_field_missing';
104 }
105 }
106
107 $email_fields = $form->get_email_fields();
108 foreach ( $email_fields as $field_name ) {
109 $value = hf_array_get( $data, $field_name );
110 if ( ! empty( $value ) && ! is_email( $value ) ) {
111 return 'invalid_email';
112 }
113 }
114
115 $error_code = '';
116
117 /**
118 * This filter allows you to perform your own form validation. The dynamic portion of the hook refers to the form slug.
119 *
120 * Return a non-empty string if you want to raise an error.
121 * Error codes with a specific error message are: "required_field_missing", "invalid_email", and "error"
122 *
123 * @param string $error_code
124 * @param Form $form
125 * @param array $data
126 */
127 $error_code = apply_filters( 'hf_validate_form_' . $form->slug, $error_code, $form, $data );
128
129 /**
130 * This filter allows you to perform your own form validation.
131 *
132 * Return a non-empty string if you want to raise an error.
133 * Error codes with a specific error message are: "required_field_missing", "invalid_email", and "error"
134 *
135 * @param string $error_code
136 * @param Form $form
137 * @param array $data
138 */
139 $error_code = apply_filters( 'hf_validate_form', $error_code, $form, $data );
140 if ( ! empty( $error_code ) ) {
141 return $error_code;
142 }
143
144 // all good: no errors!
145 return '';
146 }
147
148 /**
149 * Sanitize array with values before saving. Can be called recursively.
150 *
151 * @param mixed $value
152 * @return mixed
153 */
154 public function sanitize( $value ) {
155 if ( is_string( $value ) ) {
156 // do nothing if empty string
157 if ( $value === '' ) {
158 return $value;
159 }
160
161 // strip slashes
162 $value = stripslashes( $value );
163
164 // strip all whitespace
165 $value = trim( $value );
166
167 // convert &amp; back to &
168 $value = html_entity_decode( $value, ENT_NOQUOTES );
169 } elseif ( is_array( $value ) || is_object( $value ) ) {
170 $new_value = array();
171 $vars = is_array( $value ) ? $value : get_object_vars( $value );
172
173 // do nothing if empty array or object
174 if ( count( $vars ) === 0 ) {
175 return $value;
176 }
177
178 foreach ( $vars as $key => $sub_value ) {
179 // strip all whitespace & HTML from keys (!)
180 $key = trim( strip_tags( $key ) );
181
182 // sanitize sub value
183 $new_value[ $key ] = $this->sanitize( $sub_value );
184 }
185
186 $value = is_object( $value ) ? (object) $new_value : $new_value;
187 }
188
189 return $value;
190 }
191
192 /**
193 * @return array
194 */
195 public function get_request_data() {
196 $data = $_POST;
197
198 if ( ! empty( $_FILES ) ) {
199 foreach ( $_FILES as $field_name => $file ) {
200 // only add non-empty files so that required field validation works as expected
201 // upload could still have errored at this point
202 if ( $file['error'] !== UPLOAD_ERR_NO_FILE ) {
203 $data[ $field_name ] = $file;
204 }
205 }
206 }
207
208 return $data;
209 }
210
211 public function listen_for_submit() {
212
213 // only respond to AJAX requests with _hf_form_id set.
214 if ( empty( $_POST['_hf_form_id'] )
215 || empty( $_SERVER['HTTP_X_REQUESTED_WITH'] )
216 || strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) !== strtolower( 'XMLHttpRequest' ) ) {
217 return;
218 }
219
220 $data = $this->get_request_data();
221 $form_id = (int) $data['_hf_form_id'];
222 $form = hf_get_form( $form_id );
223 $error_code = $this->validate_form( $form, $data );
224
225 if ( empty( $error_code ) ) {
226 /**
227 * Filters the field names that should be ignored on the Submission object.
228 * Fields starting with an underscore (_) are ignored by default.
229 *
230 * @param array $names
231 */
232 $ignored_field_names = apply_filters( 'hf_ignored_field_names', array() );
233
234 // filter out ignored field names
235 foreach ( $data as $key => $value ) {
236 if ( $key[0] === '_' || in_array( $key, $ignored_field_names ) ) {
237 unset( $data[ $key ] );
238 continue;
239 }
240
241 // this detects the WPBruiser token field to ensure it isn't stored
242 // CAVEAT: this will detect any non-uppercase string with 2 dashes in the field name and no whitespace in the field value
243 if ( class_exists( 'GoodByeCaptcha' ) && is_string( $key ) && is_string( $value ) && strtoupper( $key ) !== $key && substr_count( $key, '-' ) >= 2 && substr_count( trim( $value ), ' ' ) === 0 ) {
244 unset( $data[ $key ] );
245 continue;
246 }
247 }
248
249 // sanitize data: strip tags etc.
250 $data = $this->sanitize( $data );
251
252 // save form submission
253 $submission = new Submission();
254 $submission->form_id = $form_id;
255 $submission->data = $data;
256 $submission->ip_address = ! empty( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : '';
257 $submission->user_agent = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) : '';
258 $submission->referer_url = ! empty( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '';
259 $submission->submitted_at = gmdate( 'Y-m-d H:i:s' );
260
261 // save submission object so that other form processor have an insert ID to work with (eg file upload)
262 if ( $form->settings['save_submissions'] ) {
263 $submission->save();
264 }
265
266 /**
267 * General purpose hook that runs before all form actions, so we can still modify the submission object that is passed to actions.
268 */
269 do_action( 'hf_process_form', $form, $submission );
270
271 // re-save submission object for convenience in form processors hooked into hf_process_form
272 if ( $form->settings['save_submissions'] ) {
273 $submission->save();
274 }
275
276 // process form actions
277 if ( isset( $form->settings['actions'] ) ) {
278 foreach ( $form->settings['actions'] as $action_settings ) {
279 /**
280 * Processes the specified form action and passes related data.
281 *
282 * @param array $action_settings
283 * @param Submission $submission
284 * @param Form $form
285 */
286 do_action( 'hf_process_form_action_' . $action_settings['type'], $action_settings, $submission, $form );
287 }
288 }
289
290 /**
291 * General purpose hook after all form actions have been processed for this specific form. The dynamic portion of the hook refers to the form slug.
292 *
293 * @param Submission $submission
294 * @param Form $form
295 */
296 do_action( "hf_form_{$form->slug}_success", $submission, $form );
297
298 /**
299 * General purpose hook after all form actions have been processed.
300 *
301 * @param Submission $submission
302 * @param Form $form
303 */
304 do_action( 'hf_form_success', $submission, $form );
305 } else {
306 /**
307 * General purpose hook for when a form error occurred
308 *
309 * @param string $error_code
310 * @param Form $form
311 * @param array $data
312 */
313 do_action( 'hf_form_error', $error_code, $form, $data );
314 }
315
316 // Delay response until "wp_loaded" hook to give other plugins a chance to process stuff.
317 add_action(
318 'wp_loaded',
319 function() use ( $error_code, $form, $data ) {
320 $response = $this->get_response_for_error_code( $error_code, $form, $data );
321
322 // clear output, some plugin or hooked code might have thrown errors by now.
323 if ( ob_get_level() > 0 ) {
324 ob_end_clean();
325 }
326
327 send_origin_headers();
328 send_nosniff_header();
329 nocache_headers();
330
331 wp_send_json( $response, 200 );
332 exit;
333 }
334 );
335 }
336
337 public function listen_for_preview() {
338 if ( empty( $_GET['hf_preview_form'] ) || ! current_user_can( 'edit_forms' ) ) {
339 return;
340 }
341
342 try {
343 $form = hf_get_form( $_GET['hf_preview_form'] );
344 } catch ( \Exception $e ) {
345 return;
346 }
347
348 show_admin_bar( false );
349 add_filter( 'pre_handle_404', '__return_true' );
350 remove_all_actions( 'template_redirect' );
351 add_action(
352 'template_redirect',
353 function() use ( $form ) {
354 // clear output, some plugin or hooked code might have thrown errors by now.
355 if ( ob_get_level() > 0 ) {
356 ob_end_clean();
357 }
358
359 status_header( 200 );
360 require dirname( $this->plugin_file ) . '/views/form-preview.php';
361 exit;
362 }
363 );
364 }
365
366 private function get_response_for_error_code( $error_code, Form $form, $data = array() ) {
367 // return success response for empty error code string or spam (to trick bots)
368 if ( $error_code === '' || $error_code === 'spam' ) {
369 $response = array(
370 'message' => array(
371 'type' => 'success',
372 'text' => $form->get_message( 'success' ),
373 ),
374 'hide_form' => (bool) $form->settings['hide_after_success'],
375 );
376
377 if ( ! empty( $form->settings['redirect_url'] ) ) {
378 $response['redirect_url'] = hf_replace_data_variables( $form->settings['redirect_url'], $data, 'urlencode' );
379 }
380
381 return apply_filters( 'hf_form_response', $response, $form, $data );
382 }
383
384 // get error message
385 $message = $form->get_message( $error_code );
386 if ( empty( $message ) ) {
387 $message = $form->get_message( 'error' );
388 }
389
390 // return error response
391 return array(
392 'message' => array(
393 'type' => 'warning',
394 'text' => $message,
395 ),
396 'error' => $error_code,
397 );
398 }
399
400 public function shortcode( $attributes = array(), $content = '' ) {
401 if ( empty( $attributes['slug'] ) && empty( $attributes['id'] ) ) {
402 return '';
403 }
404
405 $slug_or_id = empty( $attributes['id'] ) ? $attributes['slug'] : $attributes['id'];
406 try {
407 $form = hf_get_form( $slug_or_id );
408 } catch ( \Exception $e ) {
409 if ( ! current_user_can( 'manage_options' ) ) {
410 return $content;
411 }
412
413 return sprintf( '<p><strong>%s</strong> %s</p>', __( 'Error:', 'html-forms' ), sprintf( __( 'No form found with slug %s', 'html-forms' ), $attributes['slug'] ) );
414 }
415
416 return $form . $content;
417 }
418 }
419