PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.7
Jetpack – WP Security, Backup, Speed, & Growth v6.7
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.7, at modules/shortcodes/class.filter-embedded-html-objects.php

295 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 public static 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 public static 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 global $SyntaxHighlighter;
77
78 // Check to see if the $SyntaxHighlighter object has been created and is ready for use
79 if ( isset( $SyntaxHighlighter ) && is_array( $SyntaxHighlighter->shortcodes ) ) {
80 $shortcode_regex = implode( '|', array_map( 'preg_quote', $SyntaxHighlighter->shortcodes ) );
81 $html = preg_replace_callback(
82 '/\[(' . $shortcode_regex . ')(\s[^\]]*)?\][\s\S]*?\[\/\1\]/m',
83 array( __CLASS__, 'sh_regexp_callback' ),
84 $html
85 );
86 $unfiltered_content_tokens = self::$sh_unfiltered_content_tokens;
87 }
88 }
89
90 foreach ( $regexps as $element => $regexp ) {
91 self::$current_element = $element;
92
93 if ( false !== stripos( $html, "<$element" ) ) {
94 if ( $new_html = preg_replace_callback( $regexp, array( __CLASS__, 'dispatch' ), $html ) ) {
95 $html = $new_html;
96 }
97 }
98
99 if ( false !== stripos( $html, "&lt;$element" ) ) {
100 $regexp_entities = self::regexp_entities( $regexp );
101 if ( $new_html = preg_replace_callback( $regexp_entities, array( __CLASS__, 'dispatch_entities' ), $html ) ) {
102 $html = $new_html;
103 }
104 }
105 }
106
107 if ( count( $unfiltered_content_tokens ) > 0 ) {
108 // Replace any tokens generated earlier with their original unfiltered text
109 $html = str_replace( array_keys( $unfiltered_content_tokens ), $unfiltered_content_tokens, $html );
110 }
111
112 return $html;
113 }
114
115 public static function regexp_entities( $regexp ) {
116 return preg_replace(
117 '/\[\^&([^\]]+)\]\*\+/',
118 '(?>[^&]*+(?>&(?!\1)[^&])*+)*+',
119 str_replace( '?&gt;', '?' . '>', htmlspecialchars( $regexp, ENT_NOQUOTES ) )
120 );
121 }
122
123 public static function register( $match, $callback, $is_regexp = false, $is_html_filter = false ) {
124 if ( $is_html_filter ) {
125 if ( $is_regexp ) {
126 self::$html_regexp_filters[ $match ] = $callback;
127 } else {
128 self::$html_strpos_filters[ $match ] = $callback;
129 }
130 } else {
131 if ( $is_regexp ) {
132 self::$regexp_filters[ $match ] = $callback;
133 } else {
134 self::$strpos_filters[ $match ] = $callback;
135 }
136 }
137 }
138
139 public static function unregister( $match ) {
140 // Allow themes/plugins to remove registered embeds
141 unset( self::$regexp_filters[ $match ] );
142 unset( self::$strpos_filters[ $match ] );
143 unset( self::$html_regexp_filters[ $match ] );
144 unset( self::$html_strpos_filters[ $match ] );
145 }
146
147 static function dispatch_entities( $matches ) {
148 $matches[0] = html_entity_decode( $matches[0] );
149
150 return self::dispatch( $matches );
151 }
152
153 static function dispatch( $matches ) {
154 $html = preg_replace( '%&#0*58;//%', '://', $matches[0] );
155 $attrs = self::get_attrs( $html );
156 if ( isset( $attrs['src'] ) ) {
157 $src = $attrs['src'];
158 } elseif ( isset( $attrs['movie'] ) ) {
159 $src = $attrs['movie'];
160 } else {
161 // no src found, search html
162 foreach ( self::$html_strpos_filters as $match => $callback ) {
163 if ( false !== strpos( $html, $match ) ) {
164 return call_user_func( $callback, $attrs );
165 }
166 }
167
168 foreach ( self::$html_regexp_filters as $match => $callback ) {
169 if ( preg_match( $match, $html ) ) {
170 return call_user_func( $callback, $attrs );
171 }
172 }
173
174 return $matches[0];
175 }
176
177 $src = trim( $src );
178
179 // check source filter
180 foreach ( self::$strpos_filters as $match => $callback ) {
181 if ( false !== strpos( $src, $match ) ) {
182 return call_user_func( $callback, $attrs );
183 }
184 }
185
186 foreach ( self::$regexp_filters as $match => $callback ) {
187 if ( preg_match( $match, $src ) ) {
188 return call_user_func( $callback, $attrs );
189 }
190 }
191
192 // check html filters
193 foreach ( self::$html_strpos_filters as $match => $callback ) {
194 if ( false !== strpos( $html, $match ) ) {
195 return call_user_func( $callback, $attrs );
196 }
197 }
198
199 foreach ( self::$html_regexp_filters as $match => $callback ) {
200 if ( preg_match( $match, $html ) ) {
201 return call_user_func( $callback, $attrs );
202 }
203 }
204
205 // Log the strip
206 if ( function_exists( 'wp_kses_reject' ) ) {
207 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 ) );
208 }
209
210 // Keep the failed match so we can later replace it with a link,
211 // but return the original content to give others a chance too.
212 self::$failed_embeds[] = array(
213 'match' => $matches[0],
214 'src' => esc_url( $src ),
215 );
216
217 return $matches[0];
218 }
219
220 /**
221 * Failed embeds are stripped, so let's convert them to links at least.
222 *
223 * @param string $string Failed embed string.
224 *
225 * @return string $string Linkified string.
226 */
227 public static function maybe_create_links( $string ) {
228 if ( empty( self::$failed_embeds ) ) {
229 return $string;
230 }
231
232 foreach ( self::$failed_embeds as $entry ) {
233 $html = sprintf( '<a href="%s">%s</a>', esc_url( $entry['src'] ), esc_url( $entry['src'] ) );
234 // Check if the string doesn't contain iframe, before replace.
235 if ( ! preg_match( '/<iframe /', $string ) ) {
236 $string = str_replace( $entry['match'], $html, $string );
237 }
238 }
239
240 self::$failed_embeds = array();
241
242 return $string;
243 }
244
245 static function get_attrs( $html ) {
246 if ( ! ( class_exists( 'DOMDocument' ) && function_exists( 'libxml_use_internal_errors' ) && function_exists( 'simplexml_load_string' ) ) ) {
247 trigger_error( __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) );
248 return array();
249 }
250 // We have to go through DOM, since it can load non-well-formed XML (i.e. HTML). SimpleXML cannot.
251 $dom = new DOMDocument();
252 // The @ is not enough to suppress errors when dealing with libxml,
253 // we have to tell it directly how we want to handle errors.
254 libxml_use_internal_errors( true );
255 @$dom->loadHTML( $html ); // suppress parser warnings
256 libxml_use_internal_errors( false );
257 $xml = false;
258 foreach ( $dom->childNodes as $node ) {
259 // find the root node (html)
260 if ( XML_ELEMENT_NODE == $node->nodeType ) {
261 // 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
262 libxml_use_internal_errors( true );
263 $xml = simplexml_load_string( $dom->saveXML( $node->firstChild->firstChild ) ); // html->body->object
264 libxml_clear_errors();
265 break;
266 }
267 }
268 if ( ! $xml ) {
269 return array();
270 }
271
272 $attrs = array();
273 $attrs['_raw_html'] = $html;
274
275 // <param> elements
276 foreach ( $xml->param as $param ) {
277 $attrs[ (string) $param['name'] ] = (string) $param['value'];
278 }
279
280 // <object> attributes
281 foreach ( $xml->attributes() as $name => $attr ) {
282 $attrs[ $name ] = (string) $attr;
283 }
284
285 // <embed> attributes
286 if ( $xml->embed ) {
287 foreach ( $xml->embed->attributes() as $name => $attr ) {
288 $attrs[ $name ] = (string) $attr;
289 }
290 }
291
292 return $attrs;
293 }
294 }
295