阐述 WordPress `wp_update_term()` 函数的源码:如何更新分类术语,并处理其别名和计数。

各位老铁,大家好!今天咱们聊聊 WordPress 里一个低调但关键的函数:wp_update_term()。这玩意儿就像分类目录和标签的幕后推手,负责更新它们的各种属性。咱们一起扒一扒它的源码,看看它是怎么玩转分类术语的,特别是别名(slug)和计数这两块。

一、wp_update_term():你的分类术语变形金刚

wp_update_term() 函数位于 wp-includes/taxonomy.php 文件中。它的作用,简单来说,就是修改已存在的分类术语。它不仅能改名字,还能改描述,最重要的是,它能帮你处理别名冲突,并更新分类术语的计数。

函数签名:

/**
 * Updates a term.
 *
 * @since 3.0.0
 *
 * @param int          $term_id Term ID.
 * @param string       $taxonomy Taxonomy slug.
 * @param array|string $args {
 *     Optional. An array or string of arguments. Default empty array.
 *
 *     @type string $name        Term name. Default null.
 *     @type string $slug        Term slug. Default null.
 *     @type int    $parent      Term parent ID. Default null.
 *     @type string $description Term description. Default null.
 * }
 * @return WP_Error|array {
 *     @type int $term_id        The term ID.
 *     @type int $term_taxonomy_id The term taxonomy ID.
 * }
 */
function wp_update_term( $term_id, $taxonomy, $args = array() ) {
  // 函数体
}

参数说明:

  • $term_id:要更新的术语的 ID。这是必须的,不然你让它更新谁?
  • $taxonomy:术语所属的分类法(taxonomy)。比如 ‘category’、’post_tag’ 等。这也是必须的。
  • $args:一个数组,包含要更新的字段。可以包括 name(术语名称)、slug(别名)、parent(父级术语 ID)、description(描述)。

返回值:

  • 成功时,返回一个数组,包含 $term_id$term_taxonomy_id
  • 失败时,返回一个 WP_Error 对象,里面包含了错误信息。

二、源码解剖:一步一步来看

现在,咱们深入到 wp_update_term() 的源码中,看看它是怎么工作的。

