5 hours 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;
});
4 months ago
<?php
// Add this to the Listing Builder listing's Query tab in Dev mode
// Situation: 'enti' post type posts that have an ACF Relationshipship field that relates them with 'progetti' posts
// The 'progetti' posts have a custom field 'disattiva_dol' which is an ACF true/false field.
// The generated query arguments retrieve 'enti' post type posts
// that have a related 'progetti' post for which the 'disattiva_dol' custom field is false
// Step 1: Get the IDs of "project" posts where disattiva_dol is false.
$valid_project_ids_args = array(
'post_type' => 'progetti',
'posts_per_page' => -1, // Retrieve all matching projects
'fields' => 'ids', // Return only post IDs for efficiency
'meta_query' => array(
array(
'key' => 'disattiva_dol', // The ACF true/false field on 'project' posts
'value' => 0, // ACF stores 'false' as 0 (for true/false fields)
'compare' => '=',
'type' => 'NUMERIC', // Ensure numeric comparison
),
),
);
$valid_project_ids = get_posts( $valid_project_ids_args );
// Step 2: Create the WP_Query arguments for "enti" posts based on the valid_project_ids.
$enti_query_args = array(
'post_type' => 'enti',
'posts_per_page' => -1, // Or your desired number of posts
'post_status' => 'publish', // Only published 'enti' posts
);
if ( ! empty( $valid_project_ids ) ) {
$project_relationship_meta_query = array(
'relation' => 'OR', // An 'enti' post can be related to ANY of the valid projects
);
foreach ( $valid_project_ids as $project_id ) {
// Relationship field stores a single ID (numeric comparison)
$project_relationship_meta_query[] = array(
'key' => 'progetti_oggetto', // Replace with the actual name of your ACF Relationship field on 'enti' posts
'value' => $project_id,
'compare' => '=',
'type' => 'NUMERIC',
);
}
$enti_query_args['meta_query'] = $project_relationship_meta_query;
}
// For checking the resulting args, remove when it works
// echo '<pre>'; var_dump($enti_query_args); echo '</pre>';
return $enti_query_args;