PluginProbe
Gutenberg / 9.9.1
Gutenberg v9.9.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 9.9.1, at lib/global-styles.php

394 lines 14.4 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 * Whether the current theme has a theme.json file.
10 *
11 * @return boolean
12 */
13 function gutenberg_experimental_global_styles_has_theme_json_support() {
14 return is_readable( locate_template( 'experimental-theme.json' ) );
15 }
16
17 /**
18 * Returns the theme presets registered via add_theme_support, if any.
19 *
20 * @param array $settings Existing editor settings.
21 *
22 * @return array Config that adheres to the theme.json schema.
23 */
24 function gutenberg_experimental_global_styles_get_theme_support_settings( $settings ) {
25 $all_blocks = WP_Theme_JSON::ALL_BLOCKS_NAME;
26 $theme_settings = array();
27 $theme_settings['settings'] = array();
28 $theme_settings['settings'][ $all_blocks ] = array();
29
30 // Deprecated theme supports.
31 if ( isset( $settings['disableCustomColors'] ) ) {
32 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) {
33 $theme_settings['settings'][ $all_blocks ]['color'] = array();
34 }
35 $theme_settings['settings'][ $all_blocks ]['color']['custom'] = ! $settings['disableCustomColors'];
36 }
37
38 if ( isset( $settings['disableCustomGradients'] ) ) {
39 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) {
40 $theme_settings['settings'][ $all_blocks ]['color'] = array();
41 }
42 $theme_settings['settings'][ $all_blocks ]['color']['customGradient'] = ! $settings['disableCustomGradients'];
43 }
44
45 if ( isset( $settings['disableCustomFontSizes'] ) ) {
46 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['typography'] ) ) {
47 $theme_settings['settings'][ $all_blocks ]['typography'] = array();
48 }
49 $theme_settings['settings'][ $all_blocks ]['typography']['customFontSize'] = ! $settings['disableCustomFontSizes'];
50 }
51
52 if ( isset( $settings['enableCustomLineHeight'] ) ) {
53 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['typography'] ) ) {
54 $theme_settings['settings'][ $all_blocks ]['typography'] = array();
55 }
56 $theme_settings['settings'][ $all_blocks ]['typography']['customLineHeight'] = $settings['enableCustomLineHeight'];
57 }
58
59 if ( isset( $settings['enableCustomUnits'] ) ) {
60 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['spacing'] ) ) {
61 $theme_settings['settings'][ $all_blocks ]['spacing'] = array();
62 }
63 $theme_settings['settings'][ $all_blocks ]['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ?
64 array( 'px', 'em', 'rem', 'vh', 'vw' ) :
65 $settings['enableCustomUnits'];
66 }
67
68 if ( isset( $settings['colors'] ) ) {
69 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) {
70 $theme_settings['settings'][ $all_blocks ]['color'] = array();
71 }
72 $theme_settings['settings'][ $all_blocks ]['color']['palette'] = $settings['colors'];
73 }
74
75 if ( isset( $settings['gradients'] ) ) {
76 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) {
77 $theme_settings['settings'][ $all_blocks ]['color'] = array();
78 }
79 $theme_settings['settings'][ $all_blocks ]['color']['gradients'] = $settings['gradients'];
80 }
81
82 if ( isset( $settings['fontSizes'] ) ) {
83 $font_sizes = $settings['fontSizes'];
84 // Back-compatibility for presets without units.
85 foreach ( $font_sizes as &$font_size ) {
86 if ( is_numeric( $font_size['size'] ) ) {
87 $font_size['size'] = $font_size['size'] . 'px';
88 }
89 }
90 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['typography'] ) ) {
91 $theme_settings['settings'][ $all_blocks ]['typography'] = array();
92 }
93 $theme_settings['settings'][ $all_blocks ]['typography']['fontSizes'] = $font_sizes;
94 }
95
96 // This allows to make the plugin work with WordPress 5.7 beta
97 // as well as lower versions. The second check can be removed
98 // as soon as the minimum WordPress version for the plugin
99 // is bumped to 5.7.
100 if ( isset( $settings['enableCustomSpacing'] ) ) {
101 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['spacing'] ) ) {
102 $theme_settings['settings'][ $all_blocks ]['spacing'] = array();
103 }
104 $theme_settings['settings'][ $all_blocks ]['spacing']['customPadding'] = $settings['enableCustomSpacing'];
105 } else if ( current( (array) get_theme_support( 'custom-spacing' ) ) ) {
106 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['spacing'] ) ) {
107 $theme_settings['settings'][ $all_blocks ]['spacing'] = array();
108 }
109 $theme_settings['settings'][ $all_blocks ]['spacing']['customPadding'] = true;
110 }
111
112 // Things that didn't land in core yet, so didn't have a setting assigned.
113 if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) {
114 if ( ! isset( $theme_settings['settings'][ $all_blocks ]['color'] ) ) {
115 $theme_settings['settings'][ $all_blocks ]['color'] = array();
116 }
117 $theme_settings['settings'][ $all_blocks ]['color']['link'] = true;
118 }
119
120 return $theme_settings;
121 }
122
123 /**
124 * Takes a tree adhering to the theme.json schema and generates
125 * the corresponding stylesheet.
126 *
127 * @param WP_Theme_JSON $tree Input tree.
128 * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'.
129 *
130 * @return string Stylesheet.
131 */
132 function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'all' ) {
133 // Check if we can use cached.
134 $can_use_cached = (
135 ( 'all' === $type ) &&
136 ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
137 ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
138 ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
139 ! is_admin()
140 );
141
142 if ( $can_use_cached ) {
143 // Check if we have the styles already cached.
144 $cached = get_transient( 'global_styles' );
145 if ( $cached ) {
146 return $cached;
147 }
148 }
149
150 $stylesheet = $tree->get_stylesheet( $type );
151
152 if ( ( 'all' === $type || 'block_styles' === $type ) && gutenberg_experimental_global_styles_has_theme_json_support() ) {
153 // To support all themes, we added in the block-library stylesheet
154 // a style rule such as .has-link-color a { color: var(--wp--style--color--link, #00e); }
155 // so that existing link colors themes used didn't break.
156 // We add this here to make it work for themes that opt-in to theme.json
157 // In the future, we may do this differently.
158 $stylesheet .= 'a{color:var(--wp--style--color--link, #00e);}';
159 }
160
161 if ( $can_use_cached ) {
162 // Cache for a minute.
163 // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites.
164 set_transient( 'global_styles', $stylesheet, MINUTE_IN_SECONDS );
165 }
166
167 return $stylesheet;
168 }
169
170 /**
171 * Fetches the preferences for each origin (core, theme, user)
172 * and enqueues the resulting stylesheet.
173 */
174 function gutenberg_experimental_global_styles_enqueue_assets() {
175 if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) {
176 return;
177 }
178
179 $settings = gutenberg_get_common_block_editor_settings();
180 $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings );
181
182 $resolver = new WP_Theme_JSON_Resolver();
183 $all = $resolver->get_origin( $theme_support_data );
184
185 $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all );
186 if ( empty( $stylesheet ) ) {
187 return;
188 }
189
190 wp_register_style( 'global-styles', false, array(), true, true );
191 wp_add_inline_style( 'global-styles', $stylesheet );
192 wp_enqueue_style( 'global-styles' );
193 }
194
195 /**
196 * Adds the necessary data for the Global Styles client UI to the block settings.
197 *
198 * @param array $settings Existing block editor settings.
199 * @return array New block editor settings
200 */
201 function gutenberg_experimental_global_styles_settings( $settings ) {
202 $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings );
203 unset( $settings['colors'] );
204 unset( $settings['disableCustomColors'] );
205 unset( $settings['disableCustomFontSizes'] );
206 unset( $settings['disableCustomGradients'] );
207 unset( $settings['enableCustomLineHeight'] );
208 unset( $settings['enableCustomUnits'] );
209 unset( $settings['enableCustomSpacing'] );
210 unset( $settings['fontSizes'] );
211 unset( $settings['gradients'] );
212
213 $resolver = new WP_Theme_JSON_Resolver();
214 $origin = 'theme';
215 if (
216 gutenberg_experimental_global_styles_has_theme_json_support() &&
217 gutenberg_is_fse_theme()
218 ) {
219 // Only lookup for the user data if we need it.
220 $origin = 'user';
221 }
222 $tree = $resolver->get_origin( $theme_support_data, $origin );
223
224 // STEP 1: ADD FEATURES
225 //
226 // These need to be always added to the editor settings,
227 // even for themes that don't support theme.json.
228 // An example of this is that the presets are configured
229 // from the theme support data.
230 $settings['__experimentalFeatures'] = $tree->get_settings();
231
232 // STEP 2 - IF EDIT-SITE, ADD DATA REQUIRED FOR GLOBAL STYLES SIDEBAR
233 //
234 // In the site editor, the user can change styles, so the client
235 // needs the ability to create them. Hence, we pass it some data
236 // for this: base styles (core+theme) and the ID of the user CPT.
237 $screen = get_current_screen();
238 if (
239 ! empty( $screen ) &&
240 function_exists( 'gutenberg_is_edit_site_page' ) &&
241 gutenberg_is_edit_site_page( $screen->id ) &&
242 gutenberg_experimental_global_styles_has_theme_json_support() &&
243 gutenberg_is_fse_theme()
244 ) {
245 $user_cpt_id = WP_Theme_JSON_Resolver::get_user_custom_post_type_id();
246 $base_styles = $resolver->get_origin( $theme_support_data, 'theme' )->get_raw_data();
247
248 $settings['__experimentalGlobalStylesUserEntityId'] = $user_cpt_id;
249 $settings['__experimentalGlobalStylesBaseStyles'] = $base_styles;
250 } elseif ( gutenberg_experimental_global_styles_has_theme_json_support() ) {
251 // STEP 3 - ADD STYLES IF THEME HAS SUPPORT
252 //
253 // If we are in a block editor context, but not in edit-site,
254 // we add the styles via the settings, so the editor knows that
255 // some of these should be added the wrapper class,
256 // as if they were added via add_editor_styles.
257 $settings['styles'][] = array(
258 'css' => gutenberg_experimental_global_styles_get_stylesheet( $tree, 'css_variables' ),
259 '__experimentalNoWrapper' => true,
260 );
261 $settings['styles'][] = array(
262 'css' => gutenberg_experimental_global_styles_get_stylesheet( $tree, 'block_styles' ),
263 );
264 }
265
266 return $settings;
267 }
268
269 /**
270 * Register CPT to store/access user data.
271 *
272 * @return array|undefined
273 */
274 function gutenberg_experimental_global_styles_register_user_cpt() {
275 if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) {
276 return;
277 }
278
279 WP_Theme_JSON_Resolver::register_user_custom_post_type();
280 }
281
282 add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' );
283 add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX );
284 add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' );
285
286
287 /**
288 * Sanitizes global styles user content removing unsafe rules.
289 *
290 * @param string $content Post content to filter.
291 * @return string Filtered post content with unsafe rules removed.
292 */
293 function gutenberg_global_styles_filter_post( $content ) {
294 $decoded_data = json_decode( stripslashes( $content ), true );
295 $json_decoding_error = json_last_error();
296 if (
297 JSON_ERROR_NONE === $json_decoding_error &&
298 is_array( $decoded_data ) &&
299 isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
300 $decoded_data['isGlobalStylesUserThemeJSON']
301 ) {
302 unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
303 $theme_json = new WP_Theme_JSON( $decoded_data );
304 $theme_json->remove_insecure_properties();
305 $data_to_encode = $theme_json->get_raw_data();
306 $data_to_encode['isGlobalStylesUserThemeJSON'] = true;
307 return wp_json_encode( $data_to_encode );
308 }
309 return $content;
310 }
311
312 /**
313 * Adds the filters to filter global styles user theme.json.
314 */
315 function gutenberg_global_styles_kses_init_filters() {
316 add_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' );
317 }
318
319 /**
320 * Removes the filters to filter global styles user theme.json.
321 */
322 function gutenberg_global_styles_kses_remove_filters() {
323 remove_filter( 'content_save_pre', 'gutenberg_global_styles_filter_post' );
324 }
325
326 /**
327 * Register global styles kses filters if the user does not have unfiltered_html capability.
328 *
329 * @uses render_block_core_navigation()
330 * @throws WP_Error An WP_Error exception parsing the block definition.
331 */
332 function gutenberg_global_styles_kses_init() {
333 gutenberg_global_styles_kses_remove_filters();
334 if ( ! current_user_can( 'unfiltered_html' ) ) {
335 gutenberg_global_styles_kses_init_filters();
336 }
337 }
338
339 /**
340 * This filter is the last being executed on force_filtered_html_on_import.
341 * If the input of the filter is true it means we are in an import situation and should
342 * enable kses, independently of the user capabilities.
343 * So in that case we call gutenberg_global_styles_kses_init_filters;
344 *
345 * @param string $arg Input argument of the filter.
346 * @return string Exactly what was passed as argument.
347 */
348 function gutenberg_global_styles_force_filtered_html_on_import_filter( $arg ) {
349 // force_filtered_html_on_import is true we need to init the global styles kses filters.
350 if ( $arg ) {
351 gutenberg_global_styles_kses_init_filters();
352 }
353 return $arg;
354 }
355
356 /**
357 * This filter is the last being executed on force_filtered_html_on_import.
358 * If the input of the filter is true it means we are in an import situation and should
359 * enable kses, independently of the user capabilities.
360 * So in that case we call gutenberg_global_styles_kses_init_filters;
361 *
362 * @param bool $allow_css Whether the CSS in the test string is considered safe.
363 * @param bool $css_test_string The CSS string to test..
364 * @return bool If $allow_css is true it returns true.
365 * If $allow_css is false and the CSS rule is referencing a WordPress css variable it returns true.
366 * Otherwise the function return false.
367 */
368 function gutenberg_global_styles_include_support_for_wp_variables( $allow_css, $css_test_string ) {
369 if ( $allow_css ) {
370 return $allow_css;
371 }
372 $allowed_preset_attributes = array(
373 'background',
374 'background-color',
375 'color',
376 'font-family',
377 'font-size',
378 );
379 $parts = explode( ':', $css_test_string, 2 );
380
381 if ( ! in_array( trim( $parts[0] ), $allowed_preset_attributes, true ) ) {
382 return $allow_css;
383 }
384 return ! ! preg_match( '/^var\(--wp-[a-zA-Z0-9\-]+\)$/', trim( $parts[1] ) );
385 }
386
387
388 add_action( 'init', 'gutenberg_global_styles_kses_init' );
389 add_action( 'set_current_user', 'gutenberg_global_styles_kses_init' );
390 add_filter( 'force_filtered_html_on_import', 'gutenberg_global_styles_force_filtered_html_on_import_filter', 999 );
391 add_filter( 'safecss_filter_attr_allow_css', 'gutenberg_global_styles_include_support_for_wp_variables', 10, 2 );
392 // This filter needs to be executed last.
393
394