Changeset 46907
- Timestamp:
- 12/12/2019 06:36:20 PM (5 years ago)
- Location:
- branches/5.1
- Files:
-
- 2 added
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/5.1
- Property svn:mergeinfo changed
/trunk merged: 46893-46896
- Property svn:mergeinfo changed
-
branches/5.1/src/wp-includes/blocks.php
r44576 r46907 75 75 * @see parse_blocks() 76 76 * 77 * @param string $block_ type Full Block type to look for.77 * @param string $block_name Full Block type to look for. 78 78 * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post. 79 79 * @return bool Whether the post content contains the specified block. 80 80 */ 81 function has_block( $block_ type, $post = null ) {81 function has_block( $block_name, $post = null ) { 82 82 if ( ! has_blocks( $post ) ) { 83 83 return false; … … 91 91 } 92 92 93 return false !== strpos( $post, '<!-- wp:' . $block_type . ' ' ); 93 /* 94 * Normalize block name to include namespace, if provided as non-namespaced. 95 * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by 96 * their serialized names. 97 */ 98 if ( false === strpos( $block_name, '/' ) ) { 99 $block_name = 'core/' . $block_name; 100 } 101 102 // Test for existence of block by its fully qualified name. 103 $has_block = false !== strpos( $post, '<!-- wp:' . $block_name . ' ' ); 104 105 if ( ! $has_block ) { 106 /* 107 * If the given block name would serialize to a different name, test for 108 * existence by the serialized form. 109 */ 110 $serialized_block_name = strip_core_block_namespace( $block_name ); 111 if ( $serialized_block_name !== $block_name ) { 112 $has_block = false !== strpos( $post, '<!-- wp:' . $serialized_block_name . ' ' ); 113 } 114 } 115 116 return $has_block; 94 117 } 95 118 … … 112 135 113 136 return $dynamic_block_names; 137 } 138 139 /** 140 * Given an array of attributes, returns a string in the serialized attributes 141 * format prepared for post content. 142 * 143 * The serialized result is a JSON-encoded string, with unicode escape sequence 144 * substitution for characters which might otherwise interfere with embedding 145 * the result in an HTML comment. 146 * 147 * @since 5.3.1 148 * 149 * @param array $attributes Attributes object. 150 * @return string Serialized attributes. 151 */ 152 function serialize_block_attributes( $block_attributes ) { 153 $encoded_attributes = json_encode( $block_attributes ); 154 $encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes ); 155 $encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes ); 156 $encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes ); 157 $encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes ); 158 // Regex: /\\"/ 159 $encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes ); 160 161 return $encoded_attributes; 162 } 163 164 /** 165 * Returns the block name to use for serialization. This will remove the default 166 * "core/" namespace from a block name. 167 * 168 * @since 5.3.1 169 * 170 * @param string $block_name Original block name. 171 * @return string Block name to use for serialization. 172 */ 173 function strip_core_block_namespace( $block_name = null ) { 174 if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) { 175 return substr( $block_name, 5 ); 176 } 177 178 return $block_name; 179 } 180 181 /** 182 * Returns the content of a block, including comment delimiters. 183 * 184 * @since 5.3.1 185 * 186 * @param string $block_name Block name. 187 * @param array $attributes Block attributes. 188 * @param string $content Block save content. 189 * @return string Comment-delimited block content. 190 */ 191 function get_comment_delimited_block_content( $block_name = null, $block_attributes, $block_content ) { 192 if ( is_null( $block_name ) ) { 193 return $block_content; 194 } 195 196 $serialized_block_name = strip_core_block_namespace( $block_name ); 197 $serialized_attributes = empty( $block_attributes ) ? '' : serialize_block_attributes( $block_attributes ) . ' '; 198 199 if ( empty( $block_content ) ) { 200 return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes ); 201 } 202 203 return sprintf( 204 '<!-- wp:%s %s-->%s<!-- /wp:%s -->', 205 $serialized_block_name, 206 $serialized_attributes, 207 $block_content, 208 $serialized_block_name 209 ); 210 } 211 212 /** 213 * Returns the content of a block, including comment delimiters, serializing all 214 * attributes from the given parsed block. 215 * 216 * This should be used when preparing a block to be saved to post content. 217 * Prefer `render_block` when preparing a block for display. Unlike 218 * `render_block`, this does not evaluate a block's `render_callback`, and will 219 * instead preserve the markup as parsed. 220 * 221 * @since 5.3.1 222 * 223 * @param WP_Block_Parser_Block $block A single parsed block object. 224 * @return string String of rendered HTML. 225 */ 226 function serialize_block( $block ) { 227 $block_content = ''; 228 229 $index = 0; 230 foreach ( $block['innerContent'] as $chunk ) { 231 $block_content .= is_string( $chunk ) ? $chunk : serialize_block( $block['innerBlocks'][ $index++ ] ); 232 } 233 234 if ( ! is_array( $block['attrs'] ) ) { 235 $block['attrs'] = array(); 236 } 237 238 return get_comment_delimited_block_content( 239 $block['blockName'], 240 $block['attrs'], 241 $block_content 242 ); 243 } 244 245 /** 246 * Returns a joined string of the aggregate serialization of the given parsed 247 * blocks. 248 * 249 * @since 5.3.1 250 * 251 * @param WP_Block_Parser_Block[] $blocks Parsed block objects. 252 * @return string String of rendered HTML. 253 */ 254 function serialize_blocks( $blocks ) { 255 return implode( '', array_map( 'serialize_block', $blocks ) ); 256 } 257 258 /** 259 * Filters and sanitizes block content to remove non-allowable HTML from 260 * parsed block attribute values. 261 * 262 * @since 5.3.1 263 * 264 * @param string $text Text that may contain block content. 265 * @param array[]|string $allowed_html An array of allowed HTML elements 266 * and attributes, or a context name 267 * such as 'post'. 268 * @param string[] $allowed_protocols Array of allowed URL protocols. 269 * @return string The filtered and sanitized content result. 270 */ 271 function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) { 272 $result = ''; 273 274 $blocks = parse_blocks( $text ); 275 foreach ( $blocks as $block ) { 276 $block = filter_block_kses( $block, $allowed_html, $allowed_protocols ); 277 $result .= serialize_block( $block ); 278 } 279 280 return $result; 281 } 282 283 /** 284 * Filters and sanitizes a parsed block to remove non-allowable HTML from block 285 * attribute values. 286 * 287 * @since 5.3.1 288 * 289 * @param WP_Block_Parser_Block $block The parsed block object. 290 * @param array[]|string $allowed_html An array of allowed HTML 291 * elements and attributes, or a 292 * context name such as 'post'. 293 * @param string[] $allowed_protocols Allowed URL protocols. 294 * @return array The filtered and sanitized block object result. 295 */ 296 function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) { 297 $block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols ); 298 299 if ( is_array( $block['innerBlocks'] ) ) { 300 foreach ( $block['innerBlocks'] as $i => $inner_block ) { 301 $block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols ); 302 } 303 } 304 305 return $block; 306 } 307 308 /** 309 * Filters and sanitizes a parsed block attribute value to remove non-allowable 310 * HTML. 311 * 312 * @since 5.3.1 313 * 314 * @param mixed $value The attribute value to filter. 315 * @param array[]|string $allowed_html An array of allowed HTML elements 316 * and attributes, or a context name 317 * such as 'post'. 318 * @param string[] $allowed_protocols Array of allowed URL protocols. 319 * @return array The filtered and sanitized result. 320 */ 321 function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) { 322 if ( is_array( $value ) ) { 323 foreach ( $value as $key => $inner_value ) { 324 $filtered_key = filter_block_kses_value( $key, $allowed_html, $allowed_protocols ); 325 $filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols ); 326 327 if ( $filtered_key !== $key ) { 328 unset( $value[ $key ] ); 329 } 330 331 $value[ $filtered_key ] = $filtered_value; 332 } 333 } elseif ( is_string( $value ) ) { 334 return wp_kses( $value, $allowed_html, $allowed_protocols ); 335 } 336 337 return $value; 114 338 } 115 339 -
branches/5.1/src/wp-includes/default-filters.php
r44714 r46907 246 246 add_filter( 'teeny_mce_before_init', '_mce_set_direction' ); 247 247 add_filter( 'pre_kses', 'wp_pre_kses_less_than' ); 248 add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 ); 248 249 add_filter( 'sanitize_title', 'sanitize_title_with_dashes', 10, 3 ); 249 250 add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 ); -
branches/5.1/src/wp-includes/formatting.php
r45992 r46907 3042 3042 function wp_targeted_link_rel( $text ) { 3043 3043 // Don't run (more expensive) regex if no links with targets. 3044 if ( stripos( $text, 'target' ) !== false && stripos( $text, '<a ' ) !== false ) { 3045 $text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text ); 3044 if ( stripos( $text, 'target' ) === false || stripos( $text, '<a ' ) === false || is_serialized( $text ) ) { 3045 return $text; 3046 } 3047 3048 $script_and_style_regex = '/<(script|style).*?<\/\\1>/si'; 3049 3050 preg_match_all( $script_and_style_regex, $text, $matches ); 3051 $extra_parts = $matches[0]; 3052 $html_parts = preg_split( $script_and_style_regex, $text ); 3053 3054 foreach ( $html_parts as &$part ) { 3055 $part = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $part ); 3056 } 3057 3058 $text = ''; 3059 for ( $i = 0; $i < count( $html_parts ); $i++ ) { 3060 $text .= $html_parts[ $i ]; 3061 if ( isset( $extra_parts[ $i ] ) ) { 3062 $text .= $extra_parts[ $i ]; 3063 } 3046 3064 } 3047 3065 … … 3059 3077 */ 3060 3078 function wp_targeted_link_rel_callback( $matches ) { 3061 $link_html = $matches[1]; 3062 $rel_match = array(); 3079 $link_html = $matches[1]; 3080 $original_link_html = $link_html; 3081 3082 // Consider the html escaped if there are no unescaped quotes 3083 $is_escaped = ! preg_match( '/(^|[^\\\\])[\'"]/', $link_html ); 3084 if ( $is_escaped ) { 3085 // Replace only the quotes so that they are parsable by wp_kses_hair, leave the rest as is 3086 $link_html = preg_replace( '/\\\\([\'"])/', '$1', $link_html ); 3087 } 3088 3089 $atts = wp_kses_hair( $link_html, wp_allowed_protocols() ); 3063 3090 3064 3091 /** … … 3072 3099 $rel = apply_filters( 'wp_targeted_link_rel', 'noopener noreferrer', $link_html ); 3073 3100 3074 // Avoid additional regex if the filter removes rel values. 3075 if ( ! $rel ) { 3076 return "<a $link_html>"; 3077 } 3078 3079 // Value with delimiters, spaces around are optional. 3080 $attr_regex = '|rel\s*=\s*?(\\\\{0,1}["\'])(.*?)\\1|i'; 3081 preg_match( $attr_regex, $link_html, $rel_match ); 3082 3083 if ( empty( $rel_match[0] ) ) { 3084 // No delimiters, try with a single value and spaces, because `rel = va"lue` is totally fine... 3085 $attr_regex = '|rel\s*=(\s*)([^\s]*)|i'; 3086 preg_match( $attr_regex, $link_html, $rel_match ); 3087 } 3088 3089 if ( ! empty( $rel_match[0] ) ) { 3090 $parts = preg_split( '|\s+|', strtolower( $rel_match[2] ) ); 3091 $parts = array_map( 'esc_attr', $parts ); 3092 $needed = explode( ' ', $rel ); 3093 $parts = array_unique( array_merge( $parts, $needed ) ); 3094 $delimiter = trim( $rel_match[1] ) ? $rel_match[1] : '"'; 3095 $rel = 'rel=' . $delimiter . trim( implode( ' ', $parts ) ) . $delimiter; 3096 $link_html = str_replace( $rel_match[0], $rel, $link_html ); 3097 } else { 3098 $link_html .= " rel=\"$rel\""; 3101 // Return early if no rel values to be added or if no actual target attribute 3102 if ( ! $rel || ! isset( $atts['target'] ) ) { 3103 return "<a $original_link_html>"; 3104 } 3105 3106 if ( isset( $atts['rel'] ) ) { 3107 $all_parts = preg_split( '/\s/', "{$atts['rel']['value']} $rel", -1, PREG_SPLIT_NO_EMPTY ); 3108 $rel = implode( ' ', array_unique( $all_parts ) ); 3109 } 3110 3111 $atts['rel']['whole'] = 'rel="' . esc_attr( $rel ) . '"'; 3112 $link_html = join( ' ', array_column( $atts, 'whole' ) ); 3113 3114 if ( $is_escaped ) { 3115 $link_html = preg_replace( '/[\'"]/', '\\\\$0', $link_html ); 3099 3116 } 3100 3117 … … 4798 4815 4799 4816 /** 4817 * Remove non-allowable HTML from parsed block attribute values when filtering 4818 * in the post context. 4819 * 4820 * @since 5.3.1 4821 * 4822 * @param string $string Content to be run through KSES. 4823 * @param array[]|string $allowed_html An array of allowed HTML elements 4824 * and attributes, or a context name 4825 * such as 'post'. 4826 * @param string[] $allowed_protocols Array of allowed URL protocols. 4827 * @return string Filtered text to run through KSES. 4828 */ 4829 function wp_pre_kses_block_attributes( $string, $allowed_html, $allowed_protocols ) { 4830 /* 4831 * `filter_block_content` is expected to call `wp_kses`. Temporarily remove 4832 * the filter to avoid recursion. 4833 */ 4834 remove_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10 ); 4835 $string = filter_block_content( $string, $allowed_html, $allowed_protocols ); 4836 add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 ); 4837 4838 return $string; 4839 } 4840 4841 /** 4800 4842 * WordPress implementation of PHP sprintf() with filters. 4801 4843 * -
branches/5.1/src/wp-includes/kses.php
r46002 r46907 1659 1659 function wp_kses_bad_protocol_once( $string, $allowed_protocols, $count = 1 ) { 1660 1660 $string = preg_replace( '/(�*58(?![;0-9])|�*3a(?![;a-f0-9]))/i', '$1;', $string ); 1661 $string2 = preg_split( '/:|�*58;|�*3a; /i', $string, 2 );1661 $string2 = preg_split( '/:|�*58;|�*3a;|:/i', $string, 2 ); 1662 1662 if ( isset( $string2[1] ) && ! preg_match( '%/\?%', $string2[0] ) ) { 1663 1663 $string = trim( $string2[1] ); -
branches/5.1/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
r44225 r46907 500 500 } 501 501 502 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {502 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) { 503 503 return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); 504 504 } … … 655 655 } 656 656 657 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {657 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) { 658 658 return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); 659 659 } … … 957 957 */ 958 958 protected function prepare_item_for_database( $request ) { 959 $prepared_post = new stdClass ;959 $prepared_post = new stdClass(); 960 960 961 961 // Post ID. -
branches/5.1/tests/phpunit/tests/blocks/block-type.php
r44269 r46907 303 303 // even if it detects a proper $post global it should still be false for a missing block. 304 304 $this->assertFalse( has_block( 'core/fake' ) ); 305 } 306 307 public function test_post_has_block_serialized_name() { 308 $content = '<!-- wp:serialized /--><!-- wp:core/normalized /--><!-- wp:plugin/third-party /-->'; 309 310 $this->assertTrue( has_block( 'core/serialized', $content ) ); 311 312 /* 313 * Technically, `has_block` should receive a "full" (normalized, parsed) 314 * block name. But this test conforms to expected pre-5.3.1 behavior. 315 */ 316 $this->assertTrue( has_block( 'serialized', $content ) ); 317 $this->assertTrue( has_block( 'core/normalized', $content ) ); 318 $this->assertTrue( has_block( 'normalized', $content ) ); 319 $this->assertFalse( has_block( 'plugin/normalized', $content ) ); 320 $this->assertFalse( has_block( 'plugin/serialized', $content ) ); 321 $this->assertFalse( has_block( 'third-party', $content ) ); 322 $this->assertFalse( has_block( 'core/third-party', $content ) ); 305 323 } 306 324 -
branches/5.1/tests/phpunit/tests/formatting/WPTargetedLinkRel.php
r44714 r46907 39 39 public function test_rel_with_single_quote_delimiter() { 40 40 $content = '<p>Links: <a href="/" rel=\'existing values\' target="_blank">Existing rel</a></p>'; 41 $expected = '<p>Links: <a href="/" rel= \'existing values noopener noreferrer\'target="_blank">Existing rel</a></p>';41 $expected = '<p>Links: <a href="/" rel="existing values noopener noreferrer" target="_blank">Existing rel</a></p>'; 42 42 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 43 43 } … … 52 52 $content = '<p>Links: <a href="/" rel = existing target="_blank">Existing rel</a></p>'; 53 53 $expected = '<p>Links: <a href="/" rel="existing noopener noreferrer" target="_blank">Existing rel</a></p>'; 54 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );55 }56 57 public function test_rel_value_spaced_and_no_delimiter_and_values_to_escape() {58 $content = '<p>Links: <a href="/" rel = existing"value target="_blank">Existing rel</a></p>';59 $expected = '<p>Links: <a href="/" rel="existing"value noopener noreferrer" target="_blank">Existing rel</a></p>';60 54 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 61 55 } … … 102 96 $this->assertEquals( $expected, $post->post_content ); 103 97 } 98 99 /** 100 * Ensure JSON format is preserved when relation attribute (rel) is missing. 101 * 102 * @ticket 46316 103 */ 104 public function test_wp_targeted_link_rel_should_preserve_json() { 105 $content = '<p>Links: <a href=\"\/\" target=\"_blank\">No rel<\/a><\/p>'; 106 $expected = '<p>Links: <a href=\"\/\" target=\"_blank\" rel=\"noopener noreferrer\">No rel<\/a><\/p>'; 107 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 108 } 109 110 /** 111 * Ensure the content of style and script tags are not processed 112 * 113 * @ticket 47244 114 */ 115 public function test_wp_targeted_link_rel_skips_style_and_scripts() { 116 $content = '<style><a href="/" target=a></style><p>Links: <script>console.log("<a href=\'/\' target=a>hi</a>");</script><script>alert(1);</script>here <a href="/" target=_blank>aq</a></p><script>console.log("<a href=\'last\' target=\'_blank\'")</script>'; 117 $expected = '<style><a href="/" target=a></style><p>Links: <script>console.log("<a href=\'/\' target=a>hi</a>");</script><script>alert(1);</script>here <a href="/" target="_blank" rel="noopener noreferrer">aq</a></p><script>console.log("<a href=\'last\' target=\'_blank\'")</script>'; 118 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 119 } 120 121 /** 122 * Ensure entirely serialized content is ignored. 123 * 124 * @ticket 46402 125 */ 126 public function test_ignore_entirely_serialized_content() { 127 $content = 'a:1:{s:4:"html";s:52:"<p>Links: <a href="/" target="_blank">No Rel</a></p>";}'; 128 $expected = 'a:1:{s:4:"html";s:52:"<p>Links: <a href="/" target="_blank">No Rel</a></p>";}'; 129 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 130 } 131 132 public function test_wp_targeted_link_rel_tab_separated_values_are_split() { 133 $content = "<p>Links: <a href=\"/\" target=\"_blank\" rel=\"ugc\t\tnoopener\t\">No rel</a></p>"; 134 $expected = '<p>Links: <a href="/" target="_blank" rel="ugc noopener noreferrer">No rel</a></p>'; 135 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 136 } 137 104 138 } -
branches/5.1/tests/phpunit/tests/kses.php
r46002 r46907 179 179 } 180 180 181 $bad_not_normalized = array( 182 'dummy:alert(1)', 183 'javascript:alert(1)', 184 'javascript&CoLon;alert(1)', 185 'javascript&COLON;alert(1);', 186 'javascript:alert(1);', 187 'javascript:alert(1);', 188 'javascript:alert(1);', 189 'jav ascript&COLON;alert(1);', 190 'javascript:javascript:alert(1);', 191 'javascript:javascript:alert(1);', 192 'javascript:javascript:alert(1);', 193 'javascript:javascript:alert(1);', 194 'javascript:alert(1)', 195 ); 196 foreach ( $bad_not_normalized as $k => $x ) { 197 $result = wp_kses_bad_protocol( $x, wp_allowed_protocols() ); 198 if ( ! empty( $result ) && 'alert(1);' !== $result && 'alert(1)' !== $result ) { 199 $this->fail( "wp_kses_bad_protocol failed on $k, $x. Result: $result" ); 200 } 201 } 202 181 203 $safe = array( 182 204 'dummy:alert(1)',
Note: See TracChangeset
for help on using the changeset viewer.