: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @global wpdb $wpdb WordPress database abstraction object.
* @param int|WP_Post $post Post ID or post object.
function wp_publish_post( $post ) {
$post = get_post( $post );
if ( 'publish' === $post->post_status ) {
$post_before = get_post( $post->ID );
// Ensure at least one term is applied for taxonomies with a default term.
foreach ( get_object_taxonomies( $post->post_type, 'object' ) as $taxonomy => $tax_object ) {
// Skip taxonomy if no default term is set.
'category' !== $taxonomy &&
empty( $tax_object->default_term )
// Do not modify previously set terms.
if ( ! empty( get_the_terms( $post, $taxonomy ) ) ) {
if ( 'category' === $taxonomy ) {
$default_term_id = (int) get_option( 'default_category', 0 );
$default_term_id = (int) get_option( 'default_term_' . $taxonomy, 0 );
if ( ! $default_term_id ) {
wp_set_post_terms( $post->ID, array( $default_term_id ), $taxonomy );
$wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
clean_post_cache( $post->ID );
$old_status = $post->post_status;
$post->post_status = 'publish';
wp_transition_post_status( 'publish', $old_status, $post );
/** This action is documented in wp-includes/post.php */
do_action( "edit_post_{$post->post_type}", $post->ID, $post );
/** This action is documented in wp-includes/post.php */
do_action( 'edit_post', $post->ID, $post );
/** This action is documented in wp-includes/post.php */
do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
/** This action is documented in wp-includes/post.php */
do_action( 'save_post', $post->ID, $post, true );
/** This action is documented in wp-includes/post.php */
do_action( 'wp_insert_post', $post->ID, $post, true );
wp_after_insert_post( $post, true, $post_before );
* Publishes future post and make sure post ID has future post status.
* Invoked by cron 'publish_future_post' event. This safeguard prevents cron
* from publishing drafts, etc.
* @param int|WP_Post $post Post ID or post object.
function check_and_publish_future_post( $post ) {
$post = get_post( $post );
if ( 'future' !== $post->post_status ) {
$time = strtotime( $post->post_date_gmt . ' GMT' );
// Uh oh, someone jumped the gun!
wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) ); // Clear anything else in the system.
wp_schedule_single_event( $time, 'publish_future_post', array( $post->ID ) );
// wp_publish_post() returns no meaningful value.
wp_publish_post( $post->ID );
* Uses wp_checkdate to return a valid Gregorian-calendar value for post_date.
* If post_date is not provided, this first checks post_date_gmt if provided,
* then falls back to use the current time.
* For back-compat purposes in wp_insert_post, an empty post_date and an invalid
* post_date_gmt will continue to return '1970-01-01 00:00:00' rather than false.
* @param string $post_date The date in mysql format (`Y-m-d H:i:s`).
* @param string $post_date_gmt The GMT date in mysql format (`Y-m-d H:i:s`).
* @return string|false A valid Gregorian-calendar date string, or false on failure.
function wp_resolve_post_date( $post_date = '', $post_date_gmt = '' ) {
// If the date is empty, set the date to now.
if ( empty( $post_date ) || '0000-00-00 00:00:00' === $post_date ) {
if ( empty( $post_date_gmt ) || '0000-00-00 00:00:00' === $post_date_gmt ) {
$post_date = current_time( 'mysql' );
$post_date = get_date_from_gmt( $post_date_gmt );
$month = (int) substr( $post_date, 5, 2 );
$day = (int) substr( $post_date, 8, 2 );
$year = (int) substr( $post_date, 0, 4 );
$valid_date = wp_checkdate( $month, $day, $year, $post_date );
* Computes a unique slug for the post, when given the desired slug and some post details.
* @global wpdb $wpdb WordPress database abstraction object.
* @global WP_Rewrite $wp_rewrite WordPress rewrite component.
* @param string $slug The desired slug (post_name).
* @param int $post_id Post ID.
* @param string $post_status No uniqueness checks are made if the post is still draft or pending.
* @param string $post_type Post type.
* @param int $post_parent Post parent ID.
* @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
function wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_parent ) {
if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true )
|| ( 'inherit' === $post_status && 'revision' === $post_type ) || 'user_request' === $post_type
* Filters the post slug before it is generated to be unique.
* Returning a non-null value will short-circuit the
* unique slug generation, returning the passed value instead.
* @param string|null $override_slug Short-circuit return value.
* @param string $slug The desired slug (post_name).
* @param int $post_id Post ID.
* @param string $post_status The post status.
* @param string $post_type Post type.
* @param int $post_parent Post parent ID.
$override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_id, $post_status, $post_type, $post_parent );
if ( null !== $override_slug ) {
global $wpdb, $wp_rewrite;
$feeds = $wp_rewrite->feeds;
if ( ! is_array( $feeds ) ) {
if ( 'attachment' === $post_type ) {
// Attachment slugs must be unique across all types.
$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_id ) );
* Filters whether the post slug would make a bad attachment slug.
* @param bool $bad_slug Whether the slug would be bad as an attachment slug.
* @param string $slug The post slug.
$is_bad_attachment_slug = apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug );
|| in_array( $slug, $feeds, true ) || 'embed' === $slug
|| $is_bad_attachment_slug
$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_id ) );
} while ( $post_name_check );
} elseif ( is_post_type_hierarchical( $post_type ) ) {
if ( 'nav_menu_item' === $post_type ) {
* Page slugs must be unique within their own trees. Pages are in a separate
* namespace than posts so page slugs are allowed to overlap post slugs.
$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_id, $post_parent ) );
* Filters whether the post slug would make a bad hierarchical post slug.
* @param bool $bad_slug Whether the post slug would be bad in a hierarchical post context.
* @param string $slug The post slug.
* @param string $post_type Post type.
* @param int $post_parent Post parent ID.
$is_bad_hierarchical_slug = apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent );
|| in_array( $slug, $feeds, true ) || 'embed' === $slug
|| preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )
|| $is_bad_hierarchical_slug
$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id, $post_parent ) );
} while ( $post_name_check );
// Post slugs must be unique across all posts.
$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_id ) );
$post = get_post( $post_id );
// Prevent new post slugs that could result in URLs that conflict with date archives.
$conflicts_with_date_archive = false;
if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
$permastructs = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
$postname_index = array_search( '%postname%', $permastructs, true );
* Potential date clashes are as follows:
* - Any integer in the first permastruct position could be a year.
* - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
* - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
if ( 0 === $postname_index ||
( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
$conflicts_with_date_archive = true;
* Filters whether the post slug would be bad as a flat slug.
* @param bool $bad_slug Whether the post slug would be bad as a flat slug.
* @param string $slug The post slug.
* @param string $post_type Post type.
$is_bad_flat_slug = apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type );
|| in_array( $slug, $feeds, true ) || 'embed' === $slug
|| $conflicts_with_date_archive
$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id ) );
} while ( $post_name_check );
* Filters the unique post slug.
* @param string $slug The post slug.
* @param int $post_id Post ID.
* @param string $post_status The post status.
* @param string $post_type Post type.
* @param int $post_parent Post parent ID
* @param string $original_slug The original post slug.
return apply_filters( 'wp_unique_post_slug', $slug, $post_id, $post_status, $post_type, $post_parent, $original_slug );
* @param string $slug The slug to truncate.
* @param int $length Optional. Max length of the slug. Default 200 (characters).
* @return string The truncated slug.
function _truncate_post_slug( $slug, $length = 200 ) {
if ( strlen( $slug ) > $length ) {
$decoded_slug = urldecode( $slug );
if ( $decoded_slug === $slug ) {
$slug = substr( $slug, 0, $length );
$slug = utf8_uri_encode( $decoded_slug, $length, true );
return rtrim( $slug, '-' );
* @see wp_set_post_tags()
* @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
* @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
* separated by commas. Default empty.
* @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
function wp_add_post_tags( $post_id = 0, $tags = '' ) {
return wp_set_post_tags( $post_id, $tags, true );
* Sets the tags for a post.
* @see wp_set_object_terms()
* @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
* @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
* separated by commas. Default empty.
* @param bool $append Optional. If true, don't delete existing tags, just add on. If false,
* replace the tags with the new tags. Default false.
* @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
return wp_set_post_terms( $post_id, $tags, 'post_tag', $append );
* Sets the terms for a post.
* @see wp_set_object_terms()
* @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
* @param string|array $terms Optional. An array of terms to set for the post, or a string of terms
* separated by commas. Hierarchical taxonomies must always pass IDs rather
* than names so that children with the same names but different parents
* aren't confused. Default empty.
* @param string $taxonomy Optional. Taxonomy name. Default 'post_tag'.
* @param bool $append Optional. If true, don't delete existing terms, just add on. If false,
* replace the terms with the new terms. Default false.
* @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
function wp_set_post_terms( $post_id = 0, $terms = '', $taxonomy = 'post_tag', $append = false ) {
$post_id = (int) $post_id;
if ( ! is_array( $terms ) ) {
$comma = _x( ',', 'tag delimiter' );
$terms = str_replace( $comma, ',', $terms );
$terms = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) );
* Hierarchical taxonomies must always pass IDs rather than names so that
* children with the same names but different parents aren't confused.
if ( is_taxonomy_hierarchical( $taxonomy ) ) {
$terms = array_unique( array_map( 'intval', $terms ) );
return wp_set_object_terms( $post_id, $terms, $taxonomy, $append );
* Sets categories for a post.
* If no categories are provided, the default category is used.
* @param int $post_id Optional. The Post ID. Does not default to the ID
* of the global $post. Default 0.
* @param int[]|int $post_categories Optional. List of category IDs, or the ID of a single category.
* @param bool $append If true, don't delete existing categories, just add on.
* If false, replace the categories with the new categories.
* @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
function wp_set_post_categories( $post_id = 0, $post_categories = array(), $append = false ) {
$post_id = (int) $post_id;
$post_type = get_post_type( $post_id );
$post_status = get_post_status( $post_id );
// If $post_categories isn't already an array, make it one.
$post_categories = (array) $post_categories;
if ( empty( $post_categories ) ) {
* Filters post types (in addition to 'post') that require a default category.
* @param string[] $post_types An array of post type names. Default empty array.
$default_category_post_types = apply_filters( 'default_category_post_types', array() );
// Regular posts always require a default category.
$default_category_post_types = array_merge( $default_category_post_types, array( 'post' ) );
if ( in_array( $post_type, $default_category_post_types, true )
&& is_object_in_taxonomy( $post_type, 'category' )
&& 'auto-draft' !== $post_status
$post_categories = array( get_option( 'default_category' ) );
$post_categories = array();
} elseif ( 1 === count( $post_categories ) && '' === reset( $post_categories ) ) {
return wp_set_post_terms( $post_id, $post_categories, 'category', $append );
* Fires actions related to the transitioning of a post's status.
* When a post is saved, the post status is "transitioned" from one status to another,
* though this does not always mean the status has actually changed before and after
* the save. This function fires a number of action hooks related to that transition:
* the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
* {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
* that the function does not transition the post object in the database.
* For instance: When publishing a post for the first time, the post status may transition