Gists

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>