深入解读 WordPress `WP_Customize_Manager` 类源码:主题定制器设置与控件的管理。

大家好,今天咱们聊聊WordPress主题定制器的幕后大佬:WP_Customize_Manager

嗨,各位技术宅们!今天咱们不聊八卦,来点硬核的:WordPress 主题定制器,以及驱动它的核心类 WP_Customize_Manager。 这玩意儿可不简单,它就像一个精密的指挥中心,协调着各种设置、控件,让用户能够轻松定制主题,而不用碰一行代码。

咱们这次就来扒一扒它的源码,看看它是如何运作的。准备好了吗?Let’s dive in!

1. WP_Customize_Manager:主题定制器的总指挥

WP_Customize_Manager 类是主题定制器的核心,负责初始化、注册设置和控件、处理用户交互等。 简单来说,它就是个大管家,管理着主题定制的所有事务。

1.1 初始化 (Constructor)

首先,我们来看看它的构造函数 __construct()

public function __construct() {
    // 确保只实例化一次
    if ( self::$instance ) {
        _doing_it_wrong( __CLASS__, sprintf( __( '%s should not be instantiated more than once.' ), __CLASS__ ), '4.0.0' );
        return;
    }
    self::$instance = $this;

    // 设置一些必要的属性
    $this->settings  = array();
    $this->controls  = array();
    $this->sections  = array();
    $this->panels    = array();
    $this->nav_menus = array();

    // 注册动作钩子
    add_action( 'admin_menu',                           array( $this, 'register_admin_menu' ) );
    add_action( 'admin_enqueue_scripts',                array( $this, 'enqueue_scripts' ) );
    add_action( 'wp_ajax_customize_save',               array( $this, 'save' ) );
    add_action( 'wp_ajax_customize_refresh_nonces',     array( $this, 'refresh_nonces' ) );
    add_action( 'wp_ajax_customize_ms_loaded',          array( $this, 'ms_loaded' ) );
    add_action( 'customize_register',                   array( $this, 'register_controls' ), 11 ); // 优先级11,确保在主题的register_controls之后执行
    add_action( 'customize_controls_print_styles',      array( $this, 'controls_print_styles' ) );
    add_action( 'customize_controls_print_scripts',     array( $this, 'controls_print_scripts' ) );
    add_action( 'customize_controls_enqueue_scripts',   array( $this, 'controls_enqueue_scripts' ) );
    add_action( 'customize_preview_init',               array( $this, 'customize_preview_init' ) );
    add_action( 'customize_preview_print_styles',     array( $this, 'customize_preview_print_styles' ) );
    add_action( 'customize_preview_print_scripts',    array( $this, 'customize_preview_print_scripts' ) );
    add_action( 'customize_save',                       array( $this, 'save_nav_menus' ) );
    add_action( 'customize_update_nav_menu',            array( $this, 'update_nav_menu' ), 10, 3 );
    add_action( 'customize_update_nav_menu_item',       array( $this, 'update_nav_menu_item' ), 10, 3 );
    add_action( 'wp_loaded',                            array( $this, 'wp_loaded' ) );
    add_action( 'shutdown',                             array( $this, 'shutdown' ), 0 );
    add_action( 'wp_die_handler',                       array( $this, '_wp_die_handler' ) );

    // 其他初始化逻辑...
}
  • 单例模式: 确保 WP_Customize_Manager 只被实例化一次,避免冲突。就像一个国家只能有一个总统一样。
  • 属性初始化: 创建了 settingscontrolssectionspanels 等数组,用于存储各种定制项。这些数组就像一个个抽屉,存放着不同类型的定制信息。
  • 动作钩子 (Action Hooks): 注册了一堆动作钩子,这些钩子会在特定的时机被触发,执行相应的操作。 比如 admin_menu 钩子会在后台菜单加载时触发, customize_save 钩子会在用户保存定制时触发。 就像埋伏好的特工,等待时机行动。

1.2 关键属性

属性名 类型 描述
$settings array 存储所有注册的 WP_Customize_Setting 对象。这些是实际存储定制值的地方。
$controls array 存储所有注册的 WP_Customize_Control 对象。这些是用户界面上的控件。
$sections array 存储所有注册的 WP_Customize_Section 对象。用于组织控件。
$panels array 存储所有注册的 WP_Customize_Panel 对象。用于组织Section。
$nav_menus array 存储导航菜单相关信息。

