14 hours ago
<?php
/**
* this is a sample snippet to apply relevanssi click tracking to links
* within the results of a facetwp listing builder template
* on a page with a search facet set to the relevanssi search engine
*/
/**
* hijack facetwp_template_html to modify links within template
* to add relevanssi click tracking
*/
add_filter( 'facetwp_template_html', function( $return, $renderer ) {
if ( 'my_listing' == $renderer->template["name"] ) { // change 'my_listing' to name of your listing
/** there are different filters for get_post type depending on type of posts
* unneeded filters can be removed if they are not included in the search results
**/
add_filter( 'post_link', 'fwp_add_rel_tracking', 10, 2 ); // use for posts
add_filter( 'page_link', 'fwp_add_rel_tracking', 10, 2 ); // use for pages
add_filter( 'post_type_link', 'fwp_add_rel_tracking', 10, 2 ); // use for custom post types
// this fakes a get_search_query string which doesn't get created from a search facet
add_filter( 'get_search_query', function( $keywords ) {
return FWP()->facet->facets["my_search"]["selected_values"]; // change "my_search" to match the name of your search facet
});
}
return $return; // eturn
}, 10, 2);
/**
* hijack facetwp_render output filter to remove relevanssi click tracking
* after facetwp template has completed output
*/
add_filter( 'facetwp_render_output', function( $output, $params ) {
remove_filter( 'post_link', 'fwp_add_rel_tracking', 10 ); // use for posts
remove_filter( 'page_link', 'fwp_add_rel_tracking', 10 ); // use for pages
remove_filter( 'post_type_link', 'fwp_add_rel_tracking', 10 ); // use for custom post types
return $output; // return
}, 10, 2);
/**
* function to hook on get_permalink filters to add relevanssi
* click tracking
*/
function fwp_add_rel_tracking( $link, $post ) {
if ( function_exists( 'relevanssi_add_tracking') ) { // make sure the relevanssi_add_tracking exists
$link = relevanssi_add_tracking( $link );
}
return $link;
}
2 days ago
<?php
// See: https://facetwp.com/help-center/developers/hooks/output-hooks/facetwp_facet_html/#translate-facet-choices
add_filter( 'facetwp_facet_html', function( $output, $params ) {
if ( 'my_facet_name' == $params['facet']['name'] ) { // Replace "my_facet_name" with the name of your facet
// For WPML:
$current = ( !empty( FWP()->facet->http_params['lang'] ) ) ? FWP()->facet->http_params['lang'] : apply_filters( 'wpml_current_language', null );
$default = apply_filters('wpml_default_language', NULL );
// For Polylang, use these 2 lines instead of 2 above
//$current = ( !empty( FWP()->facet->http_params['lang'] ) ) ? FWP()->facet->http_params['lang'] : pll_current_language();
//$default = pll_default_language();
$replace_values = [];
$replace_values['nl'] = [
'Belgium' => 'België',
'Bulgaria' => 'Bulgarije'
];
if ( $current != $default && !empty( $replace_values[$current] ) ) {
$default_values = [];
$new_values = [];
foreach ( $replace_values[$current] AS $replace_value => $replacement ) {
$default_values[] = $replace_value;
$new_values[] = $replacement;
}
$output = str_replace( $default_values, $new_values, $output );
}
}
return $output;
}, 10, 2 );
2 months ago
<?php
/**
** disables new template enahncement buffer in WP 6.9 during facet's ajax refresh
** to disable WP's buffer completely, you can use
** add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_false' );
**/
add_filter( 'wp_should_output_buffer_template_for_enhancement', function( $should ) {
if ( function_exists( 'FWP' ) && FWP()->request && FWP()->request->is_refresh ) {
return false;
}
return $should;
} );
3 months ago
<?php
// Query arguments ready to use in a Listing Builder lising
// Orders 'courses' posts by a future date in the 'offer_date_1' ACF Date Picker field, with the earliest date first (ASC),
// then showing posts without the field or without a date in the field, at the bottom.
// Includes a 'offer_date_query' flag to be used in the 'posts_clauses' function below,
// which is needed to show posts with a date before the posts without one.
// If you're using an ACF Date Time Picker field instead of a Date Picker field, see the second example here:
// https://facetwp.com/help-center/using-facetwp-with/advanced-custom-fields/#order-a-listing-by-a-date-time-picker-field
return [
'post_type' => [
'courses'
],
'post_status' => [
'publish'
],
'posts_per_page' => 50,
'offer_date_query' => true, // Needed for identifying in 'posts_clauses' hook
// Order by date and fallback order
'meta_key' => 'offer_date_1',
'orderby' => array(
'meta_value_num' => 'ASC', // ASC = Smallest number (earliest date) first
'date' => 'DESC', // Fallback order if offer_date_1 the same or not exists: post_date (newest first). Adapt as needed.
),
// Only retrieve posts with a date in the future or no date at all
'meta_query' => array(
'relation' => 'OR', // Allow multiple conditions
// Get posts with a future date
array(
'key' => 'offer_date_1',
'value' => current_time('Ymd'),
'compare' => '>',
'type' => 'NUMERIC',
),
// Get posts with no date field
array(
'key' => 'offer_date_1',
'compare' => 'NOT EXISTS', // Posts without this field
),
// Get posts with no date value
array(
'key' => 'offer_date_1',
'value' => '',
'compare' => '=',
),
),
];
// Get the 'offer_date_query' query above to order
// posts with an date first, the rest after
// Add this to your functions.php
add_filter( 'posts_clauses', function( $clauses, $query ) {
if ( ! $query->get( 'offer_date_query' ) ) { // the flag in the query arguments above
return $clauses;
}
global $wpdb;
// Join postmeta explicitly to have a stable alias for ordering
// If you already have a JOIN for offer_date_1 via meta_query,
// this will effectively be a no-op because of identical JOIN.
$clauses['join'] .= $wpdb->prepare(
" LEFT JOIN {$wpdb->postmeta} AS od1
ON od1.post_id = {$wpdb->posts}.ID
AND od1.meta_key = %s",
'offer_date_1'
);
// 1) CASE: posts *with* non-empty offer_date_1 get sort key 0
// posts *without* (NULL or '') get sort key 1
// 2) Then order by the numeric value of offer_date_1 ascending
// 3) Finally, fallback to post_date DESC
$clauses['orderby'] = "
CASE
WHEN od1.meta_value IS NOT NULL AND od1.meta_value != '' THEN 0
ELSE 1
END ASC,
CAST(od1.meta_value AS UNSIGNED) ASC,
{$wpdb->posts}.post_date DESC
";
return $clauses;
}, 10, 2 );
3 months ago
<?php
/**
** change version of gmaps to load
** https://developers.google.com/maps/documentation/javascript/versions
** See: https://facetwp.com/help-center/facets/facet-types/map/advanced-map-customizations/#set-the-maps-javascript-api-version-to-load
**/
add_filter( 'facetwp_gmaps_params', function( $params ) {
$params['v'] = 'weekly';
return $params;
});
2 months ago
<?php
/**
** NOTE: This issue was FIXED in Create v1.10.2. The snippet below is only needed in older versions.
** See: https://wordpress.org/support/topic/vue-errors-in-other-plugins-facetwp/
** The Create plugin by Mediavine plugin v1.10.1 and earlier was enqueueing its JS on all admin pages.
** This snippet blocks its JS from FacetWP's settings page
** Create was setting a Vue JS object that conflicted with FacetWP's and causes these Console errors:
** 'Vue.config is undefined' in Firefox and
** 'Cannot set properties of undefined (setting 'devtools')' in Chrome
**/
add_action( 'admin_enqueue_scripts', function( $hook ) {
// block mediavine create script on FacetWP's settings page
if ( 'settings_page_facetwp' == $hook ) {
wp_dequeue_script( 'mv_create-script' );
}
}, 12 );
3 months ago
<?php
// Re-initialize Ays Pro Chartify charts used in a listing template, after a FacetWP refresh
// https://wordpress.org/plugins/chart-builder/
// https://ays-pro.com/wordpress/chart-builder
// Note: this only works in WP archive or custom WP_Query based templates, NOT in FacetWP Listing Builder listings. There is no solution for those.
// This solution works for both JS and Google charts
add_action('facetwp_scripts', function() {
?>
<script>
(function($) {
if (typeof FWP !== 'undefined') {
// Note: this only works in WP archive or custom WP_Query templates, NOT in FacetWP Listing Builder listings,
// because in those, the respons is not the whole body (only the listing content) and thus it has no access to
// the updated #chart-builder-chart-js-js-extra script, unfortunately
FWP.hooks.addFilter('facetwp/template_html', function(resp, params) {
// Create a temporary div to parse the new HTML response
let tempContainer = document.createElement('div');
tempContainer.innerHTML = params.response.template;
// Find the NEW script element from the AJAX response
// target #chart-builder-chart-js-js-extra or #chart-builder-charts-google-js-extra but NOT #chart-builder-chart-js-js and #chart-builder-charts-google-js
const newChartDataElements = tempContainer.querySelectorAll('#chart-builder-chart-js-js-extra, #chart-builder-charts-google-js-extra');
// also include #chart-builder-inline-css which contains chart specific CSS
const newChartCssElements = tempContainer.querySelectorAll('#chart-builder-inline-css');
if (newChartDataElements) {
newChartDataElements.forEach(function(newScript) {
const oldChartDataElements = document.getElementById(newScript.id);
// 1. Remove the old script element from the DOM
if (oldChartDataElements) {
oldChartDataElements.remove();
}
// 2. Create a brand new script element.
// Crucial because browsers execute scripts when they are appended
// as NEWLY created elements, not inserted via innerHTML or when using replaceWith(),
// which will cause TypeError: _this.dbData is undefined
const newScriptElement = document.createElement('script');
newScriptElement.id = newScript.id
// 4. Copy the JavaScript content (the variable definitions)
newScriptElement.textContent = newScript.textContent;
// 5. Append the newly created script element to the document body.
// This will trigger parsing and executing the script contents,
// thus defining the new 'aysChartOptions' variables globally.
document.body.appendChild(newScriptElement);
});
}
if (newChartCssElements) {
newChartCssElements.forEach(function(newCss) {
const oldChartCssElements = document.getElementById(newCss.id);
// 1. Remove the old style element from the DOM
if (oldChartCssElements) {
oldChartCssElements.remove();
}
// 2. Create a new style element.
const newInlineCSS = document.createElement('style');
newInlineCSS.id = newCss.id
// 4. Copy the CSS content
newInlineCSS.textContent = newCss.textContent;
// 5. Append the newly created style element to the document body.
document.body.appendChild(newInlineCSS);
});
}
return resp;
}, 1 );
// Retrigger the charts after a facetwp ajax refresh
FWP.hooks.addAction('facetwp/loaded', function() {
if ( FWP.loaded ) { // run only on refresh after first page load
// Reinitialize JS charts
if (typeof $.fn.ChartBuilderChartJsMain === 'function') {
$('.ays-chart-container-chartjs').each(function() {
try {
$(this).ChartBuilderChartJsMain();
} catch (e) {
console.warn('Chart initialization error:', e);
}
});
}
// Reinitialize Google charts
if (typeof $.fn.ChartBuilderGoogleChartsMain === 'function') {
$('.ays-chart-container-google').each(function() {
try {
$(this).ChartBuilderGoogleChartsMain();
} catch (e) {
console.warn('Chart initialization error:', e);
}
});
}
}
}, 100);
}
})(jQuery);
</script>
<?php
}, 100);
3 months ago
<?php
// Allow FacetWP to detect Salient's Visual Composer Post Loop Builder
// Needs the 'facetwp-template' class set on the Post Loop Builder element: https://d.pr/i/x4j75G
// Disable the element's own (Load more) pagination: https://d.pr/i/4ZumQ0
// Use a Pager facet instead (numbered or load more type).
add_filter( 'nectar_post_grid_query', function( $query_args ) {
$post_types = ['resource', 'post']; // Run only for Post Loop Builder grids with these post types
if ( in_array( $query_args['post_type'], $post_types ) ) {
// Detect Post Loop Grid query
$query_args['facetwp'] = true;
// Fix pagination
if ( array_key_exists('offset', $query_args ) ) {
unset( $query_args['offset'] );
}
}
return $query_args;
}, 10 );
4 months ago
<?php
// Scenario: an apply/submit/refresh button is clicked (in this case the Number Range facet's Go button)
// On click, the Proximity facet should use the first item in the dropdown if any text is entered in the input field,
// but not if the user actually made a selection (by clicking an item in the dropdown or using Enter/Return).
// This involves killing the original click event on the button first. Then detecting any selection in the Proximity.
// If there is no selection, trigger a click on the first dropdown item so it is selected, and perform the original refresh.
add_action( 'wp_head', function() {
?>
<script>
// This needs to run as early as possible, in the head
document.addEventListener('DOMContentLoaded', function() {
// First we need to kill the existing click event on the Number Range button
const selectorToIntercept = '.facetwp-type-number_range .facetwp-submit';
document.addEventListener('click', function(event) {
const clickedButton = event.target.closest(selectorToIntercept);
if (clickedButton) {
event.preventDefault();
event.stopImmediatePropagation();
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
// Then, if there is text in the Proximity facet, trigger the first item,
// if the facet is not already having a selection
const proximityFacet = document.querySelector('.facetwp-type-proximity');
const firstLocation= document.querySelector('.location-result:first-child');
if( firstLocation && proximityFacet.classList.contains('not-active')) {
firstLocation.dispatchEvent(clickEvent);
}
// Then refresh again
if ('undefined' !== typeof FWP) {
FWP.refresh();
}
}
}, true);
});
</script>
<?php
}, 100 );
4 months ago
<?php
// To exclude custom fields from FacetWP's get_data_sources() function, used in the admin area, you can use
// the 'facetwp_excluded_custom_fields' filter.
// To do it by partial match, e.g. for fields with a hashed part of the name,
// you can use the 'facetwp_excluded_custom_fields_like' filter,
// available in FacetWP 4.5+ :
add_filter( 'facetwp_excluded_custom_fields_like', function( $not_like ) {
$not_like[] = '_crp_cache_'; // exclude cache rows added by Contextual Related Posts plugin
return $not_like;
});