function wp_update_term( $term_id, $taxonomy, $args = array() ) {
  global $wpdb;

  // 1. 参数验证
  $term_id = (int) $term_id;
  if ( empty( $term_id ) ) {
    return new WP_Error( 'invalid_term_id', __( 'Empty term ID.' ) );
  }

  if ( empty( $taxonomy ) ) {
    return new WP_Error( 'invalid_taxonomy', __( 'Empty taxonomy.' ) );
  }

  $term = get_term( $term_id, $taxonomy );
  if ( is_wp_error( $term ) ) {
    return $term;
  }

  if ( empty( $term ) ) {
    return new WP_Error( 'invalid_term', __( 'Non-existent term.' ) );
  }

  // 2. 准备参数
  $args = wp_parse_args( $args );

  $name        = null;
  $slug        = null;
  $term_group  = null;
  $description = null;
  $parent      = null;

  if ( isset( $args['name'] ) ) {
    $name = trim( $args['name'] );
  }

  if ( isset( $args['slug'] ) ) {
    $slug = trim( $args['slug'] );
  }

  if ( isset( $args['term_group'] ) ) {
    $term_group = (int) $args['term_group'];
  }

  if ( isset( $args['description'] ) ) {
    $description = trim( $args['description'] );
  }

  if ( isset( $args['parent'] ) ) {
    $parent = (int) $args['parent'];
  }

  // 3. 数据清理和验证
  if ( ! empty( $name ) ) {
    $name = sanitize_term_field( 'name', $name, $term_id, $taxonomy, 'db' );
  }

  if ( isset( $slug ) ) { // Allow empty slug to be passed.
    $slug = sanitize_term_field( 'slug', $slug, $term_id, $taxonomy, 'db' );
    if ( empty( $slug ) && '0' !== $slug ) {
      $slug = sanitize_title( $name );
    }
  }

  if ( isset( $parent ) ) {
    if ( $parent == $term_id ) {
      return new WP_Error( 'invalid_parent', __( 'Cannot set parent to the current term.' ) );
    }

    // Check to prevent parent loop
    $ancestors = get_ancestors( $term_id, $taxonomy, 'term_id' );
    if ( is_wp_error( $ancestors ) ) {
      return $ancestors;
    }

    if ( in_array( $parent, $ancestors, true ) ) {
      return new WP_Error( 'invalid_parent', __( 'Cannot set parent to a child term.' ) );
    }
  }

  // 4. 别名 (Slug) 冲突处理
  if ( null !== $slug ) {
    $original_slug = $slug;
    $slug          = wp_unique_term_slug( $slug, (object) array( 'taxonomy' => $taxonomy, 'term_id' => $term_id ) );

    if ( is_wp_error( $slug ) ) {
      return $slug;
    }
  }

  // 5. 构建 SQL 查询
  $fields = array();
  $formats = array();

  if ( null !== $name ) {
    $fields['name'] = '%s';
    $formats[] = $name;
  }

  if ( null !== $slug ) {
    $fields['slug'] = '%s';
    $formats[] = $slug;
  }

  if ( null !== $term_group ) {
    $fields['term_group'] = '%d';
    $formats[] = $term_group;
  }

  // 6. 执行更新
  if ( ! empty( $fields ) ) {
    $sql = "UPDATE $wpdb->terms SET";
    $sets = array();
    foreach ( $fields as $field => $format ) {
      $sets[] = "`$field` = $format";
    }
    $sql .= ' ' . implode( ', ', $sets );
    $sql .= " WHERE term_id = %d";
    $formats[] = $term_id;

    $sql = $wpdb->prepare( $sql, $formats );
    $result = $wpdb->query( $sql );

    if ( false === $result ) {
      return new WP_Error( 'db_update_error', __( 'Could not update term in the database.' ), $wpdb->last_error );
    }
  }

  // 7. 更新 term_taxonomy 表
  $fields = array();
  $formats = array();

  if ( null !== $description ) {
    $fields['description'] = '%s';
    $formats[] = $description;
  }

  if ( null !== $parent ) {
    $fields['parent'] = '%d';
    $formats[] = $parent;
  }

  if ( ! empty( $fields ) ) {
    $sql = "UPDATE $wpdb->term_taxonomy SET";
    $sets = array();
    foreach ( $fields as $field => $format ) {
      $sets[] = "`$field` = $format";
    }
    $sql .= ' ' . implode( ', ', $sets );
    $sql .= " WHERE term_id = %d AND taxonomy = %s";
    $formats[] = $term_id;
    $formats[] = $taxonomy;

    $sql = $wpdb->prepare( $sql, $formats );
    $result = $wpdb->query( $sql );

    if ( false === $result ) {
      return new WP_Error( 'db_update_error', __( 'Could not update term taxonomy in the database.' ), $wpdb->last_error );
    }
  }

  // 8. 清理缓存
  wp_cache_delete( $term_id, $taxonomy . '_term_id_cache' );

  $tt_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id = %d AND taxonomy = %s", $term_id, $taxonomy ) );

  foreach ( $tt_ids as $tt_id ) {
    clean_term_cache( $tt_id, $taxonomy );
  }

  // 9. 更新计数
  if ( null !== $parent ) {
    _update_term_hierarchy( $taxonomy ); // This function deals with clearing the cache.
  } else {
    _update_generic_term_count( array( $term_id ), $taxonomy );
  }

  $term_taxonomy_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id = %d AND taxonomy = %s", $term_id, $taxonomy ) );

  /**
   * Fires after a term is updated.
   *
   * @since 4.4.0
   *
   * @param int    $term_id Term ID.
   * @param string $taxonomy Taxonomy slug.
   */
  do_action( 'edit_term', $term_id, $taxonomy );

  /**
   * Fires after a term is updated.
   *
   * @since 2.3.0
   * @deprecated 4.4.0 Use {@see 'edit_term'}.
   *
   * @param int    $term_id Term ID.
   * @param string $taxonomy Taxonomy slug.
   */
  do_action( 'edited_term', $term_id, $taxonomy );

  /**
   * Fires after a specific term is updated.
   *
   * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
   *
   * @since 3.0.0
   *
   * @param int    $term_id Term ID.
   * @param string $taxonomy Taxonomy slug.
   */
  do_action( "edit_{$taxonomy}", $term_id );

  /**
   * Fires after a specific term is updated.
   *
   * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
   *
   * @since 2.3.0
   * @deprecated 4.4.0 Use {@see 'edit_term'}.
   *
   * @param int    $term_id Term ID.
   * @param string $taxonomy Taxonomy slug.
   */
  do_action( "edited_{$taxonomy}", $term_id );

  return array( 'term_id' => $term_id, 'term_taxonomy_id' => $term_taxonomy_ids[0] );
}

