问题描述

当从类别页面或任何页面点击单个帖子时,您可以获取该引用来源的 URL 并解析它以获取查询字符串。但这只适用于默认的永久链接结构

例如,引荐来源网址是类别页面:

var_dump( parse_url( wp_get_referer() ) ); 使用默认的 permalink 结构提供以下输出

array(4) {
  ["scheme"]=>
  string(4) "http"
  ["host"]=>
  string(9) "localhost"
  ["path"]=>
  string(11) "/wordpress/"
  ["query"]=>
  string(5) "cat=7"
}

使用相同的 var_dump()与固定链接设置为/%postname%/,这是你得到的

array(3) {
  ["scheme"]=>
  string(4) "http"
  ["host"]=>
  string(9) "localhost"
  ["path"]=>
  string(32) "/wordpress/category/uit-my-lewe/"
}

我可以使用 path 从第二个代码块与 get_category_by_path(),我可以得到类别对象

我的问题是,我如何去分类术语。

我做了一个测试。我有一个自定义分类 event_cat 。如果我重写 event-slug,我得到以下 path 使用/%postname%/作为永久链接结构

/wordpress/event-slug/5-star/

event_cat=5-star

使用默认结构

自动地,我的分类名称不会在 URL 中,只是我的术语。所以,这不是一个非常失败的安全方法,从这个术语中获取对象。

我的问题是,如何正确获取默认的永久链接结构获取查询字符串,或查询字符串或/%postname%/永久链接结构中的分类和术语名称

最佳解决方案

首先我不得不说,wp_get_referer()不是 100%可靠,因为它依赖于 $_SERVER['HTTP_REFERER']不是 100%可靠,从 php.net 文档:

The address of the page (if any) which referred the user agent to the current page. This is set by the user agent. Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature. In short, it cannot really be trusted.

替代方案

如果您可以添加发布网址一个查询参数,说明帖子来自哪里,它将更可靠,您不需要解析一个 url 来获取一个术语对象。

例:

add_filter('post_link', function($permalink) {
  if (is_category() && ($cat = get_queried_object())) {
    $permalink = esc_url(add_query_arg(array('catfrom' => $cat->term_id), $permalink));
  }
  return $permalink;
});

这样做,从类别页面点击的固定链接将会发送给您的网址

http://example.com/wordpress/post-name?catfrom=12

并且您可以轻松了解用户来自哪里,而不依赖于 $_SERVER['HTTP_REFERER'],无需任何其他努力。

回答你的问题

从网址获取查询信息是 WordPress 在 WP::parse_request()方法中所做的。

该方法仅用于”main” url(用户正在查看的 url),而不是任意 URL 。

几个月前,我写了 Url To Query 插件,目的是为任意 URL 执行相同的操作。

我做的是使用 WP::parse_request(),并将其重构为一个更加准确的 OOP 代码,并使其与任意 URL 一起工作 (例如,作为参数的 url 被收到,而不是从 $_SERVER var 获取) 。

可以使用我的插件

$args = url_to_query('/wordpress/event-slug/5-star/');

var_dump($args); // array( 'event_slug' => '5-star' );

所以你从 url 开始获取查询参数 (你可以直接传递给 new WP_Query),这正是 WP::parse_request()所做的。

在你的情况下,您可以检查 args 数组,而不需要实际运行查询。

这可以肯定地起作用,但是我认为分析 URL 和 $_SERVER['HTTP_REFERER']的不可靠性需要额外的努力,使得第一个解决方案更适合您的范围。

次佳解决方案

这个问题的初衷是要知道哪一个职位是从哪里提交的,然后根据这个职位,根据职位介绍人提供下一个职位和以前的职位。

例如我想要完成的

从类别,分类标签,标签,搜索或作者归档页面点击了一个帖子。这些档案作为引荐来源。现在,正如我在问题中,wp_get_referer()通常会使用这个引用来进行进一步的查询。如 @G.M. in his accepted answer above 所述,这种方法是不可靠的,所以我去使用他的替代解决方案。

另一个问题是需要使用某种 cookie 或会话来存储此引荐来源,以便当您从特定归档中点击的原始单个帖子导航时,您仍然可以从原始引荐来源列表中删除帖子。由于 Cookie 也由最终用户控制,因此不可靠,以及 WordPress 默认情况下不使用会话的事实,我使用 @ G.M 重构了下一个和以前的帖子链接。具有可靠的方式来检查和存储我的原始引荐来源的替代解决方案。

这是我想出来的,我希望有人会在不久的将来发现它有用。请使用和滥用代码以满足您的需求,只需一个请求:将链接返回到此问题。 🙂

