PluginProbe
HTML Forms – Simple WordPress Forms Plugin / 1.3.11
HTML Forms – Simple WordPress Forms Plugin v1.3.11
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 / Forms.php

Forms.php in HTML Forms – Simple WordPress Forms Plugin 1.3.11, at src/Forms.php

412 lines 14.0 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 {
26 $this->plugin_file = $plugin_file;
27 $this->settings = $settings;
28 }
29
30 public function hook()
31 {
32 add_action('init', array($this, 'register'));
33 add_action('init', array($this, 'listen_for_submit') );
34 add_action('parse_request', array($this, 'listen_for_preview'));
35 add_action('wp_enqueue_scripts', array($this, 'assets'));
36 add_filter('hf_form_markup', 'hf_template');
37 }
38
39 public function register()
40 {
41 // register post type
42 register_post_type('html-form', 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( 'html-forms/form', array(
54 'render_callback' => array($this, 'shortcode'),
55 ));
56 }
57
58 add_shortcode('hf_form', array($this, 'shortcode'));
59
60 // enable shortcodes in text widgets
61 add_filter( 'widget_text', 'shortcode_unautop' );
62 add_filter( 'widget_text', 'do_shortcode', 11 );
63 }
64
65 public function assets()
66 {
67 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
68 $assets_url = plugins_url( 'assets/', $this->plugin_file );
69
70 wp_register_script('html-forms', $assets_url . "js/public{$suffix}.js", array(), HTML_FORMS_VERSION, true);
71 wp_localize_script('html-forms', 'hf_js_vars', array(
72 'ajax_url' => admin_url('admin-ajax.php'),
73 ));
74
75 if( $this->settings['load_stylesheet'] ) {
76 wp_enqueue_style( 'html-forms', $assets_url . "css/forms{$suffix}.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 {
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 {
156 if (is_string($value)) {
157 // strip slashes
158 $value = stripslashes( $value );
159
160 // strip all HTML tags & whitespace
161 $value = trim(strip_tags($value));
162
163 // convert &amp; back to &
164 $value = html_entity_decode($value, ENT_NOQUOTES);
165 } elseif ( is_array($value) || is_object($value) ) {
166 $new_value = array();
167 $vars = is_array( $value ) ? $value : get_object_vars( $value );
168
169 foreach($vars as $key => $sub_value) {
170 // skip empty values
171 if(empty($sub_value)) {
172 continue;
173 }
174
175 // sanitize key
176 $key = trim(strip_tags($key));
177
178 // sanitize sub value
179 $new_value[$key] = $this->sanitize($sub_value);
180 }
181 $value = is_object( $value ) ? (object) $new_value : $new_value;
182 }
183
184 return $value;
185 }
186
187 /**
188 * @return array
189 */
190 public function get_request_data() {
191 $data = $_POST;
192
193 if( ! empty( $_FILES ) ) {
194 foreach( $_FILES as $field_name => $file ) {
195 // only add non-empty files so that required field validation works as expected
196 // upload could still have errored at this point
197 if( $file['error'] !== UPLOAD_ERR_NO_FILE ) {
198 $data[$field_name] = $file;
199 }
200 }
201 }
202
203 return $data;
204 }
205
206 public function listen_for_submit()
207 {
208
209 // only respond to AJAX requests with _hf_form_id set.
210 if (empty($_POST['_hf_form_id'])
211 || empty( $_SERVER['HTTP_X_REQUESTED_WITH'] )
212 || strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) !== strtolower( 'XMLHttpRequest' ) ) {
213 return;
214 }
215
216 $data = $this->get_request_data();
217 $form_id = (int) $data['_hf_form_id'];
218 $form = hf_get_form($form_id);
219 $error_code = $this->validate_form($form, $data);
220
221 if (empty( $error_code ) ) {
222
223 /**
224 * Filters the field names that should be ignored on the Submission object.
225 * Fields starting with an underscore (_) are ignored by default.
226 *
227 * @param array $names
228 */
229 $ignored_field_names = apply_filters( 'hf_ignored_field_names', array() );
230
231 // filter out ignored field names
232 foreach( $data as $key => $value ) {
233 if( $key[0] === '_' || in_array( $key, $ignored_field_names ) ) {
234 unset( $data[$key] );
235 continue;
236 }
237
238 // this detects the WPBruiser token field to ensure it isn't stored
239 // CAVEAT: this will detect any non-uppercase string with 2 dashes in the field name and no whitespace in the field value
240 if ( is_string($key) && is_string($value) && strtoupper($key) !== $key && substr_count($key, '-') >= 2 && substr_count(trim($value), ' ') === 0) {
241 unset( $data[$key] );
242 continue;
243 }
244 }
245
246 // sanitize data: strip tags etc.
247 $data = $this->sanitize( $data );
248
249 // save form submission
250 $submission = new Submission();
251 $submission->form_id = $form_id;
252 $submission->data = $data;
253 $submission->ip_address = ! empty( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : '';
254 $submission->user_agent = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) : '';
255 $submission->referer_url = ! empty( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '';
256 $submission->submitted_at = gmdate( 'Y-m-d H:i:s' );
257
258 // save submission object so that other form processor have an insert ID to work with (eg file upload)
259 if( $form->settings['save_submissions'] ) {
260 $submission->save();
261 }
262
263 /**
264 * General purpose hook that runs before all form actions, so we can still modify the submission object that is passed to actions.
265 */
266 do_action( 'hf_process_form', $form, $submission );
267
268 // re-save submission object for convenience in form processors hooked into hf_process_form
269 if( $form->settings['save_submissions'] ) {
270 $submission->save();
271 }
272
273 // process form actions
274 if ( isset( $form->settings['actions'] ) ) {
275 foreach( $form->settings['actions'] as $action_settings ) {
276 /**
277 * Processes the specified form action and passes related data.
278 *
279 * @param array $action_settings
280 * @param Submission $submission
281 * @param Form $form
282 */
283 do_action('hf_process_form_action_' . $action_settings['type'], $action_settings, $submission, $form );
284 }
285 }
286
287 /**
288 * 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.
289 *
290 * @param Submission $submission
291 * @param Form $form
292 */
293 do_action( "hf_form_{$form->slug}_success", $submission, $form );
294
295 /**
296 * General purpose hook after all form actions have been processed.
297 *
298 * @param Submission $submission
299 * @param Form $form
300 */
301 do_action( 'hf_form_success', $submission, $form );
302 } else {
303 /**
304 * General purpose hook for when a form error occurred
305 *
306 * @param string $error_code
307 * @param Form $form
308 * @param array $data
309 */
310 do_action( 'hf_form_error', $error_code, $form, $data );
311 }
312
313 // Delay response until "wp_loaded" hook to give other plugins a chance to process stuff.
314 add_action( 'wp_loaded', function() use($error_code, $form, $data) {
315 $response = $this->get_response_for_error_code($error_code, $form, $data);
316
317 // clear output, some plugin or hooked code might have thrown errors by now.
318 if( ob_get_level() > 0 ) {
319 ob_end_clean();
320 }
321
322 send_origin_headers();
323 send_nosniff_header();
324 nocache_headers();
325
326 wp_send_json($response, 200);
327 exit;
328 });
329 }
330
331 public function listen_for_preview() {
332 if( empty( $_GET['hf_preview_form'] ) || ! current_user_can( 'edit_forms' ) ) {
333 return;
334 }
335
336 try {
337 $form = hf_get_form( $_GET['hf_preview_form'] );
338 } catch( \Exception $e ) {
339 return;
340 }
341
342 show_admin_bar(false);
343 add_filter( 'pre_handle_404', '__return_true' );
344 remove_all_actions( 'template_redirect' );
345 add_action( 'template_redirect', function() use($form) {
346 // clear output, some plugin or hooked code might have thrown errors by now.
347 if( ob_get_level() > 0 ) {
348 ob_end_clean();
349 }
350
351 status_header(200);
352 require dirname( $this->plugin_file ) . '/views/form-preview.php';
353 exit;
354 });
355 }
356
357 private function get_response_for_error_code( $error_code, Form $form, $data = array() )
358 {
359 // return success response for empty error code string or spam (to trick bots)
360 if( $error_code === "" || $error_code === "spam" ) {
361 $response = array(
362 'message' => array(
363 'type' => 'success',
364 'text' => $form->get_message( 'success' ),
365 ),
366 'hide_form' => (bool)$form->settings['hide_after_success'],
367 );
368
369 if (!empty($form->settings['redirect_url'])) {
370 $response['redirect_url'] = hf_replace_data_variables($form->settings['redirect_url'], $data, 'urlencode');
371 }
372
373 return $response;
374 }
375
376 // get error message
377 $message = $form->get_message( $error_code );
378 if( empty( $message ) ) {
379 $message = $form->get_message( 'error' );
380 }
381
382 // return error response
383 return $response = array(
384 'message' => array(
385 'type' => 'warning',
386 'text' => $message,
387 ),
388 'error' => $error_code,
389 );
390 }
391
392 public function shortcode($attributes = array(), $content = '')
393 {
394 if (empty($attributes['slug']) && empty($attributes['id'])) {
395 return '';
396 }
397
398 $slug_or_id = empty( $attributes['id'] ) ? $attributes['slug'] : $attributes['id'];
399 try {
400 $form = hf_get_form( $slug_or_id );
401 } catch( \Exception $e ) {
402 if ( ! current_user_can( 'manage_options' ) ) {
403 return $content;
404 }
405
406 return sprintf( '<p><strong>%s</strong> %s</p>', __( 'Error:', 'html-forms' ), sprintf( __( 'No form found with slug %s', 'html-forms' ), $attributes['slug'] ) );
407 }
408
409 return $form . $content;
410 }
411 }
412