PluginProbe
Gutenberg / 9.6.2
Gutenberg v9.6.2
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 / class-wp-theme-json-resolver.php

class-wp-theme-json-resolver.php in Gutenberg 9.6.2, at lib/class-wp-theme-json-resolver.php

348 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Process the different data sources for site-level
4 * config and offers and API to work with them.
5 *
6 * @package gutenberg
7 */
8
9 /**
10 * Class that abstracts the processing
11 * of the different data sources.
12 */
13 class WP_Theme_JSON_Resolver {
14
15 /**
16 * Container for data coming from core.
17 *
18 * @var WP_Theme_JSON
19 */
20 private static $core = null;
21
22 /**
23 * Container for data coming from the theme.
24 *
25 * @var WP_Theme_JSON
26 */
27 private $theme = null;
28
29 /**
30 * Container for data coming from the user.
31 *
32 * @var WP_Theme_JSON
33 */
34 private static $user = null;
35
36 /**
37 * Stores the ID of the custom post type
38 * that holds the user data.
39 *
40 * @var integer
41 */
42 private static $user_custom_post_type_id = null;
43
44 /**
45 * Processes a file that adheres to the theme.json
46 * schema and returns an array with its contents,
47 * or a void array if none found.
48 *
49 * @param string $file_path Path to file.
50 *
51 * @return array Contents that adhere to the theme.json schema.
52 */
53 private static function get_from_file( $file_path ) {
54 $config = array();
55 if ( file_exists( $file_path ) ) {
56 $decoded_file = json_decode(
57 file_get_contents( $file_path ),
58 true
59 );
60
61 $json_decoding_error = json_last_error();
62 if ( JSON_ERROR_NONE !== $json_decoding_error ) {
63 error_log( 'Error when decoding file schema: ' . json_last_error_msg() );
64 return $config;
65 }
66
67 if ( is_array( $decoded_file ) ) {
68 $config = $decoded_file;
69 }
70 }
71 return $config;
72 }
73
74 /**
75 * Return core's origin config.
76 *
77 * @return WP_Theme_JSON Entity that holds core data.
78 */
79 private static function get_core_origin() {
80 if ( null !== self::$core ) {
81 return self::$core;
82 }
83
84 $config = self::get_from_file( __DIR__ . '/experimental-default-theme.json' );
85
86 // Start i18n logic to remove when JSON i18 strings are extracted.
87 $default_colors_i18n = array(
88 'black' => __( 'Black', 'gutenberg' ),
89 'cyan-bluish-gray' => __( 'Cyan bluish gray', 'gutenberg' ),
90 'white' => __( 'White', 'gutenberg' ),
91 'pale-pink' => __( 'Pale pink', 'gutenberg' ),
92 'vivid-red' => __( 'Vivid red', 'gutenberg' ),
93 'luminous-vivid-orange' => __( 'Luminous vivid orange', 'gutenberg' ),
94 'luminous-vivid-amber' => __( 'Luminous vivid amber', 'gutenberg' ),
95 'light-green-cyan' => __( 'Light green cyan', 'gutenberg' ),
96 'vivid-green-cyan' => __( 'Vivid green cyan', 'gutenberg' ),
97 'pale-cyan-blue' => __( 'Pale cyan blue', 'gutenberg' ),
98 'vivid-cyan-blue' => __( 'Vivid cyan blue', 'gutenberg' ),
99 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ),
100 );
101 if ( ! empty( $config['global']['settings']['color']['palette'] ) ) {
102 foreach ( $config['global']['settings']['color']['palette'] as &$color ) {
103 $color['name'] = $default_colors_i18n[ $color['slug'] ];
104 }
105 }
106
107 $default_gradients_i18n = array(
108 'vivid-cyan-blue-to-vivid-purple' => __( 'Vivid cyan blue to vivid purple', 'gutenberg' ),
109 'light-green-cyan-to-vivid-green-cyan' => __( 'Light green cyan to vivid green cyan', 'gutenberg' ),
110 'luminous-vivid-amber-to-luminous-vivid-orange' => __( 'Luminous vivid amber to luminous vivid orange', 'gutenberg' ),
111 'luminous-vivid-orange-to-vivid-red' => __( 'Luminous vivid orange to vivid red', 'gutenberg' ),
112 'very-light-gray-to-cyan-bluish-gray' => __( 'Very light gray to cyan bluish gray', 'gutenberg' ),
113 'cool-to-warm-spectrum' => __( 'Cool to warm spectrum', 'gutenberg' ),
114 'blush-light-purple' => __( 'Blush light purple', 'gutenberg' ),
115 'blush-bordeaux' => __( 'Blush bordeaux', 'gutenberg' ),
116 'luminous-dusk' => __( 'Luminous dusk', 'gutenberg' ),
117 'pale-ocean' => __( 'Pale ocean', 'gutenberg' ),
118 'electric-grass' => __( 'Electric grass', 'gutenberg' ),
119 'midnight' => __( 'Midnight', 'gutenberg' ),
120 );
121 if ( ! empty( $config['global']['settings']['color']['gradients'] ) ) {
122 foreach ( $config['global']['settings']['color']['gradients'] as &$gradient ) {
123 $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ];
124 }
125 }
126
127 $default_font_sizes_i18n = array(
128 'small' => __( 'Small', 'gutenberg' ),
129 'normal' => __( 'Normal', 'gutenberg' ),
130 'medium' => __( 'Medium', 'gutenberg' ),
131 'large' => __( 'Large', 'gutenberg' ),
132 'huge' => __( 'Huge', 'gutenberg' ),
133 );
134 if ( ! empty( $config['global']['settings']['typography']['fontSizes'] ) ) {
135 foreach ( $config['global']['settings']['typography']['fontSizes'] as &$font_size ) {
136 $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ];
137 }
138 }
139 // End i18n logic to remove when JSON i18 strings are extracted.
140
141 self::$core = new WP_Theme_JSON( $config );
142
143 return self::$core;
144 }
145
146 /**
147 * Returns the theme's origin config.
148 *
149 * It uses the theme support data if
150 * the theme hasn't declared any via theme.json.
151 *
152 * @param array $theme_support_data Theme support data in theme.json format.
153 *
154 * @return WP_Theme_JSON Entity that holds theme data.
155 */
156 private function get_theme_origin( $theme_support_data = array() ) {
157 $theme_json_data = self::get_from_file( locate_template( 'experimental-theme.json' ) );
158
159 /*
160 * We want the presets and settings declared in theme.json
161 * to override the ones declared via add_theme_support.
162 */
163 $this->theme = new WP_Theme_JSON( $theme_support_data );
164 $this->theme->merge( new WP_Theme_JSON( $theme_json_data ) );
165
166 return $this->theme;
167 }
168
169 /**
170 * Returns the CPT that contains the user's origin config
171 * for the current theme or a void array if none found.
172 *
173 * It can also create and return a new draft CPT.
174 *
175 * @param bool $should_create_cpt Whether a new CPT should be created if no one was found.
176 * False by default.
177 * @param array $post_status_filter Filter CPT by post status.
178 * ['publish'] by default, so it only fetches published posts.
179 *
180 * @return array Custom Post Type for the user's origin config.
181 */
182 private static function get_user_data_from_custom_post_type( $should_create_cpt = false, $post_status_filter = array( 'publish' ) ) {
183 $user_cpt = array();
184 $post_type_filter = 'wp_global_styles';
185 $post_name_filter = 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() );
186 $recent_posts = wp_get_recent_posts(
187 array(
188 'numberposts' => 1,
189 'orderby' => 'date',
190 'order' => 'desc',
191 'post_type' => $post_type_filter,
192 'post_status' => $post_status_filter,
193 'name' => $post_name_filter,
194 )
195 );
196
197 if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) {
198 $user_cpt = $recent_posts[0];
199 } elseif ( $should_create_cpt ) {
200 $cpt_post_id = wp_insert_post(
201 array(
202 'post_content' => '{}',
203 'post_status' => 'publish',
204 'post_type' => $post_type_filter,
205 'post_name' => $post_name_filter,
206 ),
207 true
208 );
209 $user_cpt = get_post( $cpt_post_id, ARRAY_A );
210 }
211
212 return $user_cpt;
213 }
214
215 /**
216 * Returns the user's origin config.
217 *
218 * @return WP_Theme_JSON Entity that holds user data.
219 */
220 private static function get_user_origin() {
221 if ( null !== self::$user ) {
222 return self::$user;
223 }
224
225 $config = array();
226 $user_cpt = self::get_user_data_from_custom_post_type();
227 if ( array_key_exists( 'post_content', $user_cpt ) ) {
228 $decoded_data = json_decode( $user_cpt['post_content'], true );
229
230 $json_decoding_error = json_last_error();
231 if ( JSON_ERROR_NONE !== $json_decoding_error ) {
232 error_log( 'Error when decoding user schema: ' . json_last_error_msg() );
233 return $config;
234 }
235
236 if ( is_array( $decoded_data ) ) {
237 $config = $decoded_data;
238 }
239 }
240 self::$user = new WP_Theme_JSON( $config, true );
241
242 return self::$user;
243 }
244
245 /**
246 * There are three sources of data for a site:
247 * core, theme, and user.
248 *
249 * The main function of the resolver is to
250 * merge all this data following this algorithm:
251 * theme overrides core, and user overrides
252 * data coming from either theme or core.
253 *
254 * user data > theme data > core data
255 *
256 * The main use case for the resolver is to return
257 * the merged data up to the user level.However,
258 * there are situations in which we need the
259 * data merged up to a different level (theme)
260 * or no merged at all.
261 *
262 * @param array $theme_support_data Existing block editor settings.
263 * Empty array by default.
264 * @param string $origin The source of data the consumer wants.
265 * Valid values are 'core', 'theme', 'user'.
266 * Default is 'user'.
267 * @param boolean $merged Whether the data should be merged
268 * with the previous origins (the default).
269 *
270 * @return WP_Theme_JSON
271 */
272 public function get_origin( $theme_support_data = array(), $origin = 'user', $merged = true ) {
273 if ( ( 'user' === $origin ) && $merged ) {
274 $result = new WP_Theme_JSON();
275 $result->merge( self::get_core_origin() );
276 $result->merge( $this->get_theme_origin( $theme_support_data ) );
277 $result->merge( self::get_user_origin() );
278 return $result;
279 }
280
281 if ( ( 'theme' === $origin ) && $merged ) {
282 $result = new WP_Theme_JSON();
283 $result->merge( self::get_core_origin() );
284 $result->merge( $this->get_theme_origin( $theme_support_data ) );
285 return $result;
286 }
287
288 if ( 'user' === $origin ) {
289 return self::get_user_origin();
290 }
291
292 if ( 'theme' === $origin ) {
293 return $this->get_theme_origin( $theme_support_data );
294 }
295
296 return self::get_core_origin();
297 }
298
299 /**
300 * Registers a Custom Post Type to store the user's origin config.
301 */
302 public static function register_user_custom_post_type() {
303 $args = array(
304 'label' => __( 'Global Styles', 'gutenberg' ),
305 'description' => 'CPT to store user design tokens',
306 'public' => false,
307 'show_ui' => false,
308 'show_in_rest' => true,
309 'rest_base' => '__experimental/global-styles',
310 'capabilities' => array(
311 'read' => 'edit_theme_options',
312 'create_posts' => 'edit_theme_options',
313 'edit_posts' => 'edit_theme_options',
314 'edit_published_posts' => 'edit_theme_options',
315 'delete_published_posts' => 'edit_theme_options',
316 'edit_others_posts' => 'edit_theme_options',
317 'delete_others_posts' => 'edit_theme_options',
318 ),
319 'map_meta_cap' => true,
320 'supports' => array(
321 'editor',
322 'revisions',
323 ),
324 );
325 register_post_type( 'wp_global_styles', $args );
326 }
327
328 /**
329 * Returns the ID of the custom post type
330 * that stores user data.
331 *
332 * @return integer
333 */
334 public static function get_user_custom_post_type_id() {
335 if ( null !== self::$user_custom_post_type_id ) {
336 return self::$user_custom_post_type_id;
337 }
338
339 $user_cpt = self::get_user_data_from_custom_post_type( true );
340 if ( array_key_exists( 'ID', $user_cpt ) ) {
341 self::$user_custom_post_type_id = $user_cpt['ID'];
342 }
343
344 return self::$user_custom_post_type_id;
345 }
346
347 }
348