关于以下代码的注释

  • 这段代码是相当长的和密集的,所以我不会详细介绍。代码已被很好地评论

  • 该代码可以选择在同一术语中的帖子之间进行页面访问,就像 WordPress 中的默认 next_post_link()previous_post_link()函数一样。就像本机功能一样,您必须设置分类。 in_same_term 的默认值为 true,分类为 category

  • 最重要的是,这段代码需要 PHP 5.4+

代码

<?php
/**
 * @author Pieter Goosen
 * @license GPLv2
 * @link http://www.gnu.org/licenses/gpl-2.0.html
 *
 * The functions on this page returns the next and previous post links
 * depending on what has been set
 *
 * @return function single_post_navigation()
*/

/**
 * Register six new query variables aq, ,cq, tq, ttq, taq, and sq set by
 * the term_referer_link function
 *
 * @see http://codex.wordpress.org/WordPress_Query_Vars
 *
*/
add_filter( 'query_vars', function ( $vars ) {

    $vars[] = 'cq'; // Will hold category ID
    $vars[] = 'tq'; // Will hold taxonomy name
    $vars[] = 'ttq'; // Will hold term slug
    $vars[] = 'sq'; // Will hold search query
    $vars[] = 'aq'; // Will hold author name
    $vars[] = 'taq'; // Will hold tag id


    return $vars;

}, 10, 3 );

/**
 * Conditional tag to check whether or not a query_var has been set
 *
 * @param string $query_var query_var to check
 * @return (bool) true if query_var exists, false on failure
 *
*/
function has_query_var( $query_var ) {

    $array = $GLOBALS['wp_query']->query_vars;

    return array_key_exists( $query_var, $array );

}

/**
 * For posts being clicked from a category page, the query_var, 'cq' is set.
 * 'cq' holds the category ID
 *
 * Set two query_var, 'tq' and 'ttq' to single posts that was clicked on from
 * taxonomy pages. 'tq' holds the taxonomy name while 'ttq' holds the term name
 *
 * For search queries, the query_var, 'sq' is set to single posts that was clicked on from
 * the search page. 'sq' holds the search query value
 *
 * For posts being clicked from an author page, the query_var, 'aq' is set.
 * 'aq' holds the author ID
 *
 * For posts being clicked from a tag page, the query_var, 'taq' is set.
 * 'taq' holds the tag ID
 *
 * This function replaces the wp_get_referer() and $_SERVER['HTTP_REFERER']
 * functions that are not very reliable
 * @see php.net manual $_SERVER['HTTP_REFERER']
 * @link http://php.net/manual/en/reserved.variables.server.php
 *
 * @uses add_query_arg()
 * @uses post_link
 * @uses post_type_link
 *
*/
add_filter( 'post_type_link', 'term_referer_link', 10, 3 );
add_filter( 'post_link', 'term_referer_link', 10, 3 );

function term_referer_link( $permalink, $post ) {

    switch ( true ) {

        case ( is_category() ):

            $category = get_queried_object_id();

            $args = [
                'cq'    => $category,
            ];

            break;
        case ( is_tax() ):

            $term = get_queried_object();

            $args = [
                'tq'    => $term->taxonomy,
                'ttq'   => $term->slug
            ];

            break;

        case ( is_search() ):

            $search = get_search_query();

            $args = [
                'sq'    => $search,
            ];

            break;

        case ( is_author() ):

            $author = get_queried_object_id();

            $args = [
                'aq'    => $author,
            ];

            break;

        case ( is_tag() ):

            $tag = get_queried_object_id();

            $args = [
                'taq'   => $tag,
            ];

            break;

    }

    if( isset( $args ) ) {

        $permalink  = add_query_arg( $args, $permalink );

    }

    return $permalink;

}

