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 +660 -162 1.6.11.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,50 +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 69 'entity' => array(
66 - 'type' => 'Select',
67 - 'label' => esc_html__( 'Filter by', 'filter-everything' ),
68 - 'class' => 'wpc-field-entity',
69 - 'options' => $this->em->getPossibleEntities(),
70 - 'default' => 'taxonomy_category',
71 - 'instructions' => esc_html__( 'A thing by which posts will be filtered', 'filter-everything' ),
72 - '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
73 77 ),
74 78 'e_name' => array(
75 - 'type' => 'Text',
76 - 'label' => esc_html__( 'Meta Key', 'filter-everything' ),
77 - 'class' => 'wpc-field-ename',
78 - 'instructions' => esc_html__( 'Name of the Custom Field. Please, see Popular Meta keys at the bottom', 'filter-everything'),
79 - '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
80 84 ),
81 85 'label' => array(
82 - 'type' => 'Text',
83 - 'label' => esc_html__( 'Filter Label', 'filter-everything' ),
84 - 'class' => 'wpc-field-label',
85 - 'placeholder' => esc_html__( 'New Filter', 'filter-everything'),
86 - 'instructions' => esc_html__( 'Title which will appear in filter widget', 'filter-everything' )
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' )
87 91 ),
88 92 'slug' => array(
89 93 'type' => 'Text',
90 94 'label' => esc_html__( 'Var Name for URL', 'filter-everything' ),
91 95 'class' => 'wpc-field-slug',
92 - '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'),
93 97 'tooltip' => wp_kses(
94 - __( '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'),
95 99 array( 'br' => array() )
96 100 ),
97 101 'required' => true
98 102 ), // Optional
@@ -101,10 +105,69 @@
101 105 'label' => esc_html__( 'View in Widget', 'filter-everything' ),
102 106 'class' => 'wpc-field-view',
103 107 'options' => $this->getViewOptions(),
104 108 'default' => 'checkboxes',
105 - 'instructions' => ''
109 + 'instructions' => '',
106 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 + ),
107 170 'logic' => array(
108 171 'type' => 'Select',
109 172 'label' => esc_html__( 'Filter Logic', 'filter-everything' ),
110 173 'class' => 'wpc-field-logic',
@@ -109,11 +172,11 @@
109 172 'label' => esc_html__( 'Filter Logic', 'filter-everything' ),
110 173 'class' => 'wpc-field-logic',
111 174 'options' => array('or' => esc_html__('OR', 'filter-everything' ), 'and' => esc_html__('AND', 'filter-everything') ),
112 175 'default' => 'or',
113 - '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' ),
114 177 'tooltip' => wp_kses(
115 - __( '«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' ),
116 179 array( 'br' => array() )
117 180 ),
118 181 ), // AND
119 182 'orderby' => array(
@@ -121,11 +184,11 @@
121 184 'label' => esc_html__( 'Sort Terms by', 'filter-everything' ),
122 185 'class' => 'wpc-field-orderby',
123 186 'options' => $this->getOrderByOptions(),
124 187 'default' => 'default',
125 - 'instructions' => esc_html__('The order in which terms appear in widget', 'filter-everything'),
188 + 'instructions' => esc_html__('The order in which terms appear in the widget', 'filter-everything'),
126 189 'tooltip' => wp_kses(
127 - __( '"Default" option means that terms will be sorted in the order as they were received from DataBase<br />The "Menu order" option is available for WooCommerce attributes only.', 'filter-everything' ),
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' ),
128 191 array( 'br' => array() )
129 192 )
130 193 ),
131 194 'in_path' => array(
@@ -138,9 +201,9 @@
138 201 'exclude' => array(
139 202 'type' => 'Select',
140 203 'label' => esc_html__( 'Include/Exclude Terms', 'filter-everything' ),
141 204 'class' => 'wpc-field-exclude',
142 - 'options' => $entity->getTermsForSelect(),
205 + 'options' => $entityTerms, //$entity->getTermsForSelect(),
143 206 'multiple' => 'multiple',
144 207 'instructions' => '',
145 208 'skip_view' => true
146 209 ),
@@ -157,10 +220,10 @@
157 220 'type' => 'Checkbox',
158 221 'label' => esc_html__( 'Folding', 'filter-everything' ),
159 222 'class' => 'wpc-field-collapse',
160 223 'default' => 'no',
161 - 'instructions' => esc_html__( 'Makes the filter collapsible in widget', 'filter-everything'),
162 - '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' )
163 226 ),
164 227 'hierarchy' => array(
165 228 'type' => 'Checkbox',
166 229 'label' => esc_html__( 'Show Hierarchy', 'filter-everything' ),
@@ -178,9 +241,9 @@
178 241 'step' => array(
179 242 'type' => 'Text',
180 243 'label' => esc_html__( 'Slider Step', 'filter-everything' ),
181 244 'class' => 'wpc-field-value-step',
182 - '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' ),
183 246 'tooltip' => wp_kses(
184 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'),
185 248 array( 'br' => array() )
186 249 ),
@@ -187,40 +250,72 @@
187 250 'default' => 1
188 251 ),
189 252 'search' => array(
190 253 'type' => 'Checkbox',
191 - 'label' => esc_html__( 'Search field', 'filter-everything' ),
254 + 'label' => esc_html__( 'Terms Search', 'filter-everything' ),
192 255 'class' => 'wpc-field-search',
193 256 'default' => 'no',
194 - 'instructions' => esc_html__( 'Adds search field above the terms list', 'filter-everything' )
257 + 'instructions' => esc_html__( 'Adds a search field that allows you to quickly find filter terms', 'filter-everything' )
195 258 ),
196 259 'parent_filter' => array(
197 260 'type' => 'Select',
198 261 'label' => esc_html__( 'Parent Filter', 'filter-everything' ),
199 262 'class' => 'wpc-field-parent-filter',
200 - 'options' => [ 'no' => esc_html__( 'Please, add filters first', 'filter-everything' ) ],
201 - 'instructions' => esc_html__( 'If specified, current Filter terms become available only after parent Filter selected', 'filter-everything' )
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' )
202 265 ),
203 266 'hide_until_parent' => array(
204 267 'type' => 'Checkbox',
205 - 'label' => esc_html__( 'Hide until Parent selected', 'filter-everything' ),
268 + 'label' => esc_html__( 'Hide until the Parent selected', 'filter-everything' ),
206 269 'class' => 'wpc-field-hide-until-parent',
207 270 'default' => 'no',
208 - 'instructions' => esc_html__( 'Do not show this Filter until a parent selected. Useful for step by step filtering', 'filter-everything' )
271 + 'instructions' => esc_html__( 'Do not show this Filter until a parent is selected. Useful for step-by-step filtering', 'filter-everything' )
209 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 + ),
210 291 'tooltip' => array(
211 292 'type' => 'Text',
212 293 'label' => esc_html__( 'Tooltip', 'filter-everything' ),
213 294 'class' => 'wpc-field-tooltip',
214 - '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' ),
215 296 'default' => ''
216 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 + ),
217 305 'show_chips' => array(
218 306 'type' => 'Checkbox',
219 - 'label' => esc_html__( 'Show Selected', 'filter-everything' ),
307 + 'label' => esc_html__( 'Show in Chips', 'filter-everything' ),
220 308 'class' => 'wpc-field-show-chips',
221 309 'default' => 'yes',
222 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' )
223 318 )
224 319 );
225 320
226 321 $this->defaultFields = apply_filters( 'wpc_filter_default_fields', $defaultFields, $this );
@@ -232,14 +327,61 @@
232 327 'checkboxes' => esc_html__('Checkboxes', 'filter-everything'),
233 328 'radio' => esc_html__('Radio buttons', 'filter-everything'),
234 329 'labels' => esc_html__('Labels list', 'filter-everything'),
235 330 'dropdown' => esc_html__('Dropdown', 'filter-everything'),
236 - 'range' => esc_html__('Range', 'filter-everything')
331 + 'range' => esc_html__('Numeric range', 'filter-everything'),
332 + 'date' => esc_html__('Date range', 'filter-everything'),
237 333 );
238 334
335 + if(flrt_is_woocommerce()){
336 + $viewOptions['rating'] = esc_html__('Rating', 'filter-everything');
337 + }
338 +
239 339 return $viewOptions;
240 340 }
241 341
342 + public static function getDateFormatOptions( $type = 'date' )
343 + {
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' );
349 +
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 + );
355 +
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' );
361 +
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' );
371 +
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;
377 + }
378 +
379 + $formatOptions['other'] = '<span>' . esc_html__( 'Custom:', 'filter-everything' ) . '</span>';
380 +
381 + return $formatOptions;
382 + }
383 +
242 384 public function getOrderByOptions()
243 385 {
244 386 $orderBy = array(
245 387 'default' => esc_html__( 'Default (no sorting)', 'filter-everything' ),
@@ -280,22 +422,22 @@
280 422 }
281 423
282 424 $filters = $this->em->selectOnlySetFilters( $set_id );
283 425
284 - if( ! empty( $filters ) ){
285 - if( count( $filters ) > 1 ){
426 + if( ! empty( $filters ) ) {
427 + if ( count( $filters ) > 1 ) {
286 428 $filternames['-1'] = esc_html__( '— Select Filter —', 'filter-everything' ) ;
287 - }else{
288 - $filternames['no'] = esc_html__( 'Please, add filters first', 'filter-everything' );
429 + } else {
430 + $filternames['-1'] = esc_html__( 'Please, add filters first', 'filter-everything' );
289 431 }
290 432
291 - foreach ( $filters as $filter ){
433 + foreach ( $filters as $filter ) {
292 434
293 - if( $filter['entity'] === 'post_meta_num' ){
435 + if ( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) ) {
294 436 continue;
295 437 }
296 438
297 - if( isset( $filter['ID'] ) ){
439 + if ( isset( $filter['ID'] ) ) {
298 440 $filternames[ $filter['ID'] ] = $filter['label'] .' (' . $filter['e_name'] .')';
299 441 }
300 442 }
301 443 }
@@ -314,9 +456,9 @@
314 456 $defaults->post_name = '';
315 457
316 458 $filter = $this->em->prepareFilter( $defaults );
317 459
318 - return $this->prepareFilterInputs( $filter, $this->getFilterNames( $set_id ) );
460 + return $this->prepareFilterInputsToDisplay( $filter, $this->getFilterNames( $set_id ) );
319 461 }
320 462
321 463 public function getFiltersInputs( $set_id )
322 464 {
@@ -326,17 +468,16 @@
326 468 return $preparedFilters;
327 469 }
328 470
329 471 $filters = $this->em->selectOnlySetFilters( $set_id );
330 -
331 472 foreach ( $filters as $field_key => $filter ){
332 - $preparedFilters[] = $this->prepareFilterInputs( $filter, $this->getFilterNames( $set_id ) );
473 + $preparedFilters[] = $this->prepareFilterInputsToDisplay( $filter, $this->getFilterNames( $set_id ) );
333 474 }
334 475
335 476 return $preparedFilters;
336 477 }
337 478
338 - private function prepareFilterInputs( $filter, $filternames )
479 + private function prepareFilterInputsToDisplay( $filter, $filternames )
339 480 {
340 481 $postTypes = false;
341 482 $terms = [];
342 483 // Used for filter table class in filter set fields.
@@ -343,21 +484,38 @@
343 484 // E.g. post_meta, taxonomy, author.
344 485
345 486 $short_entity = $filter['entity'] ? $filter['entity'] : 'taxonomy';
346 487
347 - if( $short_entity === 'taxonomy' ){
488 + if ( $short_entity === 'taxonomy' ) {
348 489 // Add hierarchical class
349 - if( is_taxonomy_hierarchical( $filter['e_name'] ) ){
490 + if ( is_taxonomy_hierarchical( $filter['e_name'] ) ) {
350 491 $short_entity .= ' taxonomy-hierarchical';
351 492 }
352 493
353 494 // Add product attribute class
354 - if( strpos( $filter['e_name'], 'pa_' ) === 0 ){
495 + if ( strpos( $filter['e_name'], 'pa_' ) === 0 ) {
355 496 $short_entity .= ' taxonomy-product-attribute';
356 497 }
357 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 + }
358 511 }
359 512
513 + if ( in_array( $filter['e_name'], flrt_brand_filter_entities() ) ) {
514 + $short_entity .= ' wpc-filter-has-brands';
515 + }
516 +
517 +
360 518 $short_entity .= isset( $filter['view'] ) ? ' wpc-view-'.$filter['view'] : '';
361 519
362 520 $belongs = $this->filterBelongsToPostType( $filter['parent'], $filter['entity'], $filter['e_name'] );
363 521
@@ -362,9 +520,9 @@
362 520 $belongs = $this->filterBelongsToPostType( $filter['parent'], $filter['entity'], $filter['e_name'] );
363 521
364 522 $postType = '';
365 523 // For post_meta_num entity postType is critical value so we have to set it up
366 - 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' ) ) ) {
367 525 $fss = Container::instance()->getFilterSetService();
368 526 $set = $fss->getSet( $filter['parent'] );
369 527 if( isset( $set['post_type']['value'] ) ){
370 528 $postType = $set['post_type']['value'];
@@ -370,12 +528,18 @@
370 528 $postType = $set['post_type']['value'];
371 529 }
372 530 }
373 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 +
374 537 $entity = $this->em->getEntityByFilter( $filter, $postType );
375 538
376 539 if( $entity ){
377 540 $terms = $entity->getTermsForSelect();
541 + asort( $terms, SORT_NATURAL );
378 542 }
379 543
380 544 // This is required only for filter fields inputs
381 545 $filter = $this->fse->combineEntityNameInFilter( $filter );
@@ -381,17 +545,17 @@
381 545 $filter = $this->fse->combineEntityNameInFilter( $filter );
382 546
383 547 foreach( $this->getFieldsMapping() as $fieldKey => $fieldData ){
384 548
385 - if( isset( $filter[$fieldKey] ) ){
549 + if ( isset( $filter[$fieldKey] ) ) {
386 550
387 - if( $fieldKey === 'parent_filter' ){
551 + if ( $fieldKey === 'parent_filter' ) {
388 552 // Exclude current filter
389 - if( isset( $filternames[ $filter['ID'] ] ) ){
553 + if ( isset( $filternames[ $filter['ID'] ] ) ) {
390 554 unset( $filternames[ $filter['ID'] ] );
391 555 }
392 556
393 - if( ! empty( $filternames ) ){
557 + if ( ! empty( $filternames ) ) {
394 558 $fieldData['options'] = $filternames;
395 559 }
396 560 }
397 561
@@ -396,9 +560,9 @@
396 560 }
397 561
398 562 $default_value = isset( $fieldData['default'] ) ? $fieldData['default'] : '';
399 563
400 - $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' ] ) );
401 565
402 566 $fieldData['name'] = $this->generateInputName( $filter['ID'], $fieldKey, $multiple );
403 567 $fieldData['id'] = $this->generateInputID( $filter['ID'], $fieldKey );
404 568
@@ -404,34 +568,37 @@
404 568
405 569 $fieldData['value'] = ( $filter[$fieldKey] ) ? $filter[$fieldKey] : $default_value;
406 570
407 571 if( $fieldKey === 'hide_until_parent' ){
408 - if( $filter['parent_filter'] > 0 ){
572 + if( (int) $filter['parent_filter'] > 0 ){
409 573 $fieldData['additional_class'] = 'wpc-opened';
410 574 }
411 575 }
412 576
413 - if( $filter['ID'] === self::FLRT_NEW_FILTER_ID ){
414 - $fieldData['entity_belongs'] = true;
415 - }else{
416 - $fieldData['entity_belongs'] = $belongs;
577 + if ( $fieldKey === 'slug' && $fieldData['value'] ) {
578 + $fieldData['readonly'] = 'readonly';
417 579 }
418 580
419 - if( $fieldKey === 'entity' ){
420 - $fieldData['short_entity'] = $short_entity;
581 + if ( $fieldKey === 'e_name' && $fieldData['value'] ) {
582 + unset( $fieldData['options'] );
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 + }
421 590 }
422 591
423 - if( $fieldKey === 'slug' && $fieldData['value'] ){
424 - $fieldData['readonly'] = 'readonly';
425 - }
592 + if( $fieldKey === 'entity' ) {
593 + $fieldData['short_entity'] = $short_entity;
426 594
427 - if( $fieldKey === 'e_name' && $fieldData['value'] ){
428 - unset( $fieldData['options'] );
429 - $fieldData['readonly'] = 'readonly';
430 - $fieldData['type'] = 'Text';
431 - }
595 + if ( $filter['ID'] === self::FLRT_NEW_FILTER_ID ) {
596 + $fieldData['entity_belongs'] = true;
597 + } else {
598 + $fieldData['entity_belongs'] = $belongs;
599 + }
432 600
433 - if( $fieldKey === 'entity' ){
434 601 // Add instead-entity field
435 602 if( $filter['ID'] !== self::FLRT_NEW_FILTER_ID ){
436 603
437 604 /**
@@ -453,50 +620,113 @@
453 620 if( $fieldData['value'] === 'post_meta_exists' && ! defined('FLRT_FILTERS_PRO') ){
454 621 $insteadEntityVal = esc_html__('Available in PRO', 'filter-everything');
455 622 }
456 623
624 + if( $fieldData['value'] === 'tax_numeric' && ! defined('FLRT_FILTERS_PRO') ){
625 + $insteadEntityVal = esc_html__('Available in PRO', 'filter-everything');
626 + }
627 +
457 628 // Field, that will be visible instead of entity, that will be hidden.
458 629 $prepared[ 'instead-entity' ] = $this->getInsteadEntityField( $filter['ID'], $insteadEntityVal );
459 630
460 631 }
461 632
462 - 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 + }
463 638
464 - if( ! defined('FLRT_FILTERS_PRO') ){
465 - $fieldData['disabled'] = array('post_meta_exists');
466 - }
639 + }
467 640
641 + if (!defined('FLRT_FILTERS_PRO')) {
642 + if ($fieldKey === 'show_range_list') {
643 + $fieldData['value'] = '';
468 644 }
645 + }
469 646
470 - }
471 647
472 648 if( $fieldKey === 'exclude' ){
473 649 // We always add terms even they are empty array to fill Select2 with related terms.
474 650 $fieldData['options'] = $terms;
475 651
476 - if( $filter['entity'] === 'post_meta_num'){
652 + if( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) ) {
477 653 $fieldData['options'] = [];
478 654 }
479 655 }
480 656
481 657 // Set disabled fields for some situations
482 - 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' ){
483 659 $fieldData['disabled'] = array('and');
484 660 }
485 661
486 - 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' ){
487 663 $fieldData['disabled'] = array('or');
488 664 }
489 665
490 - if( $filter['entity'] === 'post_meta_num' && $fieldKey === 'view' ){
491 - $fieldData['disabled'] = array('checkboxes', 'dropdown', 'radio', 'labels', 'colors', 'image');
492 - }else if( $filter['entity'] !== 'post_meta_num' && $fieldKey === 'view' ){
493 - $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 + }
494 704 }
495 705
496 - if( $fieldKey === 'orderby' && ( mb_strpos( $filter['entity'], 'taxonomy_pa' ) === false ) ){
497 - $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 + }
498 710 }
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'];
718 +
719 + if ( ! in_array( $value_to_custom, self::getPossibleDateFormats( $date_type ) ) ) {
720 + $disabled_custom = false;
721 + $fieldData['value'] = 'other';
722 + }
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 + }
499 729 }
500 730
501 731 $prepared[ $fieldKey ] = $fieldData;
502 732 }
@@ -517,10 +747,9 @@
517 747 $sanitizedFilter[ $key ] = $value;
518 748 }else{
519 749
520 750 if( is_array( $value ) ){
521 - array_map( 'esc_html', $value );
522 - $sanitizedFilter[ $key ] = $value;
751 + $sanitizedFilter[ $key ] = map_deep( $value, 'esc_html' );
523 752 } else {
524 753 $sanitizedFilter[ $key ] = esc_html( $value );
525 754 }
526 755
@@ -566,18 +795,15 @@
566 795
567 796 return $filter;
568 797 }
569 798
570 - public function validateTheFilter( $filter, $id = false ){
799 + public function validateTheFilter( $filter, $id = false ) {
571 800
572 801 $valid = true;
573 - $newFilter = false;
574 - $filterID = false;
575 802 $validEntity = true;
576 803 $validator = new Validator();
577 804
578 - if( $filter['ID'] === self::FLRT_NEW_FILTER_ID ){
579 - $newFilter = true;
805 + if( $filter['ID'] === self::FLRT_NEW_FILTER_ID ) {
580 806 $filterID = $id;
581 807 }else{
582 808 $filterID = $filter['ID'];
583 809 }
@@ -681,23 +907,23 @@
681 907 }
682 908
683 909 // Ensure, that prefix contains at least one alphabetic character
684 910 // Also this prevents from empty prefix
685 - if ( ! $validator->validateAlphabCharsExists($prefix) ) {
911 + if ( ! $validator->validateAlphabCharsExists( $prefix ) ) {
686 912 $this->pushError( 37, $filterID, 'slug' ); // Invalid prefix. Has to contain at least one alphabetic symbol
687 913 $valid = false;
688 914 }
689 915
690 916 // Check hyphens problem when cat and cat-x exists
691 - if (!$validator->validatePrefixHyphens($prefix, $existingSlugs) ) {
917 + if ( ! $validator->validatePrefixHyphens( $prefix, $existingSlugs ) ) {
692 918 $this->pushError( 39, $filterID, 'slug' ); // Invalid prefix. Incorrect hyphens.
693 919 $valid = false;
694 920 }
695 921
696 922 // Check for slugs, that matches with native WP Entities
697 - if( ! $validator->validateAllowedPrefixes( $prefix, $filter ) ){
923 + if ( ! $validator->validateAllowedPrefixes( $prefix, $filter ) ) {
698 924 $errorCode = 3991;
699 - if( defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO ){
925 + if ( defined( 'FLRT_FILTERS_PRO' ) && FLRT_FILTERS_PRO ) {
700 926 $errorCode = 399;
701 927 }
702 928 $this->pushError( $errorCode, $filterID, 'slug' ); // Invalid prefix. Equal to wp entity.
703 929 $valid = false;
@@ -710,36 +936,83 @@
710 936
711 937 /**
712 938 * E_name validations
713 939 */
714 - if( isset( $filter['e_name'] ) ){
940 + if ( isset( $filter['e_name'] ) ) {
715 941
716 942 if( isset( $filter['entity'] ) ) {
717 - 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' ) ) ) {
718 944 $e_name = $filter['e_name'];
719 945
720 - if( $e_name === '' ){
721 - $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.
722 948 $valid = false;
723 949 } else {
724 - // Should not be empty and should contain at least one alphabetic character
725 - if (!$validator->validateAlphabCharsExists($e_name)) {
726 - $this->pushError(40, $filterID, 'e_name'); // Invalid E_name.
727 - $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 + }
728 983 }
984 + }
985 + }
729 986
730 - // Should not contain characters that escaped by esc_attr
731 - if (!$validator->validateEscAttrCharacters($e_name)) {
732 - $this->pushError(40, $filterID, 'e_name'); // Invalid E_name.
733 - $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;
734 1013 }
735 1014
736 - // Some meta keys should be prohibited to use
737 - $excludedMetaKeys = flrt_get_forbidden_meta_keys();
738 - if( in_array( $e_name, $excludedMetaKeys, true ) ){
739 - $this->pushError( 40, $filterID, 'e_name'); // Invalid E_name.
740 - $valid = false;
741 - }
742 1015 }
743 1016 }
744 1017 }
745 1018
@@ -747,8 +1020,22 @@
747 1020 $this->pushError( 40, $filterID, 'e_name' ); // Invalid E_name.
748 1021 $valid = false;
749 1022 }
750 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 +
751 1038 /**
752 1039 * View validations
753 1040 */
754 1041 if( isset( $filter['view'] ) ){
@@ -764,8 +1051,18 @@
764 1051 $valid = false;
765 1052 }
766 1053
767 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 + /**
768 1065 * Logic validations
769 1066 */
770 1067 if( isset( $filter['logic'] ) ){
771 1068
@@ -774,9 +1071,9 @@
774 1071 $valid = false;
775 1072 }
776 1073
777 1074 // For author and post_meta_exists entities logic can be only OR
778 - 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' ) ) ){
779 1076 if( $filter['logic'] !== 'or' ){
780 1077 $this->pushError( 45, $filterID, 'logic' ); // Not acceptable logic.
781 1078 $valid = false;
782 1079 }
@@ -782,9 +1079,9 @@
782 1079 }
783 1080 }
784 1081
785 1082 // For author entity logic can be only OR
786 - if( $filter['entity'] === 'post_meta_num' ){
1083 + if ( in_array( $filter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) ) {
787 1084 if( $filter['logic'] !== 'and' ){
788 1085 $this->pushError( 47, $filterID, 'logic' ); // Not acceptable logic.
789 1086 $valid = false;
790 1087 }
@@ -832,9 +1129,9 @@
832 1129 }
833 1130
834 1131 // In case when checkbox is not checked there is no $_POST['in_path'] parameter
835 1132 if( isset( $filter['in_path'] ) ){
836 - 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' ){
837 1134 $this->pushError( 46, $filterID, 'in_path' ); // Invalid In Path for Post meta num.
838 1135 $valid = false;
839 1136 }
840 1137 }
@@ -863,39 +1160,42 @@
863 1160
864 1161 return $valid;
865 1162 }
866 1163
867 - public function validateFilters( $filters ){
1164 + public function validateFilters( $filters ) {
868 1165
869 - if( ! $filters ){
1166 + if ( ! $filters ) {
870 1167 return false;
871 1168 }
872 1169 $valid = true;
873 1170
874 1171 // Check permissions
875 - 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.
876 1174 $this->pushError(202);
877 1175 $valid = false;
878 1176 }
879 1177
880 1178 // Check for equal filters
881 - foreach ( (array) $filters as $filter ){
882 - if( isset( $filter['entity'] ) && isset( $filter['e_name'] ) ){
1179 + foreach ( (array) $filters as $filter ) {
1180 + if( isset( $filter['entity'] ) && isset( $filter['e_name'] ) ) {
883 1181 // To avoid few filters with the same meta key
884 - if( in_array( $filter['entity'], array( 'post_meta', 'post_meta_num', 'post_meta_exists' ), true ) ){
885 - $keys[] = 'post_meta'.$filter['e_name'];
886 - }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 {
887 1187 $keys[] = $filter['entity'] . $filter['e_name'];
888 1188 }
889 1189 }
890 1190 }
891 1191
892 - if( flrt_array_contains_duplicate( $keys ) ){
1192 + if ( flrt_array_contains_duplicate( $keys ) ) {
893 1193 $this->pushError(31); // Equal filters
894 1194 $valid = false;
895 1195 }
896 1196
897 - return $valid;
1197 + return apply_filters( 'wpc_validate_filter_fields', $valid, $this );
898 1198 }
899 1199
900 1200 public function ajaxValidateFilters()
901 1201 {
@@ -900,8 +1200,19 @@
900 1200 public function ajaxValidateFilters()
901 1201 {
902 1202 $postData = Container::instance()->getThePost();
903 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 + }
904 1215 $response = [];
905 1216
906 1217 if( ! $data ){
907 1218 $this->pushError(20);
@@ -1060,9 +1371,9 @@
1060 1371
1061 1372 $nonce = isset( $postData['_wpnonce'] ) ? $postData['_wpnonce'] : false;
1062 1373 $errorResponse = array(
1063 1374 'fid' => $filterId,
1064 - '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')
1065 1376 );
1066 1377
1067 1378 if( ! wp_verify_nonce( $nonce, FilterSet::NONCE_ACTION ) ){
1068 1379 wp_send_json_error( $errorResponse );
@@ -1085,8 +1396,104 @@
1085 1396 wp_send_json_error( $errorResponse );
1086 1397 }
1087 1398 }
1088 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 +
1089 1496 public function sendExcludedTerms()
1090 1497 {
1091 1498 $container = Container::instance();
1092 1499 $postData = $container->getThePost();
@@ -1097,9 +1504,9 @@
1097 1504 $e_name = isset( $postData['ename'] ) ? $postData['ename'] : false;
1098 1505
1099 1506 $errorResponse = array(
1100 1507 'fid' => $filterId,
1101 - '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')
1102 1509 );
1103 1510
1104 1511 if( ! wp_verify_nonce( $nonce, FilterSet::NONCE_ACTION ) ){
1105 1512 wp_send_json_error( $errorResponse );
@@ -1130,8 +1537,35 @@
1130 1537 }
1131 1538
1132 1539 }
1133 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 +
1134 1568 public function addSpinnerToSelect( $html, $attributes )
1135 1569 {
1136 1570 if( isset( $attributes['class'] ) ){
1137 1571 $requiredIds = array(
@@ -1163,9 +1597,9 @@
1163 1597 'value' => $insteadEntityVal,
1164 1598 'readonly' => 'readonly',
1165 1599 'default' => '',
1166 1600 'instructions' => esc_html__( 'A thing by which posts will be filtered', 'filter-everything'),
1167 - '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' ),
1168 1602 'required' => true
1169 1603 );
1170 1604
1171 1605 return $insteadEntity;
@@ -1201,17 +1635,33 @@
1201 1635 if ( empty( $post_type ) ){
1202 1636 return false;
1203 1637 }
1204 1638
1205 - 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 + ) ) ){
1206 1646 return true;
1207 1647 }
1208 1648
1209 - 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' ) ){
1210 1650 return true;
1211 1651 }
1212 1652
1213 - 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 +
1214 1664 return $this->isTaxonomyBelongsToPostType( $post_type, $e_name );
1215 1665 }
1216 1666
1217 1667 return false;
@@ -1216,11 +1666,11 @@
1216 1666
1217 1667 return false;
1218 1668 }
1219 1669
1220 - public function getErrorMessage( $code = 20 )
1670 + public function getErrorMessage( $code = 20, $limit = null )
1221 1671 {
1222 - $messages = $this->getErrorsList();
1672 + $messages = self::getErrorsList($limit);
1223 1673
1224 1674 if( isset( $messages[$code] ) ){
1225 1675 return $messages[$code];
1226 1676 }
@@ -1229,9 +1679,10 @@
1229 1679 }
1230 1680
1231 1681 public function pushError( $errorCode, $filterId = false, $filterKey = '' )
1232 1682 {
1233 - $error = array(
1683 + $to_add = true;
1684 + $error = array(
1234 1685 'code' => $errorCode
1235 1686 );
1236 1687
1237 1688 if( $filterId && $filterKey ){
@@ -1237,11 +1688,23 @@
1237 1688 if( $filterId && $filterKey ){
1238 1689 $error['id'] = $this->generateInputID( $filterId, $filterKey );
1239 1690 }
1240 1691
1241 - $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 + }
1242 1700
1243 - return $error;
1701 + if ( $to_add ) {
1702 + $this->errors[] = $error;
1703 + return $error;
1704 + }
1705 +
1706 + return false;
1244 1707 }
1245 1708
1246 1709 public function getErrors()
1247 1710 {
@@ -1316,21 +1779,24 @@
1316 1779 }
1317 1780 return $name;
1318 1781 }
1319 1782
1320 - public static function getErrorsList()
1783 + public static function getErrorsList($limit = null)
1321 1784 {
1785 + $effectiveLimit = is_int($limit) ? $limit : FilterSet::FREE_LIMIT_NEW;
1786 +
1322 1787 $errors = array(
1323 1788 // Set errors
1324 - 20 => esc_html__('An error occurred. Set fields were not saved, please try again.', 'filter-everything'),
1325 - 201 => esc_html__('An error occurred. Rule fields were not saved, please try again.', 'filter-everything'),
1326 - 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'),
1327 1792 211 => esc_html__( 'Error: invalid WP Page type.', 'filter-everything' ),
1328 1793 21 => esc_html__( 'Error: invalid post type.', 'filter-everything' ),
1329 - 22 => esc_html__( 'Error: invalid location.', 'filter-everything' ),
1794 + 22 => esc_html__( 'Error: invalid value of the Where to filter? field.', 'filter-everything' ),
1330 1795 221 => esc_html__( 'Error: invalid query.', 'filter-everything' ),
1331 1796 23 => esc_html__( 'Error: invalid Hide empty field.', 'filter-everything' ),
1332 1797 24 => esc_html__( 'Error: invalid Show count field.', 'filter-everything' ),
1798 + 25 => esc_html__( 'Error: invalid Horizontal layout field.', 'filter-everything' ),
1333 1799 242 => esc_html__( 'Error: invalid «Apply Button» mode field.', 'filter-everything' ),
1334 1800 // 241 => esc_html__( 'Error: invalid Order field.', 'filter-everything' ),
1335 1801 // Filters errors
1336 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' ),
@@ -1338,23 +1804,25 @@
1338 1804 33 => esc_html__( 'Error: invalid filter ID.', 'filter-everything' ),
1339 1805 34 => esc_html__( 'Error: invalid set ID.', 'filter-everything' ),
1340 1806 35 => esc_html__( 'Error: invalid filter entity.', 'filter-everything' ),
1341 1807 36 => esc_html__( 'Error: filter prefix is already used for another entity.', 'filter-everything' ),
1342 - 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' ),
1343 1809 38 => esc_html__( 'Error: filter prefix should not be empty.', 'filter-everything' ),
1344 - 39 => esc_html__( 'Error: prefix part before "-" character can not be equal with other existing prefix.', 'filter-everything' ),
1345 - 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' ),
1346 - 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' ),
1347 1813 40 => esc_html__( 'Error: invalid Meta key value', 'filter-everything' ),
1348 - 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' ),
1349 1816 41 => esc_html__( 'Error: invalid View parameter.', 'filter-everything' ),
1817 + 411 => esc_html__( 'Error: invalid Date Type parameter.', 'filter-everything' ),
1350 1818 42 => esc_html__( 'Error: invalid Logic parameter', 'filter-everything' ),
1351 - 43 => esc_html__( 'Error: invalid Orderby parameter', 'filter-everything' ),
1819 + 43 => esc_html__( 'Error: invalid the Sort Terms by parameter', 'filter-everything' ),
1352 1820 44 => esc_html__( 'Error: invalid exclude terms', 'filter-everything' ),
1353 1821 441 => esc_html__( 'Error: invalid include checkbox value', 'filter-everything' ),
1354 1822 45 => esc_html__( 'Error: for filter Post Author logic OR is acceptable only.', 'filter-everything' ),
1355 - 46 => esc_html__( 'Error: Post meta num filter can not be in URL path.', 'filter-everything' ),
1356 - 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' ),
1357 1825 48 => esc_html__( 'Error: Range slider is acceptable for Post Meta Num filters only.', 'filter-everything' ),
1358 1826 50 => esc_html__( 'Error: SEO Rule must have specified Post Type.', 'filter-everything' ),
1359 1827 51 => esc_html__( 'Error: SEO Rule must contain at least one filter.', 'filter-everything' ),
1360 1828 52 => esc_html__( 'Error: all SEO data fields could not be empty.', 'filter-everything' ),
@@ -1360,9 +1828,39 @@
1360 1828 52 => esc_html__( 'Error: all SEO data fields could not be empty.', 'filter-everything' ),
1361 1829 53 => esc_html__( 'Error: invalid or forbidden filter presents.', 'filter-everything' ),
1362 1830 54 => esc_html__( 'Error: invalid SEO Rule ID.', 'filter-everything' ),
1363 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 +
1364 1862 );
1365 1863
1366 1864 return $errors;
1367 1865 }
1368 1866 }