PluginProbe
Block Animations, Motion & Scroll Effects – Ghost Kit / 3.7.2
Block Animations, Motion & Scroll Effects – Ghost Kit v3.7.2
3.7.2 3.7.1 3.7.0 3.6.1 trunk 1.6.3 2.25.0 3.3.0 3.3.1 3.3.2 3.3.3 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.6.0
← All changes | classes/class-breakpoints.php +246 -154 3.6.13.7.2 View file →
@@ -39,171 +39,325 @@
39 39 */
40 40 protected static $default_lg = 1200;
41 41
42 42 /**
43 - * Database saved hash option Name.
43 + * Plugin name. Also the directory under uploads that holds the generated CSS.
44 44 *
45 45 * @var string
46 46 */
47 - protected $database_saved_hash_option_name = 'ghostkit_saved_breakpoints_hash';
47 + protected $plugin_name = 'ghostkit';
48 48
49 49 /**
50 - * Plugin name.
50 + * Plugin version.
51 51 *
52 52 * @var string
53 53 */
54 - protected $plugin_name = 'ghostkit';
54 + protected $plugin_version = GHOSTKIT_VERSION;
55 55
56 56 /**
57 - * Plugin version.
57 + * GhostKit_Breakpoints constructor.
58 + */
59 + public function __construct() {
60 + add_filter( 'style_loader_src', array( $this, 'change_style_src_to_compile' ), 10, 1 );
61 + add_action( 'gkt_before_assets_register', array( $this, 'maybe_generate_css' ) );
62 + }
63 +
64 + /**
65 + * Get plugin path.
58 66 *
59 - * @var string
67 + * @return string
60 68 */
61 - protected $plugin_version = GHOSTKIT_VERSION;
69 + public function get_plugin_path() {
70 + return ghostkit()->plugin_path;
71 + }
62 72
63 73 /**
64 - * Scss Configurations.
74 + * Get plugin url.
65 75 *
66 - * @var array
76 + * @return string
67 77 */
68 - protected $scss_configs = array(
69 - 'gutenberg/style.scss',
70 - 'gutenberg/editor.scss',
71 - 'gutenberg/blocks/*/styles/style.scss',
72 - );
78 + public function get_plugin_url() {
79 + return ghostkit()->plugin_url;
80 + }
73 81
74 82 /**
75 - * Compile Scss Configurations.
83 + * Name of the option that holds the hash and the list of generated files.
76 84 *
77 - * @var array
85 + * @return string
78 86 */
79 - protected $compile_scss_configs;
87 + protected function get_option_name() {
88 + return str_replace( '-', '_', $this->plugin_name ) . '_breakpoints_css';
89 + }
80 90
81 91 /**
82 - * Background breakpoints processing.
92 + * Directory under uploads that holds the generated CSS.
83 93 *
84 - * @var object
94 + * @return string
85 95 */
86 - protected $breakpoints_background_process;
96 + protected function get_output_dir() {
97 + $upload_dir = wp_get_upload_dir();
87 98
99 + return $upload_dir['basedir'] . '/' . $this->plugin_name;
100 + }
101 +
88 102 /**
89 - * GhostKit_Breakpoints constructor.
103 + * Stored hash and list of generated files.
104 + *
105 + * @return array|false
90 106 */
91 - public function __construct() {
92 - if ( ! class_exists( 'GhostKit_Breakpoints_Background' ) ) {
93 - // breakpoints background.
94 - require_once __DIR__ . '/class-breakpoints-background.php';
107 + protected function get_generated_css() {
108 + $stored = get_option( $this->get_option_name() );
109 +
110 + if (
111 + ! is_array( $stored ) ||
112 + ! isset( $stored['hash'], $stored['files'] ) ||
113 + ! is_array( $stored['files'] )
114 + ) {
115 + return false;
95 116 }
96 117
97 - $this->compile_scss_configs = $this->get_compile_scss_configs();
98 - $this->breakpoints_background_process = new GhostKit_Breakpoints_Background();
118 + return $stored;
119 + }
99 120
100 - add_filter( 'style_loader_src', array( $this, 'change_style_src_to_compile' ), 10, 1 );
101 - add_action( 'gkt_before_assets_register', array( $this, 'maybe_compile_scss_files' ) );
121 + /**
122 + * Regenerate the CSS when the breakpoints or the plugin version changed.
123 + *
124 + * @return void
125 + */
126 + public function maybe_generate_css() {
127 + $breakpoints = self::get_breakpoints();
128 + $hash = $this->get_breakpoints_hash( $breakpoints );
129 + $stored = $this->get_generated_css();
130 +
131 + if ( $hash === $this->get_default_breakpoints_hash() ) {
132 + if ( $stored ) {
133 + $this->remove_generated_css();
134 + }
135 + return;
136 + }
137 +
138 + if ( $stored && $stored['hash'] === $hash ) {
139 + return;
140 + }
141 +
142 + $this->remove_generated_css();
143 +
144 + update_option(
145 + $this->get_option_name(),
146 + array(
147 + 'hash' => $hash,
148 + 'files' => $this->generate_css( $breakpoints ),
149 + )
150 + );
102 151 }
103 152
104 153 /**
105 - * Get plugin path.
154 + * Write the built CSS files with the breakpoint values replaced into uploads.
106 155 *
107 - * @return string
156 + * @param array $breakpoints - Breakpoints.
157 + * @return array Paths of the written files, relative to the plugin directory.
108 158 */
109 - public function get_plugin_path() {
110 - return ghostkit()->plugin_path;
159 + protected function generate_css( $breakpoints ) {
160 + $plugin_path = wp_normalize_path( $this->get_plugin_path() );
161 + $build_dir = $plugin_path . 'build';
162 + $output_dir = $this->get_output_dir();
163 + $files = array();
164 +
165 + if ( ! is_dir( $build_dir ) ) {
166 + return $files;
167 + }
168 +
169 + try {
170 + // CATCH_GET_CHILD skips a subdirectory PHP cannot open instead of ending the walk.
171 + $iterator = new RecursiveIteratorIterator(
172 + new RecursiveDirectoryIterator( $build_dir, FilesystemIterator::SKIP_DOTS ),
173 + RecursiveIteratorIterator::LEAVES_ONLY,
174 + RecursiveIteratorIterator::CATCH_GET_CHILD
175 + );
176 +
177 + foreach ( $iterator as $file ) {
178 + if ( 'css' !== $file->getExtension() ) {
179 + continue;
180 + }
181 +
182 + $path = wp_normalize_path( $file->getPathname() );
183 + // phpcs:ignore
184 + $css = file_get_contents( $path );
185 +
186 + if ( false === $css ) {
187 + continue;
188 + }
189 +
190 + $replaced = self::replace_breakpoints( $css, $breakpoints );
191 +
192 + if ( $replaced === $css ) {
193 + continue;
194 + }
195 +
196 + $relative = substr( $path, strlen( $plugin_path ) );
197 + $target = $output_dir . '/' . $relative;
198 +
199 + if ( ! wp_mkdir_p( dirname( $target ) ) ) {
200 + continue;
201 + }
202 +
203 + // phpcs:ignore
204 + if ( false === file_put_contents( $target, $replaced ) ) {
205 + continue;
206 + }
207 +
208 + $files[] = $relative;
209 + }
210 + } catch ( UnexpectedValueException $e ) {
211 + // `build` itself could not be opened; nothing was written.
212 + unset( $e );
213 + }
214 +
215 + // WordPress derives the `-rtl.css` URL from the served one, so a file whose RTL twin
216 + // exists in the build but was not written would give RTL sites a 404. Serve neither.
217 + foreach ( $files as $index => $relative ) {
218 + $rtl_twin = preg_replace( '/\.css$/', '-rtl.css', $relative );
219 +
220 + if (
221 + $rtl_twin !== $relative &&
222 + ! in_array( $rtl_twin, $files, true ) &&
223 + file_exists( $plugin_path . $rtl_twin )
224 + ) {
225 + wp_delete_file( $output_dir . '/' . $relative );
226 + unset( $files[ $index ] );
227 + }
228 + }
229 +
230 + $files = array_values( $files );
231 + sort( $files );
232 +
233 + return $files;
111 234 }
112 235
113 236 /**
114 - * Get plugin url.
237 + * Remove the generated CSS and the option that lists it.
115 238 *
116 - * @return string
239 + * @return void
117 240 */
118 - public function get_plugin_url() {
119 - return ghostkit()->plugin_url;
241 + protected function remove_generated_css() {
242 + self::remove_dir( $this->get_output_dir() . '/build' );
243 + delete_option( $this->get_option_name() );
120 244 }
121 245
122 246 /**
123 - * Get compile scss configurations.
247 + * Delete a directory with everything inside it.
124 248 *
125 - * @return array
249 + * @param string $dir - Absolute path.
250 + * @return void
126 251 */
127 - protected function get_compile_scss_configs() {
128 - $plugin_path = $this->get_plugin_path();
129 - $upload_dir = wp_upload_dir();
130 - $compile_scss_configs = array();
131 - $scss_paths = apply_filters( 'gkt_scss_paths', $plugin_path );
132 - $scss_configs = apply_filters( 'gkt_scss_configs', $this->scss_configs );
252 + public static function remove_dir( $dir ) {
253 + if ( ! is_dir( $dir ) ) {
254 + return;
255 + }
133 256
134 - if ( ! is_array( $scss_paths ) ) {
135 - $scss_paths = array( $scss_paths );
136 - }
257 + try {
258 + $iterator = new RecursiveIteratorIterator(
259 + new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS ),
260 + RecursiveIteratorIterator::CHILD_FIRST
261 + );
137 262
138 - foreach ( $scss_configs as $scss_config ) {
139 - foreach ( $scss_paths as $path ) {
140 - $compile_scss_configs = $this->get_parsed_scss_files( $compile_scss_configs, $path, $upload_dir, $scss_config );
263 + foreach ( $iterator as $item ) {
264 + if ( $item->isDir() ) {
265 + // phpcs:ignore
266 + rmdir( $item->getPathname() );
267 + } else {
268 + wp_delete_file( $item->getPathname() );
269 + }
141 270 }
271 + } catch ( UnexpectedValueException $e ) {
272 + // A directory PHP cannot open stays in place, with the directories above it.
273 + return;
142 274 }
143 275
144 - return $compile_scss_configs;
276 + // phpcs:ignore
277 + rmdir( $dir );
145 278 }
146 279
147 280 /**
148 - * Get parsed array of scss files
281 + * Replace the default breakpoint values inside `@media` preludes with the given ones.
149 282 *
150 - * @param array $compile_scss_configs - Array of config width scss files.
151 - * @param string $scss_path - Path to scss files.
152 - * @param string $upload_dir - Uploas WP dir.
153 - * @param string $scss_config - File search mask.
154 - * @return array
283 + * Only `(min-width: Npx)` and `(max-width: Npx)` whose N is a default breakpoint change,
284 + * in one pass, so a custom value equal to another default is never replaced twice.
285 + *
286 + * @param string $css - CSS to process.
287 + * @param array $breakpoints - Breakpoints keyed xs, sm, md, lg.
288 + * @return string
155 289 */
156 - protected function get_parsed_scss_files( &$compile_scss_configs, $scss_path, $upload_dir, $scss_config ) {
157 - foreach ( glob( $scss_path . $scss_config ) as $template ) {
158 - $output_file = str_replace( $scss_path, $upload_dir['basedir'] . '/' . $this->plugin_name . '/', $template );
159 - $output_file = str_replace( '.scss', '.min.css', $output_file );
290 + public static function replace_breakpoints( $css, $breakpoints ) {
291 + $defaults = array(
292 + 'xs' => self::$default_xs,
293 + 'sm' => self::$default_sm,
294 + 'md' => self::$default_md,
295 + 'lg' => self::$default_lg,
296 + );
297 + $map = array();
160 298
161 - $compile_scss_configs[] = array(
162 - 'input_file' => $template,
163 - 'output_file' => $output_file,
164 - );
299 + foreach ( $defaults as $name => $default ) {
300 + if ( isset( $breakpoints[ $name ] ) && is_numeric( $breakpoints[ $name ] ) ) {
301 + // Themes pass fractions such as 777.98 to keep a max-width query from
302 + // overlapping the min-width one, so the value is kept as given.
303 + $map[ $default ] = (string) ( 0 + $breakpoints[ $name ] );
304 + }
165 305 }
166 - return $compile_scss_configs;
306 +
307 + return preg_replace_callback(
308 + '/@media[^{;]*\{/',
309 + function ( $prelude ) use ( $map ) {
310 + return preg_replace_callback(
311 + '/\(\s*(min|max)-width\s*:\s*(\d+)px\s*\)/',
312 + function ( $query ) use ( $map ) {
313 + $value = (int) $query[2];
314 +
315 + if ( ! isset( $map[ $value ] ) ) {
316 + return $query[0];
317 + }
318 +
319 + return '(' . $query[1] . '-width:' . $map[ $value ] . 'px)';
320 + },
321 + $prelude[0]
322 + );
323 + },
324 + $css
325 + );
167 326 }
168 327
169 328 /**
170 - * Change style src to compile css files.
329 + * Serve the generated CSS instead of the plugin file when custom breakpoints are set.
171 330 *
172 331 * @param string $src - Url to style.
173 332 * @return string
174 333 */
175 334 public function change_style_src_to_compile( $src ) {
176 - $plugin_url = $this->get_plugin_url();
177 - $breakpoints = self::get_breakpoints();
178 - $breakpoints_hash = $this->get_breakpoints_hash( $breakpoints );
179 - $default_breakpoints_hash = $this->get_default_breakpoints_hash();
335 + $stored = $this->get_generated_css();
180 336
181 - if ( $breakpoints_hash !== $default_breakpoints_hash ) {
182 - $is_plugin_file = strstr( $src, $plugin_url );
337 + if ( ! $stored || empty( $stored['files'] ) ) {
338 + return $src;
339 + }
183 340
184 - if ( $is_plugin_file ) {
185 - $configs = $this->compile_scss_configs;
186 - $relative_uri = str_replace( $plugin_url, '', $is_plugin_file );
187 - $relative_path = explode( '?ver=', $relative_uri )[0];
188 - $upload_dir = wp_upload_dir();
189 - $output_file = $upload_dir['basedir'] . '/' . $this->plugin_name . '/' . $relative_path;
341 + $plugin_url = $this->get_plugin_url();
190 342
191 - foreach ( $configs as $config ) {
192 - if (
193 - $config['output_file'] === $output_file &&
194 - file_exists( $output_file )
195 - ) {
196 - // add hash to compiled CSS version to prevent problems with cache.
197 - $uri_with_hash_version = str_replace( '?ver=', '?ver=' . $breakpoints_hash . '.', $relative_uri );
343 + if ( 0 !== strpos( $src, $plugin_url ) ) {
344 + return $src;
345 + }
198 346
199 - $src = $upload_dir['baseurl'] . '/' . $this->plugin_name . '/' . $uri_with_hash_version;
200 - }
201 - }
202 - }
347 + $path = strtok( substr( $src, strlen( $plugin_url ) ), '?' );
348 +
349 + if ( ! in_array( $path, $stored['files'], true ) ) {
350 + return $src;
203 351 }
204 352
205 - return $src;
353 + if ( ! file_exists( $this->get_output_dir() . '/' . $path ) ) {
354 + return $src;
355 + }
356 +
357 + $upload_dir = wp_get_upload_dir();
358 +
359 + return add_query_arg( 'ver', $stored['hash'], $upload_dir['baseurl'] . '/' . $this->plugin_name . '/' . $path );
206 360 }
207 361
208 362 /**
209 363 * Get Breakpoints.
@@ -275,70 +429,8 @@
275 429 'md' => self::$default_md,
276 430 'lg' => self::$default_lg,
277 431 )
278 432 );
279 - }
280 -
281 - /**
282 - * Styles may need to be compiled if breakpoints have been changed.
283 - *
284 - * @return void
285 - */
286 - public function maybe_compile_scss_files() {
287 - $breakpoints = self::get_breakpoints();
288 - $breakpoints_hash = $this->get_breakpoints_hash( $breakpoints );
289 - $default_breakpoints_hash = $this->get_default_breakpoints_hash();
290 - $saved_breakpoints_hash = get_option( $this->database_saved_hash_option_name );
291 -
292 - if (
293 - $breakpoints_hash !== $saved_breakpoints_hash &&
294 - $breakpoints_hash !== $default_breakpoints_hash
295 - ) {
296 - $compile_scss_configs = $this->compile_scss_configs;
297 -
298 - // Replace modules in all SCSS files.
299 - if ( ! empty( $compile_scss_configs ) ) {
300 - $plugin_path = $this->get_plugin_path();
301 -
302 - $scss_paths = apply_filters( 'gkt_scss_paths', $plugin_path );
303 -
304 - if ( ! is_array( $scss_paths ) ) {
305 - $scss_paths = array( $scss_paths );
306 - }
307 -
308 - foreach ( $scss_paths as $path ) {
309 - new GhostKit_Scss_Replace_Modules( $path );
310 - }
311 - }
312 -
313 - $breakpoints = self::get_breakpoints();
314 -
315 - $variables = array(
316 - 'media-xs' => $breakpoints['xs'] . 'px',
317 - 'media-sm' => $breakpoints['sm'] . 'px',
318 - 'media-md' => $breakpoints['md'] . 'px',
319 - 'media-lg' => $breakpoints['lg'] . 'px',
320 - );
321 -
322 - foreach ( $compile_scss_configs as $compile_scss_config ) {
323 - $scss_config_arguments = array_merge(
324 - $compile_scss_config,
325 - array(
326 - 'variables' => $variables,
327 - )
328 - );
329 -
330 - if ( ! file_exists( $compile_scss_config['output_file'] ) ) {
331 - new GhostKit_Scss_Compiler( $scss_config_arguments );
332 - } else {
333 - $this->breakpoints_background_process->push_to_queue( $scss_config_arguments );
334 - }
335 - }
336 -
337 - $this->breakpoints_background_process->save()->dispatch();
338 -
339 - update_option( $this->database_saved_hash_option_name, $breakpoints_hash );
340 - }
341 433 }
342 434
343 435 /**
344 436 * Get Default Extra Small Breakpoint.