剖析 WordPress `wp_editor()` 函数的源码:如何渲染 Gutenberg 编辑器,并支持经典编辑器的兼容模式。

各位观众老爷,今天咱就来扒一扒 WordPress 里面的 wp_editor() 这个老家伙,看看它是怎么变着法儿地伺候 Gutenberg 编辑器,又怎么周到地照顾着经典编辑器。准备好了吗?咱们这就开始!

开场白:这 wp_editor() 到底是个啥玩意儿?

简单来说,wp_editor() 就是 WordPress 提供的一个方便的函数,它能帮你生成一个文本编辑器。以前是 TinyMCE(经典编辑器),现在嘛,自然也得支持 Gutenberg(块编辑器)。 关键是,它还得能根据用户的设置,决定到底加载哪个。

第一幕:wp_editor() 的骨架结构

咱们先来看看 wp_editor() 函数的基本结构,摸清楚它的脉络:

function wp_editor( $content, $editor_id, $settings = array() ) {
  global $tinymce, $wp_version, $concatenate_scripts;

  // 1. 处理默认参数和用户传入的参数
  $editor_id = sanitize_html_class( $editor_id ); // 清理 editor_id
  $settings = wp_parse_args( $settings, array(
    'wpautop' => true,
    'media_buttons' => true,
    'textarea_name' => $editor_id,
    'textarea_rows' => 20,
    'tabindex' => '',
    'editor_class' => '',
    'editor_css' => '',
    'teeny' => false,
    'dfw' => false,
    'tinymce' => true, // 关键:是否启用 TinyMCE (经典编辑器)
    'quicktags' => true
  ) );

  // 2. 决定使用哪个编辑器
  if ( ! empty( $_GET['classic-editor'] ) ) { // 强制使用经典编辑器
    $use_wp_editor = true;
  } elseif ( ! empty( $_GET['classic-editor__forget'] ) ) { // 强制不使用经典编辑器
    $use_wp_editor = false;
  } else {
    $use_wp_editor = ( 'classic' === get_option( 'classic-editor-replace' ) ) || ( ! function_exists( 'the_block_editor_meta_boxes' ) );
  }

  // 3. 加载相应的编辑器
  if ( $use_wp_editor ) {
    // 加载经典编辑器 (TinyMCE)
    _WP_Editors::editor( $editor_id, $content, $settings );
  } else {
    // 加载 Gutenberg 编辑器
    _WP_Editors::block_editor( $editor_id, $content, $settings );
  }
}

看到没?wp_editor() 接收三个参数:

  • $content: 编辑器的初始内容。
  • $editor_id: 编辑器的 ID,非常重要,用于 JavaScript 和 CSS 的关联。
  • $settings: 一个数组,包含各种配置选项,比如是否启用 TinyMCE,高度,CSS等等。

第二幕:参数的处理,决定命运的时刻

wp_parse_args() 函数在这里起到了关键作用。它将用户传入的 $settings 和默认设置合并,确保所有必要的选项都有值。注意 tinymce 这个参数,它控制着是否启用经典编辑器。默认情况下,它是 true,也就是说,默认启用 TinyMCE。

接下来,就是判断到底该用哪个编辑器了。WordPress 会检查 URL 参数 classic-editorclassic-editor__forget,如果存在,就强制使用或不使用经典编辑器。 如果URL参数不存在,它会检查 classic-editor-replace 选项,并检查the_block_editor_meta_boxes函数是否存在。

  • classic-editor-replace 选项:如果设置为 'classic',则始终使用经典编辑器。
  • the_block_editor_meta_boxes 函数:如果不存在,说明主题或插件禁用了 Gutenberg,那么就使用经典编辑器。

第三幕:经典编辑器的华丽登场(TinyMCE)

如果 $use_wp_editortrue,就意味着要加载经典编辑器了。 这个时候,_WP_Editors::editor() 方法会被调用。这个方法负责初始化 TinyMCE 编辑器。

class _WP_Editors {
    // ... 其他方法 ...

