Ticket #45097: 45097.diff
File 45097.diff, 21.7 KB (added by , 2 years ago) |
---|
-
new file src/wp-includes/class-wp-block-type-registry.php
diff --git a/src/wp-includes/class-wp-block-type-registry.php b/src/wp-includes/class-wp-block-type-registry.php new file mode 100644 index 0000000000..f95dec6ce4
- + 1 <?php 2 /** 3 * Blocks API: WP_Block_Type_Registry class 4 * 5 * @package gutenberg 6 * @since 0.6.0 7 */ 8 9 /** 10 * Core class used for interacting with block types. 11 * 12 * @since 0.6.0 13 */ 14 final class WP_Block_Type_Registry { 15 /** 16 * Registered block types, as `$name => $instance` pairs. 17 * 18 * @since 0.6.0 19 * @access private 20 * @var WP_Block_Type[] 21 */ 22 private $registered_block_types = array(); 23 24 /** 25 * Container for the main instance of the class. 26 * 27 * @since 0.6.0 28 * @access private 29 * @static 30 * @var WP_Block_Type_Registry|null 31 */ 32 private static $instance = null; 33 34 /** 35 * Registers a block type. 36 * 37 * @since 0.6.0 38 * @access public 39 * 40 * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a 41 * complete WP_Block_Type instance. In case a WP_Block_Type 42 * is provided, the $args parameter will be ignored. 43 * @param array $args { 44 * Optional. Array of block type arguments. Any arguments may be defined, however the 45 * ones described below are supported by default. Default empty array. 46 * 47 * @type callable $render_callback Callback used to render blocks of this block type. 48 * @type array $attributes Block attributes mapping, property name to schema. 49 * } 50 * @return WP_Block_Type|false The registered block type on success, or false on failure. 51 */ 52 public function register( $name, $args = array() ) { 53 $block_type = null; 54 if ( $name instanceof WP_Block_Type ) { 55 $block_type = $name; 56 $name = $block_type->name; 57 } 58 59 if ( ! is_string( $name ) ) { 60 $message = __( 'Block type names must be strings.', 'gutenberg' ); 61 _doing_it_wrong( __METHOD__, $message, '0.1.0' ); 62 return false; 63 } 64 65 if ( preg_match( '/[A-Z]+/', $name ) ) { 66 $message = __( 'Block type names must not contain uppercase characters.', 'gutenberg' ); 67 _doing_it_wrong( __METHOD__, $message, '1.5.0' ); 68 return false; 69 } 70 71 $name_matcher = '/^[a-z0-9-]+\/[a-z0-9-]+$/'; 72 if ( ! preg_match( $name_matcher, $name ) ) { 73 $message = __( 'Block type names must contain a namespace prefix. Example: my-plugin/my-custom-block-type', 'gutenberg' ); 74 _doing_it_wrong( __METHOD__, $message, '0.1.0' ); 75 return false; 76 } 77 78 if ( $this->is_registered( $name ) ) { 79 /* translators: 1: block name */ 80 $message = sprintf( __( 'Block type "%s" is already registered.', 'gutenberg' ), $name ); 81 _doing_it_wrong( __METHOD__, $message, '0.1.0' ); 82 return false; 83 } 84 85 if ( ! $block_type ) { 86 $block_type = new WP_Block_Type( $name, $args ); 87 } 88 89 $this->registered_block_types[ $name ] = $block_type; 90 91 return $block_type; 92 } 93 94 /** 95 * Unregisters a block type. 96 * 97 * @since 0.6.0 98 * @access public 99 * 100 * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a 101 * complete WP_Block_Type instance. 102 * @return WP_Block_Type|false The unregistered block type on success, or false on failure. 103 */ 104 public function unregister( $name ) { 105 if ( $name instanceof WP_Block_Type ) { 106 $name = $name->name; 107 } 108 109 if ( ! $this->is_registered( $name ) ) { 110 /* translators: 1: block name */ 111 $message = sprintf( __( 'Block type "%s" is not registered.', 'gutenberg' ), $name ); 112 _doing_it_wrong( __METHOD__, $message, '0.1.0' ); 113 return false; 114 } 115 116 $unregistered_block_type = $this->registered_block_types[ $name ]; 117 unset( $this->registered_block_types[ $name ] ); 118 119 return $unregistered_block_type; 120 } 121 122 /** 123 * Retrieves a registered block type. 124 * 125 * @since 0.6.0 126 * @access public 127 * 128 * @param string $name Block type name including namespace. 129 * @return WP_Block_Type|null The registered block type, or null if it is not registered. 130 */ 131 public function get_registered( $name ) { 132 if ( ! $this->is_registered( $name ) ) { 133 return null; 134 } 135 136 return $this->registered_block_types[ $name ]; 137 } 138 139 /** 140 * Retrieves all registered block types. 141 * 142 * @since 0.6.0 143 * @access public 144 * 145 * @return WP_Block_Type[] Associative array of `$block_type_name => $block_type` pairs. 146 */ 147 public function get_all_registered() { 148 return $this->registered_block_types; 149 } 150 151 /** 152 * Checks if a block type is registered. 153 * 154 * @since 0.6.0 155 * @access public 156 * 157 * @param string $name Block type name including namespace. 158 * @return bool True if the block type is registered, false otherwise. 159 */ 160 public function is_registered( $name ) { 161 return isset( $this->registered_block_types[ $name ] ); 162 } 163 164 /** 165 * Utility method to retrieve the main instance of the class. 166 * 167 * The instance will be created if it does not exist yet. 168 * 169 * @since 0.6.0 170 * @access public 171 * @static 172 * 173 * @return WP_Block_Type_Registry The main instance. 174 */ 175 public static function get_instance() { 176 if ( null === self::$instance ) { 177 self::$instance = new self(); 178 } 179 180 return self::$instance; 181 } 182 } -
new file src/wp-includes/class-wp-block-type.php
diff --git a/src/wp-includes/class-wp-block-type.php b/src/wp-includes/class-wp-block-type.php new file mode 100644 index 0000000000..0c18bb6efa
- + 1 <?php 2 /** 3 * Blocks API: WP_Block_Type class 4 * 5 * @package gutenberg 6 * @since 0.6.0 7 */ 8 9 /** 10 * Core class representing a block type. 11 * 12 * @since 0.6.0 13 * 14 * @see register_block_type() 15 */ 16 class WP_Block_Type { 17 /** 18 * Block type key. 19 * 20 * @since 0.6.0 21 * @var string 22 */ 23 public $name; 24 25 /** 26 * Block type render callback. 27 * 28 * @since 0.6.0 29 * @var callable 30 */ 31 public $render_callback; 32 33 /** 34 * Block type attributes property schemas. 35 * 36 * @since 0.10.0 37 * @var array 38 */ 39 public $attributes; 40 41 /** 42 * Block type editor script handle. 43 * 44 * @since 2.0.0 45 * @var string 46 */ 47 public $editor_script; 48 49 /** 50 * Block type front end script handle. 51 * 52 * @since 2.0.0 53 * @var string 54 */ 55 public $script; 56 57 /** 58 * Block type editor style handle. 59 * 60 * @since 2.0.0 61 * @var string 62 */ 63 public $editor_style; 64 65 /** 66 * Block type front end style handle. 67 * 68 * @since 2.0.0 69 * @var string 70 */ 71 public $style; 72 73 /** 74 * Constructor. 75 * 76 * Will populate object properties from the provided arguments. 77 * 78 * @since 0.6.0 79 * 80 * @see register_block_type() 81 * 82 * @param string $block_type Block type name including namespace. 83 * @param array|string $args Optional. Array or string of arguments for registering a block type. 84 * Default empty array. 85 */ 86 public function __construct( $block_type, $args = array() ) { 87 $this->name = $block_type; 88 89 $this->set_props( $args ); 90 } 91 92 /** 93 * Renders the block type output for given attributes. 94 * 95 * @since 0.6.0 96 * 97 * @param array $attributes Optional. Block attributes. Default empty array. 98 * @param string $content Optional. Block content. Default empty string. 99 * @return string Rendered block type output. 100 */ 101 public function render( $attributes = array(), $content = '' ) { 102 if ( ! $this->is_dynamic() ) { 103 return ''; 104 } 105 106 $attributes = $this->prepare_attributes_for_render( $attributes ); 107 108 return (string) call_user_func( $this->render_callback, $attributes, $content ); 109 } 110 111 /** 112 * Returns true if the block type is dynamic, or false otherwise. A dynamic 113 * block is one which defers its rendering to occur on-demand at runtime. 114 * 115 * @return boolean Whether block type is dynamic. 116 */ 117 public function is_dynamic() { 118 return is_callable( $this->render_callback ); 119 } 120 121 /** 122 * Validates attributes against the current block schema, populating 123 * defaulted and missing values, and omitting unknown attributes. 124 * 125 * @param array $attributes Original block attributes. 126 * @return array Prepared block attributes. 127 */ 128 public function prepare_attributes_for_render( $attributes ) { 129 if ( ! isset( $this->attributes ) ) { 130 return $attributes; 131 } 132 133 $prepared_attributes = array(); 134 135 foreach ( $this->attributes as $attribute_name => $schema ) { 136 $value = null; 137 138 if ( isset( $attributes[ $attribute_name ] ) ) { 139 $is_valid = rest_validate_value_from_schema( $attributes[ $attribute_name ], $schema ); 140 if ( ! is_wp_error( $is_valid ) ) { 141 $value = rest_sanitize_value_from_schema( $attributes[ $attribute_name ], $schema ); 142 } 143 } 144 145 if ( is_null( $value ) && isset( $schema['default'] ) ) { 146 $value = $schema['default']; 147 } 148 149 $prepared_attributes[ $attribute_name ] = $value; 150 } 151 152 return $prepared_attributes; 153 } 154 155 /** 156 * Sets block type properties. 157 * 158 * @since 0.6.0 159 * 160 * @param array|string $args Array or string of arguments for registering a block type. 161 */ 162 public function set_props( $args ) { 163 $args = wp_parse_args( 164 $args, 165 array( 166 'render_callback' => null, 167 ) 168 ); 169 170 $args['name'] = $this->name; 171 172 foreach ( $args as $property_name => $property_value ) { 173 $this->$property_name = $property_value; 174 } 175 } 176 177 /** 178 * Get all available block attributes including possible layout attribute from Columns block. 179 * 180 * @return array Array of attributes. 181 */ 182 public function get_attributes() { 183 return is_array( $this->attributes ) ? 184 array_merge( 185 $this->attributes, 186 array( 187 'layout' => array( 188 'type' => 'string', 189 ), 190 ) 191 ) : 192 array( 193 'layout' => array( 194 'type' => 'string', 195 ), 196 ); 197 } 198 } -
src/wp-settings.php
diff --git a/src/wp-settings.php b/src/wp-settings.php index 44c8a91a07..ba0f9388d7 100644
a b require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.p 240 240 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' ); 241 241 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-term-meta-fields.php' ); 242 242 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-user-meta-fields.php' ); 243 require( ABSPATH . WPINC . '/class-wp-block-type.php' ); 244 require( ABSPATH . WPINC . '/class-wp-block-type-registry.php' ); 243 245 244 246 $GLOBALS['wp_embed'] = new WP_Embed(); 245 247 -
new file tests/phpunit/tests/blocks/class-block-type-registry-test.php
diff --git a/tests/phpunit/tests/blocks/class-block-type-registry-test.php b/tests/phpunit/tests/blocks/class-block-type-registry-test.php new file mode 100644 index 0000000000..0ba75cd8d0
- + 1 <?php 2 /** 3 * WP_Block_Type_Registry Tests 4 * 5 * @package Gutenberg 6 */ 7 8 /** 9 * Tests for WP_Block_Type_Registry 10 */ 11 class Block_Type_Registry_Test extends WP_UnitTestCase { 12 13 /** 14 * Dummy block type registry. 15 * 16 * @var WP_Block_Type_Registry 17 */ 18 private $registry = null; 19 20 function setUp() { 21 parent::setUp(); 22 23 $this->registry = new WP_Block_Type_Registry(); 24 } 25 26 function tearDown() { 27 parent::tearDown(); 28 29 $this->registry = null; 30 } 31 32 /** 33 * Should reject numbers 34 * 35 * @expectedIncorrectUsage WP_Block_Type_Registry::register 36 */ 37 function test_invalid_non_string_names() { 38 $result = $this->registry->register( 1, array() ); 39 $this->assertFalse( $result ); 40 } 41 42 /** 43 * Should reject blocks without a namespace 44 * 45 * @expectedIncorrectUsage WP_Block_Type_Registry::register 46 */ 47 function test_invalid_names_without_namespace() { 48 $result = $this->registry->register( 'paragraph', array() ); 49 $this->assertFalse( $result ); 50 } 51 52 /** 53 * Should reject blocks with invalid characters 54 * 55 * @expectedIncorrectUsage WP_Block_Type_Registry::register 56 */ 57 function test_invalid_characters() { 58 $result = $this->registry->register( 'still/_doing_it_wrong', array() ); 59 $this->assertFalse( $result ); 60 } 61 62 /** 63 * Should reject blocks with uppercase characters 64 * 65 * @expectedIncorrectUsage WP_Block_Type_Registry::register 66 */ 67 function test_uppercase_characters() { 68 $result = $this->registry->register( 'Core/Paragraph', array() ); 69 $this->assertFalse( $result ); 70 } 71 72 /** 73 * Should accept valid block names 74 */ 75 function test_register_block_type() { 76 $name = 'core/paragraph'; 77 $settings = array( 78 'icon' => 'editor-paragraph', 79 ); 80 81 $block_type = $this->registry->register( $name, $settings ); 82 $this->assertEquals( $name, $block_type->name ); 83 $this->assertEquals( $settings['icon'], $block_type->icon ); 84 $this->assertEquals( $block_type, $this->registry->get_registered( $name ) ); 85 } 86 87 /** 88 * Should fail to re-register the same block 89 * 90 * @expectedIncorrectUsage WP_Block_Type_Registry::register 91 */ 92 function test_register_block_type_twice() { 93 $name = 'core/paragraph'; 94 $settings = array( 95 'icon' => 'editor-paragraph', 96 ); 97 98 $result = $this->registry->register( $name, $settings ); 99 $this->assertNotFalse( $result ); 100 $result = $this->registry->register( $name, $settings ); 101 $this->assertFalse( $result ); 102 } 103 104 /** 105 * Should accept a WP_Block_Type instance 106 */ 107 function test_register_block_type_instance() { 108 $block_type = new WP_Dummy_Block_Type( 'core/dummy' ); 109 110 $result = $this->registry->register( $block_type ); 111 $this->assertSame( $block_type, $result ); 112 } 113 114 /** 115 * Unregistering should fail if a block is not registered 116 * 117 * @expectedIncorrectUsage WP_Block_Type_Registry::unregister 118 */ 119 function test_unregister_not_registered_block() { 120 $result = $this->registry->unregister( 'core/unregistered' ); 121 $this->assertFalse( $result ); 122 } 123 124 /** 125 * Should unregister existing blocks 126 */ 127 function test_unregister_block_type() { 128 $name = 'core/paragraph'; 129 $settings = array( 130 'icon' => 'editor-paragraph', 131 ); 132 133 $this->registry->register( $name, $settings ); 134 $block_type = $this->registry->unregister( $name ); 135 $this->assertEquals( $name, $block_type->name ); 136 $this->assertEquals( $settings['icon'], $block_type->icon ); 137 $this->assertFalse( $this->registry->is_registered( $name ) ); 138 } 139 140 function test_get_all_registered() { 141 $names = array( 'core/paragraph', 'core/image', 'core/blockquote' ); 142 $settings = array( 143 'icon' => 'random', 144 ); 145 146 foreach ( $names as $name ) { 147 $this->registry->register( $name, $settings ); 148 } 149 150 $registered = $this->registry->get_all_registered(); 151 $this->assertEqualSets( $names, array_keys( $registered ) ); 152 } 153 } -
new file tests/phpunit/tests/blocks/class-block-type-test.php
diff --git a/tests/phpunit/tests/blocks/class-block-type-test.php b/tests/phpunit/tests/blocks/class-block-type-test.php new file mode 100644 index 0000000000..cada34b83c
- + 1 <?php 2 /** 3 * WP_Block_Type Tests 4 * 5 * @package Gutenberg 6 */ 7 8 /** 9 * Tests for WP_Block_Type 10 */ 11 class Block_Type_Test extends WP_UnitTestCase { 12 function setUp() { 13 parent::setUp(); 14 } 15 16 /** 17 * Editor user ID. 18 * 19 * @var int 20 */ 21 protected static $editor_user_id; 22 23 /** 24 * ID for a post containing blocks. 25 * 26 * @var int 27 */ 28 protected static $post_with_blocks; 29 30 /** 31 * ID for a post without blocks. 32 * 33 * @var int 34 */ 35 protected static $post_without_blocks; 36 37 /** 38 * Set up before class. 39 */ 40 public static function wpSetUpBeforeClass() { 41 self::$editor_user_id = self::factory()->user->create( 42 array( 43 'role' => 'editor', 44 ) 45 ); 46 47 self::$post_with_blocks = self::factory()->post->create( 48 array( 49 'post_title' => 'Example', 50 'post_content' => "<!-- wp:core/text {\"dropCap\":true} -->\n<p class=\"has-drop-cap\">Tester</p>\n<!-- /wp:core/text -->", 51 ) 52 ); 53 54 self::$post_without_blocks = self::factory()->post->create( 55 array( 56 'post_title' => 'Example', 57 'post_content' => 'Tester', 58 ) 59 ); 60 } 61 62 function test_set_props() { 63 $name = 'core/dummy'; 64 $args = array( 65 'render_callback' => array( $this, 'render_dummy_block' ), 66 'foo' => 'bar', 67 ); 68 69 $block_type = new WP_Block_Type( $name, $args ); 70 71 $this->assertSame( $name, $block_type->name ); 72 $this->assertSame( $args['render_callback'], $block_type->render_callback ); 73 $this->assertSame( $args['foo'], $block_type->foo ); 74 } 75 76 function test_render() { 77 $attributes = array( 78 'foo' => 'bar', 79 'bar' => 'foo', 80 ); 81 82 $block_type = new WP_Block_Type( 83 'core/dummy', 84 array( 85 'render_callback' => array( $this, 'render_dummy_block' ), 86 ) 87 ); 88 $output = $block_type->render( $attributes ); 89 $this->assertEquals( $attributes, json_decode( $output, true ) ); 90 } 91 92 function test_render_with_content() { 93 $attributes = array( 94 'foo' => 'bar', 95 'bar' => 'foo', 96 ); 97 98 $content = 'baz'; 99 100 $expected = array_merge( $attributes, array( '_content' => $content ) ); 101 102 $block_type = new WP_Block_Type( 103 'core/dummy', 104 array( 105 'render_callback' => array( $this, 'render_dummy_block_with_content' ), 106 ) 107 ); 108 $output = $block_type->render( $attributes, $content ); 109 $this->assertEquals( $expected, json_decode( $output, true ) ); 110 } 111 112 function test_render_for_static_block() { 113 $block_type = new WP_Block_Type( 'core/dummy', array() ); 114 $output = $block_type->render(); 115 116 $this->assertEquals( '', $output ); 117 } 118 119 function test_is_dynamic_for_static_block() { 120 $block_type = new WP_Block_Type( 'core/dummy', array() ); 121 122 $this->assertFalse( $block_type->is_dynamic() ); 123 } 124 125 function test_is_dynamic_for_dynamic_block() { 126 $block_type = new WP_Block_Type( 127 'core/dummy', 128 array( 129 'render_callback' => array( $this, 'render_dummy_block' ), 130 ) 131 ); 132 133 $this->assertTrue( $block_type->is_dynamic() ); 134 } 135 136 function test_prepare_attributes() { 137 $attributes = array( 138 'correct' => 'include', 139 'wrongType' => 5, 140 'wrongTypeDefaulted' => 5, 141 /* missingDefaulted */ 142 'undefined' => 'omit', 143 ); 144 145 $block_type = new WP_Block_Type( 146 'core/dummy', 147 array( 148 'attributes' => array( 149 'correct' => array( 150 'type' => 'string', 151 ), 152 'wrongType' => array( 153 'type' => 'string', 154 ), 155 'wrongTypeDefaulted' => array( 156 'type' => 'string', 157 'default' => 'defaulted', 158 ), 159 'missingDefaulted' => array( 160 'type' => 'string', 161 'default' => 'define', 162 ), 163 ), 164 ) 165 ); 166 167 $prepared_attributes = $block_type->prepare_attributes_for_render( $attributes ); 168 169 $this->assertEquals( 170 array( 171 'correct' => 'include', 172 'wrongType' => null, 173 'wrongTypeDefaulted' => 'defaulted', 174 'missingDefaulted' => 'define', 175 ), 176 $prepared_attributes 177 ); 178 } 179 180 function test_has_block_with_mixed_content() { 181 $mixed_post_content = 'before' . 182 '<!-- wp:core/dummy --><!-- /wp:core/dummy -->' . 183 '<!-- wp:core/dummy_atts {"value":"b1"} --><!-- /wp:core/dummy_atts -->' . 184 '<!-- wp:core/dummy-child --> 185 <p>testing the test</p> 186 <!-- /wp:core/dummy-child -->' . 187 'between' . 188 '<!-- wp:core/self-close-dummy /-->' . 189 '<!-- wp:custom/dummy {"value":"b2"} /-->' . 190 'after'; 191 192 $this->assertTrue( has_block( 'core/dummy', $mixed_post_content ) ); 193 194 $this->assertTrue( has_block( 'core/dummy_atts', $mixed_post_content ) ); 195 196 $this->assertTrue( has_block( 'core/dummy-child', $mixed_post_content ) ); 197 198 $this->assertTrue( has_block( 'core/self-close-dummy', $mixed_post_content ) ); 199 200 $this->assertTrue( has_block( 'custom/dummy', $mixed_post_content ) ); 201 202 // checking for a partial block name should fail. 203 $this->assertFalse( has_block( 'core/dumm', $mixed_post_content ) ); 204 205 // checking for a wrong namespace should fail. 206 $this->assertFalse( has_block( 'custom/dummy_atts', $mixed_post_content ) ); 207 208 // checking for namespace only should not work. Or maybe ... ? 209 $this->assertFalse( has_block( 'core', $mixed_post_content ) ); 210 } 211 212 function test_has_block_with_invalid_content() { 213 // some content with invalid HMTL comments and a single valid block. 214 $invalid_content = 'before' . 215 '<!- - wp:core/weird-space --><!-- /wp:core/weird-space -->' . 216 '<!--wp:core/untrimmed-left --><!-- /wp:core/untrimmed -->' . 217 '<!-- wp:core/dummy --><!-- /wp:core/dummy -->' . 218 '<!-- wp:core/untrimmed-right--><!-- /wp:core/untrimmed2 -->' . 219 'after'; 220 221 $this->assertFalse( has_block( 'core/text', self::$post_without_blocks ) ); 222 223 $this->assertFalse( has_block( 'core/weird-space', $invalid_content ) ); 224 225 $this->assertFalse( has_block( 'core/untrimmed-left', $invalid_content ) ); 226 227 $this->assertFalse( has_block( 'core/untrimmed-right', $invalid_content ) ); 228 229 $this->assertTrue( has_block( 'core/dummy', $invalid_content ) ); 230 } 231 232 function test_post_has_block() { 233 // should fail for a non-existent block `custom/dummy`. 234 $this->assertFalse( has_block( 'custom/dummy', self::$post_with_blocks ) ); 235 236 // this functions should not work without the second param until the $post global is set. 237 $this->assertFalse( has_block( 'core/text' ) ); 238 $this->assertFalse( has_block( 'core/dummy' ) ); 239 240 global $post; 241 $post = get_post( self::$post_with_blocks ); 242 243 // check if the function correctly detects content from the $post global. 244 $this->assertTrue( has_block( 'core/text' ) ); 245 // even if it detects a proper $post global it should still be false for a missing block. 246 $this->assertFalse( has_block( 'core/dummy' ) ); 247 } 248 249 function render_dummy_block( $attributes ) { 250 return json_encode( $attributes ); 251 } 252 253 function render_dummy_block_with_content( $attributes, $content ) { 254 $attributes['_content'] = $content; 255 256 return json_encode( $attributes ); 257 } 258 }