探究 WordPress `wp_delete_term()` 函数的源码:如何删除分类术语,并处理其下的文章和子分类。

各位观众老爷,晚上好!我是你们今晚的 WordPress “拆家” 指南特约讲解员,今天咱们不聊情怀,专啃硬骨头,一起扒一扒 wp_delete_term() 这个“辣手摧花”的函数,看看它是如何把 WordPress 的分类术语,以及跟它沾亲带故的文章和子分类,一锅端走的。

一、开场白:wp_delete_term() 是个什么玩意儿?

简单来说,wp_delete_term() 就是 WordPress 用来删除分类、标签等术语的利器。你辛辛苦苦创建的分类,不想要了?一个命令搞定!但别高兴太早,它可不是简单的“删除”操作,背后还隐藏着一系列复杂而精妙的逻辑。

二、源码剖析:一步一步揭开它的神秘面纱

好了,废话不多说,直接上代码(基于 WordPress 6.4 版本):

function wp_delete_term( $term, $taxonomy, $args = array() ) {
    $term = (int) $term;

    if ( ! taxonomy_exists( $taxonomy ) ) {
        return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    }

    $term_obj = get_term( $term, $taxonomy );

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

    if ( ! $term_obj ) {
        return false;
    }

    $defaults = array( 'default' => 0 );
    $args     = wp_parse_args( $args, $defaults );

    /**
     * Fires immediately before a term is deleted from the database.
     *
     * @since 2.9.0
     *
     * @param int    $term     Term ID.
     * @param string $taxonomy Taxonomy name.
     */
    do_action( 'pre_delete_term', $term, $taxonomy );

    $id = $term_obj->term_id;

    /**
     * Filters whether to short-circuit deleting the term.
     *
     * Passing a non-null value will effectively short-circuit the function,
     * returning the passed value instead.
     *
     * @since 3.5.0
     *
     * @param mixed  $pre      Whether to short-circuit the deletion. Default null.
     * @param int    $term     Term ID.
     * @param string $taxonomy Taxonomy name.
     */
    $pre = apply_filters( 'pre_delete_term', null, $term, $taxonomy );
    if ( null !== $pre ) {
        return $pre;
    }

    $object_terms = get_objects_in_term( $term, $taxonomy );

    if ( ! is_wp_error( $object_terms ) ) {
        foreach ( (array) $object_terms as $object ) {
            wp_remove_object_terms( $object, $term, $taxonomy );
        }
    }

    $term_children = get_terms(
        array(
            'taxonomy' => $taxonomy,
            'parent'   => $term,
            'get'      => 'all',
            'fields'   => 'ids',
        )
    );

    if ( ! is_wp_error( $term_children ) && ! empty( $term_children ) ) {
        foreach ( $term_children as $child ) {
            wp_delete_term( $child, $taxonomy, $args );
        }
    }

    $deleted = wp_delete_term_taxonomy( $term, $taxonomy );

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

    /**
     * Fires after a term is deleted from the database.
     *
     * @since 2.2.0
     *
     * @param int    $term     Term ID.
     * @param string $taxonomy Taxonomy name.
     * @param mixed  $deleted  The result of the deletion.
     */
    do_action( 'delete_term', $term, $taxonomy, $deleted );

    /**
     * Fires after a term is deleted from the database.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the taxonomy slug.
     *
     * @since 2.2.0
     *
     * @param int    $term     Term ID.
     * @param mixed  $deleted  The result of the deletion.
     */
    do_action( "delete_$taxonomy", $term, $deleted );

    if ( ! empty( $args['default'] ) ) {
        $default = (int) $args['default'];
        wp_set_object_terms( $object_terms, $default, $taxonomy );
    }

    wp_cache_delete_last_changed( 'terms', $taxonomy );

    return $deleted;
}

接下来,我们逐行分析,就像剥洋葱一样,一层一层揭开它的真面目:

第一层:参数校验和准备工作

  • $term = (int) $term;:强制将 $term 转换为整数,确保传入的是 Term ID。
  • if ( ! taxonomy_exists( $taxonomy ) ) { ... }:检查 $taxonomy 是否存在,不存在就直接返回错误,告诉你“老铁,没这个分类!”
  • $term_obj = get_term( $term, $taxonomy );:通过 Term ID 和 Taxonomy 获取 Term 对象。
  • if ( is_wp_error( $term_obj ) ) { ... }:如果获取 Term 对象失败,说明 Term 不存在,直接返回错误。
  • if ( ! $term_obj ) { ... }:再来一次,以防万一 Term 对象为空,直接返回 false。
  • $defaults = array( 'default' => 0 ); $args = wp_parse_args( $args, $defaults );:处理参数,主要是设置默认的“default”参数,这个参数后面会用到,用于指定删除 Term 后,文章应该被分配到的默认 Term。

