PluginProbe
Gutenberg / 11.7.0
Gutenberg v11.7.0
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.7.0, at lib/global-styles.php

356 lines 12.8 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['__experimentalGlobalStylesBaseStyles'] = $theme->get_raw_data();
130 }
131
132 if ( 'other' === $context ) {
133 $block_styles = array( 'css' => gutenberg_experimental_global_styles_get_stylesheet( $consolidated, 'block_styles' ) );
134 $css_variables = array(
135 'css' => gutenberg_experimental_global_styles_get_stylesheet( $consolidated, 'css_variables' ),
136 '__experimentalNoWrapper' => true,
137 );
138
139 // Make sure the styles array exists.
140 // In some contexts, like the navigation editor, it doesn't.
141 if ( ! isset( $settings['styles'] ) ) {
142 $settings['styles'] = array();
143 }
144
145 // Reset existing global styles.
146 $styles_without_existing_global_styles = array();
147 foreach ( $settings['styles'] as $style ) {
148 if ( ! isset( $style['__unstableType'] ) || 'globalStyles' !== $style['__unstableType'] ) {
149 $styles_without_existing_global_styles[] = $style;
150 }
151 }
152
153 // Add the new ones.
154 $styles_without_existing_global_styles[] = $css_variables;
155 $styles_without_existing_global_styles[] = $block_styles;
156 $settings['styles'] = $styles_without_existing_global_styles;
157 }
158
159 // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php.
160 $settings['__experimentalFeatures'] = $consolidated->get_settings();
161
162 if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) {
163 $colors_by_origin = $settings['__experimentalFeatures']['color']['palette'];
164 $settings['colors'] = isset( $colors_by_origin['user'] ) ?
165 $colors_by_origin['user'] : (
166 isset( $colors_by_origin['theme'] ) ?
167 $colors_by_origin['theme'] :
168 $colors_by_origin['core']
169 );
170 }
171
172 if ( isset( $settings['__experimentalFeatures']['color']['gradients'] ) ) {
173 $gradients_by_origin = $settings['__experimentalFeatures']['color']['gradients'];
174 $settings['gradients'] = isset( $gradients_by_origin['user'] ) ?
175 $gradients_by_origin['user'] : (
176 isset( $gradients_by_origin['theme'] ) ?
177 $gradients_by_origin['theme'] :
178 $gradients_by_origin['core']
179 );
180 }
181
182 if ( isset( $settings['__experimentalFeatures']['typography']['fontSizes'] ) ) {
183 $font_sizes_by_origin = $settings['__experimentalFeatures']['typography']['fontSizes'];
184 $settings['fontSizes'] = isset( $font_sizes_by_origin['user'] ) ?
185 $font_sizes_by_origin['user'] : (
186 isset( $font_sizes_by_origin['theme'] ) ?
187 $font_sizes_by_origin['theme'] :
188 $font_sizes_by_origin['core']
189 );
190 }
191
192 if ( isset( $settings['__experimentalFeatures']['color']['custom'] ) ) {
193 $settings['disableCustomColors'] = ! $settings['__experimentalFeatures']['color']['custom'];
194 unset( $settings['__experimentalFeatures']['color']['custom'] );
195 }
196 if ( isset( $settings['__experimentalFeatures']['color']['customGradient'] ) ) {
197 $settings['disableCustomGradients'] = ! $settings['__experimentalFeatures']['color']['customGradient'];
198 unset( $settings['__experimentalFeatures']['color']['customGradient'] );
199 }
200 if ( isset( $settings['__experimentalFeatures']['typography']['customFontSize'] ) ) {
201 $settings['disableCustomFontSizes'] = ! $settings['__experimentalFeatures']['typography']['customFontSize'];
202 unset( $settings['__experimentalFeatures']['typography']['customFontSize'] );
203 }
204 if ( isset( $settings['__experimentalFeatures']['typography']['customLineHeight'] ) ) {
205 $settings['enableCustomLineHeight'] = $settings['__experimentalFeatures']['typography']['customLineHeight'];
206 unset( $settings['__experimentalFeatures']['typography']['customLineHeight'] );
207 }
208 if ( isset( $settings['__experimentalFeatures']['spacing']['units'] ) ) {
209 $settings['enableCustomUnits'] = $settings['__experimentalFeatures']['spacing']['units'];
210 }
211 if ( isset( $settings['__experimentalFeatures']['spacing']['customPadding'] ) ) {
212 $settings['enableCustomSpacing'] = $settings['__experimentalFeatures']['spacing']['customPadding'];
213 unset( $settings['__experimentalFeatures']['spacing']['customPadding'] );
214 }
215
216 return $settings;
217 }
218
219 /**
220 * Whether or not the Site Editor is available.
221 *
222 * @return boolean
223 */
224 function gutenberg_experimental_is_site_editor_available() {
225 return gutenberg_is_fse_theme();
226 }
227
228 /**
229 * Register CPT to store/access user data.
230 *
231 * @return void
232 */
233 function gutenberg_experimental_global_styles_register_user_cpt() {
234 if ( gutenberg_experimental_is_site_editor_available() ) {
235 WP_Theme_JSON_Resolver_Gutenberg::register_user_custom_post_type();
236 }
237 }
238
239 /**
240 * Sanitizes global styles user content removing unsafe rules.
241 *
242 * @param string $content Post content to filter.
243 * @return string Filtered post content with unsafe rules removed.
244 */
245 function gutenberg_global_styles_filter_post( $content ) {
246 $decoded_data = json_decode( wp_unslash( $content ), true );
247 $json_decoding_error = json_last_error();
248 if (
249 JSON_ERROR_NONE === $json_decoding_error &&
250 is_array( $decoded_data ) &&
251 isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
252 $decoded_data['isGlobalStylesUserThemeJSON']
253 ) {
254 unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
255
256 $data_to_encode = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $decoded_data );
257
258 $data_to_encode['isGlobalStylesUserThemeJSON'] = true;
259 return wp_slash( wp_json_encode( $data_to_encode ) );
260 }
261 return $content;
262 }
263
264 /**
265 * Adds the filters to filter global styles user theme.json.
266 */
267 function gutenberg_global_styles_kses_init_filters() {
268 add_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' );
269 }
270
271 /**
272 * Removes the filters to filter global styles user theme.json.
273 */
274 function gutenberg_global_styles_kses_remove_filters() {
275 remove_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' );
276 }
277
278 /**
279 * Register global styles kses filters if the user does not have unfiltered_html capability.
280 *
281 * @uses render_block_core_navigation()
282 * @throws WP_Error An WP_Error exception parsing the block definition.
283 */
284 function gutenberg_global_styles_kses_init() {
285 gutenberg_global_styles_kses_remove_filters();
286 if ( ! current_user_can( 'unfiltered_html' ) ) {
287 gutenberg_global_styles_kses_init_filters();
288 }
289 }
290
291 /**
292 * This filter is the last being executed on force_filtered_html_on_import.
293 * If the input of the filter is true it means we are in an import situation and should
294 * enable kses, independently of the user capabilities.
295 * So in that case we call gutenberg_global_styles_kses_init_filters;
296 *
297 * @param string $arg Input argument of the filter.
298 * @return string Exactly what was passed as argument.
299 */
300 function gutenberg_global_styles_force_filtered_html_on_import_filter( $arg ) {
301 // force_filtered_html_on_import is true we need to init the global styles kses filters.
302 if ( $arg ) {
303 gutenberg_global_styles_kses_init_filters();
304 }
305 return $arg;
306 }
307
308 /**
309 * This filter is the last being executed on force_filtered_html_on_import.
310 * If the input of the filter is true it means we are in an import situation and should
311 * enable kses, independently of the user capabilities.
312 * So in that case we call gutenberg_global_styles_kses_init_filters;
313 *
314 * @param bool $allow_css Whether the CSS in the test string is considered safe.
315 * @param bool $css_test_string The CSS string to test..
316 * @return bool If $allow_css is true it returns true.
317 * If $allow_css is false and the CSS rule is referencing a WordPress css variable it returns true.
318 * Otherwise the function return false.
319 */
320 function gutenberg_global_styles_include_support_for_wp_variables( $allow_css, $css_test_string ) {
321 if ( $allow_css ) {
322 return $allow_css;
323 }
324 $allowed_preset_attributes = array(
325 'background',
326 'background-color',
327 'border-color',
328 'color',
329 'font-family',
330 'font-size',
331 );
332 $parts = explode( ':', $css_test_string, 2 );
333
334 if ( ! in_array( trim( $parts[0] ), $allowed_preset_attributes, true ) ) {
335 return $allow_css;
336 }
337 return ! ! preg_match( '/^var\(--wp-[a-zA-Z0-9\-]+\)$/', trim( $parts[1] ) );
338 }
339
340 // The else clause can be removed when plugin support requires WordPress 5.8.0+.
341 if ( function_exists( 'get_block_editor_settings' ) ) {
342 add_filter( 'block_editor_settings_all', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX );
343 } else {
344 add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX );
345 }
346
347 add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' );
348 add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' );
349
350 // kses actions&filters.
351 add_action( 'init', 'gutenberg_global_styles_kses_init' );
352 add_action( 'set_current_user', 'gutenberg_global_styles_kses_init' );
353 add_filter( 'force_filtered_html_on_import', 'gutenberg_global_styles_force_filtered_html_on_import_filter', 999 );
354 add_filter( 'safecss_filter_attr_allow_css', 'gutenberg_global_styles_include_support_for_wp_variables', 10, 2 );
355 // This filter needs to be executed last.
356