PluginProbe
Gutenberg / 11.8.1
Gutenberg v11.8.1
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / global-styles.php

global-styles.php in Gutenberg 11.8.1, at lib/global-styles.php

370 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bootstraps Global Styles.
4 *
5 * @package gutenberg
6 */
7
8 /**
9 * Takes a tree adhering to the theme.json schema and generates
10 * the corresponding stylesheet.
11 *
12 * @param WP_Theme_JSON_Gutenberg $tree Input tree.
13 * @param string $type Type of stylesheet. It accepts 'all', 'block_styles', 'css_variables', 'presets'.
14 *
15 * @return string Stylesheet.
16 */
17 function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = null ) {
18 // Check if we can use cached.
19 $can_use_cached = (
20 ( 'all' === $type ) &&
21 ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
22 ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
23 ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
24 ! is_admin()
25 );
26
27 $transient_name = 'gutenberg_global_styles_' . get_stylesheet();
28 if ( $can_use_cached ) {
29 // Check if we have the styles already cached.
30 // It's cached by theme to make sure that theme switching
31 // is inmediately reflected.
32 $cached = get_transient( $transient_name );
33 if ( $cached ) {
34 return $cached;
35 }
36 }
37
38 $supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support();
39 $supports_link_color = get_theme_support( 'experimental-link-color' );
40
41 // Only modify the $type if the consumer hasn't provided any.
42 if ( null === $type && ! $supports_theme_json ) {
43 $type = 'presets';
44 } elseif ( null === $type ) {
45 $type = 'all';
46 }
47
48 $origins = array( 'core', 'theme', 'user' );
49 if ( ! $supports_theme_json && ! $supports_link_color ) {
50 // In this case we only enqueue the core presets (CSS Custom Properties + the classes).
51 $origins = array( 'core' );
52 } elseif ( ! $supports_theme_json && $supports_link_color ) {
53 // For the legacy link color feauter to work, the CSS Custom Properties
54 // should be in scope (either the core or the theme ones).
55 $origins = array( 'core', 'theme' );
56 }
57
58 $stylesheet = $tree->get_stylesheet( $type, $origins );
59
60 if ( $can_use_cached ) {
61 // Cache for a minute.
62 // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites.
63 set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS );
64 }
65
66 return $stylesheet;
67 }
68
69 /**
70 * Fetches the preferences for each origin (core, theme, user)
71 * and enqueues the resulting stylesheet.
72 */
73 function gutenberg_experimental_global_styles_enqueue_assets() {
74 $settings = gutenberg_get_default_block_editor_settings();
75 $all = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings );
76
77 $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all );
78 if ( empty( $stylesheet ) ) {
79 return;
80 }
81
82 if ( isset( wp_styles()->registered['global-styles'] ) ) {
83 wp_styles()->registered['global-styles']->extra['after'][0] = $stylesheet;
84 } else {
85 wp_register_style( 'global-styles', false, array(), true, true );
86 wp_add_inline_style( 'global-styles', $stylesheet );
87 wp_enqueue_style( 'global-styles' );
88 }
89 }
90
91 /**
92 * Adds the necessary settings for the Global Styles client UI.
93 *
94 * @param array $settings Existing block editor settings.
95 *
96 * @return array New block editor settings.
97 */
98 function gutenberg_experimental_global_styles_settings( $settings ) {
99 // Set what is the context for this data request.
100 $context = 'other';
101 if (
102 is_callable( 'get_current_screen' ) &&
103 function_exists( 'gutenberg_is_edit_site_page' ) &&
104 gutenberg_is_edit_site_page( get_current_screen()->id )
105 ) {
106 $context = 'site-editor';
107 }
108
109 if (
110 defined( 'REST_REQUEST' ) &&
111 REST_REQUEST &&
112 isset( $_GET['context'] ) &&
113 'mobile' === $_GET['context']
114 ) {
115 $context = 'mobile';
116 }
117
118 $consolidated = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings );
119
120 if ( 'mobile' === $context && WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) {
121 $settings['__experimentalStyles'] = $consolidated->get_raw_data()['styles'];
122 }
123
124 if ( 'site-editor' === $context && gutenberg_experimental_is_site_editor_available() ) {
125 $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings, 'theme' );
126 $user_cpt_id = WP_Theme_JSON_Resolver_Gutenberg::get_user_custom_post_type_id();
127
128 $settings['__experimentalGlobalStylesUserEntityId'] = $user_cpt_id;
129 $settings['__experimentalGlobalStylesBaseConfig']['styles'] = $theme->get_raw_data()['styles'];
130 $settings['__experimentalGlobalStylesBaseConfig']['settings'] = $theme->get_settings();
131 }
132
133 if ( 'other' === $context ) {
134 $block_styles = array( 'css' => gutenberg_experimental_global_styles_get_stylesheet( $consolidated, 'block_styles' ) );
135 $css_variables = array(
136 'css' => gutenberg_experimental_global_styles_get_stylesheet( $consolidated, 'css_variables' ),
137 '__experimentalNoWrapper' => true,
138 );
139
140 // Make sure the styles array exists.
141 // In some contexts, like the navigation editor, it doesn't.
142 if ( ! isset( $settings['styles'] ) ) {
143 $settings['styles'] = array();
144 }
145
146 // Reset existing global styles.
147 $styles_without_existing_global_styles = array();
148 foreach ( $settings['styles'] as $style ) {
149 if ( ! isset( $style['__unstableType'] ) || 'globalStyles' !== $style['__unstableType'] ) {
150 $styles_without_existing_global_styles[] = $style;
151 }
152 }
153
154 // Add the new ones.
155 $styles_without_existing_global_styles[] = $css_variables;
156 $styles_without_existing_global_styles[] = $block_styles;
157 $settings['styles'] = $styles_without_existing_global_styles;
158 }
159
160 // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php.
161 $settings['__experimentalFeatures'] = $consolidated->get_settings();
162
163 if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) {
164 $colors_by_origin = $settings['__experimentalFeatures']['color']['palette'];
165 $settings['colors'] = isset( $colors_by_origin['user'] ) ?
166 $colors_by_origin['user'] : (
167 isset( $colors_by_origin['theme'] ) ?
168 $colors_by_origin['theme'] :
169 $colors_by_origin['core']
170 );
171 }
172
173 if ( isset( $settings['__experimentalFeatures']['color']['gradients'] ) ) {
174 $gradients_by_origin = $settings['__experimentalFeatures']['color']['gradients'];
175 $settings['gradients'] = isset( $gradients_by_origin['user'] ) ?
176 $gradients_by_origin['user'] : (
177 isset( $gradients_by_origin['theme'] ) ?
178 $gradients_by_origin['theme'] :
179 $gradients_by_origin['core']
180 );
181 }
182
183 if ( isset( $settings['__experimentalFeatures']['typography']['fontSizes'] ) ) {
184 $font_sizes_by_origin = $settings['__experimentalFeatures']['typography']['fontSizes'];
185 $settings['fontSizes'] = isset( $font_sizes_by_origin['user'] ) ?
186 $font_sizes_by_origin['user'] : (
187 isset( $font_sizes_by_origin['theme'] ) ?
188 $font_sizes_by_origin['theme'] :
189 $font_sizes_by_origin['core']
190 );
191 }
192
193 if ( isset( $settings['__experimentalFeatures']['color']['custom'] ) ) {
194 $settings['disableCustomColors'] = ! $settings['__experimentalFeatures']['color']['custom'];
195 unset( $settings['__experimentalFeatures']['color']['custom'] );
196 }
197 if ( isset( $settings['__experimentalFeatures']['color']['customGradient'] ) ) {
198 $settings['disableCustomGradients'] = ! $settings['__experimentalFeatures']['color']['customGradient'];
199 unset( $settings['__experimentalFeatures']['color']['customGradient'] );
200 }
201 if ( isset( $settings['__experimentalFeatures']['typography']['customFontSize'] ) ) {
202 $settings['disableCustomFontSizes'] = ! $settings['__experimentalFeatures']['typography']['customFontSize'];
203 unset( $settings['__experimentalFeatures']['typography']['customFontSize'] );
204 }
205 if ( isset( $settings['__experimentalFeatures']['typography']['customLineHeight'] ) ) {
206 $settings['enableCustomLineHeight'] = $settings['__experimentalFeatures']['typography']['customLineHeight'];
207 unset( $settings['__experimentalFeatures']['typography']['customLineHeight'] );
208 }
209 if ( isset( $settings['__experimentalFeatures']['spacing']['units'] ) ) {
210 $settings['enableCustomUnits'] = $settings['__experimentalFeatures']['spacing']['units'];
211 }
212 if ( isset( $settings['__experimentalFeatures']['spacing']['customPadding'] ) ) {
213 $settings['enableCustomSpacing'] = $settings['__experimentalFeatures']['spacing']['customPadding'];
214 unset( $settings['__experimentalFeatures']['spacing']['customPadding'] );
215 }
216
217 return $settings;
218 }
219
220 /**
221 * Whether or not the Site Editor is available.
222 *
223 * @return boolean
224 */
225 function gutenberg_experimental_is_site_editor_available() {
226 return gutenberg_is_fse_theme();
227 }
228
229 /**
230 * Register CPT to store/access user data.
231 *
232 * @return void
233 */
234 function gutenberg_experimental_global_styles_register_user_cpt() {
235 if ( gutenberg_experimental_is_site_editor_available() ) {
236 WP_Theme_JSON_Resolver_Gutenberg::register_user_custom_post_type();
237 }
238 }
239
240 /**
241 * Sanitizes global styles user content removing unsafe rules.
242 *
243 * @param string $content Post content to filter.
244 * @return string Filtered post content with unsafe rules removed.
245 */
246 function gutenberg_global_styles_filter_post( $content ) {
247 $decoded_data = json_decode( wp_unslash( $content ), true );
248 $json_decoding_error = json_last_error();
249 if (
250 JSON_ERROR_NONE === $json_decoding_error &&
251 is_array( $decoded_data ) &&
252 isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
253 $decoded_data['isGlobalStylesUserThemeJSON']
254 ) {
255 unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
256
257 $data_to_encode = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $decoded_data );
258
259 $data_to_encode['isGlobalStylesUserThemeJSON'] = true;
260 return wp_slash( wp_json_encode( $data_to_encode ) );
261 }
262 return $content;
263 }
264
265 /**
266 * Adds the filters to filter global styles user theme.json.
267 */
268 function gutenberg_global_styles_kses_init_filters() {
269 add_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' );
270 add_filter( 'safe_style_css', 'gutenberg_global_styles_include_support_for_duotone', 10, 2 );
271 }
272
273 /**
274 * Removes the filters to filter global styles user theme.json.
275 */
276 function gutenberg_global_styles_kses_remove_filters() {
277 remove_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' );
278 }
279
280 /**
281 * Register global styles kses filters if the user does not have unfiltered_html capability.
282 *
283 * @uses render_block_core_navigation()
284 * @throws WP_Error An WP_Error exception parsing the block definition.
285 */
286 function gutenberg_global_styles_kses_init() {
287 gutenberg_global_styles_kses_remove_filters();
288 if ( ! current_user_can( 'unfiltered_html' ) ) {
289 gutenberg_global_styles_kses_init_filters();
290 }
291 }
292
293 /**
294 * This filter is the last being executed on force_filtered_html_on_import.
295 * If the input of the filter is true it means we are in an import situation and should
296 * enable kses, independently of the user capabilities.
297 * So in that case we call gutenberg_global_styles_kses_init_filters;
298 *
299 * @param string $arg Input argument of the filter.
300 * @return string Exactly what was passed as argument.
301 */
302 function gutenberg_global_styles_force_filtered_html_on_import_filter( $arg ) {
303 // force_filtered_html_on_import is true we need to init the global styles kses filters.
304 if ( $arg ) {
305 gutenberg_global_styles_kses_init_filters();
306 }
307 return $arg;
308 }
309
310 /**
311 * This filter is the last being executed on force_filtered_html_on_import.
312 * If the input of the filter is true it means we are in an import situation and should
313 * enable kses, independently of the user capabilities.
314 * So in that case we call gutenberg_global_styles_kses_init_filters;
315 *
316 * @param bool $allow_css Whether the CSS in the test string is considered safe.
317 * @param bool $css_test_string The CSS string to test..
318 * @return bool If $allow_css is true it returns true.
319 * If $allow_css is false and the CSS rule is referencing a WordPress css variable it returns true.
320 * Otherwise the function return false.
321 */
322 function gutenberg_global_styles_include_support_for_wp_variables( $allow_css, $css_test_string ) {
323 if ( $allow_css ) {
324 return $allow_css;
325 }
326 $allowed_preset_attributes = array(
327 'background',
328 'background-color',
329 'border-color',
330 'color',
331 'font-family',
332 'font-size',
333 );
334 $parts = explode( ':', $css_test_string, 2 );
335
336 if ( ! in_array( trim( $parts[0] ), $allowed_preset_attributes, true ) ) {
337 return $allow_css;
338 }
339 return ! ! preg_match( '/^var\(--wp-[a-zA-Z0-9\-]+\)$/', trim( $parts[1] ) );
340 }
341
342 /**
343 * This is for using kses to test user data.
344 *
345 * @param array $atts Allowed CSS property names, according to kses.
346 *
347 * @return array The new allowed CSS property names.
348 */
349 function gutenberg_global_styles_include_support_for_duotone( $atts ) {
350 $atts[] = 'filter';
351 return $atts;
352 }
353
354 // The else clause can be removed when plugin support requires WordPress 5.8.0+.
355 if ( function_exists( 'get_block_editor_settings' ) ) {
356 add_filter( 'block_editor_settings_all', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX );
357 } else {
358 add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX );
359 }
360
361 add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' );
362 add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' );
363
364 // kses actions&filters.
365 add_action( 'init', 'gutenberg_global_styles_kses_init' );
366 add_action( 'set_current_user', 'gutenberg_global_styles_kses_init' );
367 add_filter( 'force_filtered_html_on_import', 'gutenberg_global_styles_force_filtered_html_on_import_filter', 999 );
368 add_filter( 'safecss_filter_attr_allow_css', 'gutenberg_global_styles_include_support_for_wp_variables', 10, 2 );
369 // This filter needs to be executed last.
370