PluginProbe
Gutenberg / 10.3.0
Gutenberg v10.3.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 10.3.0, at lib/global-styles.php

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