大家好,我是老码农,今天咱们聊聊 WordPress 里一个非常关键的钩子:save_post
,特别是它在古腾堡编辑器(Gutenberg)中是如何玩转区块数据的保存流程的。这玩意儿,说白了,就是把你在编辑器里辛辛苦苦堆砌的各种区块,变成数据库里能识别、能还原的数据。
咱们先从最基础的概念入手,然后一步步深入源码,最后再来点实际案例,让大家彻底明白这其中的奥秘。准备好了吗?Let’s roll!
第一幕:钩子是个啥?为啥要用它?
想象一下,WordPress 是一个大舞台,各种事件都在这里上演。save_post
钩子就像一个侦听器,它时刻监听着“文章要保存了!”这个事件。一旦这个事件发生,它就会触发你预先注册好的函数,让你有机会在文章真正保存到数据库之前,做一些手脚,比如修改文章内容、更新自定义字段等等。
为什么要用钩子呢?因为 WordPress 本身的代码已经写死了,但我们总有各种各样的定制需求。钩子就提供了一个“官方”的扩展点,让我们可以在不修改 WordPress 核心代码的前提下,实现各种各样的功能。这就像盖房子,主体结构已经完成了,但你可以通过预留的接口,往里面添加各种家具、电器,让房子更符合你的需求。
第二幕:save_post
钩子的基本用法
save_post
钩子接受两个参数:
$post_id
: 要保存的文章的 ID。$post
:WP_Post
对象,包含了文章的所有信息。
使用 add_action()
函数可以注册你的自定义函数,让它在 save_post
事件发生时被调用。
add_action( 'save_post', 'my_custom_save_post' );
function my_custom_save_post( $post_id, $post ) {
// 检查是否是自动保存
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// 检查用户是否有权限编辑文章
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 检查文章类型是否是 post
if ( 'post' !== $post->post_type ) {
return;
}
// 这里写你的自定义逻辑,比如更新自定义字段
update_post_meta( $post_id, '_my_custom_field', $_POST['my_custom_field'] );
}
这段代码就是一个最基本的 save_post
钩子的用法。它做了以下几件事:
- 注册了一个名为
my_custom_save_post
的函数,让它在save_post
事件发生时被调用。 - 在函数内部,首先进行了一系列安全检查,防止恶意代码的执行。
- 最后,更新了一个名为
_my_custom_field
的自定义字段,它的值来自于$_POST
数组。
第三幕:古腾堡区块数据的存储方式
古腾堡编辑器将文章内容分解为一个个独立的区块,每个区块都有自己的属性和内容。这些区块数据最终会被存储到文章的 post_content
字段中,以特殊的格式进行标记。
具体来说,古腾堡使用了一种名为“序列化区块”的格式来存储区块数据。这种格式使用 HTML 注释来标记区块的开始和结束,并在注释中包含区块的类型和属性。
一个典型的序列化区块的例子如下:
<!-- wp:paragraph -->
<p>这是一个段落区块。</p>
<!-- /wp:paragraph -->
<!-- wp:image {"id":123,"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="image.jpg" alt="" class="wp-image-123"/></figure>
<!-- /wp:image -->
可以看到,每个区块都用 <!-- wp:block-name -->
和 <!-- /wp:block-name -->
包裹,其中 block-name
是区块的名称。有些区块还会在起始注释中包含 JSON 格式的属性,比如上面的 image
区块。
第四幕:save_post
如何处理区块数据?源码解读
现在,我们终于要进入正题了,看看 save_post
钩子是如何处理这些区块数据的。
WordPress 核心代码中,负责处理 save_post
钩子的函数位于 wp-includes/post.php
文件中,名为 wp_insert_post()
。这个函数非常庞大,包含了文章保存的所有逻辑,包括处理区块数据。
为了简化分析,我们只关注与区块数据相关的部分。
-
wp_insert_post()
函数接收到文章数据后,会首先对post_content
字段进行处理。这个处理过程包括:
- 解析序列化区块: 将
post_content
中的序列化区块解析成一个 PHP 数组,方便后续处理。 - 过滤区块内容: 对每个区块的内容进行过滤,防止 XSS 攻击和其他安全问题。
- 更新自定义字段: 有些区块会将数据存储到自定义字段中,
wp_insert_post()
函数会负责更新这些自定义字段。 - 重新序列化区块: 经过一系列处理后,
wp_insert_post()
函数会将区块数据重新序列化成 HTML 注释,并更新post_content
字段。
- 解析序列化区块: 将
-
parse_blocks()
函数:解析序列化区块parse_blocks()
函数位于wp-includes/blocks.php
文件中,是负责解析序列化区块的核心函数。它会将
post_content
中的字符串解析成一个区块数组,每个数组元素代表一个区块,包含了区块的名称、属性和内容。function parse_blocks( $content ) { if ( ! is_string( $content ) ) { return array(); } $blocks = array(); $parser = new WP_Block_Parser(); $results = $parser->parse( $content ); if ( ! $results ) { return array( array( 'blockName' => null, 'attrs' => array(), 'innerBlocks' => array(), 'innerHTML' => $content, 'innerContent' => array( $content ), ) ); } foreach ( $results as $result ) { $blocks[] = $result->to_array(); } return $blocks; }
这个函数首先创建了一个
WP_Block_Parser
对象,然后调用parse()
方法来解析post_content
。WP_Block_Parser
类负责识别 HTML 注释中的区块标记,并提取区块的名称和属性。 -
serialize_block()
函数:重新序列化区块与
parse_blocks()
函数相反,serialize_block()
函数负责将区块数组重新序列化成 HTML 注释。这个函数会将区块的名称和属性转换成 JSON 格式,并插入到 HTML 注释中。
function serialize_block( $block ) { $block_name = isset( $block['blockName'] ) ? $block['blockName'] : null; $attrs = isset( $block['attrs'] ) ? $block['attrs'] : array(); $inner_html = isset( $block['innerHTML'] ) ? $block['innerHTML'] : ''; $output = ''; if ( $block_name ) { $output .= "<!-- wp:" . $block_name . " " . wp_json_encode( $attrs ) . " -->n"; $output .= $inner_html . "n"; $output .= "<!-- /wp:" . $block_name . " -->n"; } else { $output .= $inner_html . "n"; } return $output; }
这个函数会根据区块的名称和属性,生成相应的 HTML 注释,并将区块的内容插入到注释之间。
第五幕:自定义区块与 save_post
如果你开发了自己的自定义区块,那么你需要使用 save_post
钩子来处理这些区块的数据。
通常情况下,你需要做以下几件事:
- 注册自定义字段: 如果你的区块需要存储一些额外的数据,你需要注册一些自定义字段来存储这些数据。
- 编写
save
函数: 在你的区块定义中,你需要编写一个save
函数,这个函数会在文章保存时被调用。 - 使用
save_post
钩子更新自定义字段: 在save_post
钩子的回调函数中,你需要读取区块的数据,并将它们存储到自定义字段中。
举个例子,假设你开发了一个名为 my-plugin/custom-block
的自定义区块,它有一个名为 my_custom_text
的属性,你想将这个属性的值存储到一个名为 _my_custom_block_text
的自定义字段中。
首先,你需要注册这个自定义字段:
function my_plugin_register_meta() {
register_post_meta( 'post', '_my_custom_block_text', array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
) );
}
add_action( 'init', 'my_plugin_register_meta' );
然后,在你的区块定义中,你需要编写 save
函数:
// JavaScript (React) 代码
const { registerBlockType } = wp.blocks;
const { TextControl } = wp.components;
registerBlockType( 'my-plugin/custom-block', {
title: 'Custom Block',
icon: 'smiley',
category: 'common',
attributes: {
my_custom_text: {
type: 'string',
default: '',
},
},
edit: ( { attributes, setAttributes } ) => {
const { my_custom_text } = attributes;
return (
<TextControl
label="Custom Text"
value={ my_custom_text }
onChange={ ( newText ) => setAttributes( { my_custom_text: newText } ) }
/>
);
},
save: ( { attributes } ) => {
// 注意这里只是返回一个占位符,实际数据存储在 save_post 钩子中处理
return null;
},
} );
注意,save
函数返回 null
。这是因为我们并不想直接在 post_content
中存储数据,而是想将数据存储到自定义字段中。
最后,你需要使用 save_post
钩子来更新自定义字段:
add_action( 'save_post', 'my_plugin_save_post' );
function my_plugin_save_post( $post_id, $post ) {
// 检查是否是自动保存
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// 检查用户是否有权限编辑文章
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 获取文章内容
$content = $post->post_content;
// 解析区块
$blocks = parse_blocks( $content );
// 遍历区块,找到我们的自定义区块
foreach ( $blocks as $block ) {
if ( $block['blockName'] === 'my-plugin/custom-block' ) {
// 获取自定义属性的值
$custom_text = isset( $block['attrs']['my_custom_text'] ) ? $block['attrs']['my_custom_text'] : '';
// 更新自定义字段
update_post_meta( $post_id, '_my_custom_block_text', $custom_text );
}
}
}
这段代码会解析文章内容,找到 my-plugin/custom-block
区块,获取 my_custom_text
属性的值,并将它存储到 _my_custom_block_text
自定义字段中。
第六幕:一些需要注意的点
- 安全第一: 在
save_post
钩子的回调函数中,一定要进行安全检查,防止恶意代码的执行。 - 性能优化:
save_post
钩子会在每次文章保存时被调用,所以你的代码一定要高效,避免影响网站的性能。 - 版本兼容性: WordPress 的 API 会不断更新,所以你的代码要保持版本兼容性,避免在未来的版本中出现问题。
- 调试技巧: 使用
error_log()
函数可以方便地调试save_post
钩子的回调函数。
第七幕:总结与展望
save_post
钩子是 WordPress 中一个非常强大的工具,它可以让你在文章保存时进行各种各样的操作。在古腾堡编辑器中,save_post
钩子更是扮演着至关重要的角色,它负责处理区块数据的存储和更新。
通过深入理解 save_post
钩子的原理和用法,你可以更好地开发自定义区块,并实现各种各样的定制功能。
希望今天的讲座对大家有所帮助。如果有什么问题,欢迎在评论区留言。下次再见!
表格:save_post
钩子相关函数
函数名 | 作用 | 文件路径 |
---|---|---|
add_action() |
注册一个函数,让它在指定的动作(钩子)发生时被调用。 | wp-includes/plugin.php |
wp_insert_post() |
核心函数,负责将文章数据插入或更新到数据库。 | wp-includes/post.php |
parse_blocks() |
将包含序列化区块的字符串解析成一个区块数组。 | wp-includes/blocks.php |
serialize_block() |
将一个区块数组重新序列化成 HTML 注释。 | wp-includes/blocks.php |
register_post_meta() |
注册文章的自定义字段,允许你存储与文章相关的额外数据。 确保设置 show_in_rest 为 true ,以便在古腾堡编辑器中使用 REST API 访问自定义字段。 |
wp-includes/meta.php |
update_post_meta() |
更新指定文章的自定义字段的值。 | wp-includes/meta.php |
get_post_meta() |
获取指定文章的自定义字段的值。 | wp-includes/meta.php |
current_user_can() |
检查当前用户是否具有执行指定操作的权限。这是一个重要的安全措施,可以防止未经授权的用户修改文章数据。 | wp-includes/capabilities.php |
defined('DOING_AUTOSAVE') && DOING_AUTOSAVE |
用于检查当前是否是自动保存操作。 如果是自动保存,通常应该跳过一些比较耗时的操作,以避免影响编辑器的性能。 | N/A |