PluginProbe
HTML Forms – Simple WordPress Forms Plugin / 1.3.13
HTML Forms – Simple WordPress Forms Plugin v1.3.13
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.13, at src/Forms.php

418 lines 14.1 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 // do nothing if empty string
158 if ($value === '') {
159 return $value;
160 }
161
162 // strip slashes
163 $value = stripslashes($value);
164
165 // strip all whitespace
166 $value = trim($value);
167
168 // convert &amp; back to &
169 $value = html_entity_decode($value, ENT_NOQUOTES);
170 } elseif ( is_array($value) || is_object($value) ) {
171 $new_value = array();
172 $vars = is_array( $value ) ? $value : get_object_vars( $value );
173
174 // do nothing if empty array or object
175 if (count($vars) === 0) {
176 return $value;
177 }
178
179 foreach($vars as $key => $sub_value) {
180 // strip all whitespace & HTML from keys (!)
181 $key = trim(strip_tags($key));
182
183 // sanitize sub value
184 $new_value[$key] = $this->sanitize($sub_value);
185 }
186
187 $value = is_object( $value ) ? (object) $new_value : $new_value;
188 }
189
190 return $value;
191 }
192
193 /**
194 * @return array
195 */
196 public function get_request_data() {
197 $data = $_POST;
198
199 if( ! empty( $_FILES ) ) {
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
215 // only respond to AJAX requests with _hf_form_id set.
216 if (empty($_POST['_hf_form_id'])
217 || empty( $_SERVER['HTTP_X_REQUESTED_WITH'] )
218 || strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) !== strtolower( 'XMLHttpRequest' ) ) {
219 return;
220 }
221
222 $data = $this->get_request_data();
223 $form_id = (int) $data['_hf_form_id'];
224 $form = hf_get_form($form_id);
225 $error_code = $this->validate_form($form, $data);
226
227 if (empty( $error_code ) ) {
228
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 ( 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( 'wp_loaded', function() use($error_code, $form, $data) {
321 $response = $this->get_response_for_error_code($error_code, $form, $data);
322
323 // clear output, some plugin or hooked code might have thrown errors by now.
324 if( ob_get_level() > 0 ) {
325 ob_end_clean();
326 }
327
328 send_origin_headers();
329 send_nosniff_header();
330 nocache_headers();
331
332 wp_send_json($response, 200);
333 exit;
334 });
335 }
336
337 public function listen_for_preview() {
338 if( empty( $_GET['hf_preview_form'] ) || ! current_user_can( 'edit_forms' ) ) {
339 return;
340 }
341
342 try {
343 $form = hf_get_form( $_GET['hf_preview_form'] );
344 } catch( \Exception $e ) {
345 return;
346 }
347
348 show_admin_bar(false);
349 add_filter( 'pre_handle_404', '__return_true' );
350 remove_all_actions( 'template_redirect' );
351 add_action( 'template_redirect', 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 private function get_response_for_error_code( $error_code, Form $form, $data = array() )
364 {
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 $response;
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 $response = 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 {
400 if (empty($attributes['slug']) && empty($attributes['id'])) {
401 return '';
402 }
403
404 $slug_or_id = empty( $attributes['id'] ) ? $attributes['slug'] : $attributes['id'];
405 try {
406 $form = hf_get_form( $slug_or_id );
407 } catch( \Exception $e ) {
408 if ( ! current_user_can( 'manage_options' ) ) {
409 return $content;
410 }
411
412 return sprintf( '<p><strong>%s</strong> %s</p>', __( 'Error:', 'html-forms' ), sprintf( __( 'No form found with slug %s', 'html-forms' ), $attributes['slug'] ) );
413 }
414
415 return $form . $content;
416 }
417 }
418