Skip to content

Handling Media

Weston Ruter edited this page Dec 15, 2017 · 7 revisions

By default, the plugin attempts to gracefully handle the following media elements in your content:

  • images (converted from img => amp-img or amp-anim)
  • videos (converted from video => amp-video; Note: Flash is not supported)
  • audio (converted from audio => amp-audio)
  • iframes (converted from iframes => amp-iframes)
  • YouTube, Instagram, Twitter, and Vine oEmbeds and shortcodes (converted from the embed to the matching amp- component)

For additional media content such as custom shortcodes, oEmbeds or manually inserted embeds, ads, etc. there are several customization options available and outlined below.

Do Nothing

If your embeds/media use standard iframes, you can choose to do nothing and let the plugin handle things. They should "just work" in most cases.

the_content filter

All existing hooks on the_content will continue to work. This can be a good or bad thing. Good, because existing plugin integrations will continue to work. Bad, because not all added content may make sense in an AMP context.

You can add additional callbacks to the_content filter to output additional content as needed. Use the is_amp_endpoint() function to check if an AMP version of a post is being viewed. However, we recommend using an Embed Handler instead.

Caveat: with this method, if you add a custom component that requires inclusion of a script, you will need to add that script manually to the template using the amp_post_template_head action.

Update Existing Shortcodes

In your existing shortcode or oEmbed callbacks, you can branch using the is_amp_endpoint() and output customized content for AMP content.

The same caveat about scripts for custom AMP components applies.

Custom Embed Handler

Embed Handlers are helper classes to inject AMP-specific content for your oEmbeds and shortcodes.

Embed Handlers register the embeds they handle using standard WordPress functions such as add_shortcode. For working examples, check out the existing implementations for Instagram, Twitter, etc. as guides to build your own.

While the primary purpose of Embed Handlers is for use with embeds, you can use them for adding AMP-specific the_content callbacks as well.

Step 1: Build the Embed Handler

Your Embed Handler class needs to extend the AMP_Base_Embed_Handler class.

Note: make sure to set proper priorities or remove existing callbacks for your regular content.

In classes/class-amp-related-posts-embed.php:

class XYZ_AMP_Related_Posts_Embed extends AMP_Base_Embed_Handler {
	public function register_embed() {
		// If we have an existing callback we are overriding, remove it.
		remove_filter( 'the_content', 'xyz_add_related_posts' );

		// Add our new callback
		add_filter( 'the_content', array( $this, 'add_related_posts' ) );
	}

	public function unregister_embed() {
		// Let's clean up after ourselves, just in case.
		add_filter( 'the_content', 'xyz_add_related_posts' );
		remove_filter( 'the_content', array( $this, 'add_related_posts' ) );
	}

	public function get_scripts() {
        return array(
            'amp-mustache' => 'https://cdn.ampproject.org/v0/amp-mustache-0.1.js',
            'amp-list' => 'https://cdn.ampproject.org/v0/amp-list-0.1.js',
        );
	}

	public function add_related_posts( $content ) {
		// See https://github.com/ampproject/amphtml/blob/master/extensions/amp-list/amp-list.md for details on amp-list
		$related_posts_list = '
<amp-list src="https://data.com/articles.json?ref=CANONICAL_URL" width=300 height=200 layout=responsive>
	<template type="amp-mustache">
		<div>
			<amp-img src="{{imageUrl}}" width=50 height=50></amp-img>
			{{title}}
		</div>
	</template>
	<div overflow role=button aria-label="Show more" class="list-overflow">
		Show more
	</div>
</amp-list>';

		$content .= $related_posts_list;

		return $content;
	}
}

Step 2: Load the Embed Handler

add_filter( 'amp_content_embed_handlers', 'xyz_amp_add_related_embed', 10, 2 );

function xyz_amp_add_related_embed( $embed_handler_classes, $post ) {
	require_once( dirname( __FILE__ ) . '/classes/class-amp-related-posts-embed.php' );
	$embed_handler_classes[ 'XYZ_AMP_Related_Posts_Embed' ] = array();
	return $embed_handler_classes;
}

Custom Sanitizer

The name "sanitizer" is a bit of a misnomer. These are primarily used internally in the plugin to make your site's content compatible with the amp spec. This involves stripping unsupported tags and attributes and transforming media elements to their matching amp version (e.g. img => amp-img).

Sanitizers are pretty versatile and, unlike Embed Handlers -- which work with HTML content as a string -- they can be used to manipulate your post's AMP content using PHP's DOM library. We've included an example that shows you how to use a custom sanitizer to inject ads into your content. You can, of course, do many other things such as add related content.

Step 1: Build the Sanitizer

Your sanitizer needs to extend the AMP_Base_Sanitizer. In classes/class-ad-inject-sanitizer.php:

