save_post
云策文档标注
概述
save_post 是 WordPress 中一个核心的 Action Hook,在文章或页面被创建或更新时触发。它允许开发者在保存后执行自定义逻辑,如修改元数据、发送通知或避免无限循环。
关键要点
- save_post 在文章保存后立即触发,适用于创建或更新操作,可通过 get_post($post_id) 访问文章对象。
- 参数包括 $post_id(文章 ID)、$post(文章对象)和 $update(是否为更新操作)。
- 从 WordPress 3.7 开始,引入了 save_post_{post_type} 钩子,用于特定文章类型,以减少不必要的回调触发。
- 使用 wp_update_post 等函数时需注意避免无限循环,可通过临时移除钩子或使用全局变量快照来解决。
- 用户贡献笔记提供了多个代码示例,包括处理修订、特定文章类型、元数据更新和术语同步等场景。
代码示例
/**
* 使默认分类中的所有文章变为私有。
*
* @see 'save_post'
*
* @param int $post_id 正在保存的文章 ID。
*/
function set_private_categories( $post_id ) {
// 如果是修订版,获取真实文章 ID。
$parent_id = wp_is_post_revision( $post_id );
if ( false !== $parent_id ) {
$post_id = $parent_id;
}
// 从选项中获取默认分类 ID。
$defaultcat = get_option( 'default_category' );
// 检查此文章是否在默认分类中。
if ( in_category( $defaultcat, $post_id ) ) {
// 移除此函数以避免无限循环
remove_action( 'save_post', 'set_private_categories' );
// 更新文章,这会再次调用 save_post。
wp_update_post( array( 'ID' => $post_id, 'post_status' => 'private' ) );
// 重新添加此函数。
add_action( 'save_post', 'set_private_categories' );
}
}
add_action( 'save_post', 'set_private_categories' );注意事项
- save_post_{post_type} 在 save_post 之前触发,可能被插件覆盖,需根据需求选择钩子。
- 更新术语时,save_post 可能在术语保存到数据库前触发,建议使用 rest_after_insert_{post_type} 确保数据准确性。
- 避免在回调中直接调用触发 save_post 的函数,以防止无限循环。
原文内容
Fires once a post has been saved.
Parameters
$post_idint-
Post ID.
$postWP_Post-
Post object.
$updatebool-
Whether this is an existing post being updated.
Source
do_action( 'save_post', $post_id, $post, $update );
Changelog
| Version | Description |
|---|---|
| 1.5.0 | Introduced. |
Skip to note 11 content
Jason Adams
When using WordPress 3.7 or later, it’s a good idea to use the save_post_{$post->post_type} hook when it makes sense to in order to reduce code and fire less hooks overall when posts are created and updated.
Documentation can be found here: https://developer.wordpress.org/reference/hooks/save_post_post-post_type/
Skip to note 12 content
camparry
The save_post_{post_type} hook fires BEFORE the general save_post hook, meaning that save_post will override any meta updates made with save_post_{post_type}. Many plugins like ACF and Pods use the save post action hook, so if you are trying to update a meta field and you are using one of these plugins, you must use the save_post hook instead.
// does not work function my_save_meta_function( $post_id, $post, $update ) { // fires but can be overridden by plugins, regardless of priority number update_post_meta( $post_id, 'address', '123 Test St' ); } add_action( 'save_post_event', 'my_save_meta_function', 99, 3 ); // does work function my_save_meta_function( $post_id, $post, $update ) { if ( get_post_type( $post_id ) !== 'event' ) return; update_post_meta( $post_id, 'address', '123 Test St' ); } add_action( 'save_post', 'my_save_meta_function', 99, 3 );Skip to note 13 content
Aurovrata Venet
Force a new post of have specific category term,
add_action( 'save_post', 'set_post_default_category', 10,3 ); function set_post_default_category( $post_id, $post, $update ) { // Only want to set if this is a new post! if ( $update ){ return; } // Only set for post_type = post! if ( 'post' !== $post->post_type ) { return; } // Get the default term using the slug, its more portable! $term = get_term_by( 'slug', 'my-custom-term', 'category' ); wp_set_post_terms( $post_id, $term->term_id, 'category', true ); }Skip to note 14 content
leogermani
Below is a basic example that will send an email every time a post or page is updated on your website.
function my_project_updated_send_email( $post_id ) { // If this is just a revision, don't send the email. if ( wp_is_post_revision( $post_id ) ) { return; } $post_title = get_the_title( $post_id ); $post_url = get_permalink( $post_id ); $subject = 'A post has been updated'; $message = "A post has been updated on your website:nn"; $message .= $post_title . ": " . $post_url; // Send email to admin. wp_mail( 'admin@example.com', $subject, $message ); } add_action( 'save_post', 'my_project_updated_send_email' );Skip to note 15 content
Gerard Reches
The documentation provides a way to avoid an infinite loop by removing the hook and then adding it again after we have used a function such as `wp_update_post`.
The problem about this is that other actions on the same hook may still trigger twice.
I came up with the following alternative:
/** * Prevent infinite loop. */ //remove_action( 'save_post', array( self::class, 'save_meta_boxes' ) ); global $wp_actions, $wp_filters, $wp_filter; $actions = $wp_actions; $filters = $wp_filters; $filter = $wp_filter; remove_all_actions( 'save_post' ); wp_update_post( $post ); $wp_actions = $actions; $wp_filters = $filters; $wp_filter = $filter; //add_action( 'save_post', array( self::class, 'save_meta_boxes' ) );By doing this we take a “snapshot” of all the registered hooks before we update the post. Then we remove all the actions for the hook, and once it has been updated, we restore the snapshots.
You should also think about removing all `save_post_{$post->post-type}` actions and viceversa.
It would be nice if there was a core function for this. `wp_update_post` already has a third optional parameter to prevent firing the after insert hooks. A fourth parameter could be added to prevent firing the `save_post` and `save_post_{$post->post-type}` hooks so that we don’t have to use workarounds to prevent the infinite loop issue.
Skip to note 16 content
Muhammad Ali Akbar
To trigger for specific post type, assume we have a post type name ‘book’
function wpdocs_book_meta( $post_id ) { // Check the logged in user has permission to edit this post if ( ! current_user_can( 'manage_options' ) ) { return $post_id; } if ( isset( $_POST['website'] ) ) { $website = esc_url_raw( $_POST['website'] ); update_post_meta( $post_id, 'website', $website ); } } add_action( 'save_post_book', 'wpdocs_book_meta' );Skip to note 17 content
Arun Basil Lal
The
$post_IDpassed to the action is the ID of the revision while updating a post. To find the ID of the parent post, usewp_get_post_parent_id.function my_function_on_save_post( $post_id ) { // Find parent post_id. if ( $post_parent_id = wp_get_post_parent_id( $post_id ) ) { $post_id = $post_parent_id; } // Do something. } add_action( 'save_post', 'my_function_on_save_post' );Skip to note 18 content
cfoster
I was trying to add a hook to review the current posts and terms in the database whenever a post was updated (through the regular editing interface, not direct calls to the API) but hit a problem.
If a user updates only a post’s Terms (categories, tags),
save_postis triggered before the new Terms are pushed to the database. (If the Term changes are saved with any other changes, the Terms are written to the DB beforesave_postis triggered.)Details:
wp_insert_post()does look like it saves Terms before triggering thesave_postaction but the Term data it passes to (ultimately)wp_set_object_terms()is the OLD terms for the post. (Why resave the old data? Dunno.)After
wp_insert_post()triggerssave_post, an additional call towp_set_object_terms()is made (fromWP_REST_Posts_Controller) that has a the new Terms data in it. (I didn’t dig further to find out why the two calls for this case.)Workaround:
If your hook needs the Terms to be accurate, attach it to
rest_after_insert_(post|page|attachment)instead. This is triggered when ALL changes are stored, regardless of whether it is just the post content, just the terms, or both.Skip to note 19 content
Hitesh Patel
Here is my updated code which will check whether post title is exist or not before inserting into news post title.
function wpdocs_save_post_callback( $post_id ) { $post = get_post( $post_id ); if ( $post && 'post' === $post->post_type && isset( $_POST['post_title'] ) ) { $post_title = sanitize_text_field( $_POST['post_title'] ); // Check if a post with the same title already exists in the "news" post type $existing_post = get_page_by_title( $post_title, OBJECT, 'news' ); if ( ! $existing_post ) { // If no post with the same title exists $post_data = array( 'post_type' => 'news', 'post_status' => 'publish', 'post_title' => $post_title ); wp_insert_post( $post_data ); } else { // Post with the same title already exists // You can handle this case according to your requirement, e.g., display a message or update the existing post } } } add_action( 'save_post', 'wpdocs_save_post_callback' );Skip to note 20 content
Hitesh Patel
Here is a simple example which will save post on publish and also at the same time add the post in another post type also.
function wpdocs_save_post_callback( $post_id ) { $post = get_post( $post_id ); if ( $post && 'post' === $post->post_type && isset( $_POST['post_title'] ) ) { $post_title = sanitize_text_field( $_POST['post_title'] ); $post_data = array( 'post_type' => 'news', 'post_status' => 'publish', 'post_title' => $post_title ); wp_insert_post( $post_data ); } } add_action( 'save_post', 'wpdocs_save_post_callback' );