Menu Multiple Taxonomies

时间:2014-04-16 作者:Charles

我有一个post\\u类型的“products”,它同时具有“Category”和“Brand”分类法。每种产品都有一个类别和一个品牌。

我想显示一个菜单,首先列出类别作为顶级菜单,然后列出每个类别中包含的品牌作为子菜单。

每个子菜单应仅显示与该特定顶级类别中的产品相关的品牌。

然后,子菜单中的链接应转到仅显示所选品牌和类别中的产品的页面。。。但我想我可以用wp\\u查询来解决这一部分。大多数情况下,我对菜单问题感到困惑。

非常感谢您的帮助!

2 个回复
SO网友:gmazzap

首先,您需要一种从分类法(品牌)中获取术语的方法,该分类法与与另一个分类法(类别)关联的帖子相关。

最快的“核心”方法是:

获取所有类别的列表,为每个类别获取相关产品,循环产品并检索相关品牌。此工作流需要嵌套循环,并且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。

  • SO网友:Charles

    我知道了如何通过一个MySQL查询提取所有类别/品牌组合。

    然后,我通过循环组合来构建所有类别及其关联品牌的阵列。以下是查询:

    $brands_categories = $wpdb->get_results("
        SELECT te2.term_id AS category_id, te2.slug AS category_slug, te2.name AS category_name, t2.parent AS category_parent_id, te1.slug AS brand_slug, te1.name AS brand_name
        FROM wp_posts p
    
        LEFT JOIN wp_term_relationships r1 ON p.ID = r1.object_id
        CROSS JOIN wp_term_taxonomy t1 ON r1.term_taxonomy_id = t1.term_taxonomy_id AND t1.taxonomy = \'brands\'
        LEFT JOIN wp_terms te1 ON t1.term_id = te1.term_id
    
        LEFT JOIN wp_term_relationships r2 ON p.ID = r2.object_id
        CROSS JOIN wp_term_taxonomy t2 ON r2.term_taxonomy_id = t2.term_taxonomy_id AND t2.taxonomy = \'product_cat\'
        LEFT JOIN wp_terms te2 ON t2.term_id = te2.term_id
    
        WHERE p.post_status = \'publish\' and p.post_type = \'product\'
    
        GROUP BY te2.slug, te1.slug
    
        ORDER BY te2.slug ASC
    ", ARRAY_A);
    
    所以这基本上

    category_id
    category_slug
    category_name
    brand_slug
    brand_name
    
    对于所有组合。

    接下来,我循环并构建一个干净的类别数组,将品牌列为子数组。

    foreach ($brands_categories as $brand_category) {
        if (!is_array($categories[$brand_category[\'category_id\']])) {
            $categories[$brand_category[\'category_id\']] = array(
                "slug" => $brand_category[\'category_slug\'],
                "name" => $brand_category[\'category_name\'],
                "parent" => $brand_category[\'category_parent_id\'],
                "brands" => array()
            );
        }
        $categories[$brand_category[\'category_id\']][\'brands\'][] = array(
            "slug" => $brand_category[\'brand_slug\'],
            "name" => $brand_category[\'brand_name\']
        );
    }
    
    谢谢你的帮助!这肯定比应该的要复杂得多。在自定义php/mysql设置中,这将是一个非常简单的问题。

    结束