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

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