PluginProbe
Filter Everything — WordPress & WooCommerce Filters / 1.9.7
Filter Everything — WordPress & WooCommerce Filters v1.9.7
1.9.7 1.9.6 1.9.5 1.9.4 1.9.3 1.9.2.2 1.9.2.1 trunk 1.2.1 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.1 1.4.4 1.4.5 1.4.8 1.4.9 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 All 52 releases
← All changes | src/Admin/FilterFields.php +743 -200 1.3.21.9.7 View file →
@@ -2,10 +2,10 @@
2 2
3 3
4 4 namespace FilterEverything\Filter;
5 5
6 -if ( ! defined('WPINC') ) {
7 - wp_die();
6 +if ( ! defined('ABSPATH') ) {
7 + exit;
8 8 }
9 9
10 10 class FilterFields
11 11 {
@@ -32,15 +32,17 @@
32 32 }
33 33
34 34 public function registerHooks()
35 35 {
36 - if( ! $this->hooksRegistered ){
37 - add_filter('wpc_input_type_select', [$this, 'addSpinnerToSelect'], 10, 2);
36 + if ( ! $this->hooksRegistered ) {
37 + add_filter( 'wpc_input_type_select', [ $this, 'addSpinnerToSelect' ], 10, 2 );
38 + add_filter( 'wpc_input_type_radio', [ $this, 'addSpinnerToDateFormats' ], 10, 2 );
38 39
39 40 add_action( 'wp_ajax_wpc-delete-filter', [ $this, 'ajaxDeleteFilter' ] );
40 41 add_action( 'wp_ajax_wpc-load-exclude-terms', [ $this, 'sendExcludedTerms' ] );
42 + add_action( 'wp_ajax_wpc_get_date_formats', [ $this, 'sendDateFormats' ] );
41 43 add_action( 'wp_ajax_wpc-validate-filters', [ $this, 'ajaxValidateFilters' ] );
42 - add_action( 'after_delete_post', [$this, 'deleteRelatedFilters'], 10, 2 );
44 + add_action( 'after_delete_post', [ $this, 'deleteRelatedFilters' ], 10, 2 );
43 45
44 46 $this->hooksRegistered = true;
45 47 }
46 48 }
@@ -49,51 +51,52 @@
49 51 {
50 52 // maybe add filter in future to allow change default fields
51 53 do_action( 'wpc_before_setup_filter_fields' );
52 54 $entity = $this->em->getEntityByFilter( array('entity' => 'taxonomy', 'e_name' => 'category'), 'post' );
55 + $entityTerms = $entity->getTermsForSelect();
56 + asort( $entityTerms, SORT_NATURAL );
53 57
54 58 $defaultFields = array(
55 59 'ID' => array(
56 - 'type' => 'Hidden'
60 + 'type' => 'Hidden'
57 61 ),
58 62 'parent' => array(
59 - 'type' => 'Hidden'
63 + 'type' => 'Hidden'
60 64 ),
61 65 'menu_order' => array(
62 - 'type' => 'Hidden',
63 - 'class' => 'wpc-menu-order-field'
66 + 'type' => 'Hidden',
67 + 'class' => 'wpc-menu-order-field'
64 68 ),
65 - 'label' => array(
66 - 'type' => 'Text',
67 - 'label' => esc_html__( 'Filter Label', 'filter-everything' ),
68 - 'class' => 'wpc-field-label',
69 - 'placeholder' => esc_html__( 'New Filter', 'filter-everything'),
70 - 'instructions' => esc_html__( 'Title which will appear in filter widget', 'filter-everything' )
71 - ),
72 69 'entity' => array(
73 - 'type' => 'Select',
74 - 'label' => esc_html__( 'Filter by', 'filter-everything' ),
75 - 'class' => 'wpc-field-entity',
76 - 'options' => $this->em->getPossibleEntities(),
77 - 'default' => 'taxonomy_category',
78 - 'instructions' => esc_html__( 'A thing by which posts will be filtered', 'filter-everything' ),
79 - 'required' => true
70 + 'type' => 'Select',
71 + 'label' => esc_html__( 'Filter by', 'filter-everything' ),
72 + 'class' => 'wpc-field-entity',
73 + 'options' => $this->em->getPossibleEntities(),
74 + 'default' => 'taxonomy_category',
75 + 'instructions' => esc_html__( 'A thing by which posts will be filtered', 'filter-everything' ),
76 + 'required' => true
80 77 ),
81 78 'e_name' => array(
82 - 'type' => 'Select',
83 - 'label' => esc_html__( 'Meta Key', 'filter-everything' ),
84 - 'class' => 'wpc-field-ename',
85 - 'options' => $this->metaKeysOptions(),
86 - 'instructions' => esc_html__( 'Name of the Custom Field. Please, see Popular Meta keys at the bottom', 'filter-everything'),
87 - 'required' => true
79 + 'type' => 'Text',
80 + 'label' => esc_html__( 'Meta Key', 'filter-everything' ),
81 + 'class' => 'wpc-field-ename',
82 + 'instructions' => esc_html__( 'Name of the Custom Field', 'filter-everything'),
83 + 'required' => true
88 84 ),
85 + 'label' => array(
86 + 'type' => 'Text',
87 + 'label' => esc_html__( 'Filter Title', 'filter-everything' ),
88 + 'class' => 'wpc-field-label',
89 + 'placeholder' => esc_html__( 'New Filter', 'filter-everything'),
90 + 'instructions' => esc_html__( 'Will appear in the Filters widget', 'filter-everything' )
91 + ),
89 92 'slug' => array(
90 93 'type' => 'Text',
91 94 'label' => esc_html__( 'Var Name for URL', 'filter-everything' ),
92 95 'class' => 'wpc-field-slug',
93 - 'instructions' => esc_html__( 'Name of a part of URL responsible for this filter', 'filter-everything'),
96 + 'instructions' => esc_html__( 'Name of a part of the URL responsible for this filter', 'filter-everything'),
94 97 'tooltip' => wp_kses(
95 - __( 'For example, in the URL path:<br />/?color=blue&size=large<br />"color" and "size" are the names of the URL vars.<br />In PRO version of the plugin the URL part after "?" becomes <br />/color-blue/size-large/', 'filter-everything'),
98 + __( 'For example, in the URL path:<br />/?color=blue&size=large<br />"color" and "size" are the names of the URL vars.<br />In the PRO version of the plugin, the URL part after "?" becomes <br />/color-blue/size-large/', 'filter-everything'),
96 99 array( 'br' => array() )
97 100 ),
98 101 'required' => true
99 102 ), // Optional
@@ -102,10 +105,69 @@
102 105 'label' => esc_html__( 'View in Widget', 'filter-everything' ),
103 106 'class' => 'wpc-field-view',
104 107 'options' => $this->getViewOptions(),
105 108 'default' => 'checkboxes',
106 - 'instructions' => ''
109 + 'instructions' => '',
107 110 ),
111 + 'show_range_list' => array(
112 + 'type' => 'inProButton',
113 + 'pro_label' => flrt_pro_promo_label(),
114 + 'label' => esc_html__('Show Range List', 'filter-everything'),
115 + 'class' => 'wpc-field-show-range-list',
116 + 'default' => '',
117 + 'tooltip' => '',
118 + ),
119 + 'dropdown_label' => array(
120 + 'type' => 'Text',
121 + 'label' => esc_html__( 'Dropdown Label', 'filter-everything' ),
122 + 'class' => 'wpc-field-dropdown-label',
123 + 'default' => '',
124 + 'placeholder' => esc_html__( 'e.g. - Select color -', 'filter-everything' ),
125 + 'instructions' => esc_html__( 'Label for the default dropdown option', 'filter-everything' ),
126 + ),
127 + 'date_type' => array(
128 + 'type' => 'Select',
129 + 'label' => esc_html__( 'Date Type', 'filter-everything' ),
130 + 'class' => 'wpc-date-type',
131 + 'options' => array(
132 + 'date' => esc_html__( 'Date', 'filter-everything' ),
133 + 'datetime' => esc_html__( 'Date Time', 'filter-everything' ),
134 + 'time' => esc_html__( 'Time', 'filter-everything' ),
135 + ),
136 + 'default' => 'date',
137 + 'instructions' => '',
138 + ),
139 + 'date_format' => array(
140 + 'type' => 'Radio',
141 + 'label' => esc_html__( 'Date Format', 'filter-everything' ),
142 + 'class' => 'wpc-date-format',
143 + 'options' => $this->getDateFormatOptions(),
144 + 'default' => __( 'F j, Y' ),
145 + 'instructions' => esc_html__( 'How the date will be displayed in the Filters widget', 'filter-everything' ),
146 + 'tooltip' => wp_kses(
147 + sprintf( __( 'More PHP date formats can be found on <a href="%1$s" target="_blank">this page</a>.', 'filter-everything' ), 'https://wordpress.org/documentation/article/customize-date-and-time-format/' ),
148 + array(
149 + 'a' => array(
150 + 'href' => true,
151 + ) )
152 + )
153 + ),
154 + 'show_term_names' => array(
155 + 'type' => 'Checkbox',
156 + 'label' => esc_html__( 'Show Term names', 'filter-everything' ),
157 + 'class' => 'wpc-field-show-term-names',
158 + 'default' => 'yes',
159 + 'instructions' => esc_html__( 'Term names for filters with swatches or brand images may be hidden', 'filter-everything' ),
160 + 'tooltip' => '',
161 + ),
162 + 'selected_and_above' => array(
163 + 'type' => 'Checkbox',
164 + 'label' => esc_html__( 'Mode «All stars+»', 'filter-everything' ),
165 + 'class' => 'wpc-field-show-selected-and-above',
166 + 'default' => 'no',
167 + 'instructions' => esc_html__( 'E.g. if 3 stars are selected, all products with rating 3+ will be shown', 'filter-everything' ),
168 + 'tooltip' => '',
169 + ),
108 170 'logic' => array(
109 171 'type' => 'Select',
110 172 'label' => esc_html__( 'Filter Logic', 'filter-everything' ),
111 173 'class' => 'wpc-field-logic',
@@ -110,11 +172,11 @@
110 172 'label' => esc_html__( 'Filter Logic', 'filter-everything' ),
111 173 'class' => 'wpc-field-logic',
112 174 'options' => array('or' => esc_html__('OR', 'filter-everything' ), 'and' => esc_html__('AND', 'filter-everything') ),
113 175 'default' => 'or',
114 - 'instructions' => esc_html__( 'Determines how to select posts, when two or more terms of this filter selected', 'filter-everything' ),
176 + 'instructions' => esc_html__( 'Determines how to select posts when two or more terms of this filter are selected', 'filter-everything' ),
115 177 'tooltip' => wp_kses(
116 - __( '«OR» means to show posts if them are at least in one of selected terms. <br />«AND» means, that posts should belong to all selected terms at the same time.', 'filter-everything' ),
178 + __( '«OR» means to show posts if they are at least in one of the selected terms. <br />«AND» means that posts should belong to all selected terms at the same time.', 'filter-everything' ),
117 179 array( 'br' => array() )
118 180 ),
119 181 ), // AND
120 182 'orderby' => array(
@@ -121,10 +183,14 @@
121 183 'type' => 'Select',
122 184 'label' => esc_html__( 'Sort Terms by', 'filter-everything' ),
123 185 'class' => 'wpc-field-orderby',
124 186 'options' => $this->getOrderByOptions(),
125 - 'default' => 'nameasc',
126 - 'instructions' => esc_html__('The order in which terms appear in widget', 'filter-everything')
187 + 'default' => 'default',
188 + 'instructions' => esc_html__('The order in which terms appear in the widget', 'filter-everything'),
189 + 'tooltip' => wp_kses(
190 + __( 'The "Default" option means that terms will be sorted in the order as they were received from the Database<br />The "Menu order" option is available for WooCommerce attributes only.', 'filter-everything' ),
191 + array( 'br' => array() )
192 + )
127 193 ),
128 194 'in_path' => array(
129 195 'type' => 'Checkbox',
130 196 'label' => esc_html__( 'In URL', 'filter-everything' ),
@@ -133,29 +199,32 @@
133 199 'instructions' => ''
134 200 ),
135 201 'exclude' => array(
136 202 'type' => 'Select',
137 - 'label' => esc_html__( 'Exclude Terms', 'filter-everything' ),
203 + 'label' => esc_html__( 'Include/Exclude Terms', 'filter-everything' ),
138 204 'class' => 'wpc-field-exclude',
139 - 'options' => $entity->getTermsForSelect(),
205 + 'options' => $entityTerms, //$entity->getTermsForSelect(),
140 206 'multiple' => 'multiple',
141 - 'instructions' => esc_html__( 'Selected terms will not appear in filter widget', 'filter-everything' )
207 + 'instructions' => '',
208 + 'skip_view' => true
142 209 ),
210 + 'include' => array(
211 + 'type' => 'Select',
212 + 'label' => '',
213 + 'class' => 'wpc-field-include',
214 + 'options' => [ 'no' => esc_html__( 'exclude', 'filter-everything' ), 'yes' => esc_html__('include', 'filter-everything' ) ],
215 + 'default' => 'no',
216 + 'instructions' => esc_html__( 'Include selected terms only', 'filter-everything' ),
217 + 'skip_view' => true
218 + ),
143 219 'collapse' => array(
144 220 'type' => 'Checkbox',
145 221 'label' => esc_html__( 'Folding', 'filter-everything' ),
146 222 'class' => 'wpc-field-collapse',
147 223 'default' => 'no',
148 - 'instructions' => esc_html__( 'Makes the filter collapsible in widget', 'filter-everything'),
149 - 'tooltip' => esc_html__( 'Useful for situations when the filter is rarely applied but takes up some space in widget. Collapsed by default.', 'filter-everything' )
224 + 'instructions' => esc_html__( 'Makes filter collapsible in the widget', 'filter-everything'),
225 + 'tooltip' => esc_html__( 'Useful in situations when the filter is rarely applied but takes up some space in the widget. Collapsed by default.', 'filter-everything' )
150 226 ),
151 - 'show_chips' => array(
152 - 'type' => 'Checkbox',
153 - 'label' => esc_html__( 'Show Selected', 'filter-everything' ),
154 - 'class' => 'wpc-field-show-chips',
155 - 'default' => 'yes',
156 - 'instructions' => esc_html__( 'Show filter selected terms in the list of all chosen items', 'filter-everything' )
157 - ),
158 227 'hierarchy' => array(
159 228 'type' => 'Checkbox',
160 229 'label' => esc_html__( 'Show Hierarchy', 'filter-everything' ),
161 230 'class' => 'wpc-field-hierarchy',
@@ -172,9 +241,9 @@
172 241 'step' => array(
173 242 'type' => 'Text',
174 243 'label' => esc_html__( 'Slider Step', 'filter-everything' ),
175 244 'class' => 'wpc-field-value-step',
176 - 'instructions' => esc_html__( 'Determines how numeric value will be changed when you move slider controls', 'filter-everything' ),
245 + 'instructions' => esc_html__( 'Determines how the numeric value will be changed when you move slider controls', 'filter-everything' ),
177 246 'tooltip' => wp_kses(
178 247 __('Step 1 means possible values are 1,2,3,4 ...<br />Step 0.1 means possible values are 5.1, 5.2, 5.3, 5.4 ...<br />Step 15 means possible values are 15, 30, 45, 60...', 'filter-everything'),
179 248 array( 'br' => array() )
180 249 ),
@@ -179,14 +248,74 @@
179 248 array( 'br' => array() )
180 249 ),
181 250 'default' => 1
182 251 ),
252 + 'search' => array(
253 + 'type' => 'Checkbox',
254 + 'label' => esc_html__( 'Terms Search', 'filter-everything' ),
255 + 'class' => 'wpc-field-search',
256 + 'default' => 'no',
257 + 'instructions' => esc_html__( 'Adds a search field that allows you to quickly find filter terms', 'filter-everything' )
258 + ),
259 + 'parent_filter' => array(
260 + 'type' => 'Select',
261 + 'label' => esc_html__( 'Parent Filter', 'filter-everything' ),
262 + 'class' => 'wpc-field-parent-filter',
263 + 'options' => [ '-1' => esc_html__( 'Please, add filters first', 'filter-everything' ) ],
264 + 'instructions' => esc_html__( 'If specified, current Filter terms become available only after the parent Filter selected', 'filter-everything' )
265 + ),
266 + 'hide_until_parent' => array(
267 + 'type' => 'Checkbox',
268 + 'label' => esc_html__( 'Hide until the Parent selected', 'filter-everything' ),
269 + 'class' => 'wpc-field-hide-until-parent',
270 + 'default' => 'no',
271 + 'instructions' => esc_html__( 'Do not show this Filter until a parent is selected. Useful for step-by-step filtering', 'filter-everything' )
272 + ),
273 + 'min_num_label' => array(
274 + 'type' => 'Text',
275 + 'label' => esc_html__( 'Labels for Chips', 'filter-everything' ),
276 + 'class' => 'wpc-field-min-num-label',
277 + 'default' => '',
278 + 'placeholder' => esc_html__( 'e.g. From {value}', 'filter-everything' ),
279 + 'instructions' => '',
280 + 'skip_view' => true,
281 + ),
282 + 'max_num_label' => array(
283 + 'type' => 'Text',
284 + 'label' => '',
285 + 'class' => 'wpc-field-max-num-label',
286 + 'default' => '',
287 + 'placeholder' => esc_html__( 'e.g. To {value}', 'filter-everything' ),
288 + 'instructions' => '',
289 + 'skip_view' => true,
290 + ),
183 291 'tooltip' => array(
184 292 'type' => 'Text',
185 293 'label' => esc_html__( 'Tooltip', 'filter-everything' ),
186 294 'class' => 'wpc-field-tooltip',
187 - 'instructions' => esc_html__( 'Will appear next to the Filter Label', 'filter-everything' ),
295 + 'instructions' => esc_html__( 'Will appear next to the Filter Title', 'filter-everything' ),
188 296 'default' => ''
297 + ),
298 + 'more_less' => array(
299 + 'type' => 'Checkbox',
300 + 'label' => esc_html__( 'More/Less', 'filter-everything' ),
301 + 'class' => 'wpc-field-more-less',
302 + 'default' => 'no',
303 + 'instructions' => sprintf( esc_html__( 'Show a toggle link after %s terms', 'filter-everything' ), flrt_more_less_count() )
304 + ),
305 + 'show_chips' => array(
306 + 'type' => 'Checkbox',
307 + 'label' => esc_html__( 'Show in Chips', 'filter-everything' ),
308 + 'class' => 'wpc-field-show-chips',
309 + 'default' => 'yes',
310 + 'instructions' => esc_html__( 'Show filter selected terms in the list of all chosen items', 'filter-everything' )
311 + ),
312 + 'acf_fields' => array(
313 + 'type' => 'Text',
314 + 'label' => esc_html__( 'ACF Fields', 'filter-everything' ),
315 + 'class' => 'wpc-field-acf',
316 + 'default' => '',
317 + 'instructions' => esc_html__( 'Contains the IDs of ACF fields that are related to the Custom Field', 'filter-everything' )
189 318 )
190 319 );
191 320
192 321 $this->defaultFields = apply_filters( 'wpc_filter_default_fields', $defaultFields, $this );
@@ -191,72 +320,72 @@
191 320
192 321 $this->defaultFields = apply_filters( 'wpc_filter_default_fields', $defaultFields, $this );
193 322 }
194 323
195 - private function metaKeysOptions()
324 + public static function getViewOptions()
196 325 {
197 - $metaKeys = $this->getMetaKeys();
198 - $newKeys = [ '' => '' ];
326 + $viewOptions = array(
327 + 'checkboxes' => esc_html__('Checkboxes', 'filter-everything'),
328 + 'radio' => esc_html__('Radio buttons', 'filter-everything'),
329 + 'labels' => esc_html__('Labels list', 'filter-everything'),
330 + 'dropdown' => esc_html__('Dropdown', 'filter-everything'),
331 + 'range' => esc_html__('Numeric range', 'filter-everything'),
332 + 'date' => esc_html__('Date range', 'filter-everything'),
333 + );
199 334
200 - if( $metaKeys ){
201 - $excludedMetaKeys = flrt_get_forbidden_meta_keys();
202 -
203 - foreach ( $metaKeys as $key ) {
204 -
205 - if( ! preg_match('/[a-z]/i', $key ) ){
206 - continue;
207 - }
208 -
209 - if( in_array( $key, $excludedMetaKeys ) ){
210 - continue;
211 - }
212 -
213 - if( $key !== esc_attr( $key ) ){
214 - continue;
215 - }
216 -
217 - $newKeys[$key] = $key;
218 - }
335 + if(flrt_is_woocommerce()){
336 + $viewOptions['rating'] = esc_html__('Rating', 'filter-everything');
219 337 }
220 338
221 - return $newKeys;
339 + return $viewOptions;
222 340 }
223 341
224 - private function getMetaKeys()
342 + public static function getDateFormatOptions( $type = 'date' )
225 343 {
226 - global $wpdb;
344 + switch( $type ){
345 + case 'date':
346 + $F_j_Y = date_i18n( __( 'F j, Y' ) );
347 + $d_m_Y = date_i18n( 'd/m/Y' );
348 + $m_d_Y = date_i18n( 'm/d/Y' );
227 349
228 - if(! is_admin()){
229 - return array();
230 - }
350 + $formatOptions = array(
351 + __( 'F j, Y' ) => '<span>' . $F_j_Y . '</span><code>'.__( 'F j, Y' ).'</code>',
352 + 'd/m/Y' => '<span>' . $d_m_Y . '</span><code>d/m/Y</code>',
353 + 'm/d/Y' => '<span>' . $m_d_Y . '</span><code>m/d/Y</code>',
354 + );
231 355
232 - $limit = apply_filters( 'wpc_post_meta_keys_limit', 100 );
356 + break;
357 + case 'datetime':
358 + $F_j_Y = date_i18n( __('F j, Y g:i a') );
359 + $d_m_Y = date_i18n( 'd/m/Y g:i a' );
360 + $m_d_Y = date_i18n( 'm/d/Y g:i a' );
233 361
234 - $keys = $wpdb->get_col( $wpdb->prepare("SELECT DISTINCT meta_key FROM {$wpdb->postmeta} ORDER BY meta_key LIMIT %d", $limit ) );
362 + $formatOptions = array(
363 + __('F j, Y g:i a') => '<span>' . $F_j_Y . '</span><code>'.__('F j, Y g:i a').'</code>',
364 + 'd/m/Y g:i a' => '<span>' . $d_m_Y . '</span><code>d/m/Y g:i a</code>',
365 + 'm/d/Y g:i a' => '<span>' . $m_d_Y . '</span><code>m/d/Y g:i a</code>',
366 + );
367 + break;
368 + case 'time':
369 + $g_i_a = date_i18n( __('g:i a') );
370 + $H_i_s = date_i18n( 'H:i:s' );
235 371
236 - if ( $keys ) {
237 - natcasesort( $keys );
372 + $formatOptions = array(
373 + __('g:i a') => '<span>' . $g_i_a . '</span><code>'.__('g:i a').'</code>',
374 + 'H:i:s' => '<span>' . $H_i_s . '</span><code>H:i:s</code>',
375 + );
376 + break;
238 377 }
239 378
240 - return apply_filters( 'wpc_default_meta_keys', $keys );
241 - }
379 + $formatOptions['other'] = '<span>' . esc_html__( 'Custom:', 'filter-everything' ) . '</span>';
242 380
243 - public static function getViewOptions()
244 - {
245 - $viewOptions = array(
246 - 'checkboxes' => esc_html__('Checkboxes', 'filter-everything'),
247 - 'radio' => esc_html__('Radio buttons', 'filter-everything'),
248 - 'labels' => esc_html__('Labels list', 'filter-everything'),
249 - 'dropdown' => esc_html__('Dropdown', 'filter-everything'),
250 - 'range' => esc_html__('Range', 'filter-everything')
251 - );
252 -
253 - return $viewOptions;
381 + return $formatOptions;
254 382 }
255 383
256 384 public function getOrderByOptions()
257 385 {
258 386 $orderBy = array(
387 + 'default' => esc_html__( 'Default (no sorting)', 'filter-everything' ),
259 388 'nameasc' => esc_html__( 'Term name &laquo;abc&raquo;', 'filter-everything' ),
260 389 'postcountasc' => esc_html__( 'Posts count &laquo;123&raquo;', 'filter-everything' ),
261 390 'idasc' => esc_html__( 'Term ID &laquo;123&raquo;', 'filter-everything' ),
262 391 'menuasc' => esc_html__( 'Menu order &laquo;123&raquo;', 'filter-everything' ),
@@ -283,10 +412,42 @@
283 412 return $selected;
284 413
285 414 }
286 415
287 - public function getEmptyFilterObject()
416 + private function getFilterNames( $set_id )
288 417 {
418 + $filternames = [];
419 +
420 + if( ! $set_id ){
421 + return $filternames;
422 + }
423 +
424 + $filters = $this->em->selectOnlySetFilters( $set_id );
425 +
426 + if( ! empty( $filters ) ) {
427 + if ( count( $filters ) > 1 ) {
428 + $filternames['-1'] = esc_html__( '— Select Filter —', 'filter-everything' ) ;
429 + } else {
430 + $filternames['-1'] = esc_html__( 'Please, add filters first', 'filter-everything' );
431 + }
432 +
433 + foreach ( $filters as $filter ) {
434 +
435 + if ( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) ) {
436 + continue;
437 + }
438 +
439 + if ( isset( $filter['ID'] ) ) {
440 + $filternames[ $filter['ID'] ] = $filter['label'] .' (' . $filter['e_name'] .')';
441 + }
442 + }
443 + }
444 +
445 + return $filternames;
446 + }
447 +
448 + public function getEmptyFilterObject( $set_id )
449 + {
289 450 $defaults = new \stdClass();
290 451 $defaults->ID = self::FLRT_NEW_FILTER_ID;
291 452 $defaults->post_parent = '';
292 453 $defaults->menu_order = 0;
@@ -295,9 +456,9 @@
295 456 $defaults->post_name = '';
296 457
297 458 $filter = $this->em->prepareFilter( $defaults );
298 459
299 - return $this->prepareFilterInputs( $filter );
460 + return $this->prepareFilterInputsToDisplay( $filter, $this->getFilterNames( $set_id ) );
300 461 }
301 462
302 463 public function getFiltersInputs( $set_id )
303 464 {
@@ -307,17 +468,16 @@
307 468 return $preparedFilters;
308 469 }
309 470
310 471 $filters = $this->em->selectOnlySetFilters( $set_id );
311 -
312 472 foreach ( $filters as $field_key => $filter ){
313 - $preparedFilters[] = $this->prepareFilterInputs( $filter );
473 + $preparedFilters[] = $this->prepareFilterInputsToDisplay( $filter, $this->getFilterNames( $set_id ) );
314 474 }
315 475
316 476 return $preparedFilters;
317 477 }
318 478
319 - private function prepareFilterInputs( $filter )
479 + private function prepareFilterInputsToDisplay( $filter, $filternames )
320 480 {
321 481 $postTypes = false;
322 482 $terms = [];
323 483 // Used for filter table class in filter set fields.
@@ -324,26 +484,45 @@
324 484 // E.g. post_meta, taxonomy, author.
325 485
326 486 $short_entity = $filter['entity'] ? $filter['entity'] : 'taxonomy';
327 487
328 - if( $short_entity === 'taxonomy' ){
488 + if ( $short_entity === 'taxonomy' ) {
329 489 // Add hierarchical class
330 - if( is_taxonomy_hierarchical( $filter['e_name'] ) ){
490 + if ( is_taxonomy_hierarchical( $filter['e_name'] ) ) {
331 491 $short_entity .= ' taxonomy-hierarchical';
332 492 }
333 493
334 494 // Add product attribute class
335 - if( strpos( $filter['e_name'], 'pa_' ) === 0 ){
495 + if ( strpos( $filter['e_name'], 'pa_' ) === 0 ) {
336 496 $short_entity .= ' taxonomy-product-attribute';
337 497 }
338 498
499 + if ( strpos( $filter['e_name'], 'product_visibility') !== false
500 + && $filter['view'] === 'rating') {
501 + $short_entity .= ' selected-and-above-show ';
502 + }
503 +
504 + if ( flrt_get_experimental_option('use_color_swatches') === 'on' ) {
505 + $taxonomies = flrt_get_experimental_option( 'color_swatches_taxonomies', [] );
506 +
507 + if ( in_array( $filter['e_name'], $taxonomies ) ) {
508 + $short_entity .= ' taxonomy-has-swatches';
509 + }
510 + }
339 511 }
340 512
513 + if ( in_array( $filter['e_name'], flrt_brand_filter_entities() ) ) {
514 + $short_entity .= ' wpc-filter-has-brands';
515 + }
516 +
517 +
518 + $short_entity .= isset( $filter['view'] ) ? ' wpc-view-'.$filter['view'] : '';
519 +
341 520 $belongs = $this->filterBelongsToPostType( $filter['parent'], $filter['entity'], $filter['e_name'] );
342 521
343 522 $postType = '';
344 523 // For post_meta_num entity postType is critical value so we have to set it up
345 - if( in_array( $filter['entity'], array( 'post_meta_num', 'post_meta_exists' ) ) ){
524 + if ( in_array( $filter['entity'], array( 'post_meta_num', 'post_meta_exists', 'post_meta_date' ) ) ) {
346 525 $fss = Container::instance()->getFilterSetService();
347 526 $set = $fss->getSet( $filter['parent'] );
348 527 if( isset( $set['post_type']['value'] ) ){
349 528 $postType = $set['post_type']['value'];
@@ -349,12 +528,18 @@
349 528 $postType = $set['post_type']['value'];
350 529 }
351 530 }
352 531
532 + if ( in_array( $filter['entity'], array( 'post_meta_num', 'tax_numeric')) !== false
533 + && $filter['view'] === 'range' && (isset($filter['show_range_list']) && $filter['show_range_list'] === 'yes')) {
534 + $short_entity .= ' wpc-view-range-list';
535 + }
536 +
353 537 $entity = $this->em->getEntityByFilter( $filter, $postType );
354 538
355 539 if( $entity ){
356 540 $terms = $entity->getTermsForSelect();
541 + asort( $terms, SORT_NATURAL );
357 542 }
358 543
359 544 // This is required only for filter fields inputs
360 545 $filter = $this->fse->combineEntityNameInFilter( $filter );
@@ -360,13 +545,24 @@
360 545 $filter = $this->fse->combineEntityNameInFilter( $filter );
361 546
362 547 foreach( $this->getFieldsMapping() as $fieldKey => $fieldData ){
363 548
364 - if( isset( $filter[$fieldKey] ) ){
549 + if ( isset( $filter[$fieldKey] ) ) {
365 550
551 + if ( $fieldKey === 'parent_filter' ) {
552 + // Exclude current filter
553 + if ( isset( $filternames[ $filter['ID'] ] ) ) {
554 + unset( $filternames[ $filter['ID'] ] );
555 + }
556 +
557 + if ( ! empty( $filternames ) ) {
558 + $fieldData['options'] = $filternames;
559 + }
560 + }
561 +
366 562 $default_value = isset( $fieldData['default'] ) ? $fieldData['default'] : '';
367 563
368 - $multiple = ( $fieldKey === 'exclude' && ( $filter['entity'] !== 'post_meta_num' ) );
564 + $multiple = ( $fieldKey === 'exclude' && ! in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) );
369 565
370 566 $fieldData['name'] = $this->generateInputName( $filter['ID'], $fieldKey, $multiple );
371 567 $fieldData['id'] = $this->generateInputID( $filter['ID'], $fieldKey );
372 568
@@ -371,29 +567,38 @@
371 567 $fieldData['id'] = $this->generateInputID( $filter['ID'], $fieldKey );
372 568
373 569 $fieldData['value'] = ( $filter[$fieldKey] ) ? $filter[$fieldKey] : $default_value;
374 570
375 - if( $filter['ID'] === self::FLRT_NEW_FILTER_ID ){
376 - $fieldData['entity_belongs'] = true;
377 - }else{
378 - $fieldData['entity_belongs'] = $belongs;
571 + if( $fieldKey === 'hide_until_parent' ){
572 + if( (int) $filter['parent_filter'] > 0 ){
573 + $fieldData['additional_class'] = 'wpc-opened';
574 + }
379 575 }
380 576
381 - if( $fieldKey === 'entity' ){
382 - $fieldData['short_entity'] = $short_entity;
383 - }
384 -
385 - if( $fieldKey === 'slug' && $fieldData['value'] ){
577 + if ( $fieldKey === 'slug' && $fieldData['value'] ) {
386 578 $fieldData['readonly'] = 'readonly';
387 579 }
388 580
389 - if( $fieldKey === 'e_name' && $fieldData['value'] ){
581 + if ( $fieldKey === 'e_name' && $fieldData['value'] ) {
390 582 unset( $fieldData['options'] );
391 - $fieldData['readonly'] = 'readonly';
392 - $fieldData['type'] = 'Text';
583 + $fieldData['readonly'] = 'readonly';
584 + $fieldData['type'] = 'Text';
585 +
586 + if ( $filter['entity'] === 'tax_numeric' ) {
587 + $fieldData['label'] = esc_html__( 'Taxonomy', 'filter-everything' );
588 + $fieldData['instructions'] = esc_html__( 'Taxonomy with numeric values you need to filter by', 'filter-everything' );
589 + }
393 590 }
394 591
395 - if( $fieldKey === 'entity' ){
592 + if( $fieldKey === 'entity' ) {
593 + $fieldData['short_entity'] = $short_entity;
594 +
595 + if ( $filter['ID'] === self::FLRT_NEW_FILTER_ID ) {
596 + $fieldData['entity_belongs'] = true;
597 + } else {
598 + $fieldData['entity_belongs'] = $belongs;
599 + }
600 +
396 601 // Add instead-entity field
397 602 if( $filter['ID'] !== self::FLRT_NEW_FILTER_ID ){
398 603
399 604 /**
@@ -415,53 +620,113 @@
415 620 if( $fieldData['value'] === 'post_meta_exists' && ! defined('FLRT_FILTERS_PRO') ){
416 621 $insteadEntityVal = esc_html__('Available in PRO', 'filter-everything');
417 622 }
418 623
624 + if( $fieldData['value'] === 'tax_numeric' && ! defined('FLRT_FILTERS_PRO') ){
625 + $insteadEntityVal = esc_html__('Available in PRO', 'filter-everything');
626 + }
627 +
419 628 // Field, that will be visible instead of entity, that will be hidden.
420 629 $prepared[ 'instead-entity' ] = $this->getInsteadEntityField( $filter['ID'], $insteadEntityVal );
421 630
422 631 }
423 632
424 - if( $filter['ID'] === self::FLRT_NEW_FILTER_ID ){
633 + if ( $filter['ID'] === self::FLRT_NEW_FILTER_ID ) {
634 + if ( ! defined('FLRT_FILTERS_PRO') ) {
635 + $fieldData['disabled'] = array( 'post_meta_exists', 'tax_numeric' );
636 + }
637 + }
425 638
426 - if( ! defined('FLRT_FILTERS_PRO') ){
427 - $fieldData['disabled'] = array('post_meta_exists');
428 - }
639 + }
429 640
641 + if (!defined('FLRT_FILTERS_PRO')) {
642 + if ($fieldKey === 'show_range_list') {
643 + $fieldData['value'] = '';
430 644 }
645 + }
431 646
432 - }
433 647
434 648 if( $fieldKey === 'exclude' ){
435 649 // We always add terms even they are empty array to fill Select2 with related terms.
436 650 $fieldData['options'] = $terms;
437 651
438 - if( $filter['entity'] === 'post_meta_num'){
652 + if( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) ) {
439 653 $fieldData['options'] = [];
440 654 }
441 655 }
442 656
443 657 // Set disabled fields for some situations
444 - if( in_array( $filter['entity'], array( 'author_author', 'post_meta_exists' ) ) /* $filter['entity'] === 'author_author'*/ && $fieldKey === 'logic' ){
658 + if( in_array( $filter['entity'], array( 'author_author', 'post_meta_exists', 'taxonomy_product_visibility' ) ) /* $filter['entity'] === 'author_author'*/ && $fieldKey === 'logic' ){
445 659 $fieldData['disabled'] = array('and');
446 660 }
447 661
448 - if( $filter['entity'] === 'post_meta_num' && $fieldKey === 'logic' ){
662 + if( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) && $fieldKey === 'logic' ){
449 663 $fieldData['disabled'] = array('or');
450 664 }
451 665
452 - if( $filter['entity'] === 'post_meta_num' && $fieldKey === 'view' ){
453 - $fieldData['disabled'] = array('checkboxes', 'dropdown', 'radio', 'labels', 'colors', 'image');
454 - }else if( $filter['entity'] !== 'post_meta_num' && $fieldKey === 'view' ){
455 - $fieldData['disabled'] = array('range');
666 + if ( $fieldKey === 'view' ) {
667 + if ( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric' ] ) ) {
668 + $fieldData['disabled'] = array(
669 + 'checkboxes',
670 + 'dropdown',
671 + 'radio',
672 + 'labels',
673 + 'date',
674 + 'post_meta_date',
675 + 'colors',
676 + 'image',
677 + 'rating',
678 + );
679 + } else if ( in_array( $filter['entity'], [ 'post_date', 'post_meta_date' ] ) ) {
680 + $fieldData['disabled'] = array(
681 + 'checkboxes',
682 + 'dropdown',
683 + 'radio',
684 + 'labels',
685 + 'range',
686 + 'colors',
687 + 'image',
688 + 'rating',
689 + );
690 + } else if ( in_array( $filter['entity'], [ 'taxonomy_product_visibility' ] ) ) {
691 + $fieldData['disabled'] = array(
692 + 'date',
693 + 'post_meta_date',
694 + 'range',
695 + );
696 + }else {
697 + $fieldData['disabled'] = array(
698 + 'range',
699 + 'date',
700 + 'post_meta_date',
701 + 'rating',
702 + );
703 + }
456 704 }
457 705
458 - if( $fieldKey === 'orderby' && ( mb_strpos( $filter['entity'], 'taxonomy_pa' ) === false ) ){
459 - $fieldData['disabled'] = ['menuasc', 'menudesc'];
706 + if( $fieldKey === 'orderby' ) {
707 + if ( $filter['entity'] !== 'taxonomy_product_cat' && ( mb_strpos( $filter['entity'], 'taxonomy_pa' ) === false ) ){
708 + $fieldData['disabled'] = ['menuasc', 'menudesc'];
709 + }
460 710 }
461 711
712 + if ( ( $filter['ID'] === self::FLRT_NEW_FILTER_ID || $filter['entity'] === 'post_date' || $filter['entity'] === 'post_meta_date') && $fieldKey === 'date_format' ) {
713 + // This can be new filter or existing
714 + $date_type = $filter['date_type'] ? $filter['date_type'] : 'date';
715 + $dateFormatOptions = $this->getDateFormatOptions( $date_type );
716 + $disabled_custom = true;
717 + $value_to_custom = $fieldData['value'];
462 718
719 + if ( ! in_array( $value_to_custom, self::getPossibleDateFormats( $date_type ) ) ) {
720 + $disabled_custom = false;
721 + $fieldData['value'] = 'other';
722 + }
463 723
724 + $customFormatField = '</label><label>' . $this->addCustomDateFormatField( $fieldData['name'], $value_to_custom, $disabled_custom );
725 + $dateFormatOptions['other'] = str_replace( '</span>', '</span>' . $customFormatField, $dateFormatOptions['other'] );
726 +
727 + $fieldData['options'] = $dateFormatOptions;
728 + }
464 729 }
465 730
466 731 $prepared[ $fieldKey ] = $fieldData;
467 732 }
@@ -482,10 +747,9 @@
482 747 $sanitizedFilter[ $key ] = $value;
483 748 }else{
484 749
485 750 if( is_array( $value ) ){
486 - array_map( 'esc_html', $value );
487 - $sanitizedFilter[ $key ] = $value;
751 + $sanitizedFilter[ $key ] = map_deep( $value, 'esc_html' );
488 752 } else {
489 753 $sanitizedFilter[ $key ] = esc_html( $value );
490 754 }
491 755
@@ -531,18 +795,15 @@
531 795
532 796 return $filter;
533 797 }
534 798
535 - public function validateTheFilter( $filter, $id = false ){
799 + public function validateTheFilter( $filter, $id = false ) {
536 800
537 801 $valid = true;
538 - $newFilter = false;
539 - $filterID = false;
540 802 $validEntity = true;
541 803 $validator = new Validator();
542 804
543 - if( $filter['ID'] === self::FLRT_NEW_FILTER_ID ){
544 - $newFilter = true;
805 + if( $filter['ID'] === self::FLRT_NEW_FILTER_ID ) {
545 806 $filterID = $id;
546 807 }else{
547 808 $filterID = $filter['ID'];
548 809 }
@@ -630,9 +891,9 @@
630 891 /**
631 892 * Slug validations
632 893 */
633 894 if( isset( $filter['slug'] ) ){
634 - $existingSlugs = get_option('wpc_filter_permalinks');
895 + $existingSlugs = get_option('wpc_filter_permalinks', []);
635 896 $prefix = $filter['slug'];
636 897
637 898 // Check this only for new filters
638 899 if( $filter['ID'] === self::FLRT_NEW_FILTER_ID && $validEntity ) {
@@ -646,23 +907,23 @@
646 907 }
647 908
648 909 // Ensure, that prefix contains at least one alphabetic character
649 910 // Also this prevents from empty prefix
650 - if ( ! $validator->validateAlphabCharsExists($prefix) ) {
911 + if ( ! $validator->validateAlphabCharsExists( $prefix ) ) {
651 912 $this->pushError( 37, $filterID, 'slug' ); // Invalid prefix. Has to contain at least one alphabetic symbol
652 913 $valid = false;
653 914 }
654 915
655 916 // Check hyphens problem when cat and cat-x exists
656 - if (!$validator->validatePrefixHyphens($prefix, $existingSlugs) ) {
917 + if ( ! $validator->validatePrefixHyphens( $prefix, $existingSlugs ) ) {
657 918 $this->pushError( 39, $filterID, 'slug' ); // Invalid prefix. Incorrect hyphens.
658 919 $valid = false;
659 920 }
660 921
661 922 // Check for slugs, that matches with native WP Entities
662 - if( ! $validator->validateAllowedPrefixes( $prefix, $filter ) ){
923 + if ( ! $validator->validateAllowedPrefixes( $prefix, $filter ) ) {
663 924 $errorCode = 3991;
664 - if( defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO ){
925 + if ( defined( 'FLRT_FILTERS_PRO' ) && FLRT_FILTERS_PRO ) {
665 926 $errorCode = 399;
666 927 }
667 928 $this->pushError( $errorCode, $filterID, 'slug' ); // Invalid prefix. Equal to wp entity.
668 929 $valid = false;
@@ -675,36 +936,83 @@
675 936
676 937 /**
677 938 * E_name validations
678 939 */
679 - if( isset( $filter['e_name'] ) ){
940 + if ( isset( $filter['e_name'] ) ) {
680 941
681 942 if( isset( $filter['entity'] ) ) {
682 - if (in_array($filter['entity'], array('post_meta', 'post_meta_num', 'post_meta_exists') ) ) {
943 + if ( in_array( $filter['entity'], array( 'post_meta', 'post_meta_num', 'post_meta_exists', 'tax_numeric', 'post_meta_date' ) ) ) {
683 944 $e_name = $filter['e_name'];
684 945
685 - if( $e_name === '' ){
686 - $this->pushError(401, $filterID, 'e_name'); // Select or Enter meta key.
946 + if ( $e_name === '' ) {
947 + $this->pushError( 401, $filterID, 'e_name' ); // Select or Enter meta key.
687 948 $valid = false;
688 949 } else {
689 - // Should not be empty and should contain at least one alphabetic character
690 - if (!$validator->validateAlphabCharsExists($e_name)) {
691 - $this->pushError(40, $filterID, 'e_name'); // Invalid E_name.
692 - $valid = false;
950 +
951 + if ( $filter['entity'] === 'tax_numeric' ) {
952 + // Validate taxonomy
953 + $taxonomies = $this->em->getPossibleTaxonomies();
954 + $taxonomy_e_name = $filter[ 'e_name' ];
955 +
956 + if( mb_strpos( $filter[ 'e_name' ], 'taxonomy_' ) === false ){
957 + $taxonomy_e_name = 'taxonomy_' . $filter[ 'e_name' ];
958 + }
959 +
960 + if ( ! isset( $taxonomies[ $taxonomy_e_name ] ) ) {
961 + $this->pushError( 402, $filterID, 'e_name' ); // Invalid E_name.
962 + $valid = false;
963 + }
964 + } else {
965 + // Should not be empty and should contain at least one alphabetic character
966 + if ( ! $validator->validateAlphabCharsExists( $e_name ) ) {
967 + $this->pushError( 40, $filterID, 'e_name' ); // Invalid E_name.
968 + $valid = false;
969 + }
970 +
971 + // Should not contain characters that escaped by esc_attr
972 + if ( ! $validator->validateEscAttrCharacters( $e_name ) ) {
973 + $this->pushError( 40, $filterID, 'e_name' ); // Invalid E_name.
974 + $valid = false;
975 + }
976 +
977 + // Some meta keys should be prohibited to use
978 + $excludedMetaKeys = flrt_get_forbidden_meta_keys();
979 + if ( in_array( $e_name, $excludedMetaKeys, true ) ) {
980 + $this->pushError( 40, $filterID, 'e_name' ); // Invalid E_name.
981 + $valid = false;
982 + }
693 983 }
984 + }
985 + }
694 986
695 - // Should not contain characters that escaped by esc_attr
696 - if (!$validator->validateEscAttrCharacters($e_name)) {
697 - $this->pushError(40, $filterID, 'e_name'); // Invalid E_name.
698 - $valid = false;
987 + if ( in_array( $filter['e_name'], array( 'post_date') ) ) {
988 +
989 + if ( isset( $filter['date_format'] ) && isset( $filter['date_type'] ) ) {
990 +
991 + $date_time = flrt_split_date_time( $filter['date_format'] );
992 +
993 + switch ( $filter['date_type'] ) {
994 + case 'date':
995 + // check for date
996 + if ( ! $date_time['date'] ) {
997 + $this->pushError( 60, $filterID, 'date_format' ); // Invalid date_format.
998 + $valid = false;
999 + }
1000 + break;
1001 + case 'datetime':
1002 + if ( ! $date_time['date'] || ! $date_time['time'] ) {
1003 + $this->pushError( 62, $filterID, 'date_format' ); // Invalid date_format.
1004 + $valid = false;
1005 + }
1006 + break;
1007 + case 'time':
1008 + if ( ! $date_time['time'] ) {
1009 + $this->pushError( 61, $filterID, 'date_format' ); // Invalid date_format.
1010 + $valid = false;
1011 + }
1012 + break;
699 1013 }
700 1014
701 - // Some meta keys should be prohibited to use
702 - $excludedMetaKeys = flrt_get_forbidden_meta_keys();
703 - if( in_array( $e_name, $excludedMetaKeys, true ) ){
704 - $this->pushError( 40, $filterID, 'e_name'); // Invalid E_name.
705 - $valid = false;
706 - }
707 1015 }
708 1016 }
709 1017 }
710 1018
@@ -712,8 +1020,22 @@
712 1020 $this->pushError( 40, $filterID, 'e_name' ); // Invalid E_name.
713 1021 $valid = false;
714 1022 }
715 1023
1024 +
1025 + if ( defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO && isset($filter['e_name'])) {
1026 + if (!empty($filter['entity'])) {
1027 + if (in_array($filter['entity'], array('post_meta_num', 'tax_numeric'))) {
1028 + if (isset($filter['view']) && $filter['view'] === 'range' && !empty($filter['show_range_list']) && $filter['show_range_list'] === 'yes') {
1029 + if (empty($filter['range_list_input'])) {
1030 + $this->pushError(93, $filterID, 'range_list_input'); // Empty range_list.
1031 + $valid = false;
1032 + }
1033 + }
1034 + }
1035 + }
1036 + }
1037 +
716 1038 /**
717 1039 * View validations
718 1040 */
719 1041 if( isset( $filter['view'] ) ){
@@ -729,8 +1051,18 @@
729 1051 $valid = false;
730 1052 }
731 1053
732 1054 /**
1055 + * Date Type validations
1056 + */
1057 + if ( isset( $filter['date_type'] ) ) {
1058 + if( ! in_array( $filter['date_type'], array( 'date', 'datetime', 'time' ), true ) ){
1059 + $this->pushError( 411, $filterID, 'date_type' ); // Invalid Data Type
1060 + $valid = false;
1061 + }
1062 + }
1063 +
1064 + /**
733 1065 * Logic validations
734 1066 */
735 1067 if( isset( $filter['logic'] ) ){
736 1068
@@ -739,9 +1071,9 @@
739 1071 $valid = false;
740 1072 }
741 1073
742 1074 // For author and post_meta_exists entities logic can be only OR
743 - if( in_array( $filter['entity'], array( 'author_author', 'post_meta_exists' ) ) ){
1075 + if( in_array( $filter['entity'], array( 'author_author', 'post_meta_exists', 'taxonomy_product_visibility' ) ) ){
744 1076 if( $filter['logic'] !== 'or' ){
745 1077 $this->pushError( 45, $filterID, 'logic' ); // Not acceptable logic.
746 1078 $valid = false;
747 1079 }
@@ -747,9 +1079,9 @@
747 1079 }
748 1080 }
749 1081
750 1082 // For author entity logic can be only OR
751 - if( $filter['entity'] === 'post_meta_num' ){
1083 + if ( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) ) {
752 1084 if( $filter['logic'] !== 'and' ){
753 1085 $this->pushError( 47, $filterID, 'logic' ); // Not acceptable logic.
754 1086 $valid = false;
755 1087 }
@@ -788,11 +1120,18 @@
788 1120 $valid = false;
789 1121 }
790 1122 }
791 1123
1124 + if( isset( $filter['include'] ) ){
1125 + if( ! in_array( $filter['include'], ['yes', 'no'] ) ){
1126 + $this->pushError( 441, $filterID, 'exclude' ); // Invalid Exclude.
1127 + $valid = false;
1128 + }
1129 + }
1130 +
792 1131 // In case when checkbox is not checked there is no $_POST['in_path'] parameter
793 1132 if( isset( $filter['in_path'] ) ){
794 - if( $filter['entity'] === 'post_meta_num' && $filter['in_path'] === 'yes' ){
1133 + if( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) && $filter['in_path'] === 'yes' ){
795 1134 $this->pushError( 46, $filterID, 'in_path' ); // Invalid In Path for Post meta num.
796 1135 $valid = false;
797 1136 }
798 1137 }
@@ -821,39 +1160,42 @@
821 1160
822 1161 return $valid;
823 1162 }
824 1163
825 - public function validateFilters( $filters ){
1164 + public function validateFilters( $filters ) {
826 1165
827 - if( ! $filters ){
1166 + if ( ! $filters ) {
828 1167 return false;
829 1168 }
830 1169 $valid = true;
831 1170
832 1171 // Check permissions
833 - if( ! current_user_can( 'manage_options' ) ) {
1172 + if ( ! current_user_can( flrt_plugin_user_caps() ) ) {
1173 + // An error occurred. You do not have permission to edit this.
834 1174 $this->pushError(202);
835 1175 $valid = false;
836 1176 }
837 1177
838 1178 // Check for equal filters
839 - foreach ( (array) $filters as $filter ){
840 - if( isset( $filter['entity'] ) && isset( $filter['e_name'] ) ){
1179 + foreach ( (array) $filters as $filter ) {
1180 + if( isset( $filter['entity'] ) && isset( $filter['e_name'] ) ) {
841 1181 // To avoid few filters with the same meta key
842 - if( in_array( $filter['entity'], array( 'post_meta', 'post_meta_num', 'post_meta_exists' ), true ) ){
843 - $keys[] = 'post_meta'.$filter['e_name'];
844 - }else{
1182 + if ( in_array( $filter['entity'], array( 'post_meta', 'post_meta_num', 'post_meta_exists'), true ) ) {
1183 + $keys[] = 'post_meta' . $filter['e_name'];
1184 + } elseif ( $filter['entity'] === 'tax_numeric' || $filter['entity'] === 'post_date' || $filter['entity'] === 'post_meta_date') {
1185 + $keys[] = $filter['entity'] .'_'. $filter['e_name'];
1186 + } else {
845 1187 $keys[] = $filter['entity'] . $filter['e_name'];
846 1188 }
847 1189 }
848 1190 }
849 1191
850 - if( flrt_array_contains_duplicate( $keys ) ){
1192 + if ( flrt_array_contains_duplicate( $keys ) ) {
851 1193 $this->pushError(31); // Equal filters
852 1194 $valid = false;
853 1195 }
854 1196
855 - return $valid;
1197 + return apply_filters( 'wpc_validate_filter_fields', $valid, $this );
856 1198 }
857 1199
858 1200 public function ajaxValidateFilters()
859 1201 {
@@ -858,8 +1200,19 @@
858 1200 public function ajaxValidateFilters()
859 1201 {
860 1202 $postData = Container::instance()->getThePost();
861 1203 $data = isset( $postData['validateData'] ) ? $postData['validateData'] : false;
1204 + // The current admin script sends a JSON string, but a stale (cached or
1205 + // un-rebuilt .min) copy still sends the bracket-serialized array —
1206 + // accept both instead of fataling on stripslashes(array) under PHP 8
1207 + if ( is_array( $data ) ) {
1208 + $data = wp_unslash( $data );
1209 + } elseif ( $data ) {
1210 + $data = json_decode( stripslashes( $data ), true );
1211 + }
1212 + if ( isset( $data['wpc_set_fields']['wp_filter_query_vars'] ) ) {
1213 + $data['wpc_set_fields']['wp_filter_query_vars'] = wp_slash( $data['wpc_set_fields']['wp_filter_query_vars'] );
1214 + }
862 1215 $response = [];
863 1216
864 1217 if( ! $data ){
865 1218 $this->pushError(20);
@@ -1018,9 +1371,9 @@
1018 1371
1019 1372 $nonce = isset( $postData['_wpnonce'] ) ? $postData['_wpnonce'] : false;
1020 1373 $errorResponse = array(
1021 1374 'fid' => $filterId,
1022 - 'message' => esc_html__('An error occured. Please, refresh the page and try again.', 'filter-everything')
1375 + 'message' => esc_html__('An error occurred. Please, refresh the page and try again.', 'filter-everything')
1023 1376 );
1024 1377
1025 1378 if( ! wp_verify_nonce( $nonce, FilterSet::NONCE_ACTION ) ){
1026 1379 wp_send_json_error( $errorResponse );
@@ -1043,8 +1396,104 @@
1043 1396 wp_send_json_error( $errorResponse );
1044 1397 }
1045 1398 }
1046 1399
1400 + public static function getPossibleDateFormats( $date_type )
1401 + {
1402 + $possibleFormats = [
1403 + 'date' => array(
1404 + 'd/m/Y',
1405 + 'm/d/Y',
1406 + __('F j, Y'),
1407 + ),
1408 + 'datetime' => array(
1409 + 'd/m/Y g:i a',
1410 + 'm/d/Y g:i a',
1411 + __('F j, Y g:i a'),
1412 + ),
1413 + 'time' => array(
1414 + __('g:i a'),
1415 + 'H:i:s',
1416 + )
1417 + ];
1418 +
1419 + if ( isset( $possibleFormats[$date_type] ) ) {
1420 + return $possibleFormats[$date_type];
1421 + }
1422 +
1423 + return [];
1424 + }
1425 +
1426 + private function addCustomDateFormatField( $name, $value, $disabled = false )
1427 + {
1428 + $html = '<input type="text" name="'.$name.'" value="'.$value.'"';
1429 + if ( $disabled ) {
1430 + $html .= ' disabled="disabled"';
1431 + }
1432 +
1433 + $html .= ' class="wpc-date-custom-format" />';
1434 +
1435 + return $html;
1436 + }
1437 +
1438 + public function sendDateFormats()
1439 + {
1440 + $postData = Container::instance()->getThePost();
1441 + $filterId = isset( $postData['fid'] ) ? $postData['fid'] : false;
1442 + $setId = isset( $postData['setId'] ) ? $postData['setId'] : false;
1443 + $dateType = isset( $postData['dateType'] ) ? $postData['dateType'] : false;
1444 +
1445 + $errorResponse = array(
1446 + 'fid' => $filterId,
1447 + 'message' => esc_html__('An error occurred. Please, refresh the page and try again.', 'filter-everything')
1448 + );
1449 +
1450 + if ( ! $filterId || ! $dateType ) {
1451 + wp_send_json_error( $errorResponse );
1452 + }
1453 +
1454 + $response = [];
1455 +
1456 +// $savedValue = $this->em->getFilterBy( 'ID', $filterId, array( array( 'ID' => $setId ) ) );
1457 + $savedValue = $this->em->getFilterById( $filterId );
1458 + $formatoptions = self::getDateFormatOptions( $dateType );
1459 +
1460 + $field = [
1461 + 'type' => 'Radio',
1462 + 'class' => 'wpc-date-format',
1463 + 'default' => __( 'F j, Y' ),
1464 + ];
1465 +
1466 + $default_formats = [
1467 + 'date' => __( 'F j, Y' ),
1468 + 'datetime' => __('F j, Y g:i a'),
1469 + 'time' => __('g:i a'),
1470 + ];
1471 +
1472 + $disabled_custom = true;
1473 + $value_to_custom = ( ! empty( $savedValue['date_format'] ) && $dateType === ( $savedValue['date_type'] ?? '' ) ) ? $savedValue['date_format'] : $default_formats[$dateType];
1474 + if ( $value_to_custom && ! in_array( $value_to_custom, self::getPossibleDateFormats( $dateType ) ) ) {
1475 + $disabled_custom = false;
1476 + $field['value'] = 'other';
1477 + }
1478 +
1479 + $customFormatField = '</label><label>' . $this->addCustomDateFormatField( $this->generateInputName( $filterId, 'date_format' ), $value_to_custom, $disabled_custom );
1480 + $formatoptions['other'] = str_replace( '</span>', '</span>' . $customFormatField, $formatoptions['other'] );
1481 +
1482 + $field['name'] = $this->generateInputName( $filterId, 'date_format' );
1483 + $field['id'] = $this->generateInputID( $filterId, 'date_format' );
1484 + $field['options'] = $formatoptions;
1485 +
1486 + ob_start();
1487 +
1488 + echo flrt_render_input( $field );
1489 +
1490 + $response['html'] = ob_get_clean();
1491 +
1492 + wp_send_json_success( $response );
1493 + die();
1494 + }
1495 +
1047 1496 public function sendExcludedTerms()
1048 1497 {
1049 1498 $container = Container::instance();
1050 1499 $postData = $container->getThePost();
@@ -1055,9 +1504,9 @@
1055 1504 $e_name = isset( $postData['ename'] ) ? $postData['ename'] : false;
1056 1505
1057 1506 $errorResponse = array(
1058 1507 'fid' => $filterId,
1059 - 'message' => esc_html__('An error occured. Please, refresh the page and try again.', 'filter-everything')
1508 + 'message' => esc_html__('An error occurred. Please, refresh the page and try again.', 'filter-everything')
1060 1509 );
1061 1510
1062 1511 if( ! wp_verify_nonce( $nonce, FilterSet::NONCE_ACTION ) ){
1063 1512 wp_send_json_error( $errorResponse );
@@ -1088,8 +1537,35 @@
1088 1537 }
1089 1538
1090 1539 }
1091 1540
1541 + public function addSpinnerToDateFormats( $html, $attributes )
1542 + {
1543 + if ( isset( $attributes['class'] ) && $attributes['class'] === 'wpc-date-format' ) {
1544 + $spinner = '<span class="spinner"></span>'."\r\n";
1545 + $openContainer = '<div class="wpc-after-spinner-container">'."\r\n";
1546 +
1547 + $closeContainer = '</div>'."\r\n";
1548 +
1549 + if ( isset( $attributes['options']['d/m/Y'] ) ) {
1550 + $date_type = 'wpc-type-date';
1551 + }
1552 +
1553 + if ( isset( $attributes['options']['d/m/Y g:i a'] ) ) {
1554 + $date_type = 'wpc-type-datetime';
1555 + }
1556 +
1557 + if ( isset( $attributes['options']['H:i:s'] ) ) {
1558 + $date_type = 'wpc-type-time';
1559 + }
1560 +
1561 + $html = str_replace( 'wpc-radio-list', 'wpc-radio-list ' . $date_type, $html );
1562 +
1563 + $html = $spinner . $openContainer . $html . $closeContainer;
1564 + }
1565 + return $html;
1566 + }
1567 +
1092 1568 public function addSpinnerToSelect( $html, $attributes )
1093 1569 {
1094 1570 if( isset( $attributes['class'] ) ){
1095 1571 $requiredIds = array(
@@ -1121,9 +1597,10 @@
1121 1597 'value' => $insteadEntityVal,
1122 1598 'readonly' => 'readonly',
1123 1599 'default' => '',
1124 1600 'instructions' => esc_html__( 'A thing by which posts will be filtered', 'filter-everything'),
1125 - 'tooltip' => esc_html__( 'An already selected value cannot be changed. But you always can delete the current one and create new filter if you need.', 'filter-everything' )
1601 + 'tooltip' => esc_html__( 'An already selected value cannot be changed. But you can always delete the current one and create a new filter if you need.', 'filter-everything' ),
1602 + 'required' => true
1126 1603 );
1127 1604
1128 1605 return $insteadEntity;
1129 1606 }
@@ -1158,17 +1635,33 @@
1158 1635 if ( empty( $post_type ) ){
1159 1636 return false;
1160 1637 }
1161 1638
1162 - if( in_array( $entity, array( 'author', 'date', 'post_meta', 'post_meta_num' ) ) ){
1639 + if( in_array( $entity, array(
1640 + 'author',
1641 + 'post_date',
1642 + 'post_meta',
1643 + 'post_meta_num',
1644 + 'post_meta_date',
1645 + ) ) ){
1163 1646 return true;
1164 1647 }
1165 1648
1166 - if( in_array( $entity, array( 'post_meta_exists' ) ) && defined( 'FLRT_FILTERS_PRO' ) ){
1649 + if( in_array( $entity, array( 'post_meta_exists', 'tax_numeric' ) ) && defined( 'FLRT_FILTERS_PRO' ) ){
1167 1650 return true;
1168 1651 }
1169 1652
1170 - if( $entity === 'taxonomy' ){
1653 + if( $entity === 'taxonomy' || $entity === 'tax_numeric' ){
1654 + if ( $entity === 'tax_numeric' ) {
1655 + if ( ! defined( 'FLRT_FILTERS_PRO' ) ) {
1656 + return false;
1657 + }
1658 + // We have to cut e_name to make correct verification
1659 + if ( mb_strpos( $e_name, 'taxonomy_' ) !== false ) {
1660 + $e_name = mb_strcut( $e_name, strlen( 'taxonomy_' ) );
1661 + }
1662 + }
1663 +
1171 1664 return $this->isTaxonomyBelongsToPostType( $post_type, $e_name );
1172 1665 }
1173 1666
1174 1667 return false;
@@ -1173,11 +1666,11 @@
1173 1666
1174 1667 return false;
1175 1668 }
1176 1669
1177 - public function getErrorMessage( $code = 20 )
1670 + public function getErrorMessage( $code = 20, $limit = null )
1178 1671 {
1179 - $messages = $this->getErrorsList();
1672 + $messages = self::getErrorsList($limit);
1180 1673
1181 1674 if( isset( $messages[$code] ) ){
1182 1675 return $messages[$code];
1183 1676 }
@@ -1186,9 +1679,10 @@
1186 1679 }
1187 1680
1188 1681 public function pushError( $errorCode, $filterId = false, $filterKey = '' )
1189 1682 {
1190 - $error = array(
1683 + $to_add = true;
1684 + $error = array(
1191 1685 'code' => $errorCode
1192 1686 );
1193 1687
1194 1688 if( $filterId && $filterKey ){
@@ -1194,11 +1688,23 @@
1194 1688 if( $filterId && $filterKey ){
1195 1689 $error['id'] = $this->generateInputID( $filterId, $filterKey );
1196 1690 }
1197 1691
1198 - $this->errors[] = $error;
1692 + if ( ! empty( $this->errors ) ) {
1693 + foreach ( $this->errors as $single_error ) {
1694 + if ( isset( $single_error['code'] ) && $single_error['code'] == $errorCode ) {
1695 + $to_add = false;
1696 + break;
1697 + }
1698 + }
1699 + }
1199 1700
1200 - return $error;
1701 + if ( $to_add ) {
1702 + $this->errors[] = $error;
1703 + return $error;
1704 + }
1705 +
1706 + return false;
1201 1707 }
1202 1708
1203 1709 public function getErrors()
1204 1710 {
@@ -1273,21 +1779,25 @@
1273 1779 }
1274 1780 return $name;
1275 1781 }
1276 1782
1277 - public static function getErrorsList()
1783 + public static function getErrorsList($limit = null)
1278 1784 {
1785 + $effectiveLimit = is_int($limit) ? $limit : FilterSet::FREE_LIMIT_NEW;
1786 +
1279 1787 $errors = array(
1280 1788 // Set errors
1281 - 20 => esc_html__('An error occurred. Set fields were not saved, please try again.', 'filter-everything'),
1282 - 201 => esc_html__('An error occurred. Rule fields were not saved, please try again.', 'filter-everything'),
1283 - 202 => esc_html__('An error occurred. You do not have permissions to edit this.', 'filter-everything'),
1789 + 20 => esc_html__('An error occurred. Filter Set fields were not saved, please try again.', 'filter-everything'),
1790 + 201 => esc_html__('An error occurred. SEO Rule fields were not saved, please try again.', 'filter-everything'),
1791 + 202 => esc_html__('An error occurred. You do not have permission to edit this.', 'filter-everything'),
1284 1792 211 => esc_html__( 'Error: invalid WP Page type.', 'filter-everything' ),
1285 1793 21 => esc_html__( 'Error: invalid post type.', 'filter-everything' ),
1286 - 22 => esc_html__( 'Error: invalid location.', 'filter-everything' ),
1794 + 22 => esc_html__( 'Error: invalid value of the Where to filter? field.', 'filter-everything' ),
1287 1795 221 => esc_html__( 'Error: invalid query.', 'filter-everything' ),
1288 1796 23 => esc_html__( 'Error: invalid Hide empty field.', 'filter-everything' ),
1289 1797 24 => esc_html__( 'Error: invalid Show count field.', 'filter-everything' ),
1798 + 25 => esc_html__( 'Error: invalid Horizontal layout field.', 'filter-everything' ),
1799 + 242 => esc_html__( 'Error: invalid «Apply Button» mode field.', 'filter-everything' ),
1290 1800 // 241 => esc_html__( 'Error: invalid Order field.', 'filter-everything' ),
1291 1801 // Filters errors
1292 1802 31 => esc_html__( 'Error: two or more filters with equal Filter By and Meta key values are forbidden in the same Set. Please, remove or change equal filters.', 'filter-everything' ),
1293 1803 32 => esc_html__( 'Error: invalid fields present.', 'filter-everything' ),
@@ -1294,22 +1804,25 @@
1294 1804 33 => esc_html__( 'Error: invalid filter ID.', 'filter-everything' ),
1295 1805 34 => esc_html__( 'Error: invalid set ID.', 'filter-everything' ),
1296 1806 35 => esc_html__( 'Error: invalid filter entity.', 'filter-everything' ),
1297 1807 36 => esc_html__( 'Error: filter prefix is already used for another entity.', 'filter-everything' ),
1298 - 37 => esc_html__( 'Error: filter prefix should has at least one alphabetic symbol.', 'filter-everything' ),
1808 + 37 => esc_html__( 'Error: filter prefix should have at least one alphabetic symbol.', 'filter-everything' ),
1299 1809 38 => esc_html__( 'Error: filter prefix should not be empty.', 'filter-everything' ),
1300 - 39 => esc_html__( 'Error: prefix part before "-" character can not be equal with other existing prefix.', 'filter-everything' ),
1301 - 399 => esc_html__( 'Error: this prefix is not allowed because it matches a taxonomy or term name already in use on your site. Please use a different prefix.', 'filter-everything' ),
1302 - 3991 => esc_html__( 'Error: var name is not allowed because it matches a taxonomy or term name already in use on your site.', 'filter-everything' ),
1810 + 39 => esc_html__( 'Error: prefix part before "-" character can not be equal to another existing prefix.', 'filter-everything' ),
1811 + 399 => esc_html__( 'Error: this prefix is not allowed because it matches a taxonomy or term name already used on your site. Please use a different prefix.', 'filter-everything' ),
1812 + 3991 => esc_html__( 'Error: var name is not allowed because it matches a taxonomy or term name already used on your site.', 'filter-everything' ),
1303 1813 40 => esc_html__( 'Error: invalid Meta key value', 'filter-everything' ),
1304 - 401 => esc_html__( 'Error: you must select or enter Meta Key', 'filter-everything' ),
1814 + 401 => esc_html__( 'Error: you must enter Meta Key', 'filter-everything' ),
1815 + 402 => esc_html__( 'Error: invalid Taxonomy', 'filter-everything' ),
1305 1816 41 => esc_html__( 'Error: invalid View parameter.', 'filter-everything' ),
1817 + 411 => esc_html__( 'Error: invalid Date Type parameter.', 'filter-everything' ),
1306 1818 42 => esc_html__( 'Error: invalid Logic parameter', 'filter-everything' ),
1307 - 43 => esc_html__( 'Error: invalid Orderby parameter', 'filter-everything' ),
1819 + 43 => esc_html__( 'Error: invalid the Sort Terms by parameter', 'filter-everything' ),
1308 1820 44 => esc_html__( 'Error: invalid exclude terms', 'filter-everything' ),
1821 + 441 => esc_html__( 'Error: invalid include checkbox value', 'filter-everything' ),
1309 1822 45 => esc_html__( 'Error: for filter Post Author logic OR is acceptable only.', 'filter-everything' ),
1310 - 46 => esc_html__( 'Error: Post meta num filter can not be in URL path.', 'filter-everything' ),
1311 - 47 => esc_html__( 'Error: for filter Post Meta Num logic AND is acceptable only.', 'filter-everything' ),
1823 + 46 => esc_html__( 'Error: numeric filter can not be in the URL path.', 'filter-everything' ),
1824 + 47 => esc_html__( 'Error: only AND logic is supported for numeric filters.', 'filter-everything' ),
1312 1825 48 => esc_html__( 'Error: Range slider is acceptable for Post Meta Num filters only.', 'filter-everything' ),
1313 1826 50 => esc_html__( 'Error: SEO Rule must have specified Post Type.', 'filter-everything' ),
1314 1827 51 => esc_html__( 'Error: SEO Rule must contain at least one filter.', 'filter-everything' ),
1315 1828 52 => esc_html__( 'Error: all SEO data fields could not be empty.', 'filter-everything' ),
@@ -1315,9 +1828,39 @@
1315 1828 52 => esc_html__( 'Error: all SEO data fields could not be empty.', 'filter-everything' ),
1316 1829 53 => esc_html__( 'Error: invalid or forbidden filter presents.', 'filter-everything' ),
1317 1830 54 => esc_html__( 'Error: invalid SEO Rule ID.', 'filter-everything' ),
1318 1831 55 => esc_html__( 'Error: SEO rule with selected Filters Combination already exists.', 'filter-everything' ),
1832 + 60 => esc_html__( 'Error: Invalid date format.', 'filter-everything' ),
1833 + 61 => esc_html__( 'Error: Invalid time format.', 'filter-everything' ),
1834 + 62 => esc_html__( 'Error: Invalid date or time format.', 'filter-everything' ),
1835 + 90 => wp_kses(
1836 + sprintf(
1837 + __('Error: you can not update settings because the Filter Everything Pro plugin is locked. Please, enter your <a href="%1$s" target="_blank">license key</a> to unlock it.', 'filter-everything' ),
1838 + admin_url( 'edit.php?post_type=filter-set&page=filters-settings&tab=license' ) ),
1839 + array(
1840 + 'a' => array(
1841 + 'href'=> true,
1842 + 'target' => true
1843 + )
1844 + )
1845 + ),
1846 + 91 => esc_html__('Error: you can not update settings because the Filter Everything Pro plugin is locked. Please, ask your site Superadmin to enter the plugin license key to unlock it.', 'filter-everything' ),
1847 + 92 => wp_kses(
1848 + sprintf(
1849 + /* translators: 1: maximum number of free filter sets per post type, 2: PRO upgrade URL */
1850 + __('The free version allows up to %1$d filter sets per post type. Upgrade to <a href=\'%2$s\' target=\'_blank\'>PRO</a> for unlimited filter sets.', 'filter-everything' ),
1851 + $effectiveLimit,
1852 + esc_url(FLRT_PLUGIN_URL .'/pricing/?utm_source=free_plugin&utm_medium=internal&utm_campaign=free_plugin_upgrade&utm_content=msg_three_set') ),
1853 + array(
1854 + 'a' => array(
1855 + 'href'=> true,
1856 + 'target' => true
1857 + )
1858 + )
1859 + ),
1860 + 93 => esc_html__('Error: The range list is empty. Please add values to the list.', 'filter-everything' ),
1861 +
1319 1862 );
1320 1863
1321 1864 return $errors;
1322 1865 }
1323 1866 }