流程分解:

  1. 参数验证:

    • 确保 $term_id$taxonomy 不为空,且 $term_id 是一个整数。
    • 使用 get_term() 函数获取术语对象,如果术语不存在,则返回错误。
  2. 准备参数:

    • 使用 wp_parse_args() 函数将 $args 数组与默认参数合并。
    • 提取 nameslugterm_groupdescriptionparent 等参数的值。
  3. 数据清理和验证:

    • 使用 sanitize_term_field() 函数对 nameslug 进行清理,防止 XSS 攻击。
    • 如果 slug 为空,则使用 sanitize_title() 函数根据 name 生成一个。
    • 如果设置了 parent,则检查是否会出现父子循环引用。
  4. 别名(Slug)冲突处理:

    • 这是整个函数的核心部分之一。使用 wp_unique_term_slug() 函数来确保别名是唯一的。
    • 如果别名已经存在,wp_unique_term_slug() 会自动在别名后面添加一个数字,直到找到一个唯一的别名。
  5. 构建 SQL 查询:

    • 根据要更新的字段,构建 SQL UPDATE 查询语句。
    • 使用 $wpdb->prepare() 函数对查询语句进行预处理,防止 SQL 注入。
  6. 执行更新:

    • 使用 $wpdb->query() 函数执行 SQL 查询,更新 wp_terms 表中的数据。
  7. 更新 term_taxonomy 表:

    • 如果 $description$parent 发生了变化,则更新 wp_term_taxonomy 表。
  8. 清理缓存:

    • 使用 wp_cache_delete() 函数删除与该术语相关的缓存,确保下次获取术语时,能得到最新的数据。
    • 使用 clean_term_cache() 函数清理术语缓存。
  9. 更新计数:

    • 如果 $parent 发生了变化,则调用 _update_term_hierarchy() 函数更新分类术语的层级关系。
    • 否则,调用 _update_generic_term_count() 函数更新分类术语的计数。
  10. 触发 Action Hook:

    • 触发 edit_termedited_termedit_{$taxonomy}edited_{$taxonomy} action hook,允许其他插件或主题对术语更新进行干预。
  11. 返回结果:

    • 返回一个数组,包含 $term_id$term_taxonomy_id

三、重点解析:别名(Slug)和计数

咱们重点看看 wp_update_term() 函数是如何处理别名冲突和更新计数的。

1. 别名(Slug)冲突处理:wp_unique_term_slug()

wp_unique_term_slug() 函数位于 wp-includes/taxonomy.php 文件中,它的作用是生成一个唯一的分类术语别名。

function wp_unique_term_slug( $slug, $term ) {
  global $wpdb;

  $taxonomy = $term->taxonomy;
  $term_id  = isset( $term->term_id ) ? $term->term_id : 0;

  $original_slug = $slug;
  $i             = 2;

  while ( true ) {
    $found_slug = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.slug = %s AND t.term_id != %d LIMIT 1", $taxonomy, $slug, $term_id ) );

    if ( ! $found_slug ) {
      break;
    }

    $slug = $original_slug . "-$i";
    $i++;
  }

  return $slug;
}

工作原理:

  • 它会首先检查给定的别名 $slug 是否已经存在于指定的分类法 $taxonomy 中。
  • 如果存在,它会在别名后面添加一个数字,然后再次检查是否存在。
  • 这个过程会一直重复,直到找到一个唯一的别名。

举个例子:

假设你有一个分类目录,名称为 "WordPress教程",别名也为 "wordpress教程"。现在你想把另一个分类目录的别名也设置为 "wordpress教程"。wp_unique_term_slug() 函数会帮你生成一个唯一的别名,比如 "wordpress教程-2"。

2. 更新计数:_update_generic_term_count()

_update_generic_term_count() 函数位于 wp-includes/taxonomy.php 文件中,它的作用是更新分类术语的计数,也就是有多少篇文章属于该分类术语。

