原来主题里有个 custom/ 目录(custom.css / custom.js / custom.php), 是给用户随手加样式的入口。问题是它位于主题目录内,而 WordPress 升级 主题的做法是把整个主题目录删掉再解压(class-theme-upgrader.php 里 clear_destination = true,随后 delete_old_theme() 递归删除), 所以每次升级这些文件都会丢。custom.php 还会被 require, 等于在主题里留了一个执行任意 PHP 的口子。 改成后台选项: - 主题选项新增「自定义代码」组,含自定义 CSS / 自定义 JS 两个输入框 - 内容存在数据库的 kratos 选项里,与主题文件解耦,升级、重装都不影响 - 分别在主题样式和主题脚本之后加载,优先级最高 为此新增了 code 字段类型,没有复用 textarea:textarea 的 sanitizer 走 wp_kses(),CSS 的子选择器 > 和 JS 的 < 会被当成 HTML 标签剥掉, 样式会静默失效。code 类型只去掉 PHP 标签、打断 </style> 和 </script>, 其余原样保留。渲染时也不走公共的 stripslashes —— options.php 已经 wp_unslash 过,再剥一层会把 CSS 的 \f101、JS 的 \n 啃坏。 自定义 CSS 的默认值就是原来 custom.css 的内容,逐字节一致(354 字节), 所以升级瞬间站点外观没有任何变化。清空输入框保存即可彻底去掉。 验证:真实 WP 7.1.3 / PHP 8.5.12 环境 20 条断言全过(含 DB 往返、 子选择器 / 反斜杠保真、危险内容过滤),生产选项指纹前后一致; 反向注入「默认值改空」能立刻被抓到。
219 lines
5.5 KiB
PHP
219 lines
5.5 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
|
||
*/
|
||
|
||
/**
|
||
* Sanitization for text input
|
||
*
|
||
* @link http://developer.wordpress.org/reference/functions/sanitize_text_field/
|
||
*/
|
||
add_filter( 'of_sanitize_text', 'sanitize_text_field' );
|
||
|
||
/**
|
||
* Sanitization for password input
|
||
*
|
||
* @link http://developer.wordpress.org/reference/functions/sanitize_text_field/
|
||
*/
|
||
add_filter( 'of_sanitize_password', 'sanitize_text_field' );
|
||
|
||
/**
|
||
* Sanitization for select input
|
||
*
|
||
* Validates that the selected option is a valid option.
|
||
*/
|
||
add_filter( 'of_sanitize_select', 'of_sanitize_enum', 10, 2 );
|
||
|
||
/**
|
||
* Sanitization for radio input
|
||
*
|
||
* Validates that the selected option is a valid option.
|
||
*/
|
||
add_filter( 'of_sanitize_radio', 'of_sanitize_enum', 10, 2 );
|
||
|
||
/**
|
||
* Sanitization for image selector
|
||
*
|
||
* Validates that the selected option is a valid option.
|
||
*/
|
||
add_filter( 'of_sanitize_images', 'of_sanitize_enum', 10, 2 );
|
||
|
||
/**
|
||
* Sanitization for textarea field
|
||
*
|
||
* @param $input string
|
||
* @return $output sanitized string
|
||
*/
|
||
function of_sanitize_textarea( $input ) {
|
||
global $allowedposttags;
|
||
$output = wp_kses( $input, $allowedposttags );
|
||
return $output;
|
||
}
|
||
add_filter( 'of_sanitize_textarea', 'of_sanitize_textarea' );
|
||
|
||
/**
|
||
* Sanitization for raw code fields (theme's own and user's CSS / JS).
|
||
*
|
||
* 为什么不能复用 textarea:textarea 走 wp_kses(),CSS 里的子选择器
|
||
* `>`、`+`、`~` 以及 JS 里的比较运算符 `<`、`>` 都会被当成 HTML 标签
|
||
* 剥掉。例:`a > b { }` 存进库会变成 `a b { }`,样式静默失效。
|
||
* 所以这里只做三件必要的事:
|
||
* 1. 去掉 PHP 标签,避免这段文本将来被 eval / include 时执行代码;
|
||
* 2. 把 `</style>`、`</script>` 打断,避免提前闭合输出标签;
|
||
* 3. 其余内容原样返回(含反斜杠转义,如 CSS 的 `\f101`)。
|
||
*
|
||
* @param $input string
|
||
* @return $output sanitized string
|
||
*/
|
||
function of_sanitize_code( $input ) {
|
||
if ( ! is_string( $input ) ) {
|
||
return '';
|
||
}
|
||
$output = str_replace( array( '<?', '?>' ), '', $input );
|
||
$output = preg_replace( '#</\s*(style|script)#i', '<\\/$1', $output );
|
||
return $output;
|
||
}
|
||
add_filter( 'of_sanitize_code', 'of_sanitize_code' );
|
||
|
||
/**
|
||
* Sanitization for checkbox input
|
||
*
|
||
* @param $input string (1 or empty) checkbox state
|
||
* @return $output '1' or false
|
||
*/
|
||
function of_sanitize_checkbox( $input ) {
|
||
if ( $input ) {
|
||
$output = '1';
|
||
} else {
|
||
$output = false;
|
||
}
|
||
return $output;
|
||
}
|
||
add_filter( 'of_sanitize_checkbox', 'of_sanitize_checkbox' );
|
||
|
||
/**
|
||
* File upload sanitization.
|
||
*
|
||
* Returns a sanitized filepath if it has a valid extension.
|
||
*
|
||
* @param string $input filepath
|
||
* @returns string $output filepath
|
||
*/
|
||
function of_sanitize_upload( $input ) {
|
||
$output = '';
|
||
$filetype = wp_check_filetype( $input );
|
||
if ( $filetype["ext"] ) {
|
||
$output = esc_url( $input );
|
||
}
|
||
return $output;
|
||
}
|
||
add_filter( 'of_sanitize_upload', 'of_sanitize_upload' );
|
||
|
||
/**
|
||
* Sanitization of input with allowed tags and wpautotop.
|
||
*
|
||
* Allows allowed tags in html input and ensures tags close properly.
|
||
*
|
||
* @param string $input
|
||
* @returns string $output
|
||
*/
|
||
function of_sanitize_allowedtags( $input ) {
|
||
global $allowedtags;
|
||
$output = wpautop( wp_kses( $input, $allowedtags ) );
|
||
return $output;
|
||
}
|
||
|
||
/**
|
||
* Sanitization of input with allowed post tags and wpautotop.
|
||
*
|
||
* Allows allowed post tags in html input and ensures tags close properly.
|
||
*
|
||
* @param string $input
|
||
* @returns string $output
|
||
*/
|
||
function of_sanitize_allowedposttags( $input ) {
|
||
global $allowedposttags;
|
||
$output = wpautop( wp_kses( $input, $allowedposttags) );
|
||
return $output;
|
||
}
|
||
|
||
/**
|
||
* Validates that the $input is one of the avilable choices
|
||
* for that specific option.
|
||
*
|
||
* @param string $input
|
||
* @returns string $output
|
||
*/
|
||
function of_sanitize_enum( $input, $option ) {
|
||
$output = '';
|
||
if ( array_key_exists( $input, $option['options'] ) ) {
|
||
$output = $input;
|
||
}
|
||
return $output;
|
||
}
|
||
|
||
/**
|
||
* Sanitization for background option.
|
||
*
|
||
* @returns array $output
|
||
*/
|
||
function of_sanitize_background( $input ) {
|
||
|
||
$output = wp_parse_args( $input, array(
|
||
'color' => '',
|
||
'image' => '',
|
||
'repeat' => 'repeat',
|
||
'position' => 'top center',
|
||
'attachment' => 'scroll'
|
||
) );
|
||
|
||
$output['color'] = apply_filters( 'of_sanitize_hex', $input['color'] );
|
||
$output['image'] = apply_filters( 'of_sanitize_upload', $input['image'] );
|
||
|
||
return $output;
|
||
}
|
||
add_filter( 'of_sanitize_background', 'of_sanitize_background' );
|
||
|
||
/**
|
||
* Sanitize a color represented in hexidecimal notation.
|
||
*
|
||
* @param string Color in hexidecimal notation. "#" may or may not be prepended to the string.
|
||
* @param string The value that this function should return if it cannot be recognized as a color.
|
||
* @return string
|
||
*/
|
||
|
||
function of_sanitize_hex( $hex, $default = '' ) {
|
||
if ( of_validate_hex( $hex ) ) {
|
||
return $hex;
|
||
}
|
||
return $default;
|
||
}
|
||
add_filter( 'of_sanitize_color', 'of_sanitize_hex' );
|
||
|
||
/**
|
||
* Is a given string a color formatted in hexidecimal notation?
|
||
*
|
||
* @param string Color in hexidecimal notation. "#" may or may not be prepended to the string.
|
||
* @return bool
|
||
*/
|
||
function of_validate_hex( $hex ) {
|
||
$hex = trim( $hex );
|
||
/* Strip recognized prefixes. */
|
||
if ( 0 === strpos( $hex, '#' ) ) {
|
||
$hex = substr( $hex, 1 );
|
||
}
|
||
elseif ( 0 === strpos( $hex, '%23' ) ) {
|
||
$hex = substr( $hex, 3 );
|
||
}
|
||
/* Regex match. */
|
||
if ( 0 === preg_match( '/^[0-9a-fA-F]{6}$/', $hex ) ) {
|
||
return false;
|
||
}
|
||
else {
|
||
return true;
|
||
}
|
||
} |