PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.2
Elementor Website Builder – more than just a page builder v4.3.2
4.3.2 4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 All 455 releases
elementor / core / files / manager.php

manager.php in Elementor Website Builder – more than just a page builder 4.3.2, at core/files/manager.php

411 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Core\Files;
3
4 use Elementor\Core\Base\Document as Document_Base;
5 use Elementor\Core\Base\Elements_Iteration_Actions\Assets;
6 use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
7 use Elementor\Core\Files\CSS\Post as Post_CSS;
8 use Elementor\Core\Page_Assets\Data_Managers\Base as Page_Assets_Data_Manager;
9 use Elementor\Core\Responsive\Files\Frontend;
10 use Elementor\Plugin;
11 use Elementor\Utils;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Elementor files manager.
19 *
20 * Elementor files manager handler class is responsible for creating files.
21 *
22 * @since 1.2.0
23 */
24 class Manager {
25
26 private $files = [];
27
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 /**
41 * Files manager constructor.
42 *
43 * Initializing the Elementor files manager.
44 *
45 * @since 1.2.0
46 * @access public
47 */
48 public function __construct() {
49 $this->register_actions();
50 $this->register_site_changed_hooks();
51 }
52
53 public function get( $class_name, $args ) {
54 $id = $class_name . '-' . wp_json_encode( $args );
55
56 if ( ! isset( $this->files[ $id ] ) ) {
57 // Create an instance from dynamic args length.
58 $reflection_class = new \ReflectionClass( $class_name );
59 $this->files[ $id ] = $reflection_class->newInstanceArgs( $args );
60 }
61
62 return $this->files[ $id ];
63 }
64
65 /**
66 * On post delete.
67 *
68 * Delete post CSS immediately after a post is deleted from the database.
69 *
70 * Fired by `deleted_post` action.
71 *
72 * @since 1.2.0
73 * @access public
74 *
75 * @param string $post_id Post ID.
76 */
77 public function on_delete_post( $post_id ) {
78 if ( ! Utils::is_post_support( $post_id ) ) {
79 return;
80 }
81
82 $css_file = Post_CSS::create( $post_id );
83
84 $css_file->delete();
85 }
86
87 /**
88 * On export post meta.
89 *
90 * When exporting data using WXR, skip post CSS file meta key. This way the
91 * export won't contain the post CSS file data used by Elementor.
92 *
93 * Fired by `wxr_export_skip_postmeta` filter.
94 *
95 * @since 1.2.0
96 * @access public
97 *
98 * @param bool $skip Whether to skip the current post meta.
99 * @param string $meta_key Current meta key.
100 *
101 * @return bool Whether to skip the post CSS meta.
102 */
103 public function on_export_post_meta( $skip, $meta_key ) {
104 if ( Post_CSS::META_KEY === $meta_key ) {
105 $skip = true;
106 }
107
108 return $skip;
109 }
110
111 /**
112 * Clear cache.
113 *
114 * Delete all meta containing files data. And delete the actual
115 * files from the upload directory.
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 *
120 * @since 1.2.0
121 * @access public
122 */
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
132 // Delete files.
133 $path = Base::get_base_uploads_dir() . Base::DEFAULT_FILES_DIR . '*';
134
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 }
146 }
147
148 delete_post_meta_by_key( Post_CSS::META_KEY );
149 delete_post_meta_by_key( Document_Base::CACHE_META_KEY );
150 delete_post_meta_by_key( Assets::ASSETS_META_KEY );
151
152 delete_option( Frontend::META_KEY );
153
154 $this->reset_assets_data();
155
156 /**
157 * Elementor clear files.
158 *
159 * Fires after Elementor clears files
160 *
161 * @since 2.1.0
162 */
163 do_action( 'elementor/core/files/clear_cache' );
164 }
165
166 public function clear_custom_image_sizes() {
167 if ( ! defined( 'BFITHUMB_UPLOAD_DIR' ) ) {
168 return;
169 }
170
171 $upload_info = wp_upload_dir();
172 $upload_dir = $upload_info['basedir'] . '/' . BFITHUMB_UPLOAD_DIR;
173
174 $path = $upload_dir . '/*';
175
176 foreach ( glob( $path ) as $file_path ) {
177 unlink( $file_path );
178 }
179 }
180
181 /**
182 * Register Ajax Actions
183 *
184 * Deprecated - use the Uploads Manager instead.
185 *
186 * @deprecated 3.5.0
187 *
188 * @param Ajax $ajax
189 */
190 public function register_ajax_actions( Ajax $ajax ) {
191 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' );
192
193 Plugin::$instance->uploads_manager->register_ajax_actions( $ajax );
194 }
195
196 /**
197 * Ajax Unfiltered Files Upload
198 *
199 * Deprecated - use the Uploads Manager instead.
200 *
201 * @deprecated 3.5.0
202 */
203 public function ajax_unfiltered_files_upload() {
204 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' );
205
206 Plugin::$instance->uploads_manager->enable_unfiltered_files_upload();
207 }
208
209 /**
210 * Register actions.
211 *
212 * Register filters and actions for the files manager.
213 *
214 * @since 1.2.0
215 * @access private
216 */
217 private function register_actions() {
218 add_action( 'deleted_post', [ $this, 'on_delete_post' ] );
219
220 add_filter( 'wxr_export_skip_postmeta', [ $this, 'on_export_post_meta' ], 10, 2 );
221
222 add_action( 'update_option_home', function () {
223 $this->reset_assets_data();
224 } );
225
226 add_action( 'update_option_siteurl', function () {
227 $this->reset_assets_data();
228 } );
229
230 add_action( 'rest_api_init', [ $this, 'register_endpoints' ] );
231 }
232
233 /**
234 * Reset Assets Data.
235 *
236 * Reset the page assets data.
237 *
238 * @since 3.3.0
239 * @access private
240 */
241 private function reset_assets_data() {
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' );
341 }
342
343 /**
344 * Generate CSS.
345 *
346 * Generates CSS for all posts built with Elementor.
347 *
348 * @since 3.25.0
349 * @access public
350 */
351 public function generate_css() {
352 $batch_size = apply_filters( 'elementor/core/files/generate_css/batch_size', 100 );
353 $processed_posts = 0;
354
355 while ( true ) {
356 $args = [
357 'post_type' => get_post_types(),
358 'posts_per_page' => $batch_size,
359 'meta_query' => [
360 [
361 'key' => Document_Base::BUILT_WITH_ELEMENTOR_META_KEY,
362 'compare' => 'EXISTS',
363 ],
364 ],
365 'offset' => $processed_posts,
366 'fields' => 'ids',
367 ];
368
369 $query = new \WP_Query( $args );
370
371 if ( empty( $query->posts ) ) {
372 break;
373 }
374
375 foreach ( $query->posts as $post_id ) {
376 $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id );
377
378 if ( $document ) {
379 $css_file = Post_CSS::create( $post_id );
380 $css_file->update();
381 }
382 }
383
384 $processed_posts += $batch_size;
385 }
386
387 /**
388 * Elementor Generate CSS files.
389 *
390 * Fires after Elementor generates new CSS files
391 *
392 * @since 3.25.0
393 */
394 do_action( 'elementor/core/files/after_generate_css' );
395 }
396
397 public function register_endpoints() {
398 register_rest_route(
399 'elementor/v1',
400 '/cache',
401 [
402 'methods' => \WP_REST_Server::DELETABLE,
403 'callback' => [ $this, 'clear_cache' ],
404 'permission_callback' => function() {
405 return current_user_can( 'manage_options' );
406 },
407 ]
408 );
409 }
410 }
411