function _update_generic_term_count( $terms, $taxonomy ) {
  global $wpdb;

  $object_types = (array) apply_filters( 'get_objects_for_term', array( 'post' ), $terms, $taxonomy );

  foreach ( $object_types as $object_type ) {
    $count = (array) $wpdb->get_results( $wpdb->prepare( "SELECT term_taxonomy_id, count(*) AS c FROM $wpdb->term_relationships WHERE term_taxonomy_id IN (SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id IN (%s) AND taxonomy = %s) AND object_id IN (SELECT ID FROM $wpdb->posts WHERE post_type = %s AND post_status IN ('publish', 'future', 'draft', 'pending', 'private')) GROUP BY term_taxonomy_id", implode( ',', array_map( 'intval', $terms ) ), $taxonomy, $object_type ), ARRAY_A );

    foreach ( $count as $c ) {
      $wpdb->update( $wpdb->term_taxonomy, array( 'count' => $c['c'] ), array( 'term_taxonomy_id' => $c['term_taxonomy_id'] ) );
      wp_cache_delete( $c['term_taxonomy_id'], 'term_taxonomy' );
    }
  }

  wp_cache_delete_multiple( $terms, $taxonomy . '_term_id_cache' );
}

工作原理:

  • 它首先获取与该分类术语关联的对象类型(通常是 ‘post’)。
  • 然后,它查询数据库,统计有多少篇文章属于该分类术语。
  • 最后,它更新 wp_term_taxonomy 表中的 count 字段,并清理缓存。

举个例子:

假设你有一个分类目录 "WordPress技巧",目前有 5 篇文章属于该分类目录。当你发布一篇新的文章,并将其添加到 "WordPress技巧" 分类目录时,_update_generic_term_count() 函数会将 "WordPress技巧" 分类目录的计数更新为 6。

四、实际应用:代码示例

咱们来看几个实际的例子,演示如何使用 wp_update_term() 函数。

示例 1:更新分类目录的名称和描述

$term_id = 123; // 要更新的分类目录的 ID
$taxonomy = 'category';

$args = array(
  'name' => 'PHP高级技巧',
  'description' => '这里分享一些关于 PHP 高级开发的技巧和经验。'
);

$result = wp_update_term( $term_id, $taxonomy, $args );

if ( is_wp_error( $result ) ) {
  echo '更新失败:' . $result->get_error_message();
} else {
  echo '更新成功!';
}

示例 2:更新标签的别名

$term_id = 456; // 要更新的标签的 ID
$taxonomy = 'post_tag';

$args = array(
  'slug' => 'php-gaoji'
);

$result = wp_update_term( $term_id, $taxonomy, $args );

if ( is_wp_error( $result ) ) {
  echo '更新失败:' . $result->get_error_message();
} else {
  echo '更新成功!';
}

示例 3:更新分类目录的父级分类

$term_id = 789; // 要更新的分类目录的 ID
$taxonomy = 'category';

$args = array(
  'parent' => 10 // 父级分类目录的 ID
);

$result = wp_update_term( $term_id, $taxonomy, $args );

if ( is_wp_error( $result ) ) {
  echo '更新失败:' . $result->get_error_message();
} else {
  echo '更新成功!';
}

五、注意事项:踩坑指南

  • 权限问题: 确保当前用户有足够的权限来更新分类术语。
  • 数据验证: 在调用 wp_update_term() 函数之前,一定要对数据进行验证,防止恶意数据。
  • 缓存问题: 更新分类术语后,一定要清理缓存,确保下次获取术语时,能得到最新的数据。
  • 别名冲突: 注意别名冲突问题,wp_unique_term_slug() 函数会帮你处理,但是你最好还是提前规划好别名。
  • term_group 的使用: term_group 用于关联具有相似含义但属于不同分类法的术语。 用的不多。

六、总结:wp_update_term(),分类术语的守护者

wp_update_term() 函数是 WordPress 中一个非常重要的函数,它负责更新分类术语的各种属性,包括名称、别名、描述、父级分类等。理解它的工作原理,可以帮助你更好地管理 WordPress 的分类目录和标签,并避免一些常见的错误。特别是要理解别名冲突处理和计数更新这两个核心功能。

希望今天的讲解对大家有所帮助! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注