| @@ -77,8 +77,10 @@ | ||
| 77 | 77 | 'success' => __( 'Thank you! We will be in touch soon.', 'html-forms' ), |
| 78 | 78 | 'invalid_email' => __( 'Sorry, that email address looks invalid.', 'html-forms' ), |
| 79 | 79 | 'required_field_missing' => __( 'Please fill in the required fields.', 'html-forms' ), |
| 80 | 80 | 'error' => __( 'Oops. An error occurred.', 'html-forms' ), |
| 81 | + 'recaptcha_failed' => __( 'reCAPTCHA verification failed. Please try again.', 'html-forms' ), | |
| 82 | + 'recaptcha_low_score' => __( 'Your submission appears to be spam. Please try again.', 'html-forms' ), | |
| 81 | 83 | ); |
| 82 | 84 | $default_messages = apply_filters( 'hf_form_default_messages', $default_messages ); |
| 83 | 85 | $messages = array(); |
| 84 | 86 | foreach ( $post_meta as $meta_key => $meta_values ) { |
| @@ -100,21 +102,49 @@ | ||
| 100 | 102 | } |
| 101 | 103 | |
| 102 | 104 | /** |
| 103 | 105 | * @param $form_id |
| 106 | + * @return int | |
| 107 | + */ | |
| 108 | +function hf_count_form_submissions( $form_id, $search = '' ) { | |
| 109 | + global $wpdb; | |
| 110 | + $table = $wpdb->prefix . 'hf_submissions'; | |
| 111 | + if ( $search !== '' ) { | |
| 112 | + $result = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} s WHERE s.form_id = %d AND s.data LIKE %s;", $form_id, '%' . $wpdb->esc_like( $search ) . '%' ) ); | |
| 113 | + } else { | |
| 114 | + $result = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} s WHERE s.form_id = %d;", $form_id ) ); | |
| 115 | + } | |
| 116 | + return (int) $result; | |
| 117 | +} | |
| 118 | + | |
| 119 | +/** | |
| 120 | + * @param $form_id | |
| 104 | 121 | * @param array $args |
| 105 | 122 | * @return Submission[] |
| 106 | 123 | */ |
| 107 | 124 | function hf_get_form_submissions( $form_id, array $args = array() ) { |
| 108 | 125 | $default_args = array( |
| 109 | - 'offset' => 0, | |
| 110 | - 'limit' => 1000, | |
| 126 | + 'offset' => 0, | |
| 127 | + 'limit' => 1000, | |
| 128 | + 'orderby' => 'submitted_at', | |
| 129 | + 'order' => 'DESC', | |
| 130 | + 'search' => '', | |
| 111 | 131 | ); |
| 112 | - $args = array_merge( $default_args, $args ); | |
| 132 | + $args = array_merge( $default_args, $args ); | |
| 113 | 133 | |
| 134 | + $allowed_orderby = array( 'submitted_at', 'id' ); | |
| 135 | + $orderby = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'submitted_at'; | |
| 136 | + $order = strtoupper( $args['order'] ) === 'ASC' ? 'ASC' : 'DESC'; | |
| 137 | + | |
| 114 | 138 | global $wpdb; |
| 115 | - $table = $wpdb->prefix . 'hf_submissions'; | |
| 116 | - $results = $wpdb->get_results( $wpdb->prepare( "SELECT s.* FROM {$table} s WHERE s.form_id = %d ORDER BY s.submitted_at DESC LIMIT %d, %d;", $form_id, $args['offset'], $args['limit'] ), OBJECT_K ); | |
| 139 | + $table = $wpdb->prefix . 'hf_submissions'; | |
| 140 | + if ( $args['search'] !== '' ) { | |
| 141 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orderby and $order are whitelisted above | |
| 142 | + $results = $wpdb->get_results( $wpdb->prepare( "SELECT s.* FROM {$table} s WHERE s.form_id = %d AND s.data LIKE %s ORDER BY s.{$orderby} {$order} LIMIT %d, %d;", $form_id, '%' . $wpdb->esc_like( $args['search'] ) . '%', $args['offset'], $args['limit'] ), OBJECT_K ); | |
| 143 | + } else { | |
| 144 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orderby and $order are whitelisted above | |
| 145 | + $results = $wpdb->get_results( $wpdb->prepare( "SELECT s.* FROM {$table} s WHERE s.form_id = %d ORDER BY s.{$orderby} {$order} LIMIT %d, %d;", $form_id, $args['offset'], $args['limit'] ), OBJECT_K ); | |
| 146 | + } | |
| 117 | 147 | $submissions = array(); |
| 118 | 148 | foreach ( $results as $key => $object ) { |
| 119 | 149 | $submission = Submission::from_object( $object ); |
| 120 | 150 | $submissions[ $key ] = $submission; |
| @@ -137,9 +167,15 @@ | ||
| 137 | 167 | * @return array |
| 138 | 168 | */ |
| 139 | 169 | function hf_get_settings() { |
| 140 | 170 | $default_settings = array( |
| 171 | + 'enable_nonce' => 0, | |
| 141 | 172 | 'load_stylesheet' => 0, |
| 173 | + 'wrapper_tag' => 'p', | |
| 174 | + 'google_recaptcha' => array( | |
| 175 | + 'site_key' => '', | |
| 176 | + 'secret_key' => '', | |
| 177 | + ), | |
| 142 | 178 | ); |
| 143 | 179 | |
| 144 | 180 | $settings = get_option( 'hf_settings', null ); |
| 145 | 181 | |
| @@ -150,8 +186,16 @@ | ||
| 150 | 186 | } |
| 151 | 187 | |
| 152 | 188 | // merge with default settings |
| 153 | 189 | $settings = array_merge( $default_settings, $settings ); |
| 190 | + | |
| 191 | + // Ensure nested arrays are properly merged | |
| 192 | + if ( isset( $default_settings['google_recaptcha'] ) ) { | |
| 193 | + $settings['google_recaptcha'] = array_merge( | |
| 194 | + $default_settings['google_recaptcha'], | |
| 195 | + isset( $settings['google_recaptcha'] ) ? $settings['google_recaptcha'] : array() | |
| 196 | + ); | |
| 197 | + } | |
| 154 | 198 | |
| 155 | 199 | /** |
| 156 | 200 | * Filters the global HTML Forms hf_settings |
| 157 | 201 | * |
| @@ -241,20 +285,48 @@ | ||
| 241 | 285 | * @param array $data |
| 242 | 286 | * @param Closure|string $escape_function |
| 243 | 287 | * @return string |
| 244 | 288 | */ |
| 245 | -function hf_replace_data_variables( $string, $data = array(), $escape_function = null ) { | |
| 246 | - $string = preg_replace_callback( | |
| 247 | - '/\[([a-zA-Z0-9\-\._]+)\]/', | |
| 248 | - function( $matches ) use ( $data, $escape_function ) { | |
| 249 | - $key = $matches[1]; | |
| 250 | - $replacement = hf_array_get( $data, $key, '' ); | |
| 251 | - $replacement = hf_field_value( $replacement, 0, $escape_function ); | |
| 289 | +function hf_replace_data_variables( $string, Submission $submission, $escape_function = null ) { | |
| 290 | + $data = ( !empty( $submission->data ) ? $submission->data : array() ); | |
| 291 | + $submission_fields = array( 'HF_TIMESTAMP', 'HF_USER_AGENT', 'HF_IP_ADDRESS', 'HF_REFERRER_URL' ); | |
| 292 | + | |
| 293 | + return preg_replace_callback( | |
| 294 | + '/\[(.+?)\]/', | |
| 295 | + function( $matches ) use ( $submission, $submission_fields, $escape_function ) { | |
| 296 | + $key = $matches[1]; | |
| 297 | + | |
| 298 | + if ( in_array( $key, $submission_fields ) ) { | |
| 299 | + $replacement = ''; | |
| 300 | + | |
| 301 | + switch ( $key ) { | |
| 302 | + case 'HF_TIMESTAMP' : | |
| 303 | + $replacement = $submission->submitted_at; | |
| 304 | + break; | |
| 305 | + case 'HF_USER_AGENT' : | |
| 306 | + $replacement = $submission->user_agent; | |
| 307 | + break; | |
| 308 | + case 'HF_IP_ADDRESS' : | |
| 309 | + $replacement = $submission->ip_address; | |
| 310 | + break; | |
| 311 | + case 'HF_REFERRER_URL' : | |
| 312 | + $replacement = $submission->referer_url; | |
| 313 | + break; | |
| 314 | + default : | |
| 315 | + $replacement = ''; | |
| 316 | + break; | |
| 317 | + } | |
| 318 | + } else { | |
| 319 | + // replace spaces in name with underscores to match PHP requirement for keys in $_POST superglobal | |
| 320 | + $key = str_replace( ' ', '_', $key ); | |
| 321 | + $replacement = hf_array_get( $submission->data, $key, '' ); | |
| 322 | + $replacement = hf_field_value( $replacement, 0, $escape_function ); | |
| 323 | + } | |
| 324 | + | |
| 252 | 325 | return $replacement; |
| 253 | 326 | }, |
| 254 | 327 | $string |
| 255 | 328 | ); |
| 256 | - return $string; | |
| 257 | 329 | } |
| 258 | 330 | |
| 259 | 331 | /** |
| 260 | 332 | * Returns a formatted & HTML-escaped field value. Detects file-, array- and date-types. |
| @@ -272,20 +344,33 @@ | ||
| 272 | 344 | return $value; |
| 273 | 345 | } |
| 274 | 346 | |
| 275 | 347 | if ( hf_is_file( $value ) ) { |
| 348 | + if ( ! is_array( $value ) | |
| 349 | + || ! isset( $value['name'] ) | |
| 350 | + || ! isset( $value['size'] ) | |
| 351 | + || ! isset( $value['type'] ) ) { | |
| 352 | + return false; | |
| 353 | + } | |
| 354 | + | |
| 355 | + // Verify attachment exists | |
| 356 | + if ( isset( $value['attachment_id'] ) && get_post( $value['attachment_id'] ) == null ) { | |
| 357 | + return __( 'File not found', 'html-forms' ); | |
| 358 | + } | |
| 359 | + | |
| 276 | 360 | $file_url = isset( $value['url'] ) ? $value['url'] : ''; |
| 277 | - if ( isset( $value['attachment_id'] ) ) { | |
| 361 | + if ( isset( $value['attachment_id'] ) && apply_filters( 'hf_file_upload_use_direct_links', false ) === false ) { | |
| 278 | 362 | $file_url = admin_url( sprintf( 'post.php?action=edit&post=%d', $value['attachment_id'] ) ); |
| 279 | 363 | } |
| 364 | + | |
| 280 | 365 | $short_name = substr( $value['name'], 0, 20 ); |
| 281 | 366 | $suffix = strlen( $value['name'] ) > 20 ? '...' : ''; |
| 282 | - return sprintf( '<a href="%s">%s%s</a> (%s)', esc_attr( $file_url ), esc_html( $short_name ), esc_html( $suffix ), hf_human_filesize( $value['size'] ) ); | |
| 367 | + return sprintf( '<a href="%s">%s%s</a> (%s)', esc_url( $file_url ), esc_html( $short_name ), esc_html( $suffix ), hf_human_filesize( $value['size'] ) ); | |
| 283 | 368 | } |
| 284 | 369 | |
| 285 | 370 | if ( hf_is_date( $value ) ) { |
| 286 | 371 | $date_format = get_option( 'date_format' ); |
| 287 | - return gmdate( $date_format, strtotime( $value ) ); | |
| 372 | + return gmdate( $date_format, strtotime( str_replace( '/', '-', $value ) ) ); | |
| 288 | 373 | } |
| 289 | 374 | |
| 290 | 375 | // join array-values with comma |
| 291 | 376 | if ( is_array( $value ) ) { |
| @@ -306,8 +391,13 @@ | ||
| 306 | 391 | if ( $escape_function !== null && is_callable( $escape_function ) ) { |
| 307 | 392 | $value = $escape_function( $value ); |
| 308 | 393 | } |
| 309 | 394 | |
| 395 | + // add line breaks, if not string limited to certain length | |
| 396 | + if ( $limit === 0 ) { | |
| 397 | + $value = nl2br( $value ); | |
| 398 | + } | |
| 399 | + | |
| 310 | 400 | return $value; |
| 311 | 401 | } |
| 312 | 402 | |
| 313 | 403 | /** |
| @@ -329,9 +419,8 @@ | ||
| 329 | 419 | * @return bool |
| 330 | 420 | * @since 1.3.1 |
| 331 | 421 | */ |
| 332 | 422 | function hf_is_date( $value ) { |
| 333 | - | |
| 334 | 423 | if ( ! is_string( $value ) |
| 335 | 424 | || strlen( $value ) !== 10 |
| 336 | 425 | || (int) preg_match( '/\d{2,4}[-\/]\d{2}[-\/]\d{2,4}/', $value ) === 0 ) { |
| 337 | 426 | return false; |
| @@ -351,5 +440,108 @@ | ||
| 351 | 440 | // nothing, loop logic contains everything |
| 352 | 441 | } |
| 353 | 442 | $steps = array( 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ); |
| 354 | 443 | return round( $size, $precision ) . $steps[ $i ]; |
| 444 | +} | |
| 445 | + | |
| 446 | +/** | |
| 447 | + * Gets all the form tabs to show in the admin. | |
| 448 | + * @param Form $form | |
| 449 | + * @return array | |
| 450 | + */ | |
| 451 | +function hf_get_admin_tabs( Form $form ) { | |
| 452 | + $tabs = array( | |
| 453 | + 'fields' => __( 'Fields', 'html-forms' ), | |
| 454 | + 'messages' => __( 'Messages', 'html-forms' ), | |
| 455 | + 'settings' => __( 'Settings', 'html-forms' ), | |
| 456 | + 'actions' => __( 'Actions', 'html-forms' ), | |
| 457 | + ); | |
| 458 | + | |
| 459 | + if ( $form->settings['save_submissions'] ) { | |
| 460 | + $tabs['submissions'] = __( 'Submissions', 'html-forms' ); | |
| 461 | + } | |
| 462 | + return apply_filters( 'hf_admin_tabs', $tabs, $form ); | |
| 463 | +} | |
| 464 | + | |
| 465 | +function _hf_on_plugin_activation() { | |
| 466 | + if ( is_multisite() ) { | |
| 467 | + _hf_on_plugin_activation_multisite(); | |
| 468 | + return; | |
| 469 | + } | |
| 470 | + | |
| 471 | + // install table for regular wp install | |
| 472 | + _hf_create_submissions_table(); | |
| 473 | + | |
| 474 | + // add "edit_forms" cap to user that activated the plugin | |
| 475 | + $user = wp_get_current_user(); | |
| 476 | + $user->add_cap( 'edit_forms', true ); | |
| 477 | +} | |
| 478 | + | |
| 479 | +function _hf_on_plugin_activation_multisite() { | |
| 480 | + $added_caps = array(); | |
| 481 | + | |
| 482 | + foreach ( get_sites( array( 'number' => PHP_INT_MAX ) ) as $site ) { | |
| 483 | + switch_to_blog( (int) $site->blog_id ); | |
| 484 | + | |
| 485 | + // install table for current blog | |
| 486 | + _hf_create_submissions_table(); | |
| 487 | + | |
| 488 | + // iterate through current blog admins | |
| 489 | + foreach ( get_users( | |
| 490 | + array( | |
| 491 | + 'blog_id' => (int) $site->blog_id, | |
| 492 | + 'role' => 'administrator', | |
| 493 | + 'fields' => 'ID', | |
| 494 | + ) | |
| 495 | + ) as $admin_id ) { | |
| 496 | + if ( ! (int) $admin_id || in_array( $admin_id, $added_caps ) ) { | |
| 497 | + continue; | |
| 498 | + } | |
| 499 | + | |
| 500 | + // add "edit_forms" cap to site admin | |
| 501 | + $user = new \WP_User( (int) $admin_id ); | |
| 502 | + $user->add_cap( 'edit_forms', true ); | |
| 503 | + | |
| 504 | + $added_caps[] = $admin_id; | |
| 505 | + } | |
| 506 | + | |
| 507 | + restore_current_blog(); | |
| 508 | + } | |
| 509 | +} | |
| 510 | + | |
| 511 | +// install table for main site on regular installs, or active site for multisite | |
| 512 | +function _hf_create_submissions_table() { | |
| 513 | + /** @var wpdb */ | |
| 514 | + global $wpdb; | |
| 515 | + | |
| 516 | + $charset_collate = $wpdb->get_charset_collate(); | |
| 517 | + | |
| 518 | + // create table for storing submissions | |
| 519 | + $table = $wpdb->prefix . 'hf_submissions'; | |
| 520 | + $wpdb->query( | |
| 521 | + "CREATE TABLE IF NOT EXISTS {$table}( | |
| 522 | + `id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, | |
| 523 | + `form_id` INT UNSIGNED NOT NULL, | |
| 524 | + `data` TEXT NOT NULL, | |
| 525 | + `user_agent` TEXT NULL, | |
| 526 | + `ip_address` VARCHAR(255) NULL, | |
| 527 | + `referer_url` TEXT NULL, | |
| 528 | + `submitted_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP | |
| 529 | +) {$charset_collate};" | |
| 530 | + ); | |
| 531 | +} | |
| 532 | + | |
| 533 | +function _hf_on_add_user_to_blog( $user_id, $role, $blog_id ) { | |
| 534 | + if ( 'administrator' !== $role ) { | |
| 535 | + return; | |
| 536 | + } | |
| 537 | + | |
| 538 | + // add "edit_forms" cap to site admin | |
| 539 | + $user = new \WP_User( (int) $user_id ); | |
| 540 | + $user->add_cap( 'edit_forms', true ); | |
| 541 | +} | |
| 542 | + | |
| 543 | +function _hf_on_wp_insert_site( \WP_Site $site ) { | |
| 544 | + switch_to_blog( (int) $site->blog_id ); | |
| 545 | + _hf_create_submissions_table(); | |
| 546 | + restore_current_blog(); | |
| 355 | 547 | } |