## 管理区块分类

### `block_categories_all`

<div class="callout callout-warning">
	在 WordPress 5.8 之前，这个钩子名为 <code>block_categories</code>（现已弃用）。如需支持旧版 WordPress，可能需要检测应使用的过滤器。您可以通过检查 5.8 版本引入的 <code>WP_Block_Editor_Context</code> 类是否存在，来判断 <code>block_categories</code> 是否可安全使用。
</div>

可通过 `block_categories_all` 过滤器对默认区块分类列表进行筛选。您可以在服务器端通过返回分类列表的函数实现此功能，该列表将用于区块注册和在插入器中分组区块。您还可以使用第二个参数 `$editor_context` 根据其内容进行筛选。

```php
// my-plugin.php
function example_filter_block_categories_when_post_provided( $block_categories, $editor_context ) {
	if ( ! empty( $editor_context->post ) ) {
		array_push(
			$block_categories,
			array(
				'slug'  => 'custom-category',
				'title' => __( '自定义分类', 'custom-plugin' ),
				'icon'  => null,
			)
		);
	}
	return $block_categories;
}
add_filter( 'block_categories_all', 'example_filter_block_categories_when_post_provided', 10, 2 );
```

### `wp.blocks.updateCategory`

您还可以通过设置 `icon` 属性为区块分类显示图标。该值可以是 [WordPress Dashicon](https://developer.wordpress.org/resource/dashicons/) 的标识符。

您也可以设置 SVG 格式的自定义图标。为此，图标需要在前端渲染并设置，以便利用 WordPress 的 SVG 功能，确保移动端兼容性并提升图标的可访问性。

要为前例中的分类设置 SVG 图标，请在编辑器中添加以下 JavaScript 代码示例（调用 `wp.blocks.updateCategory`）：

```js
( function () {
	var el = React.createElement;
	var SVG = wp.primitives.SVG;
	var circle = el( 'circle', {
		cx: 10,
		cy: 10,
		r: 10,
		fill: 'red',
		stroke: 'blue',
		strokeWidth: '10',
	} );
	var svgIcon = el(
		SVG,
		{ width: 20, height: 20, viewBox: '0 0 20 20' },
		circle
	);
	wp.blocks.updateCategory( 'my-category', { icon: svgIcon } );
} )();
```

# 区块过滤器

WordPress 提供了多个 API，允许您修改现有区块的行为。

## 注册机制

WordPress 中的区块通常通过服务端和客户端使用 `block.json` 元数据完成注册。您可以使用以下过滤器在 PHP 服务端注册和 JavaScript 客户端注册期间修改或扩展区块设置。了解更多信息，请参阅[区块注册指南](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/)。

### `block_type_metadata`

在通过 PHP 服务端注册区块类型时，过滤从 `block.json` 文件加载的原始元数据。该过滤器允许在元数据处理前进行修改。

此过滤器的回调函数接收一个参数：

- `$metadata` (`array`): 从 `block.json` 加载的用于注册区块类型的元数据。

以下示例将所有区块的 `apiVersion` 设置为 `2`：

```php
function example_filter_metadata_registration( $metadata ) {
	$metadata['apiVersion'] = 2;
	return $metadata;
};
add_filter( 'block_type_metadata', 'example_filter_metadata_registration' );
```

下面是一个更复杂的示例，用于禁用标题区块的背景色和渐变支持。`block_type_metadata` 过滤器非常适合用于[优化编辑器体验](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/)：

```php
function example_disable_heading_background_color_and_gradients( $metadata ) {
	
	// 仅对标题区块应用此过滤器
	if ( ! isset( $metadata['name'] ) || 'core/heading' !== $metadata['name'] ) {
		return $metadata;
	}

	// 检查是否存在 supports 键
	if ( isset( $metadata['supports'] ) && isset( $metadata['supports']['color'] ) ) {
		
		// 移除背景色和渐变支持
		$metadata['supports']['color']['background'] = false;
		$metadata['supports']['color']['gradients']  = false;
	}

	return $metadata;
}
add_filter( 'block_type_metadata', 'example_disable_heading_background_color_and_gradients' );
```

### `block_type_metadata_settings`

过滤根据已处理的区块类型元数据确定的设置。通过该过滤器可以应用默认处理流程未覆盖的区块元数据自定义修改。

此过滤器的回调函数接收两个参数：

-   `$settings` (`array`): 注册区块类型时确定的设置数组
-   `$metadata` (`array`): 从 `block.json` 文件加载的元数据

以下示例将所有区块的 `apiVersion` 数值增加 `1`：

```php
function example_filter_metadata_registration( $settings, $metadata ) {
	$settings['api_version'] = $metadata['apiVersion'] + 1;
	return $settings;
};
add_filter( 'block_type_metadata_settings', 'example_filter_metadata_registration', 10, 2 );
```

### `register_block_type_args`

在区块类型正式于服务端注册之前，过滤区块的参数数组 (`$args`)。

此过滤器的回调函数接收两个参数：

- `$args` (`array`): 注册区块类型的参数数组
- `$block_type` (`string`): 包含命名空间的区块类型名称

`register_block_type_args` 是可用粒度最细的 PHP 过滤器，对服务端注册的所有区块均有效。服务端定义的所有设置都会以高于客户端设置的优先级传播到客户端。

以下代码将禁用段落、标题、列表和列表项区块的颜色控制功能：

```php
function example_disable_color_for_specific_blocks( $args, $block_type ) {

	// 需要修改的区块类型列表
	$block_types_to_modify = [
		'core/paragraph',
		'core/heading',
		'core/list',
		'core/list-item'
	];

	// 检查当前区块类型是否在列表中
	if ( in_array( $block_type, $block_types_to_modify, true ) ) {
		// 禁用颜色控制
		$args['supports']['color'] = array(
			'text'       => false,
			'background' => false,
			'link'       => false,
		);
	}

	return $args;
}
add_filter( 'register_block_type_args', 'example_disable_color_for_specific_blocks', 10, 2 );
```

### `blocks.registerBlockType`

用于在使用 JavaScript 在客户端注册区块时过滤区块设置。该过滤器接收区块设置、已注册区块的名称，以及 null 或已弃用的区块设置（当应用于已注册的弃用情况时）作为参数。此过滤器也会应用于区块的每个弃用设置。

以下示例确保列表区块保存时使用规范的生成类名 (`wp-block-list`)：

```js
function addListBlockClassName( settings, name ) {
	if ( name !== 'core/list' ) {
		return settings;
	}

	return {
		...settings,
		supports: {
			...settings.supports,
			className: true,
		},
	};
}

wp.hooks.addFilter(
	'blocks.registerBlockType',
	'my-plugin/class-names/list-block',
	addListBlockClassName
);
```

## 前端

以下 PHP 过滤器可用于更改区块在前端的输出。

### `render_block`

过滤任何区块的前端内容。此过滤器不会影响区块在编辑器中的行为。

该过滤器的回调函数接收三个参数：

- `$block_content` (`string`)：区块内容。
- `$block` (`array`)：完整的区块，包括名称和属性。
- `$instance` (`WP_Block`)：区块实例。

以下示例在前端为所有段落区块添加 `example-class` 类。此处使用 [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) 来轻松添加类，而不依赖正则表达式。

```php
function example_add_custom_class_to_paragraph_block( $block_content, $block ) {
	
	// 检查区块是否为段落区块。
	if ( 'core/paragraph' === $block['blockName'] ) {
	   
		// 使用 HTML API 向区块内容添加自定义类。
		$processor = new WP_HTML_Tag_Processor( $block_content );
		
		if ( $processor->next_tag( 'p' ) ) {
			$processor->add_class( 'example-class' );
		}

		return $processor->get_updated_html();
	}

	return $block_content;
}
add_filter( 'render_block', 'example_add_custom_class_to_paragraph_block', 10, 2 );
```

### `render_block_{namespace/block}`

过滤指定区块的前端内容。这是 `render_block` 的简化形式，适用于仅需修改特定区块类型的情况。

该过滤器的回调函数接收三个参数：

- `$block_content` (`string`)：区块内容。
- `$block` (`array`)：完整的区块，包括名称和属性。
- `$instance` (`WP_Block`)：区块实例。

以下示例在前端为所有段落区块添加 `example-class` 类。与上述 `render_block` 示例相比，此处无需在修改内容前检查区块类型。同样使用 [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) 替代正则表达式。

```php
function example_add_custom_class_to_paragraph_block( $block_content, $block ) {
	   
	// 使用 HTML API 向区块内容添加自定义类。
	$processor = new WP_HTML_Tag_Processor( $block_content );
	
	if ( $processor->next_tag( 'p' ) ) {
		$processor->add_class( 'example-class' );
	}

	return $processor->get_updated_html();
}
add_filter( 'render_block_core/paragraph', 'example_add_custom_class_to_paragraph_block', 10, 2 );
```

## 编辑器

以下 JavaScript 过滤器可用于在编辑器中更改区块的行为。

### `blocks.getSaveElement`

一个应用于区块 `save` 函数结果的过滤器。此过滤器用于替换或扩展元素，例如使用 `React.cloneElement` 修改元素的属性、替换其子元素或返回一个全新的元素。

该过滤器的回调函数接收三个参数：

- `element` (`Object`)：待修改并返回的元素。
- `blockType` (`Object`)：区块类型定义对象。
- `attributes` (`Object`)：区块的属性。

以下示例将封面区块包装在一个外部容器 `div` 中。

```js
function wrapCoverBlockInContainer( element, blockType, attributes ) {
	
	// 如果元素未定义，跳过处理。
	if ( ! element ) {
		return;
	}

	// 仅应用于封面区块。
	if ( blockType.name !== 'core/cover' ) {
		return element;
	}

	// 返回包装在 div 中的元素。
	return <div className="cover-block-wrapper">{ element }</div>;
}

wp.hooks.addFilter(
	'blocks.getSaveElement',
	'my-plugin/wrap-cover-block-in-container',
	wrapCoverBlockInContainer
);
```

### `blocks.getSaveContent.extraProps`

这是一个应用于所有在 `save` 函数中返回 WP 元素的区块过滤器。该过滤器用于向 `save` 函数的根元素添加额外属性。例如，您可以添加 className、id 或该元素任何有效的属性。

此过滤器的回调函数接收三个参数：

- `props`（`Object`）：当前 `save` 元素的属性，将被修改并返回。
- `blockType`（`Object`）：区块类型定义对象。
- `attributes`（`Object`）：区块的属性。

以下示例为所有区块默认添加红色背景：

```js
function addBackgroundColorStyle( props ) {
	return {
		...props,
		style: { backgroundColor: 'red' },
	};
}

wp.hooks.addFilter(
	'blocks.getSaveContent.extraProps',
	'my-plugin/add-background-color-style',
	addBackgroundColorStyle
);
```

**注意：** 如果此过滤器在下次编辑文章时修改了现有内容，将会出现[区块验证](/docs/reference-guides/block-api/block-edit-save.md#validation)错误。编辑器会验证存储在文章中的内容是否与 `save()` 函数输出的内容匹配。

为避免此验证错误，请使用服务器端的 `render_block` 来修改现有文章内容，而不是使用此过滤器。请参阅 [render_block 文档](https://developer.wordpress.org/reference/hooks/render_block/)。

### `blocks.getBlockDefaultClassName`

为区块生成的 HTML 类遵循 `wp-block-{name}` 命名规则。此过滤器允许提供替代的类名。

```js
// 我们的过滤器函数。
function setBlockCustomClassName( className, blockName ) {
	return blockName === 'core/code' ? 'my-plugin-code' : className;
}

// 添加过滤器。
wp.hooks.addFilter(
	'blocks.getBlockDefaultClassName',
	'my-plugin/set-block-custom-class-name',
	setBlockCustomClassName
);
```

### `blocks.switchToBlockType.transformedBlock`

用于过滤区块转换中的单个转换结果。由于转换是多对多而非一对一的，因此所有原始区块都会被传递。

### `blocks.getBlockAttributes`

在区块属性默认解析之后、验证之前调用，允许插件及时操作属性值，以便进行验证和/或在编辑器中渲染区块的初始值。

此过滤器的回调函数接受 4 个参数：
- `blockAttributes`（`Object`）：所有区块属性。
- `blockType`（`Object`）：区块类型。
- `innerHTML`（`string`）：原始区块内容。
- `attributes`（`object`）：已知的区块属性（来自分隔符）。

在以下示例中，我们使用 `blocks.getBlockAttributes` 过滤器锁定页面中所有段落区块的位置。

```js
// 我们的过滤器函数
function lockParagraphs( blockAttributes, blockType, innerHTML, attributes  ) {
    if('core/paragraph' === blockType.name) {
        blockAttributes['lock'] = {move: true}
    }
    return blockAttributes;
}

// 添加过滤器
wp.hooks.addFilter(
    'blocks.getBlockAttributes',
    'my-plugin/lock-paragraphs',
    lockParagraphs
);
```

### `editor.BlockEdit`

用于修改区块的 `edit` 组件。它接收原始的区块 `BlockEdit` 组件并返回一个新的包装组件。

以下示例为所有区块添加一个新的检查器面板。

```js
const { createHigherOrderComponent } = wp.compose;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;

const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => {
	return ( props ) => {
		return (
			<>
				<BlockEdit key="edit" { ...props } />
				<InspectorControls>
					<PanelBody>我的自定义控件</PanelBody>
				</InspectorControls>
			</>
		);
	};
}, 'withMyPluginControls' );

wp.hooks.addFilter(
	'editor.BlockEdit',
	'my-plugin/with-inspector-controls',
	withMyPluginControls
);
```

请注意，由于此钩子对**所有区块**运行，使用它可能会导致性能下降，尤其是在区块选择指标方面。

为了缓解此问题，请考虑是否可以将您执行的任何工作调整为仅在特定条件下运行。

例如，假设您添加的组件仅在区块**被选中时**需要渲染。在这种情况下，您可以使用区块的“选中”状态（`props.isSelected`）来条件化您的渲染。

以下示例为所有区块添加一个新的检查器面板，但仅在区块被选中时显示。

```js
const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => {
	return ( props ) => {
		return (
			<>
				<BlockEdit { ...props } />
				{ props.isSelected && (
					<InspectorControls>
						<PanelBody>我的自定义控件</PanelBody>
					</InspectorControls>
				) }
			</>
		);
	};
}, 'withMyPluginControls' );
```

### `editor.BlockListBlock`

用于修改包含块编辑组件及所有工具栏的块包装组件。该钩子接收原始的 `BlockListBlock` 组件并返回新的包装组件。

以下示例为所有块添加唯一类名：

```js
const { createHigherOrderComponent } = wp.compose;

const withClientIdClassName = createHigherOrderComponent(
	( BlockListBlock ) => {
		return ( props ) => {
			return (
				<BlockListBlock
					{ ...props }
					className={ 'block-' + props.clientId }
				/>
			);
		};
	},
	'withClientIdClassName'
);

wp.hooks.addFilter(
	'editor.BlockListBlock',
	'my-plugin/with-client-id-class-name',
	withClientIdClassName
);
```

可通过返回组件的 `wrapperProps` 属性为块包装组件添加新属性，如下例所示：

```js
const { createHigherOrderComponent } = wp.compose;
const withMyWrapperProp = createHigherOrderComponent( ( BlockListBlock ) => {
	return ( props ) => {
		const wrapperProps = {
			...props.wrapperProps,
			'data-my-property': 'the-value',
		};
		return <BlockListBlock { ...props } wrapperProps={ wrapperProps } />;
	};
}, 'withMyWrapperProp' );

wp.hooks.addFilter(
	'editor.BlockListBlock',
	'my-plugin/with-my-wrapper-prop',
	withMyWrapperProp
);
```

### `editor.postContentBlockTypes`

用于修改即使在锁定模板内使用也应启用的块列表。所有将数据保存到文章的块都应添加至此。文章特色图片块即为一例：该块虽常用于模板，但在模板锁定时仍应允许选择图片。

以下示例启用虚构块 `namespace/example`：

```js
const addExampleBlockToPostContentBlockTypes = ( blockTypes ) => {
	return [ ...blockTypes, 'namespace/example' ];
};

wp.hooks.addFilter(
	'editor.postContentBlockTypes',
	'my-plugin/post-content-block-types',
	addExampleBlockToPostContentBlockTypes
);
```

## 移除区块

### 使用禁用列表

添加区块十分简单，移除区块同样便捷。插件或主题开发者可通过 JavaScript 禁用列表来“取消注册”区块。

将以下代码置于 `my-plugin.js` 文件中：

```js
// my-plugin.js
import { unregisterBlockType } from '@wordpress/blocks';
import domReady from '@wordpress/dom-ready';

domReady( function () {
	unregisterBlockType( 'core/verse' );
} );
```

随后通过以下函数在编辑器中加载此脚本：

```php
<?php
// my-plugin.php

function my_plugin_deny_list_blocks() {
	wp_enqueue_script(
		'my-plugin-deny-list-blocks',
		plugins_url( 'my-plugin.js', __FILE__ ),
		array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' )
	);
}
add_action( 'enqueue_block_editor_assets', 'my_plugin_deny_list_blocks' );
```

<div class="callout callout-warning">
	取消注册区块时，可能存在<a href="https://en.wikipedia.org/wiki/Race_condition">竞态条件</a>：即注册区块与取消注册区块的代码执行顺序冲突。为确保取消注册代码最后执行，需将注册区块的组件指定为依赖项（本例中为 <code>wp-edit-post</code>）。此外，使用 <code>wp.domReady()</code> 可确保取消注册代码在 DOM 加载完成后执行。
</div>

### 使用允许列表

若需仅保留允许列表中的区块，可调整上述脚本如下：

```js
// my-plugin.js

var allowedBlocks = [
	'core/paragraph',
	'core/image',
	'core/html',
	'core/freeform',
];

wp.blocks.getBlockTypes().forEach( function ( blockType ) {
	if ( allowedBlocks.indexOf( blockType.name ) === -1 ) {
		wp.blocks.unregisterBlockType( blockType.name );
	}
} );
```

## 从插入器中隐藏区块

### `allowed_block_types_all`

<div class="callout callout-warning">
	在 WordPress 5.8 之前，此钩子名为 <code>allowed_block_types</code>（现已弃用）。如需支持旧版 WordPress，可能需要通过检测 <code>WP_Block_Editor_Context</code> 类（5.8 版本引入）是否存在来判断应使用的过滤器。
</div>

在服务器端，可通过 `allowed_block_types_all` 过滤器筛选插入器中显示的区块列表。可返回 true（支持所有区块类型）、false（不支持任何区块类型）或允许的区块类型名称数组。还可使用第二个参数 `$editor_context` 根据内容筛选区块类型。

```php
<?php
// my-plugin.php
function example_filter_allowed_block_types_when_post_provided( $allowed_block_types, $editor_context ) {
	if ( ! empty( $editor_context->post ) ) {
		return array( 'core/paragraph', 'core/heading' );
	}
	return $allowed_block_types;
}
add_filter( 'allowed_block_types_all', 'example_filter_allowed_block_types_when_post_provided', 10, 2 );
```