解析 WordPress `WP_Post_Revisions` 类源码:文章修订版本的管理机制。

各位观众老爷们,早上好!今天咱来聊聊WordPress里一个低调但关键的家伙——WP_Post_Revisions 类,也就是文章修订版本的管理机制。说白了,就是WordPress怎么帮你存历史版本,万一你手抖改错了,还能找回来。

一、啥是修订版本?为啥要有它?

想象一下,你辛辛苦苦写了一篇文章,改了又改,突然灵光一闪,把最重要的部分删了!然后手一抖,点了“更新”。完了!欲哭无泪啊。

这时候,修订版本就来救场了。它就像一个时光机,能让你回到过去的某个版本。

更正式点说,修订版本就是文章、页面等内容在不同时间点的快照。每次你保存或自动保存文章,WordPress就会创建一个修订版本,记录下当时的内容、标题、作者等等信息。

二、WP_Post_Revisions 类在哪?它干啥的?

WP_Post_Revisions 类,顾名思义,就是专门用来处理修订版本的。它不是一个直接让你实例化的类,而是一堆静态方法,提供了一系列函数来操作修订版本。

这个类藏在 wp-includes/post.php 文件里。 记住,它是WordPress核心的一部分,不需要额外安装插件。

三、核心函数大揭秘:wp_save_post_revision()

这个函数是修订版本机制的灵魂人物。每次你保存文章,或者自动保存触发,它都会被调用。

/**
 * Saves a post revision.
 *
 * @since 2.6.0
 *
 * @param int|WP_Post $post Post ID or WP_Post object.
 * @param bool      $autosave Optional. Whether this is an autosave. Default is false.
 * @return int|false The ID of the revision or false if error.
 */
function wp_save_post_revision( $post, $autosave = false ) {
    global $wpdb;

    if ( is_object( $post ) ) {
        $post = $post->ID;
    }

    $post = get_post( $post );

    if ( ! $post ) {
        return false;
    }

    if ( ! current_user_can( 'edit_post', $post->ID ) ) {
        return false;
    }

    if ( 'revision' === $post->post_type ) {
        return false;
    }

    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        if ( ! $autosave ) {
            return false;
        }
    }

    if ( wp_is_post_autosave( $post->ID ) ) {
        return false;
    }

    if ( wp_is_post_revision( $post->ID ) ) {
        return false;
    }

    $parent = $post;
    $post   = clone $parent;

    $post->post_parent = $parent->ID;
    unset( $post->ID );

    $post->post_status = 'inherit';
    $post->post_type   = 'revision';
    $post->comment_status = 'closed';
    $post->ping_status = 'closed';

    if ( $autosave ) {
        $post->post_name = "{$parent->ID}-autosave-v1";
        /* translators: Post revision title. %s: Time of revision. */
        $post->post_title = sprintf( __( 'Auto Draft saved at %s' ), date( __( 'g:i a' ) ) );
    } else {
        $post->post_name = "{$parent->ID}-revision-v1";
        /* translators: Post revision title. %s: Time of revision. */
        $post->post_title = sprintf( __( 'Revision saved at %s' ), date( __( 'g:i a' ) ) );
    }

    $post_id = wp_insert_post( (array) $post );

    if ( is_wp_error( $post_id ) ) {
        return false;
    }

    /**
     * Fires after a post revision is saved.
     *
     * @since 2.6.0
     *
     * @param int|WP_Post $post Post ID or WP_Post object.
     */
    do_action( 'wp_save_post_revision', $post_id, $post );

    return $post_id;
}

这个函数做了以下几件事:

  1. 安全检查: 验证用户是否有权限编辑文章。
  2. 类型判断: 确保不是在保存一个修订版本或者自动保存版本。
  3. 克隆文章: 创建一个原始文章的副本。
  4. 设置属性: 设置副本的post_typerevisionpost_statusinherit,表明这是一个修订版本,并且依附于原始文章。post_parent设置为原始文章的ID。
  5. 插入修订版本: 使用wp_insert_post()函数将修订版本插入数据库。

重点: 修订版本实际上也是一个post,只不过它的post_typerevision,并且post_parent指向原始文章。这是一种非常巧妙的设计。

四、自动保存:wp_autosave_post()

自动保存是修订版本机制的重要组成部分。WordPress会定期自动保存你的文章,防止你因为浏览器崩溃或者断电而丢失数据。

/**
 * Autosaves a post.
 *
 * @since 2.6.0
 *
 * @param array $data $_POST or $_GET data.
 * @return int|WP_Error Post ID.
 */
