PluginProbe
Gutenberg / 9.4.1
Gutenberg v9.4.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.4.1, at lib/global-styles.php

506 lines 18.3 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 * Processes a file that adheres to the theme.json
19 * schema and returns an array with its contents,
20 * or a void array if none found.
21 *
22 * @param string $file_path Path to file.
23 * @return array Contents that adhere to the theme.json schema.
24 */
25 function gutenberg_experimental_global_styles_get_from_file( $file_path ) {
26 $config = array();
27 if ( file_exists( $file_path ) ) {
28 $decoded_file = json_decode(
29 file_get_contents( $file_path ),
30 true
31 );
32
33 $json_decoding_error = json_last_error();
34 if ( JSON_ERROR_NONE !== $json_decoding_error ) {
35 error_log( 'Error when decoding file schema: ' . json_last_error_msg() );
36 return $config;
37 }
38
39 if ( is_array( $decoded_file ) ) {
40 $config = $decoded_file;
41 }
42 }
43 return $config;
44 }
45
46 /**
47 * Returns the user's origin config.
48 *
49 * @return WP_Theme_JSON Entity that holds user data.
50 */
51 function gutenberg_experimental_global_styles_get_user() {
52 $config = array();
53 $user_cpt = gutenberg_experimental_global_styles_get_user_cpt();
54 if ( array_key_exists( 'post_content', $user_cpt ) ) {
55 $decoded_data = json_decode( $user_cpt['post_content'], true );
56
57 $json_decoding_error = json_last_error();
58 if ( JSON_ERROR_NONE !== $json_decoding_error ) {
59 error_log( 'Error when decoding user schema: ' . json_last_error_msg() );
60 return $config;
61 }
62
63 if ( is_array( $decoded_data ) ) {
64 $config = $decoded_data;
65 }
66 }
67
68 return new WP_Theme_JSON( $config );
69 }
70
71 /**
72 * Returns the CPT that contains the user's origin config
73 * for the current theme or a void array if none found.
74 *
75 * It can also create and return a new draft CPT.
76 *
77 * @param bool $should_create_cpt Whether a new CPT should be created if no one was found.
78 * False by default.
79 * @param array $post_status_filter Filter CPT by post status.
80 * ['publish'] by default, so it only fetches published posts.
81 * @return array Custom Post Type for the user's origin config.
82 */
83 function gutenberg_experimental_global_styles_get_user_cpt( $should_create_cpt = false, $post_status_filter = array( 'publish' ) ) {
84 $user_cpt = array();
85 $post_type_filter = 'wp_global_styles';
86 $post_name_filter = 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() );
87 $recent_posts = wp_get_recent_posts(
88 array(
89 'numberposts' => 1,
90 'orderby' => 'date',
91 'order' => 'desc',
92 'post_type' => $post_type_filter,
93 'post_status' => $post_status_filter,
94 'name' => $post_name_filter,
95 )
96 );
97
98 if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) {
99 $user_cpt = $recent_posts[0];
100 } elseif ( $should_create_cpt ) {
101 $cpt_post_id = wp_insert_post(
102 array(
103 'post_content' => '{}',
104 'post_status' => 'publish',
105 'post_type' => $post_type_filter,
106 'post_name' => $post_name_filter,
107 ),
108 true
109 );
110 $user_cpt = get_post( $cpt_post_id, ARRAY_A );
111 }
112
113 return $user_cpt;
114 }
115
116 /**
117 * Returns the post ID of the CPT containing the user's origin config.
118 *
119 * @return integer
120 */
121 function gutenberg_experimental_global_styles_get_user_cpt_id() {
122 $user_cpt_id = null;
123 $user_cpt = gutenberg_experimental_global_styles_get_user_cpt( true );
124 if ( array_key_exists( 'ID', $user_cpt ) ) {
125 $user_cpt_id = $user_cpt['ID'];
126 }
127 return $user_cpt_id;
128 }
129
130 /**
131 * Return core's origin config.
132 *
133 * @return WP_Theme_JSON Entity that holds core data.
134 */
135 function gutenberg_experimental_global_styles_get_core() {
136 $config = gutenberg_experimental_global_styles_get_from_file(
137 __DIR__ . '/experimental-default-theme.json'
138 );
139
140 // Start i18n logic to remove when JSON i18 strings are extracted.
141 $default_colors_i18n = array(
142 'black' => __( 'Black', 'gutenberg' ),
143 'cyan-bluish-gray' => __( 'Cyan bluish gray', 'gutenberg' ),
144 'white' => __( 'White', 'gutenberg' ),
145 'pale-pink' => __( 'Pale pink', 'gutenberg' ),
146 'vivid-red' => __( 'Vivid red', 'gutenberg' ),
147 'luminous-vivid-orange' => __( 'Luminous vivid orange', 'gutenberg' ),
148 'luminous-vivid-amber' => __( 'Luminous vivid amber', 'gutenberg' ),
149 'light-green-cyan' => __( 'Light green cyan', 'gutenberg' ),
150 'vivid-green-cyan' => __( 'Vivid green cyan', 'gutenberg' ),
151 'pale-cyan-blue' => __( 'Pale cyan blue', 'gutenberg' ),
152 'vivid-cyan-blue' => __( 'Vivid cyan blue', 'gutenberg' ),
153 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ),
154 );
155 if ( ! empty( $config['global']['settings']['color']['palette'] ) ) {
156 foreach ( $config['global']['settings']['color']['palette'] as &$color ) {
157 $color['name'] = $default_colors_i18n[ $color['slug'] ];
158 }
159 }
160
161 $default_gradients_i18n = array(
162 'vivid-cyan-blue-to-vivid-purple' => __( 'Vivid cyan blue to vivid purple', 'gutenberg' ),
163 'light-green-cyan-to-vivid-green-cyan' => __( 'Light green cyan to vivid green cyan', 'gutenberg' ),
164 'luminous-vivid-amber-to-luminous-vivid-orange' => __( 'Luminous vivid amber to luminous vivid orange', 'gutenberg' ),
165 'luminous-vivid-orange-to-vivid-red' => __( 'Luminous vivid orange to vivid red', 'gutenberg' ),
166 'very-light-gray-to-cyan-bluish-gray' => __( 'Very light gray to cyan bluish gray', 'gutenberg' ),
167 'cool-to-warm-spectrum' => __( 'Cool to warm spectrum', 'gutenberg' ),
168 'blush-light-purple' => __( 'Blush light purple', 'gutenberg' ),
169 'blush-bordeaux' => __( 'Blush bordeaux', 'gutenberg' ),
170 'luminous-dusk' => __( 'Luminous dusk', 'gutenberg' ),
171 'pale-ocean' => __( 'Pale ocean', 'gutenberg' ),
172 'electric-grass' => __( 'Electric grass', 'gutenberg' ),
173 'midnight' => __( 'Midnight', 'gutenberg' ),
174 );
175 if ( ! empty( $config['global']['settings']['color']['gradients'] ) ) {
176 foreach ( $config['global']['settings']['color']['gradients'] as &$gradient ) {
177 $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ];
178 }
179 }
180
181 $default_font_sizes_i18n = array(
182 'small' => __( 'Small', 'gutenberg' ),
183 'normal' => __( 'Normal', 'gutenberg' ),
184 'medium' => __( 'Medium', 'gutenberg' ),
185 'large' => __( 'Large', 'gutenberg' ),
186 'huge' => __( 'Huge', 'gutenberg' ),
187 );
188 if ( ! empty( $config['global']['settings']['typography']['fontSizes'] ) ) {
189 foreach ( $config['global']['settings']['typography']['fontSizes'] as &$font_size ) {
190 $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ];
191 }
192 }
193
194 $default_font_styles_i18n = array(
195 'normal' => __( 'Regular', 'gutenberg' ),
196 'italic' => __( 'Italic', 'gutenberg' ),
197 'initial' => __( 'Initial', 'gutenberg' ),
198 'inherit' => __( 'Inherit', 'gutenberg' ),
199 );
200 if ( ! empty( $config['global']['settings']['typography']['fontStyles'] ) ) {
201 foreach ( $config['global']['settings']['typography']['fontStyles'] as &$font_style ) {
202 $font_style['name'] = $default_font_styles_i18n[ $font_style['slug'] ];
203 }
204 }
205
206 $default_font_weights_i18n = array(
207 '100' => __( 'Ultralight', 'gutenberg' ),
208 '200' => __( 'Thin', 'gutenberg' ),
209 '300' => __( 'Light', 'gutenberg' ),
210 '400' => __( 'Regular', 'gutenberg' ),
211 '500' => __( 'Medium', 'gutenberg' ),
212 '600' => __( 'Semibold', 'gutenberg' ),
213 '700' => __( 'Bold', 'gutenberg' ),
214 '800' => __( 'Heavy', 'gutenberg' ),
215 '900' => __( 'Black', 'gutenberg' ),
216 'initial' => __( 'Initial', 'gutenberg' ),
217 'inherit' => __( 'Inherit', 'gutenberg' ),
218 );
219 if ( ! empty( $config['global']['settings']['typography']['fontWeights'] ) ) {
220 foreach ( $config['global']['settings']['typography']['fontWeights'] as &$font_weight ) {
221 $font_weight['name'] = $default_font_weights_i18n[ $font_weight['slug'] ];
222 }
223 }
224 // End i18n logic to remove when JSON i18 strings are extracted.
225
226 return new WP_Theme_JSON( $config );
227 }
228
229 /**
230 * Returns the theme presets registered via add_theme_support, if any.
231 *
232 * @return array Config that adheres to the theme.json schema.
233 */
234 function gutenberg_experimental_global_styles_get_theme_support_settings() {
235 $theme_settings = array();
236 $theme_settings['global'] = array();
237 $theme_settings['global']['settings'] = array();
238
239 // Deprecated theme supports.
240 if ( get_theme_support( 'disable-custom-colors' ) ) {
241 if ( ! isset( $theme_settings['global']['settings']['color'] ) ) {
242 $theme_settings['global']['settings']['color'] = array();
243 }
244 $theme_settings['global']['settings']['color']['custom'] = false;
245 }
246
247 if ( get_theme_support( 'disable-custom-gradients' ) ) {
248 if ( ! isset( $theme_settings['global']['settings']['color'] ) ) {
249 $theme_settings['global']['settings']['color'] = array();
250 }
251 $theme_settings['global']['settings']['color']['customGradient'] = false;
252 }
253
254 if ( get_theme_support( 'disable-custom-font-sizes' ) ) {
255 if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) {
256 $theme_settings['global']['settings']['typography'] = array();
257 }
258 $theme_settings['global']['settings']['typography']['customFontSize'] = false;
259 }
260
261 if ( get_theme_support( 'custom-line-height' ) ) {
262 if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) {
263 $theme_settings['global']['settings']['typography'] = array();
264 }
265 $theme_settings['global']['settings']['typography']['customLineHeight'] = true;
266 }
267
268 if ( get_theme_support( 'custom-spacing' ) ) {
269 if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) {
270 $theme_settings['global']['settings']['spacing'] = array();
271 }
272 $theme_settings['global']['settings']['spacing']['custom'] = true;
273 }
274
275 if ( get_theme_support( 'experimental-link-color' ) ) {
276 if ( ! isset( $theme_settings['global']['settings']['color'] ) ) {
277 $theme_settings['global']['settings']['color'] = array();
278 }
279 $theme_settings['global']['settings']['color']['link'] = true;
280 }
281
282 $custom_units_theme_support = get_theme_support( 'custom-units' );
283 if ( $custom_units_theme_support ) {
284 if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) {
285 $theme_settings['global']['settings']['spacing'] = array();
286 }
287 $theme_settings['global']['settings']['spacing'] ['units'] = true === $custom_units_theme_support ? array( 'px', 'em', 'rem', 'vh', 'vw' ) : $custom_units_theme_support;
288 }
289
290 $theme_colors = get_theme_support( 'editor-color-palette' );
291 if ( ! empty( $theme_colors[0] ) ) {
292 if ( ! isset( $theme_settings['global']['settings']['color'] ) ) {
293 $theme_settings['global']['settings']['color'] = array();
294 }
295 $theme_settings['global']['settings']['color']['palette'] = array();
296 $theme_settings['global']['settings']['color']['palette'] = $theme_colors[0];
297 }
298
299 $theme_gradients = get_theme_support( 'editor-gradient-presets' );
300 if ( ! empty( $theme_gradients[0] ) ) {
301 if ( ! isset( $theme_settings['global']['settings']['color'] ) ) {
302 $theme_settings['global']['settings']['color'] = array();
303 }
304 $theme_settings['global']['settings']['color']['gradients'] = array();
305 $theme_settings['global']['settings']['color']['gradients'] = $theme_gradients[0];
306 }
307
308 $theme_font_sizes = get_theme_support( 'editor-font-sizes' );
309 if ( ! empty( $theme_font_sizes[0] ) ) {
310 if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) {
311 $theme_settings['global']['settings']['typography'] = array();
312 }
313 $theme_settings['global']['settings']['typography']['fontSizes'] = array();
314 // Back-compatibility for presets without units.
315 foreach ( $theme_font_sizes[0] as &$font_size ) {
316 if ( is_numeric( $font_size['size'] ) ) {
317 $font_size['size'] = $font_size['size'] . 'px';
318 }
319 }
320 $theme_settings['global']['settings']['typography']['fontSizes'] = $theme_font_sizes[0];
321 }
322
323 return $theme_settings;
324 }
325
326 /**
327 * Returns the theme's origin config.
328 *
329 * It also fetches the existing presets the theme declared via add_theme_support
330 * and uses them if the theme hasn't declared any via theme.json.
331 *
332 * @return WP_Theme_JSON Entity that holds theme data.
333 */
334 function gutenberg_experimental_global_styles_get_theme() {
335 $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings();
336 $theme_json_data = gutenberg_experimental_global_styles_get_from_file(
337 locate_template( 'experimental-theme.json' )
338 );
339
340 /*
341 * We want the presets declared in theme.json
342 * to override the ones declared via add_theme_support.
343 */
344 $result = new WP_Theme_JSON( $theme_support_data );
345 $all = new WP_Theme_JSON( $theme_json_data );
346 $result->merge( $all );
347
348 return $result;
349 }
350
351 /**
352 * Takes a tree adhering to the theme.json schema and generates
353 * the corresponding stylesheet.
354 *
355 * @param WP_Theme_JSON $tree Input tree.
356 *
357 * @return string Stylesheet.
358 */
359 function gutenberg_experimental_global_styles_get_stylesheet( $tree ) {
360 // Check if we can use cached.
361 $can_use_cached = (
362 ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
363 ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
364 ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
365 ! is_admin()
366 );
367
368 if ( $can_use_cached ) {
369 // Check if we have the styles already cached.
370 $cached = get_transient( 'global_styles' );
371 if ( $cached ) {
372 return $cached;
373 }
374 }
375
376 $stylesheet = $tree->get_stylesheet();
377
378 if ( gutenberg_experimental_global_styles_has_theme_json_support() ) {
379 // To support all themes, we added in the block-library stylesheet
380 // a style rule such as .has-link-color a { color: var(--wp--style--color--link, #00e); }
381 // so that existing link colors themes used didn't break.
382 // We add this here to make it work for themes that opt-in to theme.json
383 // In the future, we may do this differently.
384 $stylesheet .= 'a{color:var(--wp--style--color--link, #00e);}';
385 }
386
387 if ( $can_use_cached ) {
388 // Cache for a minute.
389 // This cache doesn't need to be any longer, we only want to avoid spikes on high-trafic sites.
390 set_transient( 'global_styles', $stylesheet, MINUTE_IN_SECONDS );
391 }
392
393 return $stylesheet;
394 }
395
396 /**
397 * Fetches the preferences for each origin (core, theme, user)
398 * and enqueues the resulting stylesheet.
399 */
400 function gutenberg_experimental_global_styles_enqueue_assets() {
401 $all = gutenberg_experimental_global_styles_get_core();
402 $all->merge( gutenberg_experimental_global_styles_get_theme() );
403 $all->merge( gutenberg_experimental_global_styles_get_user() );
404
405 $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all );
406 if ( empty( $stylesheet ) ) {
407 return;
408 }
409
410 wp_register_style( 'global-styles', false, array(), true, true );
411 wp_add_inline_style( 'global-styles', $stylesheet );
412 wp_enqueue_style( 'global-styles' );
413 }
414
415 /**
416 * Adds the necessary data for the Global Styles client UI to the block settings.
417 *
418 * @param array $settings Existing block editor settings.
419 * @return array New block editor settings
420 */
421 function gutenberg_experimental_global_styles_settings( $settings ) {
422 $base = gutenberg_experimental_global_styles_get_core();
423 $base->merge( gutenberg_experimental_global_styles_get_theme() );
424
425 $all = gutenberg_experimental_global_styles_get_core();
426 $all->merge( gutenberg_experimental_global_styles_get_theme() );
427 $all->merge( gutenberg_experimental_global_styles_get_user() );
428
429 // STEP 1: ADD FEATURES
430 // These need to be added to settings always.
431 // We also need to unset the deprecated settings defined by core.
432 $settings['__experimentalFeatures'] = $all->get_settings();
433
434 unset( $settings['colors'] );
435 unset( $settings['gradients'] );
436 unset( $settings['fontSizes'] );
437 unset( $settings['disableCustomColors'] );
438 unset( $settings['disableCustomGradients'] );
439 unset( $settings['disableCustomFontSizes'] );
440 unset( $settings['enableCustomLineHeight'] );
441 unset( $settings['enableCustomUnits'] );
442
443 // STEP 2 - IF EDIT-SITE, ADD DATA REQUIRED FOR GLOBAL STYLES SIDEBAR
444 // The client needs some information to be able to access/update the user styles.
445 // We only do this if the theme has support for theme.json, though,
446 // as an indicator that the theme will know how to combine this with its stylesheet.
447 $screen = get_current_screen();
448 if (
449 ! empty( $screen ) &&
450 function_exists( 'gutenberg_is_edit_site_page' ) &&
451 gutenberg_is_edit_site_page( $screen->id ) &&
452 gutenberg_experimental_global_styles_has_theme_json_support()
453 ) {
454 $settings['__experimentalGlobalStylesUserEntityId'] = gutenberg_experimental_global_styles_get_user_cpt_id();
455 $settings['__experimentalGlobalStylesContexts'] = $base->get_blocks_metadata();
456 $settings['__experimentalGlobalStylesBaseStyles'] = $base->get_raw_data();
457 } else {
458 // STEP 3 - OTHERWISE, ADD STYLES
459 //
460 // If we are in a block editor context, but not in edit-site,
461 // we need to add the styles via the settings. This is because
462 // we want them processed as if they were added via add_editor_styles,
463 // which adds the editor wrapper class.
464 $settings['styles'][] = array( 'css' => gutenberg_experimental_global_styles_get_stylesheet( $all ) );
465 }
466
467 return $settings;
468 }
469
470 /**
471 * Registers a Custom Post Type to store the user's origin config.
472 */
473 function gutenberg_experimental_global_styles_register_cpt() {
474 if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) {
475 return;
476 }
477
478 $args = array(
479 'label' => __( 'Global Styles', 'gutenberg' ),
480 'description' => 'CPT to store user design tokens',
481 'public' => false,
482 'show_ui' => false,
483 'show_in_rest' => true,
484 'rest_base' => '__experimental/global-styles',
485 'capabilities' => array(
486 'read' => 'edit_theme_options',
487 'create_posts' => 'edit_theme_options',
488 'edit_posts' => 'edit_theme_options',
489 'edit_published_posts' => 'edit_theme_options',
490 'delete_published_posts' => 'edit_theme_options',
491 'edit_others_posts' => 'edit_theme_options',
492 'delete_others_posts' => 'edit_theme_options',
493 ),
494 'map_meta_cap' => true,
495 'supports' => array(
496 'editor',
497 'revisions',
498 ),
499 );
500 register_post_type( 'wp_global_styles', $args );
501 }
502
503 add_action( 'init', 'gutenberg_experimental_global_styles_register_cpt' );
504 add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings' );
505 add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' );
506