<?php

/*
Plugin Name: WP Oembed
Description: Embed oembed compatible resources in your blog.
Plugin URL: http://wordpress.org/extend/plugins/wp-oembed/
Author: Automattic, Inc.
Author URL: http://automattic.com/
Tags: shortcodes
Version: 0.1
*/

/*
Requires PHP 5
*/

class WP_OEmbed_Consumer {
	var $url;
	var $args   = array();
	var $config = array();

	var $types = array( 'photo', 'video', 'link', 'rich' );

	var $request, $response, $output;

	function __construct( $url, $args = array(), $config = null ) {
		$this->url = $url;
		$default_config = array(
			'format' => false,
			'endpoint' => false
		);
		$this->config = wp_parse_args( $config, $default_config );
		if ( is_array( $args ) )
			$this->args = $args;
	}

	function get() {
		// Could class out the request
		if ( !$this->request = $this->generate_request( $this->url, $this->args, $this->config ) )
			return false;
		// Could class out the response handlers
		if ( !$this->response = $this->parse_response( $this->request ) )
			return false;
		return true;
	}

	function get_endpoint( $url, $format = false ) {
		$endpoints = wp_oembed_producers();
		$r = false;
		foreach ( array_keys( $endpoints ) as $match ) {
			$p_match = preg_quote( $match, '#' );
			$p_match = str_replace( '\://\*\.', '\://(.*?\.)?', $p_match );
			$p_match = str_replace( '\*', '(.*?)', $p_match );
			if ( preg_match( "#$p_match#i", $url ) ) {
				if ( is_scalar( $endpoints[$match] ) && $endpoints[$match] != $match && isset( $endpoints[$endpoints[$match]] ) )
					$r = $endpoints[$endpoints[$match]];
				elseif ( isset( $endpoints[$match]['endpoint'] ) )
					$r = $endpoints[$match];
				break;
			}
		}

		if ( $r )
			$r['endpoint'] = str_replace( '{format}', $format ? $format : $r['format'], $r['endpoint'] );
		
		return $r;
	}

	function generate_request( $url, $args = array(), $config = null ) {
		$format = isset($config['format']) ? $config['format'] : false;
		if ( isset($config['endpoint']) && $config['endpoint'] ) {
			$endpoint = $config['endpoint'];
		} elseif ( $endpoint = $this->get_endpoint( $url, $format ) ) {
			if ( !$format )
				$format = $endpoint['format'];
			$endpoint = $endpoint['endpoint'];
		} else {
			return false;
		}

		return add_query_arg( array_merge( array( 'url' => $url, 'format' => $format ), $args ), $endpoint );
	}

	function parse_response( $request ) {
		if ( !$content = trim( wp_remote_fopen( $request ) ) )
			return false;

		if ( '<' === $content[0] ) { // XML
			if ( !function_exists( 'simplexml_load_string' ) )
				return false;
			if ( !$xml = simplexml_load_string( $content ) )
				return false;
			$r = array();
			foreach ( $xml->children() as $key => $val ) {
				if ( $html = (string) $val ) { // Values should be escaped PCDATA
					$r[$key] = $html;
				} else { // sometimes they're not
					$r[$key] = '';
					foreach ( $val->children() as $child )
						$r[$key] .= $child->asXML();
				}
			}
			$r = (object) $r; // watch out, (bool) (object) array() == true
		} else { // JSON
			if ( !function_exists( 'json_decode' ) )
				return false;
			$r = json_decode( $content );
		}

		if ( !$r )
			return false;

		if ( !isset($r->version) || '1.0' != $r->version )
			return false;

		if ( !isset($r->type) || !in_array($r->type, $this->types) )
			return false;

		return $r;
	}

	function html() {
		if ( !$this->response )
			return '';

		if ( !is_callable( array( $this, 'html_' . $this->response->type ) ) )
			return false;

		call_user_func( array( $this, 'html_' . $this->response->type ) );

		return $this->output;
	}

	function html_photo() {
		foreach ( array( 'url', 'height', 'width' ) as $req )
			if ( empty($this->response->$req) )
				return false;

		$src = clean_url( $this->response->url );
		$height = (int) $this->response->height;
		$width  = (int) $this->response->width;

		if ( empty( $this->response->title ) ) {
			$alt = '';
		} else {
			$alt = attribute_escape( $this->response->title );
		}

		do_action_ref_array( 'wp_oembed_html_photo', array( &$this ) );

		if ( is_null($this->output) )
			$this->output = "<img src='$src' height='$height' width='$width' alt='$alt' />";
	}

