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

各位观众老爷们,晚上好!今天咱们来聊聊WordPress里一个挺重要的函数——wp_delete_term()。这个函数,顾名思义,就是用来删除分类术语的。别看名字简单,里面的水可深着呢。它不仅要删除术语本身,还要考虑它下面的文章怎么办,子分类怎么办,以及各种关联数据,一不小心就会把你的网站搞得一团糟。

所以,今天咱们就来扒一扒这个函数的源码,看看它到底是怎么运作的,以及我们在使用它的时候需要注意哪些坑。

1. 开场白:删除术语的诱惑与风险

在WordPress的世界里,术语(Terms)是分类法(Taxonomies)的基本组成部分。常见的分类法有分类(Categories)和标签(Tags),以及自定义分类法。每个分类法下面可以有很多术语,比如“新闻”、“科技”、“旅游”等等。

有时候,我们需要删除一些不再使用的术语,可能是因为内容过时了,或者分类体系需要调整了。这时候,wp_delete_term()就派上用场了。

但是,删除术语并不是一个简单的操作。想象一下,如果一个术语下面有很多文章,删除了这个术语,那些文章该怎么办?如果这个术语还有子分类,子分类又该怎么办?这些都是需要仔细考虑的问题。

2. wp_delete_term() 函数的庐山真面目

咱们先来看看 wp_delete_term() 函数的基本结构:

/**
 * Deletes a term from the database.
 *
 * @since 2.3.0
 * @since 4.3.0 Added `$args` parameter.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int    $term     Term ID.
 * @param string $taxonomy Taxonomy name.
 * @param array  $args     {
 *     Optional. Array of arguments to control term deletion behavior.
 *
 *     @type int|false $default  ID of the term to reassign posts to.
 *                                Default: false.
 * }
 * @return bool|WP_Error True on success, false or WP_Error on failure.
 */
