Automatically clear CloudFlare cache when Elementor cache is cleared

OVERVIEW

Elementor is a popular “page builder” for WordPress. However, if you use both Elementor and CloudFlare on your WordPress site, you may have noticed pages not showing up as expected from time to time. This can happen shortly after updating a page, or after WordPress upgrades the Elementor plugin. The reason for this is that Elementor regularly adds and deletes CSS files in the WordPress uploads directory. Since CloudFlare has no way of knowing about these changes, it may serve up outdated CSS files for your web site.

There are tutorials on the internet that instruct you create a page rule on CloudFlare that disables caching for Elementor’s /wp-content/uploads/elementor/css/ directory. But then you lose the benefit of CloudFlare’s caching for Elementor CSS files at all times. Thankfully, there is better way, and this tutorial shows you how to do it.

THE SOLUTION

The Elementor plugin provides an Action Hook that fires whenever Elementor clears its file cache from the uploads directory. The CloudFlare plugin, provided by CloudFlare for free, gives us access to the functions needed to purge everything in your CloudFlare cache. The CloudFlare plugin will take care of all the API credentials and API communications so you don’t have to. The following code combines these two plugin features to automatically clear everything from your CloudFlare cache whenever Elementor updates its cache files. Simply cut-and-paste the code shown below into your theme’s functions.php file.

First, make sure you have the following installed, configured, and working.

  • The Elementor plugin.
  • The CloudFlare plugin.
  • Your own custom WordPress theme, or create a child theme for any WordPress theme downloaded to your site. Using a child theme prevents the following code snippet from being overwritten whenever the parent theme is upgraded.
Add this code to your theme's functions.php file./**
 * If CloudFlare plugin is active, setup cache purging.
 * Purge everything when any upgrade completes, and
 * when Elementor fires its clear_cache event.
 *
 * @global \CF\WordPress\Hooks $cloudflareHooks
 */
add_action( 'init', function () {
	global $cloudflareHooks;

	if ( ! empty( $cloudflareHooks ) && $cloudflareHooks instanceof \CF\WordPress\Hooks ) {
		add_action( 'elementor/core/files/clear_cache', 'elementor_cf_cache_clear' );
		add_action( 'upgrader_process_complete', 'elementor_cf_cache_clear', 10, 2 );
	}
} );

/**
 * Purges everything from CloudFlare cache if CloudFlare plugin is installed.
 *
 * @global \CF\WordPress\Hooks $cloudflareHooks
 */
function elementor_cf_cache_clear( $upgrader_object = NULL, $options = NULL ) {
	global $cloudflareHooks;

	$cloudflareHooks->purgeCacheEverything();
}

A SCALPEL, NOT A SLEDGE HAMMER

The code example shown above gets the job done. But purging the entire CloudFlare cache on every Elementor page update is like using a sledge hammer when a scalpel is preferred. When an Elementor page update occurs, then purge only Elementor’s CSS files. When a WordPress upgrade occurs, then purge the entire CloudFlare cache. To accomplish this, do the following.

    • Use add_action( ‘elementor/document/before_save’, function($doc, $data){ }, 10, 2 ) and fill in the function with code to create an array of URLs for all the Elementor CSS files in the uploads directory. Then save the array of CSS files, and the Post ID, to a WordPress transient with set_transient().
    • Modify the elementor_cf_cache_clear function shown above to test if the transient exists. If it does, retrieve the Post ID from the transient and call
      $cloudflareHooks->purgeCacheByRelevantURLs($post_id). Otherwise, fallback to calling $cloudflareHooks->purgeCacheEverything()
    • Add a filter for the CloudFlare plugin’s ‘cloudflare_purge_by_url’ hook with add_filter(‘cloudflare_purge_by_url’, function($urls, $post_id){ }, 10, 2);
      and fill in the function with code that pulls the CSS URLs from the transient with get_transient() and add them to the $urls array that CloudFlare will purge. Then use delete_transient() to delete the transient data.
    • NOTE 1: CloudFlare will only purge 30 urls in a single Purge Files API call. The CloudFlare plugin will break up the list of urls in to chunks of 30 to process them.
    • NOTE 2: Use PHP’s array_unique() function to eliminate duplicate urls from the list to keep it as short as possible.
    • NOTE 3: Although the code calls $cloudflareHooks->purgeCacheByRelevantURLs($post_id), the Post ID really is not important. The code will purge all Elementor CSS files from the CloudFlare cache on any Elementor page update.  But the Post ID must be a valid ID.  If multiple users are editing Elementor pages at the same time, then the most recent saved Post ID will be used.
