Formidable Forms – Form Builder for WordPress - Version 2.0.18

Version Description

  • Add frm_field_extra_html hook
  • Change the License submenu to Plugin Licenses
  • Prevent specific html entity from breaking email message
  • PHP 7 updates
  • Add filter for removing wpautop from form success message
  • Fix HTML error on form builder page
  • Pro Features:
  • Allow ? and * in Phone Number Format
  • Remove child form from export options
  • Fix LIKE conditional logic bug
  • Some auto-update adjustments
  • Add frm_search_any_terms filter
  • PHP 7 updates
  • Fix file upload issue in CSV export
  • Fix issue with duplicate classes in HTML field
  • Fix filtering by user_id=current in graphs
  • Fix Dynamic List field with value like 9.99
  • Make sure userID field doesn't lose value when conditionally hidden/shown
Download this release

Release Info

Developer sswells
Plugin Icon 128x128 Formidable Forms – Form Builder for WordPress
Version 2.0.18
Comparing to
See all releases

Code changes from version 2.0.17 to 2.0.18

classes/controllers/FrmFieldsController.php CHANGED
@@ -540,6 +540,7 @@ class FrmFieldsController {
540
  self::add_html_size($field, $add_html);
541
  self::add_html_length($field, $add_html);
542
  self::add_html_placeholder($field, $add_html, $class);
 
543
 
544
  $class = apply_filters('frm_field_classes', implode(' ', $class), $field);
545
 
@@ -547,6 +548,7 @@ class FrmFieldsController {
547
 
548
  self::add_shortcodes_to_html($field, $add_html);
549
 
 
550
  $add_html = ' ' . implode( ' ', $add_html ) . ' ';
551
 
552
  if ( $echo ) {
@@ -664,6 +666,25 @@ class FrmFieldsController {
664
  }
665
  }
666
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  private static function add_shortcodes_to_html( $field, array &$add_html ) {
668
  if ( FrmField::is_option_empty( $field, 'shortcodes' ) ) {
669
  return;
540
  self::add_html_size($field, $add_html);
541
  self::add_html_length($field, $add_html);
542
  self::add_html_placeholder($field, $add_html, $class);
543
+ self::add_validation_messages( $field, $add_html );
544
 
545
  $class = apply_filters('frm_field_classes', implode(' ', $class), $field);
546
 
548
 
549
  self::add_shortcodes_to_html($field, $add_html);
550
 
551
+ $add_html = apply_filters( 'frm_field_extra_html', $add_html, $field );
552
  $add_html = ' ' . implode( ' ', $add_html ) . ' ';
553
 
554
  if ( $echo ) {
666
  }
667
  }
668
 
669
+ private static function add_validation_messages( $field, array &$add_html ) {
670
+ if ( FrmField::is_required( $field ) ) {
671
+ $required_message = FrmFieldsHelper::get_error_msg( $field, 'blank' );
672
+ $add_html['data-reqmsg'] = 'data-reqmsg="' . esc_attr( $required_message ) . '"';
673
+ }
674
+
675
+ if ( ! FrmField::is_option_empty( $field, 'invalid' ) ) {
676
+ $invalid_message = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
677
+ $add_html['data-invmsg'] = 'data-invmsg="' . esc_attr( $invalid_message ) . '"';
678
+ }
679
+
680
+ if ( $field['type'] == 'tel' ) {
681
+ $format = FrmEntryValidate::phone_format( $field );
682
+ $format = substr( $format, 2, -2 );
683
+ $key = 'pattern';
684
+ $add_html[ $key ] = $key . '="' . esc_attr( $format ) . '"';
685
+ }
686
+ }
687
+
688
  private static function add_shortcodes_to_html( $field, array &$add_html ) {
689
  if ( FrmField::is_option_empty( $field, 'shortcodes' ) ) {
690
  return;
classes/controllers/FrmFormsController.php CHANGED
@@ -133,33 +133,31 @@ class FrmFormsController {
133
  return self::get_settings_vars( $id, array(), $message );
134
  }
135
 
136
- public static function edit_key() {
137
- check_ajax_referer( 'frm_ajax', 'nonce' );
138
- FrmAppHelper::permission_check('frm_edit_forms', 'hide');
139
-
140
- $form_key = FrmAppHelper::get_post_param( 'update_value', '', 'sanitize_title' );
141
- $values = array( 'form_key' => trim( $form_key ) );
142
 
143
- $form_id = FrmAppHelper::get_post_param( 'form_id', '', 'absint' );
144
- FrmForm::update( $form_id, $values );
145
- $key = FrmForm::getKeyById( $form_id );
146
- echo stripslashes( $key );
147
- wp_die();
148
- }
149
 
150
- public static function edit_description() {
151
- check_ajax_referer( 'frm_ajax', 'nonce' );
152
- FrmAppHelper::permission_check('frm_edit_forms', 'hide');
153
 
154
  $form_id = FrmAppHelper::get_post_param( 'form_id', '', 'absint' );
155
  $value = FrmAppHelper::get_post_param( 'update_value', '', 'wp_filter_post_kses' );
156
 
157
- FrmForm::update( $form_id, array( 'description' => $value ) );
 
 
158
 
159
- $description = FrmAppHelper::use_wpautop( stripslashes( $value ) );
160
- echo $description;
161
- wp_die();
162
- }
163
 
164
  public static function update( $values = array() ) {
165
  if ( empty( $values ) ) {
133
  return self::get_settings_vars( $id, array(), $message );
134
  }
135
 
136
+ public static function edit_key() {
137
+ $values = self::edit_in_place_value( 'form_key' );
138
+ echo wp_kses( stripslashes( FrmForm::getKeyById( $values['form_id'] ) ), array() );
139
+ wp_die();
140
+ }
 
141
 
142
+ public static function edit_description() {
143
+ $values = self::edit_in_place_value( 'description' );
144
+ echo wp_kses_post( FrmAppHelper::use_wpautop( stripslashes( $values['description'] ) ) );
145
+ wp_die();
146
+ }
 
147
 
148
+ private static function edit_in_place_value( $field ) {
149
+ check_ajax_referer( 'frm_ajax', 'nonce' );
150
+ FrmAppHelper::permission_check('frm_edit_forms', 'hide');
151
 
152
  $form_id = FrmAppHelper::get_post_param( 'form_id', '', 'absint' );
153
  $value = FrmAppHelper::get_post_param( 'update_value', '', 'wp_filter_post_kses' );
154
 
155
+ $values = array( $field => trim( $value ) );
156
+ FrmForm::update( $form_id, $values );
157
+ $values['form_id'] = $form_id;
158
 
159
+ return $values;
160
+ }
 
 
161
 
162
  public static function update( $values = array() ) {
163
  if ( empty( $values ) ) {
classes/controllers/FrmSettingsController.php CHANGED
@@ -25,7 +25,10 @@ class FrmSettingsController {
25
 
26
  $sections = array();
27
  if ( apply_filters( 'frm_include_addon_page', false ) ) {
28
- $sections['licenses'] = array( 'class' => 'FrmAddonsController', 'function' => 'show_addons' );
 
 
 
29
  }
30
  $sections = apply_filters( 'frm_add_settings_section', $sections );
31
 
25
 
26
  $sections = array();
27
  if ( apply_filters( 'frm_include_addon_page', false ) ) {
28
+ $sections['licenses'] = array(
29
+ 'class' => 'FrmAddonsController', 'function' => 'show_addons',
30
+ 'name' => __( 'Plugin Licenses', 'formidable' ),
31
+ );
32
  }
33
  $sections = apply_filters( 'frm_add_settings_section', $sections );
34
 
classes/controllers/FrmStylesController.php CHANGED
@@ -195,7 +195,7 @@ class FrmStylesController {
195
 
196
  /**
197
  * @param string $message
198
- * @param array $forms
199
  */
200
  private static function manage( $message = '', $forms = array() ) {
201
  $frm_style = new FrmStyle();
195
 
196
  /**
197
  * @param string $message
198
+ * @param array|object $forms
199
  */
200
  private static function manage( $message = '', $forms = array() ) {
201
  $frm_style = new FrmStyle();
classes/controllers/FrmXMLController.php CHANGED
@@ -49,7 +49,11 @@ class FrmXMLController {
49
  }
50
 
51
  public static function form( $errors = array(), $message = '' ) {
52
- $forms = FrmForm::getAll( array( 'status' => array( null, '', 'published' ) ), 'name' );
 
 
 
 
53
 
54
  $export_types = apply_filters( 'frm_xml_export_types',
55
  array( 'forms' => __( 'Forms', 'formidable' ) )
49
  }
50
 
51
  public static function form( $errors = array(), $message = '' ) {
52
+ $where = array(
53
+ 'parent_form_id' => array( null, 0 ),
54
+ 'status' => array( null, '', 'published' )
55
+ );
56
+ $forms = FrmForm::getAll( $where, 'name' );
57
 
58
  $export_types = apply_filters( 'frm_xml_export_types',
59
  array( 'forms' => __( 'Forms', 'formidable' ) )
classes/helpers/FrmAppHelper.php CHANGED
@@ -10,7 +10,7 @@ class FrmAppHelper {
10
  /**
11
  * @since 2.0
12
  */
13
- public static $plug_version = '2.0.17';
14
 
15
  /**
16
  * @since 1.07.02
@@ -1553,6 +1553,9 @@ class FrmAppHelper {
1553
  public static function prepare_and_encode( $post_content ) {
1554
  //Loop through array to strip slashes and add only the needed ones
1555
  foreach ( $post_content as $key => $val ) {
 
 
 
1556
  self::prepare_action_slashes( $val, $key, $post_content );
1557
  unset( $key, $val );
1558
  }
10
  /**
11
  * @since 2.0
12
  */
13
+ public static $plug_version = '2.0.18';
14
 
15
  /**
16
  * @since 1.07.02
1553
  public static function prepare_and_encode( $post_content ) {
1554
  //Loop through array to strip slashes and add only the needed ones
1555
  foreach ( $post_content as $key => $val ) {
1556
+ // Replace problematic characters (like ")
1557
+ $val = str_replace( '"', '"', $val );
1558
+
1559
  self::prepare_action_slashes( $val, $key, $post_content );
1560
  unset( $key, $val );
1561
  }
classes/helpers/FrmFieldsHelper.php CHANGED
@@ -165,17 +165,20 @@ class FrmFieldsHelper {
165
  * @since 2.0
166
  */
167
  public static function get_error_msg( $field, $error ) {
168
- $frm_settings = FrmAppHelper::get_settings();
169
- $default_settings = $frm_settings->default_options();
 
170
 
171
- $defaults = array(
172
- 'unique_msg' => array( 'full' => $default_settings['unique_msg'], 'part' => $field->name . ' ' . __( 'must be unique', 'formidable' ) ),
173
- 'invalid' => array( 'full' => __( 'This field is invalid', 'formidable' ), 'part' => $field->name . ' ' . __( 'is invalid', 'formidable' ) ),
174
- );
 
175
 
176
- $msg = ( $field->field_options[ $error ] == $defaults[ $error ]['full'] || empty( $field->field_options[ $error ] ) ) ? $defaults[ $error ]['part'] : $field->field_options[ $error ];
177
- return $msg;
178
- }
 
179
 
180
  public static function get_form_fields( $form_id, $error = false ) {
181
  $fields = FrmField::get_all_for_form($form_id);
@@ -1150,6 +1153,19 @@ DEFAULT_HTML;
1150
  return $other_id;
1151
  }
1152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1153
  public static function show_onfocus_js( $is_selected ) {
1154
  $atts = array(
1155
  'icon' => 'frm_reload_icon',
165
  * @since 2.0
166
  */
167
  public static function get_error_msg( $field, $error ) {
168
+ $frm_settings = FrmAppHelper::get_settings();
169
+ $default_settings = $frm_settings->default_options();
170
+ $field_name = is_array( $field ) ? $field['name'] : $field->name;
171
 
172
+ $defaults = array(
173
+ 'unique_msg' => array( 'full' => $default_settings['unique_msg'], 'part' => sprintf( __('%s must be unique', 'formidable' ), $field_name ) ),
174
+ 'invalid' => array( 'full' => __( 'This field is invalid', 'formidable' ), 'part' => sprintf( __('%s is invalid', 'formidable' ), $field_name ) ),
175
+ 'blank' => array( 'full' => $frm_settings->blank_msg, 'part' => $frm_settings->blank_msg ),
176
+ );
177
 
178
+ $msg = FrmField::get_option( $field, $error );
179
+ $msg = ( $msg == $defaults[ $error ]['full'] || empty( $msg ) ) ? $defaults[ $error ]['part'] : $msg;
180
+ return $msg;
181
+ }
182
 
183
  public static function get_form_fields( $form_id, $error = false ) {
184
  $fields = FrmField::get_all_for_form($form_id);
1153
  return $other_id;
1154
  }
1155
 
1156
+ public static function clear_on_focus_html( $field, $display, $id = '' ) {
1157
+ if ( $display['clear_on_focus'] ) {
1158
+ echo '<span id="frm_clear_on_focus_' . esc_attr( $field['id'] . $id ) . '" class="frm-show-click">';
1159
+
1160
+ if ( $display['default_blank'] ) {
1161
+ self::show_default_blank_js( $field['default_blank'] );
1162
+ }
1163
+ self::show_onfocus_js( $field['clear_on_focus'] );
1164
+
1165
+ echo '</span>';
1166
+ }
1167
+ }
1168
+
1169
  public static function show_onfocus_js( $is_selected ) {
1170
  $atts = array(
1171
  'icon' => 'frm_reload_icon',
classes/helpers/FrmFormsHelper.php CHANGED
@@ -121,7 +121,8 @@ class FrmFormsHelper {
121
 
122
  public static function get_success_message( $atts ) {
123
  $message = apply_filters( 'frm_content', $atts['message'], $atts['form'], $atts['entry_id'] );
124
- $message = '<div class="' . esc_attr( $atts['class'] ) . '">' . wpautop( do_shortcode( $message ) ) . '</div>';
 
125
  return $message;
126
  }
127
 
121
 
122
  public static function get_success_message( $atts ) {
123
  $message = apply_filters( 'frm_content', $atts['message'], $atts['form'], $atts['entry_id'] );
124
+ $message = FrmAppHelper::use_wpautop( do_shortcode( $message ) );
125
+ $message = '<div class="' . esc_attr( $atts['class'] ) . '">' . $message . '</div>';
126
  return $message;
127
  }
128
 
classes/helpers/FrmListHelper.php CHANGED
@@ -3,17 +3,129 @@ if ( ! defined('ABSPATH') ) {
3
  die( 'You are not allowed to call this page directly.' );
4
  }
5
 
6
- class FrmListHelper extends WP_List_Table {
7
- var $params;
 
 
 
 
 
 
 
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  public function __construct( $args ) {
10
  $args = wp_parse_args( $args, array(
11
  'params' => array(),
 
 
 
 
12
  ) );
13
 
14
  $this->params = $args['params'];
15
 
16
- parent::__construct( $args );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
  public function ajax_user_can() {
@@ -27,8 +139,950 @@ class FrmListHelper extends WP_List_Table {
27
  public function display_rows() {
28
  $style = '';
29
  foreach ( $this->items as $item ) {
30
- $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
31
- echo "\n\t", $this->single_row( $item, $style );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
  }
3
  die( 'You are not allowed to call this page directly.' );
4
  }
5
 
6
+ class FrmListHelper {
7
+ /**
8
+ * The current list of items
9
+ *
10
+ * @since 2.0.18
11
+ * @var array
12
+ * @access public
13
+ */
14
+ public $items;
15
 
16
+ /**
17
+ * Various information about the current table
18
+ *
19
+ * @since 2.0.18
20
+ * @var array
21
+ * @access protected
22
+ */
23
+ protected $_args;
24
+
25
+ /**
26
+ * Various information needed for displaying the pagination
27
+ *
28
+ * @since 2.0.18
29
+ * @var array
30
+ */
31
+ protected $_pagination_args = array();
32
+
33
+ /**
34
+ * The current screen
35
+ *
36
+ * @since 2.0.18
37
+ * @var object
38
+ * @access protected
39
+ */
40
+ protected $screen;
41
+
42
+ /**
43
+ * Cached bulk actions
44
+ *
45
+ * @since 2.0.18
46
+ * @var array
47
+ * @access private
48
+ */
49
+ private $_actions;
50
+
51
+ /**
52
+ * Cached pagination output
53
+ *
54
+ * @since 2.0.18
55
+ * @var string
56
+ * @access private
57
+ */
58
+ private $_pagination;
59
+
60
+ /**
61
+ * The view switcher modes.
62
+ *
63
+ * @since 2.0.18
64
+ * @var array
65
+ * @access protected
66
+ */
67
+ protected $modes = array();
68
+
69
+ /**
70
+ *
71
+ * @var array
72
+ */
73
+ protected $params;
74
+
75
+ /**
76
+ * Stores the value returned by ->get_column_info()
77
+ *
78
+ * @var array
79
+ */
80
+ protected $_column_headers;
81
+
82
+ protected $compat_fields = array( '_args', '_pagination_args', 'screen', '_actions', '_pagination' );
83
+
84
+ protected $compat_methods = array(
85
+ 'set_pagination_args', 'get_views', 'get_bulk_actions', 'bulk_actions',
86
+ 'row_actions', 'view_switcher', 'get_items_per_page', 'pagination',
87
+ 'get_sortable_columns', 'get_column_info', 'get_table_classes', 'display_tablenav', 'extra_tablenav',
88
+ 'single_row_columns',
89
+ );
90
+
91
+ /**
92
+ * Construct the table object
93
+ */
94
  public function __construct( $args ) {
95
  $args = wp_parse_args( $args, array(
96
  'params' => array(),
97
+ 'plural' => '',
98
+ 'singular' => '',
99
+ 'ajax' => false,
100
+ 'screen' => null,
101
  ) );
102
 
103
  $this->params = $args['params'];
104
 
105
+ $this->screen = convert_to_screen( $args['screen'] );
106
+
107
+ add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
108
+
109
+ if ( ! $args['plural'] ) {
110
+ $args['plural'] = $this->screen->base;
111
+ }
112
+
113
+ $args['plural'] = sanitize_key( $args['plural'] );
114
+ $args['singular'] = sanitize_key( $args['singular'] );
115
+
116
+ $this->_args = $args;
117
+
118
+ if ( $args['ajax'] ) {
119
+ // wp_enqueue_script( 'list-table' );
120
+ add_action( 'admin_footer', array( $this, '_js_vars' ) );
121
+ }
122
+
123
+ if ( empty( $this->modes ) ) {
124
+ $this->modes = array(
125
+ 'list' => __( 'List View' ),
126
+ 'excerpt' => __( 'Excerpt View' )
127
+ );
128
+ }
129
  }
130
 
131
  public function ajax_user_can() {
139
  public function display_rows() {
140
  $style = '';
141
  foreach ( $this->items as $item ) {
142
+ echo "\n\t", $this->single_row( $item );
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Prepares the list of items for displaying.
148
+ * @uses FrmListHelper::set_pagination_args()
149
+ *
150
+ * @since 2.0.18
151
+ * @access public
152
+ * @abstract
153
+ */
154
+ public function prepare_items() {
155
+ die( 'function FrmListHelper::prepare_items() must be over-ridden in a sub-class.' );
156
+ }
157
+
158
+ /**
159
+ * An internal method that sets all the necessary pagination arguments
160
+ *
161
+ * @param array $args An associative array with information about the pagination
162
+ * @access protected
163
+ *
164
+ * @param array|string $args
165
+ */
166
+ protected function set_pagination_args( $args ) {
167
+ $args = wp_parse_args( $args, array(
168
+ 'total_items' => 0,
169
+ 'total_pages' => 0,
170
+ 'per_page' => 0,
171
+ ) );
172
+
173
+ if ( ! $args['total_pages'] && $args['per_page'] > 0 ) {
174
+ $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
175
+ }
176
+
177
+ // Redirect if page number is invalid and headers are not already sent.
178
+ if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
179
+ wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
180
+ exit;
181
+ }
182
+
183
+ $this->_pagination_args = $args;
184
+ }
185
+
186
+ /**
187
+ * Access the pagination args.
188
+ *
189
+ * @since 2.0.18
190
+ * @access public
191
+ *
192
+ * @param string $key Pagination argument to retrieve. Common values include 'total_items',
193
+ * 'total_pages', 'per_page', or 'infinite_scroll'.
194
+ * @return int Number of items that correspond to the given pagination argument.
195
+ */
196
+ public function get_pagination_arg( $key ) {
197
+ if ( 'page' == $key ) {
198
+ return $this->get_pagenum();
199
+ }
200
+
201
+ if ( isset( $this->_pagination_args[ $key ] ) ) {
202
+ return $this->_pagination_args[ $key ];
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Whether the table has items to display or not
208
+ *
209
+ * @since 2.0.18
210
+ * @access public
211
+ *
212
+ * @return bool
213
+ */
214
+ public function has_items() {
215
+ return ! empty( $this->items );
216
+ }
217
+
218
+ /**
219
+ * Message to be displayed when there are no items
220
+ *
221
+ * @since 2.0.18
222
+ * @access public
223
+ */
224
+ public function no_items() {
225
+ _e( 'No items found.' );
226
+ }
227
+
228
+ /**
229
+ * Display the search box.
230
+ *
231
+ * @since 2.0.18
232
+ * @access public
233
+ *
234
+ * @param string $text The search button text
235
+ * @param string $input_id The search input id
236
+ */
237
+ public function search_box( $text, $input_id ) {
238
+ if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
239
+ return;
240
+ }
241
+
242
+ $input_id = $input_id . '-search-input';
243
+
244
+ foreach ( array( 'orderby', 'order' ) as $search_params ) {
245
+ $this->hidden_search_inputs( $search_params );
246
+ }
247
+ ?>
248
+ <p class="search-box">
249
+ <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ) ?>"><?php echo wp_kses( $text, array() ); ?>:</label>
250
+ <input type="search" id="<?php echo esc_attr( $input_id ) ?>" name="s" value="<?php _admin_search_query(); ?>" />
251
+ <?php submit_button( $text, 'button', '', false, array( 'id' => 'search-submit' ) ); ?>
252
+ </p>
253
+ <?php
254
+ }
255
+
256
+ private function hidden_search_inputs( $param_name ) {
257
+ if ( ! empty( $_REQUEST[ $param_name ] ) ) {
258
+ echo '<input type="hidden" name="' . esc_attr( $param_name ) . '" value="' . esc_attr( $_REQUEST[ $param_name ] ) . '" />';
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Get an associative array ( id => link ) with the list
264
+ * of views available on this table.
265
+ *
266
+ * @since 2.0.18
267
+ * @access protected
268
+ *
269
+ * @return array
270
+ */
271
+ protected function get_views() {
272
+ return array();
273
+ }
274
+
275
+ /**
276
+ * Display the list of views available on this table.
277
+ *
278
+ * @since 2.0.18
279
+ * @access public
280
+ */
281
+ public function views() {
282
+ $views = $this->get_views();
283
+ /**
284
+ * Filter the list of available list table views.
285
+ *
286
+ * The dynamic portion of the hook name, `$this->screen->id`, refers
287
+ * to the ID of the current screen, usually a string.
288
+ *
289
+ * @since 3.5.0
290
+ *
291
+ * @param array $views An array of available list table views.
292
+ */
293
+ $views = apply_filters( 'views_' . $this->screen->id, $views );
294
+
295
+ if ( empty( $views ) ) {
296
+ return;
297
+ }
298
+
299
+ echo "<ul class='subsubsub'>\n";
300
+ foreach ( $views as $class => $view ) {
301
+ $views[ $class ] = "\t<li class='$class'>$view";
302
+ }
303
+ echo implode( " |</li>\n", $views ) . "</li>\n";
304
+ echo '</ul>';
305
+ }
306
+
307
+ /**
308
+ * Get an associative array ( option_name => option_title ) with the list
309
+ * of bulk actions available on this table.
310
+ *
311
+ * @since 2.0.18
312
+ * @access protected
313
+ *
314
+ * @return array
315
+ */
316
+ protected function get_bulk_actions() {
317
+ return array();
318
+ }
319
+
320
+ /**
321
+ * Display the bulk actions dropdown.
322
+ *
323
+ * @since 2.0.18
324
+ * @access protected
325
+ *
326
+ * @param string $which The location of the bulk actions: 'top' or 'bottom'.
327
+ * This is designated as optional for backwards-compatibility.
328
+ */
329
+ protected function bulk_actions( $which = '' ) {
330
+ if ( is_null( $this->_actions ) ) {
331
+ $no_new_actions = $this->_actions = $this->get_bulk_actions();
332
+ /**
333
+ * Filter the list table Bulk Actions drop-down.
334
+ *
335
+ * The dynamic portion of the hook name, `$this->screen->id`, refers
336
+ * to the ID of the current screen, usually a string.
337
+ *
338
+ * This filter can currently only be used to remove bulk actions.
339
+ *
340
+ * @since 3.5.0
341
+ *
342
+ * @param array $actions An array of the available bulk actions.
343
+ */
344
+ $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
345
+ $this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
346
+ $two = '';
347
+ } else {
348
+ $two = '2';
349
+ }
350
+
351
+ if ( empty( $this->_actions ) ) {
352
+ return;
353
+ }
354
+
355
+ echo "<label for='bulk-action-selector-" . esc_attr( $which ) . "' class='screen-reader-text'>" . esc_attr__( 'Select bulk action' ) . "</label>";
356
+ echo "<select name='action" . esc_attr( $two ) . "' id='bulk-action-selector-" . esc_attr( $which ) . "'>\n";
357
+ echo "<option value='-1' selected='selected'>" . esc_attr__( 'Bulk Actions' ) . "</option>\n";
358
+
359
+ foreach ( $this->_actions as $name => $title ) {
360
+ $class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
361
+
362
+ echo "\t<option value='". esc_attr( $name ) ."'$class>$title</option>\n";
363
+ }
364
+
365
+ echo "</select>\n";
366
+
367
+ submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) );
368
+ echo "\n";
369
+ }
370
+
371
+ /**
372
+ * Get the current action selected from the bulk actions dropdown.
373
+ *
374
+ * @since 2.0.18
375
+ * @access public
376
+ *
377
+ * @return string|false The action name or False if no action was selected
378
+ */
379
+ public function current_action() {
380
+ if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
381
+ return false;
382
+ }
383
+
384
+ $action = $this->get_bulk_action( 'action' );
385
+ if ( $action === false ) {
386
+ $action = $this->get_bulk_action( 'action2' );
387
+ }
388
+
389
+ return $action;
390
+ }
391
+
392
+ private static function get_bulk_action( $action_name ) {
393
+ $action = false;
394
+ if ( isset( $_REQUEST[ $action_name ] ) && -1 != sanitize_text_field( $_REQUEST[ $action_name ] ) ) {
395
+ $action = sanitize_text_field( $_REQUEST[ $action_name ] );
396
+ }
397
+ return $action;
398
+ }
399
+
400
+ /**
401
+ * Generate row actions div
402
+ *
403
+ * @since 2.0.18
404
+ * @access protected
405
+ *
406
+ * @param array $actions The list of actions
407
+ * @param bool $always_visible Whether the actions should be always visible
408
+ * @return string
409
+ */
410
+ protected function row_actions( $actions, $always_visible = false ) {
411
+ $action_count = count( $actions );
412
+ $i = 0;
413
+
414
+ if ( ! $action_count ) {
415
+ return '';
416
+ }
417
+
418
+ $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
419
+ foreach ( $actions as $action => $link ) {
420
+ ++$i;
421
+ ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
422
+ $out .= "<span class='$action'>$link$sep</span>";
423
+ }
424
+ $out .= '</div>';
425
+
426
+ $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>';
427
+
428
+ return $out;
429
+ }
430
+
431
+ /**
432
+ * Display a view switcher
433
+ *
434
+ * @since 2.0.18
435
+ * @access protected
436
+ *
437
+ * @param string $current_mode
438
+ */
439
+ protected function view_switcher( $current_mode ) {
440
+ ?>
441
+ <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
442
+ <div class="view-switch">
443
+ <?php
444
+ foreach ( $this->modes as $mode => $title ) {
445
+ $classes = array( 'view-' . $mode );
446
+ if ( $current_mode == $mode )
447
+ $classes[] = 'current';
448
+ printf(
449
+ "<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",
450
+ esc_url( add_query_arg( 'mode', $mode ) ),
451
+ implode( ' ', $classes ),
452
+ $title
453
+ );
454
+ }
455
+ ?>
456
+ </div>
457
+ <?php
458
+ }
459
+
460
+ /**
461
+ * Get the current page number
462
+ *
463
+ * @since 2.0.18
464
+ * @access public
465
+ *
466
+ * @return int
467
+ */
468
+ public function get_pagenum() {
469
+ $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
470
+
471
+ if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
472
+ $pagenum = $this->_pagination_args['total_pages'];
473
+ }
474
+
475
+ return max( 1, $pagenum );
476
+ }
477
+
478
+ /**
479
+ * Get number of items to display on a single page
480
+ *
481
+ * @since 2.0.18
482
+ * @access protected
483
+ *
484
+ * @param string $option
485
+ * @param int $default
486
+ * @return int
487
+ */
488
+ protected function get_items_per_page( $option, $default = 20 ) {
489
+ $per_page = (int) get_user_option( $option );
490
+ if ( empty( $per_page ) || $per_page < 1 ) {
491
+ $per_page = $default;
492
+ }
493
+
494
+ /**
495
+ * Filter the number of items to be displayed on each page of the list table.
496
+ *
497
+ * The dynamic hook name, $option, refers to the `per_page` option depending
498
+ * on the type of list table in use. Possible values include: 'edit_comments_per_page',
499
+ * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
500
+ * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
501
+ * 'edit_{$post_type}_per_page', etc.
502
+ *
503
+ * @since 2.9.0
504
+ *
505
+ * @param int $per_page Number of items to be displayed. Default 20.
506
+ */
507
+ return (int) apply_filters( $option, $per_page );
508
+ }
509
+
510
+ /**
511
+ * Display the pagination.
512
+ *
513
+ * @since 2.0.18
514
+ * @access protected
515
+ *
516
+ * @param string $which
517
+ */
518
+ protected function pagination( $which ) {
519
+ if ( empty( $this->_pagination_args ) ) {
520
+ return;
521
+ }
522
+
523
+ $total_items = $this->_pagination_args['total_items'];
524
+ $total_pages = $this->_pagination_args['total_pages'];
525
+ $infinite_scroll = false;
526
+ if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
527
+ $infinite_scroll = $this->_pagination_args['infinite_scroll'];
528
+ }
529
+
530
+ $output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
531
+
532
+ $current = $this->get_pagenum();
533
+
534
+ $current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
535
+
536
+ $current_url = remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
537
+
538
+ $page_links = array();
539
+
540
+ $total_pages_before = '<span class="paging-input">';
541
+ $total_pages_after = '</span>';
542
+
543
+ $disable_first = $disable_last = $disable_prev = $disable_next = false;
544
+
545
+ if ( $current == 1 ) {
546
+ $disable_first = true;
547
+ $disable_prev = true;
548
+ }
549
+ if ( $current == 2 ) {
550
+ $disable_first = true;
551
+ }
552
+ if ( $current == $total_pages ) {
553
+ $disable_last = true;
554
+ $disable_next = true;
555
+ }
556
+ if ( $current == $total_pages - 1 ) {
557
+ $disable_last = true;
558
+ }
559
+
560
+ if ( $disable_first ) {
561
+ $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&laquo;</span>';
562
+ } else {
563
+ $page_links[] = sprintf( "<a class='first-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
564
+ esc_url( remove_query_arg( 'paged', $current_url ) ),
565
+ __( 'First page' ),
566
+ '&laquo;'
567
+ );
568
+ }
569
+
570
+ if ( $disable_prev ) {
571
+ $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&lsaquo;</span>';
572
+ } else {
573
+ $page_links[] = sprintf( "<a class='prev-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
574
+ esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ),
575
+ __( 'Previous page' ),
576
+ '&lsaquo;'
577
+ );
578
+ }
579
+
580
+ if ( 'bottom' == $which ) {
581
+ $html_current_page = $current;
582
+ $total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input">';
583
+ } else {
584
+ $html_current_page = sprintf( "%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' />",
585
+ '<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
586
+ $current,
587
+ strlen( $total_pages )
588
+ );
589
+ }
590
+ $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
591
+ $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
592
+
593
+ if ( $disable_next ) {
594
+ $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&rsaquo;</span>';
595
+ } else {
596
+ $page_links[] = sprintf( "<a class='next-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
597
+ esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ),
598
+ __( 'Next page' ),
599
+ '&rsaquo;'
600
+ );
601
+ }
602
+
603
+ if ( $disable_last ) {
604
+ $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&raquo;</span>';
605
+ } else {
606
+ $page_links[] = sprintf( "<a class='last-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
607
+ esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
608
+ __( 'Last page' ),
609
+ '&raquo;'
610
+ );
611
+ }
612
+
613
+ $pagination_links_class = 'pagination-links';
614
+ if ( ! empty( $infinite_scroll ) ) {
615
+ $pagination_links_class = ' hide-if-js';
616
  }
617
+ $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
618
+
619
+ if ( $total_pages ) {
620
+ $page_class = $total_pages < 2 ? ' one-page' : '';
621
+ } else {
622
+ $page_class = ' no-pages';
623
+ }
624
+ $this->_pagination = "<div class='tablenav-pages" . esc_attr( $page_class ) . "'>$output</div>";
625
+
626
+ echo $this->_pagination;
627
+ }
628
+
629
+ /**
630
+ * Get a list of sortable columns. The format is:
631
+ * 'internal-name' => 'orderby'
632
+ * or
633
+ * 'internal-name' => array( 'orderby', true )
634
+ *
635
+ * The second format will make the initial sorting order be descending
636
+ *
637
+ * @since 2.0.18
638
+ * @access protected
639
+ *
640
+ * @return array
641
+ */
642
+ protected function get_sortable_columns() {
643
+ return array();
644
+ }
645
+
646
+ /**
647
+ * Gets the name of the default primary column.
648
+ *
649
+ * @since 4.3.0
650
+ * @access protected
651
+ *
652
+ * @return string Name of the default primary column, in this case, an empty string.
653
+ */
654
+ protected function get_default_primary_column_name() {
655
+ $columns = $this->get_columns();
656
+ $column = '';
657
+
658
+ // We need a primary defined so responsive views show something,
659
+ // so let's fall back to the first non-checkbox column.
660
+ foreach ( $columns as $col => $column_name ) {
661
+ if ( 'cb' === $col ) {
662
+ continue;
663
+ }
664
+
665
+ $column = $col;
666
+ break;
667
+ }
668
+
669
+ return $column;
670
+ }
671
+
672
+ /**
673
+ * Gets the name of the primary column.
674
+ *
675
+ * @since 4.3.0
676
+ * @access protected
677
+ *
678
+ * @return string The name of the primary column.
679
+ */
680
+ protected function get_primary_column_name() {
681
+ $columns = $this->get_columns();
682
+ $default = $this->get_default_primary_column_name();
683
+
684
+ // If the primary column doesn't exist fall back to the
685
+ // first non-checkbox column.
686
+ if ( ! isset( $columns[ $default ] ) ) {
687
+ $default = FrmListHelper::get_default_primary_column_name();
688
+ }
689
+
690
+ /**
691
+ * Filter the name of the primary column for the current list table.
692
+ *
693
+ * @since 4.3.0
694
+ *
695
+ * @param string $default Column name default for the specific list table, e.g. 'name'.
696
+ * @param string $context Screen ID for specific list table, e.g. 'plugins'.
697
+ */
698
+ $column = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
699
+
700
+ if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
701
+ $column = $default;
702
+ }
703
+
704
+ return $column;
705
+ }
706
+
707
+ /**
708
+ * Get a list of all, hidden and sortable columns, with filter applied
709
+ *
710
+ * @since 2.0.18
711
+ * @access protected
712
+ *
713
+ * @return array
714
+ */
715
+ protected function get_column_info() {
716
+ // $_column_headers is already set / cached
717
+ if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
718
+ // Back-compat for list tables that have been manually setting $_column_headers for horse reasons.
719
+ // In 4.3, we added a fourth argument for primary column.
720
+ $column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
721
+ foreach ( $this->_column_headers as $key => $value ) {
722
+ $column_headers[ $key ] = $value;
723
+ }
724
+
725
+ return $column_headers;
726
+ }
727
+
728
+ $columns = get_column_headers( $this->screen );
729
+ $hidden = get_hidden_columns( $this->screen );
730
+
731
+ $sortable_columns = $this->get_sortable_columns();
732
+ /**
733
+ * Filter the list table sortable columns for a specific screen.
734
+ *
735
+ * The dynamic portion of the hook name, `$this->screen->id`, refers
736
+ * to the ID of the current screen, usually a string.
737
+ *
738
+ * @since 3.5.0
739
+ *
740
+ * @param array $sortable_columns An array of sortable columns.
741
+ */
742
+ $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
743
+
744
+ $sortable = array();
745
+ foreach ( $_sortable as $id => $data ) {
746
+ if ( empty( $data ) ) {
747
+ continue;
748
+ }
749
+
750
+ $data = (array) $data;
751
+ if ( ! isset( $data[1] ) ) {
752
+ $data[1] = false;
753
+ }
754
+
755
+ $sortable[ $id ] = $data;
756
+ }
757
+
758
+ $primary = $this->get_primary_column_name();
759
+ $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
760
+
761
+ return $this->_column_headers;
762
+ }
763
+
764
+ /**
765
+ * Return number of visible columns
766
+ *
767
+ * @since 2.0.18
768
+ * @access public
769
+ *
770
+ * @return int
771
+ */
772
+ public function get_column_count() {
773
+ list ( $columns, $hidden ) = $this->get_column_info();
774
+ $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
775
+ return count( $columns ) - count( $hidden );
776
+ }
777
+
778
+ /**
779
+ * Print column headers, accounting for hidden and sortable columns.
780
+ *
781
+ * @since 2.0.18
782
+ * @access public
783
+ *
784
+ * @staticvar int $cb_counter
785
+ *
786
+ * @param bool $with_id Whether to set the id attribute or not
787
+ */
788
+ public function print_column_headers( $with_id = true ) {
789
+ list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
790
+
791
+ $current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
792
+ $current_url = remove_query_arg( 'paged', $current_url );
793
+
794
+ if ( isset( $_GET['orderby'] ) ) {
795
+ $current_orderby = sanitize_text_field( $_GET['orderby'] );
796
+ } else {
797
+ $current_orderby = '';
798
+ }
799
+
800
+ if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] ) {
801
+ $current_order = 'desc';
802
+ } else {
803
+ $current_order = 'asc';
804
+ }
805
+
806
+ if ( ! empty( $columns['cb'] ) ) {
807
+ static $cb_counter = 1;
808
+ $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
809
+ . '<input id="cb-select-all-' . esc_attr( $cb_counter ) . '" type="checkbox" />';
810
+ $cb_counter++;
811
+ }
812
+
813
+ foreach ( $columns as $column_key => $column_display_name ) {
814
+ $class = array( 'manage-column', "column-$column_key" );
815
+
816
+ if ( in_array( $column_key, $hidden ) ) {
817
+ $class[] = 'hidden';
818
+ }
819
+
820
+ if ( 'cb' == $column_key ) {
821
+ $class[] = 'check-column';
822
+ } else if ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) {
823
+ $class[] = 'num';
824
+ }
825
+
826
+ if ( $column_key === $primary ) {
827
+ $class[] = 'column-primary';
828
+ }
829
+
830
+ if ( isset( $sortable[ $column_key ] ) ) {
831
+ list( $orderby, $desc_first ) = $sortable[ $column_key ];
832
+
833
+ if ( $current_orderby == $orderby ) {
834
+ $order = 'asc' == $current_order ? 'desc' : 'asc';
835
+ $class[] = 'sorted';
836
+ $class[] = $current_order;
837
+ } else {
838
+ $order = $desc_first ? 'desc' : 'asc';
839
+ $class[] = 'sortable';
840
+ $class[] = $desc_first ? 'asc' : 'desc';
841
+ }
842
+
843
+ $column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
844
+ }
845
+
846
+ $tag = ( 'cb' === $column_key ) ? 'td' : 'th';
847
+ $scope = ( 'th' === $tag ) ? 'scope="col"' : '';
848
+ $id = $with_id ? "id='" . esc_attr( $column_key ) . "'" : '';
849
+
850
+ if ( ! empty( $class ) ) {
851
+ $class = "class='" . join( ' ', $class ) . "'";
852
+ }
853
+
854
+ echo "<$tag $scope $id $class>$column_display_name</$tag>";
855
+ }
856
+ }
857
+
858
+ /**
859
+ * Display the table
860
+ *
861
+ * @since 2.0.18
862
+ * @access public
863
+ */
864
+ public function display() {
865
+ $singular = $this->_args['singular'];
866
+
867
+ $this->display_tablenav( 'top' );
868
+ ?>
869
+ <table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
870
+ <thead>
871
+ <tr>
872
+ <?php $this->print_column_headers(); ?>
873
+ </tr>
874
+ </thead>
875
+
876
+ <tbody id="the-list"<?php
877
+ if ( $singular ) {
878
+ echo " data-wp-lists='list:" . esc_attr( $singular ) . "'";
879
+ } ?>>
880
+ <?php $this->display_rows_or_placeholder(); ?>
881
+ </tbody>
882
+
883
+ <tfoot>
884
+ <tr>
885
+ <?php $this->print_column_headers( false ); ?>
886
+ </tr>
887
+ </tfoot>
888
+
889
+ </table>
890
+ <?php
891
+ $this->display_tablenav( 'bottom' );
892
+ }
893
+
894
+ /**
895
+ * Get a list of CSS classes for the list table table tag.
896
+ *
897
+ * @since 2.0.18
898
+ * @access protected
899
+ *
900
+ * @return array List of CSS classes for the table tag.
901
+ */
902
+ protected function get_table_classes() {
903
+ return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
904
+ }
905
+
906
+ /**
907
+ * Generate the table navigation above or below the table
908
+ *
909
+ * @since 2.0.18
910
+ * @access protected
911
+ * @param string $which
912
+ */
913
+ protected function display_tablenav( $which ) {
914
+ if ( 'top' == $which ) {
915
+ wp_nonce_field( 'bulk-' . $this->_args['plural'] );
916
+ }
917
+ ?>
918
+ <div class="tablenav <?php echo esc_attr( $which ); ?>">
919
+
920
+ <div class="alignleft actions bulkactions">
921
+ <?php $this->bulk_actions( $which ); ?>
922
+ </div>
923
+ <?php
924
+ $this->extra_tablenav( $which );
925
+ $this->pagination( $which );
926
+ ?>
927
+
928
+ <br class="clear" />
929
+ </div>
930
+ <?php
931
+ }
932
+
933
+ /**
934
+ * Extra controls to be displayed between bulk actions and pagination
935
+ *
936
+ * @since 2.0.18
937
+ * @access protected
938
+ *
939
+ * @param string $which
940
+ */
941
+ protected function extra_tablenav( $which ) {}
942
+
943
+ /**
944
+ * Generate the tbody element for the list table.
945
+ *
946
+ * @since 2.0.18
947
+ * @access public
948
+ */
949
+ public function display_rows_or_placeholder() {
950
+ if ( $this->has_items() ) {
951
+ $this->display_rows();
952
+ } else {
953
+ echo '<tr class="no-items"><td class="colspanchange" colspan="' . esc_attr( $this->get_column_count() ) . '">';
954
+ $this->no_items();
955
+ echo '</td></tr>';
956
+ }
957
+ }
958
+
959
+ /**
960
+ * Generates content for a single row of the table
961
+ *
962
+ * @since 2.0.18
963
+ * @access public
964
+ *
965
+ * @param object $item The current item
966
+ */
967
+ public function single_row( $item ) {
968
+ echo '<tr>';
969
+ $this->single_row_columns( $item );
970
+ echo '</tr>';
971
+ }
972
+
973
+ /**
974
+ * Generates the columns for a single row of the table
975
+ *
976
+ * @since 2.0.18
977
+ * @access protected
978
+ *
979
+ * @param object $item The current item
980
+ */
981
+ protected function single_row_columns( $item ) {
982
+ list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
983
+
984
+ foreach ( $columns as $column_name => $column_display_name ) {
985
+ $classes = "$column_name column-$column_name";
986
+ if ( $primary === $column_name ) {
987
+ $classes .= ' has-row-actions column-primary';
988
+ }
989
+
990
+ if ( in_array( $column_name, $hidden ) ) {
991
+ $classes .= ' hidden';
992
+ }
993
+
994
+ // Comments column uses HTML in the display name with screen reader text.
995
+ // Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
996
+ $data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"';
997
+
998
+ $attributes = "class='$classes' $data";
999
+
1000
+ if ( 'cb' == $column_name ) {
1001
+ echo '<th scope="row" class="check-column"></th>';
1002
+ } elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1003
+ echo call_user_func(
1004
+ array( $this, '_column_' . $column_name ),
1005
+ $item,
1006
+ $classes,
1007
+ $data,
1008
+ $primary
1009
+ );
1010
+ } elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1011
+ echo "<td $attributes>";
1012
+ echo call_user_func( array( $this, 'column_' . $column_name ), $item );
1013
+ echo $this->handle_row_actions( $item, $column_name, $primary );
1014
+ echo '</td>';
1015
+ } else {
1016
+ echo "<td $attributes>";
1017
+ echo $this->handle_row_actions( $item, $column_name, $primary );
1018
+ echo '</td>';
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ /**
1024
+ * Generates and display row actions links for the list table.
1025
+ *
1026
+ * @since 4.3.0
1027
+ * @access protected
1028
+ *
1029
+ * @param object $item The item being acted upon.
1030
+ * @param string $column_name Current column name.
1031
+ * @param string $primary Primary column name.
1032
+ * @return string The row actions output. In this case, an empty string.
1033
+ */
1034
+ protected function handle_row_actions( $item, $column_name, $primary ) {
1035
+ return $column_name == $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : '';
1036
+ }
1037
+
1038
+ /**
1039
+ * Handle an incoming ajax request (called from admin-ajax.php)
1040
+ *
1041
+ * @since 2.0.18
1042
+ * @access public
1043
+ */
1044
+ public function ajax_response() {
1045
+ $this->prepare_items();
1046
+
1047
+ ob_start();
1048
+ if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1049
+ $this->display_rows();
1050
+ } else {
1051
+ $this->display_rows_or_placeholder();
1052
+ }
1053
+
1054
+ $rows = ob_get_clean();
1055
+
1056
+ $response = array( 'rows' => $rows );
1057
+
1058
+ if ( isset( $this->_pagination_args['total_items'] ) ) {
1059
+ $response['total_items_i18n'] = sprintf(
1060
+ _n( '%s item', '%s items', $this->_pagination_args['total_items'] ),
1061
+ number_format_i18n( $this->_pagination_args['total_items'] )
1062
+ );
1063
+ }
1064
+ if ( isset( $this->_pagination_args['total_pages'] ) ) {
1065
+ $response['total_pages'] = $this->_pagination_args['total_pages'];
1066
+ $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1067
+ }
1068
+
1069
+ die( wp_json_encode( $response ) );
1070
+ }
1071
+
1072
+ /**
1073
+ * Send required variables to JavaScript land
1074
+ *
1075
+ * @access public
1076
+ */
1077
+ public function _js_vars() {
1078
+ $args = array(
1079
+ 'class' => get_class( $this ),
1080
+ 'screen' => array(
1081
+ 'id' => $this->screen->id,
1082
+ 'base' => $this->screen->base,
1083
+ )
1084
+ );
1085
+
1086
+ printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1087
  }
1088
  }
classes/models/FrmEntry.php CHANGED
@@ -831,7 +831,7 @@ class FrmEntry {
831
  * @param array $values
832
  * @return int | boolean $entry_id
833
  */
834
- public static function create_entry_from_xml( $values ){
835
  $entry_id = self::create_entry( $values, 'xml' );
836
 
837
  return $entry_id;
831
  * @param array $values
832
  * @return int | boolean $entry_id
833
  */
834
+ public static function create_entry_from_xml( $values ) {
835
  $entry_id = self::create_entry( $values, 'xml' );
836
 
837
  return $entry_id;
classes/models/FrmEntryValidate.php CHANGED
@@ -76,14 +76,17 @@ class FrmEntryValidate {
76
  }
77
 
78
  if ( $posted_field->required == '1' && ! is_array( $value ) && trim( $value ) == '' ) {
79
- $frm_settings = FrmAppHelper::get_settings();
80
- $errors[ 'field' . $args['id'] ] = ( ! isset( $posted_field->field_options['blank'] ) || $posted_field->field_options['blank'] == '' ) ? $frm_settings->blank_msg : $posted_field->field_options['blank'];
81
  } else if ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) {
82
  $_POST['item_name'] = $value;
83
  }
84
 
85
- self::validate_url_field($errors, $posted_field, $value, $args);
86
- self::validate_email_field($errors, $posted_field, $value, $args);
 
 
 
 
87
 
88
  FrmEntriesHelper::set_posted_value($posted_field, $value, $args);
89
 
@@ -121,6 +124,80 @@ class FrmEntryValidate {
121
  }
122
  }
123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  public static function validate_recaptcha( &$errors, $field, $args ) {
125
  if ( $field->type != 'captcha' || FrmAppHelper::is_admin() || apply_filters( 'frm_is_field_hidden', false, $field, stripslashes_deep( $_POST ) ) ) {
126
  return;
76
  }
77
 
78
  if ( $posted_field->required == '1' && ! is_array( $value ) && trim( $value ) == '' ) {
79
+ $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $posted_field, 'blank' );
 
80
  } else if ( $posted_field->type == 'text' && ! isset( $_POST['item_name'] ) ) {
81
  $_POST['item_name'] = $value;
82
  }
83
 
84
+ if ( $value != '' ) {
85
+ self::validate_url_field( $errors, $posted_field, $value, $args );
86
+ self::validate_email_field( $errors, $posted_field, $value, $args );
87
+ self::validate_number_field( $errors, $posted_field, $value, $args );
88
+ self::validate_phone_field( $errors, $posted_field, $value, $args );
89
+ }
90
 
91
  FrmEntriesHelper::set_posted_value($posted_field, $value, $args);
92
 
124
  }
125
  }
126
 
127
+ public static function validate_number_field( &$errors, $field, $value, $args ) {
128
+ //validate the number format
129
+ if ( $field->type != 'number' ) {
130
+ return;
131
+ }
132
+
133
+ if ( ! is_numeric( $value) ) {
134
+ $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
135
+ }
136
+
137
+ // validate number settings
138
+ if ( $value != '' ) {
139
+ $frm_settings = FrmAppHelper::get_settings();
140
+ // only check if options are available in settings
141
+ if ( $frm_settings->use_html && isset( $field->field_options['minnum'] ) && isset( $field->field_options['maxnum'] ) ) {
142
+ //minnum maxnum
143
+ if ( (float) $value < $field->field_options['minnum'] ) {
144
+ $errors[ 'field' . $args['id'] ] = __( 'Please select a higher number', 'formidable' );
145
+ } else if ( (float) $value > $field->field_options['maxnum'] ) {
146
+ $errors[ 'field' . $args['id'] ] = __( 'Please select a lower number', 'formidable' );
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ public static function validate_phone_field( &$errors, $field, $value, $args ) {
153
+ if ( $field->type != 'phone' ) {
154
+ return;
155
+ }
156
+
157
+ $pattern = self::phone_format( $field );
158
+
159
+ if ( ! preg_match( $pattern, $value ) ) {
160
+ $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
161
+ }
162
+ }
163
+
164
+ public static function phone_format( $field ) {
165
+ $default_format = '^((\+\d{1,3}(-|.| )?\(?\d\)?(-| |.)?\d{1,5})|(\(?\d{2,6}\)?))(-|.| )?(\d{3,4})(-|.| )?(\d{4})(( x| ext)\d{1,5}){0,1}$';
166
+ if ( FrmField::is_option_empty( $field, 'format' ) ) {
167
+ $pattern = $default_format;
168
+ } else {
169
+ $pattern = FrmField::get_option( $field, 'format' );
170
+ }
171
+
172
+ $pattern = apply_filters( 'frm_phone_pattern', $pattern, $field );
173
+
174
+ //check if format is already a regular expression
175
+ if ( strpos( $pattern, '^' ) !== 0 ) {
176
+ //if not, create a regular expression
177
+ $pattern = preg_replace( '/\d/', '\d', preg_quote( $pattern ) );
178
+ $pattern = str_replace( 'a', '[a-z]', $pattern );
179
+ $pattern = str_replace( 'A', '[A-Z]', $pattern );
180
+ $pattern = str_replace( '*', 'w', $pattern );
181
+ $pattern = str_replace( '/', '\/', $pattern );
182
+
183
+ if ( strpos( $pattern, '\?' ) !== false ) {
184
+ $parts = explode( '\?', $pattern );
185
+ $pattern = '';
186
+ foreach ( $parts as $part ) {
187
+ if ( empty( $pattern ) ) {
188
+ $pattern .= $part;
189
+ } else {
190
+ $pattern .= '(' . $part . ')?';
191
+ }
192
+ }
193
+ }
194
+ $pattern = '^' . $pattern . '$';
195
+ }
196
+
197
+ $pattern = '/' . $pattern . '/';
198
+ return $pattern;
199
+ }
200
+
201
  public static function validate_recaptcha( &$errors, $field, $args ) {