	// Not sanitized!
	function html_video() {
		foreach ( array( 'html', 'height', 'width' ) as $req )
			if ( empty($this->response->$req) )
				return false;

		do_action_ref_array( 'wp_oembed_html_video', array( &$this ) );

		if ( is_null($this->output) )
			$this->output = $this->response->html;
	}

	function html_link() {
		do_action_ref_array( 'wp_oembed_html_link', array( &$this ) );

		if ( !is_null($this->output) )
			return;

		$title = $this->url;
		$quote = '';

		if ( isset($this->response->title) && $this->response->title )
			$title = $this->response->title;

		$url = clean_url( $this->url );
		$title = wp_specialchars( $title );

		$this->output = "<a href='$url'>$title</a>";

		if ( isset($this->response->html) && $this->response->html ) {
			$quote = strip_tags( $this->response->html );
			if ( 120 < strlen( $quote ) )
				$quote = substr( $quote, 0, 120 ) . '&hellip;';

			if ( $quote = wp_specialchars( $quote ) )
				$this->output .= "\n<blockquote cite='$url'><p>$quote</p></blockquote>";
		}
	}

	function html_rich() {
		// do nothing
	}

}

/*
[oembed http://example.com/object]
[oembed http://example.com/object http://example.com/oembed-endpoint/]
[oembed
	0/url=object_URL
	1/endpoint=endpount_URL
	... attributes to send to endpoint
*/

// Should probably be combined with some sort of post_content_filtered plugin
function wp_oembed_shortcode($atts) {
	global $shortcode_tags;
	$url = isset($atts['url']) ? $atts['url'] : $atts[0];

	if ( isset($atts['endpoint']) ) {
		$endpoint = $atts['endpoint'];
	} elseif ( isset($atts[1]) ) {
		$endpoint = $atts[1];
	} elseif ( !$endpoint = WP_OEmbed_Consumer::get_endpoint( $url ) )
		return "<!-- invalid oembed url -->";

	$config = null;

	// if there's a compatible shortcode, use it instead
	if ( isset($endpoint['shortcode']) && isset($shortcode_tags[$endpoint['shortcode']]) ) {
		$r = wp_shortcode_create( $endpoint['shortcode'], $atts );
		return do_shortcode("$r");
	} elseif ( is_string($endpoint) ) { // if an endpoint was provided, use it
		$config = array( 'endpoint' => $endpoint );
		foreach ( array_keys($atts) as $k )
			if ( is_numeric($k) )
				unset($atts[$k]);
	}

	$oembed = new WP_OEmbed_Consumer( $url, $atts, $config );
	$oembed->get();
	return $oembed->html();
}

function wp_oembed_producers() {
	return apply_filters( 'wp_oembed_producers', array( 
		'http://*.flickr.com/*' => array(
			'id' => 'flickr',
			'endpoint' => 'http://www.flickr.com/services/oembed/',
			'format' => 'json'
		),

		'http://*.viddler.com/*' => array(
			'id' => 'viddler',
			'endpoint' => 'http://lab.viddler.com/services/oembed/',
			'format' => 'json',
			'args' => array(
				'type' => array( 'simple', 'player' )
			)
		),

		'http://qik.com/*' => array(
			'id' => 'qik',
			'endpoint' => 'http://qik.com/api/oembed.{format}',
			'format' => 'json'
		),

		'http://*.pownce.com/*' => array(
			'id' => 'pownce',
			'endpoint' => 'http://api.pownce.com/2.1/oembed.{format}',
			'format' => 'json'
		),

		'http://*.revision3.com/*' => array(
			'id' => 'revision3',
			'endpoint' => 'http://revision3.com/api/oembed/',
			'format' => 'json'
		),

		'http://www.hulu.com/watch/*' => array(
			'id' => 'hulu',
			'endpoint' => 'http://www.hulu.com/api/oembed.{format}',
			'format' => 'xml'
		),

		'http://www.vimeo.com/*' => array(
			'id' => 'vimeo',
			'endpoint' => 'http://www.vimeo.com/api/oembed.{format}',
			'format' => 'json',
			'shortcode' => 'vimeo',
			'args' => array(
				'byline' => array( 'true', 'false' ),
				'title' => array( 'true', 'false' ),
				'portrait' => array( 'true', 'false' ),
				'color' => '#a-f0-9{6}'
			)
		)
	) );
}

add_shortcode( 'oembed', 'wp_oembed_shortcode' );

