函数文档

wp_resolve_numeric_slug_conflicts()

💡 云策文档标注

概述

wp_resolve_numeric_slug_conflicts() 函数用于解决数字 slug 与日期固定链接冲突的问题。当使用如 /%year%/%postname%/ 的固定链接结构时,数字 slug 可能被 WP_Query::parse_query() 误解析为日期归档。此函数检测并优先解析为文章固定链接,主要处理历史内容或固定链接结构变更导致的冲突。

关键要点

  • 函数检测数字 slug 与日期归档的冲突,并解析为文章链接,避免 URL 歧义。
  • 自 WordPress 4.3.0 起,wp_unique_post_slug() 已阻止创建会导致冲突的 slug,此函数主要用于遗留内容。
  • 函数接收查询变量数组作为参数,返回解析冲突后的查询变量数组。
  • 通过分析固定链接结构中的 %postname% 位置,判断 slug 是否可能被误解析为年、月或日。
  • 检查文章发布日期与 URL 中日期是否匹配,并处理文章分页情况,确保解析准确性。

代码示例

function wp_resolve_numeric_slug_conflicts( $query_vars = array() ) {
    if ( ! isset( $query_vars['year'] ) && ! isset( $query_vars['monthnum'] ) && ! isset( $query_vars['day'] ) ) {
        return $query_vars;
    }

    $permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
    $postname_index = array_search( '%postname%', $permastructs, true );

    if ( false === $postname_index ) {
        return $query_vars;
    }

    $compare = '';
    if ( 0 === $postname_index && ( isset( $query_vars['year'] ) || isset( $query_vars['monthnum'] ) ) ) {
        $compare = 'year';
    } elseif ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && ( isset( $query_vars['monthnum'] ) || isset( $query_vars['day'] ) ) ) {
        $compare = 'monthnum';
    } elseif ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && isset( $query_vars['day'] ) ) {
        $compare = 'day';
    }

    if ( ! $compare ) {
        return $query_vars;
    }

    $value = '';
    if ( array_key_exists( $compare, $query_vars ) ) {
        $value = $query_vars[ $compare ];
    }

    $post = get_page_by_path( $value, OBJECT, 'post' );
    if ( ! ( $post instanceof WP_Post ) ) {
        return $query_vars;
    }

    if ( preg_match( '/^([0-9]{4})-([0-9]{2})/', $post->post_date, $matches ) && isset( $query_vars['year'] ) && ( 'monthnum' === $compare || 'day' === $compare ) ) {
        if ( (int) $query_vars['year'] !== (int) $matches[1] ) {
            return $query_vars;
        }

        if ( 'day' === $compare && isset( $query_vars['monthnum'] ) && (int) $query_vars['monthnum'] !== (int) $matches[2] ) {
            return $query_vars;
        }
    }

    $maybe_page = '';
    if ( 'year' === $compare && isset( $query_vars['monthnum'] ) ) {
        $maybe_page = $query_vars['monthnum'];
    } elseif ( 'monthnum' === $compare && isset( $query_vars['day'] ) ) {
        $maybe_page = $query_vars['day'];
    }
    $maybe_page = (int) trim( $maybe_page, '/' );

    $post_page_count = substr_count( $post->post_content, '' ) + 1;

    if ( 1 === $post_page_count && $maybe_page ) {
        return $query_vars;
    }

    if ( $post_page_count > 1 && $maybe_page > $post_page_count ) {
        return $query_vars;
    }

    $query_vars['page'] = $maybe_page;
    unset( $query_vars['year'] );
    unset( $query_vars['monthnum'] );
    unset( $query_vars['day'] );
    $query_vars['name'] = $post->post_name;
    return $query_vars;
}

注意事项

  • 函数在 WordPress 4.3.0 中引入,主要用于处理历史遗留的冲突,新内容应通过 wp_unique_post_slug() 避免冲突。
  • 函数依赖于 get_option('permalink_structure') 获取固定链接结构,确保结构设置正确。
  • 处理分页时,需检查文章内容中的 标签数量,以确定有效页面数。

📄 原文内容

Resolves numeric slugs that collide with date permalinks.

Description

Permalinks of posts with numeric slugs can sometimes look to WP_Query::parse_query() like a date archive, as when your permalink structure is /%year%/%postname%/ and a post with post_name ’05’ has the URL /2015/05/.

This function detects conflicts of this type and resolves them in favor of the post permalink.

