| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | /* |
|---|
| 4 | Plugin Name: WP Oembed |
|---|
| 5 | Description: Embed oembed compatible resources in your blog. |
|---|
| 6 | Plugin URL: http://wordpress.org/extend/plugins/wp-oembed/ |
|---|
| 7 | Author: Automattic, Inc. |
|---|
| 8 | Author URL: http://automattic.com/ |
|---|
| 9 | Tags: shortcodes |
|---|
| 10 | Version: 0.1 |
|---|
| 11 | */ |
|---|
| 12 | |
|---|
| 13 | /* |
|---|
| 14 | Requires PHP 5 |
|---|
| 15 | */ |
|---|
| 16 | |
|---|
| 17 | class WP_OEmbed_Consumer { |
|---|
| 18 | var $url; |
|---|
| 19 | var $args = array(); |
|---|
| 20 | var $config = array(); |
|---|
| 21 | |
|---|
| 22 | var $types = array( 'photo', 'video', 'link', 'rich' ); |
|---|
| 23 | |
|---|
| 24 | var $request, $response, $output; |
|---|
| 25 | |
|---|
| 26 | function __construct( $url, $args = array(), $config = null ) { |
|---|
| 27 | $this->url = $url; |
|---|
| 28 | $default_config = array( |
|---|
| 29 | 'format' => false, |
|---|
| 30 | 'endpoint' => false |
|---|
| 31 | ); |
|---|
| 32 | $this->config = wp_parse_args( $config, $default_config ); |
|---|
| 33 | if ( is_array( $args ) ) |
|---|
| 34 | $this->args = $args; |
|---|
| 35 | } |
|---|
| 36 | |
|---|
| 37 | function get() { |
|---|
| 38 | // Could class out the request |
|---|
| 39 | if ( !$this->request = $this->generate_request( $this->url, $this->args, $this->config ) ) |
|---|
| 40 | return false; |
|---|
| 41 | // Could class out the response handlers |
|---|
| 42 | if ( !$this->response = $this->parse_response( $this->request ) ) |
|---|
| 43 | return false; |
|---|
| 44 | return true; |
|---|
| 45 | } |
|---|
| 46 | |
|---|
| 47 | function get_endpoint( $url, $format = false ) { |
|---|
| 48 | $endpoints = wp_oembed_producers(); |
|---|
| 49 | $r = false; |
|---|
| 50 | foreach ( array_keys( $endpoints ) as $match ) { |
|---|
| 51 | $p_match = preg_quote( $match, '#' ); |
|---|
| 52 | $p_match = str_replace( '\://\*\.', '\://(.*?\.)?', $p_match ); |
|---|
| 53 | $p_match = str_replace( '\*', '(.*?)', $p_match ); |
|---|
| 54 | if ( preg_match( "#$p_match#i", $url ) ) { |
|---|
| 55 | if ( is_scalar( $endpoints[$match] ) && $endpoints[$match] != $match && isset( $endpoints[$endpoints[$match]] ) ) |
|---|
| 56 | $r = $endpoints[$endpoints[$match]]; |
|---|
| 57 | elseif ( isset( $endpoints[$match]['endpoint'] ) ) |
|---|
| 58 | $r = $endpoints[$match]; |
|---|
| 59 | break; |
|---|
| 60 | } |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | if ( $r ) |
|---|
| 64 | $r['endpoint'] = str_replace( '{format}', $format ? $format : $r['format'], $r['endpoint'] ); |
|---|
| 65 | |
|---|
| 66 | return $r; |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | function generate_request( $url, $args = array(), $config = null ) { |
|---|
| 70 | $format = isset($config['format']) ? $config['format'] : false; |
|---|
| 71 | if ( isset($config['endpoint']) && $config['endpoint'] ) { |
|---|
| 72 | $endpoint = $config['endpoint']; |
|---|
| 73 | } elseif ( $endpoint = $this->get_endpoint( $url, $format ) ) { |
|---|
| 74 | if ( !$format ) |
|---|
| 75 | $format = $endpoint['format']; |
|---|
| 76 | $endpoint = $endpoint['endpoint']; |
|---|
| 77 | } else { |
|---|
| 78 | return false; |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | return add_query_arg( array_merge( array( 'url' => $url, 'format' => $format ), $args ), $endpoint ); |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | function parse_response( $request ) { |
|---|
| 85 | if ( !$content = trim( wp_remote_fopen( $request ) ) ) |
|---|
| 86 | return false; |
|---|
| 87 | |
|---|
| 88 | if ( '<' === $content[0] ) { // XML |
|---|
| 89 | if ( !function_exists( 'simplexml_load_string' ) ) |
|---|
| 90 | return false; |
|---|
| 91 | if ( !$xml = simplexml_load_string( $content ) ) |
|---|
| 92 | return false; |
|---|
| 93 | $r = array(); |
|---|
| 94 | foreach ( $xml->children() as $key => $val ) { |
|---|
| 95 | if ( $html = (string) $val ) { // Values should be escaped PCDATA |
|---|
| 96 | $r[$key] = $html; |
|---|
| 97 | } else { // sometimes they're not |
|---|
| 98 | $r[$key] = ''; |
|---|
| 99 | foreach ( $val->children() as $child ) |
|---|
| 100 | $r[$key] .= $child->asXML(); |
|---|
| 101 | } |
|---|
| 102 | } |
|---|
| 103 | $r = (object) $r; // watch out, (bool) (object) array() == true |
|---|
| 104 | } else { // JSON |
|---|
| 105 | if ( !function_exists( 'json_decode' ) ) |
|---|
| 106 | return false; |
|---|
| 107 | $r = json_decode( $content ); |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | if ( !$r ) |
|---|
| 111 | return false; |
|---|
| 112 | |
|---|
| 113 | if ( !isset($r->version) || '1.0' != $r->version ) |
|---|
| 114 | return false; |
|---|
| 115 | |
|---|
| 116 | if ( !isset($r->type) || !in_array($r->type, $this->types) ) |
|---|
| 117 | return false; |
|---|
| 118 | |
|---|
| 119 | return $r; |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | function html() { |
|---|
| 123 | if ( !$this->response ) |
|---|
| 124 | return ''; |
|---|
| 125 | |
|---|
| 126 | if ( !is_callable( array( $this, 'html_' . $this->response->type ) ) ) |
|---|
| 127 | return false; |
|---|
| 128 | |
|---|
| 129 | call_user_func( array( $this, 'html_' . $this->response->type ) ); |
|---|
| 130 | |
|---|
| 131 | return $this->output; |
|---|
| 132 | } |
|---|
| 133 | |
|---|
| 134 | function html_photo() { |
|---|
| 135 | foreach ( array( 'url', 'height', 'width' ) as $req ) |
|---|
| 136 | if ( empty($this->response->$req) ) |
|---|
| 137 | return false; |
|---|
| 138 | |
|---|
| 139 | $src = clean_url( $this->response->url ); |
|---|
| 140 | $height = (int) $this->response->height; |
|---|
| 141 | $width = (int) $this->response->width; |
|---|
| 142 | |
|---|
| 143 | if ( empty( $this->response->title ) ) { |
|---|
| 144 | $alt = ''; |
|---|
| 145 | } else { |
|---|
| 146 | $alt = attribute_escape( $this->response->title ); |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | do_action_ref_array( 'wp_oembed_html_photo', array( &$this ) ); |
|---|
| 150 | |
|---|
| 151 | if ( is_null($this->output) ) |
|---|
| 152 | $this->output = "<img src='$src' height='$height' width='$width' alt='$alt' />"; |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | // Not sanitized! |
|---|
| 156 | function html_video() { |
|---|
| 157 | foreach ( array( 'html', 'height', 'width' ) as $req ) |
|---|
| 158 | if ( empty($this->response->$req) ) |
|---|
| 159 | return false; |
|---|
| 160 | |
|---|
| 161 | do_action_ref_array( 'wp_oembed_html_video', array( &$this ) ); |
|---|
| 162 | |
|---|
| 163 | if ( is_null($this->output) ) |
|---|
| 164 | $this->output = $this->response->html; |
|---|
| 165 | } |
|---|
| 166 | |
|---|
| 167 | function html_link() { |
|---|
| 168 | do_action_ref_array( 'wp_oembed_html_link', array( &$this ) ); |
|---|
| 169 | |
|---|
| 170 | if ( !is_null($this->output) ) |
|---|
| 171 | return; |
|---|
| 172 | |
|---|
| 173 | $title = $this->url; |
|---|
| 174 | $quote = ''; |
|---|
| 175 | |
|---|
| 176 | if ( isset($this->response->title) && $this->response->title ) |
|---|
| 177 | $title = $this->response->title; |
|---|
| 178 | |
|---|
| 179 | $url = clean_url( $this->url ); |
|---|
| 180 | $title = wp_specialchars( $title ); |
|---|
| 181 | |
|---|
| 182 | $this->output = "<a href='$url'>$title</a>"; |
|---|
| 183 | |
|---|
| 184 | if ( isset($this->response->html) && $this->response->html ) { |
|---|
| 185 | $quote = strip_tags( $this->response->html ); |
|---|
| 186 | if ( 120 < strlen( $quote ) ) |
|---|
| 187 | $quote = substr( $quote, 0, 120 ) . '…'; |
|---|
| 188 | |
|---|
| 189 | if ( $quote = wp_specialchars( $quote ) ) |
|---|
| 190 | $this->output .= "\n<blockquote cite='$url'><p>$quote</p></blockquote>"; |
|---|
| 191 | } |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | function html_rich() { |
|---|
| 195 | // do nothing |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | /* |
|---|
| 201 | [oembed http://example.com/object] |
|---|
| 202 | [oembed http://example.com/object http://example.com/oembed-endpoint/] |
|---|
| 203 | [oembed |
|---|
| 204 | 0/url=object_URL |
|---|
| 205 | 1/endpoint=endpount_URL |
|---|
| 206 | ... attributes to send to endpoint |
|---|
| 207 | */ |
|---|
| 208 | |
|---|
| 209 | // Should probably be combined with some sort of post_content_filtered plugin |
|---|
| 210 | function wp_oembed_shortcode($atts) { |
|---|
| 211 | global $shortcode_tags; |
|---|
| 212 | $url = isset($atts['url']) ? $atts['url'] : $atts[0]; |
|---|
| 213 | |
|---|
| 214 | if ( isset($atts['endpoint']) ) { |
|---|
| 215 | $endpoint = $atts['endpoint']; |
|---|
| 216 | } elseif ( isset($atts[1]) ) { |
|---|
| 217 | $endpoint = $atts[1]; |
|---|
| 218 | } elseif ( !$endpoint = WP_OEmbed_Consumer::get_endpoint( $url ) ) |
|---|
| 219 | return "<!-- invalid oembed url -->"; |
|---|
| 220 | |
|---|
| 221 | $config = null; |
|---|
| 222 | |
|---|
| 223 | // if there's a compatible shortcode, use it instead |
|---|
| 224 | if ( isset($endpoint['shortcode']) && isset($shortcode_tags[$endpoint['shortcode']]) ) { |
|---|
| 225 | $r = wp_shortcode_create( $endpoint['shortcode'], $atts ); |
|---|
| 226 | return do_shortcode("$r"); |
|---|
| 227 | } elseif ( is_string($endpoint) ) { // if an endpoint was provided, use it |
|---|
| 228 | $config = array( 'endpoint' => $endpoint ); |
|---|
| 229 | foreach ( array_keys($atts) as $k ) |
|---|
| 230 | if ( is_numeric($k) ) |
|---|
| 231 | unset($atts[$k]); |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | $oembed = new WP_OEmbed_Consumer( $url, $atts, $config ); |
|---|
| 235 | $oembed->get(); |
|---|
| 236 | return $oembed->html(); |
|---|
| 237 | } |
|---|
| 238 | |
|---|
| 239 | function wp_oembed_producers() { |
|---|
| 240 | return apply_filters( 'wp_oembed_producers', array( |
|---|
| 241 | 'http://*.flickr.com/*' => array( |
|---|
| 242 | 'id' => 'flickr', |
|---|
| 243 | 'endpoint' => 'http://www.flickr.com/services/oembed/', |
|---|
| 244 | 'format' => 'json' |
|---|
| 245 | ), |
|---|
| 246 | |
|---|
| 247 | 'http://*.viddler.com/*' => array( |
|---|
| 248 | 'id' => 'viddler', |
|---|
| 249 | 'endpoint' => 'http://lab.viddler.com/services/oembed/', |
|---|
| 250 | 'format' => 'json', |
|---|
| 251 | 'args' => array( |
|---|
| 252 | 'type' => array( 'simple', 'player' ) |
|---|
| 253 | ) |
|---|
| 254 | ), |
|---|
| 255 | |
|---|
| 256 | 'http://qik.com/*' => array( |
|---|
| 257 | 'id' => 'qik', |
|---|
| 258 | 'endpoint' => 'http://qik.com/api/oembed.{format}', |
|---|
| 259 | 'format' => 'json' |
|---|
| 260 | ), |
|---|
| 261 | |
|---|
| 262 | 'http://*.pownce.com/*' => array( |
|---|
| 263 | 'id' => 'pownce', |
|---|
| 264 | 'endpoint' => 'http://api.pownce.com/2.1/oembed.{format}', |
|---|
| 265 | 'format' => 'json' |
|---|
| 266 | ), |
|---|
| 267 | |
|---|
| 268 | 'http://*.revision3.com/*' => array( |
|---|
| 269 | 'id' => 'revision3', |
|---|
| 270 | 'endpoint' => 'http://revision3.com/api/oembed/', |
|---|
| 271 | 'format' => 'json' |
|---|
| 272 | ), |
|---|
| 273 | |
|---|
| 274 | 'http://www.hulu.com/watch/*' => array( |
|---|
| 275 | 'id' => 'hulu', |
|---|
| 276 | 'endpoint' => 'http://www.hulu.com/api/oembed.{format}', |
|---|
| 277 | 'format' => 'xml' |
|---|
| 278 | ), |
|---|
| 279 | |
|---|
| 280 | 'http://www.vimeo.com/*' => array( |
|---|
| 281 | 'id' => 'vimeo', |
|---|
| 282 | 'endpoint' => 'http://www.vimeo.com/api/oembed.{format}', |
|---|
| 283 | 'format' => 'json', |
|---|
| 284 | 'shortcode' => 'vimeo', |
|---|
| 285 | 'args' => array( |
|---|
| 286 | 'byline' => array( 'true', 'false' ), |
|---|
| 287 | 'title' => array( 'true', 'false' ), |
|---|
| 288 | 'portrait' => array( 'true', 'false' ), |
|---|
| 289 | 'color' => '#a-f0-9{6}' |
|---|
| 290 | ) |
|---|
| 291 | ) |
|---|
| 292 | ) ); |
|---|
| 293 | } |
|---|
| 294 | |
|---|
| 295 | add_shortcode( 'oembed', 'wp_oembed_shortcode' ); |
|---|