第二层:钩子(Hooks)初体验

  • do_action( 'pre_delete_term', $term, $taxonomy );:在删除 Term 之前,触发 pre_delete_term 动作钩子。这个钩子允许开发者在删除 Term 之前执行一些自定义操作,比如记录日志、发送通知等等。
  • $pre = apply_filters( 'pre_delete_term', null, $term, $taxonomy ); if ( null !== $pre ) { ... }:这是个过滤器钩子,允许开发者通过 pre_delete_term 过滤器来“短路”删除操作。如果过滤器返回非 null 值,则直接返回该值,不再执行后续的删除操作。这个功能非常强大,可以用来实现一些复杂的权限控制或者数据验证。

第三层:解除文章与 Term 的关联

  • $object_terms = get_objects_in_term( $term, $taxonomy );:获取所有与该 Term 关联的文章 ID。
  • if ( ! is_wp_error( $object_terms ) ) { ... }:遍历所有文章 ID,并使用 wp_remove_object_terms() 函数将这些文章与该 Term 解除关联。这一步至关重要,否则删除 Term 后,文章就“脱离组织”了。

第四层:递归删除子分类

  • $term_children = get_terms( ... );:获取所有该 Term 的子分类。
  • if ( ! is_wp_error( $term_children ) && ! empty( $term_children ) ) { ... }:如果存在子分类,则递归调用 wp_delete_term() 函数,将子分类也一并删除。这个递归操作保证了删除 Term 时,其下的所有子分类都会被干净彻底地清除。

第五层:真正删除 Term 的核心函数

  • $deleted = wp_delete_term_taxonomy( $term, $taxonomy );:调用 wp_delete_term_taxonomy() 函数,真正执行删除 Term 的操作。这个函数才是删除 Term 的“幕后黑手”,它负责从数据库中删除 Term 的相关数据。

第六层:删除后的扫尾工作

  • do_action( 'delete_term', $term, $taxonomy, $deleted );:删除 Term 之后,触发 delete_term 动作钩子。
  • do_action( "delete_$taxonomy", $term, $deleted );:触发动态动作钩子 delete_$taxonomy。这两个钩子允许开发者在删除 Term 之后执行一些自定义操作,比如更新缓存、发送通知等等。
  • if ( ! empty( $args['default'] ) ) { ... }:如果设置了 default 参数,则将所有与该 Term 关联的文章,重新分配到指定的默认 Term。
  • wp_cache_delete_last_changed( 'terms', $taxonomy );:清除 Term 缓存。
  • return $deleted;:返回删除结果。

三、核心函数:wp_delete_term_taxonomy() 的剖析

wp_delete_term() 函数的灵魂在于 wp_delete_term_taxonomy() 函数,它负责执行真正的数据库删除操作。我们继续深入分析这个函数:

function wp_delete_term_taxonomy( $term, $taxonomy ) {
    global $wpdb;

    $term = (int) $term;

    $taxonomy = sanitize_key( $taxonomy );

    /**
     * Fires immediately before a term taxonomy ID is deleted.
     *
     * @since 5.1.0
     *
     * @param int    $term     Term ID.
     * @param string $taxonomy Taxonomy name.
     */
    do_action( 'pre_delete_term_taxonomy', $term, $taxonomy );

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

    if ( ! $tt_id ) {
        return false;
    }

    /**
     * Filters whether to short-circuit deleting the term taxonomy.
     *
     * Passing a non-null value will effectively short-circuit the function,
     * returning the passed value instead.
     *
     * @since 5.1.0
     *
     * @param mixed  $pre      Whether to short-circuit the deletion. Default null.
     * @param int    $term     Term ID.
     * @param string $taxonomy Taxonomy name.
     */
    $pre = apply_filters( 'pre_delete_term_taxonomy', null, $term, $taxonomy );
    if ( null !== $pre ) {
        return $pre;
    }

    $term_taxonomy_id = (int) $tt_id;

    /**
     * Fires before term taxonomy is deleted.
     *
     * @since 2.3.0
     *
     * @param int    $term_taxonomy_id Term taxonomy ID.
     * @param string $taxonomy         Taxonomy name.
     */
    do_action( 'delete_term_taxonomy', $term_taxonomy_id, $taxonomy );

    $result = $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term_taxonomy_id ) );

    if ( ! $result ) {
        return false;
    }

    $wpdb->delete( $wpdb->termmeta, array( 'term_id' => $term ) );

    wp_cache_delete_last_changed( 'terms', $taxonomy );

    /**
     * Fires after term taxonomy is deleted.
     *
     * @since 2.3.0
     *
     * @param int    $term_taxonomy_id Term taxonomy ID.
     * @param string $taxonomy         Taxonomy name.
     */
    do_action( 'deleted_term_taxonomy', $term_taxonomy_id, $taxonomy );

    return true;
}
  • $tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id = %d AND taxonomy = %s", $term, $taxonomy ) );:从 $wpdb->term_taxonomy 表中查询 term_taxonomy_id,这个 ID 是 Term 和 Taxonomy 关联的关键。
  • $result = $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term_taxonomy_id ) );:从 $wpdb->term_taxonomy 表中删除对应的记录。
  • $wpdb->delete( $wpdb->termmeta, array( 'term_id' => $term ) );:删除与该 Term 相关的 Term Meta 数据。
  • 一系列的钩子(pre_delete_term_taxonomydelete_term_taxonomydeleted_term_taxonomy),允许开发者在删除 Term Taxonomy 的前后执行自定义操作。