Note that, since 4.3.0, wp_unique_post_slug() prevents the creation of post slugs that would result in a date archive conflict. The resolution performed in this function is primarily for legacy content, as well as cases when the admin has changed the site’s permalink structure in a way that introduces URL conflicts.

Parameters

$query_varsarrayoptional
Query variables for setting up the loop, as determined in WP::parse_request().

Default:array()

Return

array Returns the original array of query vars, with date/post conflicts resolved.

Source

function wp_resolve_numeric_slug_conflicts( $query_vars = array() ) {
	if ( ! isset( $query_vars['year'] ) && ! isset( $query_vars['monthnum'] ) && ! isset( $query_vars['day'] ) ) {
		return $query_vars;
	}

	// Identify the 'postname' position in the permastruct array.
	$permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
	$postname_index = array_search( '%postname%', $permastructs, true );

	if ( false === $postname_index ) {
		return $query_vars;
	}

	/*
	 * A numeric slug could be confused with a year, month, or day, depending on position. To account for
	 * the possibility of post pagination (eg 2015/2 for the second page of a post called '2015'), our
	 * `is_*` checks are generous: check for year-slug clashes when `is_year` *or* `is_month`, and check
	 * for month-slug clashes when `is_month` *or* `is_day`.
	 */
	$compare = '';
	if ( 0 === $postname_index && ( isset( $query_vars['year'] ) || isset( $query_vars['monthnum'] ) ) ) {
		$compare = 'year';
	} elseif ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && ( isset( $query_vars['monthnum'] ) || isset( $query_vars['day'] ) ) ) {
		$compare = 'monthnum';
	} elseif ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && isset( $query_vars['day'] ) ) {
		$compare = 'day';
	}

	if ( ! $compare ) {
		return $query_vars;
	}

	// This is the potentially clashing slug.
	$value = '';
	if ( array_key_exists( $compare, $query_vars ) ) {
		$value = $query_vars[ $compare ];
	}

	$post = get_page_by_path( $value, OBJECT, 'post' );
	if ( ! ( $post instanceof WP_Post ) ) {
		return $query_vars;
	}

	// If the date of the post doesn't match the date specified in the URL, resolve to the date archive.
	if ( preg_match( '/^([0-9]{4})-([0-9]{2})/', $post->post_date, $matches ) && isset( $query_vars['year'] ) && ( 'monthnum' === $compare || 'day' === $compare ) ) {
		// $matches[1] is the year the post was published.
		if ( (int) $query_vars['year'] !== (int) $matches[1] ) {
			return $query_vars;
		}

		// $matches[2] is the month the post was published.
		if ( 'day' === $compare && isset( $query_vars['monthnum'] ) && (int) $query_vars['monthnum'] !== (int) $matches[2] ) {
			return $query_vars;
		}
	}

	/*
	 * If the located post contains nextpage pagination, then the URL chunk following postname may be
	 * intended as the page number. Verify that it's a valid page before resolving to it.
	 */
	$maybe_page = '';
	if ( 'year' === $compare && isset( $query_vars['monthnum'] ) ) {
		$maybe_page = $query_vars['monthnum'];
	} elseif ( 'monthnum' === $compare && isset( $query_vars['day'] ) ) {
		$maybe_page = $query_vars['day'];
	}
	// Bug found in #11694 - 'page' was returning '/4'.
	$maybe_page = (int) trim( $maybe_page, '/' );

	$post_page_count = substr_count( $post->post_content, '<!--nextpage-->' ) + 1;

	// If the post doesn't have multiple pages, but a 'page' candidate is found, resolve to the date archive.
	if ( 1 === $post_page_count && $maybe_page ) {
		return $query_vars;
	}

	// If the post has multiple pages and the 'page' number isn't valid, resolve to the date archive.
	if ( $post_page_count > 1 && $maybe_page > $post_page_count ) {
		return $query_vars;
	}

	// If we've gotten to this point, we have a slug/date clash. First, adjust for nextpage.
	$query_vars['page'] = $maybe_page;

	// Next, unset autodetected date-related query vars.
	unset( $query_vars['year'] );
	unset( $query_vars['monthnum'] );
	unset( $query_vars['day'] );

	// Then, set the identified post.
	$query_vars['name'] = $post->post_name;

	// Finally, return the modified query vars.
	return $query_vars;
}

Changelog

Version Description
4.3.0 Introduced.