PluginProbe
Filter Everything — WordPress & WooCommerce Filters / 1.6.1
Filter Everything — WordPress & WooCommerce Filters v1.6.1
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
filter-everything / src / Admin / FilterSet.php

FilterSet.php in Filter Everything — WordPress & WooCommerce Filters 1.6.1, at src/Admin/FilterSet.php

1,066 lines 40.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 namespace FilterEverything\Filter;
5
6 if ( ! defined('WPINC') ) {
7 wp_die();
8 }
9
10 class FilterSet
11 {
12 const NONCE_ACTION = 'wpc-f-set-nonce';
13
14 const FIELD_NAME_PREFIX = 'wpc_set_fields';
15
16 private $defaultFields = [];
17
18 private $hooksRegistered = false;
19
20 private $errors;
21
22 public function __construct()
23 {
24 $this->registerHooks();
25 }
26
27 private function setupDefaultFields()
28 {
29 // maybe add filter in future to allow change default fields
30 $defaultFields = array(
31 'wp_page_type' => array(
32 'type' => 'Hidden',
33 'label' => esc_html__('Where to filter?', 'filter-everything'),
34 'class' => 'wpc-field-wp-page-type',
35 'id' => $this->generateFieldId('wp_page_type'),
36 'name' => $this->generateFieldName('wp_page_type'),
37 'default' => 'common___common',
38 'instructions' => esc_html__('Specify page(s) where posts that should be filtered are located', 'filter-everything'),
39 'settings' => true
40 ),
41 'post_type' => array(
42 'type' => 'Select',
43 'label' => esc_html__('Post Type to filter', 'filter-everything'),
44 'name' => $this->generateFieldName('post_type'),
45 'id' => $this->generateFieldId('post_type'),
46 'class' => 'wpc-field-post-type',
47 'options' => $this->getPostTypes(),
48 'default' => 'post',
49 'instructions' => esc_html__('Select Post Type you need to filter', 'filter-everything'),
50 'particular' => 'post_excerpt' // Determine that this is specific field should be stored in wp_post column
51 ),
52 'post_name' => array(
53 'type' => 'Hidden',
54 'label' => '',
55 'name' => $this->generateFieldName('post_name'),
56 'id' => $this->generateFieldId('post_name'),
57 'class' => 'wpc-field-location',
58 'default' => '1',
59 'instructions' => esc_html__('Specify page(s) where posts that should be filtered are located', 'filter-everything'),
60 'particular' => 'post_name',
61 'settings' => true
62 ),
63 'wp_filter_query' => array(
64 'type' => 'Hidden',
65 'label' => esc_html__('And what to filter?', 'filter-everything'),
66 'class' => 'wpc-field-wp-filter-query',
67 'id' => $this->generateFieldId('wp_filter_query'),
68 'name' => $this->generateFieldName('wp_filter_query'),
69 'options' => '',
70 'default' => '-1',
71 'instructions' => esc_html__('Select WP Query you want to filter', 'filter-everything'),
72 'settings' => true
73 ),
74 'hide_empty' => array(
75 'type' => 'Select',
76 'label' => esc_html__('Empty Terms', 'filter-everything'),
77 'name' => $this->generateFieldName('hide_empty'),
78 'id' => $this->generateFieldId('hide_empty'),
79 'class' => 'wpc-field-hide-empty',
80 'options' => array(
81 'no' => esc_html__('Never hide', 'filter-everything'),
82 'yes' => esc_html__('Always hide', 'filter-everything'),
83 'initial' => esc_html__('Hide in the initial Filter only', 'filter-everything')
84 ),
85 'default' => 'no',
86 'instructions' => esc_html__('To hide or not a Filter terms that do not contain posts', 'filter-everything'),
87 'settings' => true
88 ),
89 'show_count' => array(
90 'type' => 'Checkbox',
91 'label' => esc_html__('Show count', 'filter-everything'),
92 'name' => $this->generateFieldName('show_count'),
93 'id' => $this->generateFieldId('show_count'),
94 'class' => 'wpc-field-show-count',
95 'default' => 'yes',
96 'instructions' => esc_html__('Display number of posts in a term', 'filter-everything'),
97 'settings' => true
98 )
99
100 );
101
102 $this->defaultFields = apply_filters( 'wpc_filter_set_default_fields', $defaultFields, $this );
103 }
104
105 private function registerHooks()
106 {
107 if (!$this->hooksRegistered) {
108 add_filter('wpc_input_type_select', array($this, 'addCustomLabel'), 10, 2);
109 add_action('admin_print_scripts', array($this, 'includeAdminJs'), 9999);
110
111 add_filter( 'post_updated_messages', [$this, 'filterSetActionsMessages'] );
112 add_filter( 'bulk_post_updated_messages', [ $this, 'filterSetBulkActionsMessages' ], 10, 2 );
113
114 add_filter( 'page_row_actions', [$this, 'filterSetRowActions'], 10, 2 );
115
116 add_action( 'restrict_manage_posts', [$this, 'restrictManagePosts'], 999 );
117
118 $this->hooksRegistered = true;
119 }
120 }
121
122 public function restrictManagePosts( $post_type )
123 {
124 if( $post_type === FLRT_FILTERS_SET_POST_TYPE ){
125 $output = ob_get_clean();
126 ob_start();
127 }
128 }
129
130 public function filterSetRowActions( $actions, $post )
131 {
132 if( isset( $post->post_type ) && $post->post_type === FLRT_FILTERS_SET_POST_TYPE ){
133 $new_actions = [];
134 foreach( $actions as $key => $action ){
135 if( in_array( $key, array( 'edit', 'trash', 'untrash', 'delete' ) ) ){
136 $new_actions[$key] = $action;
137 }
138 }
139 return $new_actions;
140 }
141 return $actions;
142 }
143
144 public function filterSetBulkActionsMessages( $messages, $bulk_counts )
145 {
146 if( ! isset( $messages[ FLRT_FILTERS_SET_POST_TYPE ] ) ){
147 $messages[ FLRT_FILTERS_SET_POST_TYPE ] = array(
148 /* translators: %s: Number of posts. */
149 'updated' => esc_html( _n( '%s filter set updated.', '%s filter sets updated.', $bulk_counts['updated'], 'filter-everything' ) ),
150 'locked' => ( 1 === $bulk_counts['locked'] ) ? esc_html__( '1 filter set not updated, somebody is editing it.', 'filter-everything' ) :
151 /* translators: %s: Number of posts. */
152 esc_html( _n( '%s filter set not updated, somebody is editing it.', '%s filter sets not updated, somebody is editing them.', $bulk_counts['locked'], 'filter-everything' ) ),
153 /* translators: %s: Number of posts. */
154 'deleted' => esc_html( _n( '%s filter set permanently deleted.', '%s filter sets permanently deleted.', $bulk_counts['deleted'], 'filter-everything' ) ),
155 /* translators: %s: Number of posts. */
156 'trashed' => esc_html( _n( '%s filter set moved to the Trash.', '%s filter sets moved to the Trash.', $bulk_counts['trashed'], 'filter-everything' ) ),
157 /* translators: %s: Number of posts. */
158 'untrashed' => esc_html( _n( '%s filter set restored from the Trash.', '%s filter sets restored from the Trash.', $bulk_counts['untrashed'], 'filter-everything' ) ),
159 );
160 }
161 return $messages;
162 }
163
164 public function filterSetActionsMessages( $messages )
165 {
166 if( ! isset( $messages[ FLRT_FILTERS_SET_POST_TYPE ] ) ){
167 // No need to escape
168 $messages[ FLRT_FILTERS_SET_POST_TYPE ] = array(
169 0 => '',
170 1 => esc_html__( 'Filters Set updated.', 'filter-everything' ),
171 2 => esc_html__( 'Custom field updated.', 'filter-everything' ),
172 3 => esc_html__( 'Custom field deleted.', 'filter-everything' ),
173 4 => esc_html__( 'Filters Set updated.', 'filter-everything' ),
174 5 => false,
175 6 => esc_html__( 'Filter set created.', 'filter-everything' ),
176 7 => esc_html__( 'Filter set saved.', 'filter-everything' ),
177 8 => esc_html__( 'Filter set submitted.', 'filter-everything' ),
178 9 => esc_html__( 'Filter set scheduled for', 'filter-everything' ),
179 10 => esc_html__( 'Filter set draft updated.', 'filter-everything' ),
180 // Errors
181 11 => esc_html__('Filter set was not updated.', 'filter-everything')
182 );
183 }
184
185 return $messages;
186 }
187
188 /**
189 * @return array
190 */
191 private function getExistingFilterSlugs()
192 {
193 $existingSlugs = get_option('wpc_filter_permalinks', []);
194 $convertedExistingSlugs = [];
195
196 foreach( $existingSlugs as $entityKey => $slug ){
197 $parts = explode( '#', $entityKey, 2 );
198 $newEntityKey = implode('_', $parts);
199 $convertedExistingSlugs[$newEntityKey] = $slug;
200 }
201
202 return $convertedExistingSlugs;
203 }
204
205 /**
206 * @return array
207 */
208 private function getPostTypesTaxList()
209 {
210 $postTypesTaxList = [];
211
212 $taxonomies = EntityManager::getTaxonomies();
213 $postTypes = array_keys( $this->getPostTypes() );
214
215 foreach ( $postTypes as $postType ){
216 foreach ( $taxonomies as $taxonomy) {
217
218 if( in_array( $postType, $taxonomy->object_type ) ){
219 $postTypesTaxList[$postType][] = array(
220 'name' => 'taxonomy_' . $taxonomy->name,
221 'hierarchical' => $taxonomy->hierarchical
222 );
223 }
224 }
225 }
226
227 return $postTypesTaxList;
228 }
229
230 public function includeAdminJs()
231 {
232 $screen = get_current_screen();
233
234 if( isset( $screen->id ) && $screen->id === FLRT_FILTERS_SET_POST_TYPE ){
235 global $post_id;
236
237 // Disable autosavings
238 wp_dequeue_script( 'autosave' );
239
240 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
241 $ver = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? rand(0, 1000) : FLRT_PLUGIN_VER;
242 $select2ver = '4.1.0';
243
244 // Filter Set script
245 wp_enqueue_script('wpc-filters-admin-filter-set', FLRT_PLUGIN_URL . 'assets/js/wpc-filter-set-admin'.$suffix.'.js', array('jquery', 'wp-util', 'jquery-ui-sortable', 'select2'), $ver );
246
247 $l10n = array(
248 'filterSlugs' => $this->getExistingFilterSlugs(),
249 'postTypesTaxList' => $this->getPostTypesTaxList(),
250 'moreOptions' => esc_html__( 'More options', 'filter-everything' ),
251 'lessOptions' => esc_html__( 'Less options', 'filter-everything' ),
252 'filtersPro' => defined( 'FLRT_FILTERS_PRO' ),
253 'wPQuerySelectId' => $this->generateFieldId('wp_filter_query'),
254 'excludePlaceholder' => esc_html__('Select terms and', 'filter-everything' ),
255 'newFilter' => esc_html__('New Filter', 'filter-everything' ),
256 'addFilter' => esc_html__( 'Please, add filters first', 'filter-everything' ),
257 'selectFilter' => esc_html__( '— Select Filter —', 'filter-everything' )
258 );
259
260 wp_localize_script( 'wpc-filters-admin-filter-set', 'wpcSetVars', $l10n );
261
262 // Select2
263 wp_enqueue_script( 'select2', FLRT_PLUGIN_URL . "assets/js/select2/select2".$suffix.".js", array('jquery'), $select2ver );
264 wp_enqueue_style('select2', FLRT_PLUGIN_URL . "assets/css/select2/select2".$suffix.".css", '', $select2ver );
265
266 }
267 }
268
269 public function addCustomLabel( $html, $attributes )
270 {
271 if( isset( $attributes['id'] ) ){
272 if( $attributes['id'] == $this->generateFieldId('post_name') ){
273
274 $spinner = '<span class="spinner"></span>'."\r\n";
275 $openContainer = '<div id="wpc-field-location-container"><span class="wpc-full-width">&nbsp;</span><div class="wpc-field-location-wrapper">'."\r\n";
276 $closeContainer = '</div></div>'."\r\n";
277 $link = '';
278
279 $current_index = isset( $attributes['value'] ) ? $attributes['value'] : '';
280 $options = ! empty( $attributes['options'] ) ? $attributes['options'] : [];
281
282 if( isset( $options[ $current_index ]['data-link'] ) ){
283 $link = '<a class="wpc-location-preview" href="'.esc_attr( $options[ $current_index ]['data-link'] ).'" ';
284 $link .= 'title="'.esc_attr( esc_html__('Preview selected location in a new tab', 'filter-everything') ).'" ';
285 $link .= 'target="_blank">';
286 $link .= '<span class="dashicons dashicons-visibility"></span></a>';
287 }
288 $html = $spinner . $openContainer . $html . $link . $closeContainer;
289 }
290
291 if( $attributes['id'] == $this->generateFieldId('apply_button_post_name') ){
292
293 $spinner = '<span class="spinner"></span>'."\r\n";
294 $openContainer = '<div id="wpc-field-apply-button-location-container"><span class="wpc-full-width">&nbsp;</span>'."\r\n";
295 $closeContainer = '</div>'."\r\n";
296 $link = '';
297
298 $html = $spinner . $openContainer . $html . $link . $closeContainer;
299 }
300
301 if( $attributes['id'] == $this->generateFieldId('wp_filter_query') ){
302
303 $spinner = '<span class="spinner"></span>'."\n";
304 $openContainer = '<div id="wpc-field-wp-query-container">&nbsp;'."\n";
305 $description = '<p class="description">'.esc_html__( 'Note: if you will modify the query on the selected page, do not forget to update the current Filter Set.', 'filter-everything' ).'</p>'."\n";
306 $closeContainer = '<div id="wpc_query_vars"></div></div>'."\n";
307
308 $html = $spinner . $openContainer . $html . $description . $closeContainer;
309 }
310
311 if( $attributes['id'] == $this->generateFieldId('wp_page_type') ){
312 $label = '<label class="wpc-location-label" for="'.esc_attr($attributes['id']).'">'.esc_html__( 'Apply filtering if page is:', 'filter-everything' ).'</label>'."\r\n";
313 $html = $label . $html;
314 }
315
316 if( $attributes['id'] == $this->generateFieldId('apply_button_page_type') ){
317 $label = '<label class="wpc-location-label" for="'.esc_attr($attributes['id']).'">'.esc_html__( 'Show this Filter Set if page is:', 'filter-everything' ).'</label>'."\r\n";
318 $html = $label . $html;
319 }
320 }
321
322 return $html;
323 }
324
325
326 private function getSpecificFields( $type )
327 {
328 $particular = [];
329
330 foreach( $this->getFieldsMapping() as $key => $field ){
331 if( isset( $field[$type] ) && ! empty( $field[$type] ) ){
332 $particular[ $key ] = $field;
333 }
334 }
335
336 return $particular;
337 }
338
339 public function getPostTypes()
340 {
341 $allowed_types = [];
342 $post_types = get_post_types( array( 'public' => true ), 'objects' );
343 $exclude = apply_filters( 'wpc_filter_post_types', [] );
344
345 foreach ( $post_types as $type ){
346 if( in_array( $type->name, $exclude ) ){
347 continue;
348 }
349 $allowed_types[$type->name] = isset( $type->labels->name ) ? $type->labels->name : $type->labels->singular_name;
350 }
351
352 return $allowed_types;
353
354 }
355
356 public function getFieldsMapping()
357 {
358 return $this->defaultFields;
359 }
360
361 /**
362 * @var $queriedObject object
363 * @return array (empty array if there is no related set id)
364 */
365 public function findRelevantSets( $queriedObject )
366 {
367 // We need to search all relevantSetS
368 $filterSets = [];
369
370 $filterSets = apply_filters( 'wpc_relevant_set_ids', $filterSets, $queriedObject );
371
372 if( ! empty( $filterSets ) ){
373 foreach ( $filterSets as $set ){
374 if( isset( $set['show_on_the_page'] ) && $set['show_on_the_page'] === true ){
375 return $filterSets;
376 }
377 }
378 }
379
380 // Get main filter set for post type
381 if( isset( $queriedObject['post_types'] ) && ! isset( $queriedObject['post_id'] ) ){
382 foreach( $queriedObject['post_types'] as $post_type ){
383 $sets = $this->getSetIdForPostType( $post_type );
384 if( $sets !== false ){
385 $filterSets = array_merge( $filterSets, $sets );
386 // return $sets;
387 }
388 }
389 }
390
391 return $filterSets;
392 }
393
394 /**
395 * @var $post_type string - post_type post|product|page|...
396 * @return int|false
397 */
398 public function getSetIdForPostType( $post_type )
399 {
400 if( ! $post_type ){
401 return false;
402 }
403
404 $container = Container::instance();
405 $sets = [];
406 $pll_lang_id = false;
407
408 $key = 'set_' . $post_type;
409
410 if( ! $sets = $container->getParam( $key ) ){
411 global $wpdb;
412 $is_fitler_set_translatable = false;
413
414 if( flrt_wpml_active() ){
415 $wpml_settings = get_option( 'icl_sitepress_settings' );
416 if( isset( $wpml_settings['custom_posts_sync_option'][FLRT_FILTERS_SET_POST_TYPE] ) ){
417 if( $wpml_settings['custom_posts_sync_option'][FLRT_FILTERS_SET_POST_TYPE] === '1' ){
418 $is_fitler_set_translatable = true;
419 }
420 }
421 }
422
423 $sql[] = "SELECT {$wpdb->posts}.ID,{$wpdb->posts}.post_content,{$wpdb->posts}.post_excerpt,{$wpdb->posts}.post_name";
424 $sql[] = "FROM {$wpdb->posts}";
425
426 if ( flrt_wpml_active() && defined('ICL_LANGUAGE_CODE') && $is_fitler_set_translatable ) {
427 $sql[] = "LEFT JOIN {$wpdb->prefix}icl_translations AS wpml_translations";
428 $sql[] = "ON {$wpdb->posts}.ID = wpml_translations.element_id";
429 $sql[] = "AND wpml_translations.element_type IN(";
430 $sql[] = $wpdb->prepare( "CONCAT('post_', '%s')", FLRT_FILTERS_SET_POST_TYPE );
431 $sql[] = ")";
432 }
433
434 // Check common if Polylang PRO is active and Filter Set is translatable post type
435 if( flrt_pll_pro_active() && defined('FLRT_ALLOW_PLL_TRANSLATIONS') && FLRT_ALLOW_PLL_TRANSLATIONS ){
436 $pll_current_language = pll_current_language();
437 $pll_languages = pll_the_languages( array('raw' => 1 ) );
438 if( $pll_current_language && isset( $pll_languages[ $pll_current_language ]['id'] ) ){
439 $pll_lang_id = $pll_languages[ $pll_current_language ]['id'];
440
441 $sql[] = "LEFT JOIN {$wpdb->term_relationships}";
442 $sql[] = "ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)";
443 }
444 }
445
446 $sql[] = "WHERE 1=1";
447 $sql[] = "AND ( {$wpdb->posts}.post_name IN ( '1' )";
448
449 if( defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO ){
450 $sql[] = "OR {$wpdb->posts}.ID IN ( ";
451 $sql[] = "SELECT {$wpdb->posts}.ID FROM {$wpdb->posts}";
452 $sql[] = "LEFT JOIN {$wpdb->postmeta} ON ( {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id )";
453 $sql[] = "WHERE 1=1";
454 $sql[] = $wpdb->prepare( "AND {$wpdb->postmeta}.meta_key = %s", FLRT_APPLY_BUTTON_META_KEY);
455 $sql[] = "AND {$wpdb->postmeta}.meta_value IN ( '1' )";
456 $sql[] = ")";
457 }
458
459 $sql[] = ")";
460 $sql[] = $wpdb->prepare("AND {$wpdb->posts}.post_type = '%s'", FLRT_FILTERS_SET_POST_TYPE );
461 $sql[] = $wpdb->prepare("AND {$wpdb->posts}.post_excerpt = '%s'", $post_type );
462
463 $sql[] = "AND ( ({$wpdb->posts}.post_status = 'publish') )";
464
465 if( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) && $is_fitler_set_translatable ){
466 $sql[] = $wpdb->prepare("AND wpml_translations.language_code = '%s'", ICL_LANGUAGE_CODE );
467 }
468
469 if( flrt_pll_pro_active() && defined('FLRT_ALLOW_PLL_TRANSLATIONS') && FLRT_ALLOW_PLL_TRANSLATIONS ){
470 if( $pll_lang_id ){
471 $sql[] = $wpdb->prepare("AND {$wpdb->term_relationships}.term_taxonomy_id IN (%d)", $pll_lang_id );
472 }
473 }
474
475 $sql[] = "ORDER BY {$wpdb->posts}.menu_order DESC, {$wpdb->posts}.ID ASC";
476
477 if( ! defined('FLRT_FILTERS_PRO') ) {
478 $sql[] = "LIMIT 0, 1";
479 }
480
481 $sql = implode(' ', $sql );
482 $setPosts = $wpdb->get_results( $sql, OBJECT );
483
484 if( ! empty( $setPosts ) ){
485 $sets = flrt_is_query_on_page( $setPosts, array( '1' ) );
486 }else{
487 return false;
488 }
489
490 $container->storeParam( $key, $sets );
491
492 }
493
494 return $sets;
495 }
496
497 public function validateSets( $sets )
498 {
499 if( ! is_array( $sets ) || empty( $sets ) ){
500 return false;
501 }
502
503 foreach ( $sets as $i => $set ){
504 if( ! isset( $set['ID'] ) || ! $set['ID'] ){
505 return false;
506 }
507 }
508
509 return true;
510 }
511
512 public function preSaveSet( $post_id, $data )
513 {
514 $postData = Container::instance()->getThePost();
515
516 if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
517 return $post_id;
518 }
519
520 if( wp_is_post_revision( $post_id ) ) {
521 return $post_id;
522 }
523
524 if( $data['post_type'] !== FLRT_FILTERS_SET_POST_TYPE ) {
525 return $post_id;
526 }
527
528 $nonce = filter_input( INPUT_POST, '_flrt_nonce' );
529
530 if( ! $this->verifyNonce( $nonce ) ) {
531 return $post_id;
532 }
533
534 if( ! current_user_can( 'manage_options' ) ) {
535 return $post_id;
536 }
537
538 // Do not fire this function twice, on saving Set Fields action
539 remove_action( 'pre_post_update', [$this, 'preSaveSet'], 10 );
540
541 $set_fields_key = self::FIELD_NAME_PREFIX;
542
543 if( isset( $postData[$set_fields_key] ) && ! empty( $postData[$set_fields_key] ) ){
544 $setFields = $postData[$set_fields_key];
545 $setFields['ID'] = $post_id;
546 $setFields['title'] = isset( $data['post_title'] ) ? $data['post_title'] : '';
547
548 $setFields = apply_filters( 'wpc_pre_save_set_fields', $setFields );
549
550 $setFields = $this->sanitizeSetFields( $setFields );
551
552 if( ! $this->validateSetFields( $setFields ) ){
553 flrt_redirect_to_error( $post_id, $this->errors );
554 }
555 }
556
557
558 return $post_id;
559 }
560
561 public function saveSet( $post_id, $post )
562 {
563 $postData = Container::instance()->getThePost();
564 if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
565 return $post_id;
566 }
567
568 if( wp_is_post_revision( $post_id ) ) {
569 return $post_id;
570 }
571
572 if( $post->post_type !== FLRT_FILTERS_SET_POST_TYPE ) {
573 return $post_id;
574 }
575
576 $nonce = filter_input( INPUT_POST, '_flrt_nonce' );
577
578 if( ! $this->verifyNonce( $nonce ) ) {
579 return $post_id;
580 }
581
582 if( ! current_user_can( 'manage_options' ) ) {
583 return $post_id;
584 }
585
586 remove_action( 'save_post', array( $this, 'saveSet' ), 10, 2 );
587
588 $filterFields = $this->getFilterFieldService();
589 $saveFiltersTrigger = true;
590 $allFiltersValid = true;
591
592 // Save filter fields
593 if( isset( $postData['wpc_filter_fields'] ) && ! empty( $postData['wpc_filter_fields'] ) ) {
594
595 // Validate filters
596 if( ! $filterFields->validateFilters( $postData['wpc_filter_fields'] ) ) {
597 $saveFiltersTrigger = false;
598 }
599
600 if( $saveFiltersTrigger ) {
601 $filtersToSave = [];
602
603 // loop
604 $filterConfiguredFields = $filterFields->getFieldsMapping();
605 foreach ($postData['wpc_filter_fields'] as $filterId => $filter) {
606
607 // Set up checkbox fields if they are empty
608 $filter = $filterFields->prepareFilterCheckboxFields($filter, $filterFields->getFieldsByType('checkbox', $filterConfiguredFields));
609
610 // set parent
611 if (!$filter['parent']) {
612 $filter['parent'] = $post_id;
613 }
614
615 $filter = $filterFields->sanitizeFilterFields( $filter );
616 $filtersToSave[ $filterId ] = $filter;
617
618 if ( ! $filterFields->validateTheFilter( $filter, $filterId ) ) {
619 $allFiltersValid = false;
620 break;
621 }
622
623 }
624
625 // Loop to save
626 if( $allFiltersValid ){
627
628 $update_after_save = [];
629 $old_new_ids = [];
630
631 foreach ( $filtersToSave as $filterId => $filter ){
632 // save filter
633 $saved_filter = $filterFields->saveFilter($filter);
634
635 if( isset( $saved_filter['parent_filter'] ) && isset( $saved_filter['ID'] ) ){
636 if( strpos( $saved_filter['parent_filter'], 'filter_', 0 ) !== false ){
637 $update_after_save[ $saved_filter['ID'] ][] = [
638 'key' => 'parent_filter',
639 'value' => $saved_filter['parent_filter']
640 ];
641 }
642 $old_new_ids[$filterId] = $saved_filter['ID'];
643 }
644 }
645
646 // Update data after saving Filters and getting IDs of new ones.
647 if( ! empty( $update_after_save ) ){
648
649 foreach ( $update_after_save as $filter_post_id => $fields_to_update ){
650 $filter_post_data = get_post( $filter_post_id );
651 $filter_data = maybe_unserialize( $filter_post_data->post_content );
652
653 if( ! $filter_data ){
654 continue;
655 }
656
657 foreach ( $fields_to_update as $field_attr ){
658 if( $field_attr['key'] === 'parent_filter' ){
659 if( isset( $filter_data[ 'parent_filter' ] ) ){
660 if( isset( $old_new_ids[ $field_attr['value'] ] ) ){
661 $filter_data[ $field_attr['key'] ] = $old_new_ids[ $field_attr['value'] ];
662 }
663 }
664 }
665 }
666
667 $to_update = array(
668 'ID' => $filter_post_id,
669 'post_content' => maybe_serialize( $filter_data )
670 );
671
672 // Unhook wp_targeted_link_rel() filter from WP 5.1 corrupting serialized data.
673 remove_filter( 'content_save_pre', 'wp_targeted_link_rel' );
674 add_filter( 'pre_wp_unique_post_slug', 'flrt_force_non_unique_slug', 10, 2 );
675
676 // Slash data.
677 // WP expects all data to be slashed and will unslash it (fixes '\' character issues)
678 $to_update = wp_slash( $to_update );
679
680 wp_update_post( $to_update );
681
682 remove_filter( 'pre_wp_unique_post_slug', 'flrt_force_non_unique_slug', 10 );
683 }
684 }
685 }
686 }
687 }
688
689 // Save Set fields
690 $set_fields_key = self::FIELD_NAME_PREFIX;
691
692 if( isset( $postData[$set_fields_key] ) && ! empty( $postData[$set_fields_key] ) ){
693 $setFields = $postData[$set_fields_key];
694 $setFields['ID'] = $post_id;
695 $setFields['title'] = isset( $post->post_title ) ? $post->post_title : '';
696
697 $this->saveSetFields( $setFields );
698 }
699
700 if( ! $saveFiltersTrigger || ! $allFiltersValid ){
701 flrt_redirect_to_error( $post_id, $filterFields->getErrorCodes() );
702 }
703
704 add_action( 'save_post', array( $this, 'saveSet' ), 10, 2 );
705
706 return $post_id;
707 }
708
709
710
711 private function saveSetFields( $setFields ){
712 $post_id = $setFields['ID'];
713
714 $setFields = apply_filters( 'wpc_pre_save_set_fields', $setFields );
715
716 $setFields = $this->sanitizeSetFields( $setFields );
717
718 $setFields = wp_unslash( $setFields );
719
720 // Set up checkbox fields if they are empty
721 $filterFields = $this->getFilterFieldService();
722 /**
723 * @feature It seems we need to move methods 'prepareFilterCheckboxFields' and 'getFieldsByType'
724 * to one level above to parent class
725 */
726 $this->setupDefaultFields();
727 $setFields = $filterFields->prepareFilterCheckboxFields( $setFields, $filterFields->getFieldsByType( 'checkbox', $this->getFieldsMapping()) );
728 $_setFields = $setFields;
729
730 // Remove elements, that shouldn't be serialized
731 flrt_extract_vars( $_setFields, array( 'ID', 'title', 'post_type', 'menu_order', 'post_name', 'wp_filter_query_vars' ) );
732 $menu_order = isset( $setFields['menu_order'] ) ? $setFields['menu_order'] : 0;
733
734 // Create array of data to save.
735 $to_save = array(
736 'ID' => $setFields['ID'],
737 'post_status' => 'publish',
738 'post_type' => FLRT_FILTERS_SET_POST_TYPE,
739 'post_title' => $setFields['title'],
740 'post_content' => maybe_serialize( $_setFields ),
741 'post_excerpt' => $setFields['post_type'],
742 'menu_order' => $menu_order,
743 'post_name' => $setFields['post_name']
744 );
745
746 // Unhook wp_targeted_link_rel() filter from WP 5.1 corrupting serialized data.
747 remove_filter( 'content_save_pre', 'wp_targeted_link_rel' );
748
749 $to_save = wp_slash( $to_save );
750
751 add_filter( 'pre_wp_unique_post_slug', 'flrt_force_non_unique_slug', 10, 2 );
752
753 // Update or Insert.
754 if( $setFields['ID'] ) {
755 wp_update_post( $to_save );
756 } else {
757 $setFields['ID'] = wp_insert_post( $to_save );
758 }
759
760 remove_filter( 'pre_wp_unique_post_slug', 'flrt_force_non_unique_slug', 10 );
761 // Update meta_fields
762
763 update_post_meta( $setFields['ID'], 'wpc_filter_set_post_type', $setFields['post_type'] );
764
765 $set_query_vars = NULL;
766 // Save selected wp_query->query_vars
767 if( isset( $setFields['wp_filter_query'] ) ){
768 $filterQueryHash = $setFields['wp_filter_query'];
769 if( isset( $setFields['wp_filter_query_vars'][$filterQueryHash] ) ){
770 $set_query_vars = $setFields['wp_filter_query_vars'][$filterQueryHash];
771 }
772 }
773
774 update_post_meta( $setFields['ID'], 'wpc_filter_set_query_vars', $set_query_vars );
775
776 if( isset( $setFields['apply_button_post_name'] ) ){
777 update_post_meta( $setFields['ID'], FLRT_APPLY_BUTTON_META_KEY, $setFields['apply_button_post_name'] );
778 }
779
780 return $setFields['ID'];
781 }
782
783 private function sanitizeSetFields( $setFields )
784 {
785 if( is_array( $setFields ) ){
786 $sanitizedFields = [];
787
788 foreach ( $setFields as $key => $setField ) {
789 if( is_array( $setField ) ){
790 $sanitizedValue = $setField;
791 }else{
792 $sanitizedValue = esc_html( $setField );
793 }
794
795 $sanitizedFields[ $key ] = $sanitizedValue;
796 }
797
798 if( isset( $sanitizedFields['menu_order'] ) ){
799 $sanitizedFields['menu_order'] = flrt_sanitize_int( $sanitizedFields['menu_order'] );
800 $sanitizedFields['menu_order'] = $sanitizedFields['menu_order'] ? $sanitizedFields['menu_order'] : 0;
801 }
802
803 return $sanitizedFields;
804 }
805
806 return $setFields;
807 }
808
809 private function prepareSetParameters( $set_post )
810 {
811 /**
812 * @feature this should be not so complex. I'm ashamed of this.
813 */
814 if( ! isset( $set_post->ID ) ){
815 return false;
816 }
817
818 $this->setupDefaultFields();
819 $defaults = $this->getFieldsMapping();
820
821 $defaults = apply_filters( 'wpc_prepare_filter_set_parameters', $defaults, $set_post );
822
823 $unserialized = maybe_unserialize( $set_post->post_content );
824
825 // For backward compatibility. From v.1.1.24
826 if( isset( $unserialized['wp_page_type'] ) ){
827 $unserialized['wp_page_type'] = str_replace(":", "___", $unserialized['wp_page_type']);
828 }
829
830 if( empty( $unserialized ) ){
831 $unserialized = [];
832 }
833
834 foreach( $this->getSpecificFields( 'particular' ) as $key => $field ){
835 $unserialized[$key] = $set_post->{$field['particular']};
836 }
837
838 $populated = $this->populateValues( $unserialized, $defaults );
839 $parsed = $this->parseValues( $populated, $defaults );
840
841 // In case if some settings field was missing
842 $parsed = wp_parse_args( $parsed, $defaults );
843 $parsed = apply_filters( 'wpc_filter_before_make_default_set_values', $parsed );
844
845 // Set default values, if there is no saved
846 foreach( $parsed as $field => $params ){
847
848 if( isset( $params['particular'] ) && ! defined( 'FLRT_FILTERS_PRO' ) && $params['particular'] === 'post_name' ){
849 $parsed[$field]['value'] = $params['default'];
850 }
851
852 if( $field === 'wp_page_type' && ! defined( 'FLRT_FILTERS_PRO' ) ){
853 $parsed[$field]['value'] = $params['default'];
854 }
855
856 if( ! isset( $params['value'] ) && isset( $params['default'] )){
857 $parsed[$field]['value'] = $params['default'];
858 }
859 }
860
861 $parsed['ID'] = $set_post->ID;
862
863 return apply_filters( 'wpc_filter_set_prepared_values', $parsed );
864 }
865
866 private function parseValues( $populated, $defaults )
867 {
868 $parsed = [];
869
870 foreach ( $populated as $field_key => $values_array ){
871 // In case if we have saved field, that is absent in fieldsMapping
872 if( ! isset( $defaults[$field_key] ) ){
873 continue;
874 }
875
876 if( isset( $values_array['value'] ) ){
877 $parsed[$field_key] = wp_parse_args( $values_array, $defaults[$field_key] );
878 }else{
879 $parsed[$field_key] = $this->parseValues( $values_array, $defaults[$field_key] );
880 }
881 }
882
883 return $parsed;
884 }
885
886 private function populateValues( $saved_values )
887 {
888 $transformed = [];
889
890 foreach ( $saved_values as $field_key => $field_value ) {
891 if( is_array( $field_value ) ){
892 $transformed[ $field_key ] = $this->populateValues( $field_value );
893 }else{
894 $transformed[ $field_key ] = array( 'value' => $field_value );
895 }
896 }
897
898 return $transformed;
899 }
900
901 public function getSet( $ID )
902 {
903 $parameters = [];
904
905 if( ! $ID || empty( $ID ) ){
906 return $parameters;
907 }
908
909 $container = Container::instance();
910 $key = 'wpc_set_' . $ID;
911
912 if( ! $set = $container->getParam( $key ) ){
913 $set_post = get_post( $ID );
914 /**
915 * @feature add this post to cache.
916 */
917 $set = $this->prepareSetParameters( $set_post );
918 $container->storeParam( $key, $set );
919 }
920
921 return $set;
922 }
923
924 public function validateSetFields( $setFields ){
925
926 // Validate post_type
927 if( isset( $setFields['post_type'] ) ){
928 $postTypes = array_keys( $this->getPostTypes() );
929 if( ! in_array( $setFields['post_type'], $postTypes, true ) ){
930 $this->errors[] = 21; // Invalid post type
931 return false;
932 }
933 } else {
934 $this->errors[] = 21; // Invalid post type
935 return false;
936 }
937
938 // We have to validate wp_page_type before locations field
939 // because the last one expects valid wp_page_type
940 if( isset( $setFields['wp_page_type'] ) ){
941 $possibleWpPageType = apply_filters( 'wpc_validation_wp_page_type_entities', array('common___common') );
942
943 if( ! in_array( $setFields['wp_page_type'], $possibleWpPageType ) ){
944 $this->errors[] = 211; // Invalid WP Page Type
945 return false;
946 }
947 }else{
948 $this->errors[] = 211; // Invalid WP Page Type
949 return false;
950 }
951
952 // Validate post_name aka location
953 // We can not forbid to save "No WP Queries..." option
954 // Because All archive pages for selected post type may not contain relevant query.
955 if( isset( $setFields['post_name'] ) ){
956 $flatEntities = apply_filters( 'wpc_validation_location_entities', array('1'), $setFields );
957
958 if( ! in_array( $setFields['post_name'], $flatEntities ) ){
959 $this->errors[] = 22; // Invalid location
960 return false;
961 }
962
963 } else {
964 $this->errors[] = 22; // Invalid location
965 return false;
966 }
967
968 //Validate wp_filter_query
969 if( isset( $setFields['wp_filter_query'] ) ){
970 if(
971 ! preg_match('/^[a-f0-9]{32}$/', $setFields['wp_filter_query'] )
972 &&
973 $setFields['wp_filter_query'] !== '-1'
974 ){
975 $this->errors[] = 221; // Invalid WP Filter Query
976 return false;
977 }
978 }else{
979 $this->errors[] = 221; // Invalid WP Filter Query
980 return false;
981 }
982
983 // Validate hide_empty
984 if( isset( $setFields['hide_empty'] ) ){
985 if( ! in_array( $setFields['hide_empty'], array( 'yes', 'no', 'initial' ), true ) ){
986 $this->errors[] = 23; // Invalid empty field
987 return false;
988 }
989 }
990
991 // Validate show_count
992 if( isset( $setFields['show_count'] ) ){
993 if( ! in_array( $setFields['show_count'], array( 'yes', 'no' ), true ) ){
994 $this->errors[] = 24; // Invalid show count
995 return false;
996 }
997 }
998
999 if( isset( $setFields['wp_filter_query_vars'] ) ){
1000 if( ! empty( $setFields['wp_filter_query_vars'] ) ){
1001
1002 foreach ( $setFields['wp_filter_query_vars'] as $query_vars_serialized ){
1003 if( ! is_serialized( $query_vars_serialized ) ){
1004 $this->errors[] = 20; // Common Error
1005 return false;
1006 }
1007 }
1008 }
1009 }
1010
1011 if( isset( $setFields['use_apply_button'] ) ){
1012 if( ! in_array( $setFields['use_apply_button'], array( 'yes', 'no' ), true ) ){
1013 $this->errors[] = 242; // Invalid show count
1014 return false;
1015 }
1016 }
1017
1018 return $setFields;
1019 }
1020
1021 private function getFilterFieldService()
1022 {
1023 return Container::instance()->getFilterFieldsService();
1024 }
1025
1026 public function getPostTypeField( $post_id )
1027 {
1028 $set = $this->getSet( $post_id );
1029 $field['post_type'] = ( $set['post_type'] ) ? $set['post_type'] : NULL;
1030 return $field;
1031 }
1032
1033 public function getSettingsTypeFields( $post_id )
1034 {
1035 $set = $this->getSet( $post_id );
1036 $settings_fields_map = $this->getSpecificFields('settings');
1037
1038 return flrt_extract_vars($set, array_keys( $settings_fields_map ) );
1039 }
1040
1041 public function generateFieldName( $field_name, $sub_name = '', $index = 0 ){
1042 $attr = self::FIELD_NAME_PREFIX . '['.$field_name.']';
1043 if( $sub_name ){
1044 $attr .= '['.$index.']['.$sub_name.']';
1045 }
1046 return $attr;
1047 }
1048
1049 public function generateFieldId( $field_name, $sub_name = '', $index = 0 ){
1050 $attr = self::FIELD_NAME_PREFIX . '-' . $field_name;
1051 if( $sub_name ){
1052 $attr .= '-' . $sub_name . '-' . $index;
1053 }
1054 return $attr;
1055 }
1056
1057 public static function createNonce()
1058 {
1059 return wp_create_nonce( self::NONCE_ACTION );
1060 }
1061
1062 private function verifyNonce( $nonce )
1063 {
1064 return wp_verify_nonce( $nonce, self::NONCE_ACTION );
1065 }
1066 }