function wp_autosave_post( $data ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // Non-existent nonce? Don't bother.
    if ( ! isset( $data['autosave'] ) ) {
        return;
    }

    $post_ID = (int) $data['post_ID'];

    // Missing post ID?  Bail.
    if ( empty( $post_ID ) ) {
        return;
    }

    // Is the user allowed to edit this post?
    if ( ! current_user_can( 'edit_post', $post_ID ) ) {
        return new WP_Error( 'autosave_error', __( 'Sorry, you are not allowed to edit this post.' ) );
    }

    // Gotta have some content to autosave.
    if ( empty( $data['content'] ) ) {
        return;
    }

    // Don't autosave if the form hasn't been submitted
    if ( 'draft' == get_post_status( $post_ID ) && isset( $_REQUEST['action'] ) && 'editpost' != $_REQUEST['action'] ) {
        return;
    }

    define( 'DOING_AUTOSAVE', true );

    // Disable filters that modify content, excerpt, titles, etc.
    remove_all_filters( 'content_save_pre' );
    remove_all_filters( 'excerpt_save_pre' );
    remove_all_filters( 'title_save_pre' );

    // Expected $_POST values.
    $post = array();
    $post['ID']           = $post_ID;
    $post['post_content'] = $data['content'];
    $post['post_excerpt'] = $data['excerpt'];
    $post['post_title']   = $data['title'];

    // Typecast to array to avoid PHP 8.1+ deprecation notice.
    $post = wp_kses_post( (array) $post );

    // Attempt to write the autosave.
    $post_id = wp_save_post_revision( $post['ID'], true );

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

    add_action( 'edit_form_advanced', 'wp_print_revision_js' );
    add_action( 'edit_page_form', 'wp_print_revision_js' );

    return $post_id;
}

这个函数主要做了:

  1. 权限验证: 验证用户是否有权限编辑文章。
  2. 数据准备:$_POST$_GET中获取文章内容、摘要、标题等数据。
  3. 调用wp_save_post_revision() 调用wp_save_post_revision()函数,并将$autosave参数设置为true,表明这是一个自动保存版本。

五、获取修订版本:wp_get_post_revisions()

有了保存,当然也要有读取。wp_get_post_revisions()函数就是用来获取文章的修订版本的。

/**
 * Retrieves post revisions.
 *
 * @since 2.6.0
 *
 * @param int|WP_Post $post Post ID or WP_Post object.
 * @param array       $args Optional. Arguments for retrieving revisions.
 *                              See {@see WP_Query::parse_query()} for information on accepted arguments.
 *                              Default empty array.
 * @return WP_Post[] Array of revisions.
 */
function wp_get_post_revisions( $post, $args = array() ) {
    $post = get_post( $post );

    if ( ! $post ) {
        return array();
    }

    $defaults = array(
        'order'      => 'DESC',
        'orderby'    => 'date ID',
        'check_enabled' => true,
    );

    $args = wp_parse_args( $args, $defaults );

    if ( $args['check_enabled'] && ! wp_revisions_enabled( $post ) ) {
        return array();
    }

    $query_args = array(
        'post_parent'    => $post->ID,
        'post_type'      => 'revision',
        'posts_per_page' => -1,
        'post_status'    => 'any',
        'orderby'          => $args['orderby'],
        'order'            => $args['order'],
    );

    $query = new WP_Query( $query_args );

    return $query->posts;
}

这个函数主要做了:

  1. 参数处理: 处理传入的参数,例如排序方式、排序字段等。
  2. 构建查询: 构建一个WP_Query对象,查询post_parent为原始文章ID,post_typerevision的所有文章。
  3. 返回结果: 返回查询到的所有修订版本。

六、启用/禁用修订版本:wp_revisions_enabled()wp_revisions_to_keep()

WordPress允许你控制是否启用修订版本,以及保留多少个修订版本。这两个函数就是用来控制这些行为的。

/**
 * Determines whether revisions are enabled.
 *
 * @since 2.6.0
 *
 * @param WP_Post|int $post Post object or ID.
 * @return bool Whether revisions are enabled.
 */
function wp_revisions_enabled( $post = null ) {
    if ( ! defined( 'WP_POST_REVISIONS' ) ) {
        return true;
    }

    if ( false === WP_POST_REVISIONS ) {
        return false;
    }

    if ( true === WP_POST_REVISIONS ) {
        return true;
    }

    if ( is_numeric( WP_POST_REVISIONS ) ) {
        return (int) WP_POST_REVISIONS > 0;
    }

    return true;
}

/**
 * Determines how many revisions to keep.
 *
 * @since 2.6.0
 *
 * @return int Number of revisions to keep.
 */
