PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.4.5
Jetpack – WP Security, Backup, Speed, & Growth v13.4.5
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 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / shortcodes / class.filter-embedded-html-objects.php
jetpack / modules / shortcodes Last commit date
css 2 years ago images 12 years ago img 13 years ago js 4 years ago archiveorg-book.php 5 years ago archiveorg.php 5 years ago archives.php 5 years ago bandcamp.php 3 years ago brightcove.php 5 years ago cartodb.php 5 years ago class.filter-embedded-html-objects.php 2 years ago codepen.php 5 years ago crowdsignal.php 2 years ago dailymotion.php 3 years ago descript.php 4 years ago facebook.php 2 years ago flatio.php 5 years ago flickr.php 2 years ago getty.php 2 years ago gist.php 3 years ago googleapps.php 2 years ago googlemaps.php 2 years ago googleplus.php 5 years ago gravatar.php 2 years ago houzz.php 5 years ago inline-pdfs.php 4 years ago instagram.php 3 years ago kickstarter.php 3 years ago mailchimp.php 3 years ago medium.php 3 years ago mixcloud.php 2 years ago others.php 2 years ago pinterest.php 2 years ago presentations.php 2 years ago quiz.php 4 years ago recipe.php 2 years ago scribd.php 5 years ago sitemap.php 5 years ago slideshare.php 5 years ago slideshow.php 3 years ago smartframe.php 3 years ago soundcloud.php 3 years ago spotify.php 2 years ago ted.php 5 years ago tweet.php 2 years ago twitchtv.php 5 years ago twitter-timeline.php 5 years ago unavailable.php 3 years ago untappd-menu.php 3 years ago upcoming-events.php 3 years ago ustream.php 5 years ago videopress.php 4 years ago vimeo.php 3 years ago vine.php 5 years ago vr.php 2 years ago wordads.php 5 years ago wufoo.php 3 years ago youtube.php 2 years ago
class.filter-embedded-html-objects.php
404 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
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 * @package automattic/jetpack
9 */
10
11 add_filter( 'pre_kses', array( 'Filter_Embedded_HTML_Objects', 'filter' ), 11 );
12 add_filter( 'pre_kses', array( 'Filter_Embedded_HTML_Objects', 'maybe_create_links' ), 100 ); // See WPCom_Embed_Stats::init().
13
14 /**
15 * Helper class for identifying and parsing known HTML embeds (iframe, object, embed, etc. elements), then converting them to shortcodes.
16 * For unknown HTML embeds, the class still tries to convert them to plain links so that at least something is preserved instead of having the entire element stripped by KSES.
17 *
18 * @since 4.5.0
19 */
20 class Filter_Embedded_HTML_Objects {
21 /**
22 * Array of patterns to search for via strpos().
23 * Keys are patterns, values are callback functions that implement the HTML -> shortcode replacement.
24 * Patterns are matched against URLs (src or movie HTML attributes).
25 *
26 * @var array
27 */
28 public static $strpos_filters = array();
29 /**
30 * Array of patterns to search for via preg_match().
31 * Keys are patterns, values are callback functions that implement the HTML -> shortcode replacement.
32 * Patterns are matched against URLs (src or movie HTML attributes).
33 *
34 * @var array
35 */
36 public static $regexp_filters = array();
37 /**
38 * HTML element being processed.
39 *
40 * @var string
41 */
42 public static $current_element = false;
43 /**
44 * Array of patterns to search for via strpos().
45 * Keys are patterns, values are callback functions that implement the HTML -> shortcode replacement.
46 * Patterns are matched against full HTML elements.
47 *
48 * @var array
49 */
50 public static $html_strpos_filters = array();
51 /**
52 * Array of patterns to search for via preg_match().
53 * Keys are patterns, values are callback functions that implement the HTML -> shortcode replacement.
54 * Patterns are matched against full HTML elements.
55 *
56 * @var array
57 */
58 public static $html_regexp_filters = array();
59 /**
60 * Failed embeds (stripped)
61 *
62 * @var array
63 */
64 public static $failed_embeds = array();
65
66 /**
67 * Store tokens found in Syntax Highlighter.
68 *
69 * @since 4.5.0
70 *
71 * @var array
72 */
73 private static $sh_unfiltered_content_tokens;
74
75 /**
76 * Capture tokens found in Syntax Highlighter and collect them in self::$sh_unfiltered_content_tokens.
77 *
78 * @since 4.5.0
79 *
80 * @param array $match Array of Syntax Highlighter matches.
81 *
82 * @return string
83 */
84 public static function sh_regexp_callback( $match ) {
85 $token = sprintf(
86 '[prekses-filter-token-%1$d-%2$s-%1$d]',
87 wp_rand(),
88 md5( $match[0] )
89 );
90 self::$sh_unfiltered_content_tokens[ $token ] = $match[0];
91 return $token;
92 }
93
94 /**
95 * Look for HTML elements that match the registered patterns.
96 * Replace them with the HTML generated by the registered replacement callbacks.
97 *
98 * @param string $html Post content.
99 */
100 public static function filter( $html ) {
101 if ( ! $html || ! is_string( $html ) ) {
102 return $html;
103 }
104
105 $regexps = array(
106 'object' => '%<object[^>]*+>(?>[^<]*+(?><(?!/object>)[^<]*+)*)</object>%i',
107 'embed' => '%<embed[^>]*+>(?:\s*</embed>)?%i',
108 'iframe' => '%<iframe[^>]*+>(?>[^<]*+(?><(?!/iframe>)[^<]*+)*)</iframe>%i',
109 'div' => '%<div[^>]*+>(?>[^<]*+(?><(?!/div>)[^<]*+)*+)(?:</div>)+%i',
110 'script' => '%<script[^>]*+>(?>[^<]*+(?><(?!/script>)[^<]*+)*)</script>%i',
111 );
112
113 $unfiltered_content_tokens = array();
114 self::$sh_unfiltered_content_tokens = array();
115
116 // Check here to make sure that SyntaxHighlighter is still used. (Just a little future proofing).
117 if ( class_exists( 'SyntaxHighlighter' ) ) {
118 /*
119 * Replace any "code" shortcode blocks with a token that we'll later replace with its original text.
120 * This will keep the contents of the shortcode from being filtered.
121 */
122 global $SyntaxHighlighter; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
123
124 // Check to see if the $syntax_highlighter object has been created and is ready for use.
125 if ( isset( $SyntaxHighlighter ) && is_array( $SyntaxHighlighter->shortcodes ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
126 $shortcode_regex = implode( '|', array_map( 'preg_quote', $SyntaxHighlighter->shortcodes ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
127 $html = preg_replace_callback(
128 '/\[(' . $shortcode_regex . ')(\s[^\]]*)?\][\s\S]*?\[\/\1\]/m',
129 array( __CLASS__, 'sh_regexp_callback' ),
130 $html
131 );
132 $unfiltered_content_tokens = self::$sh_unfiltered_content_tokens;
133 }
134 }
135
136 foreach ( $regexps as $element => $regexp ) {
137 self::$current_element = $element;
138
139 if ( false !== stripos( $html, "<$element" ) ) {
140 $new_html = preg_replace_callback( $regexp, array( __CLASS__, 'dispatch' ), $html );
141 if ( $new_html ) {
142 $html = $new_html;
143 }
144 }
145
146 if ( false !== stripos( $html, "&lt;$element" ) ) {
147 $regexp_entities = self::regexp_entities( $regexp );
148 $new_html = preg_replace_callback( $regexp_entities, array( __CLASS__, 'dispatch_entities' ), $html );
149 if ( $new_html ) {
150 $html = $new_html;
151 }
152 }
153 }
154
155 if ( $unfiltered_content_tokens !== array() ) {
156 // Replace any tokens generated earlier with their original unfiltered text.
157 $html = str_replace( array_keys( $unfiltered_content_tokens ), $unfiltered_content_tokens, $html );
158 }
159
160 return $html;
161 }
162
163 /**
164 * Replace HTML entities in current HTML element regexp.
165 * This is useful when the content is HTML encoded by TinyMCE.
166 *
167 * @param string $regexp Selected regexp.
168 */
169 public static function regexp_entities( $regexp ) {
170 return preg_replace(
171 '/\[\^&([^\]]+)\]\*\+/',
172 '(?>[^&]*+(?>&(?!\1)[^&])*+)*+',
173 str_replace( '?&gt;', '?' . '>', htmlspecialchars( $regexp, ENT_NOQUOTES ) )
174 );
175 }
176
177 /**
178 * Register a filter to convert a matching HTML element to a shortcode.
179 *
180 * We can match the provided pattern against the source URL of the HTML element
181 * (generally the value of the src attribute of the HTML element), or against the full HTML element.
182 *
183 * The callback is passed an array containing the raw HTML of the element as well as pre-parsed attribute name/values.
184 *
185 * @param string $match Pattern to search for: either a regular expression to use with preg_match() or a search string to use with strpos().
186 * @param string $callback Function used to convert embed into shortcode.
187 * @param bool $is_regexp Is $match a regular expression? If true, match using preg_match(). If not, match using strpos(). Default false.
188 * @param bool $is_html_filter Match the pattern against the full HTML (true) or just the source URL (false)? Default false.
189 */
190 public static function register( $match, $callback, $is_regexp = false, $is_html_filter = false ) {
191 if ( $is_html_filter ) {
192 if ( $is_regexp ) {
193 self::$html_regexp_filters[ $match ] = $callback;
194 } else {
195 self::$html_strpos_filters[ $match ] = $callback;
196 }
197 } elseif ( $is_regexp ) {
198 self::$regexp_filters[ $match ] = $callback;
199 } else {
200 self::$strpos_filters[ $match ] = $callback;
201 }
202 }
203
204 /**
205 * Delete an existing registered pattern/replacement filter.
206 *
207 * @param string $match Embed regexp.
208 */
209 public static function unregister( $match ) {
210 // Allow themes/plugins to remove registered embeds.
211 unset( self::$regexp_filters[ $match ] );
212 unset( self::$strpos_filters[ $match ] );
213 unset( self::$html_regexp_filters[ $match ] );
214 unset( self::$html_strpos_filters[ $match ] );
215 }
216
217 /**
218 * Filter and replace HTML element entity.
219 *
220 * @param array $matches Array of matches.
221 */
222 private static function dispatch_entities( $matches ) {
223 $orig_html = $matches[0];
224 $decoded_matches = array( html_entity_decode( $matches[0], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) );
225
226 return self::dispatch( $decoded_matches, $orig_html );
227 }
228
229 /**
230 * Filter and replace HTML element.
231 *
232 * @param array $matches Array of matches.
233 * @param string $orig_html Original html. Returned if no results are found via $matches processing.
234 */
235 private static function dispatch( $matches, $orig_html = null ) {
236 if ( null === $orig_html ) {
237 $orig_html = $matches[0];
238 }
239 $html = preg_replace( '%&#0*58;//%', '://', $matches[0] );
240 $attrs = self::get_attrs( $html );
241 if ( isset( $attrs['src'] ) ) {
242 $src = $attrs['src'];
243 } elseif ( isset( $attrs['movie'] ) ) {
244 $src = $attrs['movie'];
245 } else {
246 // no src found, search html.
247 foreach ( self::$html_strpos_filters as $match => $callback ) {
248 if ( str_contains( $html, $match ) ) {
249 return call_user_func( $callback, $attrs );
250 }
251 }
252
253 foreach ( self::$html_regexp_filters as $match => $callback ) {
254 if ( preg_match( $match, $html ) ) {
255 return call_user_func( $callback, $attrs );
256 }
257 }
258
259 return $orig_html;
260 }
261
262 $src = trim( $src );
263
264 // check source filter.
265 foreach ( self::$strpos_filters as $match => $callback ) {
266 if ( str_contains( $src, $match ) ) {
267 return call_user_func( $callback, $attrs );
268 }
269 }
270
271 foreach ( self::$regexp_filters as $match => $callback ) {
272 if ( preg_match( $match, $src ) ) {
273 return call_user_func( $callback, $attrs );
274 }
275 }
276
277 // check html filters.
278 foreach ( self::$html_strpos_filters as $match => $callback ) {
279 if ( str_contains( $html, $match ) ) {
280 return call_user_func( $callback, $attrs );
281 }
282 }
283
284 foreach ( self::$html_regexp_filters as $match => $callback ) {
285 if ( preg_match( $match, $html ) ) {
286 return call_user_func( $callback, $attrs );
287 }
288 }
289
290 // Log the strip.
291 if ( function_exists( 'wp_kses_reject' ) ) {
292 wp_kses_reject(
293 sprintf(
294 /* translators: placeholder is an HTML tag. */
295 __( '<code>%s</code> HTML tag removed as it is not allowed', 'jetpack' ),
296 '&lt;' . self::$current_element . '&gt;'
297 ),
298 array( self::$current_element => $attrs )
299 );
300 }
301
302 // Keep the failed match so we can later replace it with a link,
303 // but return the original content to give others a chance too.
304 self::$failed_embeds[] = array(
305 'match' => $orig_html,
306 'src' => esc_url( $src ),
307 );
308
309 return $orig_html;
310 }
311
312 /**
313 * Failed embeds are stripped, so let's convert them to links at least.
314 *
315 * @param string $string Failed embed string.
316 *
317 * @return string $string Linkified string.
318 */
319 public static function maybe_create_links( $string ) {
320 if ( empty( self::$failed_embeds ) ) {
321 return $string;
322 }
323
324 foreach ( self::$failed_embeds as $entry ) {
325 $html = sprintf( '<a href="%s">%s</a>', esc_url( $entry['src'] ), esc_url( $entry['src'] ) );
326 // Check if the string doesn't contain iframe, before replace.
327 if ( ! preg_match( '/<iframe /', $string ) ) {
328 $string = str_replace( $entry['match'], $html, $string );
329 }
330 }
331
332 self::$failed_embeds = array();
333
334 return $string;
335 }
336
337 /**
338 * Parse post HTML for HTML tags.
339 *
340 * @param string $html Post HTML.
341 */
342 public static function get_attrs( $html ) {
343 if (
344 ! ( class_exists( 'DOMDocument' ) && function_exists( 'simplexml_load_string' ) ) ) {
345 trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
346 esc_html__( 'PHP’s XML extension is not available. Please contact your hosting provider to enable PHP’s XML extension.', 'jetpack' )
347 );
348 return array();
349 }
350 // We have to go through DOM, since it can load non-well-formed XML (i.e. HTML). SimpleXML cannot.
351 $dom = new DOMDocument();
352 // The @ is not enough to suppress errors when dealing with libxml,
353 // we have to tell it directly how we want to handle errors.
354 libxml_use_internal_errors( true );
355 // Suppress parser warnings.
356 @$dom->loadHTML( $html ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
357 libxml_use_internal_errors( false );
358 $xml = false;
359 // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
360 foreach ( $dom->childNodes as $node ) {
361 // find the root node (html).
362 if ( XML_ELEMENT_NODE === $node->nodeType ) {
363 /*
364 * Use simplexml_load_string rather than simplexml_import_dom
365 * as the later doesn't cope well if the XML is malformmed in the DOM
366 * See #1688-wpcom.
367 */
368 libxml_use_internal_errors( true );
369 // html->body->object.
370 $xml = simplexml_load_string( $dom->saveXML( $node->firstChild->firstChild ) );
371 libxml_clear_errors();
372 break;
373 }
374 }
375 // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
376
377 if ( ! $xml ) {
378 return array();
379 }
380
381 $attrs = array();
382 $attrs['_raw_html'] = $html;
383
384 // <param> elements
385 foreach ( $xml->param as $param ) {
386 $attrs[ (string) $param['name'] ] = (string) $param['value'];
387 }
388
389 // <object> attributes
390 foreach ( $xml->attributes() as $name => $attr ) {
391 $attrs[ $name ] = (string) $attr;
392 }
393
394 // <embed> attributes
395 if ( $xml->embed ) {
396 foreach ( $xml->embed->attributes() as $name => $attr ) {
397 $attrs[ $name ] = (string) $attr;
398 }
399 }
400
401 return $attrs;
402 }
403 }
404