: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.
* @param bool $add_trashed_suffix Whether to attempt to add the suffix.
* @param string $post_name The name of the post being updated.
* @param int $post_id Post ID.
$add_trashed_suffix = apply_filters( 'add_trashed_suffix_to_trashed_posts', true, $post_name, $post_id );
if ( $add_trashed_suffix ) {
wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_id );
// When trashing an existing post, change its slug to allow non-trashed posts to use it.
if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
$post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_id );
$post_name = wp_unique_post_slug( $post_name, $post_id, $post_status, $post_type, $post_parent );
$post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
// Expected_slashed (everything!).
$emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
foreach ( $emoji_fields as $emoji_field ) {
if ( isset( $data[ $emoji_field ] ) ) {
$charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
if ( 'utf8' === $charset ) {
$data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
if ( 'attachment' === $post_type ) {
* Filters attachment post data before it is updated in or added to the database.
* @since 5.4.1 The `$unsanitized_postarr` parameter was added.
* @since 6.0.0 The `$update` parameter was added.
* @param array $data An array of slashed, sanitized, and processed attachment post data.
* @param array $postarr An array of slashed and sanitized attachment post data, but not processed.
* @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed attachment post data
* as originally passed to wp_insert_post().
* @param bool $update Whether this is an existing attachment post being updated.
$data = apply_filters( 'wp_insert_attachment_data', $data, $postarr, $unsanitized_postarr, $update );
* Filters slashed post data just before it is inserted into the database.
* @since 5.4.1 The `$unsanitized_postarr` parameter was added.
* @since 6.0.0 The `$update` parameter was added.
* @param array $data An array of slashed, sanitized, and processed post data.
* @param array $postarr An array of sanitized (and slashed) but otherwise unmodified post data.
* @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed post data as
* originally passed to wp_insert_post().
* @param bool $update Whether this is an existing post being updated.
$data = apply_filters( 'wp_insert_post_data', $data, $postarr, $unsanitized_postarr, $update );
$data = wp_unslash( $data );
$where = array( 'ID' => $post_id );
* Fires immediately before an existing post is updated in the database.
* @param int $post_id Post ID.
* @param array $data Array of unslashed post data.
do_action( 'pre_post_update', $post_id, $data );
if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
if ( 'attachment' === $post_type ) {
$message = __( 'Could not update attachment in the database.' );
$message = __( 'Could not update post in the database.' );
return new WP_Error( 'db_update_error', $message, $wpdb->last_error );
// If there is a suggested ID, use it if not already present.
if ( ! empty( $import_id ) ) {
$import_id = (int) $import_id;
if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
$data['ID'] = $import_id;
if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
if ( 'attachment' === $post_type ) {
$message = __( 'Could not insert attachment into the database.' );
$message = __( 'Could not insert post into the database.' );
return new WP_Error( 'db_insert_error', $message, $wpdb->last_error );
$post_id = (int) $wpdb->insert_id;
// Use the newly generated $post_id.
$where = array( 'ID' => $post_id );
if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ), true ) ) {
$data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_id ), $post_id, $data['post_status'], $post_type, $post_parent );
$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
clean_post_cache( $post_id );
if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
wp_set_post_categories( $post_id, $post_category );
if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
wp_set_post_tags( $post_id, $postarr['tags_input'] );
// Add default term for all associated custom taxonomies.
if ( 'auto-draft' !== $post_status ) {
foreach ( get_object_taxonomies( $post_type, 'object' ) as $taxonomy => $tax_object ) {
if ( ! empty( $tax_object->default_term ) ) {
// Filter out empty terms.
if ( isset( $postarr['tax_input'][ $taxonomy ] ) && is_array( $postarr['tax_input'][ $taxonomy ] ) ) {
$postarr['tax_input'][ $taxonomy ] = array_filter( $postarr['tax_input'][ $taxonomy ] );
// Passed custom taxonomy list overwrites the existing list if not empty.
$terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
if ( ! empty( $terms ) && empty( $postarr['tax_input'][ $taxonomy ] ) ) {
$postarr['tax_input'][ $taxonomy ] = $terms;
if ( empty( $postarr['tax_input'][ $taxonomy ] ) ) {
$default_term_id = get_option( 'default_term_' . $taxonomy );
if ( ! empty( $default_term_id ) ) {
$postarr['tax_input'][ $taxonomy ] = array( (int) $default_term_id );
// New-style support for all custom taxonomies.
if ( ! empty( $postarr['tax_input'] ) ) {
foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
$taxonomy_obj = get_taxonomy( $taxonomy );
/* translators: %s: Taxonomy name. */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
// array = hierarchical, string = non-hierarchical.
if ( is_array( $tags ) ) {
$tags = array_filter( $tags );
if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
wp_set_post_terms( $post_id, $tags, $taxonomy );
if ( ! empty( $postarr['meta_input'] ) ) {
foreach ( $postarr['meta_input'] as $field => $value ) {
update_post_meta( $post_id, $field, $value );
$current_guid = get_post_field( 'guid', $post_id );
if ( ! $update && '' === $current_guid ) {
$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_id ) ), $where );
if ( 'attachment' === $postarr['post_type'] ) {
if ( ! empty( $postarr['file'] ) ) {
update_attached_file( $post_id, $postarr['file'] );
if ( ! empty( $postarr['context'] ) ) {
add_post_meta( $post_id, '_wp_attachment_context', $postarr['context'], true );
// Set or remove featured image.
if ( isset( $postarr['_thumbnail_id'] ) ) {
$thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
if ( wp_attachment_is( 'audio', $post_id ) ) {
$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
} elseif ( wp_attachment_is( 'video', $post_id ) ) {
$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
if ( $thumbnail_support ) {
$thumbnail_id = (int) $postarr['_thumbnail_id'];
if ( -1 === $thumbnail_id ) {
delete_post_thumbnail( $post_id );
set_post_thumbnail( $post_id, $thumbnail_id );
clean_post_cache( $post_id );
$post = get_post( $post_id );
if ( ! empty( $postarr['page_template'] ) ) {
$post->page_template = $postarr['page_template'];
$page_templates = wp_get_theme()->get_page_templates( $post );
if ( 'default' !== $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
update_post_meta( $post_id, '_wp_page_template', 'default' );
update_post_meta( $post_id, '_wp_page_template', $postarr['page_template'] );
if ( 'attachment' !== $postarr['post_type'] ) {
wp_transition_post_status( $data['post_status'], $previous_status, $post );
* Fires once an existing attachment has been updated.
* @param int $post_id Attachment ID.
do_action( 'edit_attachment', $post_id );
$post_after = get_post( $post_id );
* Fires once an existing attachment has been updated.
* @param int $post_id Post ID.
* @param WP_Post $post_after Post object following the update.
* @param WP_Post $post_before Post object before the update.
do_action( 'attachment_updated', $post_id, $post_after, $post_before );
* Fires once an attachment has been added.
* @param int $post_id Attachment ID.
do_action( 'add_attachment', $post_id );
* Fires once an existing post has been updated.
* The dynamic portion of the hook name, `$post->post_type`, refers to
* Possible hook names include:
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
do_action( "edit_post_{$post->post_type}", $post_id, $post );
* Fires once an existing post has been updated.
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
do_action( 'edit_post', $post_id, $post );
$post_after = get_post( $post_id );
* Fires once an existing post has been updated.
* @param int $post_id Post ID.
* @param WP_Post $post_after Post object following the update.
* @param WP_Post $post_before Post object before the update.
do_action( 'post_updated', $post_id, $post_after, $post_before );
* Fires once a post has been saved.
* The dynamic portion of the hook name, `$post->post_type`, refers to
* Possible hook names include:
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Whether this is an existing post being updated.
do_action( "save_post_{$post->post_type}", $post_id, $post, $update );
* Fires once a post has been saved.
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Whether this is an existing post being updated.
do_action( 'save_post', $post_id, $post, $update );
* Fires once a post has been saved.
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Whether this is an existing post being updated.
do_action( 'wp_insert_post', $post_id, $post, $update );
if ( $fire_after_hooks ) {
wp_after_insert_post( $post, $update, $post_before );
* Updates a post with new post data.
* The date does not have to be set for drafts. You can set the date and it will
* @since 3.5.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
* @since 5.6.0 Added the `$fire_after_hooks` parameter.
* @param array|object $postarr Optional. Post data. Arrays are expected to be escaped,
* objects are not. See wp_insert_post() for accepted arguments.
* @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
* @param bool $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
* @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
function wp_update_post( $postarr = array(), $wp_error = false, $fire_after_hooks = true ) {
if ( is_object( $postarr ) ) {
// Non-escaped post was passed.
$postarr = get_object_vars( $postarr );
$postarr = wp_slash( $postarr );
// First, get all of the original fields.
$post = get_post( $postarr['ID'], ARRAY_A );
if ( is_null( $post ) ) {
return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
// Escape data pulled from DB.
$post = wp_slash( $post );
// Passed post category list overwrites existing category list if not empty.
if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
&& count( $postarr['post_category'] ) > 0
$post_cats = $postarr['post_category'];
$post_cats = $post['post_category'];
// Drafts shouldn't be assigned a date unless explicitly done so by the user.
if ( isset( $post['post_status'] )
&& in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ), true )
&& empty( $postarr['edit_date'] ) && ( '0000-00-00 00:00:00' === $post['post_date_gmt'] )
// Merge old and new fields with new fields overwriting old ones.
$postarr = array_merge( $post, $postarr );
$postarr['post_category'] = $post_cats;
$postarr['post_date'] = current_time( 'mysql' );
$postarr['post_date_gmt'] = '';
if ( 'attachment' === $postarr['post_type'] ) {
return wp_insert_attachment( $postarr, false, 0, $wp_error );
// Discard 'tags_input' parameter if it's the same as existing post tags.
if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $postarr['post_type'], 'post_tag' ) ) {
$tags = get_the_terms( $postarr['ID'], 'post_tag' );
if ( $tags && ! is_wp_error( $tags ) ) {
$tag_names = wp_list_pluck( $tags, 'name' );
if ( $postarr['tags_input'] === $tag_names ) {
unset( $postarr['tags_input'] );
return wp_insert_post( $postarr, $wp_error, $fire_after_hooks );
* Publishes a post by transitioning the post status.