| 1 |
<?php |
| 2 |
/** |
| 3 |
* Render_Caches — invalidate caches of RENDERED output owned by OTHER |
| 4 |
* plugins. |
| 5 |
* |
| 6 |
* Why this exists: not every asset-URL rewrite happens on the finished page. |
| 7 |
* The CDN module hooks `wp_get_attachment_url`, which fires *during* element |
| 8 |
* render, and core derives every sized image URL from it (`image_downsize()` |
| 9 |
* calls it and swaps the basename for the intermediate size). So the CDN host |
| 10 |
* is already in the markup by the time a page builder captures that markup |
| 11 |
* into a cache of its own. |
| 12 |
* |
| 13 |
* Elementor does exactly that, in two places: |
| 14 |
* |
| 15 |
* - `_elementor_element_cache` post meta — the buffered output of a whole |
| 16 |
* document, written on any ordinary front-end render, 24 h TTL by |
| 17 |
* default (element caching is ON out of the box: the gate is |
| 18 |
* `'disable' !== get_option( 'elementor_element_cache_ttl', '' )` and the |
| 19 |
* option is unset on a stock install). |
| 20 |
* - `uploads/elementor/css/post-<id>.css` — background-image `url()` values |
| 21 |
* built from the same attachment URLs. **No TTL at all**; regenerated |
| 22 |
* only on post save, "Regenerate Files & Data", or an Elementor version |
| 23 |
* bump. |
| 24 |
* |
| 25 |
* `Cache::purge_all()` sweeps only trees xSpeed owns, so neither is reached. |
| 26 |
* The observable bug: disabling the CDN keeps emitting the old host for a day |
| 27 |
* (indefinitely from the CSS files), and *enabling* it leaves the heaviest |
| 28 |
* images — including the LCP hero — on the origin for the same window, which |
| 29 |
* reads as "the CDN does nothing". |
| 30 |
* |
| 31 |
* Deliberately NOT wired to every purge. A post-publish purge fires |
| 32 |
* constantly and blowing away every generated CSS file each time would be a |
| 33 |
* real regression; Elementor already invalidates its own copy for the post |
| 34 |
* being saved. This runs only where asset URLs themselves can have changed — |
| 35 |
* see Cache::purge_render_caches() for the call sites. |
| 36 |
* |
| 37 |
* Detection is by class/constant only, never `is_plugin_active()` on a path |
| 38 |
* string: a renamed plugin folder must not silently turn the integration off. |
| 39 |
* |
| 40 |
* @package XSpeed |
| 41 |
*/ |
| 42 |
|
| 43 |
declare(strict_types=1); |
| 44 |
|
| 45 |
namespace XSpeed; |
| 46 |
|
| 47 |
defined( 'ABSPATH' ) || exit; |
| 48 |
|
| 49 |
final class Render_Caches { |
| 50 |
|
| 51 |
/** |
| 52 |
* Register the built-in integrations. |
| 53 |
* |
| 54 |
* Everything hangs off the public `xspeed_purge_third_party_render_caches` |
| 55 |
* filter, so Pro — or a site owner with one builder we don't ship support |
| 56 |
* for — adds a listener rather than patching this class. |
| 57 |
*/ |
| 58 |
public static function boot(): void { |
| 59 |
add_filter( 'xspeed_purge_third_party_render_caches', array( __CLASS__, 'purge_elementor' ), 10, 2 ); |
| 60 |
} |
| 61 |
|
| 62 |
/** |
| 63 |
* Elementor: generated CSS files, `_elementor_css`, |
| 64 |
* `_elementor_element_cache` and `_elementor_page_assets`. |
| 65 |
* |
| 66 |
* `Files_Manager::clear_cache()` clears all four in one pass and is what |
| 67 |
* Elementor's own *Regenerate Files & Data* tool runs — public since |
| 68 |
* Elementor 1.2.0. Everything regenerates lazily on the next front-end |
| 69 |
* render of each page (measured on a 128 KB-CSS Elementor homepage: |
| 70 |
* ~1.2 s for that first render against ~0.3 s warm, then back to normal). |
| 71 |
* |
| 72 |
* @param string[] $cleared Labels of caches cleared so far. |
| 73 |
* @param string $cause Who asked, threaded through for the log. |
| 74 |
* @return string[] |
| 75 |
*/ |
| 76 |
public static function purge_elementor( $cleared, $cause = 'manual' ): array { |
| 77 |
$cleared = is_array( $cleared ) ? $cleared : array(); |
| 78 |
unset( $cause ); |
| 79 |
|
| 80 |
$files_manager = self::elementor_files_manager(); |
| 81 |
if ( null === $files_manager ) { |
| 82 |
return $cleared; |
| 83 |
} |
| 84 |
|
| 85 |
$files_manager->clear_cache(); |
| 86 |
$cleared[] = 'Elementor'; |
| 87 |
|
| 88 |
return $cleared; |
| 89 |
} |
| 90 |
|
| 91 |
/** |
| 92 |
* Elementor's files manager, or null when Elementor is absent or has not |
| 93 |
* finished booting. |
| 94 |
* |
| 95 |
* Every step is guarded rather than assumed: this can run from a REST |
| 96 |
* settings write, from WP-CLI and from admin-post, and `$instance` is |
| 97 |
* populated late enough that "class exists" alone is not evidence the |
| 98 |
* manager is there. |
| 99 |
* |
| 100 |
* @return object|null |
| 101 |
*/ |
| 102 |
private static function elementor_files_manager() { |
| 103 |
if ( ! class_exists( '\\Elementor\\Plugin' ) ) { |
| 104 |
return null; |
| 105 |
} |
| 106 |
$instance = \Elementor\Plugin::$instance; |
| 107 |
if ( ! is_object( $instance ) || ! isset( $instance->files_manager ) ) { |
| 108 |
return null; |
| 109 |
} |
| 110 |
$files_manager = $instance->files_manager; |
| 111 |
if ( ! is_object( $files_manager ) || ! method_exists( $files_manager, 'clear_cache' ) ) { |
| 112 |
return null; |
| 113 |
} |
| 114 |
return $files_manager; |
| 115 |
} |
| 116 |
} |
| 117 |
|