PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.1.3
Jetpack – WP Security, Backup, Speed, & Growth v6.1.3
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / shortcodes / class.filter-embedded-html-objects.php

class.filter-embedded-html-objects.php in Jetpack – WP Security, Backup, Speed, & Growth 6.1.3, at modules/shortcodes/class.filter-embedded-html-objects.php

299 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The companion file to shortcodes.php
4 *
5 * This file contains the code that converts HTML embeds into shortcodes
6 * for when the user copy/pastes in HTML.
7 */
8
9 add_filter( 'pre_kses', array( 'Filter_Embedded_HTML_Objects', 'filter' ), 11 );
10 add_filter( 'pre_kses', array( 'Filter_Embedded_HTML_Objects', 'maybe_create_links' ), 100 ); // See WPCom_Embed_Stats::init()
11
12 /**
13 * Helper class for identifying and parsing known HTML blocks
14 *
15 * @since 4.5.0
16 *
17 * @author mdawaffe
18 *
19 * Not completely done, but seems to work okay
20 * Stolen from Mike's Seaside presentation:
21 * @link http://mdawaffepresents.wordpress.com/?p=36
22 */
23
24 class Filter_Embedded_HTML_Objects {
25 static public $strpos_filters = array();
26 static public $regexp_filters = array();
27 static public $current_element = false;
28 static public $html_strpos_filters = array();
29 static public $html_regexp_filters = array();
30 static public $failed_embeds = array();
31
32 /**
33 * Store tokens found in Syntax Highlighter.
34 *
35 * @since 4.5.0
36 *
37 * @var array
38 */
39 static private $sh_unfiltered_content_tokens;
40
41 /**
42 * Capture tokens found in Syntax Highlighter and collect them in self::$sh_unfiltered_content_tokens.
43 *
44 * @since 4.5.0
45 *
46 * @param array $match
47 *
48 * @return string
49 */
50 static public function sh_regexp_callback( $match ) {
51 $token = '[prekses-filter-token-' . mt_rand() . '-' . md5( $match[0] ) . '-' . mt_rand() . ']';
52 self::$sh_unfiltered_content_tokens[$token] = $match[0];
53 return $token;
54 }
55
56 static public function filter( $html ) {
57 if ( ! $html || ! is_string( $html ) ) {
58 return $html;
59 }
60
61 $regexps = array(
62 'object' => '%<object[^>]*+>(?>[^<]*+(?><(?!/object>)[^<]*+)*)</object>%i',
63 'embed' => '%<embed[^>]*+>(?:\s*</embed>)?%i',
64 'iframe' => '%<iframe[^>]*+>(?>[^<]*+(?><(?!/iframe>)[^<]*+)*)</iframe>%i',
65 'div' => '%<div[^>]*+>(?>[^<]*+(?><(?!/div>)[^<]*+)*+)(?:</div>)+%i',
66 'script' => '%<script[^>]*+>(?>[^<]*+(?><(?!/script>)[^<]*+)*)</script>%i',
67 );
68
69 $unfiltered_content_tokens = array();
70 self::$sh_unfiltered_content_tokens = array();
71
72 // Check here to make sure that SyntaxHighlighter is still used. (Just a little future proofing)
73 if ( class_exists( 'SyntaxHighlighter' ) ) {
74 // Replace any "code" shortcode blocks with a token that we'll later replace with its original text.
75 // This will keep the contents of the shortcode from being filtered
76
77 global $SyntaxHighlighter;
78
79 // Check to see if the $SyntaxHighlighter object has been created and is ready for use
80 if ( isset( $SyntaxHighlighter ) && is_array( $SyntaxHighlighter->shortcodes ) ) {
81 $shortcode_regex = implode( '|', array_map( 'preg_quote', $SyntaxHighlighter->shortcodes ) );
82 $html = preg_replace_callback(
83 '/\[(' . $shortcode_regex . ')(\s[^\]]*)?\][\s\S]*?\[\/\1\]/m', array( __CLASS__, 'sh_regexp_callback' ), $html
84 );
85 $unfiltered_content_tokens = self::$sh_unfiltered_content_tokens;
86 }
87 }
88
89 foreach ( $regexps as $element => $regexp ) {
90 self::$current_element = $element;
91
92 if ( false !== stripos( $html, "<$element" ) ) {
93 if ( $new_html = preg_replace_callback( $regexp, array( __CLASS__, 'dispatch' ), $html ) ) {
94 $html = $new_html;
95 }
96 }
97
98 if ( false !== stripos( $html, "&lt;$element" ) ) {
99 $regexp_entities = self::regexp_entities( $regexp );
100 if ( $new_html = preg_replace_callback( $regexp_entities, array( __CLASS__, 'dispatch_entities' ), $html ) ) {
101 $html = $new_html;
102 }
103 }
104 }
105
106 if ( count( $unfiltered_content_tokens ) > 0 ) {
107 // Replace any tokens generated earlier with their original unfiltered text
108 $html = str_replace( array_keys( $unfiltered_content_tokens ), $unfiltered_content_tokens, $html );
109 }
110
111 return $html;
112 }
113
114 static public function regexp_entities( $regexp ) {
115 return preg_replace(
116 '/\[\^&([^\]]+)\]\*\+/',
117 '(?>[^&]*+(?>&(?!\1)[^&])*+)*+',
118 str_replace( '?&gt;', '?' . '>', htmlspecialchars( $regexp, ENT_NOQUOTES ) )
119 );
120 }
121
122 static public function register( $match, $callback, $is_regexp = false, $is_html_filter = false ) {
123 if ( $is_html_filter ) {
124 if ( $is_regexp ) {
125 self::$html_regexp_filters[$match] = $callback;
126 } else {
127 self::$html_strpos_filters[$match] = $callback;
128 }
129 } else {
130 if ( $is_regexp ) {
131 self::$regexp_filters[$match] = $callback;
132 } else {
133 self::$strpos_filters[$match] = $callback;
134 }
135 }
136 }
137
138 static public function unregister( $match ) {
139 // Allow themes/plugins to remove registered embeds
140 unset( self::$regexp_filters[$match] );
141 unset( self::$strpos_filters[$match] );
142 unset( self::$html_regexp_filters[$match] );
143 unset( self::$html_strpos_filters[$match] );
144 }
145
146 static function dispatch_entities( $matches ) {
147 $orig_html = $matches[0];
148 $decoded_matches = array( html_entity_decode( $matches[0] ) );
149
150 return self::dispatch( $decoded_matches, $orig_html );
151 }
152
153 static function dispatch( $matches, $orig_html = null ) {
154 if ( null === $orig_html ) {
155 $orig_html = $matches[0];
156 }
157
158 $html = preg_replace( '%&#0*58;//%', '://', $matches[0] );
159 $attrs = self::get_attrs( $html );
160 if ( isset( $attrs['src'] ) ) {
161 $src = $attrs['src'];
162 } else if ( isset( $attrs['movie'] ) ) {
163 $src = $attrs['movie'];
164 } else {
165 // no src found, search html
166 foreach ( self::$html_strpos_filters as $match => $callback ) {
167 if ( false !== strpos( $html, $match ) ) {
168 return call_user_func( $callback, $attrs );
169 }
170 }
171
172 foreach ( self::$html_regexp_filters as $match => $callback ) {
173 if ( preg_match( $match, $html ) ) {
174 return call_user_func( $callback, $attrs );
175 }
176 }
177
178 return $orig_html;
179 }
180
181 $src = trim( $src );
182
183 // check source filter
184 foreach ( self::$strpos_filters as $match => $callback ) {
185 if ( false !== strpos( $src, $match ) ) {
186 return call_user_func( $callback, $attrs );
187 }
188 }
189
190 foreach ( self::$regexp_filters as $match => $callback ) {
191 if ( preg_match( $match, $src ) ) {
192 return call_user_func( $callback, $attrs );
193 }
194 }
195
196 // check html filters
197 foreach ( self::$html_strpos_filters as $match => $callback ) {
198 if ( false !== strpos( $html, $match ) ) {
199 return call_user_func( $callback, $attrs );
200 }
201 }
202
203 foreach ( self::$html_regexp_filters as $match => $callback ) {
204 if ( preg_match( $match, $html ) ) {
205 return call_user_func( $callback, $attrs );
206 }
207 }
208
209 // Log the strip
210 if ( function_exists( 'wp_kses_reject' ) ) {
211 wp_kses_reject( sprintf( __( '<code>%s</code> HTML tag removed as it is not allowed', 'jetpack' ), '&lt;' . self::$current_element . '&gt;' ), array( self::$current_element => $attrs ) );
212 }
213
214 // Keep the failed match so we can later replace it with a link,
215 // but return the original content to give others a chance too.
216 self::$failed_embeds[] = array(
217 'match' => $orig_html,
218 'src' => esc_url( $src ),
219 );
220
221 return $orig_html;
222 }
223
224 /**
225 * Failed embeds are stripped, so let's convert them to links at least.
226 *
227 * @param string $string Failed embed string.
228 *
229 * @return string $string Linkified string.
230 */
231 public static function maybe_create_links( $string ) {
232 if ( empty( self::$failed_embeds ) ) {
233 return $string;
234 }
235
236 foreach ( self::$failed_embeds as $entry ) {
237 $html = sprintf( '<a href="%s">%s</a>', esc_url( $entry['src'] ), esc_url( $entry['src'] ) );
238 // Check if the string doesn't contain iframe, before replace.
239 if ( ! preg_match( '/<iframe /', $string ) ) {
240 $string = str_replace( $entry['match'], $html, $string );
241 }
242 }
243
244 self::$failed_embeds = array();
245
246 return $string;
247 }
248
249 static function get_attrs( $html ) {
250 if ( ! ( class_exists( 'DOMDocument' ) && function_exists( 'libxml_use_internal_errors' ) && function_exists( 'simplexml_load_string' ) ) ) {
251 trigger_error( __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) );
252 return array();
253 }
254 // We have to go through DOM, since it can load non-well-formed XML (i.e. HTML). SimpleXML cannot.
255 $dom = new DOMDocument;
256 // The @ is not enough to suppress errors when dealing with libxml,
257 // we have to tell it directly how we want to handle errors.
258 libxml_use_internal_errors( TRUE );
259 @$dom->loadHTML( $html ); // suppress parser warnings
260 libxml_use_internal_errors( FALSE );
261 $xml = false;
262 foreach ( $dom->childNodes as $node ) {
263 // find the root node (html)
264 if ( XML_ELEMENT_NODE == $node->nodeType ) {
265 // Use simplexml_load_string rather than simplexml_import_dom as the later doesn't cope well if the XML is malformmed in the DOM See #1688-wpcom
266 libxml_use_internal_errors( true );
267 $xml = simplexml_load_string( $dom->saveXML( $node->firstChild->firstChild ) ); // html->body->object
268 libxml_clear_errors();
269 break;
270 }
271 }
272 if ( ! $xml ) {
273 return array();
274 }
275
276 $attrs = array();
277 $attrs['_raw_html'] = $html;
278
279 // <param> elements
280 foreach ( $xml->param as $param ) {
281 $attrs[(string) $param['name']] = (string) $param['value'];
282 }
283
284 // <object> attributes
285 foreach ( $xml->attributes() as $name => $attr ) {
286 $attrs[$name] = (string) $attr;
287 }
288
289 // <embed> attributes
290 if ( $xml->embed ) {
291 foreach ( $xml->embed->attributes() as $name => $attr ) {
292 $attrs[$name] = (string) $attr;
293 }
294 }
295
296 return $attrs;
297 }
298 }
299