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

300 lines 9.3 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 $orig_html = $matches[0];
149 $decoded_matches = array( html_entity_decode( $matches[0] ) );
150
151 return self::dispatch( $decoded_matches, $orig_html );
152 }
153
154 static function dispatch( $matches, $orig_html = null ) {
155 if ( null === $orig_html ) {
156 $orig_html = $matches[0];
157 }
158
159 $html = preg_replace( '%&#0*58;//%', '://', $matches[0] );
160 $attrs = self::get_attrs( $html );
161 if ( isset( $attrs['src'] ) ) {
162 $src = $attrs['src'];
163 } elseif ( isset( $attrs['movie'] ) ) {
164 $src = $attrs['movie'];
165 } else {
166 // no src found, search html
167 foreach ( self::$html_strpos_filters as $match => $callback ) {
168 if ( false !== strpos( $html, $match ) ) {
169 return call_user_func( $callback, $attrs );
170 }
171 }
172
173 foreach ( self::$html_regexp_filters as $match => $callback ) {
174 if ( preg_match( $match, $html ) ) {
175 return call_user_func( $callback, $attrs );
176 }
177 }
178
179 return $orig_html;
180 }
181
182 $src = trim( $src );
183
184 // check source filter
185 foreach ( self::$strpos_filters as $match => $callback ) {
186 if ( false !== strpos( $src, $match ) ) {
187 return call_user_func( $callback, $attrs );
188 }
189 }
190
191 foreach ( self::$regexp_filters as $match => $callback ) {
192 if ( preg_match( $match, $src ) ) {
193 return call_user_func( $callback, $attrs );
194 }
195 }
196
197 // check html filters
198 foreach ( self::$html_strpos_filters as $match => $callback ) {
199 if ( false !== strpos( $html, $match ) ) {
200 return call_user_func( $callback, $attrs );
201 }
202 }
203
204 foreach ( self::$html_regexp_filters as $match => $callback ) {
205 if ( preg_match( $match, $html ) ) {
206 return call_user_func( $callback, $attrs );
207 }
208 }
209
210 // Log the strip
211 if ( function_exists( 'wp_kses_reject' ) ) {
212 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 ) );
213 }
214
215 // Keep the failed match so we can later replace it with a link,
216 // but return the original content to give others a chance too.
217 self::$failed_embeds[] = array(
218 'match' => $orig_html,
219 'src' => esc_url( $src ),
220 );
221
222 return $orig_html;
223 }
224
225 /**
226 * Failed embeds are stripped, so let's convert them to links at least.
227 *
228 * @param string $string Failed embed string.
229 *
230 * @return string $string Linkified string.
231 */
232 public static function maybe_create_links( $string ) {
233 if ( empty( self::$failed_embeds ) ) {
234 return $string;
235 }
236
237 foreach ( self::$failed_embeds as $entry ) {
238 $html = sprintf( '<a href="%s">%s</a>', esc_url( $entry['src'] ), esc_url( $entry['src'] ) );
239 // Check if the string doesn't contain iframe, before replace.
240 if ( ! preg_match( '/<iframe /', $string ) ) {
241 $string = str_replace( $entry['match'], $html, $string );
242 }
243 }
244
245 self::$failed_embeds = array();
246
247 return $string;
248 }
249
250 static function get_attrs( $html ) {
251 if ( ! ( class_exists( 'DOMDocument' ) && function_exists( 'libxml_use_internal_errors' ) && function_exists( 'simplexml_load_string' ) ) ) {
252 trigger_error( __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) );
253 return array();
254 }
255 // We have to go through DOM, since it can load non-well-formed XML (i.e. HTML). SimpleXML cannot.
256 $dom = new DOMDocument();
257 // The @ is not enough to suppress errors when dealing with libxml,
258 // we have to tell it directly how we want to handle errors.
259 libxml_use_internal_errors( true );
260 @$dom->loadHTML( $html ); // suppress parser warnings
261 libxml_use_internal_errors( false );
262 $xml = false;
263 foreach ( $dom->childNodes as $node ) {
264 // find the root node (html)
265 if ( XML_ELEMENT_NODE == $node->nodeType ) {
266 // 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
267 libxml_use_internal_errors( true );
268 $xml = simplexml_load_string( $dom->saveXML( $node->firstChild->firstChild ) ); // html->body->object
269 libxml_clear_errors();
270 break;
271 }
272 }
273 if ( ! $xml ) {
274 return array();
275 }
276
277 $attrs = array();
278 $attrs['_raw_html'] = $html;
279
280 // <param> elements
281 foreach ( $xml->param as $param ) {
282 $attrs[ (string) $param['name'] ] = (string) $param['value'];
283 }
284
285 // <object> attributes
286 foreach ( $xml->attributes() as $name => $attr ) {
287 $attrs[ $name ] = (string) $attr;
288 }
289
290 // <embed> attributes
291 if ( $xml->embed ) {
292 foreach ( $xml->embed->attributes() as $name => $attr ) {
293 $attrs[ $name ] = (string) $attr;
294 }
295 }
296
297 return $attrs;
298 }
299 }
300