Gists

21 hours ago
<?php
// Add an "Export CSV" button above the listing template that exports selected post data of posts in the filtered results

// Part 1 - Add the "Export CSV" button above the listing
add_action( 'facetwp_scripts', function() {
    ?>
    <script>
        var fwp_export = {
            nonce: '<?php echo wp_create_nonce('fwp_export_csv'); ?>',
            ajaxurl: '<?php echo admin_url('admin-ajax.php'); ?>'
        };
    </script>
    <script>

        (function() {
            document.addEventListener('facetwp-loaded', function() {
                // Only inject the button once
                if (!document.querySelector('.fwp-export-btn')) {
                    var btn = document.createElement('button');
                    btn.className = 'fwp-export-btn';
                    btn.textContent = 'Export CSV';
                    document.querySelector('.facetwp-template').insertAdjacentElement('beforebegin', btn);

                    btn.addEventListener('click', function() {
                        var params = new URLSearchParams();
                        params.set('action', 'fwp_export_csv');
                        params.set('nonce', fwp_export.nonce);
                        window.location.href = fwp_export.ajaxurl + '?' + params.toString();
                    });

                }
            });
        })();

    </script>
    <?php
}, 100 );

// Part 2 - Store filtered post IDs in a transient
add_filter( 'facetwp_filtered_post_ids', function( $post_ids, $class ) {

    // Only store the transient on the relevant listing.
    // Use $class->ajax_params['template'] to check for a FacetWP listing.
    // Or use $class->http_params['uri'] to check the URI if it is another listing template type
    // For examples, see: https://facetwp.com/help-center/developers/hooks/querying-hooks/facetwp_filtered_post_ids/#usage-examples
    if ( 'my_template' !== $class->ajax_params['template'] ) { // Change "my_template" to the name of your template
        return $post_ids;
    }
    $session_id = fwp_get_export_session_id();
    if ( empty( $session_id ) ) {
        return $post_ids;
    }
    set_transient( 'fwp_export_ids_' . $session_id, $post_ids, 5 * MINUTE_IN_SECONDS );

    return $post_ids;
}, 10, 2 );

// Part 3 - Set cookie instead of session ID for logged-out users
// Remove this part if the button is admin-only
add_action( 'init', function() {
    if ( ! is_user_logged_in() && empty( $_COOKIE['fwp_export_session'] ) ) {
        $token = wp_generate_password( 12, false );
        setcookie( 'fwp_export_session', $token, time() + 3600, COOKIEPATH, COOKIE_DOMAIN );
        $_COOKIE['fwp_export_session'] = $token; // make it available in the same request
    }
} );

// Part 4 - Get a session ID from the transient (when logged in) or cookie (when logged out)
function fwp_get_export_session_id() {
    $token = wp_get_session_token();
    if ( ! empty( $token ) ) {
        return $token;
    }
    // Cookie fallback for logged-out users
    // Remove these lines if the button is admin-only
    return ! empty( $_COOKIE['fwp_export_session'] )
        ? sanitize_key( $_COOKIE['fwp_export_session'] )
        : '';
}

// Part 5 - Handle CSV export
add_action( 'wp_ajax_fwp_export_csv', 'fwp_handle_export_csv' );
add_action( 'wp_ajax_nopriv_fwp_export_csv', 'fwp_handle_export_csv' ); // Only for logged-out users. Remove if the button is admin-only.

function fwp_handle_export_csv() {
    ob_start(); // Catch any accidental output

    check_ajax_referer( 'fwp_export_csv', 'nonce' );

    $session_id = fwp_get_export_session_id();
    if ( empty( $session_id ) ) {
        wp_die( 'Session not found.' );
    }

    $post_ids = get_transient( 'fwp_export_ids_' . $session_id );
    if ( empty( $post_ids ) ) {
        wp_die( 'No results to export.' );
    }

    $query = new WP_Query( [
        'post__in'       => $post_ids,
        'orderby'        => 'post__in',
        'posts_per_page' => -1,
        'post_type'      => 'any',
    ] );

    ob_end_clean(); // Discard any buffered output before sending headers

    header( 'Content-Type: text/csv; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename="export.csv"' );

    $out = fopen( 'php://output', 'w' );
    if ( ! $out ) {
        wp_die( 'Could not open output stream.' );
    }

    fputcsv( $out, [ 'Post ID', 'Post Title', 'Post Date', 'My field' ] ); // Add CSV headings for the output below

    foreach ( $query->posts as $post ) {
        fputcsv( $out, [
            $post->ID,
            $post->post_title,
            $post->post_date,
            get_field( 'my_field_name', $post->ID ),// example ACF field
            //.. add any other data
        ] );
    }

    fclose( $out );
    exit;
}
19 hours ago
<?php
// Tells FacetWP to skip the first detected query and use the second.
// Can be useful if for some reason a facetwp-enabled is running twice on a page, 
// which will cause a query detection issue and non-functioning facets or pagination.

add_filter( 'facetwp_is_main_query', function( $is_main_query, $query ) {
    if ( 0 === strpos( FWP()->helper->get_uri(), 'special-project' ) ) {
        global $fwp_runonce;
        if ( $is_main_query && !isset($fwp_runonce) ) {
            $is_main_query = false;
            $fwp_runonce = true;
        }
    }
    return $is_main_query;

}, 10, 2 );
2 months ago
<?php

/**
 * Example of dynamic tag (https://facetwp.com/help-center/listing-templates/listing-builder/#using-dynamic-tags)
 * for FacetWP Listing Builder to display a
 * Co-Authors Plus template tag https://github.com/Automattic/Co-Authors-Plus/wiki/Template-tags
 * See: https://facetwp.com/help-center/using-facetwp-with/co-authors-plus/#using-a-custom-dynamic-tag-to-display-co-authors
 */
add_filter( 'facetwp_builder_dynamic_tag_value', function( $tag_value, $tag_name, $params ) {
  if ( 'co:authors' == $tag_name ) {
    $tag_value = coauthors_posts_links( ',', ' and ', null, null, false );
  }
  return $tag_value;
}, 10, 3 );
2 months ago
<?php
/**
 * Set author_name for a FacetWP Listing Builder listing template query on a author archive page
 * for compatibility with co-authors plus https://wordpress.org/plugins/co-authors-plus/
 * Change 'my_author_listing' in ln 9 to name of the facetwp listing you are using in the author archive
 * See: https://facetwp.com/help-center/using-facetwp-with/co-authors-plus/#using-a-listing-builder-listing-in-an-author-archive
 */

add_filter( 'facetwp_query_args', function( $query_args, $class ) {
  if ( 'my_author_listing' == $class->ajax_params['template'] ) { // Change 'my_author_listing' to name of the listing you are using in the author archive
  $uri_parts = explode( '/', $class->ajax_params['http_params']['uri'] ); // On refresh get author_name from last part of FWP uri
    $query_args['author_name'] = ! empty( get_query_var( 'author_name' ) )
    ? get_query_var( 'author_name' )
    : array_pop( $uri_parts );
  }
  return $query_args;
}, 10, 2 );
3 months 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;
}
3 months 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 );
5 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;
} );
5 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 );
5 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;
});
5 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 );