两个问题叠在一起,都不报错,只是「测了等于没测」:
1. 按钮发的是纯文本
validate_options() 里 isset($_POST['sendmail']) 那个分支只有一句裸
wp_mail(),正文是「恭喜您 SMTP 邮件服务配置成功!」。于是点测试收到的
东西和真实评论通知(HTML 模板)完全不同,没法用它验收版式。
主题里 yang_mail_send_test() 本来就是干这件事的,但全仓库零调用点 ——
注释写着「后台测试按钮走这条」,实际上从没接上线。
2. 发信发生在配置落库之前
validate_options() 是 register_setting 的回调,返回值之后才由 WP
写进数据库。在分支里直接发信,主题配 PHPMailer 时读 kratos_option()
拿到的还是上一次保存的主机/账号/密码。刚改完 SMTP 密码点测试,
测的是旧密码,表现成「明明填对了却发不出去」。
改法:
- 分支里先 update_option('kratos', $clean) 再调 yang_send_test_mail()
- yang_send_test_mail() 优先调主题的 yang_mail_send_test();
主题被换掉时退回原来的纯文本,不能因此白屏
- send_mail_notice() 从「一律发送完成」改成三态:WP_Error / wp_mail
返回 false / 成功,前两者用 error 样式。原来密码错了也显示成功,
用户会去邮箱里干等。
assertions: run_tests 95 → 104 条(新增 2k 组 8 条 + 1 条)
- sendmail 分支确实调用主题发信函数、确实先落库
- 落库位置在发信位置之前(判顺序,不判有没有)
- 分支里没有裸 wp_mail
- 发信函数对主题缺失做了兜底
- 提示语分三态,两类失败都用 error 样式
falsify 22 → 24 条:
- 注入「先发信后落库」⇒ 变红(这条一开始是假绿:判据比的是注释里
那两个词谁先出现,注释里 update_option 和函数名都在,恒真。
改成先剥注释再比代码里的位置)
- 注入「退回裸 wp_mail」⇒ 变红
顺带修了 falsify 的一个基础设施坑:options-framework 那批文件在工作区
是 CRLF,锚点里写 \n 对多行内容恒不匹配,报的是「命中 0 次」,
很像锚点选错了。inject() 现在先试原样、不中再把 \n 换 \r\n 重试一次。
全量 12 组 / 506 条断言全绿。
369 lines
11 KiB
PHP
369 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* @package Options_Framework
|
|
* @author Devin Price <devin@wptheming.com>
|
|
* @license GPL-2.0+
|
|
* @link http://wptheming.com
|
|
* @copyright 2010-2014 WP Theming
|
|
*/
|
|
|
|
class Options_Framework_Admin {
|
|
|
|
/**
|
|
* Page hook for the options screen
|
|
*
|
|
* @since 1.7.0
|
|
* @type string
|
|
*/
|
|
protected $options_screen = null;
|
|
|
|
/**
|
|
* Hook in the scripts and styles
|
|
*
|
|
* @since 1.7.0
|
|
*/
|
|
public function init() {
|
|
|
|
// Gets options to load
|
|
$options = & Options_Framework::_optionsframework_options();
|
|
|
|
// Checks if options are available
|
|
if ( $options ) {
|
|
|
|
// Add the options page and menu item.
|
|
add_action('admin_menu', array($this, 'add_top_options_page'));
|
|
add_action('admin_menu', array($this, 'add_sub_options_page'));
|
|
|
|
// Add the required scripts and styles
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
|
|
|
|
// Settings need to be registered after admin_init
|
|
add_action( 'admin_init', array( $this, 'settings_init' ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Registers the settings
|
|
*
|
|
* @since 1.7.0
|
|
*/
|
|
function settings_init() {
|
|
|
|
// Get the option name
|
|
$options_framework = new Options_Framework;
|
|
$name = $options_framework->get_option_name();
|
|
|
|
// Registers the settings fields and callback
|
|
register_setting( 'optionsframework', $name, array ( $this, 'validate_options' ) );
|
|
|
|
// Displays notice after options save
|
|
add_action( 'optionsframework_after_validate', array( $this, 'save_options_notice' ) );
|
|
|
|
add_action( 'optionsframework_after_sendmail', array( $this, 'send_mail_notice' ) );
|
|
}
|
|
|
|
public function add_top_options_page()
|
|
{
|
|
add_menu_page(
|
|
__('主题设置', 'kratos'),
|
|
__('主题设置', 'kratos'),
|
|
'manage_options',
|
|
'kratos_options',
|
|
'',
|
|
'dashicons-admin-generic',
|
|
99
|
|
);
|
|
}
|
|
|
|
public static function menu_settings()
|
|
{
|
|
$menu = array(
|
|
'parent_slug' => 'kratos_options',
|
|
'page_title' => __('主题设置', 'kratos'),
|
|
'menu_title' => __('主题设置', 'kratos'),
|
|
'capability' => 'manage_options',
|
|
'menu_slug' => 'kratos_options',
|
|
);
|
|
|
|
return $menu;
|
|
}
|
|
|
|
public function add_sub_options_page()
|
|
{
|
|
$menu = $this->menu_settings();
|
|
|
|
$this->options_screen = add_submenu_page(
|
|
$menu['parent_slug'],
|
|
$menu['page_title'],
|
|
$menu['menu_title'],
|
|
$menu['capability'],
|
|
$menu['menu_slug'],
|
|
array($this, 'options_page')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Loads the required stylesheets
|
|
*
|
|
* @since 1.7.0
|
|
*/
|
|
|
|
function enqueue_admin_styles( $hook ) {
|
|
|
|
if ( $this->options_screen != $hook )
|
|
return;
|
|
|
|
wp_enqueue_style( 'optionsframework', get_stylesheet_directory_uri() . '/inc/options-framework/css/optionsframework.css', array(), Options_Framework::VERSION );
|
|
wp_enqueue_style( 'wp-color-picker' );
|
|
}
|
|
|
|
/**
|
|
* Loads the required javascript
|
|
*
|
|
* @since 1.7.0
|
|
*/
|
|
function enqueue_admin_scripts( $hook ) {
|
|
|
|
if ( $this->options_screen != $hook )
|
|
return;
|
|
|
|
// Enqueue custom option panel JS
|
|
wp_enqueue_script(
|
|
'options-custom',
|
|
get_stylesheet_directory_uri() . '/inc/options-framework/js/options-custom.js',
|
|
array( 'jquery','wp-color-picker' ),
|
|
Options_Framework::VERSION
|
|
);
|
|
|
|
// Inline scripts from options-interface.php
|
|
add_action( 'admin_head', array( $this, 'of_admin_head' ) );
|
|
}
|
|
|
|
function of_admin_head() {
|
|
// Hook to add custom scripts
|
|
do_action( 'optionsframework_custom_scripts' );
|
|
}
|
|
|
|
/**
|
|
* Builds out the options panel.
|
|
*
|
|
* If we were using the Settings API as it was intended we would use
|
|
* do_settings_sections here. But as we don't want the settings wrapped in a table,
|
|
* we'll call our own custom optionsframework_fields. See options-interface.php
|
|
* for specifics on how each individual field is generated.
|
|
*
|
|
* Nonces are provided using the settings_fields()
|
|
*
|
|
* @since 1.7.0
|
|
*/
|
|
function options_page() { ?>
|
|
|
|
<div id="optionsframework-wrap" class="wrap">
|
|
|
|
<?php $menu = $this->menu_settings(); ?>
|
|
<h2><?php echo esc_html( $menu['page_title'] ); ?></h2>
|
|
|
|
<h2 class="nav-tab-wrapper">
|
|
<?php echo Options_Framework_Interface::optionsframework_tabs(); ?>
|
|
</h2>
|
|
|
|
<?php settings_errors( 'options-framework' ); ?>
|
|
|
|
<div id="optionsframework-metabox" class="metabox-holder">
|
|
<div id="optionsframework" class="postbox">
|
|
<form action="options.php" method="post">
|
|
<?php settings_fields( 'optionsframework' ); ?>
|
|
<?php Options_Framework_Interface::optionsframework_fields(); /* Settings */ ?>
|
|
<div id="optionsframework-submit">
|
|
<input type="submit" class="button-primary" name="update" value="<?php esc_attr_e( '保存配置', 'kratos' ); ?>" />
|
|
<input type="submit" class="reset-button button-secondary" name="reset" value="<?php esc_attr_e( '恢复默认', 'kratos' ); ?>" onclick="return confirm( '<?php print esc_js( __( '单击「确定」进行恢复,但所有的配置都将丢失!', 'kratos' ) ); ?>' );" />
|
|
<div class="clear"></div>
|
|
</div>
|
|
</form>
|
|
</div> <!-- / #container -->
|
|
</div>
|
|
<?php do_action( 'optionsframework_after' ); ?>
|
|
</div> <!-- / .wrap -->
|
|
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Validate Options.
|
|
*
|
|
* This runs after the submit/reset button has been clicked and
|
|
* validates the inputs.
|
|
*
|
|
* @uses $_POST['reset'] to restore default options
|
|
*/
|
|
function validate_options( $input ) {
|
|
|
|
/*
|
|
* Restore Defaults.
|
|
*
|
|
* In the event that the user clicked the "Restore Defaults"
|
|
* button, the options defined in the theme's options.php
|
|
* file will be added to the option for the active theme.
|
|
*/
|
|
|
|
if ( isset( $_POST['reset'] ) ) {
|
|
add_settings_error( 'options-framework', 'restore_defaults', __( '恢复完成', 'kratos' ), 'updated fade' );
|
|
return $this->get_default_values();
|
|
}
|
|
|
|
/*
|
|
* Update Settings
|
|
*
|
|
* This used to check for $_POST['update'], but has been updated
|
|
* to be compatible with the theme customizer introduced in WordPress 3.4
|
|
*/
|
|
|
|
$clean = array();
|
|
$options = & Options_Framework::_optionsframework_options();
|
|
foreach ( $options as $option ) {
|
|
|
|
if ( ! isset( $option['id'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ! isset( $option['type'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$id = preg_replace( '/[^a-zA-Z0-9._\-]/', '', strtolower( $option['id'] ) );
|
|
|
|
// Set checkbox to false if it wasn't sent in the $_POST
|
|
if ( 'checkbox' == $option['type'] && ! isset( $input[$id] ) ) {
|
|
$input[$id] = false;
|
|
}
|
|
|
|
// Set each item in the multicheck to false if it wasn't sent in the $_POST
|
|
if ( 'multicheck' == $option['type'] && ! isset( $input[$id] ) ) {
|
|
foreach ( $option['options'] as $key => $value ) {
|
|
$input[$id][$key] = false;
|
|
}
|
|
}
|
|
|
|
// For a value to be submitted to database it must pass through a sanitization filter
|
|
if ( has_filter( 'of_sanitize_' . $option['type'] ) ) {
|
|
$clean[$id] = apply_filters( 'of_sanitize_' . $option['type'], $input[$id], $option );
|
|
}
|
|
}
|
|
|
|
if ( isset( $_POST['sendmail'] ) ) {
|
|
/*
|
|
* 测试发信必须先落库再发。
|
|
*
|
|
* 这个函数是 register_setting 的回调,返回值**之后**才会被
|
|
* update_option() 写进数据库。如果在这里直接 wp_mail(),
|
|
* 主题配置 PHPMailer 时读 kratos_option() 拿到的还是**上一次**保存的
|
|
* 主机/账号/密码 —— 刚改完配置点测试,测的其实是旧配置,
|
|
* 用户会看到"明明填对了却发不出去"或"改了密码还能发"这种怪现象。
|
|
*/
|
|
update_option( 'kratos', $clean );
|
|
|
|
$this->yang_send_test_mail( $clean );
|
|
|
|
do_action( 'optionsframework_after_sendmail', $clean );
|
|
return $clean;
|
|
} else {
|
|
do_action( 'optionsframework_after_validate', $clean );
|
|
return $clean;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 发测试邮件,走主题模板
|
|
*
|
|
* 原来这里是一句裸 wp_mail(),正文只有一行纯文本 —— 点「测试邮件」
|
|
* 收到的样子和真实评论通知(HTML 模板)完全不同,等于没法用它验收版式。
|
|
* 主题里早有 yang_mail_send_test() 专门干这件事,但一直没接线,
|
|
* 所以优先调它;主题被换掉或不完全时退回原样,不能因此白屏。
|
|
*/
|
|
private function yang_send_test_mail( $clean )
|
|
{
|
|
$to = get_bloginfo( 'admin_email' );
|
|
|
|
if ( function_exists( 'yang_mail_send_test' ) ) {
|
|
$r = yang_mail_send_test( $to );
|
|
$GLOBALS['__yang_sendmail_result'] = $r;
|
|
return;
|
|
}
|
|
|
|
wp_mail( $to, __( '[测试]邮件服务配置成功', 'kratos' ), __( '恭喜您 SMTP 邮件服务配置成功!', 'kratos' ) );
|
|
$GLOBALS['__yang_sendmail_result'] = null;
|
|
}
|
|
|
|
/**
|
|
* Display message when options have been saved
|
|
*/
|
|
function save_options_notice() {
|
|
add_settings_error( 'options-framework', 'save_options', __( '保存成功', 'kratos' ), 'updated fade' );
|
|
}
|
|
|
|
/**
|
|
* 测试邮件的提示
|
|
*
|
|
* 原来不管发没发出去都显示「发送完成」—— SMTP 密码填错时用户也看到
|
|
* "成功",然后去邮箱里干等。现在按真实返回值分三态:成功 / 失败 / 地址不合法。
|
|
*/
|
|
function send_mail_notice() {
|
|
$to = get_bloginfo( 'admin_email' );
|
|
$r = isset( $GLOBALS['__yang_sendmail_result'] ) ? $GLOBALS['__yang_sendmail_result'] : null;
|
|
|
|
if ( is_wp_error( $r ) ) {
|
|
add_settings_error(
|
|
'options-framework', 'send_mail',
|
|
sprintf( __( '测试邮件未发出:%s', 'kratos' ), $r->get_error_message() ),
|
|
'error fade'
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ( $r === false ) {
|
|
add_settings_error(
|
|
'options-framework', 'send_mail',
|
|
__( '测试邮件发送失败,请检查服务器地址、端口、加密方式与密码。', 'kratos' ),
|
|
'error fade'
|
|
);
|
|
return;
|
|
}
|
|
|
|
add_settings_error( 'options-framework', 'send_mail', sprintf( __( '测试邮件已发出,请查收:%s(若没收到请先看垃圾箱)', 'kratos' ), $to ), 'updated fade' );
|
|
}
|
|
|
|
/**
|
|
* Get the default values for all the theme options
|
|
*
|
|
* Get an array of all default values as set in
|
|
* options.php. The 'id','std' and 'type' keys need
|
|
* to be defined in the configuration array. In the
|
|
* event that these keys are not present the option
|
|
* will not be included in this function's output.
|
|
*
|
|
* @return array Re-keyed options configuration array.
|
|
*
|
|
*/
|
|
function get_default_values() {
|
|
$output = array();
|
|
$config = & Options_Framework::_optionsframework_options();
|
|
foreach ( (array) $config as $option ) {
|
|
if ( ! isset( $option['id'] ) ) {
|
|
continue;
|
|
}
|
|
if ( ! isset( $option['std'] ) ) {
|
|
continue;
|
|
}
|
|
if ( ! isset( $option['type'] ) ) {
|
|
continue;
|
|
}
|
|
if ( has_filter( 'of_sanitize_' . $option['type'] ) ) {
|
|
$output[$option['id']] = apply_filters( 'of_sanitize_' . $option['type'], $option['std'], $option );
|
|
}
|
|
}
|
|
return $output;
|
|
}
|
|
}
|