function wp_delete_term( $term, $taxonomy, $args = array() ) {
    global $wpdb;

    $term = (int) $term;

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

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

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

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

    if ( apply_filters( 'pre_delete_term', false, $term->term_id, $taxonomy ) ) {
        return true;
    }

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

    if ( 'category' === $taxonomy && (int) get_option( 'default_category' ) === $term->term_id ) {
        return new WP_Error( 'default_category', __( 'You cannot delete the default category.' ) );
    }

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

    $id = $term->term_id;

    $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term->term_taxonomy_id ) );

    if ( ! empty( $object_ids ) ) {
        $default = isset( $args['default'] ) ? (int) $args['default'] : false;

        if ( false === $default ) {
            return new WP_Error( 'term_has_posts', __( 'Term is not empty.' ) );
        }

        if ( ! term_exists( $default, $taxonomy ) ) {
            $default = (int) get_option( 'default_category' );
            if ( empty( $default ) ) {
                $delete_term = false;
                foreach ( $object_ids as $object_id ) {
                    $delete_term = wp_delete_post( $object_id, true );
                    if ( ! $delete_term ) {
                        return false;
                    }
                }
            }
        }

        if ( ! empty( $default ) ) {
            $wpdb->update( $wpdb->term_relationships, array( 'term_taxonomy_id' => $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id = %d AND taxonomy = %s", $default, $taxonomy ) ) ), array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
            clean_term_cache( $object_ids, $taxonomy );
        }
    }

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

    if ( ! $delete_term ) {
        return false;
    }

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

    if ( ! $delete_term ) {
        return false;
    }

    wp_cache_delete_last_changed( 'terms' );

    clean_term_cache( $id, $taxonomy );

    /**
     * Fires after a term is deleted from the database.
     *
     * @since 2.3.0
     *
     * @param int    $term     Term ID.
     * @param string $taxonomy Taxonomy name.
     */
    do_action( 'deleted_term', $term->term_id, $taxonomy );

    return true;
}

这个函数接收三个参数:

  • $term: 要删除的术语的ID。必须是整数。
  • $taxonomy: 术语所属的分类法的名称。必须是字符串。
  • $args: 一个可选的数组,用于控制删除行为。

函数返回 true 表示删除成功,false 或者 WP_Error 对象表示删除失败。

3. 源码逐行分析:步步惊心

接下来,咱们一行一行地分析 wp_delete_term() 函数的源码,看看它都做了些什么:

  1. 参数类型转换和校验:

    $term = (int) $term;
    if ( ! taxonomy_exists( $taxonomy ) ) {
        return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    }
    
    if ( ! term_exists( $term, $taxonomy ) ) {
        return new WP_Error( 'invalid_term', __( 'Non-existent term.' ) );
    }

    首先,把 $term 转换为整数,确保类型正确。然后,使用 taxonomy_exists()term_exists() 函数来校验分类法和术语是否存在。如果不存在,就返回一个 WP_Error 对象,表示删除失败。这是最基本的参数校验,确保后续操作不会出错。

  2. 获取术语对象:

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

    使用 get_term() 函数获取术语对象。如果获取失败(比如数据库里找不到这个术语),get_term() 会返回一个 WP_Error 对象,这里直接返回,终止删除操作。

  3. pre_delete_term 过滤器:

    if ( apply_filters( 'pre_delete_term', false, $term->term_id, $taxonomy ) ) {
        return true;
    }

    这是一个过滤器,允许开发者在删除术语之前进行一些自定义操作,甚至可以阻止删除操作。如果过滤器返回 true,表示已经处理了删除操作,函数直接返回 true,不再执行后续的删除逻辑。这个过滤器提供了一个扩展点,可以用来实现一些高级功能,比如记录删除日志、发送通知等等。

  4. 处理默认参数:

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

    使用 wp_parse_args() 函数将传入的 $args 参数与默认参数合并。默认参数只有一个:'default' => false。这个参数用于指定在删除术语后,将该术语下的文章重新分配给哪个术语。如果设置为 false,表示不重新分配,而是删除文章与该术语的关联。

  5. 禁止删除默认分类:

    if ( 'category' === $taxonomy && (int) get_option( 'default_category' ) === $term->term_id ) {
        return new WP_Error( 'default_category', __( 'You cannot delete the default category.' ) );
    }

    这段代码判断要删除的术语是否是默认分类。如果是,则返回一个 WP_Error 对象,阻止删除操作。WordPress不允许删除默认分类,因为至少需要一个分类来分配文章。

  6. delete_term 动作:

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

    这是一个动作,允许开发者在删除术语之前执行一些自定义操作。与 pre_delete_term 过滤器不同,delete_term 动作不能阻止删除操作,只能在删除之前执行一些附加操作。

  7. 获取关联的文章ID:

    $id = $term->term_id;
    
    $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term->term_taxonomy_id ) );

    这段代码使用 $wpdb 对象执行SQL查询,获取与要删除的术语关联的文章ID。$wpdb->term_relationships 表存储了文章和术语之间的关联关系。

  8. 处理关联的文章:

    if ( ! empty( $object_ids ) ) {
        $default = isset( $args['default'] ) ? (int) $args['default'] : false;
    
        if ( false === $default ) {
            return new WP_Error( 'term_has_posts', __( 'Term is not empty.' ) );
        }
    
        if ( ! term_exists( $default, $taxonomy ) ) {
            $default = (int) get_option( 'default_category' );
            if ( empty( $default ) ) {
                $delete_term = false;
                foreach ( $object_ids as $object_id ) {
                    $delete_term = wp_delete_post( $object_id, true );
                    if ( ! $delete_term ) {
                        return false;
                    }
                }
            }
        }
    
        if ( ! empty( $default ) ) {
            $wpdb->update( $wpdb->term_relationships, array( 'term_taxonomy_id' => $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id = %d AND taxonomy = %s", $default, $taxonomy ) ) ), array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
            clean_term_cache( $object_ids, $taxonomy );
        }
    }

    这段代码是整个函数的核心部分,用于处理与要删除的术语关联的文章。

    • 如果 $object_ids 不为空,表示该术语下有文章。
    • 如果 $args['default']false,表示没有指定重新分配的术语,则返回一个 WP_Error 对象,阻止删除操作。这是为了防止误删术语,导致文章丢失。
    • 如果 $args['default'] 指定了重新分配的术语,但是该术语不存在,则尝试使用默认分类。如果默认分类也不存在,那么就会直接删除文章!
    • 如果 $args['default'] 存在且有效,则更新 $wpdb->term_relationships 表,将文章与要删除的术语的关联关系修改为与 $args['default'] 指定的术语的关联关系。然后,使用 clean_term_cache() 函数清理缓存。
  9. 删除术语的term_taxonomy记录:

    $delete_term = $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
    
    if ( ! $delete_term ) {
        return false;
    }

    这段代码从 $wpdb->term_taxonomy 表中删除与要删除的术语相关的记录。$wpdb->term_taxonomy 表存储了术语的分类法信息。如果删除失败,则返回 false,表示删除失败。

  10. 删除术语记录:

    $delete_term = $wpdb->delete( $wpdb->terms, array( 'term_id' => $term->term_id ) );
    
    if ( ! $delete_term ) {
        return false;
    }

    这段代码从 $wpdb->terms 表中删除要删除的术语的记录。$wpdb->terms 表存储了术语的基本信息,比如名称、别名等等。如果删除失败,则返回 false,表示删除失败。

  11. 清理缓存:

    wp_cache_delete_last_changed( 'terms' );
    
    clean_term_cache( $id, $taxonomy );

    这段代码使用 wp_cache_delete_last_changed()clean_term_cache() 函数清理缓存,确保删除操作能够及时生效。

  12. deleted_term 动作:

    /**
     * Fires after a term is deleted from the database.
     *
     * @since 2.3.0
     *
     * @param int    $term     Term ID.
     * @param string $taxonomy Taxonomy name.
     */
    do_action( 'deleted_term', $term->term_id, $taxonomy );

    这是一个动作,允许开发者在删除术语之后执行一些自定义操作。

  13. 返回成功:

    return true;

    如果所有操作都成功完成,则返回 true,表示删除成功。

4. 使用示例:如何正确删除术语

现在,咱们来看几个使用 wp_delete_term() 函数的例子,演示如何正确地删除术语。

  • 示例1:删除一个没有文章关联的术语

    $term_id = 123; // 要删除的术语ID
    $taxonomy = 'category'; // 术语所属的分类法
    
    $result = wp_delete_term( $term_id, $taxonomy );
    
    if ( is_wp_error( $result ) ) {
        echo '删除失败:' . $result->get_error_message();
    } else {
        echo '删除成功!';
    }

    这个例子演示了如何删除一个没有文章关联的术语。由于没有文章关联,所以可以直接删除,不需要指定 $args 参数。

  • 示例2:删除一个有文章关联的术语,并将文章重新分配给另一个术语

    $term_id = 456; // 要删除的术语ID
    $taxonomy = 'category'; // 术语所属的分类法
    $default_term_id = 789; // 要重新分配的术语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 '删除成功!文章已重新分配给ID为 ' . $default_term_id . ' 的术语。';
    }

    这个例子演示了如何删除一个有文章关联的术语,并将文章重新分配给另一个术语。需要指定 $args 参数,并将 'default' 设置为要重新分配的术语的ID。

  • 示例3:删除一个有文章关联的术语,并删除所有关联的文章

    请谨慎使用!

    $term_id = 789; // 要删除的术语ID
    $taxonomy = 'category'; // 术语所属的分类法
    
    $args = array(
        'default' => 0, // 传递0或者false,如果default category不存在将会删除文章。
    );
    
    $result = wp_delete_term( $term_id, $taxonomy, $args );
    
    if ( is_wp_error( $result ) ) {
        echo '删除失败:' . $result->get_error_message();
    } else {
        echo '删除成功!所有关联的文章已被删除。';
    }

    重要提示: 这个例子演示了如何删除一个有文章关联的术语,并删除所有关联的文章。这是一种非常危险的操作,请务必谨慎使用! 除非你非常确定要删除所有关联的文章,否则不要使用这种方法。建议先备份数据库,以防万一。

5. 注意事项:踩坑指南

在使用 wp_delete_term() 函数的时候,有一些需要注意的地方,避免踩坑:

  • 删除前备份: 在进行任何删除操作之前,务必备份数据库。这是最基本的安全措施,可以防止数据丢失。
  • 谨慎删除关联文章: 如果要删除的术语下面有很多文章,一定要慎重考虑是否要删除这些文章。删除文章可能会对网站的SEO和用户体验产生负面影响。
  • 考虑子分类: 如果要删除的术语有子分类,需要先删除子分类,或者将子分类移动到其他术语下。否则,可能会导致数据不一致。
  • 使用过滤器和动作: wp_delete_term() 函数提供了 pre_delete_term 过滤器和 delete_termdeleted_term 动作,可以用来扩展删除操作的功能。合理使用这些钩子,可以实现一些高级功能,比如记录删除日志、发送通知等等。
  • 测试: 在生产环境中使用 wp_delete_term() 函数之前,务必在测试环境中进行充分的测试,确保删除操作不会对网站产生任何负面影响。
  • Default Category不存在的问题: 如果Default Category被删除,并且wp_delete_term$args['default']传的是false,那么与该term相关联的所有文章都会被删除。

6. 总结:掌握删除的艺术

wp_delete_term() 函数是WordPress中一个非常重要的函数,用于删除分类术语。虽然使用起来很简单,但是内部逻辑却比较复杂,需要仔细考虑各种情况,避免误删数据。

通过今天的讲解,相信大家对 wp_delete_term() 函数的源码有了更深入的了解,也掌握了正确使用该函数的方法。希望大家在实际开发中能够灵活运用这些知识,写出更健壮、更可靠的WordPress代码。

好了,今天的讲座就到这里。感谢各位观众老爷的收听!我们下期再见!

发表回复

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