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

404 lines 13.5 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 }
236 }
237
238 // sanitize data: strip tags etc.
239 $data = $this->sanitize( $data );
240
241 // save form submission
242 $submission = new Submission();
243 $submission->form_id = $form_id;
244 $submission->data = $data;
245 $submission->ip_address = ! empty( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : '';
246 $submission->user_agent = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) : '';
247 $submission->referer_url = ! empty( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( $_SERVER['HTTP_REFERER'] ) : '';
248 $submission->submitted_at = gmdate( 'Y-m-d H:i:s' );
249
250 // save submission object so that other form processor have an insert ID to work with (eg file upload)
251 if( $form->settings['save_submissions'] ) {
252 $submission->save();
253 }
254
255 /**
256 * General purpose hook that runs before all form actions, so we can still modify the submission object that is passed to actions.
257 */
258 do_action( 'hf_process_form', $form, $submission );
259
260 // re-save submission object for convenience in form processors hooked into hf_process_form
261 if( $form->settings['save_submissions'] ) {
262 $submission->save();
263 }
264
265 // process form actions
266 if ( isset( $form->settings['actions'] ) ) {
267 foreach( $form->settings['actions'] as $action_settings ) {
268 /**
269 * Processes the specified form action and passes related data.
270 *
271 * @param array $action_settings
272 * @param Submission $submission
273 * @param Form $form
274 */
275 do_action('hf_process_form_action_' . $action_settings['type'], $action_settings, $submission, $form );
276 }
277 }
278
279 /**
280 * 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.
281 *
282 * @param Submission $submission
283 * @param Form $form
284 */
285 do_action( "hf_form_{$form->slug}_success", $submission, $form );
286
287 /**
288 * General purpose hook after all form actions have been processed.
289 *
290 * @param Submission $submission
291 * @param Form $form
292 */
293 do_action( 'hf_form_success', $submission, $form );
294 } else {
295 /**
296 * General purpose hook for when a form error occurred
297 *
298 * @param string $error_code
299 * @param Form $form
300 * @param array $data
301 */
302 do_action( 'hf_form_error', $error_code, $form, $data );
303 }
304
305 // Delay response until "wp_loaded" hook to give other plugins a chance to process stuff.
306 add_action( 'wp_loaded', function() use($error_code, $form, $data) {
307 $response = $this->get_response_for_error_code($error_code, $form, $data);
308
309 // clear output, some plugin or hooked code might have thrown errors by now.
310 if( ob_get_level() > 0 ) {
311 ob_end_clean();
312 }
313
314 send_origin_headers();
315 send_nosniff_header();
316 nocache_headers();
317
318 wp_send_json($response, 200);
319 exit;
320 });
321 }
322
323 public function listen_for_preview() {
324 if( empty( $_GET['hf_preview_form'] ) || ! current_user_can( 'edit_forms' ) ) {
325 return;
326 }
327
328 try {
329 $form = hf_get_form( $_GET['hf_preview_form'] );
330 } catch( \Exception $e ) {
331 return;
332 }
333
334 show_admin_bar(false);
335 add_filter( 'pre_handle_404', '__return_true' );
336 remove_all_actions( 'template_redirect' );
337 add_action( 'template_redirect', function() use($form) {
338 // clear output, some plugin or hooked code might have thrown errors by now.
339 if( ob_get_level() > 0 ) {
340 ob_end_clean();
341 }
342
343 status_header(200);
344 require dirname( $this->plugin_file ) . '/views/form-preview.php';
345 exit;
346 });
347 }
348
349 private function get_response_for_error_code( $error_code, Form $form, $data = array() )
350 {
351 // return success response for empty error code string or spam (to trick bots)
352 if( $error_code === "" || $error_code === "spam" ) {
353 $response = array(
354 'message' => array(
355 'type' => 'success',
356 'text' => $form->get_message( 'success' ),
357 ),
358 'hide_form' => (bool)$form->settings['hide_after_success'],
359 );
360
361 if (!empty($form->settings['redirect_url'])) {
362 $response['redirect_url'] = hf_replace_data_variables($form->settings['redirect_url'], $data, 'urlencode');
363 }
364
365 return $response;
366 }
367
368 // get error message
369 $message = $form->get_message( $error_code );
370 if( empty( $message ) ) {
371 $message = $form->get_message( 'error' );
372 }
373
374 // return error response
375 return $response = array(
376 'message' => array(
377 'type' => 'warning',
378 'text' => $message,
379 ),
380 'error' => $error_code,
381 );
382 }
383
384 public function shortcode($attributes = array(), $content = '')
385 {
386 if (empty($attributes['slug']) && empty($attributes['id'])) {
387 return '';
388 }
389
390 $slug_or_id = empty( $attributes['id'] ) ? $attributes['slug'] : $attributes['id'];
391 try {
392 $form = hf_get_form( $slug_or_id );
393 } catch( \Exception $e ) {
394 if ( ! current_user_can( 'manage_options' ) ) {
395 return $content;
396 }
397
398 return sprintf( '<p><strong>%s</strong> %s</p>', __( 'Error:', 'html-forms' ), sprintf( __( 'No form found with slug %s', 'html-forms' ), $attributes['slug'] ) );
399 }
400
401 return $form . $content;
402 }
403 }
404