| @@ -1,131 +1,273 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Global styles functions, filters and actions, etc. | |
| 3 | + * Bootstraps Global Styles. | |
| 4 | 4 | * |
| 5 | 5 | * @package gutenberg |
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | /** |
| 9 | - * Function that given a branch of the global styles object recursively generates | |
| 10 | - * an array defining all the css vars that the global styles object represents. | |
| 9 | + * Whether the current theme has a theme.json file. | |
| 11 | 10 | * |
| 12 | - * @param array $global_styles_branch Array representing a brach of the global styles object. | |
| 13 | - * @param string $prefix Prefix to append to each variable. | |
| 14 | - * @param bool $is_start Indicates if we are on the first call to gutenberg_get_css_vars (outside the recursion). | |
| 15 | - * @return array An array whose keys are css variable names and whose values are the css variables value. | |
| 11 | + * @return boolean | |
| 16 | 12 | */ |
| 17 | -function gutenberg_get_css_vars( $global_styles_branch, $prefix = '', $is_start = true ) { | |
| 18 | - $result = array(); | |
| 19 | - foreach ( $global_styles_branch as $key => $value ) { | |
| 20 | - $processed_key = str_replace( '/', '-', $key ); | |
| 21 | - $separator = $is_start ? '' : '--'; | |
| 22 | - $new_key = ( $prefix ? $prefix . $separator : '' ) . $processed_key; | |
| 13 | +function gutenberg_experimental_global_styles_has_theme_json_support() { | |
| 14 | + return is_readable( locate_template( 'experimental-theme.json' ) ); | |
| 15 | +} | |
| 23 | 16 | |
| 24 | - if ( is_array( $value ) ) { | |
| 25 | - $result = array_merge( | |
| 26 | - $result, | |
| 27 | - gutenberg_get_css_vars( $value, $new_key, false ) | |
| 28 | - ); | |
| 29 | - } else { | |
| 30 | - $result[ $new_key ] = $value; | |
| 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 | + $theme_settings = array(); | |
| 26 | + $theme_settings['global'] = array(); | |
| 27 | + $theme_settings['global']['settings'] = array(); | |
| 28 | + | |
| 29 | + // Deprecated theme supports. | |
| 30 | + if ( isset( $settings['disableCustomColors'] ) ) { | |
| 31 | + if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { | |
| 32 | + $theme_settings['global']['settings']['color'] = array(); | |
| 31 | 33 | } |
| 34 | + $theme_settings['global']['settings']['color']['custom'] = ! $settings['disableCustomColors']; | |
| 32 | 35 | } |
| 33 | - return $result; | |
| 34 | -} | |
| 35 | 36 | |
| 36 | -/** | |
| 37 | - * Function responsible for enqueuing the style that define the global styles css variables. | |
| 38 | - */ | |
| 39 | -function gutenberg_enqueue_global_styles_assets() { | |
| 40 | - if ( ! locate_template( 'experimental-theme.json' ) ) { | |
| 41 | - return; | |
| 37 | + if ( isset( $settings['disableCustomGradients'] ) ) { | |
| 38 | + if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { | |
| 39 | + $theme_settings['global']['settings']['color'] = array(); | |
| 40 | + } | |
| 41 | + $theme_settings['global']['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; | |
| 42 | 42 | } |
| 43 | - $default_global_styles_path = dirname( dirname( __FILE__ ) ) . '/experimental-default-global-styles.json'; | |
| 44 | - $default_global_styles = null; | |
| 45 | 43 | |
| 46 | - if ( file_exists( $default_global_styles_path ) ) { | |
| 47 | - $default_global_styles = json_decode( | |
| 48 | - file_get_contents( $default_global_styles_path ), | |
| 49 | - true | |
| 50 | - ); | |
| 44 | + if ( isset( $settings['disableCustomFontSizes'] ) ) { | |
| 45 | + if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { | |
| 46 | + $theme_settings['global']['settings']['typography'] = array(); | |
| 47 | + } | |
| 48 | + $theme_settings['global']['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; | |
| 51 | 49 | } |
| 52 | 50 | |
| 53 | - $theme_json_path = locate_template( 'experimental-theme.json' ); | |
| 54 | - $theme_json_global_styles = null; | |
| 55 | - if ( $theme_json_path ) { | |
| 56 | - $theme_json_global_styles = json_decode( | |
| 57 | - file_get_contents( $theme_json_path ), | |
| 58 | - true | |
| 59 | - ); | |
| 51 | + if ( isset( $settings['enableCustomLineHeight'] ) ) { | |
| 52 | + if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { | |
| 53 | + $theme_settings['global']['settings']['typography'] = array(); | |
| 54 | + } | |
| 55 | + $theme_settings['global']['settings']['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; | |
| 60 | 56 | } |
| 61 | 57 | |
| 62 | - // To-do: Load user customizations from a CPT. | |
| 63 | - $css_vars = array(); | |
| 64 | - foreach ( | |
| 65 | - array( | |
| 66 | - $default_global_styles, | |
| 67 | - $theme_json_global_styles, | |
| 68 | - ) as $global_styles_definition | |
| 69 | - ) { | |
| 70 | - if ( ! $global_styles_definition ) { | |
| 71 | - continue; | |
| 58 | + if ( isset( $settings['enableCustomUnits'] ) ) { | |
| 59 | + if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { | |
| 60 | + $theme_settings['global']['settings']['spacing'] = array(); | |
| 72 | 61 | } |
| 73 | - if ( isset( $global_styles_definition['global'] ) ) { | |
| 74 | - $css_vars = array_merge( | |
| 75 | - $css_vars, | |
| 76 | - gutenberg_get_css_vars( $global_styles_definition['global'], '--wp-' ) | |
| 77 | - ); | |
| 62 | + $theme_settings['global']['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? | |
| 63 | + array( 'px', 'em', 'rem', 'vh', 'vw' ) : | |
| 64 | + $settings['enableCustomUnits']; | |
| 65 | + } | |
| 66 | + | |
| 67 | + if ( isset( $settings['colors'] ) ) { | |
| 68 | + if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { | |
| 69 | + $theme_settings['global']['settings']['color'] = array(); | |
| 78 | 70 | } |
| 79 | - if ( isset( $global_styles_definition['blocks'] ) ) { | |
| 80 | - $css_vars = array_merge( | |
| 81 | - $css_vars, | |
| 82 | - gutenberg_get_css_vars( $global_styles_definition['blocks'], '--wp-block-' ) | |
| 83 | - ); | |
| 71 | + $theme_settings['global']['settings']['color']['palette'] = $settings['colors']; | |
| 72 | + } | |
| 73 | + | |
| 74 | + if ( isset( $settings['gradients'] ) ) { | |
| 75 | + if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { | |
| 76 | + $theme_settings['global']['settings']['color'] = array(); | |
| 84 | 77 | } |
| 78 | + $theme_settings['global']['settings']['color']['gradients'] = $settings['gradients']; | |
| 85 | 79 | } |
| 86 | 80 | |
| 87 | - if ( empty( $css_vars ) ) { | |
| 81 | + if ( isset( $settings['fontSizes'] ) ) { | |
| 82 | + $font_sizes = $settings['fontSizes']; | |
| 83 | + // Back-compatibility for presets without units. | |
| 84 | + foreach ( $font_sizes as &$font_size ) { | |
| 85 | + if ( is_numeric( $font_size['size'] ) ) { | |
| 86 | + $font_size['size'] = $font_size['size'] . 'px'; | |
| 87 | + } | |
| 88 | + } | |
| 89 | + if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { | |
| 90 | + $theme_settings['global']['settings']['typography'] = array(); | |
| 91 | + } | |
| 92 | + $theme_settings['global']['settings']['typography']['fontSizes'] = $font_sizes; | |
| 93 | + } | |
| 94 | + | |
| 95 | + // Things that didn't land in core yet, so didn't have a setting assigned. | |
| 96 | + if ( current( (array) get_theme_support( 'custom-spacing' ) ) ) { | |
| 97 | + if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { | |
| 98 | + $theme_settings['global']['settings']['spacing'] = array(); | |
| 99 | + } | |
| 100 | + $theme_settings['global']['settings']['spacing']['customPadding'] = true; | |
| 101 | + } | |
| 102 | + | |
| 103 | + if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) { | |
| 104 | + if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { | |
| 105 | + $theme_settings['global']['settings']['color'] = array(); | |
| 106 | + } | |
| 107 | + $theme_settings['global']['settings']['color']['link'] = true; | |
| 108 | + } | |
| 109 | + | |
| 110 | + return $theme_settings; | |
| 111 | +} | |
| 112 | + | |
| 113 | +/** | |
| 114 | + * Takes a tree adhering to the theme.json schema and generates | |
| 115 | + * the corresponding stylesheet. | |
| 116 | + * | |
| 117 | + * @param WP_Theme_JSON $tree Input tree. | |
| 118 | + * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'. | |
| 119 | + * | |
| 120 | + * @return string Stylesheet. | |
| 121 | + */ | |
| 122 | +function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'all' ) { | |
| 123 | + // Check if we can use cached. | |
| 124 | + $can_use_cached = ( | |
| 125 | + ( 'all' === $type ) && | |
| 126 | + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && | |
| 127 | + ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && | |
| 128 | + ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && | |
| 129 | + ! is_admin() | |
| 130 | + ); | |
| 131 | + | |
| 132 | + if ( $can_use_cached ) { | |
| 133 | + // Check if we have the styles already cached. | |
| 134 | + $cached = get_transient( 'global_styles' ); | |
| 135 | + if ( $cached ) { | |
| 136 | + return $cached; | |
| 137 | + } | |
| 138 | + } | |
| 139 | + | |
| 140 | + $stylesheet = $tree->get_stylesheet( $type ); | |
| 141 | + | |
| 142 | + if ( ( 'all' === $type || 'block_styles' === $type ) && gutenberg_experimental_global_styles_has_theme_json_support() ) { | |
| 143 | + // To support all themes, we added in the block-library stylesheet | |
| 144 | + // a style rule such as .has-link-color a { color: var(--wp--style--color--link, #00e); } | |
| 145 | + // so that existing link colors themes used didn't break. | |
| 146 | + // We add this here to make it work for themes that opt-in to theme.json | |
| 147 | + // In the future, we may do this differently. | |
| 148 | + $stylesheet .= 'a{color:var(--wp--style--color--link, #00e);}'; | |
| 149 | + } | |
| 150 | + | |
| 151 | + if ( $can_use_cached ) { | |
| 152 | + // Cache for a minute. | |
| 153 | + // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. | |
| 154 | + set_transient( 'global_styles', $stylesheet, MINUTE_IN_SECONDS ); | |
| 155 | + } | |
| 156 | + | |
| 157 | + return $stylesheet; | |
| 158 | +} | |
| 159 | + | |
| 160 | +/** | |
| 161 | + * Fetches the preferences for each origin (core, theme, user) | |
| 162 | + * and enqueues the resulting stylesheet. | |
| 163 | + */ | |
| 164 | +function gutenberg_experimental_global_styles_enqueue_assets() { | |
| 165 | + if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { | |
| 88 | 166 | return; |
| 89 | 167 | } |
| 90 | 168 | |
| 91 | - $inline_style = ":root {\n"; | |
| 92 | - foreach ( $css_vars as $var => $value ) { | |
| 93 | - $inline_style .= "\t" . $var . ': ' . $value . ";\n"; | |
| 169 | + $settings = gutenberg_get_common_block_editor_settings(); | |
| 170 | + $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings ); | |
| 171 | + | |
| 172 | + $resolver = new WP_Theme_JSON_Resolver(); | |
| 173 | + $all = $resolver->get_origin( $theme_support_data ); | |
| 174 | + | |
| 175 | + $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all ); | |
| 176 | + if ( empty( $stylesheet ) ) { | |
| 177 | + return; | |
| 94 | 178 | } |
| 95 | - $inline_style .= '}'; | |
| 96 | 179 | |
| 97 | 180 | wp_register_style( 'global-styles', false, array(), true, true ); |
| 98 | - wp_add_inline_style( 'global-styles', $inline_style ); | |
| 181 | + wp_add_inline_style( 'global-styles', $stylesheet ); | |
| 99 | 182 | wp_enqueue_style( 'global-styles' ); |
| 100 | 183 | } |
| 101 | -add_action( 'enqueue_block_assets', 'gutenberg_enqueue_global_styles_assets' ); | |
| 102 | 184 | |
| 103 | 185 | /** |
| 104 | - * Adds class wp-gs to the frontend body class if the theme defines a experimental-theme.json. | |
| 186 | + * Adds the necessary data for the Global Styles client UI to the block settings. | |
| 105 | 187 | * |
| 106 | - * @param array $classes Existing body classes. | |
| 107 | - * @return array The filtered array of body classes. | |
| 188 | + * @param array $settings Existing block editor settings. | |
| 189 | + * @return array New block editor settings | |
| 108 | 190 | */ |
| 109 | -function gutenberg_add_wp_gs_class_front_end( $classes ) { | |
| 110 | - if ( locate_template( 'experimental-theme.json' ) ) { | |
| 111 | - return array_merge( $classes, array( 'wp-gs' ) ); | |
| 191 | +function gutenberg_experimental_global_styles_settings( $settings ) { | |
| 192 | + $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings ); | |
| 193 | + unset( $settings['colors'] ); | |
| 194 | + unset( $settings['disableCustomColors'] ); | |
| 195 | + unset( $settings['disableCustomFontSizes'] ); | |
| 196 | + unset( $settings['disableCustomGradients'] ); | |
| 197 | + unset( $settings['enableCustomLineHeight'] ); | |
| 198 | + unset( $settings['enableCustomUnits'] ); | |
| 199 | + unset( $settings['fontSizes'] ); | |
| 200 | + unset( $settings['gradients'] ); | |
| 201 | + | |
| 202 | + $resolver = new WP_Theme_JSON_Resolver(); | |
| 203 | + $origin = 'theme'; | |
| 204 | + if ( | |
| 205 | + gutenberg_experimental_global_styles_has_theme_json_support() && | |
| 206 | + gutenberg_is_fse_theme() | |
| 207 | + ) { | |
| 208 | + // Only lookup for the user data if we need it. | |
| 209 | + $origin = 'user'; | |
| 112 | 210 | } |
| 113 | - return $classes; | |
| 211 | + $tree = $resolver->get_origin( $theme_support_data, $origin ); | |
| 212 | + | |
| 213 | + // STEP 1: ADD FEATURES | |
| 214 | + // | |
| 215 | + // These need to be always added to the editor settings, | |
| 216 | + // even for themes that don't support theme.json. | |
| 217 | + // An example of this is that the presets are configured | |
| 218 | + // from the theme support data. | |
| 219 | + $settings['__experimentalFeatures'] = $tree->get_settings(); | |
| 220 | + | |
| 221 | + // STEP 2 - IF EDIT-SITE, ADD DATA REQUIRED FOR GLOBAL STYLES SIDEBAR | |
| 222 | + // | |
| 223 | + // In the site editor, the user can change styles, so the client | |
| 224 | + // needs the ability to create them. Hence, we pass it some data | |
| 225 | + // for this: base styles (core+theme) and the ID of the user CPT. | |
| 226 | + $screen = get_current_screen(); | |
| 227 | + if ( | |
| 228 | + ! empty( $screen ) && | |
| 229 | + function_exists( 'gutenberg_is_edit_site_page' ) && | |
| 230 | + gutenberg_is_edit_site_page( $screen->id ) && | |
| 231 | + gutenberg_experimental_global_styles_has_theme_json_support() && | |
| 232 | + gutenberg_is_fse_theme() | |
| 233 | + ) { | |
| 234 | + $user_cpt_id = WP_Theme_JSON_Resolver::get_user_custom_post_type_id(); | |
| 235 | + $base_styles = $resolver->get_origin( $theme_support_data, 'theme' )->get_raw_data(); | |
| 236 | + | |
| 237 | + $settings['__experimentalGlobalStylesUserEntityId'] = $user_cpt_id; | |
| 238 | + $settings['__experimentalGlobalStylesBaseStyles'] = $base_styles; | |
| 239 | + } elseif ( gutenberg_experimental_global_styles_has_theme_json_support() ) { | |
| 240 | + // STEP 3 - ADD STYLES IF THEME HAS SUPPORT | |
| 241 | + // | |
| 242 | + // If we are in a block editor context, but not in edit-site, | |
| 243 | + // we add the styles via the settings, so the editor knows that | |
| 244 | + // some of these should be added the wrapper class, | |
| 245 | + // as if they were added via add_editor_styles. | |
| 246 | + $settings['styles'][] = array( | |
| 247 | + 'css' => gutenberg_experimental_global_styles_get_stylesheet( $tree, 'css_variables' ), | |
| 248 | + '__experimentalNoWrapper' => true, | |
| 249 | + ); | |
| 250 | + $settings['styles'][] = array( | |
| 251 | + 'css' => gutenberg_experimental_global_styles_get_stylesheet( $tree, 'block_styles' ), | |
| 252 | + ); | |
| 253 | + } | |
| 254 | + | |
| 255 | + return $settings; | |
| 114 | 256 | } |
| 115 | -add_filter( 'body_class', 'gutenberg_add_wp_gs_class_front_end' ); | |
| 116 | 257 | |
| 117 | - | |
| 118 | 258 | /** |
| 119 | - * Adds class wp-gs to the block-editor body class if the theme defines a experimental-theme.json. | |
| 259 | + * Register CPT to store/access user data. | |
| 120 | 260 | * |
| 121 | - * @param string $classes Existing body classes separated by space. | |
| 122 | - * @return string The filtered string of body classes. | |
| 261 | + * @return array|undefined | |
| 123 | 262 | */ |
| 124 | -function gutenberg_add_wp_gs_class_editor( $classes ) { | |
| 125 | - global $current_screen; | |
| 126 | - if ( $current_screen->is_block_editor() && locate_template( 'experimental-theme.json' ) ) { | |
| 127 | - return $classes . ' wp-gs'; | |
| 263 | +function gutenberg_experimental_global_styles_register_user_cpt() { | |
| 264 | + if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { | |
| 265 | + return; | |
| 128 | 266 | } |
| 129 | - return $classes; | |
| 267 | + | |
| 268 | + WP_Theme_JSON_Resolver::register_user_custom_post_type(); | |
| 130 | 269 | } |
| 131 | -add_filter( 'admin_body_class', 'gutenberg_add_wp_gs_class_editor' ); | |
| 270 | + | |
| 271 | +add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' ); | |
| 272 | +add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX ); | |
| 273 | +add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' ); | |