Replace the previous code example with the following in your theme's functions.php file./**
 * If Elementor and CloudFlare plugins are active, setup cache purging.
 *
 * @global \CF\WordPress\Hooks $cloudflareHooks
 */
add_action( 'init', function () {
	global $cloudflareHooks;

	if ( class_exists( '\Elementor\Plugin' ) && ! empty( \Elementor\Plugin::$instance )
			&& ! empty( $cloudflareHooks ) && $cloudflareHooks instanceof \CF\WordPress\Hooks ) {
		add_action( 'upgrader_process_complete', 'elementor_cf_cache_clear', 10, 2 );
		add_action( 'elementor/core/files/clear_cache', 'elementor_cf_cache_clear' );
		add_action( 'elementor/document/before_save', 'elementor_css_url_list', 10, 2 );
		add_filter( 'cloudflare_purge_by_url', 'cf_add_elementor_css_urls', 10, 2 );
	}
} );

/**
 * If this is not an upgrader action, and a cache list is available, use it.
 * Otherwise, purge everything to be on the safe side.
 *
 * @param \WP_Upgrader $upgrader_object
 * @param array $options
 * @global \CF\WordPress\Hooks $cloudflareHooks
 */
function elementor_cf_cache_clear( $upgrader_object = NULL, $options = NULL ) {
	global $cloudflareHooks;

	$trans = get_transient( 'elem_cf_cache_list' );
	if ( empty( $upgrader_object ) && ! empty( $trans ) && isset( $trans[ 'ID' ] ) ) {
		$cloudflareHooks->purgeCacheByRelevantURLs( (int) $trans[ 'ID' ] );
	} else {
		$cloudflareHooks->purgeCacheEverything();
	}
}

/**
 * Gather list of Elementor CSS URLs.
 *
 * @param \Elementor\Core\Base\Document $doc
 * @param array $data
 */
function elementor_css_url_list( $doc, $data ) {
	$path = \Elementor\Core\Files\Base::get_base_uploads_dir() . \Elementor\Core\Files\Base::DEFAULT_FILES_DIR . '*.css';

	if ( $files = glob( $path, GLOB_NOSORT ) ) {
		/* Create list of all Elementor CSS files. */
		$urls	 = [];
		$baseurl = \Elementor\Core\Files\Base::get_base_uploads_url() . \Elementor\Core\Files\Base::DEFAULT_FILES_DIR;
		foreach ( $files as $filepath ) {
			$file	 = basename( $filepath );
			$urls[]	 = $baseurl . $file;
		}

		/* Create transient value with post ID and CSS URL array.
		 * Expire transient in 10 minutes in case something goes wrong.
		 */
		$trans			 = [];
		$trans[ 'ID' ]	 = $doc->get_main_id();
		$trans[ 'urls' ] = $urls;
		set_transient( 'elem_cf_cache_list', $trans, 600 );
	}
}

/**
 * Add Elementor CSS URLs to the list of URLs to be purged from CloudFlare cache.
 *
 * @param array $urls Array of URLs the CloudFlare plugin thinks it should purge.
 * @param int $post_id Database ID of post being cleared from CloudFlare cache.
 * @return array  Possibly modified array of URLs to purge.
 */
function cf_add_elementor_css_urls( $urls, $post_id ) {
	/* If a transient exists and has a URL array,
	 * add those URLs to CloudFlare's URL array.
	 */
	$trans = get_transient( 'elem_cf_cache_list' );
	if ( ! empty( $trans ) && ! empty( $trans[ 'ID' ] ) && ! empty( $trans[ 'urls' ] && is_array( $trans[ 'urls' ] ) ) ) {
		foreach ( $trans[ 'urls' ] as $url ) {
			$urls[] = $url;
		}

		/* Delete any duplicate URLs. Delete transient. */
		$urls = array_unique( $urls, SORT_STRING );
		delete_transient( 'elem_cf_cache_list' );
	}
	return $urls;
}

FURTHER DEVELOPMENT

The function elementor_css_url_list() can support more plugins that cache data in the uploads directory. For example, the Essential Addons for Elementor plugin caches CSS and Javascript files in the /wp-content/uploads/essential-addons-elementor/ directory. The plugin defines this directory as EAEL_ASSET_PATH in its code, which you could use to verify the plugin is active.  The plugin also defines EAEL_ASSET_URL to give you the base URL for that directory. Then, glob() that directory for assets to purge from CloudFlare and add them to the transient.

Before adding support for a plugin to the code, search the CloudFlare plugin code for the name of that plugin. For example, the Autoptimize plugin already has cache clearing support built-in to the CloudFlare plugin.