首先,您需要一种从分类法(品牌)中获取术语的方法,该分类法与与另一个分类法(类别)关联的帖子相关。
最快的“核心”方法是:
获取所有类别的列表,为每个类别获取相关产品,循环产品并检索相关品牌。此工作流需要嵌套循环,并且a + b + 1
查询,其中a
是类别数,并且b
是产品的数量。
因此,如果您有200篇文章和20个类别,这个函数将在所有类别和所有文章上运行221个数据库查询和嵌套循环。
请注意,我们不能使用分页,因为我们需要遍历所有产品,因此如果您有数千种产品,此功能可能会破坏您的站点性能。
我们能做些什么来改善这种情况?2件事:
第一部分,我们可以编写一个自定义SQL查询,使用适当的JOIN和WHERE子句从两个分类中获取相关术语,而不涉及POST。
看看这个函数:
function term_related_terms( $term, $term_tax, $target_tax, $onlyparent = FALSE ) {
if ( ! taxonomy_exists( $term_tax ) || ! taxonomy_exists( $target_tax ) ) {
return FALSE;
}
$term_tax_obj = $term_tax_id = FALSE;
if ( is_numeric( $term ) ) {
$term_tax_obj = get_term( $term, $term_tax );
} elseif ( is_string( $term ) ) {
$term_tax_obj = get_term_by( \'slug\', $term, $term_tax );
} elseif ( is_object( $term ) ) {
$term_tax_obj = $term;
}
if ( is_object( $term_tax_obj ) && isset( $term_tax_obj->term_taxonomy_id ) ) {
$term_tax_id = $term_tax_obj->term_taxonomy_id;
}
if ( ! $term_tax_id ) return FALSE;
global $wpdb;
$query = $wpdb->prepare(
"SELECT t.term_id, t.name, t.slug, tt.* FROM {$wpdb->terms} t
INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id
INNER JOIN {$wpdb->term_relationships} tr ON tr.term_taxonomy_id = tt.term_taxonomy_id
INNER JOIN {$wpdb->term_relationships} tr2 ON tr2.object_id = tr.object_id
INNER JOIN {$wpdb->term_taxonomy} tt2 ON tr2.term_taxonomy_id = tt2.term_taxonomy_id
WHERE tt.taxonomy = %s AND tt2.taxonomy = %s AND tr2.term_taxonomy_id = %s",
$target_tax, $term_tax, $term_tax_id
);
if ( $onlyparent ) $query .= " AND tt.parent = 0";
return $wpdb->get_results( $query .= " GROUP BY tt.term_taxonomy_id");
}
此函数获取3个必需参数:
术语(可以是术语id、slug或object)
该术语所属的分类法属于另一个分类法名称,并返回与具有第一个分类法中给定术语的帖子相关联的第二个分类法中的所有术语。可选的$onlyparent
参数,可以猜测,如果设置为true,则函数只返回第二个分类法中的顶级术语。
这意味着调用此函数如下:
term_related_terms( $cadID, \'category\', \'brand\' );
我们可以获得与具有给定$cadID
, 仅运行一个db查询。对所有类别运行此函数,我们可以获得所需的所有数据,运行n
+ 1查询,其中n
是类别数,附加查询用于获取所有类别。
使用前面的示例(200个产品和20个类别),我们需要运行21个查询,而不是221个查询,并且我们还需要运行一个简单的循环,而不是嵌套的循环。
改进是显著的,但21 db查询不是一项容易的任务,我们需要缓存结果。
让我们编写一个函数,在第一次运行时调用前一个函数,将结果缓存在transient, 并在后续调用中返回缓存的结果:
function category_brand_menu_data() {
// be sure following slugs match your setup
$category_slug = \'category\';
$brand_slug = \'brand\';
$cache = get_transient( \'category_brand_menu_data\' ); // try to get from cache
if ( ! empty( $cache ) ) {
return $cache;
}
// firts get the categories
$cats = get_terms( $category_slug );
$data = array();
if ( ! empty( $cats ) ) {
// get brands related to each category using term_related_terms()
foreach ( $cats as $cat ) {
$brands = term_related_terms( $cat, $category_slug, $brand_slug );
if ( ! empty( $brands ) ) {
$data[$cat->term_id] = array(
\'name\' => $cat->name,
\'slug\' => $cat->slug,
\'brands\' => $brands
);
}
}
if ( ! empty( $brands ) ) {
set_transient( \'category_brand_menu_data\', $data ); // cache data for next call
}
}
return $data;
}
现在我们实现了缓存,性能有了很大的提高,但是我们需要一种机制来在相关事件发生时使缓存失效。更准确地说,相关的是:
产品与类别关联产品与品牌关联产品与类别之间的关联被删除产品与品牌之间的关联被删除类别术语被删除品牌术语被删除前4个事件可以使用\'set_object_terms\'
动作,将分类传递给挂钩函数作为第四个参数。
剩余的2个事件可以使用\'delete_term\'
操作,将分类传递给挂钩函数作为第三个参数。
因此,让我们编写一个使缓存无效(即删除瞬态)的函数,并将其添加到两个挂钩中:
function category_brand_menu_clean( $a, $b, $tax_del, $tax_upd = NULL ) {
// be sure following slugs match your setup
$category_slug = \'category\';
$brand_slug = \'brand\';
// \'delete_term\' pass taxonomy as 3rd argument, \'set_object_terms\' as 4th
$tax = ( current_filter() === \'delete_term\' ) ? $tax_del : $tax_upd;
if ( in_array( $tax, array( $category_slug, $brand_slug ) ) ) {
delete_transient( \'category_brand_menu_data\' );
category_brand_menu_data();
}
}
add_action( \'set_object_terms\', \'category_brand_menu_clean\', 20, 4 );
add_action( \'delete_term\', \'category_brand_menu_clean\', 20, 3 );
该函数在删除缓存后调用category_brand_menu_data()
所以缓存被再次构建,当从前端调用函数时,将从性能良好的缓存中获取输出。现在我们有了一个performant函数来检索术语关联,我们只需要一个使用检索到的数据来显示菜单的函数:
function category_brand_menu() {
// be sure following slugs match your setup
$category_slug = \'category\';
$brand_slug = \'brand\';
// get data (can be cached or not)
$data = category_brand_menu_data();
if ( empty( $data ) ) return;
// set html format according to your needs
$format = \'<nav><ul class="menu">%s</ul></nav>\';
$parentformat = \'<li><a href="%s">%s</a><ul class="submenu">%s</ul></li>\';
$itemformat = \'<li><a href="%s">%s</a></li>\';
$menu = \'\';
$tax_obj = get_taxonomy( $category_slug );
$query_var = $tax_obj->query_var;
// loop through data retrieved to build menu html string
foreach ( $data as $catid => $cat_data ) {
$cat_link = get_term_link( $catid, $category_slug );
$items = \'\';
foreach( $cat_data[\'brands\'] as $brand ) {
$t_link = get_term_link( $brand, $brand_slug );
// add category query arg to brand link, so when link is clicked
// will show archives having both terms: the category and the brand
$link = add_query_arg( array( $query_var => $cat_data[\'slug\'] ), $t_link );
$items .= sprintf( $itemformat, $link, $brand->name );
}
$menu .= sprintf( $parentformat, $cat_link, $cat_data[\'name\'], $items );
}
// print the menu
printf( $format, $menu );
}
在要输出菜单的模板中,只需使用全新的模板标记,如下所示:<?php category_brand_menu(); ?>
你就完了。请注意,在上述所有函数中,我设置了两个变量:
$category_slug = \'category\';
$brand_slug = \'brand\';
因为我不确定哪些是适合您的分类法的正确slug,所以在测试我的代码之前,请确保在所有函数中设置正确的slug。