Remote Content Shortcode

Note: This plugin is now part of the WordPress Plugin Repository as Remote Content Shortcode.

The Remote Content Shortcode plugin will enable a shortcode to display remote content embedded into a post or page. In fact, it is being used to embed the code below from my SVN server. To use it, just place the code in your page where you want to remote content to be placed.

[remote_content url="http://www.example.com"/]

By default this will just fetch the content and drop it straight into the page, which isn’t always ideal (like when you are using a code formatter like I am). Because of this, there are a few more parameters you can pass to Remote Content Shortcode:

  • url – the url that you want to request.
  • method=[ GET | POST ] – set the request type, defaults to GET.
  • timeout=[ 0-9... ] – set the request timeout in seconds, defaults to 10 seconds.
  • userpwd=[ username:password | post_meta | site_option | constant ] – the username and password to send for BASIC authentication. It is recommended to not set the username and password directly in the tag, as it will be visible on your site if this plugin is disabled, and instead use one of the other options. By order of priority, if the value matches a post meta_key the meta_value is used, if it matches a site_option the option_value is used, and if it matches a constant the constant value is used, otherwise the data is passed as is. The format is username:password.
  • htmlentities=[ false | true ] – if you want to HTML encode the content for display set to true, defaults to false.
  • strip_tags=[ false | true ] – remove all tags from the remote content (after CSS selection).
  • decode_atts=[ false | true ] – the SyntaxHighlighter plugin will HTML encode your shortcode attributes, so attr="blah" becomes attr="blah". This fixes it to the intended value when set to true, defaults to false.
  • selector=[ CSS Selectors... ] – the CSS selector or comma separated list or selectors for the content you would like to display, for example div.main-content or div.this-class #this-id, defaults to the entire document.
  • remove=[ CSS Selectors... ] – the CSS selector or comma separated list or selectors for the content that you would like to remove from the content, for example h2.this-class or div#this-id, defaults to no replacement.
  • find=[ regex ] – use a PHP regular expression to find content and replace it based on the replaceattribute, for example ~http://([^\.]*?)\.example\.com~, defaults to disabled.
  • replace=[ regex ] – the replacement text to use with the results of the find regular expression, for example https://\\1.new-domain.com, defaults to empty string replacement.
  • cache=[true| false ] – set to false to prevent the contents from being cached in the WP-Cache/WordPress transients, defaults to true for performance.
  • cache_ttl=[ 0-9... 3600 ] – the cache expiration in seconds or 0 for as long as possible, defaults to 3600 seconds.
If you specify any content for Remote Content Shortcode, it will be sent to the remote server as the request data.
[remote_content url="http://www.example.com" method="POST"]
{ json: { example: request } }
[/remote_content]

This source files for Remote Content Shortcode are always up to date based on the trunk of the WordPress Plugin SVN repository.

remote-content-shortcode.php

<?php
/*
Plugin Name: Remote Content Shortcode
Plugin URI: http://www.doublesharp.com
Description: Embed remote content with a shortcode
Author: Justin Silver
Version: 1.5
Author URI: http://doublesharp.com
License: GPL2
*/

if ( ! class_exists( 'RemoteContentShortcode' ) ):

class RemoteContentShortcode {

	private static $instance;

	private function __construct() { }

	public static function init() {
		if ( ! is_admin() && ! self::$instance ) {
			self::$instance = new RemoteContentShortcode();
			self::$instance->add_shortcode();
		}
	}

	public function add_shortcode(){
		add_shortcode( 'remote_content', array( &$this, 'remote_content_shortcode' ) );
	}