/**
 * @access private
 * This function is marked private and should not be used in any other functions
 *
 * This is a helper function for the main navigation function
 *
 * This function checks if any of the query variables is set in the single
 * post page URL. If they exist, the values are retrieved that were set
 * by the query variables
 *
 * These query variables are converted into query arguments for the query that will
 * be used to determine the current post position and the posts adjacent to the
 * current post which will translate in the next and previous post.
 *
 * When no query variables are present, an empty array of arguments is returned
 *
 * @uses has_query_var()
 * @return (array) $add_query_args_to_args Query variable to determine the next/previous post links
 * @see http://codex.wordpress.org/Function_Reference/add_query_arg
 *
*/
function _query_vars_to_query_args() {

    switch ( true ) {

        case ( has_query_var( 'cq' ) ): // For category referrer

            $category = get_query_var( 'cq' );

            $add_query_args_to_args = [
                'cat' => $category,
            ];

            break;

        case ( has_query_var( 'tq' ) && has_query_var( 'ttq' ) ): // For taxonomy term referrer

            $taxonomy   = get_query_var( 'tq' );
            $term       = get_query_var( 'ttq' );

            $add_query_args_to_args = [
                'tax_query' => [
                    [
                        'taxonomy'          => $taxonomy,
                        'field'             => 'slug',
                        'terms'             => $term,
                        'include_children'  => false,
                    ],
                ],
            ];

            break;

        case ( has_query_var( 'sq' ) ): // For search referrer

            $search = get_query_var( 'sq' );

            $add_query_args_to_args = [
                's' => $search,
            ];

            break;

        case ( has_query_var( 'aq' ) ): // For author referrer

            $author = get_query_var( 'aq' );

            $add_query_args_to_args = [
                'author' => $author,
            ];

            break;

        case ( has_query_var( 'taq' ) ): // For tag referrer

            $tag = get_query_var( 'taq' );

            $add_query_args_to_args = [
                'tag_id' => $tag,
            ];

            break;

        default: // Default: returns empty array on any other archive or homepage

            $add_query_args_to_args = [];

            break;

    }

    return $add_query_args_to_args;

}
/**
 * @access private
 * This function is marked private and should not be used in any other functions
 *
 * This is a helper function for the main pagination function. This function
 * checks if the defined query variables has been set in the URL of a single
 * post
 *
 * If any of the query variables are found on any given single post page, then
 * these query variables will be set to the next and previous post links according
 * to the single post's query variables
 *
 * This way, next and previous posts will be shown from the same category, term,
 * search query or author archive from which the original single post was referred
 * from.
 *
 * If a single post was referred from any other archive or main page, these query
 * variables will not be set, and function will default to an empty array and no
 * query variables will be set to the next and previous post links
 *
 * @uses has_query_var()
 * @return (array) $qv Query variable to add to next/previous post links
 * @see http://codex.wordpress.org/Function_Reference/add_query_arg
 *
 * @todo Other archives can be added later
*/
function _add_query_vars_to_nav_links() {

    switch ( true ) {

        case ( has_query_var( 'cq' ) ): // For category referrer

            $category = get_query_var( 'cq' );

            $qv = [
                'cq'    => $category,
            ];

            break;

        case ( has_query_var( 'tq' ) && has_query_var( 'ttq' ) ): // For taxonomy term referrer

            $taxonomy   = get_query_var( 'tq' );
            $term       = get_query_var( 'ttq' );

            $qv = [
                'tq'    => $term->taxonomy,
                'ttq'   => $term->slug
            ];

            break;

        case ( has_query_var( 'sq' ) ): // For search referrer

            $search = get_query_var( 'sq' );

            $qv = [
                'sq'    => $search,
            ];

            break;

        case ( has_query_var( 'aq' ) ): // For author referrer

            $author = get_query_var( 'aq' );

            $qv = [
                'aq'    => $author,
            ];

            break;

        case ( has_query_var( 'taq' ) ): // For tag referrer

            $tag = get_query_var( 'taq' );

            $qv = [
                'taq'   => $tag,
            ];

            break;


        default: // Default: returns empty array on any other archive or homepage

            $qv = [];

            break;

    }

    return $qv;

}