function wp_revisions_to_keep() {
    if ( ! defined( 'WP_POST_REVISIONS' ) ) {
        return 0;
    }

    if ( is_numeric( WP_POST_REVISIONS ) ) {
        return (int) WP_POST_REVISIONS;
    }

    return 0;
}

你可以在wp-config.php文件中定义WP_POST_REVISIONS常量来控制修订版本的行为:

  • define('WP_POST_REVISIONS', true);:启用修订版本,保留所有修订版本。
  • define('WP_POST_REVISIONS', false);:禁用修订版本。
  • define('WP_POST_REVISIONS', 3);:启用修订版本,最多保留3个修订版本。

七、删除修订版本:wp_delete_post_revision()

这个函数用于删除指定的修订版本。

/**
 * Deletes a post revision.
 *
 * @since 2.6.0
 *
 * @param int|WP_Post $revision Revision ID or WP_Post object.
 * @return bool|WP_Error True on success, false or WP_Error on failure.
 */
function wp_delete_post_revision( $revision ) {
    $revision = get_post( $revision );

    if ( ! $revision ) {
        return false;
    }

    if ( 'revision' !== $revision->post_type ) {
        return false;
    }

    if ( ! current_user_can( 'delete_post', $revision->ID ) ) {
        return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to delete revisions on this post.' ) );
    }

    $result = wp_delete_post( $revision->ID, true );

    return $result;
}

八、核心函数总结

为了方便大家记忆,我把这些核心函数整理成一个表格:

函数名 功能描述
wp_save_post_revision() 保存文章修订版本。
wp_autosave_post() 自动保存文章。
wp_get_post_revisions() 获取文章的修订版本。
wp_revisions_enabled() 判断是否启用了修订版本。
wp_revisions_to_keep() 获取最多保留多少个修订版本。
wp_delete_post_revision() 删除指定的修订版本。

九、一些小技巧和注意事项

  • 修订版本过多会影响性能: 过多的修订版本会占用数据库空间,并且在编辑文章时会增加加载时间。所以,建议根据实际情况设置WP_POST_REVISIONS常量,限制修订版本的数量。
  • 可以使用插件来管理修订版本: 有很多插件可以帮助你更方便地管理修订版本,例如删除旧的修订版本、比较不同版本之间的差异等等。
  • 自动保存是临时的: 自动保存版本不会永久保存,当你手动保存文章后,自动保存版本会被删除。

十、实战演练:如何自定义修订版本功能

虽然WordPress已经提供了强大的修订版本功能,但有时候我们可能需要根据自己的需求进行定制。

场景一:只为特定文章类型启用修订版本

默认情况下,修订版本对所有文章类型都是启用的。如果我们只想为post类型的文章启用修订版本,可以在functions.php文件中添加以下代码:

add_filter( 'wp_revisions_enabled', 'my_custom_revisions_enabled', 10, 2 );

function my_custom_revisions_enabled( $enabled, $post ) {
    if ( $post->post_type == 'post' ) {
        return $enabled; // 使用默认设置
    } else {
        return false; // 禁用其他文章类型的修订版本
    }
}

场景二:根据用户角色设置修订版本数量

我们可以根据用户的角色来设置不同的修订版本数量。例如,管理员可以保留更多的修订版本,而普通用户则只能保留较少的修订版本。

add_filter( 'wp_revisions_to_keep', 'my_custom_revisions_to_keep' );

function my_custom_revisions_to_keep() {
    if ( current_user_can( 'administrator' ) ) {
        return 5; // 管理员保留5个修订版本
    } else {
        return 2; // 普通用户保留2个修订版本
    }
}

场景三:在文章列表中显示修订版本数量

我们可以在文章列表中添加一列,显示每个文章的修订版本数量。

add_filter( 'manage_posts_columns', 'my_custom_posts_columns' );
add_action( 'manage_posts_custom_column', 'my_custom_posts_column_content', 10, 2 );

function my_custom_posts_columns( $columns ) {
    $columns['revisions'] = 'Revisions';
    return $columns;
}

function my_custom_posts_column_content( $column, $post_id ) {
    if ( $column == 'revisions' ) {
        $revisions = wp_get_post_revisions( $post_id );
        echo count( $revisions );
    }
}

十一、总结

WP_Post_Revisions 类是WordPress中一个非常重要的组成部分,它为我们提供了文章修订版本的管理机制,帮助我们避免了数据丢失的风险。通过理解这个类的核心函数和工作原理,我们可以更好地利用修订版本功能,并且可以根据自己的需求进行定制。

希望今天的讲解能够帮助大家更深入地了解WordPress的修订版本机制。下次再见!

发表回复

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