	public function remote_content_shortcode( $atts, $content=null ) {
		// decode and remove quotes, if we wanted to (for use with SyntaxHighlighter)
		if ( isset( $atts['decode_atts'] ) ) {
			switch ( strtolower( html_entity_decode( $atts['decode_atts'] ) ) ) {
			 	case 'true': case '"true"':
					foreach ( $atts as $key => &$value ) {
						$value = html_entity_decode( $value );
						if ( strpos( $value, '"' ) === 0 ) $value = substr( $value, 1, strlen( $value ) - 2 );
					}
			 		break;
			 	default:
			 		break;
			}
		}

		$atts = shortcode_atts( 
			array(
				'userpwd' => false,
				'method' => 'GET',
				'timeout' => 10,
				'url' => '',
				'selector' => false,
				'remove' => false,
				'find' => false,
				'replace' => false,
				'htmlentities' => false,
				'params' => false,
				'strip_tags' => false,
				'cache' => true,
				'cache_ttl' => 3600,
			), 
			$atts
		);

		// convert %QUOT% to "
		$atts['find'] = $this->quote_replace( $atts['find'] );
		$atts['replace'] = $this->quote_replace( $atts['replace'] );

		// extract attributes
		extract( $atts );

		// normalize parameters
		$is_cache = strtolower( $cache ) != 'false';
		$is_htmlentities = strtolower( $htmlentities ) == 'true';
		$is_strip_tags = strtolower( $strip_tags ) == 'true';
		$method = strtoupper( $method );

		$group = 'remote_content_cache';
		$key = implode( $atts );
		$error = false;
		if ( ! $is_cache || false === ( $response = wp_cache_get( $key, $group ) ) ){

			// if we don't have a url, don't bother
			if ( empty( $url ) ) return;
			
			// change ampersands back since WP will encode them between the visual/text editor
			if ( strpos( $url, '&amp;' ) !== false ) {
				$url = str_replace( '&amp;', '&', $url );
			}

			// inherit params from the parent page query string
			if ( ! empty($params) ) {
				if (strpos($url, '?') === false) {
					$url .= '?';
				}

				$qs = explode( ',', trim( $params ) );

				foreach ( $qs as $q ) {
					$q = trim( $q );
					if ( strpos( $q, '=') !== false ) {
						$p = explode('=', $q );
						$q = trim( $p[0] );
						$v = trim( $p[1] );
					}
					if ( isset( $_REQUEST[$q] ) ) {
						$v = $_REQUEST[$q];
					}
					$url .= "&{$q}={$v}";
				}
			}

			// apply filters to the arguments
			$url = apply_filters( 'remote_content_shortcode_url', $url );
			$content = apply_filters( 'remote_content_shortcode_postfields', $content, $url );
			$ssl_verifyhost = apply_filters( 'remote_content_shortcode_ssl_verifyhost', false, $url );
			$ssl_verifypeer = apply_filters( 'remote_content_shortcode_ssl_verifypeer', false, $url );

			// get the user:password BASIC AUTH
			if ( ! empty( $userpwd ) ){
				global $post;
				if ( false!==( $meta_userpwd = get_post_meta( $userpwd, $post->ID, true ) ) ) {
					// if the userpwd is a post meta, use that
					$userpwd = $meta_userpwd;
				} elseif ( false !== ( $option_userpwd = get_option( $userpwd ) ) ) {
					// if the userpwd is a site option, use that
					$userpwd = $option_userpwd;
				} elseif ( defined( $userpwd ) ) {
					// if the userpwd is a constant, use that
					$userpwd = constant( $userpwd );
				} 
				/* lastly assume the userpwd is plaintext, this is not safe as it will be
				 displayed in the browser if this plugin is disabled */
			}

			// set up curl
			$ch = curl_init();
			// the url to request
			curl_setopt( $ch, CURLOPT_URL, $url );
			// follow redirects
			curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
			// set a timeout
			curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
			// return to variable
			curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
			// (don't) verify host ssl cert
			curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, $ssl_verifyhost );
			// (don't) verify peer ssl cert	
			curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, $ssl_verifypeer );
			// send a user:password
			if ( ! empty( $userpwd ) ) {
				curl_setopt( $ch, CURLOPT_USERPWD, $userpwd );
			}
			// optionally POST
			if ( $method == 'POST' ) {
				curl_setopt( $ch, CURLOPT_POST, true );
			}
			// send content of tag
			if ( ! empty( $content ) ) {
				curl_setopt( $ch, CURLOPT_POSTFIELDS, $content );
			}
			// fetch remote contents
			if ( false === ( $response = curl_exec( $ch ) ) )	{
				// if we get an error, use that
				$error = curl_error( $ch );						
			}
			// close the resource
			curl_close( $ch );

			if ( $response ){
				if ( $selector || $remove ){
					// include phpQuery	
					include_once( 'inc/phpQuery.php' );
					// filter the content
					$response = apply_filters( 'remote_content_shortcode_phpQuery', $response, $url, $selector, $remove );
					// load the response HTML DOM
					phpQuery::newDocument( $response );
					// $remove defaults to false
					if ( $remove ) {
						// remove() the elements
						pq( $remove )->remove();
					}
					// use a CSS selector or default to everything
					$response = pq( $selector );
				}

				// perform a regex find and replace
				if ( $find ) {
					$response = preg_replace( $find, $replace | '', $response );
				}

				// strip the tags
				if ( $is_strip_tags ) {
					$response = strip_tags( $response );
				}

				// HTML encode the response
				if ( $is_htmlentities ) {
					$response = htmlentities( $response );
				}
			} else {
				// send back the error unmodified so we can debug
				$response = $error;
			}

			// Cache the result based on the TTL
			if ( $is_cache ) {
				wp_cache_set( $key, $response, $group, $cache_ttl );
			}
		}
		
		// filter the response
		return apply_filters( 'remote_content_shortcode_return', $response, $url );
	}

	private function quote_replace( $input ){
		if ( ! $input ) return false;
		return str_replace( '%QUOT%', '"', strval( $input ) );
	}
}

// init the class/shortcode
RemoteContentShortcode::init();

endif; //class exists

You may also like...

3 Responses

  1. Alf says:

    Great plugin! Is ti possibile to get HTML which is loaded by Javascript as well?

    • Justin Silver says:

      The content is loaded by curl and parsed by PHP so unfortunately there is no Javascript engine to load any additional HTML.

  2. Mike iLL says:

    This is really sweet, man. I like the code formatting. Great plugin and great post.

Leave a Reply

Your email address will not be published. Required fields are marked *