PluginProbe
HTML Forms – Simple WordPress Forms Plugin / 1.3.20
HTML Forms – Simple WordPress Forms Plugin v1.3.20
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 1.3.17 All 66 releases
html-forms / src / class-forms.php

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

421 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
200 foreach ( $_FILES as $field_name => $file ) {
201 // only add non-empty files so that required field validation works as expected
202 // upload could still have errored at this point
203 if ( $file['error'] !== UPLOAD_ERR_NO_FILE ) {
204 $data[ $field_name ] = $file;
205 }
206 }
207 }
208
209 return $data;
210 }
211
212 public function listen_for_submit() {
213
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 $data = $this->get_request_data();
222 $form_id = (int) $data['_hf_form_id'];
223 $form = hf_get_form( $form_id );
224 $error_code = $this->validate_form( $form, $data );
225
226 if ( empty( $error_code ) ) {
227
228 /**
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 */
234 $ignored_field_names = apply_filters( 'hf_ignored_field_names', array() );
235
236 // filter out ignored field names
237 foreach ( $data as $key => $value ) {
238 if ( $key[0] === '_' || in_array( $key, $ignored_field_names ) ) {
239 unset( $data[ $key ] );
240 continue;
241 }
242
243 // this detects the WPBruiser token field to ensure it isn't stored
244 // 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 ) {
246 unset( $data[ $key ] );
247 continue;
248 }
249 }
250
251 // sanitize data: strip tags etc.
252 $data = $this->sanitize( $data );
253
254 // save form submission
255 $submission = new Submission();
256 $submission->form_id = $form_id;
257 $submission->data = $data;
258 $submission->ip_address = ! empty( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : '';
259 $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'] ) : '';
261 $submission->submitted_at = gmdate( 'Y-m-d H:i:s' );
262
263 // save submission object so that other form processor have an insert ID to work with (eg file upload)
264 if ( $form->settings['save_submissions'] ) {
265 $submission->save();
266 }
267
268 /**
269 * General purpose hook that runs before all form actions, so we can still modify the submission object that is passed to actions.
270 */
271 do_action( 'hf_process_form', $form, $submission );
272
273 // re-save submission object for convenience in form processors hooked into hf_process_form
274 if ( $form->settings['save_submissions'] ) {
275 $submission->save();
276 }
277
278 // process form actions
279 if ( isset( $form->settings['actions'] ) ) {
280 foreach ( $form->settings['actions'] as $action_settings ) {
281 /**
282 * Processes the specified form action and passes related data.
283 *
284 * @param array $action_settings
285 * @param Submission $submission
286 * @param Form $form
287 */
288 do_action( 'hf_process_form_action_' . $action_settings['type'], $action_settings, $submission, $form );
289 }
290 }
291
292 /**
293 * 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.
294 *
295 * @param Submission $submission
296 * @param Form $form
297 */
298 do_action( "hf_form_{$form->slug}_success", $submission, $form );
299
300 /**
301 * General purpose hook after all form actions have been processed.
302 *
303 * @param Submission $submission
304 * @param Form $form
305 */
306 do_action( 'hf_form_success', $submission, $form );
307 } else {
308 /**
309 * General purpose hook for when a form error occurred
310 *
311 * @param string $error_code
312 * @param Form $form
313 * @param array $data
314 */
315 do_action( 'hf_form_error', $error_code, $form, $data );
316 }
317
318 // 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 );
337 }
338
339 public function listen_for_preview() {
340 if ( empty( $_GET['hf_preview_form'] ) || ! current_user_can( 'edit_forms' ) ) {
341 return;
342 }
343
344 try {
345 $form = hf_get_form( $_GET['hf_preview_form'] );
346 } catch ( \Exception $e ) {
347 return;
348 }
349
350 show_admin_bar( false );
351 add_filter( 'pre_handle_404', '__return_true' );
352 remove_all_actions( 'template_redirect' );
353 add_action(
354 'template_redirect',
355 function() use ( $form ) {
356 // clear output, some plugin or hooked code might have thrown errors by now.
357 if ( ob_get_level() > 0 ) {
358 ob_end_clean();
359 }
360
361 status_header( 200 );
362 require dirname( $this->plugin_file ) . '/views/form-preview.php';
363 exit;
364 }
365 );
366 }
367
368 private function get_response_for_error_code( $error_code, Form $form, $data = array() ) {
369 // return success response for empty error code string or spam (to trick bots)
370 if ( $error_code === '' || $error_code === 'spam' ) {
371 $response = array(
372 'message' => array(
373 'type' => 'success',
374 'text' => $form->get_message( 'success' ),
375 ),
376 'hide_form' => (bool) $form->settings['hide_after_success'],
377 );
378
379 if ( ! empty( $form->settings['redirect_url'] ) ) {
380 $response['redirect_url'] = hf_replace_data_variables( $form->settings['redirect_url'], $data, 'urlencode' );
381 }
382
383 return apply_filters( 'hf_form_response', $response, $form, $data );
384 }
385
386 // get error message
387 $message = $form->get_message( $error_code );
388 if ( empty( $message ) ) {
389 $message = $form->get_message( 'error' );
390 }
391
392 // return error response
393 return array(
394 'message' => array(
395 'type' => 'warning',
396 'text' => $message,
397 ),
398 'error' => $error_code,
399 );
400 }
401
402 public function shortcode( $attributes = array(), $content = '' ) {
403 if ( empty( $attributes['slug'] ) && empty( $attributes['id'] ) ) {
404 return '';
405 }
406
407 $slug_or_id = empty( $attributes['id'] ) ? $attributes['slug'] : $attributes['id'];
408 try {
409 $form = hf_get_form( $slug_or_id );
410 } catch ( \Exception $e ) {
411 if ( ! current_user_can( 'manage_options' ) ) {
412 return $content;
413 }
414
415 return sprintf( '<p><strong>%s</strong> %s</p>', __( 'Error:', 'html-forms' ), sprintf( __( 'No form found with slug %s', 'html-forms' ), $attributes['slug'] ) );
416 }
417
418 return $form . $content;
419 }
420 }
421