diff --git a/package-lock.json b/package-lock.json index 257c1400218fd..b78961ef1754f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3888,6 +3888,44 @@ } } }, + "@wordpress/edit-site": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-3.0.5.tgz", + "integrity": "sha512-FYr6xwZcDaPxiOBsmzHnbaSdGeGD55I2j2flJPtLM4v7jzrIFVOBBaFtGUAUTtl0oagOlVNkQidL+/mVAcbIeQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@wordpress/a11y": "^3.2.3", + "@wordpress/api-fetch": "^5.2.5", + "@wordpress/block-editor": "^8.0.0", + "@wordpress/block-library": "^6.0.3", + "@wordpress/blocks": "^11.1.3", + "@wordpress/components": "^19.0.1", + "@wordpress/compose": "^5.0.5", + "@wordpress/core-data": "^4.0.5", + "@wordpress/data": "^6.1.3", + "@wordpress/data-controls": "^2.2.6", + "@wordpress/editor": "^12.0.2", + "@wordpress/element": "^4.0.3", + "@wordpress/hooks": "^3.2.1", + "@wordpress/html-entities": "^3.2.2", + "@wordpress/i18n": "^4.2.3", + "@wordpress/icons": "^6.1.0", + "@wordpress/interface": "^4.1.3", + "@wordpress/keyboard-shortcuts": "^3.0.5", + "@wordpress/keycodes": "^3.2.3", + "@wordpress/media-utils": "^3.0.4", + "@wordpress/notices": "^3.2.6", + "@wordpress/plugins": "^4.0.5", + "@wordpress/primitives": "^3.0.3", + "@wordpress/url": "^3.3.0", + "classnames": "^2.3.1", + "downloadjs": "^1.4.7", + "file-saver": "^2.0.2", + "jszip": "^3.2.2", + "lodash": "^4.17.21", + "rememo": "^3.0.0" + } + }, "@wordpress/edit-widgets": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-3.1.0.tgz", @@ -7438,7 +7476,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -10097,6 +10135,11 @@ } } }, + "downloadjs": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", + "integrity": "sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=" + }, "downshift": { "version": "6.1.7", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.7.tgz", @@ -12061,6 +12104,11 @@ "flat-cache": "^3.0.4" } }, + "file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "file-sync-cmp": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", @@ -14372,6 +14420,11 @@ "ev-emitter": "^1.0.0" } }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -16924,6 +16977,17 @@ "object.assign": "^4.1.2" } }, + "jszip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -17202,6 +17266,14 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "liftup": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", @@ -19837,8 +19909,7 @@ "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parallel-transform": { "version": "1.2.0", @@ -22865,6 +22936,11 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", diff --git a/package.json b/package.json index 08a23f46cc078..e10b8907f21d2 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@wordpress/dom": "3.2.6", "@wordpress/dom-ready": "3.2.2", "@wordpress/edit-post": "5.0.5", + "@wordpress/edit-site": "3.0.5", "@wordpress/edit-widgets": "3.1.0", "@wordpress/editor": "12.0.2", "@wordpress/element": "4.0.3", diff --git a/src/wp-admin/menu.php b/src/wp-admin/menu.php index 20a0a2f4771e2..94f479fb8cf6b 100644 --- a/src/wp-admin/menu.php +++ b/src/wp-admin/menu.php @@ -202,8 +202,24 @@ /* translators: %s: Number of available theme updates. */ $submenu['themes.php'][5] = array( sprintf( __( 'Themes %s' ), $count ), $appearance_cap, 'themes.php' ); +if ( wp_is_block_template_theme() ) { + $submenu['themes.php'][6] = array( + sprintf( + /* translators: %s: "beta" label */ + __( 'Editor %s' ), + '' . __( 'beta' ) . '' + ), + 'edit_theme_options', + 'site-editor.php', + ); +} + +// Hide Customize link on block themes unless a plugin or theme is using +// customize_register to add a setting. +if ( ! wp_is_block_template_theme() || has_action( 'customize_register' ) ) { $customize_url = add_query_arg( 'return', urlencode( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ), 'customize.php' ); - $submenu['themes.php'][6] = array( __( 'Customize' ), 'customize', esc_url( $customize_url ), '', 'hide-if-no-customize' ); + $submenu['themes.php'][7] = array( __( 'Customize' ), 'customize', esc_url( $customize_url ), '', 'hide-if-no-customize' ); +} if ( current_theme_supports( 'menus' ) || current_theme_supports( 'widgets' ) ) { $submenu['themes.php'][10] = array( __( 'Menus' ), 'edit_theme_options', 'nav-menus.php' ); diff --git a/src/wp-admin/site-editor.php b/src/wp-admin/site-editor.php new file mode 100644 index 0000000000000..e5d149f23515d --- /dev/null +++ b/src/wp-admin/site-editor.php @@ -0,0 +1,115 @@ +' . __( 'You need a higher level of permission.' ) . '' . + '

' . __( 'Sorry, you are not allowed to edit theme options on this site.' ) . '

', + 403 + ); +} + +if ( ! wp_is_block_template_theme() ) { + wp_die( __( 'The theme you are currently using is not compatible with Full Site Editing.' ) ); +} + +// Used in the HTML title tag. +$title = __( 'Editor (beta)' ); +$parent_file = 'themes.php'; + +// Flag that we're loading the block editor. +$current_screen = get_current_screen(); +$current_screen->is_block_editor( true ); + +$block_editor_context = new WP_Block_Editor_Context(); + +$active_global_styles_id = WP_Theme_JSON_Resolver::get_user_custom_post_type_id(); +$active_theme = wp_get_theme()->get_stylesheet(); +$preload_paths = array( + array( '/wp/v2/media', 'OPTIONS' ), + '/', + '/wp/v2/types?context=edit', + '/wp/v2/taxonomies?context=edit', + '/wp/v2/pages?context=edit', + '/wp/v2/categories?context=edit', + '/wp/v2/posts?context=edit', + '/wp/v2/tags?context=edit', + '/wp/v2/templates?context=edit', + '/wp/v2/template-parts?context=edit', + '/wp/v2/settings', + '/wp/v2/themes?context=edit&status=active', + '/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit', + '/wp/v2/global-styles/' . $active_global_styles_id, + '/wp/v2/themes/' . $active_theme . '/global-styles', +); +block_editor_rest_api_preload( $preload_paths, $block_editor_context ); + +$editor_settings = get_block_editor_settings( + array( + 'siteUrl' => site_url(), + 'postsPerPage' => get_option( 'posts_per_page' ), + 'styles' => get_block_editor_theme_styles(), + 'defaultTemplateTypes' => get_default_block_template_types(), + 'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(), + '__experimentalBlockPatterns' => WP_Block_Patterns_Registry::get_instance()->get_all_registered(), + '__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(), + ), + $block_editor_context +); + +wp_add_inline_script( + 'wp-edit-site', + sprintf( + 'wp.domReady( function() { + wp.editSite.initialize( "site-editor", %s ); + } );', + wp_json_encode( $editor_settings ) + ) +); + +// Preload server-registered block schemas. +wp_add_inline_script( + 'wp-blocks', + 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');' +); + +wp_add_inline_script( + 'wp-blocks', + sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $post ) ) ), + 'after' +); + +wp_enqueue_script( 'wp-edit-site' ); +wp_enqueue_script( 'wp-format-library' ); +wp_enqueue_style( 'wp-edit-site' ); +wp_enqueue_style( 'wp-format-library' ); +wp_enqueue_media(); + +if ( + current_theme_supports( 'wp-block-styles' ) || + ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 ) +) { + wp_enqueue_style( 'wp-block-library-theme' ); +} + +/** This action is documented in wp-admin/edit-form-blocks.php */ +do_action( 'enqueue_block_editor_assets' ); + +require_once ABSPATH . 'wp-admin/admin-header.php'; +?> + +
+ + array('dependencies' => array('wp-dom-ready', 'wp-i18n', 'wp-polyfill'), 'version' => '531b91a758b525158d15c140a522819b'), 'annotations.js' => array('dependencies' => array('lodash', 'wp-data', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-rich-text'), 'version' => '460983ea433ad4adb480359a940d1271'), 'api-fetch.js' => array('dependencies' => array('wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => 'e57113647e68a852cb7b1cd8e140bc82'), 'autop.js' => array('dependencies' => array('wp-polyfill'), 'version' => '98ee456bef50e518083b2d908b12efd6'), 'blob.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'b8855b04f6a52a7bca89f8d945d46866'), 'block-directory.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives'), 'version' => 'e3b4864b1761f45559b125d181eeccea'), 'block-editor.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-shortcode', 'wp-token-list', 'wp-url', 'wp-warning', 'wp-wordcount'), 'version' => '078463eccb0f2a6c4b207270e5938454'), 'block-library.js' => array('dependencies' => array('lodash', 'moment', 'wp-a11y', 'wp-api-fetch', 'wp-autop', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keycodes', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-reusable-blocks', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport'), 'version' => '0a3e4865c8c2025c841cea5aee3c6e84'), 'block-serialization-default-parser.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'cc8e5d0e23cf68bb9944bf2a7a016e52'), 'blocks.js' => array('dependencies' => array('lodash', 'wp-autop', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-shortcode'), 'version' => '85a08117e58397f5e519e8ee9f1ef820'), 'components.js' => array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-a11y', 'wp-compose', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-warning'), 'version' => '1e5a68c6be5b9cc6532d58a5062c4707'), 'compose.js' => array('dependencies' => array('lodash', 'react', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-polyfill', 'wp-priority-queue'), 'version' => '68b1701c372843d24a167ee93e21f28f'), 'core-data.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-blocks', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-url'), 'version' => '2df3489e429bffb1891d0d748c7b4b31'), 'customize-widgets.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-viewport', 'wp-widgets'), 'version' => '61524e8db487c3e60a2b3ce6e5371dbd'), 'data.js' => array('dependencies' => array('lodash', 'react', 'wp-compose', 'wp-deprecated', 'wp-element', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-priority-queue', 'wp-redux-routine'), 'version' => '3fa92a13f4f6644731f73af5ecdbf518'), 'data-controls.js' => array('dependencies' => array('wp-api-fetch', 'wp-data', 'wp-deprecated', 'wp-polyfill'), 'version' => 'ceef9ce8c9c058d69ebf44db39353687'), 'date.js' => array('dependencies' => array('moment', 'wp-polyfill'), 'version' => '70a2be92e6099f67b996b8a7e0266f5c'), 'deprecated.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => '13724e4923d40e224e7c99fdc350a6a1'), 'dom.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '567a844d6765c6c0422c55df898f1681'), 'dom-ready.js' => array('dependencies' => array('wp-polyfill'), 'version' => '1e1b9c1535c1c32d3236d41aa27c7bf9'), 'edit-post.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-url', 'wp-viewport', 'wp-warning'), 'version' => 'ee6c27d32b67d2c0f22907b72d8762ed'), 'edit-widgets.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-reusable-blocks', 'wp-url', 'wp-viewport', 'wp-widgets'), 'version' => 'b921a96625a3d8c0d1e0d7472df34de8'), 'editor.js' => array('dependencies' => array('lodash', 'react', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-date', 'wp-deprecated', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-reusable-blocks', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-wordcount'), 'version' => 'c6e02b89186dd132d70191c58a3dec41'), 'element.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-escape-html', 'wp-polyfill'), 'version' => 'ed6d85e269fcb3632c76dec6b9ac02f1'), 'escape-html.js' => array('dependencies' => array('wp-polyfill'), 'version' => '4f03d9c45a01df8191f6833373fe1cfa'), 'format-library.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-url'), 'version' => '86f8584fd0845366c326ed2e31326a1e'), 'hooks.js' => array('dependencies' => array('wp-polyfill'), 'version' => '02fd2defc23cb5c2837081df67b97158'), 'html-entities.js' => array('dependencies' => array('wp-polyfill'), 'version' => '140ecb91be91e7eec34220584d085033'), 'i18n.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => '48abe0c2523b902e445788bb8132ef7e'), 'is-shallow-equal.js' => array('dependencies' => array('wp-polyfill'), 'version' => '798b660598677b39413fdb5f68624aa8'), 'keyboard-shortcuts.js' => array('dependencies' => array('lodash', 'wp-data', 'wp-element', 'wp-keycodes', 'wp-polyfill'), 'version' => '03be8557c1d6f9c98d9f2a39b0d45968'), 'keycodes.js' => array('dependencies' => array('lodash', 'wp-i18n', 'wp-polyfill'), 'version' => '34f4da80b9b52d7f35a142592777b29c'), 'list-reusable-blocks.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '99752f8ee65e66fefce46b8af1dac732'), 'media-utils.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-blob', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '52a1fb71f5e51adfecbe905eccae04b4'), 'notices.js' => array('dependencies' => array('lodash', 'wp-data', 'wp-polyfill'), 'version' => '0b40907eed64e81bf22bb448c88642d3'), 'nux.js' => array('dependencies' => array('lodash', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => '1120e26c43e94121389f5d1c9f6e89b6'), 'plugins.js' => array('dependencies' => array('lodash', 'wp-compose', 'wp-element', 'wp-hooks', 'wp-polyfill', 'wp-primitives'), 'version' => 'c860421df5601d15a8df4afcbc212304'), 'primitives.js' => array('dependencies' => array('wp-element', 'wp-polyfill'), 'version' => 'dc0962ac2885a26f8c2abdbe0cf0e333'), 'priority-queue.js' => array('dependencies' => array('wp-polyfill'), 'version' => '18c93919f04bb681234310b314675b17'), 'redux-routine.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => 'c825bcfd263c88dcd76e14b67d47340b'), 'reusable-blocks.js' => array('dependencies' => array('lodash', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '2d4d45c0e771117074787655ee5efbcb'), 'rich-text.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-compose', 'wp-data', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-keycodes', 'wp-polyfill'), 'version' => '04ed6c49d04854d7ca20cb9d321e5e87'), 'server-side-render.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '04ce14348d15952b7f9fed3b6e32a745'), 'shortcode.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '15757929bc8d52a3b184c3fcaeb38c64'), 'token-list.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '7bcd3684420783ef1f6ebb7680fe958e'), 'url.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => 'ef8beff0847cbaa0e337362716c13bea'), 'viewport.js' => array('dependencies' => array('lodash', 'wp-compose', 'wp-data', 'wp-element', 'wp-polyfill'), 'version' => 'a4b7cdcc29f09d698255b3aec03ac6c1'), 'warning.js' => array('dependencies' => array('wp-polyfill'), 'version' => '5ebe2df53cf3c74ebffc7e2d3e76184c'), 'widgets.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives'), 'version' => 'd8086829048e496d396102031b6b98f4'), 'wordcount.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '02339c8ad03da1e7a03f9212da004007')); \ No newline at end of file + array('dependencies' => array('wp-dom-ready', 'wp-i18n', 'wp-polyfill'), 'version' => '531b91a758b525158d15c140a522819b'), 'annotations.js' => array('dependencies' => array('lodash', 'wp-data', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-rich-text'), 'version' => '460983ea433ad4adb480359a940d1271'), 'api-fetch.js' => array('dependencies' => array('wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => 'e57113647e68a852cb7b1cd8e140bc82'), 'autop.js' => array('dependencies' => array('wp-polyfill'), 'version' => '98ee456bef50e518083b2d908b12efd6'), 'blob.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'b8855b04f6a52a7bca89f8d945d46866'), 'block-directory.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives'), 'version' => 'e3b4864b1761f45559b125d181eeccea'), 'block-editor.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-shortcode', 'wp-token-list', 'wp-url', 'wp-warning', 'wp-wordcount'), 'version' => '078463eccb0f2a6c4b207270e5938454'), 'block-library.js' => array('dependencies' => array('lodash', 'moment', 'wp-a11y', 'wp-api-fetch', 'wp-autop', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keycodes', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-reusable-blocks', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport'), 'version' => '0a3e4865c8c2025c841cea5aee3c6e84'), 'block-serialization-default-parser.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'cc8e5d0e23cf68bb9944bf2a7a016e52'), 'blocks.js' => array('dependencies' => array('lodash', 'wp-autop', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-shortcode'), 'version' => '85a08117e58397f5e519e8ee9f1ef820'), 'components.js' => array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-a11y', 'wp-compose', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-warning'), 'version' => '1e5a68c6be5b9cc6532d58a5062c4707'), 'compose.js' => array('dependencies' => array('lodash', 'react', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-polyfill', 'wp-priority-queue'), 'version' => '68b1701c372843d24a167ee93e21f28f'), 'core-data.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-blocks', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-url'), 'version' => '2df3489e429bffb1891d0d748c7b4b31'), 'customize-widgets.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-viewport', 'wp-widgets'), 'version' => '61524e8db487c3e60a2b3ce6e5371dbd'), 'data.js' => array('dependencies' => array('lodash', 'react', 'wp-compose', 'wp-deprecated', 'wp-element', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-priority-queue', 'wp-redux-routine'), 'version' => '3fa92a13f4f6644731f73af5ecdbf518'), 'data-controls.js' => array('dependencies' => array('wp-api-fetch', 'wp-data', 'wp-deprecated', 'wp-polyfill'), 'version' => 'ceef9ce8c9c058d69ebf44db39353687'), 'date.js' => array('dependencies' => array('moment', 'wp-polyfill'), 'version' => '70a2be92e6099f67b996b8a7e0266f5c'), 'deprecated.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => '13724e4923d40e224e7c99fdc350a6a1'), 'dom.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '567a844d6765c6c0422c55df898f1681'), 'dom-ready.js' => array('dependencies' => array('wp-polyfill'), 'version' => '1e1b9c1535c1c32d3236d41aa27c7bf9'), 'edit-post.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-url', 'wp-viewport', 'wp-warning'), 'version' => 'ee6c27d32b67d2c0f22907b72d8762ed'), 'edit-site.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-url', 'wp-viewport'), 'version' => '162621f7d189e8e2a1bfe12183f73ece'), 'edit-widgets.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-reusable-blocks', 'wp-url', 'wp-viewport', 'wp-widgets'), 'version' => 'b921a96625a3d8c0d1e0d7472df34de8'), 'editor.js' => array('dependencies' => array('lodash', 'react', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-date', 'wp-deprecated', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-reusable-blocks', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-wordcount'), 'version' => 'c6e02b89186dd132d70191c58a3dec41'), 'element.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-escape-html', 'wp-polyfill'), 'version' => 'ed6d85e269fcb3632c76dec6b9ac02f1'), 'escape-html.js' => array('dependencies' => array('wp-polyfill'), 'version' => '4f03d9c45a01df8191f6833373fe1cfa'), 'format-library.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-url'), 'version' => '86f8584fd0845366c326ed2e31326a1e'), 'hooks.js' => array('dependencies' => array('wp-polyfill'), 'version' => '02fd2defc23cb5c2837081df67b97158'), 'html-entities.js' => array('dependencies' => array('wp-polyfill'), 'version' => '140ecb91be91e7eec34220584d085033'), 'i18n.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => '48abe0c2523b902e445788bb8132ef7e'), 'is-shallow-equal.js' => array('dependencies' => array('wp-polyfill'), 'version' => '798b660598677b39413fdb5f68624aa8'), 'keyboard-shortcuts.js' => array('dependencies' => array('lodash', 'wp-data', 'wp-element', 'wp-keycodes', 'wp-polyfill'), 'version' => '03be8557c1d6f9c98d9f2a39b0d45968'), 'keycodes.js' => array('dependencies' => array('lodash', 'wp-i18n', 'wp-polyfill'), 'version' => '34f4da80b9b52d7f35a142592777b29c'), 'list-reusable-blocks.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '99752f8ee65e66fefce46b8af1dac732'), 'media-utils.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-blob', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '52a1fb71f5e51adfecbe905eccae04b4'), 'notices.js' => array('dependencies' => array('lodash', 'wp-data', 'wp-polyfill'), 'version' => '0b40907eed64e81bf22bb448c88642d3'), 'nux.js' => array('dependencies' => array('lodash', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => '1120e26c43e94121389f5d1c9f6e89b6'), 'plugins.js' => array('dependencies' => array('lodash', 'wp-compose', 'wp-element', 'wp-hooks', 'wp-polyfill', 'wp-primitives'), 'version' => 'c860421df5601d15a8df4afcbc212304'), 'primitives.js' => array('dependencies' => array('wp-element', 'wp-polyfill'), 'version' => 'dc0962ac2885a26f8c2abdbe0cf0e333'), 'priority-queue.js' => array('dependencies' => array('wp-polyfill'), 'version' => '18c93919f04bb681234310b314675b17'), 'redux-routine.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => 'c825bcfd263c88dcd76e14b67d47340b'), 'reusable-blocks.js' => array('dependencies' => array('lodash', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '2d4d45c0e771117074787655ee5efbcb'), 'rich-text.js' => array('dependencies' => array('lodash', 'wp-a11y', 'wp-compose', 'wp-data', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-keycodes', 'wp-polyfill'), 'version' => '04ed6c49d04854d7ca20cb9d321e5e87'), 'server-side-render.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '04ce14348d15952b7f9fed3b6e32a745'), 'shortcode.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '15757929bc8d52a3b184c3fcaeb38c64'), 'token-list.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '7bcd3684420783ef1f6ebb7680fe958e'), 'url.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => 'ef8beff0847cbaa0e337362716c13bea'), 'viewport.js' => array('dependencies' => array('lodash', 'wp-compose', 'wp-data', 'wp-element', 'wp-polyfill'), 'version' => 'a4b7cdcc29f09d698255b3aec03ac6c1'), 'warning.js' => array('dependencies' => array('wp-polyfill'), 'version' => '5ebe2df53cf3c74ebffc7e2d3e76184c'), 'widgets.js' => array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives'), 'version' => 'd8086829048e496d396102031b6b98f4'), 'wordcount.js' => array('dependencies' => array('lodash', 'wp-polyfill'), 'version' => '02339c8ad03da1e7a03f9212da004007')); \ No newline at end of file diff --git a/src/wp-includes/block-patterns/query-medium-posts.php b/src/wp-includes/block-patterns/query-medium-posts.php index fdcbce323acd4..bd564ebe43a20 100644 --- a/src/wp-includes/block-patterns/query-medium-posts.php +++ b/src/wp-includes/block-patterns/query-medium-posts.php @@ -9,7 +9,7 @@ 'title' => _x( 'Image at left', 'Block pattern title' ), 'blockTypes' => array( 'core/query' ), 'categories' => array( 'query' ), - 'content' => ' + 'content' => '
diff --git a/src/wp-includes/block-patterns/query-small-posts.php b/src/wp-includes/block-patterns/query-small-posts.php index c858f198133c6..2f165c1b8ba93 100644 --- a/src/wp-includes/block-patterns/query-small-posts.php +++ b/src/wp-includes/block-patterns/query-small-posts.php @@ -9,7 +9,7 @@ 'title' => _x( 'Small image and title', 'Block pattern title' ), 'blockTypes' => array( 'core/query' ), 'categories' => array( 'query' ), - 'content' => ' + 'content' => '
diff --git a/src/wp-includes/block-patterns/query-standard-posts.php b/src/wp-includes/block-patterns/query-standard-posts.php index 50fa37d637075..c05ace98db93c 100644 --- a/src/wp-includes/block-patterns/query-standard-posts.php +++ b/src/wp-includes/block-patterns/query-standard-posts.php @@ -9,7 +9,7 @@ 'title' => _x( 'Standard', 'Block pattern title' ), 'blockTypes' => array( 'core/query' ), 'categories' => array( 'query' ), - 'content' => ' + 'content' => '
diff --git a/src/wp-includes/block-supports/border.php b/src/wp-includes/block-supports/border.php index 08f3781ba6b3e..ca213cd5dc056 100644 --- a/src/wp-includes/block-supports/border.php +++ b/src/wp-includes/block-supports/border.php @@ -63,8 +63,23 @@ function wp_apply_border_support( $block_type, $block_attributes ) { wp_has_border_feature_support( $block_type, 'radius' ) && isset( $block_attributes['style']['border']['radius'] ) ) { - $border_radius = (int) $block_attributes['style']['border']['radius']; - $styles[] = sprintf( 'border-radius: %dpx;', $border_radius ); + $border_radius = $block_attributes['style']['border']['radius']; + + if ( is_array( $border_radius ) ) { + // We have individual border radius corner values. + foreach ( $border_radius as $key => $radius ) { + // Convert CamelCase corner name to kebab-case. + $corner = strtolower( preg_replace( '/(?attributes ) { + $block_type->attributes = array(); + } + + // Check for existing style attribute definition e.g. from block.json. + if ( array_key_exists( 'style', $block_type->attributes ) ) { + return; + } + + $has_dimensions_support = block_has_support( $block_type, array( '__experimentalDimensions' ), false ); + // Future block supports such as height & width will be added here. + + if ( $has_dimensions_support ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } +} + +/** + * Add CSS classes for block dimensions to the incoming attributes array. + * This will be applied to the block markup in the front-end. + * + * @since 5.9.0 + * @access private + * + * @param WP_Block_Type $block_type Block Type. + * @param array $block_attributes Block attributes. + * + * @return array Block dimensions CSS classes and inline styles. + */ +function wp_apply_dimensions_support( $block_type, $block_attributes ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( wp_skip_dimensions_serialization( $block_type ) ) { + return array(); + } + + $styles = array(); + + // Height support to be added in near future. + // Width support to be added in near future. + + return empty( $styles ) ? array() : array( 'style' => implode( ' ', $styles ) ); +} + +/** + * Checks whether serialization of the current block's dimensions properties + * should occur. + * + * @since 5.9.0 + * @access private + * + * @param WP_Block_type $block_type Block type. + * + * @return boolean Whether to serialize spacing support styles & classes. + */ +function wp_skip_dimensions_serialization( $block_type ) { + $dimensions_support = _wp_array_get( $block_type->supports, array( '__experimentalDimensions' ), false ); + return is_array( $dimensions_support ) && + array_key_exists( '__experimentalSkipSerialization', $dimensions_support ) && + $dimensions_support['__experimentalSkipSerialization']; +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'dimensions', + array( + 'register_attribute' => 'wp_register_dimensions_support', + 'apply' => 'wp_apply_dimensions_support', + ) +); diff --git a/src/wp-includes/block-supports/duotone.php b/src/wp-includes/block-supports/duotone.php index c2f2d5256d676..974aba5133a0d 100644 --- a/src/wp-includes/block-supports/duotone.php +++ b/src/wp-includes/block-supports/duotone.php @@ -266,7 +266,7 @@ function wp_tinycolor_string_to_rgb( $color_str ) { $hsla_regexp = '/^hsla' . $permissive_match4 . '$/'; if ( preg_match( $hsla_regexp, $color_str, $match ) ) { - return wp_tinycolor_hsl_to_rgb( + $rgb = wp_tinycolor_hsl_to_rgb( array( 'h' => $match[1], 's' => $match[2], @@ -384,13 +384,16 @@ function wp_register_duotone_support( $block_type ) { } } } + /** * Renders the duotone filter SVG and returns the CSS filter property to * reference the rendered SVG. * * @since 5.9.0 - * + * @access private + * @param array $preset Duotone preset value as seen in theme.json. + * * @return string Duotone CSS filter property. */ function wp_render_duotone_filter_preset( $preset ) { @@ -460,13 +463,11 @@ function wp_render_duotone_filter_preset( $preset ) { } add_action( - /* - * Safari doesn't render SVG filters defined in data URIs, - * and SVG filters won't render in the head of a document, - * so the next best place to put the SVG is in the footer. - */ + // Safari doesn't render SVG filters defined in data URIs, + // and SVG filters won't render in the head of a document, + // so the next best place to put the SVG is in the footer. is_admin() ? 'admin_footer' : 'wp_footer', - static function () use ( $svg ) { + function () use ( $svg ) { echo $svg; } ); @@ -502,84 +503,38 @@ function wp_render_duotone_support( $block_content, $block ) { return $block_content; } - $duotone_colors = $block['attrs']['style']['color']['duotone']; - - $duotone_values = array( - 'r' => array(), - 'g' => array(), - 'b' => array(), + $filter_preset = array( + 'slug' => uniqid(), + 'colors' => $block['attrs']['style']['color']['duotone'], ); - foreach ( $duotone_colors as $color_str ) { - $color = wp_tinycolor_string_to_rgb( $color_str ); - - $duotone_values['r'][] = $color['r'] / 255; - $duotone_values['g'][] = $color['g'] / 255; - $duotone_values['b'][] = $color['b'] / 255; + $filter_property = wp_render_duotone_filter_preset( $filter_preset ); + $filter_id = 'wp-duotone-' . $filter_preset['slug']; + + $scope = '.' . $filter_id; + $selectors = explode( ',', $duotone_support ); + $scoped = array(); + foreach ( $selectors as $sel ) { + $scoped[] = $scope . ' ' . trim( $sel ); } + $selector = implode( ', ', $scoped ); - $duotone_id = 'wp-duotone-filter-' . uniqid(); - - $selectors = explode( ',', $duotone_support ); - $selectors_scoped = array_map( - static function ( $selector ) use ( $duotone_id ) { - return '.' . $duotone_id . ' ' . trim( $selector ); - }, - $selectors - ); - $selectors_group = implode( ', ', $selectors_scoped ); - - ob_start(); - - ?> + // !important is needed because these styles render before global styles, + // and they should be overriding the duotone filters set by global styles. + $filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG + ? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n" + : $selector . '{filter:' . $filter_property . ' !important;}'; - - - - - - - values=".299 .587 .114 0 0 - .299 .587 .114 0 0 - .299 .587 .114 0 0 - 0 0 0 1 0" - - /> - - - - - - - - - - .$class_name a{" . $link_color_declaration . " !important;}\n"; + $style = "\n"; // Like the layout hook this assumes the hook only applies to blocks with a single wrapper. // Retrieve the opening tag of the first HTML element. @@ -64,8 +68,19 @@ function wp_render_elements_support( $block_content, $block ) { $content = substr_replace( $block_content, ' class="' . $class_name . '"', $first_element_offset + strlen( $first_element ) - 1, 0 ); } - return $content . $style; + /* + * Ideally styles should be loaded in the head, but blocks may be parsed + * after that, so loading in the footer for now. + * See https://core.trac.wordpress.org/ticket/53494. + */ + add_action( + 'wp_footer', + static function () use ( $style ) { + echo $style; + } + ); + return $content; } add_filter( 'render_block', 'wp_render_elements_support', 10, 2 ); diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index be1da09899da3..88eb2d50f9af5 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -29,6 +29,106 @@ function wp_register_layout_support( $block_type ) { } } +/** + * Generates the CSS corresponding to the provided layout. + * + * @since 5.9.0 + * @access private + * + * @param string $selector CSS selector. + * @param array $layout Layout object. The one that is passed has already checked the existance of default block layout. + * @param boolean $has_block_gap_support Whether the theme has support for the block gap. + * + * @return string CSS style. + */ +function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false ) { + $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default'; + + $style = ''; + if ( 'default' === $layout_type ) { + $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : null; + $wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : null; + + $all_max_width_value = $content_size ? $content_size : $wide_size; + $wide_max_width_value = $wide_size ? $wide_size : $content_size; + + // Make sure there is a single CSS rule, and all tags are stripped for security. + // TODO: Use `safecss_filter_attr` instead - once https://core.trac.wordpress.org/ticket/46197 is patched. + $all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] ); + $wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] ); + + $style = ''; + if ( $content_size || $wide_size ) { + $style = "$selector > * {"; + $style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';'; + $style .= 'margin-left: auto !important;'; + $style .= 'margin-right: auto !important;'; + $style .= '}'; + + $style .= "$selector > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}'; + $style .= "$selector .alignfull { max-width: none; }"; + } + + $style .= "$selector .alignleft { float: left; margin-right: 2em; }"; + $style .= "$selector .alignright { float: right; margin-left: 2em; }"; + if ( $has_block_gap_support ) { + $style .= "$selector > * { margin-top: 0; margin-bottom: 0; }"; + $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }"; + } + } elseif ( 'flex' === $layout_type ) { + $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal'; + + $justify_content_options = array( + 'left' => 'flex-start', + 'right' => 'flex-end', + 'center' => 'center', + ); + + if ( 'horizontal' === $layout_orientation ) { + $justify_content_options += array( 'space-between' => 'space-between' ); + } + + $flex_wrap_options = array( 'wrap', 'nowrap' ); + $flex_wrap = ! empty( $layout['flexWrap'] ) && in_array( $layout['flexWrap'], $flex_wrap_options, true ) ? + $layout['flexWrap'] : + 'wrap'; + + $style = "$selector {"; + $style .= 'display: flex;'; + if ( $has_block_gap_support ) { + $style .= 'gap: var( --wp--style--block-gap, 0.5em );'; + } else { + $style .= 'gap: 0.5em;'; + } + $style .= "flex-wrap: $flex_wrap;"; + $style .= 'align-items: center;'; + if ( 'horizontal' === $layout_orientation ) { + $style .= 'align-items: center;'; + /** + * Add this style only if is not empty for backwards compatibility, + * since we intend to convert blocks that had flex layout implemented + * by custom css. + */ + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { + $style .= "justify-content: {$justify_content_options[ $layout['justifyContent'] ]};"; + // --justification-setting allows children to inherit the value regardless or row or column direction. + $style .= "--justification-setting: {$justify_content_options[ $layout['justifyContent'] ]};"; + } + } else { + $style .= 'flex-direction: column;'; + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { + $style .= "align-items: {$justify_content_options[ $layout['justifyContent'] ]};"; + $style .= "--justification-setting: {$justify_content_options[ $layout['justifyContent'] ]};"; + } + } + $style .= '}'; + + $style .= "$selector > * { margin: 0; }"; + } + + return $style; +} + /** * Renders the layout config to the block wrapper. * @@ -42,47 +142,25 @@ function wp_register_layout_support( $block_type ) { function wp_render_layout_support_flag( $block_content, $block ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); $support_layout = block_has_support( $block_type, array( '__experimentalLayout' ), false ); - if ( ! $support_layout || ! isset( $block['attrs']['layout'] ) ) { + + if ( ! $support_layout ) { return $block_content; } - $used_layout = $block['attrs']['layout']; + $block_gap = wp_get_global_settings( array( 'spacing', 'blockGap' ) ); + $default_layout = wp_get_global_settings( array( 'layout' ) ); + $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false; + $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); + $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) { - $tree = WP_Theme_JSON_Resolver::get_merged_data(); - $default_layout = _wp_array_get( $tree->get_settings(), array( 'layout' ) ); if ( ! $default_layout ) { return $block_content; } $used_layout = $default_layout; } - $id = uniqid(); - $content_size = isset( $used_layout['contentSize'] ) ? $used_layout['contentSize'] : null; - $wide_size = isset( $used_layout['wideSize'] ) ? $used_layout['wideSize'] : null; - - $all_max_width_value = $content_size ? $content_size : $wide_size; - $wide_max_width_value = $wide_size ? $wide_size : $content_size; - - // Make sure there is a single CSS rule, and all tags are stripped for security. - $all_max_width_value = safecss_filter_attr( explode( ';', $all_max_width_value )[0] ); - $wide_max_width_value = safecss_filter_attr( explode( ';', $wide_max_width_value )[0] ); - - $style = ''; - if ( $content_size || $wide_size ) { - $style = ".wp-container-$id > * {"; - $style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';'; - $style .= 'margin-left: auto !important;'; - $style .= 'margin-right: auto !important;'; - $style .= '}'; - - $style .= ".wp-container-$id > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}'; - - $style .= ".wp-container-$id .alignfull { max-width: none; }"; - } - - $style .= ".wp-container-$id .alignleft { float: left; margin-right: 2em; }"; - $style .= ".wp-container-$id .alignright { float: right; margin-left: 2em; }"; - + $id = uniqid(); + $style = wp_get_layout_style( ".wp-container-$id", $used_layout, $has_block_gap_support ); // This assumes the hook only applies to blocks with a single wrapper. // I think this is a reasonable limitation for that particular hook. $content = preg_replace( @@ -92,7 +170,19 @@ function wp_render_layout_support_flag( $block_content, $block ) { 1 ); - return $content . ''; + /* + * Ideally styles should be loaded in the head, but blocks may be parsed + * after that, so loading in the footer for now. + * See https://core.trac.wordpress.org/ticket/53494. + */ + add_action( + 'wp_footer', + static function () use ( $style ) { + echo ''; + } + ); + + return $content; } // Register the block support. @@ -123,7 +213,8 @@ function wp_restore_group_inner_container( $block_content, $block ) { if ( 'core/group' !== $block['blockName'] || WP_Theme_JSON_Resolver::theme_has_support() || - 1 === preg_match( $group_with_inner_container_regex, $block_content ) + 1 === preg_match( $group_with_inner_container_regex, $block_content ) || + ( isset( $block['attrs']['layout']['type'] ) && 'default' !== $block['attrs']['layout']['type'] ) ) { return $block_content; } diff --git a/src/wp-includes/block-supports/spacing.php b/src/wp-includes/block-supports/spacing.php index 69c1b77f20950..8d93d4e1e98c3 100644 --- a/src/wp-includes/block-supports/spacing.php +++ b/src/wp-includes/block-supports/spacing.php @@ -1,6 +1,9 @@ $value ) { $styles[] = sprintf( 'padding-%s: %s;', $key, $value ); } + } elseif ( null !== $padding_value ) { + $styles[] = sprintf( 'padding: %s;', $padding_value ); } } if ( $has_margin_support ) { $margin_value = _wp_array_get( $block_attributes, array( 'style', 'spacing', 'margin' ), null ); - if ( null !== $margin_value ) { + if ( is_array( $margin_value ) ) { foreach ( $margin_value as $key => $value ) { $styles[] = sprintf( 'margin-%s: %s;', $key, $value ); } + } elseif ( null !== $margin_value ) { + $styles[] = sprintf( 'margin: %s;', $margin_value ); } } @@ -68,20 +79,75 @@ function wp_apply_spacing_support( $block_type, $block_attributes ) { } /** - * Checks whether the current block type supports the spacing feature requested. + * Checks whether serialization of the current block's spacing properties should + * occur. * - * @since 5.8.0 + * @since 5.9.0 * @access private * - * @param WP_Block_Type $block_type Block type to check for support. - * @param string $feature Name of the feature to check support for. - * @param mixed $default Fallback value for feature support. Default false. - * @return bool Whether the feature is supported. + * @param WP_Block_Type $block_type Block type. + * + * @return boolean Whether to serialize spacing support styles & classes. */ -function wp_has_spacing_feature_support( $block_type, $feature, $default = false ) { - // Check if the specific feature has been opted into individually - // via nested flag under `spacing`. - return block_has_support( $block_type, array( 'spacing', $feature ), $default ); +function wp_skip_spacing_serialization( $block_type ) { + $spacing_support = _wp_array_get( $block_type->supports, array( 'spacing' ), false ); + + return is_array( $spacing_support ) && + array_key_exists( '__experimentalSkipSerialization', $spacing_support ) && + $spacing_support['__experimentalSkipSerialization']; +} + +/** + * Renders the spacing gap support to the block wrapper, to ensure + * that the CSS variable is rendered in all environments. + * + * @since 5.9.0 + * @access private + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function wp_render_spacing_gap_support( $block_content, $block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $has_gap_support = block_has_support( $block_type, array( 'spacing', 'blockGap' ), false ); + if ( ! $has_gap_support || ! isset( $block['attrs']['style']['spacing']['blockGap'] ) ) { + return $block_content; + } + + $gap_value = $block['attrs']['style']['spacing']['blockGap']; + + // Skip if gap value contains unsupported characters. + // Regex for CSS value borrowed from `safecss_filter_attr`, and used here + // because we only want to match against the value, not the CSS attribute. + if ( preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ) { + return $block_content; + } + + $style = sprintf( + '--wp--style--block-gap: %s', + esc_attr( $gap_value ) + ); + + // Attempt to update an existing style attribute on the wrapper element. + $injected_style = preg_replace( + '/^([^>.]+?)(' . preg_quote( 'style="', '/' ) . ')(?=.+?>)/', + '$1$2' . $style . '; ', + $block_content, + 1 + ); + + // If there is no existing style attribute, add one to the wrapper element. + if ( $injected_style === $block_content ) { + $injected_style = preg_replace( + '/<([a-zA-Z0-9]+)([ >])/', + '<$1 style="' . $style . '"$2', + $block_content, + 1 + ); + }; + + return $injected_style; } // Register the block support. @@ -92,3 +158,5 @@ function wp_has_spacing_feature_support( $block_type, $feature, $default = false 'apply' => 'wp_apply_spacing_support', ) ); + +add_filter( 'render_block', 'wp_render_spacing_gap_support', 10, 2 ); diff --git a/src/wp-includes/block-supports/typography.php b/src/wp-includes/block-supports/typography.php index 2d9f69e773f5d..82ee29af676df 100644 --- a/src/wp-includes/block-supports/typography.php +++ b/src/wp-includes/block-supports/typography.php @@ -28,6 +28,7 @@ function wp_register_typography_support( $block_type ) { $has_font_size_support = _wp_array_get( $typography_supports, array( 'fontSize' ), false ); $has_font_style_support = _wp_array_get( $typography_supports, array( '__experimentalFontStyle' ), false ); $has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false ); + $has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false ); $has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false ); $has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false ); $has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false ); @@ -36,6 +37,7 @@ function wp_register_typography_support( $block_type ) { || $has_font_size_support || $has_font_style_support || $has_font_weight_support + || $has_letter_spacing_support || $has_line_height_support || $has_text_decoration_support || $has_text_transform_support; @@ -93,6 +95,7 @@ function wp_apply_typography_support( $block_type, $block_attributes ) { $has_font_size_support = _wp_array_get( $typography_supports, array( 'fontSize' ), false ); $has_font_style_support = _wp_array_get( $typography_supports, array( '__experimentalFontStyle' ), false ); $has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false ); + $has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false ); $has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false ); $has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false ); $has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false ); @@ -102,24 +105,28 @@ function wp_apply_typography_support( $block_type, $block_attributes ) { $has_custom_font_size = isset( $block_attributes['style']['typography']['fontSize'] ); if ( $has_named_font_size ) { - $classes[] = sprintf( 'has-%s-font-size', $block_attributes['fontSize'] ); + $classes[] = sprintf( 'has-%s-font-size', _wp_to_kebab_case( $block_attributes['fontSize'] ) ); } elseif ( $has_custom_font_size ) { $styles[] = sprintf( 'font-size: %s;', $block_attributes['style']['typography']['fontSize'] ); } } if ( $has_font_family_support ) { - $has_font_family = isset( $block_attributes['style']['typography']['fontFamily'] ); - if ( $has_font_family ) { - $font_family = $block_attributes['style']['typography']['fontFamily']; - if ( strpos( $font_family, 'var:preset|font-family' ) !== false ) { - // Get the name from the string and add proper styles. - $index_to_splice = strrpos( $font_family, '|' ) + 1; - $font_family_name = substr( $font_family, $index_to_splice ); - $styles[] = sprintf( 'font-family: var(--wp--preset--font-family--%s);', $font_family_name ); - } else { - $styles[] = sprintf( 'font-family: %s;', $block_attributes['style']['typography']['fontFamily'] ); + $has_named_font_family = array_key_exists( 'fontFamily', $block_attributes ); + $has_custom_font_family = isset( $block_attributes['style']['typography']['fontFamily'] ); + + if ( $has_named_font_family ) { + $classes[] = sprintf( 'has-%s-font-family', _wp_to_kebab_case( $block_attributes['fontFamily'] ) ); + } elseif ( $has_custom_font_family ) { + // Before using classes, the value was serialized as a CSS Custom Property. + // We don't need this code path when it lands in core. + $font_family_custom = $block_attributes['style']['typography']['fontFamily']; + if ( strpos( $font_family_custom, 'var:preset|font-family' ) !== false ) { + $index_to_splice = strrpos( $font_family_custom, '|' ) + 1; + $font_family_slug = _wp_to_kebab_case( substr( $font_family_custom, $index_to_splice ) ); + $font_family_custom = sprintf( 'var(--wp--preset--font-family--%s)', $font_family_slug ); } + $styles[] = sprintf( 'font-family: %s;', $font_family_custom ); } } @@ -158,6 +165,13 @@ function wp_apply_typography_support( $block_type, $block_attributes ) { } } + if ( $has_letter_spacing_support ) { + $letter_spacing_style = wp_typography_get_css_variable_inline_style( $block_attributes, 'letterSpacing', 'letter-spacing' ); + if ( $letter_spacing_style ) { + $styles[] = $letter_spacing_style; + } + } + if ( ! empty( $classes ) ) { $attributes['class'] = implode( ' ', $classes ); } diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index f94a60d921502..f5b8b47e5ef20 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1182,3 +1182,117 @@ function get_query_pagination_arrow( $block, $is_next ) { } return null; } + +/** + * Enqueue a stylesheet for a specific block. + * + * If the theme has opted-in to separate-styles loading, + * then the stylesheet will be enqueued on-render, + * otherwise when the block inits. + * + * @param string $block_name The block-name, including namespace. + * @param array $args An array of arguments [handle,src,deps,ver,media]. + * + * @return void + */ +function wp_enqueue_block_style( $block_name, $args ) { + $args = wp_parse_args( + $args, + array( + 'handle' => '', + 'src' => '', + 'deps' => array(), + 'ver' => false, + 'media' => 'all', + ) + ); + + /** + * Callback function to register and enqueue styles. + * + * @param string $content When the callback is used for the render_block filter, + * the content needs to be returned so the function parameter + * is to ensure the content exists. + * + * @return string + */ + $callback = static function( $content ) use ( $args ) { + // Register the stylesheet. + if ( ! empty( $args['src'] ) ) { + wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] ); + } + + // Add `path` data if provided. + if ( isset( $args['path'] ) ) { + wp_style_add_data( $args['handle'], 'path', $args['path'] ); + + // Get the RTL file path. + $rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] ); + + // Add RTL stylesheet. + if ( file_exists( $rtl_file_path ) ) { + wp_style_add_data( $args['hanle'], 'rtl', 'replace' ); + + if ( is_rtl() ) { + wp_style_add_data( $args['handle'], 'path', $rtl_file_path ); + } + } + } + + // Enqueue the stylesheet. + wp_enqueue_style( $args['handle'] ); + + return $content; + }; + + $hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts'; + if ( wp_should_load_separate_core_block_assets() ) { + $hook = "render_block_$block_name"; + } + + /* + * The filter's callback here is an anonymous function because + * using a named function in this case is not possible. + * + * The function cannot be unhooked, however, users are still able + * to dequeue the stylesheets registered/enqueued by the callback + * which is why in this case, using an anonymous function + * was deemed acceptable. + */ + add_filter( $hook, $callback ); + + // Enqueue assets in the editor. + add_action( 'enqueue_block_assets', $callback ); +} + +/** + * Allow multiple block styles. + * + * @param array $metadata Metadata for registering a block type. + * + * @return array + */ +function _wp_multiple_block_styles( $metadata ) { + foreach ( array( 'style', 'editorStyle' ) as $key ) { + if ( ! empty( $metadata[ $key ] ) && is_array( $metadata[ $key ] ) ) { + $default_style = array_shift( $metadata[ $key ] ); + foreach ( $metadata[ $key ] as $handle ) { + $args = array( 'handle' => $handle ); + if ( 0 === strpos( $handle, 'file:' ) && isset( $metadata['file'] ) ) { + $style_path = remove_block_asset_path_prefix( $handle ); + $args = array( + 'handle' => sanitize_key( "{$metadata['name']}-{$style_path}" ), + 'src' => plugins_url( $style_path, $metadata['file'] ), + ); + } + + wp_enqueue_block_style( $metadata['name'], $args ); + } + + // Only return the 1st item in the array. + $metadata[ $key ] = $default_style; + } + } + return $metadata; +} +add_filter( 'block_type_metadata', '_wp_multiple_block_styles' ); diff --git a/src/wp-includes/blocks/index.php b/src/wp-includes/blocks/index.php index d3aa3f6d7dde4..3ff63f5738dbe 100644 --- a/src/wp-includes/blocks/index.php +++ b/src/wp-includes/blocks/index.php @@ -15,29 +15,38 @@ require ABSPATH . WPINC . '/blocks/latest-posts.php'; require ABSPATH . WPINC . '/blocks/legacy-widget.php'; require ABSPATH . WPINC . '/blocks/loginout.php'; +require ABSPATH . WPINC . '/blocks/navigation-area.php'; +require ABSPATH . WPINC . '/blocks/navigation-link.php'; +require ABSPATH . WPINC . '/blocks/navigation-submenu.php'; +require ABSPATH . WPINC . '/blocks/navigation.php'; require ABSPATH . WPINC . '/blocks/page-list.php'; +require ABSPATH . WPINC . '/blocks/pattern.php'; +require ABSPATH . WPINC . '/blocks/post-author.php'; +require ABSPATH . WPINC . '/blocks/post-comments.php'; require ABSPATH . WPINC . '/blocks/post-content.php'; require ABSPATH . WPINC . '/blocks/post-date.php'; require ABSPATH . WPINC . '/blocks/post-excerpt.php'; require ABSPATH . WPINC . '/blocks/post-featured-image.php'; +require ABSPATH . WPINC . '/blocks/post-navigation-link.php'; +require ABSPATH . WPINC . '/blocks/post-template.php'; require ABSPATH . WPINC . '/blocks/post-terms.php'; require ABSPATH . WPINC . '/blocks/post-title.php'; -require ABSPATH . WPINC . '/blocks/post-template.php'; -require ABSPATH . WPINC . '/blocks/query.php'; -require ABSPATH . WPINC . '/blocks/query-pagination.php'; require ABSPATH . WPINC . '/blocks/query-pagination-next.php'; require ABSPATH . WPINC . '/blocks/query-pagination-numbers.php'; require ABSPATH . WPINC . '/blocks/query-pagination-previous.php'; +require ABSPATH . WPINC . '/blocks/query-pagination.php'; require ABSPATH . WPINC . '/blocks/query-title.php'; +require ABSPATH . WPINC . '/blocks/query.php'; require ABSPATH . WPINC . '/blocks/rss.php'; require ABSPATH . WPINC . '/blocks/search.php'; require ABSPATH . WPINC . '/blocks/shortcode.php'; -require ABSPATH . WPINC . '/blocks/site-tagline.php'; require ABSPATH . WPINC . '/blocks/site-logo.php'; +require ABSPATH . WPINC . '/blocks/site-tagline.php'; require ABSPATH . WPINC . '/blocks/site-title.php'; require ABSPATH . WPINC . '/blocks/social-link.php'; require ABSPATH . WPINC . '/blocks/tag-cloud.php'; require ABSPATH . WPINC . '/blocks/template-part.php'; +require ABSPATH . WPINC . '/blocks/term-description.php'; /** * Registers core block types using metadata files. diff --git a/src/wp-includes/blocks/navigation-area.php b/src/wp-includes/blocks/navigation-area.php new file mode 100644 index 0000000000000..76a2748361c47 --- /dev/null +++ b/src/wp-includes/blocks/navigation-area.php @@ -0,0 +1,21 @@ + array( + 'navigationArea' => 'area', + ), + ) + ); +} +add_action( 'init', 'register_block_core_navigation_area' ); diff --git a/src/wp-includes/blocks/navigation-area/block.json b/src/wp-includes/blocks/navigation-area/block.json new file mode 100644 index 0000000000000..95ad0a513e65b --- /dev/null +++ b/src/wp-includes/blocks/navigation-area/block.json @@ -0,0 +1,27 @@ +{ + "apiVersion": 2, + "name": "core/navigation-area", + "title": "Navigation Area", + "category": "theme", + "description": "Define a navigation area for your theme. The navigation block associated with this area will be automatically displayed.", + "keywords": [ + "menu", + "navigation", + "links", + "location" + ], + "textdomain": "default", + "attributes": { + "area": { + "type": "string", + "default": "primary" + } + }, + "providesContext": { + "navigationArea": "area" + }, + "supports": { + "html": false, + "inserter": true + } +} diff --git a/src/wp-includes/blocks/navigation-link.php b/src/wp-includes/blocks/navigation-link.php new file mode 100644 index 0000000000000..2f7620ad34f98 --- /dev/null +++ b/src/wp-includes/blocks/navigation-link.php @@ -0,0 +1,350 @@ + array(), + 'inline_styles' => '', + ); + + $is_sub_menu = isset( $attributes['isTopLevelLink'] ) ? ( ! $attributes['isTopLevelLink'] ) : false; + + // Text color. + $named_text_color = null; + $custom_text_color = null; + + if ( $is_sub_menu && array_key_exists( 'customOverlayTextColor', $context ) ) { + $custom_text_color = $context['customOverlayTextColor']; + } elseif ( $is_sub_menu && array_key_exists( 'overlayTextColor', $context ) ) { + $named_text_color = $context['overlayTextColor']; + } elseif ( array_key_exists( 'customTextColor', $context ) ) { + $custom_text_color = $context['customTextColor']; + } elseif ( array_key_exists( 'textColor', $context ) ) { + $named_text_color = $context['textColor']; + } elseif ( isset( $context['style']['color']['text'] ) ) { + $custom_text_color = $context['style']['color']['text']; + } + + // If has text color. + if ( ! is_null( $named_text_color ) ) { + // Add the color class. + array_push( $colors['css_classes'], 'has-text-color', sprintf( 'has-%s-color', $named_text_color ) ); + } elseif ( ! is_null( $custom_text_color ) ) { + // Add the custom color inline style. + $colors['css_classes'][] = 'has-text-color'; + $colors['inline_styles'] .= sprintf( 'color: %s;', $custom_text_color ); + } + + // Background color. + $named_background_color = null; + $custom_background_color = null; + + if ( $is_sub_menu && array_key_exists( 'customOverlayBackgroundColor', $context ) ) { + $custom_background_color = $context['customOverlayBackgroundColor']; + } elseif ( $is_sub_menu && array_key_exists( 'overlayBackgroundColor', $context ) ) { + $named_background_color = $context['overlayBackgroundColor']; + } elseif ( array_key_exists( 'customBackgroundColor', $context ) ) { + $custom_background_color = $context['customBackgroundColor']; + } elseif ( array_key_exists( 'backgroundColor', $context ) ) { + $named_background_color = $context['backgroundColor']; + } elseif ( isset( $context['style']['color']['background'] ) ) { + $custom_background_color = $context['style']['color']['background']; + } + + // If has background color. + if ( ! is_null( $named_background_color ) ) { + // Add the background-color class. + array_push( $colors['css_classes'], 'has-background', sprintf( 'has-%s-background-color', $named_background_color ) ); + } elseif ( ! is_null( $custom_background_color ) ) { + // Add the custom background-color inline style. + $colors['css_classes'][] = 'has-background'; + $colors['inline_styles'] .= sprintf( 'background-color: %s;', $custom_background_color ); + } + + return $colors; +} + +/** + * Build an array with CSS classes and inline styles defining the font sizes + * which will be applied to the navigation markup in the front-end. + * + * @param array $context Navigation block context. + * @return array Font size CSS classes and inline styles. + */ +function block_core_navigation_link_build_css_font_sizes( $context ) { + // CSS classes. + $font_sizes = array( + 'css_classes' => array(), + 'inline_styles' => '', + ); + + $has_named_font_size = array_key_exists( 'fontSize', $context ); + $has_custom_font_size = isset( $context['style']['typography']['fontSize'] ); + + if ( $has_named_font_size ) { + // Add the font size class. + $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); + } elseif ( $has_custom_font_size ) { + // Add the custom font size inline style. + $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $context['style']['typography']['fontSize'] ); + } + + return $font_sizes; +} + +/** + * Returns the top-level submenu SVG chevron icon. + * + * @return string + */ +function block_core_navigation_link_render_submenu_icon() { + return ''; +} + +/** + * Renders the `core/navigation-link` block. + * + * @param array $attributes The block attributes. + * @param array $content The saved content. + * @param array $block The parsed block. + * + * @return string Returns the post content with the legacy widget added. + */ +function render_block_core_navigation_link( $attributes, $content, $block ) { + $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] ); + $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind']; + $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] ); + + // Don't render the block's subtree if it is a draft or if the ID does not exist. + if ( $is_post_type && $navigation_link_has_id ) { + $post = get_post( $attributes['id'] ); + if ( ! $post || 'publish' !== $post->post_status ) { + return ''; + } + } + + // Don't render the block's subtree if it has no label. + if ( empty( $attributes['label'] ) ) { + return ''; + } + + $colors = block_core_navigation_link_build_css_colors( $block->context, $attributes ); + $font_sizes = block_core_navigation_link_build_css_font_sizes( $block->context ); + $classes = array_merge( + $colors['css_classes'], + $font_sizes['css_classes'] + ); + $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] ); + + $css_classes = trim( implode( ' ', $classes ) ); + $has_submenu = count( $block->inner_blocks ) > 0; + $is_active = ! empty( $attributes['id'] ) && ( get_the_ID() === $attributes['id'] ); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $css_classes . ' wp-block-navigation-item' . ( $has_submenu ? ' has-child' : '' ) . + ( $is_active ? ' current-menu-item' : '' ), + 'style' => $style_attribute, + ) + ); + $html = '
  • ' . + ''; + + if ( isset( $attributes['label'] ) ) { + $html .= wp_kses( + $attributes['label'], + array( + 'code' => array(), + 'em' => array(), + 'img' => array( + 'scale' => array(), + 'class' => array(), + 'style' => array(), + 'src' => array(), + 'alt' => array(), + ), + 's' => array(), + 'span' => array( + 'style' => array(), + ), + 'strong' => array(), + ) + ); + } + + $html .= ''; + + if ( isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon'] && $has_submenu ) { + // The submenu icon can be hidden by a CSS rule on the Navigation Block. + $html .= '' . block_core_navigation_link_render_submenu_icon() . ''; + } + + $html .= ''; + // End anchor tag content. + + if ( $has_submenu ) { + $inner_blocks_html = ''; + foreach ( $block->inner_blocks as $inner_block ) { + $inner_blocks_html .= $inner_block->render(); + } + + $html .= sprintf( + '
      %s
    ', + $inner_blocks_html + ); + } + + $html .= '
  • '; + + return $html; +} + +/** + * Returns a navigation link variation + * + * @param WP_Taxonomy|WP_Post_Type $entity post type or taxonomy entity. + * @param string $kind string of value 'taxonomy' or 'post-type'. + * + * @return array + */ +function build_variation_for_navigation_link( $entity, $kind ) { + $title = ''; + $description = ''; + + if ( property_exists( $entity->labels, 'item_link' ) ) { + $title = $entity->labels->item_link; + } + if ( property_exists( $entity->labels, 'item_link_description' ) ) { + $description = $entity->labels->item_link_description; + } + + $variation = array( + 'name' => $entity->name, + 'title' => $title, + 'description' => $description, + 'attributes' => array( + 'type' => $entity->name, + 'kind' => $kind, + ), + ); + + // Tweak some value for the variations. + $variation_overrides = array( + 'post_tag' => array( + 'name' => 'tag', + 'attributes' => array( + 'type' => 'tag', + 'kind' => $kind, + ), + ), + 'post_format' => array( + // The item_link and item_link_description for post formats is the + // same as for tags, so need to be overridden. + 'title' => __( 'Post Format Link' ), + 'description' => __( 'A link to a post format' ), + 'attributes' => array( + 'type' => 'post_format', + 'kind' => $kind, + ), + ), + ); + + if ( array_key_exists( $entity->name, $variation_overrides ) ) { + $variation = array_merge( + $variation, + $variation_overrides[ $entity->name ] + ); + } + + return $variation; +} + +/** + * Register the navigation link block. + * + * @uses render_block_core_navigation() + * @throws WP_Error An WP_Error exception parsing the block definition. + */ +function register_block_core_navigation_link() { + $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' ); + $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' ); + + // Use two separate arrays as a way to order the variations in the UI. + // Known variations (like Post Link and Page Link) are added to the + // `built_ins` array. Variations for custom post types and taxonomies are + // added to the `variations` array and will always appear after `built-ins. + $built_ins = array(); + $variations = array(); + + if ( $post_types ) { + foreach ( $post_types as $post_type ) { + $variation = build_variation_for_navigation_link( $post_type, 'post-type' ); + if ( $post_type->_builtin ) { + $built_ins[] = $variation; + } else { + $variations[] = $variation; + } + } + } + if ( $taxonomies ) { + foreach ( $taxonomies as $taxonomy ) { + $variation = build_variation_for_navigation_link( $taxonomy, 'taxonomy' ); + if ( $taxonomy->_builtin ) { + $built_ins[] = $variation; + } else { + $variations[] = $variation; + } + } + } + + register_block_type_from_metadata( + __DIR__ . '/navigation-link', + array( + 'render_callback' => 'render_block_core_navigation_link', + 'variations' => array_merge( $built_ins, $variations ), + ) + ); +} +add_action( 'init', 'register_block_core_navigation_link' ); diff --git a/src/wp-includes/blocks/navigation-link/block.json b/src/wp-includes/blocks/navigation-link/block.json new file mode 100644 index 0000000000000..eac8af0cdec69 --- /dev/null +++ b/src/wp-includes/blocks/navigation-link/block.json @@ -0,0 +1,65 @@ +{ + "apiVersion": 2, + "name": "core/navigation-link", + "title": "Custom Link", + "category": "design", + "parent": [ + "core/navigation" + ], + "description": "Add a page, link, or another item to your navigation.", + "textdomain": "default", + "attributes": { + "label": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "id": { + "type": "number" + }, + "opensInNewTab": { + "type": "boolean", + "default": false + }, + "url": { + "type": "string" + }, + "title": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "isTopLevelLink": { + "type": "boolean" + } + }, + "usesContext": [ + "textColor", + "customTextColor", + "backgroundColor", + "customBackgroundColor", + "overlayTextColor", + "customOverlayTextColor", + "overlayBackgroundColor", + "customOverlayBackgroundColor", + "fontSize", + "customFontSize", + "showSubmenuIcon", + "style" + ], + "supports": { + "reusable": false, + "html": false, + "__experimentalSlashInserter": true + }, + "editorStyle": "wp-block-navigation-link-editor", + "style": "wp-block-navigation-link" +} diff --git a/src/wp-includes/blocks/navigation-submenu.php b/src/wp-includes/blocks/navigation-submenu.php new file mode 100644 index 0000000000000..cdb26b93b1b50 --- /dev/null +++ b/src/wp-includes/blocks/navigation-submenu.php @@ -0,0 +1,300 @@ + array(), + 'inline_styles' => '', + ); + + $is_sub_menu = isset( $attributes['isTopLevelLink'] ) ? ( ! $attributes['isTopLevelLink'] ) : false; + + // Text color. + $named_text_color = null; + $custom_text_color = null; + + if ( $is_sub_menu && array_key_exists( 'customOverlayTextColor', $context ) ) { + $custom_text_color = $context['customOverlayTextColor']; + } elseif ( $is_sub_menu && array_key_exists( 'overlayTextColor', $context ) ) { + $named_text_color = $context['overlayTextColor']; + } elseif ( array_key_exists( 'customTextColor', $context ) ) { + $custom_text_color = $context['customTextColor']; + } elseif ( array_key_exists( 'textColor', $context ) ) { + $named_text_color = $context['textColor']; + } elseif ( isset( $context['style']['color']['text'] ) ) { + $custom_text_color = $context['style']['color']['text']; + } + + // If has text color. + if ( ! is_null( $named_text_color ) ) { + // Add the color class. + array_push( $colors['css_classes'], 'has-text-color', sprintf( 'has-%s-color', $named_text_color ) ); + } elseif ( ! is_null( $custom_text_color ) ) { + // Add the custom color inline style. + $colors['css_classes'][] = 'has-text-color'; + $colors['inline_styles'] .= sprintf( 'color: %s;', $custom_text_color ); + } + + // Background color. + $named_background_color = null; + $custom_background_color = null; + + if ( $is_sub_menu && array_key_exists( 'customOverlayBackgroundColor', $context ) ) { + $custom_background_color = $context['customOverlayBackgroundColor']; + } elseif ( $is_sub_menu && array_key_exists( 'overlayBackgroundColor', $context ) ) { + $named_background_color = $context['overlayBackgroundColor']; + } elseif ( array_key_exists( 'customBackgroundColor', $context ) ) { + $custom_background_color = $context['customBackgroundColor']; + } elseif ( array_key_exists( 'backgroundColor', $context ) ) { + $named_background_color = $context['backgroundColor']; + } elseif ( isset( $context['style']['color']['background'] ) ) { + $custom_background_color = $context['style']['color']['background']; + } + + // If has background color. + if ( ! is_null( $named_background_color ) ) { + // Add the background-color class. + array_push( $colors['css_classes'], 'has-background', sprintf( 'has-%s-background-color', $named_background_color ) ); + } elseif ( ! is_null( $custom_background_color ) ) { + // Add the custom background-color inline style. + $colors['css_classes'][] = 'has-background'; + $colors['inline_styles'] .= sprintf( 'background-color: %s;', $custom_background_color ); + } + + return $colors; +} + +/** + * Build an array with CSS classes and inline styles defining the font sizes + * which will be applied to the navigation markup in the front-end. + * + * @param array $context Navigation block context. + * @return array Font size CSS classes and inline styles. + */ +function block_core_navigation_submenu_build_css_font_sizes( $context ) { + // CSS classes. + $font_sizes = array( + 'css_classes' => array(), + 'inline_styles' => '', + ); + + $has_named_font_size = array_key_exists( 'fontSize', $context ); + $has_custom_font_size = isset( $context['style']['typography']['fontSize'] ); + + if ( $has_named_font_size ) { + // Add the font size class. + $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); + } elseif ( $has_custom_font_size ) { + // Add the custom font size inline style. + $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $context['style']['typography']['fontSize'] ); + } + + return $font_sizes; +} + +/** + * Returns the top-level submenu SVG chevron icon. + * + * @return string + */ +function block_core_navigation_submenu_render_submenu_icon() { + return ''; +} + +/** + * Renders the `core/navigation-submenu` block. + * + * @param array $attributes The block attributes. + * @param string $content The saved content. + * @param object $block The parsed block. + * + * @return string Returns the post content with the legacy widget added. + */ +function render_block_core_navigation_submenu( $attributes, $content, $block ) { + + $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] ); + $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind']; + $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] ); + + // Don't render the block's subtree if it is a draft. + if ( $is_post_type && $navigation_link_has_id && 'publish' !== get_post_status( $attributes['id'] ) ) { + return ''; + } + + // Don't render the block's subtree if it has no label. + if ( empty( $attributes['label'] ) ) { + return ''; + } + + $colors = block_core_navigation_submenu_build_css_colors( $block->context, $attributes ); + $font_sizes = block_core_navigation_submenu_build_css_font_sizes( $block->context ); + $classes = array_merge( + $colors['css_classes'], + $font_sizes['css_classes'] + ); + $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] ); + + $css_classes = trim( implode( ' ', $classes ) ); + $has_submenu = count( $block->inner_blocks ) > 0; + $is_active = ! empty( $attributes['id'] ) && ( get_the_ID() === $attributes['id'] ); + + $class_name = ! empty( $attributes['className'] ) ? implode( ' ', (array) $attributes['className'] ) : false; + + if ( false !== $class_name ) { + $css_classes .= ' ' . $class_name; + } + + $show_submenu_indicators = isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon']; + $open_on_click = isset( $block->context['openSubmenusOnClick'] ) && $block->context['openSubmenusOnClick']; + $open_on_hover_and_click = isset( $block->context['openSubmenusOnClick'] ) && ! $block->context['openSubmenusOnClick'] && + $show_submenu_indicators; + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $css_classes . ' wp-block-navigation-item' . ( $has_submenu ? ' has-child' : '' ) . + ( $open_on_click ? ' open-on-click' : '' ) . ( $open_on_hover_and_click ? ' open-on-hover-click' : '' ) . + ( $is_active ? ' current-menu-item' : '' ), + 'style' => $style_attribute, + ) + ); + $html = '
  • '; + + // If Submenus open on hover, we render an anchor tag with attributes. + // If submenu icons are set to show, we also render a submenu button, so the submenu can be opened on click. + if ( ! $open_on_click ) { + $item_url = isset( $attributes['url'] ) ? esc_url( $attributes['url'] ) : ''; + // Start appending HTML attributes to anchor tag. + $html .= ' array(), + 'em' => array(), + 'img' => array( + 'scale' => array(), + 'class' => array(), + 'style' => array(), + 'src' => array(), + 'alt' => array(), + ), + 's' => array(), + 'span' => array( + 'style' => array(), + ), + 'strong' => array(), + ) + ); + } + + $html .= ''; + // End anchor tag content. + + if ( $show_submenu_indicators ) { + // The submenu icon is rendered in a button here + // so that there's a clickable elment to open the submenu. + $html .= ''; + } + } else { + // If menus open on click, we render the parent as a button. + $html .= ''; + + } + + if ( $has_submenu ) { + $inner_blocks_html = ''; + foreach ( $block->inner_blocks as $inner_block ) { + $inner_blocks_html .= $inner_block->render(); + } + + $html .= sprintf( + '
      %s
    ', + $inner_blocks_html + ); + } + + $html .= '
  • '; + + return $html; +} + +/** + * Register the navigation submenu block. + * + * @uses render_block_core_navigation_submenu() + * @throws WP_Error An WP_Error exception parsing the block definition. + */ +function register_block_core_navigation_submenu() { + register_block_type_from_metadata( + __DIR__ . '/navigation-submenu', + array( + 'render_callback' => 'render_block_core_navigation_submenu', + ) + ); +} +add_action( 'init', 'register_block_core_navigation_submenu' ); diff --git a/src/wp-includes/blocks/navigation-submenu/block.json b/src/wp-includes/blocks/navigation-submenu/block.json new file mode 100644 index 0000000000000..db0791485a8cb --- /dev/null +++ b/src/wp-includes/blocks/navigation-submenu/block.json @@ -0,0 +1,65 @@ +{ + "apiVersion": 2, + "name": "core/navigation-submenu", + "title": "Submenu", + "category": "design", + "parent": [ + "core/navigation" + ], + "description": "Add a submenu to your navigation.", + "textdomain": "default", + "attributes": { + "label": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "id": { + "type": "number" + }, + "opensInNewTab": { + "type": "boolean", + "default": false + }, + "url": { + "type": "string" + }, + "title": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "isTopLevelItem": { + "type": "boolean" + } + }, + "usesContext": [ + "textColor", + "customTextColor", + "backgroundColor", + "customBackgroundColor", + "overlayTextColor", + "customOverlayTextColor", + "overlayBackgroundColor", + "customOverlayBackgroundColor", + "fontSize", + "customFontSize", + "showSubmenuIcon", + "openSubmenusOnClick", + "style" + ], + "supports": { + "reusable": false, + "html": false + }, + "editorStyle": "wp-block-navigation-submenu-editor", + "style": "wp-block-navigation-submenu" +} diff --git a/src/wp-includes/blocks/navigation.php b/src/wp-includes/blocks/navigation.php new file mode 100644 index 0000000000000..6f52c41c71d7d --- /dev/null +++ b/src/wp-includes/blocks/navigation.php @@ -0,0 +1,429 @@ + array(), + 'inline_styles' => '', + ); + + // Text color. + $has_named_text_color = array_key_exists( 'textColor', $attributes ); + $has_custom_text_color = array_key_exists( 'customTextColor', $attributes ); + + // If has text color. + if ( $has_custom_text_color || $has_named_text_color ) { + // Add has-text-color class. + $colors['css_classes'][] = 'has-text-color'; + } + + if ( $has_named_text_color ) { + // Add the color class. + $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] ); + } elseif ( $has_custom_text_color ) { + // Add the custom color inline style. + $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] ); + } + + // Background color. + $has_named_background_color = array_key_exists( 'backgroundColor', $attributes ); + $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes ); + + // If has background color. + if ( $has_custom_background_color || $has_named_background_color ) { + // Add has-background class. + $colors['css_classes'][] = 'has-background'; + } + + if ( $has_named_background_color ) { + // Add the background-color class. + $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] ); + } elseif ( $has_custom_background_color ) { + // Add the custom background-color inline style. + $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] ); + } + + return $colors; +} + +/** + * Build an array with CSS classes and inline styles defining the font sizes + * which will be applied to the navigation markup in the front-end. + * + * @param array $attributes Navigation block attributes. + * @return array Font size CSS classes and inline styles. + */ +function block_core_navigation_build_css_font_sizes( $attributes ) { + // CSS classes. + $font_sizes = array( + 'css_classes' => array(), + 'inline_styles' => '', + ); + + $has_named_font_size = array_key_exists( 'fontSize', $attributes ); + $has_custom_font_size = array_key_exists( 'customFontSize', $attributes ); + + if ( $has_named_font_size ) { + // Add the font size class. + $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $attributes['fontSize'] ); + } elseif ( $has_custom_font_size ) { + // Add the custom font size inline style. + $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $attributes['customFontSize'] ); + } + + return $font_sizes; +} + +/** + * Returns the menu items for a WordPress menu location. + * + * @param string $location The menu location. + * @return array Menu items for the location. + */ +function gutenberg_get_menu_items_at_location( $location ) { + if ( empty( $location ) ) { + return; + } + + // Build menu data. The following approximates the code in + // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`. + + // Find the location in the list of locations, returning early if the + // location can't be found. + $locations = get_nav_menu_locations(); + if ( ! isset( $locations[ $location ] ) ) { + return; + } + + // Get the menu from the location, returning early if there is no + // menu or there was an error. + $menu = wp_get_nav_menu_object( $locations[ $location ] ); + if ( ! $menu || is_wp_error( $menu ) ) { + return; + } + + $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) ); + _wp_menu_item_classes_by_context( $menu_items ); + + return $menu_items; +} + +/** + * Sorts a standard array of menu items into a nested structure keyed by the + * id of the parent menu. + * + * @param array $menu_items Menu items to sort. + * @return array An array keyed by the id of the parent menu where each element + * is an array of menu items that belong to that parent. + */ +function gutenberg_sort_menu_items_by_parent_id( $menu_items ) { + $sorted_menu_items = array(); + foreach ( (array) $menu_items as $menu_item ) { + $sorted_menu_items[ $menu_item->menu_order ] = $menu_item; + } + unset( $menu_items, $menu_item ); + + $menu_items_by_parent_id = array(); + foreach ( $sorted_menu_items as $menu_item ) { + $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item; + } + + return $menu_items_by_parent_id; +} + +/** + * Turns menu item data into a nested array of parsed blocks + * + * @param array $menu_items An array of menu items that represent + * an individual level of a menu. + * @param array $menu_items_by_parent_id An array keyed by the id of the + * parent menu where each element is an + * array of menu items that belong to + * that parent. + * @return array An array of parsed block data. + */ +function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) { + if ( empty( $menu_items ) ) { + return array(); + } + + $blocks = array(); + + foreach ( $menu_items as $menu_item ) { + $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null; + $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null; + $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target; + $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null; + $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; + + $block = array( + 'blockName' => 'core/navigation-link', + 'attrs' => array( + 'className' => $class_name, + 'description' => $menu_item->description, + 'id' => $id, + 'kind' => $kind, + 'label' => $menu_item->title, + 'opensInNewTab' => $opens_in_new_tab, + 'rel' => $rel, + 'title' => $menu_item->attr_title, + 'type' => $menu_item->object, + 'url' => $menu_item->url, + ), + ); + + $block['innerBlocks'] = gutenberg_parse_blocks_from_menu_items( + isset( $menu_items_by_parent_id[ $menu_item->ID ] ) + ? $menu_items_by_parent_id[ $menu_item->ID ] + : array(), + $menu_items_by_parent_id + ); + + $blocks[] = $block; + } + + return $blocks; +} + +/** + * Returns the top-level submenu SVG chevron icon. + * + * @return string + */ +function block_core_navigation_render_submenu_icon() { + return ''; +} + +/** + * Renders the `core/navigation` block on server. + * + * @param array $attributes The block attributes. + * @param array $content The saved content. + * @param array $block The parsed block. + * + * @return string Returns the post content with the legacy widget added. + */ +function render_block_core_navigation( $attributes, $content, $block ) { + /** + * Deprecated: + * The rgbTextColor and rgbBackgroundColor attributes + * have been deprecated in favor of + * customTextColor and customBackgroundColor ones. + * Move the values from old attrs to the new ones. + */ + if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) { + $attributes['customTextColor'] = $attributes['rgbTextColor']; + } + + if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) { + $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor']; + } + + unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] ); + + /** + * This is for backwards compatibility after `isResponsive` attribute has been removed. + */ + $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive']; + $is_responsive_menu = isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute; + $should_load_view_script = ! wp_script_is( 'wp-block-navigation-view' ) && ( $is_responsive_menu || $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ); + if ( $should_load_view_script ) { + wp_enqueue_script( 'wp-block-navigation-view' ); + } + + $inner_blocks = $block->inner_blocks; + + // If `__unstableLocation` is defined, create inner blocks from the classic menu assigned to that location. + if ( empty( $inner_blocks ) && array_key_exists( '__unstableLocation', $attributes ) ) { + $menu_items = gutenberg_get_menu_items_at_location( $attributes['__unstableLocation'] ); + if ( empty( $menu_items ) ) { + return ''; + } + + $menu_items_by_parent_id = gutenberg_sort_menu_items_by_parent_id( $menu_items ); + $parsed_blocks = gutenberg_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id ); + $inner_blocks = new WP_Block_List( $parsed_blocks, $attributes ); + } + + if ( ! empty( $block->context['navigationArea'] ) ) { + $area = $block->context['navigationArea']; + $mapping = get_option( 'fse_navigation_areas', array() ); + if ( ! empty( $mapping[ $area ] ) ) { + $attributes['navigationMenuId'] = $mapping[ $area ]; + } + } + + // Load inner blocks from the navigation post. + if ( array_key_exists( 'navigationMenuId', $attributes ) ) { + $navigation_post = get_post( $attributes['navigationMenuId'] ); + if ( ! isset( $navigation_post ) ) { + return ''; + } + + $parsed_blocks = parse_blocks( $navigation_post->post_content ); + + // 'parse_blocks' includes a null block with '\n\n' as the content when + // it encounters whitespace. This code strips it. + $compacted_blocks = array_filter( + $parsed_blocks, + function( $block ) { + return isset( $block['blockName'] ); + } + ); + + // TODO - this uses the full navigation block attributes for the + // context which could be refined. + $inner_blocks = new WP_Block_List( $compacted_blocks, $attributes ); + } + + if ( empty( $inner_blocks ) ) { + return ''; + } + $colors = block_core_navigation_build_css_colors( $attributes ); + $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); + $classes = array_merge( + $colors['css_classes'], + $font_sizes['css_classes'], + $is_responsive_menu ? array( 'is-responsive' ) : array() + ); + + $inner_blocks_html = ''; + $is_list_open = false; + foreach ( $inner_blocks as $inner_block ) { + if ( ( 'core/navigation-link' === $inner_block->name || 'core/home-link' === $inner_block->name || 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name || 'core/navigation-submenu' === $inner_block->name ) && ! $is_list_open ) { + $is_list_open = true; + $inner_blocks_html .= '
      '; + } + if ( 'core/navigation-link' !== $inner_block->name && 'core/home-link' !== $inner_block->name && 'core/site-title' !== $inner_block->name && 'core/site-logo' !== $inner_block->name && 'core/navigation-submenu' !== $inner_block->name && $is_list_open ) { + $is_list_open = false; + $inner_blocks_html .= '
    '; + } + if ( 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name ) { + $inner_blocks_html .= '
  • ' . $inner_block->render() . '
  • '; + } else { + $inner_blocks_html .= $inner_block->render(); + } + } + + if ( $is_list_open ) { + $inner_blocks_html .= ''; + } + + $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => implode( ' ', $classes ), + 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'], + ) + ); + + $modal_unique_id = uniqid(); + + // Determine whether or not navigation elements should be wrapped in the markup required to make it responsive, + // return early if they don't. + if ( ! $is_responsive_menu ) { + return sprintf( + '', + $wrapper_attributes, + $inner_blocks_html + ); + } + + $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; + + $responsive_container_classes = array( + 'wp-block-navigation__responsive-container', + $is_hidden_by_default ? 'hidden-by-default' : '', + ); + $open_button_classes = array( + 'wp-block-navigation__responsive-container-open', + $is_hidden_by_default ? 'always-shown' : '', + ); + + $responsive_container_markup = sprintf( + ' + ', + $modal_unique_id, + $inner_blocks_html, + __( 'Open menu' ), // Open button label. + __( 'Close menu' ), // Close button label. + implode( ' ', $responsive_container_classes ), + implode( ' ', $open_button_classes ) + ); + + return sprintf( + '', + $wrapper_attributes, + $responsive_container_markup + ); +} + +/** + * Register the navigation block. + * + * @uses render_block_core_navigation() + * @throws WP_Error An WP_Error exception parsing the block definition. + */ +function register_block_core_navigation() { + register_block_type_from_metadata( + __DIR__ . '/navigation', + array( + 'render_callback' => 'render_block_core_navigation', + ) + ); +} + +add_action( 'init', 'register_block_core_navigation' ); + +/** + * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly. + * + * @param array $parsed_block The block being rendered. + * @return array The block being rendered without typographic presets. + */ +function block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) { + if ( 'core/navigation' === $parsed_block['blockName'] ) { + $attribute_to_prefix_map = array( + 'fontStyle' => 'var:preset|font-style|', + 'fontWeight' => 'var:preset|font-weight|', + 'textDecoration' => 'var:preset|text-decoration|', + 'textTransform' => 'var:preset|text-transform|', + ); + foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) { + if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) { + $prefix_len = strlen( $prefix ); + $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ]; + if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) { + $attribute_value = substr( $attribute_value, $prefix_len ); + } + if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) { + $attribute_value = 'line-through'; + } + } + } + } + return $parsed_block; +} + +add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' ); diff --git a/src/wp-includes/blocks/navigation/block.json b/src/wp-includes/blocks/navigation/block.json new file mode 100644 index 0000000000000..931010815030e --- /dev/null +++ b/src/wp-includes/blocks/navigation/block.json @@ -0,0 +1,124 @@ +{ + "apiVersion": 2, + "name": "core/navigation", + "title": "Navigation", + "category": "theme", + "description": "A collection of blocks that allow visitors to get around your site.", + "keywords": [ + "menu", + "navigation", + "links" + ], + "textdomain": "default", + "attributes": { + "navigationMenuId": { + "type": "number" + }, + "textColor": { + "type": "string" + }, + "customTextColor": { + "type": "string" + }, + "rgbTextColor": { + "type": "string" + }, + "backgroundColor": { + "type": "string" + }, + "customBackgroundColor": { + "type": "string" + }, + "rgbBackgroundColor": { + "type": "string" + }, + "showSubmenuIcon": { + "type": "boolean", + "default": true + }, + "openSubmenusOnClick": { + "type": "boolean", + "default": false + }, + "overlayMenu": { + "type": "string", + "default": "mobile" + }, + "__unstableLocation": { + "type": "string" + }, + "overlayBackgroundColor": { + "type": "string" + }, + "customOverlayBackgroundColor": { + "type": "string" + }, + "overlayTextColor": { + "type": "string" + }, + "customOverlayTextColor": { + "type": "string" + } + }, + "usesContext": [ "navigationArea" ], + "providesContext": { + "textColor": "textColor", + "customTextColor": "customTextColor", + "backgroundColor": "backgroundColor", + "customBackgroundColor": "customBackgroundColor", + "overlayTextColor": "overlayTextColor", + "customOverlayTextColor": "customOverlayTextColor", + "overlayBackgroundColor": "overlayBackgroundColor", + "customOverlayBackgroundColor": "customOverlayBackgroundColor", + "fontSize": "fontSize", + "customFontSize": "customFontSize", + "showSubmenuIcon": "showSubmenuIcon", + "openSubmenusOnClick": "openSubmenusOnClick", + "style": "style", + "orientation": "orientation" + }, + "supports": { + "align": [ + "wide", + "full" + ], + "anchor": true, + "html": false, + "inserter": true, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalTextTransform": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, + "spacing": { + "blockGap": true, + "units": [ + "px", + "em", + "rem", + "vh", + "vw" + ], + "__experimentalDefaultControls": { + "blockGap": true + } + }, + "__experimentalLayout": { + "allowSwitching": false, + "allowInheriting": false, + "default": { + "type": "flex" + } + } + }, + "viewScript": "file:./view.min.js", + "editorStyle": "wp-block-navigation-editor", + "style": "wp-block-navigation" +} diff --git a/src/wp-includes/blocks/navigation/view.asset.php b/src/wp-includes/blocks/navigation/view.asset.php new file mode 100644 index 0000000000000..d28c9db95b5b3 --- /dev/null +++ b/src/wp-includes/blocks/navigation/view.asset.php @@ -0,0 +1 @@ + array(), 'version' => '86538493346805d860c94eb70dd1323d'); \ No newline at end of file diff --git a/src/wp-includes/blocks/navigation/view.min.asset.php b/src/wp-includes/blocks/navigation/view.min.asset.php new file mode 100644 index 0000000000000..444bff68672db --- /dev/null +++ b/src/wp-includes/blocks/navigation/view.min.asset.php @@ -0,0 +1 @@ + array(), 'version' => '7b2c5174a07c417dc3db6f1d9a9c3f78'); \ No newline at end of file diff --git a/src/wp-includes/blocks/pattern.php b/src/wp-includes/blocks/pattern.php new file mode 100644 index 0000000000000..32a08601ca808 --- /dev/null +++ b/src/wp-includes/blocks/pattern.php @@ -0,0 +1,44 @@ + 'render_block_core_pattern', + ) + ); +} + +/** + * Renders the `core/pattern` block on the server. + * + * @param array $attributes Block attributes. + * + * @return string Returns the output of the pattern. + */ +function render_block_core_pattern( $attributes ) { + if ( empty( $attributes['slug'] ) ) { + return ''; + } + + $slug = $attributes['slug']; + $registry = WP_Block_Patterns_Registry::get_instance(); + if ( ! $registry->is_registered( $slug ) ) { + return ''; + } + + $pattern = $registry->get_registered( $slug ); + return do_blocks( $pattern['content'] ); +} + +add_action( 'init', 'register_block_core_pattern' ); diff --git a/src/wp-includes/blocks/pattern/block.json b/src/wp-includes/blocks/pattern/block.json new file mode 100644 index 0000000000000..78423e8d49123 --- /dev/null +++ b/src/wp-includes/blocks/pattern/block.json @@ -0,0 +1,17 @@ +{ + "apiVersion": 2, + "name": "core/pattern", + "title": "Pattern", + "category": "design", + "description": "Show a block pattern.", + "supports": { + "html": false, + "inserter": false + }, + "textdomain": "default", + "attributes": { + "slug": { + "type": "string" + } + } +} diff --git a/src/wp-includes/blocks/post-author.php b/src/wp-includes/blocks/post-author.php new file mode 100644 index 0000000000000..e31be65f70994 --- /dev/null +++ b/src/wp-includes/blocks/post-author.php @@ -0,0 +1,61 @@ +context['postId'] ) ) { + return ''; + } + + $author_id = get_post_field( 'post_author', $block->context['postId'] ); + if ( empty( $author_id ) ) { + return ''; + } + + $avatar = ! empty( $attributes['avatarSize'] ) ? get_avatar( + $author_id, + $attributes['avatarSize'] + ) : null; + + $byline = ! empty( $attributes['byline'] ) ? $attributes['byline'] : false; + $classes = array_merge( + isset( $attributes['className'] ) ? array( $attributes['className'] ) : array(), + isset( $attributes['itemsJustification'] ) ? array( 'items-justified-' . $attributes['itemsJustification'] ) : array(), + isset( $attributes['textAlign'] ) ? array( 'has-text-align-' . $attributes['textAlign'] ) : array() + ); + + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) ); + + return sprintf( '
    ', $wrapper_attributes ) . + ( ! empty( $attributes['showAvatar'] ) ? '' : '' ) . + '' . + '
    '; +} + +/** + * Registers the `core/post-author` block on the server. + */ +function register_block_core_post_author() { + register_block_type_from_metadata( + __DIR__ . '/post-author', + array( + 'render_callback' => 'render_block_core_post_author', + ) + ); +} +add_action( 'init', 'register_block_core_post_author' ); diff --git a/src/wp-includes/blocks/post-author/block.json b/src/wp-includes/blocks/post-author/block.json new file mode 100644 index 0000000000000..d46c8df69da29 --- /dev/null +++ b/src/wp-includes/blocks/post-author/block.json @@ -0,0 +1,53 @@ +{ + "apiVersion": 2, + "name": "core/post-author", + "title": "Post Author", + "category": "theme", + "description": "Add the author of this post.", + "textdomain": "default", + "attributes": { + "textAlign": { + "type": "string" + }, + "avatarSize": { + "type": "number", + "default": 48 + }, + "showAvatar": { + "type": "boolean", + "default": true + }, + "showBio": { + "type": "boolean" + }, + "byline": { + "type": "string" + } + }, + "usesContext": [ "postType", "postId", "queryId" ], + "supports": { + "html": false, + "spacing": { + "margin": true, + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, + "color": { + "gradients": true, + "link": true, + "__experimentalDuotone": ".wp-block-post-author__avatar img" + } + }, + "editorStyle": "wp-block-post-author-editor", + "style": "wp-block-post-author" +} diff --git a/src/wp-includes/blocks/post-comments.php b/src/wp-includes/blocks/post-comments.php new file mode 100644 index 0000000000000..42f9a8d60d94e --- /dev/null +++ b/src/wp-includes/blocks/post-comments.php @@ -0,0 +1,68 @@ +context['postId']; + if ( ! isset( $post_id ) ) { + return ''; + } + + $comment_args = array( + 'post_id' => $post_id, + 'count' => true, + ); + // Return early if there are no comments and comments are closed. + if ( ! comments_open( $post_id ) && get_comments( $comment_args ) === 0 ) { + return ''; + } + + $post_before = $post; + $post = get_post( $post_id ); + setup_postdata( $post ); + + ob_start(); + // There's a deprecation warning generated by WP Core. + // Ideally this deprecation is removed from Core. + // In the meantime, this removes it from the output. + add_filter( 'deprecated_file_trigger_error', '__return_false' ); + comments_template(); + remove_filter( 'deprecated_file_trigger_error', '__return_false' ); + $post = $post_before; + + $classes = ''; + if ( isset( $attributes['textAlign'] ) ) { + $classes .= 'has-text-align-' . $attributes['textAlign']; + } + + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) ); + $output = ob_get_clean(); + + return sprintf( '
    %2$s
    ', $wrapper_attributes, $output ); +} + +/** + * Registers the `core/post-comments` block on the server. + */ +function register_block_core_post_comments() { + register_block_type_from_metadata( + __DIR__ . '/post-comments', + array( + 'render_callback' => 'render_block_core_post_comments', + ) + ); +} +add_action( 'init', 'register_block_core_post_comments' ); diff --git a/src/wp-includes/blocks/post-comments/block.json b/src/wp-includes/blocks/post-comments/block.json new file mode 100644 index 0000000000000..2aab1d09f9902 --- /dev/null +++ b/src/wp-includes/blocks/post-comments/block.json @@ -0,0 +1,34 @@ +{ + "apiVersion": 2, + "name": "core/post-comments", + "title": "Post Comments", + "category": "theme", + "description": "Display a post's comments.", + "textdomain": "default", + "attributes": { + "textAlign": { + "type": "string" + } + }, + "usesContext": [ "postId", "postType" ], + "supports": { + "html": false, + "align": [ "wide", "full" ], + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, + "color": { + "gradients": true, + "link": true + } + }, + "style": "wp-block-post-comments" +} diff --git a/src/wp-includes/blocks/post-navigation-link.php b/src/wp-includes/blocks/post-navigation-link.php new file mode 100644 index 0000000000000..9fedf38d6fa1c --- /dev/null +++ b/src/wp-includes/blocks/post-navigation-link.php @@ -0,0 +1,96 @@ + $classes ) ); + // Set default values. + $format = '%link'; + $link = 'next' === $navigation_type ? _x( 'Next', 'label for next post link' ) : _x( 'Previous', 'label for previous post link' ); + $label = ''; + + // If a custom label is provided, make this a link. + // `$label` is used to prepend the provided label, if we want to show the page title as well. + if ( isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ) { + $label = "{$attributes['label']}"; + $link = $label; + } + + // If we want to also show the page title, make the page title a link and prepend the label. + if ( isset( $attributes['showTitle'] ) && $attributes['showTitle'] ) { + /* + * If the label link option is not enabled but there is a custom label, + * display the custom label as text before the linked title. + */ + if ( ! $attributes['linkLabel'] ) { + if ( $label ) { + $format = '' . $label . ' %link'; + } + $link = '%title'; + } elseif ( isset( $attributes['linkLabel'] ) && $attributes['linkLabel'] ) { + // If the label link option is enabled and there is a custom label, display it before the title. + if ( $label ) { + $link = '' . $label . ' %title'; + } else { + /* + * If the label link option is enabled and there is no custom label, + * add a colon between the label and the post title. + */ + $label = 'next' === $navigation_type ? _x( 'Next:', 'label before the title of the next post' ) : _x( 'Previous:', 'label before the title of the previous post' ); + $link = sprintf( + '%1$s %2$s', + $label, + '%title' + ); + } + } + } + + // The dynamic portion of the function name, `$navigation_type`, + // refers to the type of adjacency, 'next' or 'previous'. + $get_link_function = "get_{$navigation_type}_post_link"; + $content = $get_link_function( $format, $link ); + return sprintf( + '
    %2$s
    ', + $wrapper_attributes, + $content + ); +} + +/** + * Registers the `core/post-navigation-link` block on the server. + */ +function register_block_core_post_navigation_link() { + register_block_type_from_metadata( + __DIR__ . '/post-navigation-link', + array( + 'render_callback' => 'render_block_core_post_navigation_link', + ) + ); +} +add_action( 'init', 'register_block_core_post_navigation_link' ); diff --git a/src/wp-includes/blocks/post-navigation-link/block.json b/src/wp-includes/blocks/post-navigation-link/block.json new file mode 100644 index 0000000000000..ec983b6b57a6f --- /dev/null +++ b/src/wp-includes/blocks/post-navigation-link/block.json @@ -0,0 +1,43 @@ +{ + "apiVersion": 2, + "name": "core/post-navigation-link", + "title": "Post Navigation Link", + "category": "theme", + "description": "Displays the next or previous post link that is adjacent to the current post.", + "textdomain": "default", + "attributes": { + "textAlign": { + "type": "string" + }, + "type": { + "type": "string", + "default": "next" + }, + "label": { + "type": "string" + }, + "showTitle": { + "type": "boolean", + "default": false + }, + "linkLabel": { + "type": "boolean", + "default": false + } + }, + "supports": { + "reusable": false, + "html": false, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": { + "fontSize": true + } + } + } +} diff --git a/src/wp-includes/blocks/term-description.php b/src/wp-includes/blocks/term-description.php new file mode 100644 index 0000000000000..4f1cd7e518d11 --- /dev/null +++ b/src/wp-includes/blocks/term-description.php @@ -0,0 +1,45 @@ + 'has-text-align-' . $attributes['textAlign'] ) + : array(); + $wrapper_attributes = get_block_wrapper_attributes( $extra_attributes ); + + return '
    ' . $term_description . '
    '; +} + +/** + * Registers the `core/term-description` block on the server. + */ +function register_block_core_term_description() { + register_block_type_from_metadata( + __DIR__ . '/term-description', + array( + 'render_callback' => 'render_block_core_term_description', + ) + ); +} +add_action( 'init', 'register_block_core_term_description' ); diff --git a/src/wp-includes/blocks/term-description/block.json b/src/wp-includes/blocks/term-description/block.json new file mode 100644 index 0000000000000..608ae1a242349 --- /dev/null +++ b/src/wp-includes/blocks/term-description/block.json @@ -0,0 +1,28 @@ +{ + "apiVersion": 2, + "name": "core/term-description", + "title": "Term Description", + "category": "theme", + "description": "Display the description of categories, tags and custom taxonomies when viewing an archive.", + "textdomain": "default", + "attributes": { + "textAlign": { + "type": "string" + } + }, + "supports": { + "align": [ "wide", "full" ], + "html": false, + "color": { + "link": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalDefaultControls": { + "fontSize": true + } + } + }, + "editorStyle": "wp-block-term-description-editor" +} diff --git a/src/wp-includes/blocks/widget-group.php b/src/wp-includes/blocks/widget-group.php new file mode 100644 index 0000000000000..6cf6442346a30 --- /dev/null +++ b/src/wp-includes/blocks/widget-group.php @@ -0,0 +1,78 @@ +'; + $after_title = ''; + } + + $html = ''; + + if ( ! empty( $attributes['title'] ) ) { + $html .= $before_title . $attributes['title'] . $after_title; + } + + $html .= '
    '; + foreach ( $block->inner_blocks as $inner_block ) { + $html .= $inner_block->render(); + } + $html .= '
    '; + + return $html; +} + +/** + * Registers the 'core/widget-group' block. + */ +function register_block_core_widget_group() { + register_block_type_from_metadata( + __DIR__ . '/widget-group', + array( + 'render_callback' => 'render_block_core_widget_group', + ) + ); +} + +add_action( 'init', 'register_block_core_widget_group' ); + +/** + * Make a note of the sidebar being rendered before WordPress starts rendering + * it. This lets us get to the current sidebar in + * render_block_core_widget_group(). + * + * @param int|string $index Index, name, or ID of the dynamic sidebar. + */ +function note_sidebar_being_rendered( $index ) { + global $_sidebar_being_rendered; + $_sidebar_being_rendered = $index; +} +add_action( 'dynamic_sidebar_before', 'note_sidebar_being_rendered' ); + +/** + * Clear whatever we set in note_sidebar_being_rendered() after WordPress + * finishes rendering a sidebar. + */ +function discard_sidebar_being_rendered() { + global $_sidebar_being_rendered; + unset( $_sidebar_being_rendered ); +} +add_action( 'dynamic_sidebar_after', 'discard_sidebar_being_rendered' ); diff --git a/src/wp-includes/blocks/widget-group/block.json b/src/wp-includes/blocks/widget-group/block.json new file mode 100644 index 0000000000000..ec48d90eda5ca --- /dev/null +++ b/src/wp-includes/blocks/widget-group/block.json @@ -0,0 +1,18 @@ +{ + "apiVersion": 2, + "name": "core/widget-group", + "category": "widgets", + "attributes": { + "title": { + "type": "string" + } + }, + "supports": { + "html": false, + "inserter": true, + "customClassName": true, + "reusable": false + }, + "editorStyle": "wp-block-widget-group-editor", + "style": "wp-block-widget-group" +} diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 5783d5cd0f33d..d67a432637732 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -581,6 +581,7 @@ add_action( 'admin_footer-post.php', 'wp_add_iframed_editor_assets_html' ); add_action( 'admin_footer-post-new.php', 'wp_add_iframed_editor_assets_html' ); +add_action( 'admin_footer-widgets.php', 'wp_add_iframed_editor_assets_html' ); // Taxonomy. add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority. diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 90169c54dd0c3..32df2c64060b0 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2287,6 +2287,8 @@ function safecss_filter_attr( $css, $deprecated = '' ) { 'border-right-width', 'border-bottom', 'border-bottom-color', + 'border-bottom-left-radius', + 'border-bottom-right-radius', 'border-bottom-style', 'border-bottom-width', 'border-bottom-right-radius', @@ -2297,6 +2299,8 @@ function safecss_filter_attr( $css, $deprecated = '' ) { 'border-left-width', 'border-top', 'border-top-color', + 'border-top-left-radius', + 'border-top-right-radius', 'border-top-style', 'border-top-width', 'border-top-left-radius', diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 8716e29961397..96ed177773e41 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -450,6 +450,51 @@ function create_initial_post_types() { ) ); + register_post_type( + 'wp_navigation', + array( + 'labels' => array( + 'name' => __( 'Navigation Menus', 'gutenberg' ), + 'singular_name' => __( 'Navigation Menu', 'gutenberg' ), + 'menu_name' => _x( 'Navigation Menus', 'Admin Menu text', 'gutenberg' ), + 'add_new' => _x( 'Add New', 'Navigation Menu', 'gutenberg' ), + 'add_new_item' => __( 'Add New Navigation Menu', 'gutenberg' ), + 'new_item' => __( 'New Navigation Menu', 'gutenberg' ), + 'edit_item' => __( 'Edit Navigation Menu', 'gutenberg' ), + 'view_item' => __( 'View Navigation Menu', 'gutenberg' ), + 'all_items' => __( 'All Navigation Menus', 'gutenberg' ), + 'search_items' => __( 'Search Navigation Menus', 'gutenberg' ), + 'parent_item_colon' => __( 'Parent Navigation Menu:', 'gutenberg' ), + 'not_found' => __( 'No Navigation Menu found.', 'gutenberg' ), + 'not_found_in_trash' => __( 'No Navigation Menu found in Trash.', 'gutenberg' ), + 'archives' => __( 'Navigation Menu archives', 'gutenberg' ), + 'insert_into_item' => __( 'Insert into Navigation Menu', 'gutenberg' ), + 'uploaded_to_this_item' => __( 'Uploaded to this Navigation Menu', 'gutenberg' ), + // Some of these are a bit weird, what are they for? + 'filter_items_list' => __( 'Filter Navigation Menu list', 'gutenberg' ), + 'items_list_navigation' => __( 'Navigation Menus list navigation', 'gutenberg' ), + 'items_list' => __( 'Navigation Menus list', 'gutenberg' ), + ), + 'description' => __( 'Navigation menus.', 'gutenberg' ), + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + 'has_archive' => false, + 'show_ui' => false, + 'show_in_menu' => 'themes.php', + 'show_in_admin_bar' => false, + 'show_in_rest' => true, + 'rewrite' => false, + 'map_meta_cap' => true, + 'rest_base' => 'navigation', + 'rest_controller_class' => 'WP_REST_Posts_Controller', + 'supports' => array( + 'title', + 'editor', + 'revisions', + ), + ) + ); + register_post_status( 'publish', array( diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php index e3e5d935d7a1a..a299580be1d2a 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php @@ -311,6 +311,12 @@ public function get_items( $request ) { $prepared_args['has_published_posts'] = get_post_types( array( 'show_in_rest' => true ), 'names' ); } + if ( ! empty( $request['has_published_posts'] ) ) { + $prepared_args['has_published_posts'] = ( true === $request['has_published_posts'] ) + ? get_post_types( array( 'show_in_rest' => true ), 'names' ) + : (array) $request['has_published_posts']; + } + if ( ! empty( $prepared_args['search'] ) ) { $prepared_args['search'] = '*' . $prepared_args['search'] . '*'; } @@ -1580,6 +1586,15 @@ public function get_collection_params() { ), ); + $query_params['has_published_posts'] = array( + 'description' => __( 'Limit result set to users who have published posts.' ), + 'type' => array( 'boolean', 'array' ), + 'items' => array( + 'type' => 'string', + 'enum' => get_post_types( array( 'show_in_rest' => true ), 'names' ), + ), + ); + /** * Filters REST API collection parameters for the users controller. * diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php index 82c3a5255ee84..2fd591c719b10 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php @@ -99,6 +99,29 @@ public function register_routes() { ), ) ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/render', + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'callback' => array( $this, 'render' ), + 'args' => array( + 'id' => array( + 'description' => __( 'The widget type id.', 'default' ), + 'type' => 'string', + 'required' => true, + ), + 'instance' => array( + 'description' => __( 'Current instance settings of the widget.', 'default' ), + 'type' => 'object', + ), + ), + ), + ) + ); } /** @@ -559,6 +582,78 @@ private function get_widget_form( $widget_object, $instance ) { return ob_get_clean(); } + /** + * Renders a single Legacy Widget and wraps it in a JSON-encodable array. + * + * @since 5.9.0 + * + * @param WP_REST_Request $request Full details about the request. + * + * @return array An array with rendered Legacy Widget HTML. + */ + public function render( $request ) { + return array( + 'preview' => $this->render_legacy_widget_preview_iframe( + $request['id'], + isset( $request['instance'] ) ? $request['instance'] : null + ), + ); + } + + /** + * Renders a page containing a preview of the requested Legacy Widget block. + * + * @since 5.9.0 + * + * @param string $id_base The id base of the requested widget. + * @param array $instance The widget instance attributes. + * + * @return string Rendered Legacy Widget block preview. + */ + private function render_legacy_widget_preview_iframe( $id_base, $instance ) { + if ( ! defined( 'IFRAME_REQUEST' ) ) { + define( 'IFRAME_REQUEST', true ); + } + + ob_start(); + ?> + + > + + + + + + + + > +
    +
    + get_registered( 'core/legacy-widget' ); + echo $block->render( + array( + 'idBase' => $id_base, + 'instance' => $instance, + ) + ); + ?> +
    +
    + + + + '16.13.1', - 'react-dom' => '16.13.1', + 'react' => '17.0.1', + 'react-dom' => '17.0.1', 'regenerator-runtime' => '0.13.7', 'moment' => '2.29.1', 'lodash' => '4.17.19', @@ -1598,6 +1598,11 @@ function wp_default_styles( $styles ) { 'wp-block-library', 'wp-reusable-blocks', ), + 'edit-site' => array( + 'wp-components', + 'wp-block-editor', + 'wp-edit-blocks', + ), ); foreach ( $package_styles as $package => $dependencies ) { @@ -1655,6 +1660,7 @@ function wp_default_styles( $styles ) { 'wp-components', 'wp-customize-widgets', 'wp-edit-post', + 'wp-edit-site', 'wp-edit-widgets', 'wp-editor', 'wp-format-library', @@ -2265,7 +2271,19 @@ function wp_common_block_scripts_and_styles() { wp_enqueue_style( 'wp-block-library' ); if ( current_theme_supports( 'wp-block-styles' ) ) { - wp_enqueue_style( 'wp-block-library-theme' ); + if ( wp_should_load_separate_core_block_assets() ) { + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? 'css' : 'min.css'; + $files = glob( __DIR__ . "/blocks/**/theme.$suffix" ); + foreach ( $files as $path ) { + $block_name = basename( dirname( $path ) ); + if ( is_rtl() && file_exists( __DIR__ . "/blocks/$block_name/theme-rtl.$suffix" ) ) { + $path = __DIR__ . "/blocks/$block_name/theme-rtl.$suffix"; + } + wp_add_inline_style( "wp-block-{$block_name}", file_get_contents( $path ) ); + } + } else { + wp_enqueue_style( 'wp-block-library-theme' ); + } } /** @@ -2773,6 +2791,8 @@ function _wp_normalize_relative_css_links( $css, $stylesheet_url ) { * @since 5.8.0 */ function wp_add_iframed_editor_assets_html() { + global $pagenow; + if ( ! wp_should_load_block_editor_scripts_and_styles() ) { return; } @@ -2785,6 +2805,11 @@ function wp_add_iframed_editor_assets_html() { 'wp-edit-blocks', ); + if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) { + $style_handles[] = 'wp-widgets'; + $style_handles[] = 'wp-edit-widgets'; + } + $block_registry = WP_Block_Type_Registry::get_instance(); foreach ( $block_registry->get_all_registered() as $block_type ) { @@ -2806,7 +2831,8 @@ function wp_add_iframed_editor_assets_html() { ob_start(); - wp_styles()->done = array(); + // We do not need reset styles for the iframed editor. + wp_styles()->done = array( 'wp-reset-editor-styles' ); wp_styles()->do_items( $style_handles ); wp_styles()->done = $done; diff --git a/src/wp-includes/template-canvas.php b/src/wp-includes/template-canvas.php index 2ce5b12ca6596..2972c94bd16fc 100644 --- a/src/wp-includes/template-canvas.php +++ b/src/wp-includes/template-canvas.php @@ -10,8 +10,7 @@ * This needs to run before so that blocks can add scripts and styles in wp_head(). */ $template_html = get_the_block_template_html(); -?> - +?> > diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php index 7d17540142a8f..d0471792e19ab 100644 --- a/src/wp-includes/theme.php +++ b/src/wp-includes/theme.php @@ -4075,3 +4075,14 @@ function create_initial_theme_features() { ) ); } + +/** + * Returns whether the current theme is a block-based theme or not. + * + * @since 5.9.0 + * + * @return boolean Whether the current theme is a block-based theme or not. + */ +function wp_is_block_template_theme() { + return is_readable( get_theme_file_path( '/block-templates/index.html' ) ); +} diff --git a/src/wp-settings.php b/src/wp-settings.php index 9f027ee645f07..978b451415073 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -317,6 +317,7 @@ require ABSPATH . WPINC . '/block-supports/border.php'; require ABSPATH . WPINC . '/block-supports/colors.php'; require ABSPATH . WPINC . '/block-supports/custom-classname.php'; +require ABSPATH . WPINC . '/block-supports/dimensions.php'; require ABSPATH . WPINC . '/block-supports/duotone.php'; require ABSPATH . WPINC . '/block-supports/elements.php'; require ABSPATH . WPINC . '/block-supports/generated-classname.php'; diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php index 02d6ba343b1ff..1a100a3432168 100644 --- a/tests/phpunit/includes/functions.php +++ b/tests/phpunit/includes/functions.php @@ -307,20 +307,27 @@ function _unhook_block_registration() { remove_action( 'init', 'register_block_core_calendar' ); remove_action( 'init', 'register_block_core_categories' ); remove_action( 'init', 'register_block_core_file' ); - remove_action( 'init', 'register_block_core_loginout' ); remove_action( 'init', 'register_block_core_latest_comments' ); remove_action( 'init', 'register_block_core_latest_posts' ); + remove_action( 'init', 'register_block_core_legacy_widget' ); + remove_action( 'init', 'register_block_core_loginout' ); + remove_action( 'init', 'register_block_core_navigation' ); + remove_action( 'init', 'register_block_core_navigation_area' ); + remove_action( 'init', 'register_block_core_navigation_link' ); + remove_action( 'init', 'register_block_core_navigation_submenu' ); remove_action( 'init', 'register_block_core_page_list' ); + remove_action( 'init', 'register_block_core_pattern' ); remove_action( 'init', 'register_block_core_post_author' ); + remove_action( 'init', 'register_block_core_post_comments' ); remove_action( 'init', 'register_block_core_post_content' ); remove_action( 'init', 'register_block_core_post_date' ); remove_action( 'init', 'register_block_core_post_excerpt' ); remove_action( 'init', 'register_block_core_post_featured_image' ); + remove_action( 'init', 'register_block_core_post_navigation_link' ); + remove_action( 'init', 'register_block_core_post_template' ); remove_action( 'init', 'register_block_core_post_terms' ); remove_action( 'init', 'register_block_core_post_title' ); remove_action( 'init', 'register_block_core_query' ); - remove_action( 'init', 'register_block_core_post_template' ); - remove_action( 'init', 'gutenberg_register_legacy_query_loop_block' ); remove_action( 'init', 'register_block_core_query_pagination' ); remove_action( 'init', 'register_block_core_query_pagination_next' ); remove_action( 'init', 'register_block_core_query_pagination_numbers' ); @@ -329,14 +336,14 @@ function _unhook_block_registration() { remove_action( 'init', 'register_block_core_rss' ); remove_action( 'init', 'register_block_core_search' ); remove_action( 'init', 'register_block_core_shortcode' ); - remove_action( 'init', 'register_block_core_site_tagline' ); remove_action( 'init', 'register_block_core_site_logo' ); + remove_action( 'init', 'register_block_core_site_tagline' ); remove_action( 'init', 'register_block_core_site_title' ); remove_action( 'init', 'register_block_core_social_link' ); remove_action( 'init', 'register_block_core_social_link' ); remove_action( 'init', 'register_block_core_tag_cloud' ); - remove_action( 'init', 'register_core_block_types_from_metadata' ); - remove_action( 'init', 'register_block_core_legacy_widget' ); remove_action( 'init', 'register_block_core_template_part' ); + remove_action( 'init', 'register_block_core_term_description' ); + remove_action( 'init', 'register_core_block_types_from_metadata' ); } tests_add_filter( 'init', '_unhook_block_registration', 1000 ); diff --git a/tests/phpunit/tests/block-supports/colors.php b/tests/phpunit/tests/block-supports/colors.php new file mode 100644 index 0000000000000..dbc32d6d276e3 --- /dev/null +++ b/tests/phpunit/tests/block-supports/colors.php @@ -0,0 +1,47 @@ + 2, + 'attributes' => array( + 'textColor' => array( + 'type' => 'string', + ), + 'backgroundColor' => array( + 'type' => 'string', + ), + 'gradient' => array( + 'type' => 'string', + ), + ), + 'supports' => array( + 'color' => array( + 'text' => true, + 'background' => true, + 'gradients' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( 'test/color-slug-with-numbers' ); + + $block_atts = array( + 'textColor' => 'fg1', + 'backgroundColor' => 'bg2', + 'gradient' => 'gr3', + ); + + $actual = wp_apply_colors_support( $block_type, $block_atts ); + $expected = array( 'class' => 'has-text-color has-fg-1-color has-background has-bg-2-background-color has-background has-gr-3-gradient-background' ); + + $this->assertSame( $expected, $actual ); + unregister_block_type( 'test/color-slug-with-numbers' ); + } +} diff --git a/tests/phpunit/tests/block-supports/elements.php b/tests/phpunit/tests/block-supports/elements.php new file mode 100644 index 0000000000000..6a56c083e6c09 --- /dev/null +++ b/tests/phpunit/tests/block-supports/elements.php @@ -0,0 +1,104 @@ +Hello WordPress!

    ', + array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'style' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'var:preset|color|subtle-background', + ), + ), + ), + ), + ), + ) + ) + ); + $this->assertSame( + $result, + '

    Hello WordPress!

    ' + ); + } + + /** + * Test wp_render_elements_support() with a paragraph containing a class. + */ + public function test_class_paragraph_link_color() { + $result = self::make_unique_id_one( + wp_render_elements_support( + '

    Hello WordPress!

    ', + array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'style' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'red', + ), + ), + ), + ), + 'backgroundColor' => 'dark-gray', + ), + ) + ) + ); + $this->assertSame( + $result, + '

    Hello WordPress!

    ' + ); + } + + /** + * Test wp_render_elements_support() with a paragraph containing a anchor. + */ + public function test_anchor_paragraph_link_color() { + $result = self::make_unique_id_one( + wp_render_elements_support( + '

    Hello WordPress!

    ', + array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'style' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#fff000', + ), + ), + ), + ), + ), + ) + ) + ); + $this->assertSame( + $result, + '

    Hello WordPress!

    ' + ); + } +} diff --git a/tests/phpunit/tests/block-supports/spacing.php b/tests/phpunit/tests/block-supports/spacing.php new file mode 100644 index 0000000000000..7c965214c0e7b --- /dev/null +++ b/tests/phpunit/tests/block-supports/spacing.php @@ -0,0 +1,138 @@ +Test
    '; + private $test_gap_block_value = array(); + private $test_gap_block_args = array(); + + function set_up() { + parent::set_up(); + + $this->test_gap_block_value = array( + 'blockName' => 'test/test-block', + 'attrs' => array( + 'style' => array( + 'spacing' => array( + 'blockGap' => '3em', + ), + ), + ), + ); + + $this->test_gap_block_args = array( + 'api_version' => 2, + 'supports' => array( + 'spacing' => array( + 'blockGap' => true, + ), + ), + ); + } + + function tear_down() { + unregister_block_type( 'test/test-block' ); + + parent::tear_down(); + } + + function test_spacing_gap_block_support_renders_block_inline_style() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = wp_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertSame( + '
    Test
    ', + $render_output + ); + } + + function test_spacing_gap_block_support_renders_block_inline_style_with_inner_tag() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = wp_render_spacing_gap_support( + '

    Test

    ', + $this->test_gap_block_value + ); + + $this->assertSame( + '

    Test

    ', + $render_output + ); + } + + function test_spacing_gap_block_support_renders_block_inline_style_with_no_other_attributes() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = wp_render_spacing_gap_support( + '

    Test

    ', + $this->test_gap_block_value + ); + + $this->assertSame( + '

    Test

    ', + $render_output + ); + } + + function test_spacing_gap_block_support_renders_appended_block_inline_style() { + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = wp_render_spacing_gap_support( + '

    Test

    ', + $this->test_gap_block_value + ); + + $this->assertSame( + '

    Test

    ', + $render_output + ); + } + + function test_spacing_gap_block_support_does_not_render_style_when_support_is_false() { + $this->test_gap_block_args['supports']['spacing']['blockGap'] = false; + + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = wp_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertEquals( + $this->sample_block_content, + $render_output + ); + } + + function test_spacing_gap_block_support_does_not_render_style_when_gap_is_null() { + $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = null; + $this->test_gap_block_args['supports']['spacing']['blockGap'] = true; + + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = wp_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertEquals( + $this->sample_block_content, + $render_output + ); + } + + function test_spacing_gap_block_support_does_not_render_style_when_gap_is_illegal_value() { + $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = '" javascript="alert("hello");'; + $this->test_gap_block_args['supports']['spacing']['blockGap'] = true; + + register_block_type( 'test/test-block', $this->test_gap_block_args ); + $render_output = wp_render_spacing_gap_support( + $this->sample_block_content, + $this->test_gap_block_value + ); + + $this->assertEquals( + $this->sample_block_content, + $render_output + ); + } +} diff --git a/tests/phpunit/tests/block-supports/typography.php b/tests/phpunit/tests/block-supports/typography.php new file mode 100644 index 0000000000000..6bda93d92f9c0 --- /dev/null +++ b/tests/phpunit/tests/block-supports/typography.php @@ -0,0 +1,123 @@ + 2, + 'attributes' => array( + 'fontSize' => array( + 'type' => 'string', + ), + ), + 'supports' => array( + 'typography' => array( + 'fontSize' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( 'test/font-size-slug-with-numbers' ); + + $block_atts = array( 'fontSize' => 'h1' ); + + $actual = wp_apply_typography_support( $block_type, $block_atts ); + $expected = array( 'class' => 'has-h-1-font-size' ); + + $this->assertSame( $expected, $actual ); + unregister_block_type( 'test/font-size-slug-with-numbers' ); + } + + function test_font_family_with_legacy_inline_styles_using_a_value() { + $block_name = 'test/font-family-with-inline-styles-using-value'; + register_block_type( + $block_name, + array( + 'api_version' => 2, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'typography' => array( + '__experimentalFontFamily' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $block_name ); + $block_atts = array( 'style' => array( 'typography' => array( 'fontFamily' => 'serif' ) ) ); + + $actual = wp_apply_typography_support( $block_type, $block_atts ); + $expected = array( 'style' => 'font-family: serif;' ); + + $this->assertSame( $expected, $actual ); + unregister_block_type( $block_name ); + } + + function test_font_family_with_legacy_inline_styles_using_a_css_var() { + $block_name = 'test/font-family-with-inline-styles-using-css-var'; + register_block_type( + $block_name, + array( + 'api_version' => 2, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'typography' => array( + '__experimentalFontFamily' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $block_name ); + $block_atts = array( 'style' => array( 'typography' => array( 'fontFamily' => 'var:preset|font-family|h1' ) ) ); + + $actual = wp_apply_typography_support( $block_type, $block_atts ); + $expected = array( 'style' => 'font-family: var(--wp--preset--font-family--h-1);' ); + + $this->assertSame( $expected, $actual ); + unregister_block_type( $block_name ); + } + + function test_font_family_with_class() { + $block_name = 'test/font-family-with-class'; + register_block_type( + $block_name, + array( + 'api_version' => 2, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'typography' => array( + '__experimentalFontFamily' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $block_name ); + $block_atts = array( 'fontFamily' => 'h1' ); + + $actual = wp_apply_typography_support( $block_type, $block_atts ); + $expected = array( 'class' => 'has-h-1-font-family' ); + + $this->assertSame( $expected, $actual ); + unregister_block_type( $block_name ); + } + +} diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index 714885319aadb..9d32e36731271 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -75,7 +75,7 @@ public static function wpTearDownAfterClass() { wp_delete_post( self::$post->ID ); } - public function test_build_template_result_from_post() { + public function test_build_block_template_result_from_post() { $template = _build_block_template_result_from_post( self::$post, 'wp_template' diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index a81b5b21e2358..4390bc2f9fccd 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -154,8 +154,15 @@ public function test_expected_routes_in_schema() { '/wp/v2/widget-types', '/wp/v2/widget-types/(?P[a-zA-Z0-9_-]+)', '/wp/v2/widget-types/(?P[a-zA-Z0-9_-]+)/encode', + '/wp/v2/widget-types/(?P[a-zA-Z0-9_-]+)/render', '/wp/v2/widgets', '/wp/v2/widgets/(?P[\w\-]+)', + '/wp/v2/navigation', + '/wp/v2/navigation/(?P[\d]+)', + '/wp/v2/navigation/(?P[\d]+)/autosaves', + '/wp/v2/navigation/(?P[\d]+)/autosaves/(?P[\d]+)', + '/wp/v2/navigation/(?P[\d]+)/revisions', + '/wp/v2/navigation/(?P[\d]+)/revisions/(?P[\d]+)', '/wp-site-health/v1', '/wp-site-health/v1/tests/background-updates', '/wp-site-health/v1/tests/loopback-requests', diff --git a/tests/phpunit/tests/rest-api/rest-users-controller.php b/tests/phpunit/tests/rest-api/rest-users-controller.php index c3f620c8cccd6..0c47f935aef82 100644 --- a/tests/phpunit/tests/rest-api/rest-users-controller.php +++ b/tests/phpunit/tests/rest-api/rest-users-controller.php @@ -207,6 +207,7 @@ public function test_registered_query_params() { 'search', 'slug', 'who', + 'has_published_posts', ), $keys ); diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index f61e16362e782..1eaf81db959d6 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -5115,6 +5115,814 @@ mockedApiResponse.Schema = { } ] }, + "/wp/v2/navigation": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, + "required": false + }, + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100, + "required": false + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string", + "required": false + }, + "after": { + "description": "Limit response to posts published after a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, + "modified_after": { + "description": "Limit response to posts modified after a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, + "before": { + "description": "Limit response to posts published before a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, + "modified_before": { + "description": "Limit response to posts modified before a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by post attribute.", + "type": "string", + "default": "date", + "enum": [ + "author", + "date", + "id", + "include", + "modified", + "parent", + "relevance", + "slug", + "include_slugs", + "title" + ], + "required": false + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "status": { + "default": "publish", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "publish", + "future", + "draft", + "pending", + "private", + "trash", + "auto-draft", + "inherit", + "request-pending", + "request-confirmed", + "request-failed", + "request-completed", + "any" + ], + "type": "string" + }, + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "password": { + "description": "A password to protect access to the content and excerpt.", + "type": "string", + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "content": { + "description": "The content for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML content for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" + } + ] + } + }, + "/wp/v2/navigation/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string", + "required": false + } + } + }, + { + "methods": [ + "POST", + "PUT", + "PATCH" + ], + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "password": { + "description": "A password to protect access to the content and excerpt.", + "type": "string", + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "content": { + "description": "The content for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML content for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", + "required": false + } + } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/revisions": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, + "required": false + }, + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "minimum": 1, + "maximum": 100, + "required": false + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string", + "required": false + }, + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by object attribute.", + "type": "string", + "default": "date", + "enum": [ + "date", + "id", + "include", + "relevance", + "slug", + "include_slugs", + "title" + ], + "required": false + } + } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Required to be true, as revisions do not support trashing.", + "required": false + } + } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "password": { + "description": "A password to protect access to the content and excerpt.", + "type": "string", + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "content": { + "description": "The content for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML content for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + } + } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "id": { + "description": "The ID for the autosave.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + } + } + } + ] + }, "/wp/v2/types": { "namespace": "wp/v2", "methods": [ @@ -5930,6 +6738,26 @@ mockedApiResponse.Schema = { "authors" ], "required": false + }, + "has_published_posts": { + "description": "Limit result set to users who have published posts.", + "type": [ + "boolean", + "array" + ], + "items": { + "type": "string", + "enum": { + "post": "post", + "page": "page", + "attachment": "attachment", + "wp_block": "wp_block", + "wp_template": "wp_template", + "wp_template_part": "wp_template_part", + "wp_navigation": "wp_navigation" + } + }, + "required": false } } }, @@ -7728,6 +8556,31 @@ mockedApiResponse.Schema = { } ] }, + "/wp/v2/widget-types/(?P[a-zA-Z0-9_-]+)/render": { + "namespace": "wp/v2", + "methods": [ + "POST" + ], + "endpoints": [ + { + "methods": [ + "POST" + ], + "args": { + "id": { + "description": "The widget type id.", + "type": "string", + "required": true + }, + "instance": { + "description": "Current instance settings of the widget.", + "type": "object", + "required": false + } + } + } + ] + }, "/wp/v2/widgets": { "namespace": "wp/v2", "methods": [ @@ -9210,6 +10063,34 @@ mockedApiResponse.TypesCollection = { } ] } + }, + "wp_navigation": { + "description": "Navigation menus.", + "hierarchical": false, + "name": "Navigation Menus", + "slug": "wp_navigation", + "taxonomies": [], + "rest_base": "navigation", + "rest_namespace": "wp/v2", + "_links": { + "collection": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/types" + } + ], + "wp:items": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" + } + ], + "curies": [ + { + "name": "wp", + "href": "https://api.w.org/{rel}", + "templated": true + } + ] + } } }; diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index 9604e1b6b9246..7aa2b291cb7f4 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -31,14 +31,22 @@ module.exports = function( env = { environment: 'production', watch: false, buil 'latest-comments', 'latest-posts', 'loginout', + 'navigation', + 'navigation-area', + 'navigation-link', + 'navigation-submenu', 'page-list', + 'pattern', + 'post-author', + 'post-comments', 'post-content', 'post-date', 'post-excerpt', 'post-featured-image', + 'post-navigation-link', + 'post-template', 'post-terms', 'post-title', - 'post-template', 'query', 'query-pagination', 'query-pagination-next', @@ -54,6 +62,7 @@ module.exports = function( env = { environment: 'production', watch: false, buil 'social-link', 'tag-cloud', 'template-part', + 'term-description', ]; const blockFolders = [ 'audio', @@ -90,6 +99,7 @@ module.exports = function( env = { environment: 'production', watch: false, buil ]; const blockPHPFiles = { 'widgets/src/blocks/legacy-widget/index.php': 'wp-includes/blocks/legacy-widget.php', + 'widgets/src/blocks/widget-group/index.php': 'wp-includes/blocks/widget-group.php', ...dynamicBlockFolders.reduce( ( files, blockName ) => { files[ `block-library/src/${ blockName }/index.php` ] = `wp-includes/blocks/${ blockName }.php`; return files; @@ -97,6 +107,7 @@ module.exports = function( env = { environment: 'production', watch: false, buil }; const blockMetadataFiles = { 'widgets/src/blocks/legacy-widget/block.json': 'wp-includes/blocks/legacy-widget/block.json', + 'widgets/src/blocks/widget-group/block.json': 'wp-includes/blocks/widget-group/block.json', ...blockFolders.reduce( ( files, blockName ) => { files[ `block-library/src/${ blockName }/block.json` ] = `wp-includes/blocks/${ blockName }/block.json`; return files; @@ -143,6 +154,7 @@ module.exports = function( env = { environment: 'production', watch: false, buil mode, entry: { 'file/view': join( baseDir, `node_modules/@wordpress/block-library/build-module/file/view` ), + 'navigation/view': join( baseDir, `node_modules/@wordpress/block-library/build-module/navigation/view` ), }, output: { devtoolNamespace: 'wp',