| 1 |
<?php |
| 2 |
|
| 3 |
namespace FilterEverything\Filter; |
| 4 |
|
| 5 |
if ( ! defined('ABSPATH') ) { |
| 6 |
exit; |
| 7 |
} |
| 8 |
|
| 9 |
class SettingsTab extends BaseSettings |
| 10 |
{ |
| 11 |
protected $page = 'wpc-filter-admin-settings'; |
| 12 |
|
| 13 |
protected $group = 'wpc_filter'; |
| 14 |
|
| 15 |
protected $optionName = 'wpc_filter_settings'; |
| 16 |
|
| 17 |
public function init() |
| 18 |
{ |
| 19 |
add_action('admin_init', array($this, 'initSettings')); |
| 20 |
} |
| 21 |
|
| 22 |
public function initSettings() |
| 23 |
{ |
| 24 |
register_setting($this->group, $this->optionName); |
| 25 |
/** |
| 26 |
* @see https://developer.wordpress.org/reference/functions/add_settings_field/ |
| 27 |
*/ |
| 28 |
$defaultPostsContainer = flrt_default_posts_container(); |
| 29 |
$defaultPrimaryColor = flrt_default_theme_color(); |
| 30 |
|
| 31 |
$settings = array( |
| 32 |
'mobile_devices' => array( |
| 33 |
'label' => esc_html__('Mobile devices', 'filter-everything'), |
| 34 |
'fields' => array( |
| 35 |
'mobile_filter_settings' => array( |
| 36 |
'type' => 'select', |
| 37 |
'title' => esc_html__('Filters widget on mobile should be', 'filter-everything'), |
| 38 |
'options' => array( |
| 39 |
'nothing' => esc_html__('The same as on a desktop', 'filter-everything'), |
| 40 |
'show_open_close_button' => esc_html__('Collapsed and expanded', 'filter-everything'), |
| 41 |
), |
| 42 |
'default' => 'no', |
| 43 |
'id' => 'mobile_filter_settings', |
| 44 |
), |
| 45 |
/*'show_open_close_button' => array( |
| 46 |
'type' => 'checkbox', |
| 47 |
'title' => esc_html__('Collapse Filters Widget on Mobile devices', 'filter-everything'), |
| 48 |
'id' => 'show_open_close_button', |
| 49 |
'label' => esc_html__('Collapse the widget and show the Filters opening button', 'filter-everything'), |
| 50 |
),*/ |
| 51 |
'try_move_to_top_sidebar' => array( |
| 52 |
'type' => 'checkbox', |
| 53 |
'title' => esc_html__('Sidebar on top', 'filter-everything'), |
| 54 |
'id' => 'try_move_to_top_sidebar', |
| 55 |
'label' => esc_html__('Try to move the sidebar to the top on mobile devices', 'filter-everything'), |
| 56 |
) |
| 57 |
), |
| 58 |
), |
| 59 |
'ajax' => array( |
| 60 |
'label' => esc_html__('AJAX', 'filter-everything'), |
| 61 |
'fields' => array( |
| 62 |
'enable_ajax' => array( |
| 63 |
'type' => 'checkbox', |
| 64 |
'title' => esc_html__('AJAX for Filters', 'filter-everything'), |
| 65 |
'id' => 'enable_ajax', |
| 66 |
'label' => esc_html__('Try to use AJAX', 'filter-everything'), |
| 67 |
'description' => esc_html__( 'Please enable this option only after you have ensured that the filtering is working correctly', 'filter-everything' ), |
| 68 |
), |
| 69 |
'posts_container' => array( |
| 70 |
'type' => 'text', |
| 71 |
'title' => esc_html__('Results container', 'filter-everything'), |
| 72 |
'id' => 'posts_container', |
| 73 |
'default' => $defaultPostsContainer, |
| 74 |
'description' => esc_html__( 'e.g. #primary or .main-content', 'filter-everything' ), |
| 75 |
'label' => '', |
| 76 |
'tooltip' => wp_kses( |
| 77 |
__( 'The part of the page where your posts or products are listed. When AJAX is enabled, the plugin refreshes only this area after each filter click, without reloading the whole page.<br /><br />Click «Select visually» and simply click the area with your posts, or enter its CSS id or class if you know it.', 'filter-everything' ), |
| 78 |
array( 'br' => array() ) |
| 79 |
), |
| 80 |
'additional_link' => [ |
| 81 |
'label' => esc_html__( 'Select visually', 'filter-everything' ), |
| 82 |
'url' => add_query_arg( |
| 83 |
[ |
| 84 |
'flrt_get_html_selector' => 1, |
| 85 |
'flrt_set_id' => 'global_posts_container', |
| 86 |
], |
| 87 |
flrt_default_selector_page_link() |
| 88 |
), |
| 89 |
], |
| 90 |
), |
| 91 |
'apply_button_instant_recount' => array( |
| 92 |
'type' => (defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO) ? 'checkbox' : 'inProButton', |
| 93 |
'pro_label' => (defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO) ? '' : flrt_pro_promo_label(), |
| 94 |
'title' => esc_html__('Instant recount for the «Apply Button» mode', 'filter-everything'), |
| 95 |
'id' => 'apply_button_instant_recount', |
| 96 |
'label' => esc_html__('Instantly recalculate filter counters in the browser', 'filter-everything'), |
| 97 |
'description' => (defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO) |
| 98 |
? esc_html__( 'When disabled, Filter Sets in the «Apply Button» mode recalculate counters on the server with an AJAX request on every filter click.', 'filter-everything' ) |
| 99 |
// Free: promo wording — sells what the PRO option does |
| 100 |
: esc_html__( 'When enabled, Filter Sets in the «Apply Button» mode recalculate counters instantly in the browser, without an AJAX request on every filter click.', 'filter-everything' ), |
| 101 |
'tooltip' => wp_kses( |
| 102 |
__( 'This option greatly speeds up filtering, but the recount runs in the visitor\'s browser and may be heavy on sites with a large amount of content. If you filter more than 30,000 posts, make sure filtering does not freeze — especially on mobile devices.', 'filter-everything' ), |
| 103 |
array( 'br' => array() ) |
| 104 |
), |
| 105 |
) |
| 106 |
) |
| 107 |
), |
| 108 |
'crawlers' => array( |
| 109 |
'label' => esc_html__('Crawlers and bots', 'filter-everything'), |
| 110 |
'fields' => $this->crawlerFields(), |
| 111 |
), |
| 112 |
'common_settings' => array( |
| 113 |
'label' => esc_html__('Other', 'filter-everything'), |
| 114 |
'fields' => array( |
| 115 |
'primary_color' => array( |
| 116 |
'type' => 'text', |
| 117 |
'title' => esc_html__('Widget Primary Color', 'filter-everything'), |
| 118 |
'id' => 'wpc_primary_color', |
| 119 |
'default' => $defaultPrimaryColor, |
| 120 |
'label' => '', |
| 121 |
), |
| 122 |
'container_height' => array( |
| 123 |
'type' => 'text', |
| 124 |
'title' => esc_html__('Filter Container max height, px', 'filter-everything'), |
| 125 |
'id' => 'container_height', |
| 126 |
'label' => '', |
| 127 |
), |
| 128 |
'show_terms_in_content' => array( |
| 129 |
'type' => 'select', |
| 130 |
'title' => esc_html__('Selected Filters (Chips) integration', 'filter-everything'), |
| 131 |
'id' => 'show_terms_in_content', |
| 132 |
'label' => esc_html__('Try to show selected terms above the Results container', 'filter-everything'), |
| 133 |
'options' => array(), |
| 134 |
'multiple' => true, |
| 135 |
'description' => esc_html__( 'Select where to show Chips on your site. Or enter your theme\'s hooks. For example: before_main_content', 'filter-everything' ) |
| 136 |
), |
| 137 |
'widget_debug_messages' => array( |
| 138 |
'type' => 'checkbox', |
| 139 |
'title' => esc_html__('Debug mode', 'filter-everything'), |
| 140 |
'id' => 'widget_debug_messages', |
| 141 |
'label' => esc_html__('Enable debugging messages to help to configure filters', 'filter-everything'), |
| 142 |
) |
| 143 |
) |
| 144 |
) |
| 145 |
); |
| 146 |
|
| 147 |
if (!defined('FLRT_FILTERS_PRO')) { |
| 148 |
$settings['mobile_devices']['fields']['mobile_filter_settings']['options'][''] = esc_html__('Appear as a Pop-up', 'filter-everything') . ' — ' . esc_html__('Available in PRO', 'filter-everything'); |
| 149 |
$settings['mobile_devices']['fields']['mobile_filter_settings']['disabled'][] = ''; |
| 150 |
} |
| 151 |
|
| 152 |
$settings = apply_filters('wpc_general_filters_settings', $settings); |
| 153 |
|
| 154 |
$this->registerSettings($settings, $this->page, $this->optionName); |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* «Crawlers and bots» section. |
| 159 |
* |
| 160 |
* Hiding the filter links is available in both builds since 1.9.6: in free every |
| 161 |
* filter link becomes a <span> (filter pages are noindex there by design, so the |
| 162 |
* links only feed crawler traps); PRO keeps a real <a> for the targets its SEO |
| 163 |
* Rules index (smart spans). The robots.txt helper (both builds; the PRO rule |
| 164 |
* set names only never-indexable URL classes) follows |
| 165 |
* flrt_robots_helper_available() — see src/RobotsTxt.php. |
| 166 |
*/ |
| 167 |
protected function crawlerFields() |
| 168 |
{ |
| 169 |
$is_pro = defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO; |
| 170 |
|
| 171 |
$fields = array( |
| 172 |
'disable_filter_links_for_bots' => array( |
| 173 |
'type' => 'checkbox', |
| 174 |
'title' => esc_html__('Disable filter links for crawlers', 'filter-everything'), |
| 175 |
'id' => 'disable_filter_links_for_bots', |
| 176 |
'description' => esc_html__( 'Replaces <a> tags with <span> in filters to prevent crawlers from following filter links and overloading your site.', 'filter-everything' ), |
| 177 |
'tooltip' => $is_pro |
| 178 |
? wp_kses( |
| 179 |
__( 'Filter combinations produce thousands of URLs, and crawlers may waste your crawl budget and server resources trying to follow them all. This option renders filter links as <span> tags — they keep working for visitors, but crawlers no longer see them as links.<br /><br />Links to pages that your SEO Rules define as indexed always keep the real <a> tag, so search engines can still discover and rank those pages.<br /><br />You can also block unwanted pages and specific bots in the robots.txt file.', 'filter-everything' ), |
| 180 |
array( 'br' => array() ) |
| 181 |
) |
| 182 |
: wp_kses( |
| 183 |
__( 'Filter combinations produce thousands of URLs, and crawlers may waste your crawl budget and server resources trying to follow them all. This option renders filter links as <span> tags — they keep working for visitors, but crawlers no longer see them as links.<br /><br />Filtering result pages are marked noindex in the free version anyway, so hiding the links costs nothing in search visibility. The PRO version keeps the real <a> tag for the filter pages that its SEO Rules make indexable.<br /><br />Crawlers that already know your filter URLs may keep requesting them for a while — the robots.txt rules below stop that too.', 'filter-everything' ), |
| 184 |
array( 'br' => array() ) |
| 185 |
), |
| 186 |
), |
| 187 |
); |
| 188 |
|
| 189 |
if ( function_exists( 'flrt_robots_helper_available' ) && flrt_robots_helper_available() ) { |
| 190 |
$fields['robots_txt_block_filters'] = array( |
| 191 |
'type' => 'checkbox', |
| 192 |
'title' => esc_html__('Block filter URLs in robots.txt', 'filter-everything'), |
| 193 |
'id' => 'robots_txt_block_filters', |
| 194 |
'label' => esc_html__('Add Disallow rules for all filter URLs to the robots.txt file', 'filter-everything'), |
| 195 |
'description' => esc_html__( 'Tells search engines and other well-behaved crawlers not to request filtering result pages at all. Bots that ignore robots.txt are not affected.', 'filter-everything' ), |
| 196 |
'tooltip' => $is_pro |
| 197 |
? wp_kses( |
| 198 |
__( 'Hiding the filter links stops crawlers from discovering new filter URLs, but the URLs they already know stay in their queues for weeks or months. A Disallow rule in robots.txt is the only signal that also stops compliant crawlers — Google, Bing and most AI bots — from requesting those pages at all, which frees both your server and your crawl budget.<br /><br />With pretty permalinks the rules name only the filter URLs that are never indexable: several values of one filter (…/color-red-or-blue/), more filters in one URL than your Indexing Depth allows, and numeric or date ranges. Single-filter pages stay crawlable, so the pages your SEO Rules index are never touched. Review the list before enabling: a product or category slug that starts with one of your filter prefixes would match the same pattern.<br /><br />When your site has a physical robots.txt file, WordPress cannot add the rules for you — copy them into that file yourself.', 'filter-everything' ), |
| 199 |
array( 'br' => array() ) |
| 200 |
) |
| 201 |
: wp_kses( |
| 202 |
__( 'Hiding the filter links stops crawlers from discovering new filter URLs, but the URLs they already know stay in their queues for weeks or months. A Disallow rule in robots.txt is the only signal that also stops compliant crawlers — Google, Bing and most AI bots — from requesting those pages at all, which frees both your server and your crawl budget.<br /><br />The rules are generated from your filters and stay in sync automatically. When your site has a physical robots.txt file, WordPress cannot add them for you — copy the rules shown below into that file yourself.', 'filter-everything' ), |
| 203 |
array( 'br' => array() ) |
| 204 |
), |
| 205 |
); |
| 206 |
$fields['robots_txt_rules'] = array( |
| 207 |
'type' => 'html', |
| 208 |
'title' => esc_html__('Rules for robots.txt', 'filter-everything'), |
| 209 |
'id' => 'robots_txt_rules', |
| 210 |
'render' => array( $this, 'renderRobotsRules' ), |
| 211 |
); |
| 212 |
} |
| 213 |
|
| 214 |
return $fields; |
| 215 |
} |
| 216 |
|
| 217 |
/** |
| 218 |
* The generated robots.txt block plus a status line saying whether WordPress |
| 219 |
* adds it automatically (virtual robots.txt) or the user has to paste it. |
| 220 |
*/ |
| 221 |
public function renderRobotsRules( $args ) |
| 222 |
{ |
| 223 |
$rules = function_exists( 'flrt_robots_txt_rules' ) ? flrt_robots_txt_rules() : ''; |
| 224 |
|
| 225 |
if ( $rules === '' ) { |
| 226 |
printf( |
| 227 |
'<p class="description">%s</p>', |
| 228 |
esc_html__( 'No filters are configured yet — the rules will appear here as soon as you create a Filter Set.', 'filter-everything' ) |
| 229 |
); |
| 230 |
return; |
| 231 |
} |
| 232 |
|
| 233 |
$rows = min( 20, substr_count( $rules, "\n" ) + 2 ); |
| 234 |
|
| 235 |
printf( |
| 236 |
'<textarea class="large-text code" rows="%d" id="%s" readonly onclick="this.select()">%s</textarea>', |
| 237 |
(int) $rows, |
| 238 |
esc_attr( $args['id'] ), |
| 239 |
esc_textarea( $rules ) |
| 240 |
); |
| 241 |
|
| 242 |
if ( flrt_robots_txt_is_physical() ) { |
| 243 |
printf( |
| 244 |
'<p class="wpc-warning">%s</p>', |
| 245 |
esc_html__( 'Your site has a physical robots.txt file, so WordPress cannot add these rules automatically. Copy them into that file yourself.', 'filter-everything' ) |
| 246 |
); |
| 247 |
} elseif ( ! get_option( 'permalink_structure' ) ) { |
| 248 |
printf( |
| 249 |
'<p class="wpc-warning">%s</p>', |
| 250 |
esc_html__( 'WordPress serves its robots.txt only when pretty permalinks are enabled (Settings → Permalinks). Until then, copy these rules into a physical robots.txt file.', 'filter-everything' ) |
| 251 |
); |
| 252 |
} elseif ( flrt_get_option( 'robots_txt_block_filters' ) === 'on' ) { |
| 253 |
printf( |
| 254 |
'<p class="description">%s</p>', |
| 255 |
sprintf( |
| 256 |
/* translators: %s: link to the site's robots.txt */ |
| 257 |
esc_html__( 'These rules are added to %s automatically.', 'filter-everything' ), |
| 258 |
'<a href="' . esc_url( home_url( '/robots.txt' ) ) . '" target="_blank" rel="noopener">robots.txt</a>' |
| 259 |
) |
| 260 |
); |
| 261 |
} else { |
| 262 |
printf( |
| 263 |
'<p class="description">%s</p>', |
| 264 |
esc_html__( 'Enable the option above to add these rules to robots.txt automatically, or copy them into your own robots.txt file.', 'filter-everything' ) |
| 265 |
); |
| 266 |
} |
| 267 |
} |
| 268 |
|
| 269 |
public function getLabel() |
| 270 |
{ |
| 271 |
return esc_html__('General', 'filter-everything'); |
| 272 |
} |
| 273 |
|
| 274 |
public function getName() |
| 275 |
{ |
| 276 |
return 'settings'; |
| 277 |
} |
| 278 |
|
| 279 |
public function valid() |
| 280 |
{ |
| 281 |
return true; |
| 282 |
} |
| 283 |
} |
| 284 |
|
| 285 |
|