Files
wordpress-kratos-yang/inc/options-framework/includes/class-options-framework-admin.php
T

412 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
// ⚠️ 版本号必须跟主题版本(THEME_VERSION)走:3.2.24 踩坑——
// 原来用框架常量 Options_Framework::VERSION(1.9.1 永不变),主题升级后
// 资源 URL 不变,浏览器拿缓存旧 CSS,后台「设置页重排」看起来像没做。
wp_enqueue_style( 'optionsframework', get_stylesheet_directory_uri() . '/inc/options-framework/css/optionsframework.css', array(), THEME_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' ),
THEME_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();
// 已保存的值,供"字段未提交时沿用旧值"用。放循环外,避免重复查库。
$saved_options = get_option( 'kratos' );
if ( ! is_array( $saved_options ) ) {
$saved_options = array();
}
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'] ) ) {
/*
* ⚠️ 表单里没有这个键时,**必须保留库里已有的值**,不能写空。
*
* 原来这里直接写 `$input[$id]`。如果该字段没被提交(比如某个
* 分组没渲染出来、或者字段被条件隐藏、或者表单中途出错只提交了一半),
* `$input[$id]` 是未定义索引 ⇒ 取到 null ⇒
* `sanitize_text_field(null)` 返回 `''` ⇒ **把用户存了很久的
* 配置静默清成空串**。而"清成空串"和"用户主动清空"在库里长得一模一样,
* 事后完全无法区分,只会表现为"我明明填过,怎么没了"。
*
* 例外:checkbox 和 multicheck 上面已经把缺省项补成了 false,
* 这两类的"没提交"在语义上确实等于"没勾",保持原样。
*/
$is_toggle = in_array( $option['type'], array( 'checkbox', 'multicheck' ), true );
if ( array_key_exists( $id, $input ) ) {
$clean[$id] = apply_filters( 'of_sanitize_' . $option['type'], $input[$id], $option );
} elseif ( $is_toggle ) {
$clean[$id] = apply_filters( 'of_sanitize_' . $option['type'], $input[$id], $option );
} else {
// 没提交 ⇒ 沿用旧值(旧值也没有就不写这个键,让运行时默认值生效)
if ( array_key_exists( $id, $saved_options ) ) {
$clean[$id] = $saved_options[$id];
}
}
}
}
if ( isset( $_POST['sendmail'] ) ) {
/*
* 测试发信要让「这一批新配置」立刻生效,但**绝对不能**
* 在这里调 update_option()。
*
* ── 为什么不调(3.2.11 的血案)──────────────────────────
* 本函数是 register_setting() 注册的 sanitize_callback,
* WP 把挂在 `sanitize_option_kratos` 这个过滤器上
* (wp-includes/option.php:3073)。而 update_option() 内部
* **会再跑一遍**同名过滤器 ⇒ 又调回本函数 ⇒ 本函数再
* update_option() ⇒ 无限递归。
*
* 递归每层都在复制 $clean 和 $_POST 派生的大数组,最终把
* 内存吃到上限,报出来的是
* 「Allowed memory size of 268435456 bytes exhausted
* in class-wp-block-parser.php」。报错落在区块解析器上纯属
* 巧合 —— 那只是递归链最后执行到的一行,看它会完全找错方向。
*
* 正确做法:不写库、不递归,只把新值放进一个一次性覆盖层,
* 让 kratos_option() 在本次请求里优先读它。写完库由 WP 自己
* 在 validate_options 返回后完成。
*/
$GLOBALS['__yang_options_override'] = is_array( $clean ) ? $clean : array();
$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;
}
}