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

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