1 week 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 );
1 week 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 weeks ago
<?php
/**
** Create by Mediavine plugin is enqueueing everywhere in admin
** this blocks it from FacetWP's settings page
** Mediavine is setting a Vue js object that conflicts
** 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 weeks 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 weeks 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 );
1 month 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 );
1 month 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;
});
2 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;
2 months ago
<?php
/**
* prevent default map loading
**/
add_filter( 'facetwp_load_gmaps', '__return_false' );
/**
* load gmaps script for cmplz
*/
add_filter( 'cmplz_added_scripts', function( $added_scripts ) {
$params = [
'key' => FWP()->display->get_gmaps_api_key(),
'v' => 'quarterly',
];
$params_string = '';
foreach ( apply_filters( 'facetwp_gmaps_params', $params ) AS $param => $val ) {
$params_string .= $param . ': "' . $val . '",';
}
$script = '(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. FacetWP\'s instance is ignored."):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({';
$script .= $params_string;
$script .= '});';
$added_scripts[] = [
'name' => 'FacetWP',
'category' => 'marketing',
'editor' => $script,
'async' => 0
];
return $added_scripts;
});
/**
* Init map after cookies accepted
*
*/
add_action('wp_enqueue_scripts', function()
{
ob_start();
?>
<script>
/**
* Make functions await for 'ms' milliseconds
* https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep
*
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Wait for 'facetwp-map' to be initialized
*
*/
const checkIfMapIsInitialized = () => {
const map = document.getElementById('facetwp-map');
return map.children.length > 0 && FWP_MAP.markersArray.length > 0;
}
/**
* Wait for 'FWP', 'FWP_MAP' and 'FWP_MAP.map' to be set
*
*/
const checkIfFWPExists = async () => {
if (typeof FWP !== 'undefined' && typeof FWP_MAP !== 'undefined' && typeof FWP_MAP.map !== 'undefined') {
return true;
}
await sleep(500);
checkIfFWPExists();
}
/**
* Reinitialize FacetWP Google Maps' map
*
*/
const reinitializeMap = async () => {
FWP.loaded = false;
delete FWP.frozen_facets.map; // change 'map' to name of your map
FWP.refresh();
await sleep(6000);
if (checkIfMapIsInitialized() !== true) {
reinitializeMap();
}
}
const fixMap = async (event) => {
if (!cmplz_has_consent("marketing")) {
return;
}
if (event.type === "cmplz_enable_category") {
document.removeEventListener('cmplz_enable_category', fixMap);
window.removeEventListener('load', fixMap);
}
await checkIfFWPExists();
if (checkIfMapIsInitialized() !== true) {
await reinitializeMap();
}
}
document.addEventListener('cmplz_enable_category', fixMap);
window.addEventListener('load', fixMap);
</script>
<?php
$script = ob_get_clean();
$script = str_replace(array('<script>', '</script>'), '', $script);
wp_add_inline_script('cmplz-cookiebanner', $script);
}, PHP_INT_MAX);
2 months ago
<!--
In the listing's Display tab in Dev mode, add the following to var_dump the SQL of the listing query.
This can be handy for seeing the SQL of a static listing, for which Debug Mode (FWP.settings.debug.sql in the Console) will not work.
See: https://facetwp.com/help-center/listing-templates/listing-builder/using-the-listing-builder-in-dev-mode/#debug-static-listing-queries
-->
<pre>
<?php var_dump( $GLOBALS['wp_query']->request ); ?>
</pre>