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

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