PluginProbe
HTML Forms – Simple WordPress Forms Plugin / 1.3.25
HTML Forms – Simple WordPress Forms Plugin v1.3.25
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.25, at src/class-forms.php

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