这是 WordPress 重写 API 系列的第二部分。在第一部分中,我们简要介绍了 WordPress 的 Rewrite API 的基础知识。在本教程中,我们将了解注册帖子类型或分类法时可用的重写设置。虽然自定义帖子类型和分类法(与默认帖子、类别和标签不同)不会从任何“设置”->“永久链接”界面中受益,但为自定义类型设置重写仍然相当简单。我们还将使用第一部分中介绍的方法,所以如果您还没有阅读 WordPress 的重写 API 第一部分:基础知识。
当您注册自定义类型时,WordPress 还会注册重写规则(实际上,不完全,我将在“永久结构”部分中解释原因)。正如第一部分中提到的,只有在重写规则被“刷新”后,这些规则才会被包含在内。主题和插件可以通过调用 flush_rewrite_rules()
强制执行此“刷新”。这需要且应该仅在激活时执行一次,然后在停用时再次执行(以便您自行清理)。
显然,在刷新重写规则之前,您需要添加它们。然而,应该注册帖子类型的 init
钩子已经被触发,当它被触发时,您的插件或主题尚未激活,因此您的帖子类型和分类法尚未注册。为了注册您的帖子类型和分类法附带的重写规则,这意味着您需要在激活时“手动”注册它们,然后再刷新重写规则。所以,这应该是您的设置:
function wptuts_register_types() { //function which registers your custom post type and taxonomies } add_action('init','wptuts_register_types'); function wptuts_plugin_activation() { // Register types to register the rewrite rules wptuts_register_types(); // Then flush them flush_rewrite_rules(); } register_activation_hook( __FILE__, 'wptuts_plugin_activation'); function wptuts_plugin_deactivation() { flush_rewrite_rules(); } register_activation_hook( __FILE__, 'wptuts_plugin_activation');
主题可以使用挂钩 after_switch_theme
进行激活,使用 switch_theme
进行停用。
当您使用 register_post_type
注册帖子类型时,您可用的参数之一是重写参数。这应该是一个包含以下键的数组:
slug
– 用于识别 URL 中的帖子类型的 slug。帖子的 slug 附加到此帖子的永久链接,例如www.example.com/书籍/the-wizard-of-oz
with_front
– true 或 false。如果您的帖子的永久链接结构以常量基开头,例如“/blog” - 也可以通过将其设置为 true 将其添加到您的自定义帖子类型的永久链接结构中,例如true 将给出 www.example.com/blog/books/
和 false www.example.com/books/
feeds
– 正确或错误。是否生成 feed 重写规则,例如www.example.com/books/feed/rss
和 www.example.com/book/rss
。默认值为 has_archive
的值。pages
– 正确或错误。是否为帖子类型存档生成“漂亮”分页规则,例如www.example.com/books/page/2
而不是 www.example.com/books?page=2
。默认为 true。ep_mask
– 这曾经是一个单独的参数:permalink_epmask
。从 3.4 开始,它将移至重写数组。默认为 EP_PERMALINK
。“feeds”和“pages”键仅涉及 post 类型的存档页面(为此,您需要将 has_archive
参数设置为 true)。 WordPress 会根据此重写数组自动处理帖子类型的重写规则的生成。举个例子:
$labels = array( 'name' => __('Books', 'your-plugins-translation-domain'), //array of labels ); $args = array( 'labels' => $labels, 'has_archive'=>true, 'rewrite' => array( 'slug'=>'books', 'with_front'=> false, 'feed'=> true, 'pages'=> true ) ); register_post_type('book',$args);
将给出以下重写规则:
the-wizard-of-oz
》一书的永久链接: www.example.com/books/the-wizard-of-oz
www.example.com/books
(和 www.example.com/books/page/2
)www.example.com/books/feed/rss
(以及 www.example.com/books/rss
)register_taxonomy()
函数提供的选项较少:
slug
– 用于识别分类的 slug,例如www.example.com/流派/history
with_front
– 如上所述。hierarchical
– 正确或错误。如果设置为 true 并且分类是分层的,则术语永久链接反映层次结构。默认为 false。ep_mask
– 3.4 中添加。请参阅下面的 EP 面膜部分。前两个选项与上面类似。分层选项使术语永久链接具有与页面相同的结构。例如,让“历史”为一个流派,“军事史”为一个子流派。将层级设置为 false 时,“军事历史”将有一个永久链接:
www.example.com/genre/military-history
然而,设置为 true 时,它将具有:
www.example.com/genre/military/military-history
注册分类法会自动注册您的分类术语的 Feed:
www.example.com/genre/military-history/feed
您可以使用
$feed_link = get_term_feed_link($term_id, $taxonomy, $feed)
获取任何分类术语的 feed-link 永久链接
默认情况下,WordPress 不会为您的自定义帖子类型的年、月或日存档(也不为作者存档 - 但我们暂时保留该存档)生成“漂亮”的永久链接。同时:
www.example.com/?post_type=book&year=2012&monthnum=05
正确给出 2012 年 5 月出版的所有书籍的档案:
www.example.com/books/2012/05
会给出 404 错误。但是,我们可以使用第一部分中介绍的可用重写 API 方法简单地添加额外的重写规则。一种方法是在注册帖子类型时添加以下重写规则列表:
// Add day archive (and pagination) add_rewrite_rule("books/([0-9]{4})/([0-9]{2})/([0-9]{2})/page/?([0-9]{1,})/?",'index.php?post_type=book&year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]','top'); add_rewrite_rule("books/([0-9]{4})/([0-9]{2})/([0-9]{2})/?",'index.php?post_type=book&year=$matches[1]&monthnum=$matches[2]&day=$matches[3]','top'); // Add month archive (and pagination) add_rewrite_rule("books/([0-9]{4})/([0-9]{2})/page/?([0-9]{1,})/?",'index.php?post_type=book&year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]','top'); add_rewrite_rule("books/([0-9]{4})/([0-9]{2})/?",'index.php?post_type=book&year=$matches[1]&monthnum=$matches[2]','top'); // Add year archive (and pagination) add_rewrite_rule("books/([0-9]{4})/page/?([0-9]{1,})/?",'index.php?post_type=book&year=$matches[1]&paged=$matches[2]','top'); add_rewrite_rule("books/([0-9]{4})/?",'index.php?post_type=book&year=$matches[1]','top');
这很容易变得有点混乱——尤其是当您认为如果您希望您的档案支持其提要的漂亮 URL 时,您需要添加额外的规则。但是,上面说明了有关添加自定义规则的一个重要事实:添加规则的顺序很重要。
回想一下,这些规则按照您调用 add_rewrite_rule
的顺序添加到重写数组中。解析请求时,WordPress 使用first匹配规则。尝试切换添加年份和月份存档规则的顺序。您会发现 www.example.com/books/2012/04/
将您带到 2012 年存档。这是因为该 URL 与年份和月份档案的模式匹配,但前者是先添加的。 请记住始终先添加更具体的规则。
另一方面,如果您添加一条重写规则,其正则表达式已作为规则存在,则该规则将被新规则覆盖。
有一个简单的方法可以实现上述目的:add_permastruct
。 WordPress 使用此函数来创建“永久结构”,从中生成重写规则(如上所示),用于处理分页和提要。当您注册自定义帖子类型时,WordPress 不会自动创建所有重写规则。相反,它注册了一个永久结构 - 并且只有当规则生成时(即当它们被刷新时)WordPress 才会使用这些永久结构来生成实际的重写规则。
永久结构的一个示例是您在“设置”->“永久链接”中使用的示例。这些接受任何“硬编码”的 slugs 或默认存在的任何标签或已使用 add_rewrite_tag
添加的标签,我们在第一部分中对此进行了介绍。例如,永久结构 %year%/%category%/%author%
将生成以下重写规则:
www.example.com/2012/url-rewriting/stephen
www.example.com/2012/url-rewriting/stephen/page/2
www.example.com/2012/url-rewriting/stephen/feed/rss
www.example.com/2012/url-rewriting/
www.example.com/2012/url-rewriting/page/2
www.example.com/2012/url-rewriting/feed/rss
www.example.com/2012/
www.example.com/2012/page/2
www.example.com/2012/feed/rss
add_permastruct
函数add_permastruct
函数接受四个参数:
name
– 一个独特的 slug 来识别您的永久结构struct
– 永久结构本身,例如'%year%/%category%/%author%
'with_front
– true 或 false。这与注册帖子类型时的参数相同ep_mask
– 请参阅下面的 EP 掩码部分这里需要提出一些关于使用 add_permastruct
的警告。首先:您需要确保自定义永久结构不会与 WordPress 的帖子和页面重写规则发生冲突。这可以通过在自定义永久结构前添加硬编码的内容来完成。例如:
'something-hard-coded/%year%/%monthnum%/%day%'
其次 - 规则按该顺序添加 - 因此,如果您的标签“太通用”,则后面的规则可能永远不会应用。例如,上述结构(您可以在“设置”->“永久链接”页面上尝试)通常效果很好,除了:
www.example.com/2012/page/2
被解释为作者“2”在 2012 年类别“页面”中的帖子页面。如果您想使用 add_permastruct
并让您的分页和提要规则很好地级联,那么您需要使用以下标签不是“通用”(我的意思是正则表达式不是通用的)。 %author%
和 %category%
是通用标记的很好示例,因为它们通常会匹配任何字符。
另一方面,年、月和日标签非常具体 - 仅匹配长度为 4 和 2 的正整数,因此我们可以使用 add_permastruct
作为我们的帖子类型的日期存档。由于日期标签的特定性质,我们需要将这些规则添加到帖子类型永久链接规则之前 - 因此在注册您的帖子类型之前立即添加以下内容:< /p>
// Please note that this will only work on WordPress 3.4+ http://core.trac.wordpress.org/ticket/19871 add_rewrite_tag('%book_cpt%','(book)s','post_type='); add_permastruct('book_archive', '%book_cpt%/%year%/%monthnum%/%day%');
在上面,自定义标签 %book_cpt%
充当硬编码的 slug,以区分此永久结构与其他规则(根据第一个警告)。仅当 %book_cpt%
匹配“books”时,生成的规则才适用,在这种情况下,“book”部分将被捕获并解释为 post_type
的值。请注意,自 WordPress 3.4 起,add_rewrite_tag
只接受第三个参数。但是,您可以使用以下解决方法:
global $wp_rewrite; $wp_rewrite->add_rewrite_tag('%book_cpt%','(book)s','post_type=');
设置图书档案后,您可能还会期望
www.example.com/books?year=2012
也会带我们到 2012 年图书档案馆。然而,对其进行测试后发现,它会将我们带到年后存档页面:
www.example.com/2012/
由于所谓的规范化,WordPress 将我们重定向到了另一个页面。
通常有许多 URL 可能指向您网站上的相同内容。例如:
www.example.com/year/2012 www.example.com/year/2012/page/1 www.example.com/2012/////////page/1 www.example.com/index.php/2012/ www.example.com/index.php////2012///page/1
所有这些都会将您带到 2012 年档案的第一页。从 SEO 的角度来看,这并不好——我们不能假设搜索引擎会将这些 URL 识别为相同的资源,并且这些 URL 最终可能会相互竞争。 Google 还可能会因重复内容而主动惩罚您,虽然它擅长确定这种重复内容何时不是“恶意”,但它仍然建议将这些多余的 URL 重定向到一个首选的“规范”(或标准)URL。这称为规范化。
这样做不仅有助于巩固链接流行度等评级,而且对您的用户也有帮助。如果他们使用丑陋或“不正确”的 URL,他们就会被带到“正确”的 URL,而他们的地址栏中的内容就是他们更有可能返回的内容。
自 2.1.0 起,WordPress 就开始处理规范重定向,甚至在原始查询返回 404 的情况下对所需内容进行有根据的猜测。不幸的是,在这种情况下,WordPress 重定向到错误的 URL。这是因为 WordPress 本身并不理解我们实际想要的 URL,并且它忽略了 URL 的“帖子类型”部分。幸运的是,我们可以使用 redirect_canonical
过滤器来解决这个问题。
add_filter('redirect_canonical', 'wptuts_redirect_canonical', 10, 2); function wptuts_redirect_canonical($redirect_url, $requested_url) { global $wp_rewrite; // Abort if not using pretty permalinks, is a feed, or not an archive for the post type 'book' if( ! $wp_rewrite->using_permalinks() || is_feed() || ! is_post_type_archive('book') ) return $redirect_url; // Get the original query parts $redirect = @parse_url($requested_url); $original = $redirect_url; if( !isset($redirect['query'] ) ) $redirect['query'] =''; // If is year/month/day - append year if ( is_year() || is_month() || is_day() ) { $year = get_query_var('year'); $redirect['query'] = remove_query_arg( 'year', $redirect['query'] ); $redirect_url = user_trailingslashit(get_post_type_archive_link('book')).$year; } // If is month/day - append month if ( is_month() || is_day() ) { $month = zeroise( intval(get_query_var('monthnum')), 2 ); $redirect['query'] = remove_query_arg( 'monthnum', $redirect['query'] ); $redirect_url .= '/'.$month; } // If is day - append day if ( is_day() ) { $day = zeroise( intval(get_query_var('day')), 2 ); $redirect['query'] = remove_query_arg( 'day', $redirect['query'] ); $redirect_url .= '/'.$day; } // If paged, apppend pagination if ( get_query_var('paged') > 0 ) { $paged = (int) get_query_var('paged'); $redirect['query'] = remove_query_arg( 'paged', $redirect['query'] ); if ( $paged > 1 ) $redirect_url .= user_trailingslashit("/page/$paged", 'paged'); } if( $redirect_url == $original ) return $original; // tack on any additional query vars $redirect['query'] = preg_replace( '#^??&*?#', '', $redirect['query'] ); if ( $redirect_url && !empty($redirect['query']) ) { parse_str( $redirect['query'], $_parsed_query ); $_parsed_query = array_map( 'rawurlencode', $_parsed_query ); $redirect_url = add_query_arg( $_parsed_query, $redirect_url ); } return $redirect_url; }
上面的函数虽然比较长,但是并不是很复杂。它可以进行改进,并且仅作为您可以使用 redirect_canonical
过滤器执行的操作的示例。它首先检查漂亮的永久链接是否打开,我们正在寻找我们的“书籍”档案并且它不是一个提要。然后依次检查:
www.example.com/books/[year]
www.example.com/books/[year]/[monthnum]
www.example.com/books/[year]/[monthnum]/[day]
“开箱即用”的帖子类型或分类法不支持的另一个功能是在永久链接结构中使用标签。虽然帖子类型(或分类法)重写数组的“slug”中使用的标签可以正确解释,但 WordPress 在生成永久链接时不会将这些标签替换为适当的值 - 我们需要自己替换它。然而,使用这样的标签也会破坏帖子类型的存档页面——所以我们将使用不同的方法。例如,假设我们希望自定义帖子类型“book”具有以下结构:
www.example.com/books/[some-genre]/[a-book]
我使用的是自定义分类法的示例,但相同的方法可以用于任何永久结构(例如包括日期、作者,甚至自定义字段)。首先我们添加重写规则:
function wptuts_custom_tags() { add_rewrite_rule("^books/([^/]+)/([^/]+)/?",'index.php?post_type=book&genre=$matches[1]&book=$matches[2]','top'); } add_action('init','wptuts_custom_tags');
现在,例如 www.example.com/book/fiction/the-wizard-of-oz
指向“the-wizard-of-oz
”一书。然而,WordPress 生成的永久链接仍然会生成 www.example.com/book/the-wizard-of-oz
。下一步是更改生成的永久链接。
当我们想在帖子永久链接结构中使用自定义标签时,我们在第一部分中做了类似的事情。然后我们使用了 post_link
过滤器;这次我们使用自定义帖子类型的等效项,即 post_type_link
过滤器。使用这个钩子,我们可以将我们的结构注入到书籍的永久链接中。
function wptuts_book_link( $post_link, $id = 0 ) { $post = get_post($id); if ( is_wp_error($post) || 'book' != $post->post_type || empty($post->post_name) ) return $post_link; // Get the genre: $terms = get_the_terms($post->ID, 'genre'); if( is_wp_error($terms) || !$terms ) { $genre = 'uncategorised'; } else { $genre_obj = array_pop($terms); $genre = $genre_obj->slug; } return home_url(user_trailingslashit( "books/$genre/$post->post_name" )); } add_filter( 'post_type_link', 'wptuts_book_link' , 10, 2 );
让我们扩展上面的永久链接结构以实现以下目标:
www.example.com/books/[some-genre]/[a-book]
www.example.com/books/[some-genre]
www.example.com/books/
回想一下,添加重写规则的顺序很重要。具体来说,先添加的规则优先。
因此,首先我们使用以下方法注册自定义分类“流派”:
$args = array( ... 'rewrite' => array( 'slug'=>'books' ), ... ) register_taxonomy('genre',$args);
这添加了以下永久结构:
www.example.com/books/[some-genre]
注册分类法后,我们将注册自定义帖子类型,如下所示:
$args = array( ... 'rewrite' => array( 'slug'=>'books' ), ... ) register_post_type('book',$args);
这将注册以下规则:
www.example.com/books/
(which we want)www.example.com/books/[a-book]
(我们没有)但是,第二个规则与我们注册分类法时添加的竞争规则相冲突(并被“击败”)。最终的结构是:
www.example.com/books/fiction/slug
www.example.com/books/slug
www.example.com/books/
之前,当我们查看注册帖子类型、分类法(或其他永久结构)时,WordPress 让我们指定自己的“ep_mask
”。那么它们是什么?
在第一部分中,我们研究了如何使用 add_rewrite_endpoint
添加端点。该函数中的第二个参数是一个常量(或使用按位运算符的常量组合),它确定添加端点的位置。例如:
add_rewrite_endpoint( 'form', EP_PAGES );
将重写 form(/(.*))?/?$
添加到每个页面永久链接并且:
add_rewrite_endpoint( 'json', EP_PAGES | EP_PERMALINKS);
将重写 json(/(.*))?/?$
添加到每个帖子和页面的永久链接。因此,这些常量指定一个“位置”(即“在帖子永久链接的末尾”),它们被称为端点掩码(或 ep 掩码)。
当您注册帖子类型时,WordPress 会注册一个永久结构 - 并与其关联一个端点掩码。然后,当生成重写规则时,它还会添加已添加到该端点掩码的所有端点重写规则。
例如,当 WordPress 注册默认的“页面”帖子类型时,它将端点掩码 EP_PAGES
与页面永久结构相关联。然后,添加到 EP_PAGES
的任何端点重写规则实际上都会添加到该页面永久结构中。注册帖子类型时,您可以指定自己的端点掩码,或使用现有的端点掩码。默认情况下,它是 EP_PERMALINKS
– 这意味着添加到 EP_PERMALINKS
的任何端点重写规则都会添加到您的自定义帖子类型的重写规则中。
当然,您可能不希望为您的帖子类型添加端点规则(在这种情况下,您可以使用端点掩码 EP_NONE
),或者您可能希望添加一些端点重写规则仅您的自定义帖子类型。为此,您首先需要创建一个端点掩码,它只不过是一个满足以下条件的常量:
2 的幂要求是必要的,因为 WordPress 使用二进制逻辑来确定应在何处添加端点规则。不幸的是,这几乎不可能检查,因此最好的建议是仅在必要时添加端点掩码并为其指定一个非常高的值(例如 221)。在撰写本文时,Core 使用了 20 至 213。
在注册帖子类型或分类之前定义端点掩码:
define('EP_BOOK', 8388608); // 8388608 = 2^23 $args = array( 'labels' => $labels, 'has_archive'=>true, 'rewrite' => array( 'slug'=>'books' 'with_front'=> false 'feed'=> true 'pages'=> true 'ep_mask'=> EP_BOOK ) ); register_post_type('book',$args); // Then you can endpoint rewrite rules to this endpoint mask add_rewrite_endpoint('loan', EP_BOOK);
(注意:上面使用的是 WordPress 3.4 参数。如果您使用的是旧版本的 WordPress,则必须使用现已弃用的 permalink_epmask
。)。从 WordPress 3.4 开始,您也可以在注册分类法时指定端点掩码。
在本教程中,我介绍了帖子类型和分类法的重写 API 的基础知识,还有一些更高级的主题。 WordPress 对重写的处理(必然)很复杂,理解它的最佳方法是深入研究源代码并使用您学到的知识和重写分析器插件对其进行测试。
目前,有几个与 Rewrite API 相关的票据正在通过 WordPress 开发 Trac 进行。将来,我们会看到一种更简单且无冲突的处理端点掩码的方式。