PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta3
Elementor Website Builder – more than just a page builder v4.3.0-beta3
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 4.0.8 4.1.0-beta1 4.1.0-dev1 All 452 releases
elementor / modules / atomic-widgets / styles / css-files-manager.php

css-files-manager.php in Elementor Website Builder – more than just a page builder 4.3.0-beta3, at modules/atomic-widgets/styles/css-files-manager.php

201 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\AtomicWidgets\Styles;
4
5 use Elementor\Core\Files\Base as Base_File;
6 use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity;
7
8 class CSS_Files_Manager {
9 const DEFAULT_CSS_DIR = 'css/';
10 const FILE_EXTENSION = '.css';
11 // Read and write permissions for the owner
12 const PERMISSIONS = 0644;
13
14 private Cache_Validity $cache_validity;
15
16 public function __construct( ?Cache_Validity $cache_validity = null ) {
17 $this->cache_validity = $cache_validity ?? new Cache_Validity();
18 }
19
20 /**
21 * Return the CSS file to enqueue for the given cache path, generating it on demand.
22 *
23 * The `should_exist` meta flag distinguishes an intentionally empty file from one
24 * that went missing after being generated, and cache is regenerated whenever it's
25 * invalid or the expected file can't be found on disk.
26 *
27 * @param string $handle
28 * @param string $media
29 * @param callable $get_css
30 * @param array<string> $cache_path Cache-validity leaf path.
31 * @return Style_File|null
32 */
33 public function get( string $handle, string $media, callable $get_css, array $cache_path ): ?Style_File {
34 $path = $this->get_path( $handle );
35 $is_cache_valid = $this->cache_validity->is_valid( $cache_path );
36 $should_exist = $this->get_should_exist_meta( $cache_path );
37
38 if ( $is_cache_valid ) {
39 if ( false === $should_exist ) {
40 return null;
41 }
42
43 if ( $this->has_non_empty_file( $path ) ) {
44 return $this->create_style_file( $handle, $path, $media );
45 }
46 }
47
48 $css = $get_css();
49
50 if ( empty( $css ) ) {
51 // Nothing to serve for this path; purge any stale file and record the state
52 // so we don't try to regenerate on every subsequent request.
53 $this->delete_if_exists( $path );
54 $this->cache_validity->validate( $cache_path, [ 'should_exist' => false ] );
55
56 return null;
57 }
58
59 $filesystem_path = $this->get_filesystem_path( $path );
60
61 if ( ! $this->write_atomically( $filesystem_path, $css ) ) {
62 // Hard write failure: leave the cache invalid so the next request retries.
63 return null;
64 }
65
66 $this->cache_validity->validate( $cache_path, [ 'should_exist' => true ] );
67
68 return $this->create_style_file( $handle, $path, $media );
69 }
70
71 public function delete( string $handle ): void {
72 $this->delete_if_exists( $this->get_path( $handle ) );
73 }
74
75 private function get_should_exist_meta( array $cache_path ): ?bool {
76 $meta = $this->cache_validity->get_meta( $cache_path );
77
78 if ( ! is_array( $meta ) || ! array_key_exists( 'should_exist', $meta ) ) {
79 return null;
80 }
81
82 return (bool) $meta['should_exist'];
83 }
84
85 private function create_style_file( string $handle, string $path, string $media ): Style_File {
86 return Style_File::create(
87 $this->sanitize_handle( $handle ),
88 $this->get_filesystem_path( $path ),
89 $this->get_url( $handle ),
90 $media
91 );
92 }
93
94 private function has_non_empty_file( string $path ): bool {
95 $filesystem = $this->get_filesystem();
96
97 if ( ! $filesystem->exists( $path ) ) {
98 return false;
99 }
100
101 $size = $filesystem->size( $path );
102
103 // `size()` may return false on filesystems that don't support it; treat that as
104 // "we don't know" and fall back to trusting the existence check.
105 if ( false === $size ) {
106 return true;
107 }
108
109 return $size > 0;
110 }
111
112 private function delete_if_exists( string $path ): void {
113 $filesystem = $this->get_filesystem();
114
115 if ( ! $filesystem->exists( $path ) ) {
116 return;
117 }
118
119 $filesystem->delete( $path );
120 }
121
122 /**
123 * Write to a temp file first and then move it into place. The move step is atomic on
124 * POSIX filesystems, so the public URL cannot serve a partial or zero-byte asset while a
125 * concurrent render is in progress. If the atomic move is not supported by the current
126 * filesystem adapter, fall back to a direct write.
127 */
128 private function write_atomically( string $filesystem_path, string $css ): bool {
129 $filesystem = $this->get_filesystem();
130
131 if ( ! $this->ensure_directory_exists( dirname( $filesystem_path ) ) ) {
132 return false;
133 }
134
135 $tmp_path = $filesystem_path . '.tmp-' . wp_generate_password( 8, false, false );
136
137 $is_written = $filesystem->put_contents( $tmp_path, $css, self::PERMISSIONS );
138
139 if ( false === $is_written ) {
140 return false;
141 }
142
143 if ( ! method_exists( $filesystem, 'move' ) || ! $filesystem->move( $tmp_path, $filesystem_path, true ) ) {
144 $fallback = $filesystem->put_contents( $filesystem_path, $css, self::PERMISSIONS );
145
146 if ( $filesystem->exists( $tmp_path ) ) {
147 $filesystem->delete( $tmp_path );
148 }
149
150 return false !== $fallback;
151 }
152
153 return true;
154 }
155
156 private function ensure_directory_exists( string $directory ): bool {
157 $filesystem = $this->get_filesystem();
158
159 if ( $filesystem->is_dir( $directory ) ) {
160 return true;
161 }
162
163 return wp_mkdir_p( $directory );
164 }
165
166 private function get_filesystem(): \WP_Filesystem_Base {
167 global $wp_filesystem;
168
169 if ( empty( $wp_filesystem ) ) {
170 require_once ABSPATH . '/wp-admin/includes/file.php';
171 WP_Filesystem();
172 }
173
174 return $wp_filesystem;
175 }
176
177 private function get_filesystem_path( $path ): string {
178 $filesystem = $this->get_filesystem();
179
180 return str_replace( ABSPATH, $filesystem->abspath(), $path );
181 }
182
183 private function get_url( string $handle ): string {
184 $sanitized_handle = $this->sanitize_handle( $handle );
185 $handle = $sanitized_handle . self::FILE_EXTENSION;
186
187 return Base_File::get_base_uploads_url() . self::DEFAULT_CSS_DIR . $handle;
188 }
189
190 private function get_path( string $handle ): string {
191 $sanitized_handle = $this->sanitize_handle( $handle );
192 $handle = $sanitized_handle . self::FILE_EXTENSION;
193
194 return Base_File::get_base_uploads_dir() . self::DEFAULT_CSS_DIR . $handle;
195 }
196
197 private function sanitize_handle( string $handle ): string {
198 return preg_replace( '/[^a-zA-Z0-9_-]/', '', $handle );
199 }
200 }
201