| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * The design guide ability, which hands agents that work on the site through 4 * the Abilities API what Ipsum is built with and how it is meant to be used. 5 * 6 * @package Ipsum 7 * @since Ipsum 1.0 8 */ 9 10 if ( ! function_exists( 'ipsum_register_abilities' ) ) : 11 /** 12 * Registers the design guide ability, so agents that work on the site 13 * through the Abilities API, rather than this repository, can learn how 14 * Ipsum is meant to be built with. 15 * 16 * It needs `edit_theme_options`, the capability behind the Site Editor and 17 * Global Styles, since the guide is for whoever builds the site's design. 18 * 19 * @since Ipsum 1.0 20 * 21 * @return void 22 */ 23 function ipsum_register_abilities() { 24 $output_schema = ipsum_get_design_guide_schema(); 25 26 wp_register_ability( 27 'ipsum/get-design-guide', 28 array( 29 'label' => __( 'Get Design Guide', 'ipsum' ), 30 'description' => __( 'Returns the design guide of the Ipsum theme, read from the site as it is now: the color palette and what each color is for, whether it is light or dark, fonts, font sizes, spacing, layout widths, style variations, block styles, patterns, and the templates and template parts with the pattern that fills each. Read it before building pages, templates or styles. Pass fields to get only some sections.', 'ipsum' ), 31 'category' => 'site', 32 'input_schema' => array( 33 'type' => 'object', 34 'properties' => array( 35 'fields' => array( 36 'type' => 'array', 37 'items' => array( 38 'type' => 'string', 39 'enum' => array_keys( $output_schema['properties'] ), 40 ), 41 'description' => __( 'Optional. Limits the guide to these sections. All sections are returned when it is omitted.', 'ipsum' ), 42 ), 43 ), 44 'additionalProperties' => false, 45 'default' => array(), 46 ), 47 'output_schema' => $output_schema, 48 'execute_callback' => 'ipsum_get_design_guide', 49 'required_capabilities' => array( 'edit_theme_options' ), 50 'meta' => array( 51 'annotations' => array( 52 'readonly' => true, 53 'destructive' => false, 54 'idempotent' => true, 55 ), 56 'public' => true, 57 ), 58 ) 59 ); 60 } 61 endif; 62 add_action( 'wp_abilities_api_init', 'ipsum_register_abilities' ); 63 64 if ( ! function_exists( 'ipsum_get_color_roles' ) ) : 65 /** 66 * Returns what each palette color is for, keyed by its slug. 67 * 68 * Every color variation redefines the same slugs, so the roles hold under 69 * whichever variation is active. 70 * 71 * @since Ipsum 1.0 72 * 73 * @return array<string, string> Role descriptions, keyed by palette slug. 74 */ 75 function ipsum_get_color_roles() { 76 return array( 77 'base' => __( 'The page background.', 'ipsum' ), 78 'contrast' => __( 'Body text, headings, links and buttons, on Base. It can be translucent, so on any other background use Strong Contrast.', 'ipsum' ), 79 'accent-1' => __( 'An accent for small highlights. The theme uses it only for the arrival bar. Links and buttons use Contrast, not this. Use it sparingly.', 'ipsum' ), 80 'accent-3' => __( 'The strongest contrast with the background, not an accent: hover states, and the background of inverted sections such as the navigation overlay, with Base for the text.', 'ipsum' ), 81 'accent-5' => __( 'A subtle, translucent wash behind quotes, tags and table headers. It works on any background; keep Contrast text on it.', 'ipsum' ), 82 'accent-6' => __( 'Faint, translucent lines, such as borders, list markers and table rules. Not for text.', 'ipsum' ), 83 ); 84 } 85 endif; 86 87 if ( ! function_exists( 'ipsum_get_design_guide_schema' ) ) : 88 /** 89 * Returns the JSON Schema of the design guide, with one property per section. 90 * 91 * @since Ipsum 1.0 92 * 93 * @return array The output schema of the design guide ability. 94 */ 95 function ipsum_get_design_guide_schema() { 96 $string = array( 'type' => 'string' ); 97 $nullable = array( 'type' => array( 'string', 'null' ) ); 98 $strings = array( 99 'type' => 'array', 100 'items' => $string, 101 ); 102 $scheme = array( 103 'type' => 'string', 104 'enum' => array( 'light', 'dark' ), 105 ); 106 $object_list = static function ( $description, $properties ) { 107 return array( 108 'type' => 'array', 109 'description' => $description, 110 'items' => array( 111 'type' => 'object', 112 'properties' => $properties, 113 ), 114 ); 115 }; 116 $preset_list = static function ( $description, $value_key, $value_schema ) use ( $object_list, $string ) { 117 return $object_list( 118 $description, 119 array( 120 'slug' => $string, 121 'name' => $string, 122 $value_key => $value_schema, 123 ) 124 ); 125 }; 126 127 $colors = $preset_list( __( 'The color palette, with what each color is for. Every style variation redefines the same slugs. Colors added on the site have no role.', 'ipsum' ), 'color', $string ); 128 129 $colors['items']['properties']['role'] = $string; 130 131 return array( 132 'type' => 'object', 133 'additionalProperties' => false, 134 'properties' => array( 135 'theme' => array( 136 'type' => 'object', 137 'description' => __( 'The active theme. Its stylesheet is the one to use in REST routes such as /wp/v2/global-styles/themes/{stylesheet}.', 'ipsum' ), 138 'properties' => array( 139 'name' => $string, 140 'stylesheet' => $string, 141 ), 142 ), 143 'colorScheme' => array_merge( 144 $scheme, 145 array( 'description' => __( 'Whether the active palette is light or dark.', 'ipsum' ) ) 146 ), 147 'colors' => $colors, 148 'gradients' => $preset_list( __( 'The gradient presets.', 'ipsum' ), 'gradient', $string ), 149 'duotone' => $preset_list( __( 'The duotone filter presets.', 'ipsum' ), 'colors', $strings ), 150 'typography' => array( 151 'type' => 'object', 152 'description' => __( 'The font families of body text and headings, by slug, and the font family and font size presets.', 'ipsum' ), 153 'properties' => array( 154 'bodyFont' => $nullable, 155 'headingFont' => $nullable, 156 'blockFonts' => array( 157 'type' => 'object', 158 'description' => __( 'The blocks set in another font than body text, such as buttons, navigation and post meta, grouped by font family slug.', 'ipsum' ), 159 'additionalProperties' => $strings, 160 ), 161 'fontFamilies' => $preset_list( __( 'The font family presets.', 'ipsum' ), 'fontFamily', $string ), 162 'fontSizes' => $preset_list( __( 'The font size presets, smallest first.', 'ipsum' ), 'size', $string ), 163 ), 164 ), 165 'spacing' => $preset_list( __( 'The spacing scale, smallest first.', 'ipsum' ), 'size', $string ), 166 'layout' => array( 167 'type' => 'object', 168 'description' => __( 'The width of the content, and of wide-aligned blocks.', 'ipsum' ), 169 'properties' => array( 170 'contentSize' => $nullable, 171 'wideSize' => $nullable, 172 ), 173 ), 174 'styleVariations' => $object_list( 175 __( 'The style variations. Each one lists only what it changes: colorScheme and colors for the palette, bodyFont and headingFont for the fonts, and blocks for the style properties it sets on each block. Their full data is at /wp/v2/global-styles/themes/{stylesheet}/variations.', 'ipsum' ), 176 array( 177 'title' => $string, 178 'colorScheme' => $scheme, 179 'colors' => array( 180 'type' => 'object', 181 'additionalProperties' => $string, 182 ), 183 'bodyFont' => $nullable, 184 'headingFont' => $nullable, 185 'blocks' => array( 186 'type' => 'object', 187 'additionalProperties' => $strings, 188 ), 189 ) 190 ), 191 'blockStyles' => $object_list( 192 __( 'The block styles registered on this site, beyond the ones blocks define themselves. Add className to a block of one of the blockTypes to apply it.', 'ipsum' ), 193 array( 194 'name' => $string, 195 'label' => $string, 196 'className' => $string, 197 'blockTypes' => $strings, 198 ) 199 ), 200 'patterns' => $object_list( 201 __( 'The patterns of the theme, without their markup, which is at /wp/v2/block-patterns/patterns. Hidden patterns fill templates and template parts and are left out of the inserter.', 'ipsum' ), 202 array( 203 'slug' => $string, 204 'title' => $string, 205 'description' => $string, 206 'categories' => $strings, 207 'blockTypes' => $strings, 208 'templateTypes' => $strings, 209 'postTypes' => $strings, 210 'hidden' => array( 'type' => 'boolean' ), 211 ) 212 ), 213 'templates' => $object_list( 214 __( 'The templates, with the pattern that fills each. The pattern is null when a template holds its own markup, as it does once it is customized.', 'ipsum' ), 215 array( 216 'slug' => $string, 217 'title' => $string, 218 'description' => $string, 219 'pattern' => $nullable, 220 'customized' => array( 'type' => 'boolean' ), 221 'postTypes' => $strings, 222 ) 223 ), 224 'templateParts' => $object_list( 225 __( 'The template parts, with their area and the pattern that fills each.', 'ipsum' ), 226 array( 227 'slug' => $string, 228 'title' => $string, 229 'area' => $string, 230 'pattern' => $nullable, 231 'customized' => array( 'type' => 'boolean' ), 232 ) 233 ), 234 'templatePartAreas' => $object_list( 235 __( 'The areas a template part can belong to.', 'ipsum' ), 236 array( 237 'area' => $string, 238 'label' => $string, 239 'description' => $string, 240 ) 241 ), 242 'guidelines' => array_merge( 243 $strings, 244 array( 'description' => __( 'How the theme is meant to be built with.', 'ipsum' ) ) 245 ), 246 ), 247 ); 248 } 249 endif; 250 251 if ( ! function_exists( 'ipsum_get_design_guide_presets' ) ) : 252 /** 253 * Lists the theme's presets of one kind, followed by any the site adds. 254 * 255 * @since Ipsum 1.0 256 * 257 * @param array $presets The presets from the global settings, keyed by origin. 258 * @param string[] $keys The preset properties to keep. 259 * @return array[] The presets, with only the given properties. 260 */ 261 function ipsum_get_design_guide_presets( $presets, $keys ) { 262 $items = array(); 263 foreach ( array( 'theme', 'custom' ) as $origin ) { 264 foreach ( $presets[ $origin ] ?? array() as $preset ) { 265 $item = array(); 266 foreach ( $keys as $key ) { 267 if ( isset( $preset[ $key ] ) ) { 268 $item[ $key ] = $preset[ $key ]; 269 } 270 } 271 $items[] = $item; 272 } 273 } 274 return $items; 275 } 276 endif; 277 278 if ( ! function_exists( 'ipsum_get_design_guide_font_slug' ) ) : 279 /** 280 * Returns the slug of the font family preset a style refers to. 281 * 282 * @since Ipsum 1.0 283 * 284 * @param mixed $font_family A fontFamily style value. 285 * @return string|null The preset slug, the value itself when it is not a 286 * preset, or null when there is no value. 287 */ 288 function ipsum_get_design_guide_font_slug( $font_family ) { 289 if ( ! is_string( $font_family ) || '' === $font_family ) { 290 return null; 291 } 292 if ( preg_match( '/^var(?::preset\|font-family\||\(--wp--preset--font-family--)([a-z0-9-]+)\)?$/', $font_family, $matches ) ) { 293 return $matches[1]; 294 } 295 return $font_family; 296 } 297 endif; 298 299 if ( ! function_exists( 'ipsum_get_design_guide_pattern_slug' ) ) : 300 /** 301 * Returns the pattern a template or template part delegates to. 302 * 303 * @since Ipsum 1.0 304 * 305 * @param string $content The template or template part markup. 306 * @return string|null The pattern slug, or null when the markup is not a 307 * single Pattern block. 308 */ 309 function ipsum_get_design_guide_pattern_slug( $content ) { 310 $blocks = array_values( 311 array_filter( 312 parse_blocks( (string) $content ), 313 static function ( $block ) { 314 return null !== $block['blockName']; 315 } 316 ) 317 ); 318 if ( 1 === count( $blocks ) && 'core/pattern' === $blocks[0]['blockName'] && ! empty( $blocks[0]['attrs']['slug'] ) ) { 319 return $blocks[0]['attrs']['slug']; 320 } 321 return null; 322 } 323 endif; 324 325 if ( ! function_exists( 'ipsum_get_design_guide' ) ) : 326 /** 327 * Builds the design guide from the site as it is, so it follows the active 328 * style variation and any change made in Global Styles or the Site Editor. 329 * 330 * @since Ipsum 1.0 331 * 332 * @param array $input { 333 * The ability input. 334 * 335 * @type string[] $fields Optional. The sections to return. Default all of them. 336 * } 337 * @return array The design guide. 338 */ 339 function ipsum_get_design_guide( $input = array() ) { 340 $sections = array_keys( ipsum_get_design_guide_schema()['properties'] ); 341 if ( ! empty( $input['fields'] ) ) { 342 $sections = array_intersect( $sections, $input['fields'] ); 343 } 344 $sections = array_flip( $sections ); 345 346 // Resolving the merged data also registers the block styles in styles/blocks. 347 $settings = wp_get_global_settings(); 348 $styles = wp_get_global_styles(); 349 $guide = array(); 350 351 if ( isset( $sections['theme'] ) ) { 352 $guide['theme'] = array( 353 'name' => wp_get_theme()->get( 'Name' ), 354 'stylesheet' => get_stylesheet(), 355 ); 356 } 357 358 if ( isset( $sections['colorScheme'] ) ) { 359 // The audio player's CSS also reads a palette that sets no scheme as light. 360 $guide['colorScheme'] = 'dark' === ( $settings['custom']['controlScheme'] ?? '' ) ? 'dark' : 'light'; 361 } 362 363 if ( isset( $sections['colors'] ) ) { 364 $roles = ipsum_get_color_roles(); 365 $guide['colors'] = array(); 366 foreach ( ipsum_get_design_guide_presets( $settings['color']['palette'] ?? array(), array( 'slug', 'name', 'color' ) ) as $color ) { 367 if ( isset( $color['slug'], $roles[ $color['slug'] ] ) ) { 368 $color['role'] = $roles[ $color['slug'] ]; 369 } 370 $guide['colors'][] = $color; 371 } 372 } 373 374 if ( isset( $sections['gradients'] ) ) { 375 $guide['gradients'] = ipsum_get_design_guide_presets( $settings['color']['gradients'] ?? array(), array( 'slug', 'name', 'gradient' ) ); 376 } 377 378 if ( isset( $sections['duotone'] ) ) { 379 $guide['duotone'] = ipsum_get_design_guide_presets( $settings['color']['duotone'] ?? array(), array( 'slug', 'name', 'colors' ) ); 380 } 381 382 if ( isset( $sections['typography'] ) ) { 383 $body_font = ipsum_get_design_guide_font_slug( $styles['typography']['fontFamily'] ?? '' ); 384 $block_fonts = array(); 385 foreach ( $styles['blocks'] ?? array() as $block_type => $block_styles ) { 386 $block_font = ipsum_get_design_guide_font_slug( $block_styles['typography']['fontFamily'] ?? '' ); 387 if ( null !== $block_font && $block_font !== $body_font ) { 388 $block_fonts[ $block_font ][] = $block_type; 389 } 390 } 391 $guide['typography'] = array( 392 'bodyFont' => $body_font, 393 'headingFont' => ipsum_get_design_guide_font_slug( $styles['elements']['heading']['typography']['fontFamily'] ?? '' ) ?? $body_font, 394 'blockFonts' => $block_fonts, 395 'fontFamilies' => ipsum_get_design_guide_presets( $settings['typography']['fontFamilies'] ?? array(), array( 'slug', 'name', 'fontFamily' ) ), 396 'fontSizes' => ipsum_get_design_guide_presets( $settings['typography']['fontSizes'] ?? array(), array( 'slug', 'name', 'size' ) ), 397 ); 398 } 399 400 if ( isset( $sections['spacing'] ) ) { 401 $guide['spacing'] = ipsum_get_design_guide_presets( $settings['spacing']['spacingSizes'] ?? array(), array( 'slug', 'name', 'size' ) ); 402 } 403 404 if ( isset( $sections['layout'] ) ) { 405 $guide['layout'] = array( 406 'contentSize' => $settings['layout']['contentSize'] ?? null, 407 'wideSize' => $settings['layout']['wideSize'] ?? null, 408 ); 409 } 410 411 if ( isset( $sections['styleVariations'] ) ) { 412 $guide['styleVariations'] = array(); 413 foreach ( WP_Theme_JSON_Resolver::get_style_variations() as $variation ) { 414 $variation_settings = $variation['settings'] ?? array(); 415 $variation_styles = $variation['styles'] ?? array(); 416 $item = array( 'title' => $variation['title'] ); 417 418 if ( isset( $variation_settings['custom']['controlScheme'] ) ) { 419 $item['colorScheme'] = 'dark' === $variation_settings['custom']['controlScheme'] ? 'dark' : 'light'; 420 } 421 if ( ! empty( $variation_settings['color']['palette'] ) ) { 422 $item['colors'] = array_column( ipsum_get_design_guide_presets( $variation_settings['color']['palette'], array( 'slug', 'color' ) ), 'color', 'slug' ); 423 } 424 if ( ! empty( $variation_styles['typography']['fontFamily'] ) ) { 425 $item['bodyFont'] = ipsum_get_design_guide_font_slug( $variation_styles['typography']['fontFamily'] ); 426 } 427 if ( ! empty( $variation_styles['elements']['heading']['typography']['fontFamily'] ) ) { 428 $item['headingFont'] = ipsum_get_design_guide_font_slug( $variation_styles['elements']['heading']['typography']['fontFamily'] ); 429 } 430 if ( ! empty( $variation_styles['blocks'] ) ) { 431 $item['blocks'] = array_map( 'array_keys', $variation_styles['blocks'] ); 432 } 433 $guide['styleVariations'][] = $item; 434 } 435 } 436 437 if ( isset( $sections['blockStyles'] ) ) { 438 $block_styles = array(); 439 foreach ( WP_Block_Styles_Registry::get_instance()->get_all_registered() as $block_type => $block_type_styles ) { 440 foreach ( $block_type_styles as $name => $style ) { 441 if ( ! isset( $block_styles[ $name ] ) ) { 442 $block_styles[ $name ] = array( 443 'name' => $name, 444 'label' => $style['label'] ?? $name, 445 'className' => 'is-style-' . $name, 446 'blockTypes' => array(), 447 ); 448 } 449 $block_styles[ $name ]['blockTypes'][] = $block_type; 450 } 451 } 452 $guide['blockStyles'] = array_values( $block_styles ); 453 } 454 455 if ( isset( $sections['patterns'] ) ) { 456 $theme_pattern_dirs = array( 457 wp_normalize_path( get_stylesheet_directory() . '/patterns' ), 458 wp_normalize_path( get_template_directory() . '/patterns' ), 459 ); 460 $guide['patterns'] = array(); 461 foreach ( WP_Block_Patterns_Registry::get_instance()->get_all_registered() as $pattern ) { 462 if ( empty( $pattern['filePath'] ) || ! in_array( wp_normalize_path( dirname( $pattern['filePath'] ) ), $theme_pattern_dirs, true ) ) { 463 continue; 464 } 465 $item = array( 466 'slug' => $pattern['name'], 467 'title' => $pattern['title'], 468 'description' => $pattern['description'] ?? '', 469 'categories' => $pattern['categories'] ?? array(), 470 ); 471 foreach ( array( 'blockTypes', 'templateTypes', 'postTypes' ) as $key ) { 472 if ( ! empty( $pattern[ $key ] ) ) { 473 $item[ $key ] = $pattern[ $key ]; 474 } 475 } 476 $item['hidden'] = isset( $pattern['inserter'] ) && ! $pattern['inserter']; 477 $guide['patterns'][] = $item; 478 } 479 } 480 481 foreach ( array( 482 'templates' => 'wp_template', 483 'templateParts' => 'wp_template_part', 484 ) as $section => $template_type ) { 485 if ( ! isset( $sections[ $section ] ) ) { 486 continue; 487 } 488 $guide[ $section ] = array(); 489 foreach ( get_block_templates( array(), $template_type ) as $template ) { 490 $item = array( 491 'slug' => $template->slug, 492 'title' => $template->title, 493 ); 494 if ( 'wp_template' === $template_type ) { 495 $item['description'] = (string) $template->description; 496 } else { 497 $item['area'] = $template->area; 498 } 499 $item['pattern'] = ipsum_get_design_guide_pattern_slug( $template->content ); 500 $item['customized'] = 'custom' === $template->source; 501 if ( ! empty( $template->post_types ) ) { 502 $item['postTypes'] = $template->post_types; 503 } 504 $guide[ $section ][] = $item; 505 } 506 } 507 508 if ( isset( $sections['templatePartAreas'] ) ) { 509 $guide['templatePartAreas'] = array(); 510 foreach ( get_allowed_block_template_part_areas() as $area ) { 511 $guide['templatePartAreas'][] = array( 512 'area' => $area['area'], 513 'label' => $area['label'], 514 'description' => $area['description'], 515 ); 516 } 517 } 518 519 if ( isset( $sections['guidelines'] ) ) { 520 $guide['guidelines'] = array( 521 __( 'Style with the presets in this guide, by slug, rather than with literal values. Every style variation redefines the colors, so a literal color keeps the look of one variation under all the others.', 'ipsum' ), 522 __( 'Each template and template part holds a single Pattern block, and that pattern holds the markup. To change one on this site, save new markup to the template or template part itself.', 'ipsum' ), 523 __( 'Hidden patterns are there to fill templates and template parts, not to be inserted on their own.', 'ipsum' ), 524 ); 525 } 526 527 return $guide; 528 } 529 endif;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Nov 4 08:20:38 2026 | Cross-referenced by PHPXref |