四、参数详解:$args 数组的奥秘

wp_delete_term() 函数接受一个可选的 $args 数组,用于控制删除 Term 的行为。目前,唯一有效的参数是 'default'

参数 类型 描述
'default' int 指定删除 Term 后,文章应该被分配到的默认 Term ID。如果未指定,则文章将不再属于任何 Term。 注意: 如果你的 Taxonomy 是类似 Category 这种必须要有 Term 的,不设置 default 会导致文章显示异常。

五、使用场景:一些实用的例子

  1. 删除分类,并将文章分配到默认分类:

    $term_id = 123; // 要删除的分类 ID
    $taxonomy = 'category'; // 分类法
    $default_term_id = 1; // 默认分类 ID
    
    $args = array(
        'default' => $default_term_id,
    );
    
    $result = wp_delete_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'; // 分类法
    
    $result = wp_delete_term( $term_id, $taxonomy );
    
    if ( is_wp_error( $result ) ) {
        echo '删除失败:' . $result->get_error_message();
    } else {
        echo '删除成功!';
    }
  3. 通过钩子阻止删除分类:

    add_filter( 'pre_delete_term', 'my_prevent_term_deletion', 10, 3 );
    
    function my_prevent_term_deletion( $pre, $term, $taxonomy ) {
        if ( $term == 789 && $taxonomy == 'category' ) { // 假设要阻止删除 ID 为 789 的 Category
            return new WP_Error( 'deletion_denied', '不能删除这个分类!' );
        }
        return $pre;
    }
    
    $term_id = 789; // 要删除的分类 ID
    $taxonomy = 'category'; // 分类法
    
    $result = wp_delete_term( $term_id, $taxonomy );
    
    if ( is_wp_error( $result ) ) {
        echo '删除失败:' . $result->get_error_message(); // 输出:不能删除这个分类!
    } else {
        echo '删除成功!'; // 不会执行到这里
    }

六、注意事项:踩坑指南

  • 权限问题: 确保当前用户拥有删除 Term 的权限。
  • Term Meta: wp_delete_term() 会删除与 Term 相关的 Term Meta 数据,所以在删除 Term 之前,请务必备份重要的 Term Meta 数据。
  • 子分类: wp_delete_term() 会递归删除子分类,请谨慎操作,以免误删数据。
  • 关联文章: 如果没有设置 default 参数,删除 Term 后,与该 Term 关联的文章将不再属于任何 Term,可能会影响文章的显示。
  • 钩子: 利用钩子可以实现很多自定义功能,比如记录删除日志、发送通知、阻止删除操作等等。

七、总结:wp_delete_term() 的正确打开方式

wp_delete_term() 函数是 WordPress 中一个非常重要的函数,用于删除分类、标签等术语。它不仅可以删除 Term 本身,还可以处理与 Term 关联的文章和子分类。通过理解 wp_delete_term() 函数的源码和参数,我们可以更好地控制删除 Term 的行为,避免出现意外情况。

记住,删除操作需谨慎,备份数据是王道!希望今天的讲解能帮助你更好地理解 wp_delete_term() 函数,并在实际开发中灵活运用。

好啦,今天的“拆家”指南就到这里,希望各位观众老爷喜欢,我们下期再见!

发表回复

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