    public static function editor( $editor_id, $content, $settings = array() ) {
        global $tinymce, $concatenate_scripts, $wp_styles, $wp_scripts;

        // 1. 准备 TinyMCE 的配置
        $mce_settings = self::mce_settings( $editor_id, $settings );

        // 2. 加载 TinyMCE 的 JavaScript 和 CSS
        wp_enqueue_script( 'wp-tinymce' );
        wp_enqueue_style( 'editor-buttons' );

        // 3. 输出 HTML 结构
        echo "<div id='wp-$editor_id-wrap' class='wp-core-ui wp-editor-wrap " . ( $settings['teeny'] ? 'teeny' : 'wp-editor-expand' ) . "'>";

        // 3.1 Visual 和 Text 模式切换标签
        echo "<div id='wp-$editor_id-editor-tools' class='wp-editor-tools hide-if-no-js'>";
        echo "<div class='wp-media-buttons'>";
        if ( $settings['media_buttons'] ) {
            do_action( 'media_buttons', $editor_id );
        }
        echo "</div>";
        echo "<div class='wp-editor-tabs'>";
        echo "<button type='button' id='$editor_id-tmce' class='wp-switch-editor switch-tmce' data-wp-editor-id='$editor_id'>" . __( 'Visual' ) . "</button>";
        echo "<button type='button' id='$editor_id-html' class='wp-switch-editor switch-html' data-wp-editor-id='$editor_id'>" . __( 'Text' ) . "</button>";
        echo "</div>";
        echo "</div>";

        // 3.2 TinyMCE 编辑器本体
        echo "<div id='wp-$editor_id-editor-container' class='wp-editor-container'>";
        echo "<textarea class='wp-editor-area' rows='" . absint( $settings['textarea_rows'] ) . "' cols='40' name='" . esc_attr( $settings['textarea_name'] ) . "' id='$editor_id'" . ( $settings['tabindex'] ? ' tabindex="' . absint( $settings['tabindex'] ) . '"' : '' ) . ">" . esc_textarea( $content ) . "</textarea>";
        echo "</div>";

        echo "</div>";

        // 4. 启动 TinyMCE (通过 JavaScript)
        self::enqueue_scripts(); // 确保脚本已经加载
        self::editor_js( $editor_id, $settings );
    }

    // ... 其他方法 ...
}

这个方法主要做了这几件事:

  1. 准备 TinyMCE 的配置: self::mce_settings() 方法会根据 $settings 数组生成 TinyMCE 的配置参数,包括工具栏按钮、插件等等。
  2. 加载 TinyMCE 的 JavaScript 和 CSS: wp_enqueue_script()wp_enqueue_style() 函数负责加载 TinyMCE 的必要资源。
  3. 输出 HTML 结构: 这个部分负责生成编辑器界面的 HTML 代码,包括 Visual 和 Text 模式切换标签,以及 TinyMCE 编辑器的 textarea
  4. 启动 TinyMCE (通过 JavaScript): self::editor_js() 方法会输出 JavaScript 代码,用于初始化 TinyMCE 编辑器。

重点来了:self::mce_settings() 方法

这个方法非常重要,它决定了 TinyMCE 编辑器的外观和功能。 它会根据 $settings 数组中的选项,生成一个 JavaScript 对象,作为 TinyMCE 的初始化参数。

public static function mce_settings( $editor_id, $settings ) {
    global $tinymce, $wp_version, $concatenate_scripts;

    $mce_css = apply_filters( 'mce_css', '' );
    if ( ! empty( $mce_css ) ) {
        $mce_css = ', ' . $mce_css;
    }

    $baseurl = includes_url( 'js/tinymce' );

    $plugins = array( 'charmap', 'hr', 'media', 'paste', 'tabfocus', 'textcolor', 'fullscreen', 'wordpress', 'wpautoresize', 'wpeditimage', 'wpemoji', 'wpgallery', 'wplink', 'wpdialogs', 'wptextpattern' );

    if ( ! $settings['teeny'] ) {
        $plugins[] = 'image';
    }

    $plugins = apply_filters( 'mce_plugins', $plugins, $editor_id );
    $plugins = implode( ',', array_unique( $plugins ) );

    $rel_url = false;
    if ( isset( $settings['relative_urls'] ) && $settings['relative_urls'] ) {
        $rel_url = true;
    }

    $remove_script_host = false;
    if ( isset( $settings['remove_script_host'] ) && $settings['remove_script_host'] ) {
        $remove_script_host = true;
    }

    $mceInit = array(
        'selector' => "#$editor_id",
        'wp_autoresize_on' => ! $settings['fixed_height'],
        'wp_keep_scroll_position' => true,
        'body_class' => $editor_id,
        'skin' => 'lightgray',
        'toolbar1' => 'formatselect,bold,italic,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link,unlink,wp_more,spellchecker,fullscreen,wp_adv',
        'toolbar2' => 'strikethrough,hr,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo,wp_help',
        'toolbar3' => '',
        'toolbar4' => '',
        'plugins' => $plugins,
        'content_css' => includes_url( 'css/dashicons.min.css' ) . ',' . includes_url( 'css/editor-style.css?ver=' . $wp_version ) . $mce_css,
        'menubar' => false,
        'wpautop' => $settings['wpautop'],
        'indent' => ! $settings['wpautop'],
        'toolbar_items_size' => 'small',
        'browser_spellcheck' => true,
        'relative_urls' => $rel_url,
        'remove_script_host' => $remove_script_host,
        'convert_urls' => false,
        'remove_trailing_brs' => true,
        'verify_html' => false,
        'apply_source_formatting' => true,
        'directionality' => is_rtl() ? 'rtl' : 'ltr',
        'wp_lang_attr' => get_language_attributes(),
        'end_container_on_empty_block' => true,
        'add_unload_trigger' => false
    );

    if ( $settings['teeny'] ) {
        $mceInit['toolbar1'] = 'bold,italic,underline,separator,bullist,numlist,separator,link,unlink,separator,undo,redo';
        unset( $mceInit['toolbar2'], $mceInit['toolbar3'], $mceInit['toolbar4'] );
    }

    if ( ! empty( $settings['editor_css'] ) ) {
        $mceInit['content_css'] .= ',' . $settings['editor_css'];
    }

    $mceInit = apply_filters( 'tiny_mce_before_init', $mceInit, $editor_id );
    $mceInit['selector'] = sprintf( '#%s', $editor_id );

    return $mceInit;
}

