Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
150dd442d2 | ||
|
|
b3356b6f77 | ||
|
|
60eb789f93 | ||
|
|
5d196f2b09 | ||
|
|
9e426bbb49 | ||
|
|
cfd130d33a | ||
|
|
551057bb68 | ||
|
|
6dbcbfae60 | ||
|
|
a820c920f9 | ||
|
|
c9168e3a54 | ||
|
|
69de48fd72 | ||
|
|
7e35f92679 | ||
|
|
d6df175451 | ||
|
|
4fe0310d7a | ||
|
|
4b6fa85282 |
@@ -9,7 +9,7 @@
|
||||
<body>
|
||||
<h1 id="logo">
|
||||
<a href="https://wordpress.org/"><img alt="WordPress" src="wp-admin/images/wordpress-logo.png" /></a>
|
||||
<br /> Version 4.0.3
|
||||
<br /> Version 4.0.6
|
||||
</h1>
|
||||
<p style="text-align: center">Semantic Personal Publishing Platform</p>
|
||||
|
||||
|
||||
@@ -42,18 +42,30 @@ include( ABSPATH . 'wp-admin/admin-header.php' );
|
||||
</h2>
|
||||
|
||||
<div class="changelog point-releases">
|
||||
<h3><?php echo _n( 'Maintenance and Security Release', 'Maintenance and Security Releases', 3 ); ?></h3>
|
||||
<h3><?php echo _n( 'Maintenance and Security Release', 'Maintenance and Security Releases', 6 ); ?></h3>
|
||||
<p><?php printf( _n( '<strong>Version %1$s</strong> addressed a security issue.',
|
||||
'<strong>Version %1$s</strong> addressed some security issues.', 2 ), '4.0.6' ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'https://codex.wordpress.org/Version_4.0.6' ); ?>
|
||||
</p>
|
||||
<p><?php printf( _n( '<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bug.',
|
||||
'<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bugs.', 3 ), '4.0.5', number_format_i18n( 3 ) ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'https://codex.wordpress.org/Version_4.0.5' ); ?>
|
||||
</p>
|
||||
<p><?php printf( _n( '<strong>Version %1$s</strong> addressed a security issue.',
|
||||
'<strong>Version %1$s</strong> addressed some security issues.', 1 ), '4.0.4' ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'https://codex.wordpress.org/Version_4.0.4' ); ?>
|
||||
</p>
|
||||
<p><?php printf( _n( '<strong>Version %1$s</strong> addressed %2$s bug.',
|
||||
'<strong>Version %1$s</strong> addressed %2$s bugs.', 1 ), '4.0.3', number_format_i18n( 1 ) ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'https://codex.wordpress.org/Version_4.0.3' ); ?>
|
||||
</p>
|
||||
<p><?php printf( _n( '<strong>Version %1$s</strong> addressed a security issue.',
|
||||
'<strong>Version %1$s</strong> addressed some security issues.', 8 ), '4.0.2' ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'http://codex.wordpress.org/Version_4.0.2' ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'https://codex.wordpress.org/Version_4.0.2' ); ?>
|
||||
</p>
|
||||
<p><?php printf( _n( '<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bug.',
|
||||
'<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bugs.', 23 ), '4.0.1', number_format_i18n( 23 ) ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'http://codex.wordpress.org/Version_4.0.1' ); ?>
|
||||
<?php printf( __( 'For more information, see <a href="%s">the release notes</a>.' ), 'https://codex.wordpress.org/Version_4.0.1' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -416,6 +416,10 @@ function wp_network_dashboard_right_now() {
|
||||
function wp_dashboard_quick_press( $error_msg = false ) {
|
||||
global $post_ID;
|
||||
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if a new auto-draft (= no new post_ID) is needed or if the old can be used */
|
||||
$last_post_id = (int) get_user_option( 'dashboard_quick_press_last_post_id' ); // Get the last post_ID
|
||||
if ( $last_post_id ) {
|
||||
|
||||
@@ -1011,6 +1011,9 @@ function update_core($from, $to) {
|
||||
$wp_filesystem->delete($old_file, true);
|
||||
}
|
||||
|
||||
// Remove any Genericons example.html's from the filesystem
|
||||
_upgrade_422_remove_genericons();
|
||||
|
||||
// Upgrade DB with separate request
|
||||
/** This filter is documented in wp-admin/includes/update-core.php */
|
||||
apply_filters( 'update_feedback', __( 'Upgrading database…' ) );
|
||||
@@ -1149,3 +1152,67 @@ window.location = 'about.php?updated';
|
||||
exit();
|
||||
}
|
||||
add_action( '_core_updated_successfully', '_redirect_to_about_wordpress' );
|
||||
|
||||
/**
|
||||
* Cleans up Genericons example files.
|
||||
*
|
||||
* @since 4.2.2
|
||||
*/
|
||||
function _upgrade_422_remove_genericons() {
|
||||
global $wp_theme_directories, $wp_filesystem;
|
||||
|
||||
// A list of the affected files using the filesystem absolute paths.
|
||||
$affected_files = array();
|
||||
|
||||
// Themes
|
||||
foreach ( $wp_theme_directories as $directory ) {
|
||||
$affected_theme_files = _upgrade_422_find_genericons_files_in_folder( $directory );
|
||||
$affected_files = array_merge( $affected_files, $affected_theme_files );
|
||||
}
|
||||
|
||||
// Plugins
|
||||
$affected_plugin_files = _upgrade_422_find_genericons_files_in_folder( WP_PLUGIN_DIR );
|
||||
$affected_files = array_merge( $affected_files, $affected_plugin_files );
|
||||
|
||||
foreach ( $affected_files as $file ) {
|
||||
$gen_dir = $wp_filesystem->find_folder( trailingslashit( dirname( $file ) ) );
|
||||
if ( empty( $gen_dir ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The path when the file is accessed via WP_Filesystem may differ in the case of FTP
|
||||
$remote_file = $gen_dir . basename( $file );
|
||||
|
||||
if ( ! $wp_filesystem->exists( $remote_file ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $wp_filesystem->delete( $remote_file, false, 'f' ) ) {
|
||||
$wp_filesystem->put_contents( $remote_file, '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find Genericons example files in a given folder.
|
||||
*
|
||||
* @ignore
|
||||
* @since 4.2.2
|
||||
*
|
||||
* @param string $directory Directory path. Expects trailingslashed.
|
||||
* @return array
|
||||
*/
|
||||
function _upgrade_422_find_genericons_files_in_folder( $directory ) {
|
||||
$directory = trailingslashit( $directory );
|
||||
$files = array();
|
||||
|
||||
if ( file_exists( "{$directory}example.html" ) && false !== strpos( file_get_contents( "{$directory}example.html" ), '<title>Genericons</title>' ) ) {
|
||||
$files[] = "{$directory}example.html";
|
||||
}
|
||||
|
||||
foreach ( glob( $directory . '*', GLOB_ONLYDIR ) as $dir ) {
|
||||
$files = array_merge( $files, _upgrade_422_find_genericons_files_in_folder( $dir ) );
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
@@ -440,6 +440,9 @@ function upgrade_all() {
|
||||
if ( $wp_current_db_version < 29630 )
|
||||
upgrade_400();
|
||||
|
||||
if ( $wp_current_db_version < 29632 )
|
||||
upgrade_405();
|
||||
|
||||
maybe_disable_link_manager();
|
||||
|
||||
maybe_disable_automattic_widgets();
|
||||
@@ -1326,6 +1329,62 @@ function upgrade_400() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute changes made in WordPress 4.0.4.
|
||||
*
|
||||
* @since 4.0.4
|
||||
*/
|
||||
function upgrade_404() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute changes made in WordPress 4.0.5.
|
||||
*
|
||||
* @since 4.0.5
|
||||
*/
|
||||
function upgrade_405() {
|
||||
global $wp_current_db_version, $wpdb;
|
||||
|
||||
if ( $wp_current_db_version < 29632 ) {
|
||||
$content_length = $wpdb->get_col_length( $wpdb->comments, 'comment_content' );
|
||||
|
||||
if ( is_wp_error( $content_length ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false === $content_length ) {
|
||||
$content_length = array(
|
||||
'type' => 'byte',
|
||||
'length' => 65535,
|
||||
);
|
||||
} elseif ( ! is_array( $content_length ) ) {
|
||||
$length = (int) $content_length > 0 ? (int) $content_length : 65535;
|
||||
$content_length = array(
|
||||
'type' => 'byte',
|
||||
'length' => $length
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'byte' !== $content_length['type'] || 0 === $content_length['length'] ) {
|
||||
// Sites with malformed DB schemas are on their own.
|
||||
return;
|
||||
}
|
||||
|
||||
$allowed_length = intval( $content_length['length'] ) - 10;
|
||||
|
||||
$comments = $wpdb->get_results(
|
||||
"SELECT `comment_ID` FROM `{$wpdb->comments}`
|
||||
WHERE `comment_date_gmt` > '2015-04-26'
|
||||
AND LENGTH( `comment_content` ) >= {$allowed_length}
|
||||
AND ( `comment_content` LIKE '%<%' OR `comment_content` LIKE '%>%' )"
|
||||
);
|
||||
|
||||
foreach ( $comments as $comment ) {
|
||||
wp_delete_comment( $comment->comment_ID, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute network level changes
|
||||
*
|
||||
|
||||
@@ -113,8 +113,9 @@ case 'post-quickdraft-save':
|
||||
if ( ! wp_verify_nonce( $nonce, 'add-post' ) )
|
||||
$error_msg = __( 'Unable to submit this form, please refresh and try again.' );
|
||||
|
||||
if ( ! current_user_can( 'edit_posts' ) )
|
||||
$error_msg = __( 'Oops, you don’t have access to add new drafts.' );
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $error_msg )
|
||||
return wp_dashboard_quick_press( $error_msg );
|
||||
|
||||
@@ -1114,8 +1114,10 @@ function map_meta_cap( $cap, $user_id ) {
|
||||
case 'edit_post':
|
||||
case 'edit_page':
|
||||
$post = get_post( $args[0] );
|
||||
if ( empty( $post ) )
|
||||
if ( empty( $post ) ) {
|
||||
$caps[] = 'do_not_allow';
|
||||
break;
|
||||
}
|
||||
|
||||
if ( 'revision' == $post->post_type ) {
|
||||
$post = get_post( $post->post_parent );
|
||||
|
||||
@@ -60,7 +60,7 @@ class WP_Embed {
|
||||
add_shortcode( 'embed', array( $this, 'shortcode' ) );
|
||||
|
||||
// Do the shortcode (only the [embed] one is registered)
|
||||
$content = do_shortcode( $content );
|
||||
$content = do_shortcode( $content, true );
|
||||
|
||||
// Put the original shortcodes back
|
||||
$shortcode_tags = $orig_shortcode_tags;
|
||||
@@ -326,6 +326,10 @@ class WP_Embed {
|
||||
* @return string Potentially modified $content.
|
||||
*/
|
||||
public function autoembed( $content ) {
|
||||
// Strip newlines from all elements.
|
||||
$content = wp_replace_in_html_tags( $content, array( "\n" => " " ) );
|
||||
|
||||
// Find URLs that are on their own line.
|
||||
return preg_replace_callback( '|^\s*(https?://[^\s"]+)\s*$|im', array( $this, 'autoembed_callback' ), $content );
|
||||
}
|
||||
|
||||
|
||||
@@ -13,23 +13,141 @@ if ( !function_exists('_') ) {
|
||||
}
|
||||
}
|
||||
|
||||
if ( !function_exists('mb_substr') ):
|
||||
function mb_substr( $str, $start, $length=null, $encoding=null ) {
|
||||
return _mb_substr($str, $start, $length, $encoding);
|
||||
/**
|
||||
* Returns whether PCRE/u (PCRE_UTF8 modifier) is available for use.
|
||||
*
|
||||
* @ignore
|
||||
* @since 4.2.2
|
||||
* @access private
|
||||
*
|
||||
* @param bool $set - Used for testing only
|
||||
* null : default - get PCRE/u capability
|
||||
* false : Used for testing - return false for future calls to this function
|
||||
* 'reset': Used for testing - restore default behavior of this function
|
||||
*/
|
||||
function _wp_can_use_pcre_u( $set = null ) {
|
||||
static $utf8_pcre = 'reset';
|
||||
|
||||
if ( null !== $set ) {
|
||||
$utf8_pcre = $set;
|
||||
}
|
||||
|
||||
if ( 'reset' === $utf8_pcre ) {
|
||||
$utf8_pcre = @preg_match( '/^./u', 'a' );
|
||||
}
|
||||
|
||||
return $utf8_pcre;
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'mb_substr' ) ) :
|
||||
function mb_substr( $str, $start, $length = null, $encoding = null ) {
|
||||
return _mb_substr( $str, $start, $length, $encoding );
|
||||
}
|
||||
endif;
|
||||
|
||||
function _mb_substr( $str, $start, $length=null, $encoding=null ) {
|
||||
// the solution below, works only for utf-8, so in case of a different
|
||||
// charset, just use built-in substr
|
||||
$charset = get_option( 'blog_charset' );
|
||||
if ( !in_array( $charset, array('utf8', 'utf-8', 'UTF8', 'UTF-8') ) ) {
|
||||
return is_null( $length )? substr( $str, $start ) : substr( $str, $start, $length);
|
||||
/*
|
||||
* Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit.
|
||||
* For $encoding === UTF-8, the $str input is expected to be a valid UTF-8 byte sequence.
|
||||
* The behavior of this function for invalid inputs is undefined.
|
||||
*/
|
||||
function _mb_substr( $str, $start, $length = null, $encoding = null ) {
|
||||
if ( null === $encoding ) {
|
||||
$encoding = get_option( 'blog_charset' );
|
||||
}
|
||||
// use the regex unicode support to separate the UTF-8 characters into an array
|
||||
preg_match_all( '/./us', $str, $match );
|
||||
$chars = is_null( $length )? array_slice( $match[0], $start ) : array_slice( $match[0], $start, $length );
|
||||
return implode( '', $chars );
|
||||
|
||||
// The solution below works only for UTF-8,
|
||||
// so in case of a different charset just use built-in substr()
|
||||
if ( ! in_array( $encoding, array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) {
|
||||
return is_null( $length ) ? substr( $str, $start ) : substr( $str, $start, $length );
|
||||
}
|
||||
|
||||
if ( _wp_can_use_pcre_u() ) {
|
||||
// Use the regex unicode support to separate the UTF-8 characters into an array
|
||||
preg_match_all( '/./us', $str, $match );
|
||||
$chars = is_null( $length ) ? array_slice( $match[0], $start ) : array_slice( $match[0], $start, $length );
|
||||
return implode( '', $chars );
|
||||
}
|
||||
|
||||
$regex = '/(
|
||||
[\x00-\x7F] # single-byte sequences 0xxxxxxx
|
||||
| [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx
|
||||
| \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2
|
||||
| [\xE1-\xEC][\x80-\xBF]{2}
|
||||
| \xED[\x80-\x9F][\x80-\xBF]
|
||||
| [\xEE-\xEF][\x80-\xBF]{2}
|
||||
| \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3
|
||||
| [\xF1-\xF3][\x80-\xBF]{3}
|
||||
| \xF4[\x80-\x8F][\x80-\xBF]{2}
|
||||
)/x';
|
||||
|
||||
$chars = array( '' ); // Start with 1 element instead of 0 since the first thing we do is pop
|
||||
do {
|
||||
// We had some string left over from the last round, but we counted it in that last round.
|
||||
array_pop( $chars );
|
||||
|
||||
// Split by UTF-8 character, limit to 1000 characters (last array element will contain the rest of the string)
|
||||
$pieces = preg_split( $regex, $str, 1000, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
|
||||
|
||||
$chars = array_merge( $chars, $pieces );
|
||||
} while ( count( $pieces ) > 1 && $str = array_pop( $pieces ) ); // If there's anything left over, repeat the loop.
|
||||
|
||||
return join( '', array_slice( $chars, $start, $length ) );
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'mb_strlen' ) ) :
|
||||
function mb_strlen( $str, $encoding = null ) {
|
||||
return _mb_strlen( $str, $encoding );
|
||||
}
|
||||
endif;
|
||||
|
||||
/*
|
||||
* Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit.
|
||||
* For $encoding === UTF-8, the $str input is expected to be a valid UTF-8 byte sequence.
|
||||
* The behavior of this function for invalid inputs is undefined.
|
||||
*/
|
||||
function _mb_strlen( $str, $encoding = null ) {
|
||||
if ( null === $encoding ) {
|
||||
$encoding = get_option( 'blog_charset' );
|
||||
}
|
||||
|
||||
// The solution below works only for UTF-8,
|
||||
// so in case of a different charset just use built-in strlen()
|
||||
if ( ! in_array( $encoding, array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) {
|
||||
return strlen( $str );
|
||||
}
|
||||
|
||||
if ( _wp_can_use_pcre_u() ) {
|
||||
// Use the regex unicode support to separate the UTF-8 characters into an array
|
||||
preg_match_all( '/./us', $str, $match );
|
||||
return count( $match[0] );
|
||||
}
|
||||
|
||||
$regex = '/(?:
|
||||
[\x00-\x7F] # single-byte sequences 0xxxxxxx
|
||||
| [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx
|
||||
| \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2
|
||||
| [\xE1-\xEC][\x80-\xBF]{2}
|
||||
| \xED[\x80-\x9F][\x80-\xBF]
|
||||
| [\xEE-\xEF][\x80-\xBF]{2}
|
||||
| \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3
|
||||
| [\xF1-\xF3][\x80-\xBF]{3}
|
||||
| \xF4[\x80-\x8F][\x80-\xBF]{2}
|
||||
)/x';
|
||||
|
||||
$count = 1; // Start at 1 instead of 0 since the first thing we do is decrement
|
||||
do {
|
||||
// We had some string left over from the last round, but we counted it in that last round.
|
||||
$count--;
|
||||
|
||||
// Split by UTF-8 character, limit to 1000 characters (last array element will contain the rest of the string)
|
||||
$pieces = preg_split( $regex, $str, 1000 );
|
||||
|
||||
// Increment
|
||||
$count += count( $pieces );
|
||||
} while ( $str = array_pop( $pieces ) ); // If there's anything left over, repeat the loop.
|
||||
|
||||
// Fencepost: preg_split() always returns one extra item in the array
|
||||
return --$count;
|
||||
}
|
||||
|
||||
if ( !function_exists('hash_hmac') ):
|
||||
|
||||
@@ -405,6 +405,9 @@ function wpautop($pee, $br = true) {
|
||||
$pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee);
|
||||
$pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines
|
||||
|
||||
// Strip newlines from all elements.
|
||||
$pee = wp_replace_in_html_tags( $pee, array( "\n" => " " ) );
|
||||
|
||||
if ( strpos( $pee, '<option' ) !== false ) {
|
||||
// no P/BR around option
|
||||
$pee = preg_replace( '|\s*<option|', '<option', $pee );
|
||||
@@ -459,6 +462,74 @@ function wpautop($pee, $br = true) {
|
||||
return $pee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace characters or phrases within HTML elements only.
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @param string $haystack The text which has to be formatted.
|
||||
* @param array $replace_pairs In the form array('from' => 'to', ...).
|
||||
* @return string The formatted text.
|
||||
*/
|
||||
function wp_replace_in_html_tags( $haystack, $replace_pairs ) {
|
||||
// Find all elements.
|
||||
$comments =
|
||||
'!' // Start of comment, after the <.
|
||||
. '(?:' // Unroll the loop: Consume everything until --> is found.
|
||||
. '-(?!->)' // Dash not followed by end of comment.
|
||||
. '[^\-]*+' // Consume non-dashes.
|
||||
. ')*+' // Loop possessively.
|
||||
. '(?:-->)?'; // End of comment. If not found, match all input.
|
||||
|
||||
$regex =
|
||||
'/(' // Capture the entire match.
|
||||
. '<' // Find start of element.
|
||||
. '(?(?=!--)' // Is this a comment?
|
||||
. $comments // Find end of comment.
|
||||
. '|'
|
||||
. '[^>]*>?' // Find end of element. If not found, match all input.
|
||||
. ')'
|
||||
. ')/s';
|
||||
|
||||
$textarr = preg_split( $regex, $haystack, -1, PREG_SPLIT_DELIM_CAPTURE );
|
||||
$changed = false;
|
||||
|
||||
// Optimize when searching for one item.
|
||||
if ( 1 === count( $replace_pairs ) ) {
|
||||
// Extract $needle and $replace.
|
||||
foreach ( $replace_pairs as $needle => $replace );
|
||||
|
||||
// Loop through delimeters (elements) only.
|
||||
for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
|
||||
if ( false !== strpos( $textarr[$i], $needle ) ) {
|
||||
$textarr[$i] = str_replace( $needle, $replace, $textarr[$i] );
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Extract all $needles.
|
||||
$needles = array_keys( $replace_pairs );
|
||||
|
||||
// Loop through delimeters (elements) only.
|
||||
for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
|
||||
foreach ( $needles as $needle ) {
|
||||
if ( false !== strpos( $textarr[$i], $needle ) ) {
|
||||
$textarr[$i] = strtr( $textarr[$i], $replace_pairs );
|
||||
$changed = true;
|
||||
// After one strtr() break out of the foreach loop and look at next element.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $changed ) {
|
||||
$haystack = implode( $textarr );
|
||||
}
|
||||
|
||||
return $haystack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Newline preservation help function for wpautop
|
||||
*
|
||||
|
||||
@@ -102,6 +102,10 @@ tinymce.PluginManager.add( 'wordpress', function( editor ) {
|
||||
'<img src="' + tinymce.Env.transparentSrc + '" data-wp-more="nextpage" class="wp-more-tag mce-wp-nextpage" ' +
|
||||
'title="' + title + '" data-mce-resize="false" data-mce-placeholder="1" />' );
|
||||
}
|
||||
|
||||
if ( e.content.indexOf( '<?"' ) !== -1 ) {
|
||||
e.content = e.content.replace( /<\?"/g, '' );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -493,6 +493,82 @@ function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) {
|
||||
return wp_kses_split($string, $allowed_html, $allowed_protocols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters one attribute only and ensures its value is allowed.
|
||||
*
|
||||
* This function has the advantage of being more secure than esc_attr() and can
|
||||
* escape data in some situations where wp_kses() must strip the whole attribute.
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @param string $string The 'whole' attribute, including name and value.
|
||||
* @param string $element The element name to which the attribute belongs.
|
||||
* @return string Filtered attribute.
|
||||
*/
|
||||
function wp_kses_one_attr( $string, $element ) {
|
||||
$uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action');
|
||||
$allowed_html = wp_kses_allowed_html( 'post' );
|
||||
$allowed_protocols = wp_allowed_protocols();
|
||||
$string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
|
||||
$string = wp_kses_js_entities( $string );
|
||||
$string = wp_kses_normalize_entities( $string );
|
||||
|
||||
// Preserve leading and trailing whitespace.
|
||||
$matches = array();
|
||||
preg_match('/^\s*/', $string, $matches);
|
||||
$lead = $matches[0];
|
||||
preg_match('/\s*$/', $string, $matches);
|
||||
$trail = $matches[0];
|
||||
if ( empty( $trail ) ) {
|
||||
$string = substr( $string, strlen( $lead ) );
|
||||
} else {
|
||||
$string = substr( $string, strlen( $lead ), -strlen( $trail ) );
|
||||
}
|
||||
|
||||
// Parse attribute name and value from input.
|
||||
$split = preg_split( '/\s*=\s*/', $string, 2 );
|
||||
$name = $split[0];
|
||||
if ( count( $split ) == 2 ) {
|
||||
$value = $split[1];
|
||||
|
||||
// Remove quotes surrounding $value.
|
||||
// Also guarantee correct quoting in $string for this one attribute.
|
||||
if ( '' == $value ) {
|
||||
$quote = '';
|
||||
} else {
|
||||
$quote = $value[0];
|
||||
}
|
||||
if ( '"' == $quote || "'" == $quote ) {
|
||||
if ( substr( $value, -1 ) != $quote ) {
|
||||
return '';
|
||||
}
|
||||
$value = substr( $value, 1, -1 );
|
||||
} else {
|
||||
$quote = '"';
|
||||
}
|
||||
|
||||
// Sanitize quotes and angle braces.
|
||||
$value = htmlspecialchars( $value, ENT_QUOTES, null, false );
|
||||
|
||||
// Sanitize URI values.
|
||||
if ( in_array( strtolower( $name ), $uris ) ) {
|
||||
$value = wp_kses_bad_protocol( $value, $allowed_protocols );
|
||||
}
|
||||
|
||||
$string = "$name=$quote$value$quote";
|
||||
$vless = 'n';
|
||||
} else {
|
||||
$value = '';
|
||||
$vless = 'y';
|
||||
}
|
||||
|
||||
// Sanitize attribute by name.
|
||||
wp_kses_attr_check( $name, $value, $string, $vless, $element, $allowed_html );
|
||||
|
||||
// Restore whitespace.
|
||||
return $lead . $string . $trail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of allowed tags and attributes for a given context.
|
||||
*
|
||||
@@ -714,45 +790,11 @@ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
|
||||
# Go through $attrarr, and save the allowed attributes for this element
|
||||
# in $attr2
|
||||
$attr2 = '';
|
||||
|
||||
$allowed_attr = $allowed_html[strtolower($element)];
|
||||
foreach ($attrarr as $arreach) {
|
||||
if ( ! isset( $allowed_attr[strtolower($arreach['name'])] ) )
|
||||
continue; # the attribute is not allowed
|
||||
|
||||
$current = $allowed_attr[strtolower($arreach['name'])];
|
||||
if ( $current == '' )
|
||||
continue; # the attribute is not allowed
|
||||
|
||||
if ( strtolower( $arreach['name'] ) == 'style' ) {
|
||||
$orig_value = $arreach['value'];
|
||||
$value = safecss_filter_attr( $orig_value );
|
||||
|
||||
if ( empty( $value ) )
|
||||
continue;
|
||||
|
||||
$arreach['value'] = $value;
|
||||
$arreach['whole'] = str_replace( $orig_value, $value, $arreach['whole'] );
|
||||
}
|
||||
|
||||
if ( ! is_array($current) ) {
|
||||
foreach ( $attrarr as $arreach ) {
|
||||
if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) {
|
||||
$attr2 .= ' '.$arreach['whole'];
|
||||
# there are no checks
|
||||
|
||||
} else {
|
||||
# there are some checks
|
||||
$ok = true;
|
||||
foreach ($current as $currkey => $currval) {
|
||||
if ( ! wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval) ) {
|
||||
$ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $ok )
|
||||
$attr2 .= ' '.$arreach['whole']; # it passed them
|
||||
} # if !is_array($current)
|
||||
} # foreach
|
||||
}
|
||||
}
|
||||
|
||||
# Remove any "<" or ">" characters
|
||||
$attr2 = preg_replace('/[<>]/', '', $attr2);
|
||||
@@ -760,6 +802,53 @@ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
|
||||
return "<$element$attr2$xhtml_slash>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an attribute is allowed.
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @param string $name The attribute name. Returns empty string when not allowed.
|
||||
* @param string $value The attribute value. Returns a filtered value.
|
||||
* @param string $whole The name=value input. Returns filtered input.
|
||||
* @param string $vless 'y' when attribute like "enabled", otherwise 'n'.
|
||||
* @param string $element The name of the element to which this attribute belongs.
|
||||
* @param array $allowed_html The full list of allowed elements and attributes.
|
||||
* @return bool Is the attribute allowed?
|
||||
*/
|
||||
function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowed_html ) {
|
||||
$allowed_attr = $allowed_html[strtolower( $element )];
|
||||
|
||||
$name_low = strtolower( $name );
|
||||
if ( ! isset( $allowed_attr[$name_low] ) || '' == $allowed_attr[$name_low] ) {
|
||||
$name = $value = $whole = '';
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'style' == $name_low ) {
|
||||
$new_value = safecss_filter_attr( $value );
|
||||
|
||||
if ( empty( $new_value ) ) {
|
||||
$name = $value = $whole = '';
|
||||
return false;
|
||||
}
|
||||
|
||||
$whole = str_replace( $value, $new_value, $whole );
|
||||
$value = $new_value;
|
||||
}
|
||||
|
||||
if ( is_array( $allowed_attr[$name_low] ) ) {
|
||||
// there are some checks
|
||||
foreach ( $allowed_attr[$name_low] as $currkey => $currval ) {
|
||||
if ( ! wp_kses_check_attr_val( $value, $vless, $currkey, $currval ) ) {
|
||||
$name = $value = $whole = '';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an attribute list from string containing attributes.
|
||||
*
|
||||
@@ -889,6 +978,109 @@ function wp_kses_hair($attr, $allowed_protocols) {
|
||||
return $attrarr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all attributes of an HTML element.
|
||||
*
|
||||
* Does not modify input. May return "evil" output.
|
||||
*
|
||||
* Based on wp_kses_split2() and wp_kses_attr()
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @param string $element HTML element/tag
|
||||
* @return array|bool List of attributes found in $element. Returns false on failure.
|
||||
*/
|
||||
function wp_kses_attr_parse( $element ) {
|
||||
$valid = preg_match('%^(<\s*)(/\s*)?([a-zA-Z0-9]+\s*)([^>]*)(>?)$%', $element, $matches);
|
||||
if ( 1 !== $valid ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$begin = $matches[1];
|
||||
$slash = $matches[2];
|
||||
$elname = $matches[3];
|
||||
$attr = $matches[4];
|
||||
$end = $matches[5];
|
||||
|
||||
if ( '' !== $slash ) {
|
||||
// Closing elements do not get parsed.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is there a closing XHTML slash at the end of the attributes?
|
||||
if ( 1 === preg_match( '%\s*/\s*$%', $attr, $matches ) ) {
|
||||
$xhtml_slash = $matches[0];
|
||||
$attr = substr( $attr, 0, -strlen( $xhtml_slash ) );
|
||||
} else {
|
||||
$xhtml_slash = '';
|
||||
}
|
||||
|
||||
// Split it
|
||||
$attrarr = wp_kses_hair_parse( $attr );
|
||||
if ( false === $attrarr ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure all input is returned by adding front and back matter.
|
||||
array_unshift( $attrarr, $begin . $slash . $elname );
|
||||
array_push( $attrarr, $xhtml_slash . $end );
|
||||
|
||||
return $attrarr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an attribute list from string containing attributes.
|
||||
*
|
||||
* Does not modify input. May return "evil" output.
|
||||
* In case of unexpected input, returns false instead of stripping things.
|
||||
*
|
||||
* Based on wp_kses_hair() but does not return a multi-dimensional array.
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @param string $attr Attribute list from HTML element to closing HTML element tag
|
||||
* @return array|bool List of attributes found in $attr. Returns false on failure.
|
||||
*/
|
||||
function wp_kses_hair_parse( $attr ) {
|
||||
if ( '' === $attr ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$regex =
|
||||
'(?:'
|
||||
. '[-a-zA-Z:]+' // Attribute name.
|
||||
. '|'
|
||||
. '\[\[?[^\[\]]+\]\]?' // Shortcode in the name position implies unfiltered_html.
|
||||
. ')'
|
||||
. '(?:' // Attribute value.
|
||||
. '\s*=\s*' // All values begin with '='
|
||||
. '(?:'
|
||||
. '"[^"]*"' // Double-quoted
|
||||
. '|'
|
||||
. "'[^']*'" // Single-quoted
|
||||
. '|'
|
||||
. '[^\s"\']+' // Non-quoted
|
||||
. '(?:\s|$)' // Must have a space
|
||||
. ')'
|
||||
. '|'
|
||||
. '(?:\s|$)' // If attribute has no value, space is required.
|
||||
. ')'
|
||||
. '\s*'; // Trailing space is optional except as mentioned above.
|
||||
|
||||
// Although it is possible to reduce this procedure to a single regexp,
|
||||
// we must run that regexp twice to get exactly the expected result.
|
||||
|
||||
$validation = "%^($regex)+$%";
|
||||
$extraction = "%$regex%";
|
||||
|
||||
if ( 1 === preg_match( $validation, $attr ) ) {
|
||||
preg_match_all( $extraction, $attr, $attrarr );
|
||||
return $attrarr[0];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs different checks for attribute values.
|
||||
*
|
||||
|
||||
@@ -188,9 +188,10 @@ function has_shortcode( $content, $tag ) {
|
||||
* @uses get_shortcode_regex() Gets the search pattern for searching shortcodes.
|
||||
*
|
||||
* @param string $content Content to search for shortcodes
|
||||
* @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped.
|
||||
* @return string Content with shortcodes filtered out.
|
||||
*/
|
||||
function do_shortcode($content) {
|
||||
function do_shortcode( $content, $ignore_html = false ) {
|
||||
global $shortcode_tags;
|
||||
|
||||
if ( false === strpos( $content, '[' ) ) {
|
||||
@@ -200,8 +201,24 @@ function do_shortcode($content) {
|
||||
if (empty($shortcode_tags) || !is_array($shortcode_tags))
|
||||
return $content;
|
||||
|
||||
$tagnames = array_keys($shortcode_tags);
|
||||
$tagregexp = join( '|', array_map('preg_quote', $tagnames) );
|
||||
$pattern = "/\\[($tagregexp)/s";
|
||||
|
||||
if ( 1 !== preg_match( $pattern, $content ) ) {
|
||||
// Avoids parsing HTML when there are no shortcodes or embeds anyway.
|
||||
return $content;
|
||||
}
|
||||
|
||||
$content = do_shortcodes_in_html_tags( $content, $ignore_html );
|
||||
|
||||
$pattern = get_shortcode_regex();
|
||||
return preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );
|
||||
$content = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );
|
||||
|
||||
// Always restore square braces so we don't break things like <!--[if IE ]>
|
||||
$content = unescape_invalid_shortcodes( $content );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,6 +311,141 @@ function do_shortcode_tag( $m ) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search only inside HTML elements for shortcodes and process them.
|
||||
*
|
||||
* Any [ or ] characters remaining inside elements will be HTML encoded
|
||||
* to prevent interference with shortcodes that are outside the elements.
|
||||
* Assumes $content processed by KSES already. Users with unfiltered_html
|
||||
* capability may get unexpected output if angle braces are nested in tags.
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @param string $content Content to search for shortcodes
|
||||
* @param bool $ignore_html When true, all square braces inside elements will be encoded.
|
||||
* @return string Content with shortcodes filtered out.
|
||||
*/
|
||||
function do_shortcodes_in_html_tags( $content, $ignore_html ) {
|
||||
// Normalize entities in unfiltered HTML before adding placeholders.
|
||||
$trans = array( '[' => '[', ']' => ']' );
|
||||
$content = strtr( $content, $trans );
|
||||
$trans = array( '[' => '[', ']' => ']' );
|
||||
|
||||
$pattern = get_shortcode_regex();
|
||||
|
||||
$comment_regex =
|
||||
'!' // Start of comment, after the <.
|
||||
. '(?:' // Unroll the loop: Consume everything until --> is found.
|
||||
. '-(?!->)' // Dash not followed by end of comment.
|
||||
. '[^\-]*+' // Consume non-dashes.
|
||||
. ')*+' // Loop possessively.
|
||||
. '(?:-->)?'; // End of comment. If not found, match all input.
|
||||
|
||||
$regex =
|
||||
'/(' // Capture the entire match.
|
||||
. '<' // Find start of element.
|
||||
. '(?(?=!--)' // Is this a comment?
|
||||
. $comment_regex // Find end of comment.
|
||||
. '|'
|
||||
. '[^>]*>?' // Find end of element. If not found, match all input.
|
||||
. ')'
|
||||
. ')/s';
|
||||
|
||||
$textarr = preg_split( $regex, $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
|
||||
|
||||
foreach ( $textarr as &$element ) {
|
||||
if ( '<' !== $element[0] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$noopen = false === strpos( $element, '[' );
|
||||
$noclose = false === strpos( $element, ']' );
|
||||
if ( $noopen || $noclose ) {
|
||||
// This element does not contain shortcodes.
|
||||
if ( $noopen xor $noclose ) {
|
||||
// Need to encode stray [ or ] chars.
|
||||
$element = strtr( $element, $trans );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $ignore_html || '<!--' === substr( $element, 0, 4 ) ) {
|
||||
// Encode all [ and ] chars.
|
||||
$element = strtr( $element, $trans );
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes = wp_kses_attr_parse( $element );
|
||||
if ( false === $attributes ) {
|
||||
// Looks like we found some crazy unfiltered HTML. Skipping it for sanity.
|
||||
$element = strtr( $element, $trans );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get element name
|
||||
$front = array_shift( $attributes );
|
||||
$back = array_pop( $attributes );
|
||||
$matches = array();
|
||||
preg_match('%[a-zA-Z0-9]+%', $front, $matches);
|
||||
$elname = $matches[0];
|
||||
|
||||
// Look for shortcodes in each attribute separately.
|
||||
foreach ( $attributes as &$attr ) {
|
||||
$open = strpos( $attr, '[' );
|
||||
$close = strpos( $attr, ']' );
|
||||
if ( false === $open || false === $close ) {
|
||||
continue; // Go to next attribute. Square braces will be escaped at end of loop.
|
||||
}
|
||||
$double = strpos( $attr, '"' );
|
||||
$single = strpos( $attr, "'" );
|
||||
if ( ( false === $single || $open < $single ) && ( false === $double || $open < $double ) ) {
|
||||
// $attr like '[shortcode]' or 'name = [shortcode]' implies unfiltered_html.
|
||||
// In this specific situation we assume KSES did not run because the input
|
||||
// was written by an administrator, so we should avoid changing the output
|
||||
// and we do not need to run KSES here.
|
||||
$attr = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $attr );
|
||||
} else {
|
||||
// $attr like 'name = "[shortcode]"' or "name = '[shortcode]'"
|
||||
// We do not know if $content was unfiltered. Assume KSES ran before shortcodes.
|
||||
$count = 0;
|
||||
$new_attr = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $attr, -1, $count );
|
||||
if ( $count > 0 ) {
|
||||
// Sanitize the shortcode output using KSES.
|
||||
$new_attr = wp_kses_one_attr( $new_attr, $elname );
|
||||
if ( '' !== $new_attr ) {
|
||||
// The shortcode is safe to use now.
|
||||
$attr = $new_attr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$element = $front . implode( '', $attributes ) . $back;
|
||||
|
||||
// Now encode any remaining [ or ] chars.
|
||||
$element = strtr( $element, $trans );
|
||||
}
|
||||
|
||||
$content = implode( '', $textarr );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove placeholders added by do_shortcodes_in_html_tags().
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @param string $content Content to search for placeholders.
|
||||
* @return string Content with placeholders removed.
|
||||
*/
|
||||
function unescape_invalid_shortcodes( $content ) {
|
||||
// Clean up entire string, avoids re-parsing HTML.
|
||||
$trans = array( '[' => '[', ']' => ']' );
|
||||
$content = strtr( $content, $trans );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all attributes from the shortcodes tag.
|
||||
*
|
||||
@@ -393,9 +545,15 @@ function strip_shortcodes( $content ) {
|
||||
if (empty($shortcode_tags) || !is_array($shortcode_tags))
|
||||
return $content;
|
||||
|
||||
$pattern = get_shortcode_regex();
|
||||
$content = do_shortcodes_in_html_tags( $content, true );
|
||||
|
||||
return preg_replace_callback( "/$pattern/s", 'strip_shortcode_tag', $content );
|
||||
$pattern = get_shortcode_regex();
|
||||
$content = preg_replace_callback( "/$pattern/s", 'strip_shortcode_tag', $content );
|
||||
|
||||
// Always restore square braces so we don't break things like <!--[if IE ]>
|
||||
$content = unescape_invalid_shortcodes( $content );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
function strip_shortcode_tag( $m ) {
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
*
|
||||
* @global string $wp_version
|
||||
*/
|
||||
$wp_version = '4.0.3';
|
||||
$wp_version = '4.0.6';
|
||||
|
||||
/**
|
||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||
*
|
||||
* @global int $wp_db_version
|
||||
*/
|
||||
$wp_db_version = 29630;
|
||||
$wp_db_version = 29632;
|
||||
|
||||
/**
|
||||
* Holds the TinyMCE version
|
||||
*
|
||||
* @global string $tinymce_version
|
||||
*/
|
||||
$tinymce_version = '4104-20140822';
|
||||
$tinymce_version = '4104-20150505';
|
||||
|
||||
/**
|
||||
* Holds the required PHP version
|
||||
|
||||
@@ -1787,6 +1787,8 @@ class wpdb {
|
||||
* @return int|false The number of rows affected, or false on error.
|
||||
*/
|
||||
function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
|
||||
$this->insert_id = 0;
|
||||
|
||||
if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
|
||||
return false;
|
||||
}
|
||||
@@ -1807,7 +1809,6 @@ class wpdb {
|
||||
|
||||
$sql = "$type INTO `$table` ($fields) VALUES ($formats)";
|
||||
|
||||
$this->insert_id = 0;
|
||||
$this->check_current_query = false;
|
||||
return $this->query( $this->prepare( $sql, $values ) );
|
||||
}
|
||||
@@ -1928,11 +1929,20 @@ class wpdb {
|
||||
*/
|
||||
protected function process_fields( $table, $data, $format ) {
|
||||
$data = $this->process_field_formats( $data, $format );
|
||||
if ( false === $data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->process_field_charsets( $data, $table );
|
||||
if ( false === $data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->process_field_lengths( $data, $table );
|
||||
if ( false === $data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$converted_data = $this->strip_invalid_text( $data );
|
||||
|
||||
if ( $data !== $converted_data ) {
|
||||
@@ -1994,17 +2004,41 @@ class wpdb {
|
||||
// We can skip this field if we know it isn't a string.
|
||||
// This checks %d/%f versus ! %s because it's sprintf() could take more.
|
||||
$value['charset'] = false;
|
||||
} elseif ( $this->check_ascii( $value['value'] ) ) {
|
||||
// If it's ASCII, then we don't need the charset. We can skip this field.
|
||||
$value['charset'] = false;
|
||||
} else {
|
||||
$value['charset'] = $this->get_col_charset( $table, $field );
|
||||
if ( is_wp_error( $value['charset'] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// This isn't ASCII. Don't have strip_invalid_text() re-check.
|
||||
$value['ascii'] = false;
|
||||
$data[ $field ] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* For string fields, record the maximum string length that field can safely save.
|
||||
*
|
||||
* @since 4.2.1
|
||||
* @access protected
|
||||
*
|
||||
* @param array $data As it comes from the wpdb::process_field_charsets() method.
|
||||
* @param string $table Table name.
|
||||
* @return array|False The same array as $data with additional 'length' keys, or false if
|
||||
* any of the values were too long for their corresponding field.
|
||||
*/
|
||||
protected function process_field_lengths( $data, $table ) {
|
||||
foreach ( $data as $field => $value ) {
|
||||
if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
|
||||
// We can skip this field if we know it isn't a string.
|
||||
// This checks %d/%f versus ! %s because it's sprintf() could take more.
|
||||
$value['length'] = false;
|
||||
} else {
|
||||
$value['length'] = $this->get_col_length( $table, $field );
|
||||
if ( is_wp_error( $value['length'] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$data[ $field ] = $value;
|
||||
@@ -2211,7 +2245,10 @@ class wpdb {
|
||||
}
|
||||
|
||||
$charsets = $columns = array();
|
||||
$results = $this->get_results( "SHOW FULL COLUMNS FROM `$table`" );
|
||||
|
||||
$table_parts = explode( '.', $table );
|
||||
$table = '`' . implode( '`.`', $table_parts ) . '`';
|
||||
$results = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
|
||||
if ( ! $results ) {
|
||||
return new WP_Error( 'wpdb_get_table_charset_failure' );
|
||||
}
|
||||
@@ -2334,6 +2371,99 @@ class wpdb {
|
||||
return $charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum string length allowed in a given column.
|
||||
* The length may either be specified as a byte length or a character length.
|
||||
*
|
||||
* @since 4.2.1
|
||||
* @access public
|
||||
*
|
||||
* @param string $table Table name.
|
||||
* @param string $column Column name.
|
||||
* @return mixed array( 'length' => (int), 'type' => 'byte' | 'char' )
|
||||
* false if the column has no length (for example, numeric column)
|
||||
* WP_Error object if there was an error.
|
||||
*/
|
||||
public function get_col_length( $table, $column ) {
|
||||
$tablekey = strtolower( $table );
|
||||
$columnkey = strtolower( $column );
|
||||
|
||||
// Skip this entirely if this isn't a MySQL database.
|
||||
if ( false === $this->is_mysql ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( empty( $this->col_meta[ $tablekey ] ) ) {
|
||||
// This primes column information for us.
|
||||
$table_charset = $this->get_table_charset( $table );
|
||||
if ( is_wp_error( $table_charset ) ) {
|
||||
return $table_charset;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );
|
||||
|
||||
$type = strtolower( $typeinfo[0] );
|
||||
if ( ! empty( $typeinfo[1] ) ) {
|
||||
$length = trim( $typeinfo[1], ')' );
|
||||
} else {
|
||||
$length = false;
|
||||
}
|
||||
|
||||
switch( $type ) {
|
||||
case 'char':
|
||||
case 'varchar':
|
||||
return array(
|
||||
'type' => 'char',
|
||||
'length' => (int) $length,
|
||||
);
|
||||
break;
|
||||
case 'binary':
|
||||
case 'varbinary':
|
||||
return array(
|
||||
'type' => 'byte',
|
||||
'length' => (int) $length,
|
||||
);
|
||||
break;
|
||||
case 'tinyblob':
|
||||
case 'tinytext':
|
||||
return array(
|
||||
'type' => 'byte',
|
||||
'length' => 255, // 2^8 - 1
|
||||
);
|
||||
break;
|
||||
case 'blob':
|
||||
case 'text':
|
||||
return array(
|
||||
'type' => 'byte',
|
||||
'length' => 65535, // 2^16 - 1
|
||||
);
|
||||
break;
|
||||
case 'mediumblob':
|
||||
case 'mediumtext':
|
||||
return array(
|
||||
'type' => 'byte',
|
||||
'length' => 16777215, // 2^24 - 1
|
||||
);
|
||||
break;
|
||||
case 'longblob':
|
||||
case 'longtext':
|
||||
return array(
|
||||
'type' => 'byte',
|
||||
'length' => 4294967295, // 2^32 - 1
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is ASCII.
|
||||
*
|
||||
@@ -2374,7 +2504,7 @@ class wpdb {
|
||||
|
||||
// We don't need to check the collation for queries that don't read data.
|
||||
$query = ltrim( $query, "\r\n\t (" );
|
||||
if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN)\s/i', $query ) ) {
|
||||
if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2432,50 +2562,55 @@ class wpdb {
|
||||
*/
|
||||
// If any of the columns don't have one of these collations, it needs more sanity checking.
|
||||
protected function strip_invalid_text( $data ) {
|
||||
// Some multibyte character sets that we can check in PHP.
|
||||
$mb_charsets = array(
|
||||
'ascii' => 'ASCII',
|
||||
'big5' => 'BIG-5',
|
||||
'eucjpms' => 'eucJP-win',
|
||||
'gb2312' => 'EUC-CN',
|
||||
'ujis' => 'EUC-JP',
|
||||
'utf32' => 'UTF-32',
|
||||
);
|
||||
|
||||
$supported_charsets = array();
|
||||
if ( function_exists( 'mb_list_encodings' ) ) {
|
||||
$supported_charsets = mb_list_encodings();
|
||||
}
|
||||
|
||||
$db_check_string = false;
|
||||
|
||||
foreach ( $data as &$value ) {
|
||||
$charset = $value['charset'];
|
||||
|
||||
// Column isn't a string, or is latin1, which will will happily store anything.
|
||||
if ( false === $charset || 'latin1' === $charset ) {
|
||||
if ( is_array( $value['length'] ) ) {
|
||||
$length = $value['length']['length'];
|
||||
} else {
|
||||
$length = false;
|
||||
}
|
||||
|
||||
// There's no charset to work with.
|
||||
if ( false === $charset ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Column isn't a string.
|
||||
if ( ! is_string( $value['value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ASCII is always OK.
|
||||
if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) {
|
||||
continue;
|
||||
$truncate_by_byte_length = 'byte' === $value['length']['type'];
|
||||
|
||||
$needs_validation = true;
|
||||
if (
|
||||
// latin1 can store any byte sequence
|
||||
'latin1' === $charset
|
||||
||
|
||||
// ASCII is always OK.
|
||||
( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
|
||||
) {
|
||||
$truncate_by_byte_length = true;
|
||||
$needs_validation = false;
|
||||
}
|
||||
|
||||
// Convert the text locally.
|
||||
if ( $supported_charsets ) {
|
||||
if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
|
||||
$value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
|
||||
if ( $truncate_by_byte_length ) {
|
||||
mbstring_binary_safe_encoding();
|
||||
if ( false !== $length && strlen( $value['value'] ) > $length ) {
|
||||
$value['value'] = substr( $value['value'], 0, $length );
|
||||
}
|
||||
reset_mbstring_encoding();
|
||||
|
||||
if ( ! $needs_validation ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// utf8 can be handled by regex, which is a bunch faster than a DB lookup.
|
||||
if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
|
||||
if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
|
||||
$regex = '/
|
||||
(
|
||||
(?: [\x00-\x7F] # single-byte sequences 0xxxxxxx
|
||||
@@ -2485,7 +2620,7 @@ class wpdb {
|
||||
| \xED[\x80-\x9F][\x80-\xBF]
|
||||
| [\xEE-\xEF][\x80-\xBF]{2}';
|
||||
|
||||
if ( 'utf8mb4' === $charset) {
|
||||
if ( 'utf8mb4' === $charset ) {
|
||||
$regex .= '
|
||||
| \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3
|
||||
| [\xF1-\xF3][\x80-\xBF]{3}
|
||||
@@ -2493,11 +2628,16 @@ class wpdb {
|
||||
';
|
||||
}
|
||||
|
||||
$regex .= '){1,50} # ...one or more times
|
||||
$regex .= '){1,40} # ...one or more times
|
||||
)
|
||||
| . # anything else
|
||||
/x';
|
||||
$value['value'] = preg_replace( $regex, '$1', $value['value'] );
|
||||
|
||||
|
||||
if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
|
||||
$value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2514,8 +2654,14 @@ class wpdb {
|
||||
$queries[ $value['charset'] ] = array();
|
||||
}
|
||||
|
||||
// Split the CONVERT() calls by charset, so we can make sure the connection is right
|
||||
$queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
|
||||
// We're going to need to truncate by characters or bytes, depending on the length value we have.
|
||||
if ( 'byte' === $value['length']['type'] ) {
|
||||
// Split the CONVERT() calls by charset, so we can make sure the connection is right
|
||||
$queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING binary ), %d ) USING {$value['charset']} )", $value['value'], $value['length']['length'] );
|
||||
} else {
|
||||
$queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] );
|
||||
}
|
||||
|
||||
unset( $data[ $col ]['db'] );
|
||||
}
|
||||
}
|
||||
@@ -2534,16 +2680,19 @@ class wpdb {
|
||||
|
||||
$this->check_current_query = false;
|
||||
|
||||
$row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
|
||||
$sql = array();
|
||||
foreach ( $query as $column => $column_query ) {
|
||||
$sql[] = $column_query . " AS x_$column";
|
||||
}
|
||||
|
||||
$row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A );
|
||||
if ( ! $row ) {
|
||||
$this->set_charset( $this->dbh, $connection_charset );
|
||||
return new WP_Error( 'wpdb_strip_invalid_text_failure' );
|
||||
}
|
||||
|
||||
$cols = array_keys( $query );
|
||||
$col_count = count( $cols );
|
||||
for ( $ii = 0; $ii < $col_count; $ii++ ) {
|
||||
$data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
|
||||
foreach ( array_keys( $query ) as $column ) {
|
||||
$data[ $column ]['value'] = $row["x_$column"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2566,6 +2715,12 @@ class wpdb {
|
||||
* @return string|WP_Error The converted query, or a {@see WP_Error} object if the conversion fails.
|
||||
*/
|
||||
protected function strip_invalid_text_from_query( $query ) {
|
||||
// We don't need to check the collation for queries that don't read data.
|
||||
$trimmed_query = ltrim( $query, "\r\n\t (" );
|
||||
if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$table = $this->get_table_from_query( $query );
|
||||
if ( $table ) {
|
||||
$charset = $this->get_table_charset( $table );
|
||||
@@ -2585,6 +2740,7 @@ class wpdb {
|
||||
'value' => $query,
|
||||
'charset' => $charset,
|
||||
'ascii' => false,
|
||||
'length' => false,
|
||||
);
|
||||
|
||||
$data = $this->strip_invalid_text( array( $data ) );
|
||||
@@ -2607,7 +2763,7 @@ class wpdb {
|
||||
* @return string|WP_Error The converted string, or a `WP_Error` object if the conversion fails.
|
||||
*/
|
||||
public function strip_invalid_text_for_column( $table, $column, $value ) {
|
||||
if ( ! is_string( $value ) || $this->check_ascii( $value ) ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -2624,7 +2780,7 @@ class wpdb {
|
||||
$column => array(
|
||||
'value' => $value,
|
||||
'charset' => $charset,
|
||||
'ascii' => false,
|
||||
'length' => $this->get_col_length( $table, $column ),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -2665,16 +2821,16 @@ class wpdb {
|
||||
. '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
|
||||
. '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
|
||||
. '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?'
|
||||
. ')\s+`?([\w-]+)`?/is', $query, $maybe ) ) {
|
||||
return $maybe[1];
|
||||
. ')\s+((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)/is', $query, $maybe ) ) {
|
||||
return str_replace( '`', '', $maybe[1] );
|
||||
}
|
||||
|
||||
// SHOW TABLE STATUS and SHOW TABLES
|
||||
if ( preg_match( '/^\s*(?:'
|
||||
. 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
|
||||
. '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
|
||||
. ')\W([\w-]+)\W/is', $query, $maybe ) ) {
|
||||
return $maybe[1];
|
||||
. ')\W((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)\W/is', $query, $maybe ) ) {
|
||||
return str_replace( '`', '', $maybe[1] );
|
||||
}
|
||||
|
||||
// Big pattern for the rest of the table-related queries.
|
||||
@@ -2692,8 +2848,8 @@ class wpdb {
|
||||
. '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
|
||||
. '|(?:GRANT|REVOKE).*ON\s+TABLE'
|
||||
. '|SHOW\s+(?:.*FROM|.*TABLE)'
|
||||
. ')\s+\(*\s*`?([\w-]+)`?\s*\)*/is', $query, $maybe ) ) {
|
||||
return $maybe[1];
|
||||
. ')\s+\(*\s*((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', $query, $maybe ) ) {
|
||||
return str_replace( '`', '', $maybe[1] );
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user