class XYZ_AMP_Ad_Injection_Sanitizer extends AMP_Base_Sanitizer {
	public function sanitize() {
		$body = $this->get_body_node();

		// Build our amp-ad tag
		$ad_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-ad', array(
			// Taken from example at https://github.com/ampproject/amphtml/blob/master/builtins/amp-ad.md
			'width' => 300,
			'height' => 250,
			'type' => 'a9',
			'data-aax_size' => '300x250',
			'data-aax_pubname' => 'test123',
			'data-aax_src' => '302',
		) );

		// Add a placeholder to show while loading
		$fallback_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-img', array(
			'placeholder' => '',
			'layout' => 'fill',
			'src' => 'https://placehold.it/300X250',
		) );
		$ad_node->appendChild( $fallback_node );

		// If we have a lot of paragraphs, insert before the 4th one.
		// Otherwise, add it to the end.
		$p_nodes = $body->getElementsByTagName( 'p' );
		if ( $p_nodes->length > 6 ) {
			$p_nodes->item( 4 )->parentNode->insertBefore( $ad_node, $p_nodes->item( 4 ));
		} else {
			$body->appendChild( $ad_node );
		}
	}
}

Step 2: Load the Sanitizer

add_filter( 'amp_content_sanitizers', 'xyz_amp_add_ad_sanitizer', 10, 2 );

function xyz_amp_add_ad_sanitizer( $sanitizer_classes, $post ) {
	require_once( dirname( __FILE__ ) . '/classes/class-ad-inject-sanitizer.php' );
	$sanitizer_classes[ 'XYZ_AMP_Ad_Injection_Sanitizer' ] = array(); // the array can be used to pass args to your sanitizer and accessed within the class via `$this->args`
	return $sanitizer_classes;
}

Extracting Image Dimensions

AMP requires images to have width and height attributes. When these attributes aren't present in an image tag, AMP-WP will attempt to determine them for the image.

Extraction Methods

Concurrent Dimension Extraction - PHP 5.3+ and cURL

If you're using PHP 5.3+ and have the cURL extension installed, AMP-WP will attempt to determine dimensions for all images that need them concurrently. Only the minimum number of bytes required to determine the dimensions for a given image type are retrieved. Dimensions are then cached via transients for subsequent requests. This is the fastest and therefore recommended method.

Sequential Dimension Extraction - PHP 5.2 or no cURL

If you're using PHP 5.2 or do not have the cURL extension installed, AMP-WP will attempt to determine image dimensions sequentially. Only the minimum number of bytes required to determine the dimensions for a given image type are retrieved, but the time it takes to retrieve each image's dimensions sequentially can still add up. Dimensions are then cached via transients for subsequent requests.

Custom Dimension Extraction

You can implement your own image dimension extraction method by adding a callback to the amp_extract_image_dimensions_batch filter.

amp_extract_image_dimensions_batch callback functions take a single argument, $dimensions by convention, which is a map/array of image urls to either an array containing the dimensions of the image at the url (if another callback for the filter was able to determine them), or false if the dimensions have yet to be determined, e.g.

array(
    'http://i0.wp.com/placehold.it/350x150.png' => array(
        'width' => 350,
        'height' => 150,
     ),
     'http://i0.wp.com/placehold.it/1024x768.png' => false,
);

Your custom dimension extraction callback would iterate through the mappings contained in this single argument, determining dimensions via your custom method for all image url keys whose values are not arrays of dimensions, e.g.

function my_custom_dimension_extraction_callback( $dimensions ) {
    foreach ( $dimensions as $url => $value ) {
        // Skip if dimensions have already been determined for this image.
        if ( is_array( $value ) ) {
            continue;                
        }
        $width = <YOUR CUSTOM CODE TO DETERMINE WIDTH>
        $height = <YOUR CUSTOM CODE TO DETERMINE HEIGHT>
        $dimensions[ $url ] = array(
            'width' => $width,
            'height' => $height,
         );
    }

    return $dimensions;

Your callback needs to return $dimensions so that the value either cascades to the next callback that was added to the amp_extract_image_dimensions_batch filter or is returned to the apply_filter() call (if there are no more unprocessed callbacks).

The default callback provided by WP-AMP described above, extract_by_downloading_images, will fire unless explicitly removed, so be sure to remove it from the callback chain if you don't want it to, e.g.

	remove_filter( 'amp_extract_image_dimensions_batch', array( 'AMP_Image_Dimension_Extractor', 'extract_by_downloading_images' ), 999, 1 );

Note that if you previously added a custom dimension extraction callback to the amp_extract_image_dimensions filter, you need to update it to hook into the amp_extract_image_dimensions_batch filter instead and iterate over the key value pairs in the single argument as per the example above.