这个方法会设置 TinyMCE 的各种选项,比如:

  • plugins: 启用的 TinyMCE 插件,比如 charmap(特殊字符), media(媒体插入)等等。
  • toolbar1toolbar2: 工具栏按钮的排列。
  • content_css: 编辑器的 CSS 样式。
  • wpautop: 是否自动添加 <p> 标签。

第四幕:Gutenberg 编辑器的异军突起

如果 $use_wp_editorfalse,就意味着要加载 Gutenberg 编辑器了。 这个时候,_WP_Editors::block_editor() 方法会被调用。

    public static function block_editor( $editor_id, $content, $settings = array() ) {
        global $post;

        // 1. 准备数据
        $block_editor_context = [
            'editor_styles' => [],
            'settings' => $settings,
            'post' => $post
        ];

        // 2. 允许插件修改 block editor 的 context
        $block_editor_context = apply_filters( 'block_editor_settings', $block_editor_context );

        // 3. 渲染 block editor
        $render_block_editor = function() use ( $editor_id, $content, $block_editor_context ) {
            $settings = $block_editor_context['settings'];
            $editor_settings = wp_json_encode( $settings );

            ?>
            <div id="wp-<?php echo esc_attr( $editor_id ); ?>-wrap" class="wp-core-ui wp-block-editor-container <?php echo esc_attr( $settings['editor_class'] ); ?>">
                <input type="hidden" id="wp-block-editor-initial-content" value="<?php echo esc_attr( $content ); ?>">
                <div id="<?php echo esc_attr( $editor_id ); ?>" class="wp-block-editor"></div>
                <script type="text/javascript">
                ( function() {
                    var mountNode = document.getElementById( '<?php echo esc_attr( $editor_id ); ?>' );
                    if ( mountNode ) {
                        wp.editPost.initialize( '<?php echo esc_attr( $editor_id ); ?>', 'post', <?php echo $editor_settings; ?> );
                    }
                } )();
                </script>
            </div>
            <?php
        };

        $render_block_editor();
    }

这个方法相对简单,主要做了这几件事:

  1. 准备数据: 创建一个 $block_editor_context 数组,包含编辑器样式、设置和当前文章对象。
  2. 允许插件修改 block editor 的 context: 通过 apply_filters( 'block_editor_settings', $block_editor_context ) 允许插件修改 $block_editor_context 数组,从而自定义 Gutenberg 编辑器的行为。
  3. 渲染 block editor: 输出 HTML 结构,包括一个 <div> 作为 Gutenberg 编辑器的挂载点,以及一段 JavaScript 代码来初始化 Gutenberg 编辑器。

关键点:Gutenberg 的初始化

注意这段 JavaScript 代码:

( function() {
  var mountNode = document.getElementById( '<?php echo esc_attr( $editor_id ); ?>' );
  if ( mountNode ) {
    wp.editPost.initialize( '<?php echo esc_attr( $editor_id ); ?>', 'post', <?php echo $editor_settings; ?> );
  }
} )();

它使用 wp.editPost.initialize() 函数来初始化 Gutenberg 编辑器。这个函数是 Gutenberg 编辑器的核心,它负责加载编辑器界面,处理用户输入,并将内容保存到数据库。

第五幕:兼容模式的秘密武器

wp_editor() 函数通过检查 URL 参数 classic-editorclassic-editor__forget,以及 classic-editor-replace 选项,实现了对经典编辑器的兼容模式。 这意味着,即使你的 WordPress 版本已经升级到 Gutenberg,你仍然可以通过这些方式来强制使用经典编辑器。

总结陈词:wp_editor() 的角色定位

wp_editor() 函数就像一个万能的管家,它能根据你的需求,为你提供合适的编辑器。 无论是经典编辑器还是 Gutenberg 编辑器,它都能完美地支持。

表格总结:

功能点 经典编辑器 (TinyMCE) Gutenberg 编辑器 (Block Editor)
加载方式 _WP_Editors::editor() _WP_Editors::block_editor()
初始化 TinyMCE JavaScript API wp.editPost.initialize()
配置参数 self::mce_settings() 生成的 JavaScript 对象 $block_editor_context 数组
兼容模式 通过 URL 参数 classic-editorclassic-editor__forget 以及 classic-editor-replace 选项支持 N/A
可扩展性 mce_pluginstiny_mce_before_init 过滤器 block_editor_settings 过滤器

最后,留个小问题:

你觉得 wp_editor() 函数还有哪些可以改进的地方? 欢迎在评论区留言,咱们一起探讨!

今天的讲座就到这里,谢谢大家! 别忘了点赞、收藏、加关注哦! 下次再见!

发表回复

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