2. 设置 (Settings): 定制项的存储仓库

WP_Customize_Setting 类代表一个可定制的设置项,比如主题颜色、logo 图片等。它负责验证、清理和存储定制值。

2.1 注册设置 (add_setting() 方法)

public function add_setting( $id, $args = array() ) {
    if ( ! is_string( $id ) || '' === $id ) {
        _doing_it_wrong( __FUNCTION__, __( 'Setting ID must be a non-empty string.' ), '4.2.0' );
        return null;
    }

    if ( isset( $this->settings[ $id ] ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Setting %s is already registered.' ), $id ), '4.0.0' );
        return null;
    }

    // 实例化 WP_Customize_Setting 对象
    $class = 'WP_Customize_Setting'; // 默认的设置类

    if ( isset( $args['type'] ) ) {
        $class = $args['type'];
    }

    if ( ! class_exists( $class ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Setting type %s does not exist.' ), $class ), '4.0.0' );
        return null;
    }

    $setting = new $class( $this, $id, $args );

    $this->settings[ $id ] = $setting;

    return $setting;
}

这个方法负责注册一个新的设置。

  • 参数校验: 确保设置 ID 是一个非空字符串,并且没有被重复注册。
  • 实例化 WP_Customize_Setting: 根据 $args['type'] 参数,实例化对应的 WP_Customize_Setting 子类。 如果没有指定 type,则使用默认的 WP_Customize_Setting 类。
  • 存储设置: 将新创建的 WP_Customize_Setting 对象存储到 $this->settings 数组中。

2.2 WP_Customize_Setting 的子类

WordPress 提供了几个 WP_Customize_Setting 的子类,用于处理不同类型的设置:

  • WP_Customize_Theme_Option:用于存储主题选项的值。
  • WP_Customize_Nav_Menu_Setting:用于存储导航菜单的设置。
  • WP_Customize_Nav_Menu_Item_Setting:用于存储导航菜单项的设置。

2.3 获取设置值 (get_setting() 方法)

public function get_setting( $id ) {
    if ( ! isset( $this->settings[ $id ] ) ) {
        return false;
    }
    return $this->settings[ $id ];
}

这个方法用于根据 ID 获取已注册的 WP_Customize_Setting 对象。

2.4 设置值的存储机制

WP_Customize_Setting 对象最终会将定制值存储到 WordPress 的 options 表中,或者使用主题 mod。 具体使用哪种方式取决于设置的 transport 属性。

  • theme_mod:定制值存储为主题 mod。这是推荐的方式,因为它与主题相关联,当主题切换时,定制值也会随之改变。
  • option:定制值存储为 WordPress option。这种方式不推荐,因为它与主题无关,当主题切换时,定制值不会改变。

3. 控件 (Controls): 用户交互的界面元素

WP_Customize_Control 类代表一个用户界面控件,比如文本框、下拉列表、颜色选择器等。它负责显示设置项,并允许用户修改它的值。

3.1 注册控件 (add_control() 方法)

public function add_control( $id, $args = array() ) {
    if ( ! is_string( $id ) || '' === $id ) {
        _doing_it_wrong( __FUNCTION__, __( 'Control ID must be a non-empty string.' ), '4.2.0' );
        return null;
    }

    if ( isset( $this->controls[ $id ] ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Control %s is already registered.' ), $id ), '4.0.0' );
        return null;
    }

    // 实例化 WP_Customize_Control 对象
    $class = 'WP_Customize_Control';

    if ( isset( $args['type'] ) ) {
        $class = $args['type'];
    }

    if ( ! class_exists( $class ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Control type %s does not exist.' ), $class ), '4.0.0' );
        return null;
    }

    $control = new $class( $this, $id, $args );

    $this->controls[ $id ] = $control;

    return $control;
}

这个方法与 add_setting() 非常相似,负责注册一个新的控件。

  • 参数校验: 确保控件 ID 是一个非空字符串,并且没有被重复注册。
  • 实例化 WP_Customize_Control: 根据 $args['type'] 参数,实例化对应的 WP_Customize_Control 子类。 如果没有指定 type,则使用默认的 WP_Customize_Control 类。
  • 存储控件: 将新创建的 WP_Customize_Control 对象存储到 $this->controls 数组中。

3.2 WP_Customize_Control 的子类

WordPress 提供了丰富的 WP_Customize_Control 子类,涵盖了常见的用户界面控件:

控件类 描述
WP_Customize_Color_Control 颜色选择器
WP_Customize_Image_Control 图片上传控件
WP_Customize_Upload_Control 文件上传控件
WP_Customize_Media_Control 多媒体上传控件(图片、视频、音频)
WP_Customize_Cropped_Image_Control 可裁剪的图片上传控件
WP_Customize_Background_Image_Control 背景图片控件,集成了图片选择、裁剪、平铺等功能
WP_Customize_Header_Image_Control 头部图片控件,集成了图片选择、裁剪等功能
WP_Customize_Nav_Menu_Control 导航菜单选择控件
WP_Customize_Nav_Menu_Location_Control 导航菜单位置控件,用于指定菜单在主题中的显示位置
WP_Customize_Nav_Menu_Item_Control 导航菜单项控件,用于编辑菜单项的属性,例如标题、链接、目标等
WP_Customize_Dropdown_Pages_Control 下拉列表选择页面控件
WP_Customize_TextArea_Control 多行文本框
WP_Customize_Code_Editor_Control 代码编辑器控件,用于编辑代码片段

如果你觉得这些还不够用,还可以自定义 WP_Customize_Control 的子类,创建自己的控件。

3.3 控件与设置的关联

每个 WP_Customize_Control 对象都必须与一个 WP_Customize_Setting 对象相关联。 通过 controlsetting 属性,可以指定它所关联的设置。

$wp_customize->add_control( 'blogname', array(
    'label'    => __( 'Site Title' ),
    'section'  => 'title_tagline',
    'settings' => 'blogname', // 与 blogname 设置相关联
) );

3.4 控件的渲染 (render_content() 方法)

每个 WP_Customize_Control 子类都需要实现 render_content() 方法,用于渲染控件的内容。 这个方法会生成 HTML 代码,显示控件的界面元素。

例如,WP_Customize_Color_Controlrender_content() 方法会生成一个颜色选择器:

protected function render_content() {
    ?>
    <label>
        <?php if ( ! empty( $this->label ) ) : ?>
            <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
        <?php endif; ?>
        <?php if ( ! empty( $this->description ) ) : ?>
            <span class="description customize-control-description"><?php echo $this->description; ?></span>
        <?php endif; ?>
    </label>
    <input class="color-picker-hex" type="text" data-palette="<?php echo esc_attr( wp_json_encode( $this->palette ) ); ?>" data-default-color="<?php echo esc_attr( $this->default ); ?>" value="<?php echo esc_attr( $this->value() ); ?>" />
    <?php
}

4. 区块 (Sections) 和 面板 (Panels): 定制项的组织者

为了更好地组织控件,WordPress 提供了区块 (Sections) 和 面板 (Panels) 的概念。

  • 区块 (Sections): 用于将相关的控件组织在一起。 比如,可以将 "网站标题"、"网站副标题"、"网站 Logo" 等控件放在 "网站身份" 区块中。
  • 面板 (Panels): 用于将相关的区块组织在一起。 比如,可以将 "网站身份" 区块、"主题颜色" 区块、"页眉图片" 区块放在 "外观" 面板中。

4.1 注册区块 (add_section() 方法)

public function add_section( $id, $args = array() ) {
    if ( ! is_string( $id ) || '' === $id ) {
        _doing_it_wrong( __FUNCTION__, __( 'Section ID must be a non-empty string.' ), '4.2.0' );
        return null;
    }

    if ( isset( $this->sections[ $id ] ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Section %s is already registered.' ), $id ), '4.0.0' );
        return null;
    }

    $class = 'WP_Customize_Section';

    if ( isset( $args['type'] ) ) {
        $class = $args['type'];
    }

    if ( ! class_exists( $class ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Section type %s does not exist.' ), $class ), '4.0.0' );
        return null;
    }

    $section = new $class( $this, $id, $args );

    $this->sections[ $id ] = $section;

    return $section;
}

4.2 注册面板 (add_panel() 方法)

public function add_panel( $id, $args = array() ) {
    if ( ! is_string( $id ) || '' === $id ) {
        _doing_it_wrong( __FUNCTION__, __( 'Panel ID must be a non-empty string.' ), '4.2.0' );
        return null;
    }

    if ( isset( $this->panels[ $id ] ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Panel %s is already registered.' ), $id ), '4.0.0' );
        return null;
    }

    $class = 'WP_Customize_Panel';

    if ( isset( $args['type'] ) ) {
        $class = $args['type'];
    }

    if ( ! class_exists( $class ) ) {
        _doing_it_wrong( __FUNCTION__, sprintf( __( 'Panel type %s does not exist.' ), $class ), '4.0.0' );
        return null;
    }

    $panel = new $class( $this, $id, $args );

    $this->panels[ $id ] = $panel;

    return $panel;
}

4.3 WP_Customize_SectionWP_Customize_Panel 的子类

WordPress 也提供了一些 WP_Customize_SectionWP_Customize_Panel 的子类,用于实现更高级的功能。

  • WP_Customize_Sidebar_Section:用于定制侧边栏。
  • WP_Customize_Nav_Menu_Panel:用于定制导航菜单。

4.4 控件与区块的关联

在注册控件时,可以使用 section 属性将控件与一个区块关联起来。

$wp_customize->add_control( 'blogname', array(
    'label'    => __( 'Site Title' ),
    'section'  => 'title_tagline', // 与 title_tagline 区块相关联
    'settings' => 'blogname',
) );

5. 主题定制器的预览

主题定制器的一个重要功能是实时预览。 当用户修改一个设置的值时,预览窗口会立即更新,显示修改后的效果。

5.1 customize_preview_init 动作钩子

WP_Customize_Manager 类在 customize_preview_init 动作钩子中注册了一个回调函数,用于初始化预览窗口。

add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );

public function customize_preview_init() {
    // 加载主题定制器的预览脚本
    wp_enqueue_script( 'customize-preview' );

    // 其他初始化逻辑...
}

5.2 预览脚本 (customize-preview.js)

customize-preview.js 是一个 JavaScript 文件,负责与主题定制器进行通信,并更新预览窗口的内容。

它会监听 wp.customize 对象的变化,当一个设置的值发生改变时,它会向预览窗口发送消息,告诉它更新相应的元素。

5.3 postMessage API

主题定制器和预览窗口运行在不同的域名下,它们之间使用 postMessage API 进行通信。 postMessage API 允许跨域名的 JavaScript 代码安全地进行通信。

6. 保存定制

当用户点击 "保存并发布" 按钮时,主题定制器会将所有修改后的设置值保存到数据库中。

6.1 customize_save 动作钩子

WP_Customize_Manager 类在 customize_save 动作钩子中注册了一个回调函数,用于保存定制值。

add_action( 'customize_save', array( $this, 'save' ) );

public function save() {
    // 验证用户权限
    if ( ! current_user_can( 'customize' ) ) {
        wp_die( -1 );
    }

    // 循环遍历所有设置,并保存它们的值
    foreach ( $this->settings as $setting ) {
        $setting->save();
    }

    // 其他保存逻辑...
}

6.2 WP_Customize_Setting::save() 方法

每个 WP_Customize_Setting 对象都有一个 save() 方法,用于将设置的值保存到数据库中。

public function save() {
    if ( ! $this->check_capabilities() ) {
        return false;
    }

    $value = $this->post_value();

    if ( is_null( $value ) ) {
        return false;
    }

    return $this->update( $value );
}

7. 总结

今天我们深入探讨了 WordPress WP_Customize_Manager 类的源码,了解了它如何管理主题定制器的设置、控件、区块和面板,以及如何实现实时预览和保存定制值。

WP_Customize_Manager 类是主题定制器的核心,理解它的运作机制对于开发自定义主题和插件非常重要。

希望这次的分享对大家有所帮助! 下次有机会再聊!

发表回复

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