/**
 * This function returns navigation links to the next/previous single post
 * There are choices to which taxonomy to use, and whether adjacent posts should
 * be of the same term or not
 *
 * When in_same_term is set to true, you have a choice to use the parent term or
 * child term if a post belongs to both. If the parent term is not available, the child term
 * is automatically used
 *
 * @param array $defaults An array of key => value arguments. Defaults below
 * - bool in_same_term       Whether or not next/previous post should be in the same term Default true
 * - bool parent_term        If in_same_term is true, should the parent or child terms be used Default true
 * - string/array taxonomy   The taxonomy from which terms to use Default category
 * - string/array post_types Post types to get posts from. Uses current post's post type on empty string. Default empty string
 * - string previous_text    Text to display with previous post Default 'Previous post'
 * - string next_text        Text to display with next post Default 'Next post'
 *
 * @return string $links
*/
function get_single_post_navigation( $args = [] ) {

    // Sets the default arguments for default usage
    $defaults = [
        'in_same_term'      => true,
        'parent_term'       => true,
        'post_types'         => '',
        'taxonomy'          => 'category',
        'previous_text'     => __( 'Previous post' ),
        'next_text'         => __( 'Next post' ),
    ];

    // Merges the default arguments with user defined variables
    $args = wp_parse_args( $args, $defaults );

    /**
     * Get the currently displayed single post. For this use
     * get_queried_object() as this is more safe than the global $post
     *
     * The $post global is very easily changed by any poorly written custom query
     * or function, and is there for not reliable
     *
     * @see Post below on WPSE for explanation
     * @link https://wordpress.stackexchange.com/q/167706/31545
    */
    $single_post = get_queried_object();

    /**
     * Use the post type of the current post or post types entered in args
     *
    */
    $post_type   = ( empty( $args['post_types'] ) ) ? $single_post->post_type : $args['post_types'];


    // Set the variable query variables according to condition
    if( !empty( _query_vars_to_query_args() ) ) {

        $query_args = _query_vars_to_query_args();

    }elseif( true === $args['in_same_term'] ) {

        $terms =  wp_get_post_terms( $single_post->ID, $args['taxonomy'] );

        if ( ! empty( $terms ) && ! is_wp_error( $terms ) ){

            foreach ( $terms as $term ) {
                if( $term->parent === 0 ) {
                    $parent[] = $term;
                }else{
                    $child[] = $term;
                }
            }

            $term_id = ( $args['parent_term'] === true && isset( $parent ) ) ? $parent[0]->term_id : $child[0]->term_id;

            $query_args = [
                'tax_query' => [
                    [
                        'taxonomy'          => $args['taxonomy'],
                        'field'             => 'term_id',
                        'terms'             => $term_id,
                        'include_children'  => false,
                    ],
                ],
            ];
        }

    }else{

        $query_args = [];

    }

    // Default arguments to use with all the conditional statements above
    $default_query_args = [
        'post_type'         => $post_type,
        'fields'            => 'ids',
        'posts_per_page'    => -1,
        'suppress_filters'  => true,
    ];

    // Merges the default arguments with the arguments from the conditional statement
    $combined_args = wp_parse_args( $query_args, $default_query_args );

    $q = new WP_Query( $combined_args );

    // Get the current post position. Will be used to determine adjacent posts
    $current_post_position = array_search( $single_post->ID, $q->posts );

    // Get the returned values from '_add_query_vars_to_nav_links()' to build links
    $get_qv = _add_query_vars_to_nav_links();

    // Get the next/older post ID
    if ( array_key_exists( $current_post_position + 1 , $q->posts ) ) {
        $next = $q->posts[$current_post_position + 1];
    }

    // Get post title link to the next post
    if( isset( $next ) ) {

        $next_post      = get_post( $next );
        $next_post_link = ( !empty( $get_qv ) ) ? add_query_arg( $get_qv, get_permalink( $next ) ) : get_permalink( $next );
        $next_title     = '<span class="meta-nav">' . $args['next_text'] . ': </span><a href="'%20.%20$next_post_link%20.%20'">' . $next_post->post_title . '</a></br>';

    }else{

        $next_title     = '';

    }

    // Get the previous/newer post ID
    if ( array_key_exists( $current_post_position - 1 , $q->posts ) ) {
        $previous = $q->posts[$current_post_position - 1];
    }

    // Get post title link to the previous post
    if( isset( $previous ) ) {

        $previous_post      = get_post( $previous );
        $previous_post_link = ( !empty( $get_qv ) ) ? add_query_arg( $get_qv, get_permalink( $previous ) ) : get_permalink( $previous );
        $previous_title     = '<span class="meta-nav">' . $args['previous_text'] . ': </span><a href="'%20.%20$previous_post_link%20.%20'">' . $previous_post->post_title . '</a></br>';

    }else{

        $previous_title     = '';

    }

    // Create the next/previous post links
    $links  = '<nav class="navigation post-navigation" role="navigation">';
    $links .= '<div class="nav-links">';
    $links .= $previous_title;
    $links .= $next_title;
    $links .= '</div><!-- .nav-links -->';
    $links .= '</nav><!-- .navigation -->';

    // Returns the post links with HTML mark-up
    return $links;

}

/**
 * This function is simply just a wrapper for the main navigation
 * function and echo's the returned values from the main navigation
 * function
*/
function single_post_navigation( $args = [] ) {

    echo get_single_post_navigation( $args );

}

在单一模式下使用

如果您不需要在同一术语中浏览帖子,请从所有帖子类型发帖,并通过链接自定义下一个和上一个文本,您可以执行以下操作:

$args = [
    'in_same_term'     => false,
    'post_types'       => ['post', 'my_post_type'],
    'previous_text'      => __( 'Vorige Pos' ),
    'next_text'      => __( 'Volgende Pos' ),
];

single_post_navigation( $args );

编辑 1

根据 SO 上的帖子的要求,作为 @todo 的一部分,我现在已经介绍了支持,不仅可以在当前帖子的帖子类型的帖子之间导航,而且还可以使用用户使用 post_types 参数设置的一组帖子类型功能。请参阅更新的代码。

编辑 2

'suppress_filters' => true, 添加到 WP_Query 参数,以使分页不被 WP_Query 中使用的任何过滤器更改

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。