| @@ -25,8 +25,20 @@ | ||
| 25 | 25 | |
| 26 | 26 | private $files = []; |
| 27 | 27 | |
| 28 | 28 | /** |
| 29 | + * Whether `clear_cache()` already ran once during the current request. | |
| 30 | + * | |
| 31 | + * Only consulted when the `e_optimized_css_files` experiment is active, to | |
| 32 | + * collapse the multiple purges a single genuine update can trigger (e.g. | |
| 33 | + * Elementor's own DB upgrade purges at both upgrade-start and | |
| 34 | + * upgrade-complete, on top of the `upgrader_process_complete` hook). | |
| 35 | + * | |
| 36 | + * @var bool | |
| 37 | + */ | |
| 38 | + private $has_cleared_cache_this_request = false; | |
| 39 | + | |
| 40 | + /** | |
| 29 | 41 | * Files manager constructor. |
| 30 | 42 | * |
| 31 | 43 | * Initializing the Elementor files manager. |
| 32 | 44 | * |
| @@ -34,8 +46,9 @@ | ||
| 34 | 46 | * @access public |
| 35 | 47 | */ |
| 36 | 48 | public function __construct() { |
| 37 | 49 | $this->register_actions(); |
| 50 | + $this->register_site_changed_hooks(); | |
| 38 | 51 | } |
| 39 | 52 | |
| 40 | 53 | public function get( $class_name, $args ) { |
| 41 | 54 | $id = $class_name . '-' . wp_json_encode( $args ); |
| @@ -100,17 +113,37 @@ | ||
| 100 | 113 | * |
| 101 | 114 | * Delete all meta containing files data. And delete the actual |
| 102 | 115 | * files from the upload directory. |
| 103 | 116 | * |
| 117 | + * When the `e_optimized_css_files` experiment is active, repeated calls within | |
| 118 | + * the same request are collapsed into a single purge. | |
| 119 | + * | |
| 104 | 120 | * @since 1.2.0 |
| 105 | 121 | * @access public |
| 106 | 122 | */ |
| 107 | 123 | public function clear_cache() { |
| 124 | + if ( $this->is_optimized_css_files_active() ) { | |
| 125 | + if ( $this->has_cleared_cache_this_request ) { | |
| 126 | + return; | |
| 127 | + } | |
| 128 | + | |
| 129 | + $this->has_cleared_cache_this_request = true; | |
| 130 | + } | |
| 131 | + | |
| 108 | 132 | // Delete files. |
| 109 | 133 | $path = Base::get_base_uploads_dir() . Base::DEFAULT_FILES_DIR . '*'; |
| 110 | 134 | |
| 111 | - foreach ( glob( $path ) as $file_path ) { | |
| 112 | - unlink( $file_path ); | |
| 135 | + $file_paths = glob( $path ); | |
| 136 | + | |
| 137 | + if ( is_array( $file_paths ) ) { | |
| 138 | + foreach ( $file_paths as $file_path ) { | |
| 139 | + // A file that vanished between `glob()` and `unlink()` (e.g. a concurrent | |
| 140 | + // purge) is not an error, so guard with `is_file()` instead of letting | |
| 141 | + // `unlink()` emit a PHP warning. | |
| 142 | + if ( is_file( $file_path ) ) { | |
| 143 | + unlink( $file_path ); | |
| 144 | + } | |
| 145 | + } | |
| 113 | 146 | } |
| 114 | 147 | |
| 115 | 148 | delete_post_meta_by_key( Post_CSS::META_KEY ); |
| 116 | 149 | delete_post_meta_by_key( Document_Base::CACHE_META_KEY ); |
| @@ -206,8 +239,106 @@ | ||
| 206 | 239 | * @access private |
| 207 | 240 | */ |
| 208 | 241 | private function reset_assets_data() { |
| 209 | 242 | delete_option( Page_Assets_Data_Manager::ASSETS_DATA_KEY ); |
| 243 | + } | |
| 244 | + | |
| 245 | + /** | |
| 246 | + * Register site-changed hooks. | |
| 247 | + * | |
| 248 | + * Purge the files cache whenever the site's plugins, theme or Elementor's | |
| 249 | + * own element-cache TTL setting change. Relocated here (from the | |
| 250 | + * `element-cache` module) because the module's Performance-tab setting | |
| 251 | + * does not actually govern this purge - it is a files/assets concern. | |
| 252 | + * | |
| 253 | + * Ungated: this registration is behaviour-neutral regardless of the | |
| 254 | + * `e_optimized_css_files` experiment. | |
| 255 | + * | |
| 256 | + * @since 3.33.0 | |
| 257 | + * @access private | |
| 258 | + */ | |
| 259 | + private function register_site_changed_hooks() { | |
| 260 | + add_action( 'activated_plugin', [ $this, 'clear_cache' ] ); | |
| 261 | + add_action( 'deactivated_plugin', [ $this, 'clear_cache' ] ); | |
| 262 | + add_action( 'switch_theme', [ $this, 'clear_cache' ] ); | |
| 263 | + add_action( 'upgrader_process_complete', [ $this, 'on_upgrader_process_complete' ], 10, 2 ); | |
| 264 | + | |
| 265 | + add_action( 'update_option_elementor_element_cache_ttl', [ $this, 'clear_cache' ] ); | |
| 266 | + } | |
| 267 | + | |
| 268 | + /** | |
| 269 | + * On upgrader process complete. | |
| 270 | + * | |
| 271 | + * Fired by the `upgrader_process_complete` action, which WordPress also fires on | |
| 272 | + * mere update checks, translation updates, and bulk-update submissions with an | |
| 273 | + * empty item queue - none of which changed anything Elementor needs to purge for. | |
| 274 | + * | |
| 275 | + * When the `e_optimized_css_files` experiment is active, those false alarms are | |
| 276 | + * skipped. When inactive, behaviour is unchanged: always purge. | |
| 277 | + * | |
| 278 | + * WordPress passes the `hook_extra` array directly as the second argument to this | |
| 279 | + * action (see `WP_Upgrader::run()`), NOT nested under a `hook_extra` key - do not | |
| 280 | + * confuse this with `WP_Upgrader::$result['hook_extra']` accessed elsewhere. | |
| 281 | + * | |
| 282 | + * @since 3.33.0 | |
| 283 | + * @access public | |
| 284 | + * | |
| 285 | + * @param \WP_Upgrader|false $upgrader The upgrader instance, or false. | |
| 286 | + * @param array $hook_extra The upgrade payload (`action`, `type`, `plugins`/`themes`/`plugin`/`theme`, `translations`, ...). | |
| 287 | + */ | |
| 288 | + public function on_upgrader_process_complete( $upgrader, $hook_extra ) { | |
| 289 | + if ( $this->is_optimized_css_files_active() && ! $this->is_genuine_update( $hook_extra ) ) { | |
| 290 | + return; | |
| 291 | + } | |
| 292 | + | |
| 293 | + $this->clear_cache(); | |
| 294 | + } | |
| 295 | + | |
| 296 | + /** | |
| 297 | + * Whether the `upgrader_process_complete` payload represents a genuine plugin, | |
| 298 | + * theme or core update (as opposed to an update check, a translation update, or | |
| 299 | + * a bulk-update form submitted with nothing selected). | |
| 300 | + * | |
| 301 | + * @since 3.33.0 | |
| 302 | + * @access private | |
| 303 | + * | |
| 304 | + * @param array $hook_extra The upgrade payload passed as the hook's second argument. | |
| 305 | + * | |
| 306 | + * @return bool | |
| 307 | + */ | |
| 308 | + private function is_genuine_update( $hook_extra ) { | |
| 309 | + if ( empty( $hook_extra ) || ! is_array( $hook_extra ) ) { | |
| 310 | + return false; | |
| 311 | + } | |
| 312 | + | |
| 313 | + if ( 'translation' === ( $hook_extra['type'] ?? null ) || ! empty( $hook_extra['translations'] ) ) { | |
| 314 | + return false; | |
| 315 | + } | |
| 316 | + | |
| 317 | + if ( 'update' === ( $hook_extra['action'] ?? null ) && 'core' !== ( $hook_extra['type'] ?? null ) ) { | |
| 318 | + $has_queued_items = ! empty( $hook_extra['plugins'] ) | |
| 319 | + || ! empty( $hook_extra['themes'] ) | |
| 320 | + || ! empty( $hook_extra['plugin'] ) | |
| 321 | + || ! empty( $hook_extra['theme'] ); | |
| 322 | + | |
| 323 | + if ( ! $has_queued_items ) { | |
| 324 | + return false; | |
| 325 | + } | |
| 326 | + } | |
| 327 | + | |
| 328 | + return true; | |
| 329 | + } | |
| 330 | + | |
| 331 | + /** | |
| 332 | + * Whether the `e_optimized_css_files` experiment is active. | |
| 333 | + * | |
| 334 | + * @since 3.33.0 | |
| 335 | + * @access private | |
| 336 | + * | |
| 337 | + * @return bool | |
| 338 | + */ | |
| 339 | + private function is_optimized_css_files_active() { | |
| 340 | + return Plugin::$instance->experiments->is_feature_active( 'e_optimized_css_files' ); | |
| 210 | 341 | } |
| 211 | 342 | |
| 212 | 343 | /** |
| 213 | 344 | * Generate CSS. |