* @copyright Copyright (c) 2018, Pixelative
* @since 1.0.0
*/
class Amp_WP_IMG_Component extends Amp_WP_Component_Base implements Amp_WP_Component_Interface {
/**
* Constructor.
*
* @since 1.0.0
*/
public function head() {
if ( ! amp_wp_is_customize_preview() ) {
add_filter( 'post_thumbnail_html', array( $this, 'transform_image_tag_to_amp' ) );
add_filter( 'get_avatar', array( $this, 'transform_image_tag_to_amp' ) );
}
}
/**
* Contract implementation
*
* @since 1.0.0
*
* @return array
*/
public function config() {
return array(
'scripts' => array(
'amp-anim' => 'https://cdn.ampproject.org/v0/amp-anim-0.1.js',
),
);
}
/**
* Transform
tags to the or tags
*
* @param Amp_WP_Html_Util $instance
*
* @return Amp_WP_Html_Util
* @since 1.0.0
*/
public function transform( Amp_WP_Html_Util $instance ) {
// Get all img tags.
$elements = $instance->getElementsByTagName( 'img' );
/**
* Has all img tags from DOMElement
*
* @var DOMElement $element
*/
$nodes_count = $elements->length;
if ( $nodes_count ) {
for ( $i = $nodes_count - 1; $i >= 0; $i -- ) {
$element = $elements->item( $i );
if ( $this->is_animated_image_element( $element ) ) {
$this->enable_enqueue_scripts = true;
$tag_name = 'amp-anim';
} else {
$tag_name = 'amp-img';
}
$attributes = $instance->filter_attributes( $instance->get_node_attributes( $element ) );
$attributes = $this->modify_attributes( $attributes );
$attributes = $this->filter_attributes( $attributes, $tag_name );
$instance->replace_node( $element, $tag_name, $attributes );
}
}
return $instance;
}
/**
* Append or modify amp-img|amp-anim attributes
*
* @param array $attributes
*
* @since 1.0.0
*
* @return array
*/
protected function modify_attributes( $attributes ) {
if ( ! isset( $attributes['class'] ) ) {
$attributes['class'] = '';
}
$attributes['class'] .= ' amp-image-tag';
if ( ! isset( $attributes['width'] ) || ! isset( $attributes['height'] ) ) {
if ( isset( $attributes['src'] ) ) {
$dim = $this->get_image_dimension( $attributes['src'] );
if ( $dim ) {
$attributes['width'] = $dim[0];
$attributes['height'] = $dim[1];
}
}
}
return $this->enforce_sizes_attribute( $attributes );
}
/**
* Filter amp-img | amp-anim attributes list
*
* To-do list all valid amp attributes for amp-img & amp-anim
*
* @param array $attributes
* @param string $tag_name
*
* @since 1.0.0
*
* @return array
*/
protected function filter_attributes( $attributes, $tag_name ) {
$valid_atts = array(
'src',
'height',
'width',
'class',
'alt',
'sizes',
'on',
);
return amp_wp_filter_attributes( $attributes, $valid_atts, $tag_name );
}
/**
* Detect is given
element is animation
*
* @param DOMElement $element
element object.
*
* @since 1.0.0
*
* @return bool true if image was animated or false otherwise
*/
protected function is_animated_image_element( $element ) {
// Get src attribute.
$src = $element->attributes->getNamedItem( 'src' );
if ( $src && isset( $src->value ) ) {
return $this->is_animated_image_url( $src->value );
}
$class = $element->attributes->getNamedItem( 'class' );
if ( $class && isset( $class->value ) ) {
// Image will be animated if it has a animated class.
return preg_match( '/\b animated-img \b/ix', $class->value );
}
return false;
}
/**
* Generate amp-image tag of attachment post
*
* @param WP_Post $attachment attachment post.
*
* @since 1.0.0
*
* @return string
*/
public function print_attachment_image( $attachment ) {
return $this->get_attachment_image( $attachment->ID );
}
/**
* Get an HTML img element representing an image attachment
*
* todo fix default size
*
* @see wp_get_attachment_image for more documentation
*
* @param int $attachment_id Image attachment ID.
* @param string|array $size Optional. Image size. Accepts any valid image size, or an array of width
* and height values in pixels (in that order). Default 'full'.
* @param bool $icon Optional. Whether the image should be treated as an icon. Default false.
* @param string|array $attr Optional. Attributes for the image markup. Default empty.
*
* @since 1.0.0
*
* @return string HTML img element or empty string on failure.
*/
public function get_attachment_image( $attachment_id, $size = 'full', $icon = false, $attr = '' ) {
$html = '';
$image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
if ( $image ) {
list( $src, $width, $height ) = $image;
$hwstring = image_hwstring( $width, $height );
$size_class = $size;
if ( is_array( $size_class ) ) {
$size_class = join( 'x', $size_class );
}
$attachment = get_post( $attachment_id );
$default_attr = array(
'src' => $src,
'class' => "attachment-$size_class size-$size_class",
'alt' => trim( wp_strip_all_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ), // Use Alt field first.
);
if ( empty( $default_attr['alt'] ) ) {
$default_attr['alt'] = trim( wp_strip_all_tags( $attachment->post_excerpt ) );
} // If not, Use the Caption
if ( empty( $default_attr['alt'] ) ) {
$default_attr['alt'] = trim( wp_strip_all_tags( $attachment->post_title ) );
} // Finally, use the title
$attr = wp_parse_args( $attr, $default_attr );
// Generate 'srcset' and 'sizes' if not already present.
if ( empty( $attr['srcset'] ) ) {
$image_meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
if ( is_array( $image_meta ) ) {
$size_array = array( absint( $width ), absint( $height ) );
$srcset = wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id );
$sizes = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id );
if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) {
$attr['srcset'] = $srcset;
if ( empty( $attr['sizes'] ) ) {
$attr['sizes'] = $sizes;
}
}
}
}
/**
* Filters the list of attachment image attributes.
*
* @since 1.0.0
*
* @param array $attr Attributes for the image markup.
* @param WP_Post $attachment Image attachment post.
* @param string|array $size Requested size. Image size or array of width and height values
* (in that order). Default 'thumbnail'.
*/
$attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size );
$attr = $this->filter_attributes( $attr, 'amp-img' );
$attr = array_map( 'esc_attr', $attr );
$html = rtrim( " $value ) {
$html .= " $name=" . '"' . $value . '"';
}
$html .= '>';
}
return $html;
}
/**
* This is our workaround to enforce max sizing with layout=responsive.
*
* We want elements to not grow beyond their width and shrink to fill the screen on viewports smaller than their
* width.
*
* See https://github.com/ampproject/amphtml/issues/1280#issuecomment-171533526
* See https://github.com/Automattic/amp-wp/issues/101
*
* @since 1.0.0
*
* @copyright credit goes to automattic amp - github.com/Automattic/amp-wp
*/
public function enforce_sizes_attribute( $attributes ) {
if ( ! isset( $attributes['width'], $attributes['height'] ) ) {
return $attributes;
}
$max_width = $attributes['width'];
$_max_width = amp_wp_get_container_width();
if ( ( $_max_width ) && $max_width > $_max_width ) {
$max_width = $_max_width;
}
$attributes['sizes'] = sprintf( '(min-width: %1$dpx) %1$dpx, 100vw', absint( $max_width ) );
return $attributes;
}
/**
* Fetch remote image dimension
*
* @param string $url Image URL.
*
* @see github.com/tommoor/fastimage
* @since 1.0.0
* @return bool|array array of width & height on success or false on error.
* array {
* 0 => image width
* 1 => image height
* }
*/
public function fetch_image_dimension( $url ) {
if ( ! class_exists( 'FastImage' ) ) {
require AMP_WP_DIR_PATH . 'includes/Fastimage.php';
}
$fast_image = new FastImage( $url );
return $fast_image->getSize();
}
/**
* Get remote image dimension
*
* @param string $url
*
* @since 1.0.0
*
* @return bool|array array on success or false on error. @see fetch_image_dimension for more doc
*/
public function get_image_dimension( $url ) {
$url = $this->normalize_url( $url );
if ( ! ( $url ) ) {
return false; }
$url_hash = md5( $url );
$dimension = get_transient( 'amp_wp_dimension_' . $url_hash );
if ( ! ( $dimension ) ) {
if ( $dimension = $this->fetch_image_dimension( $url ) ) {
set_transient( 'amp_wp_dimension_' . $url_hash, $dimension, HOUR_IN_SECONDS );
} else {
$dimension = array(
738, // fallback for width.
400, // fallback for height.
);
}
}
return $dimension;
}
/**
* @param string $url
*
* @since 1.0.0
* @copyright credit goes to automattic amp - github.com/Automattic/amp-wp
*
* @return bool|string url string on success
*/
public static function normalize_url( $url ) {
if ( empty( $url ) ) {
return false; }
if ( 0 === strpos( $url, 'data:' ) ) {
return false; }
if ( 0 === strpos( $url, '//' ) ) {
return set_url_scheme( $url, 'http' ); }
$parsed = wp_parse_url( $url );
if ( ! isset( $parsed['host'] ) ) {
$path = '';
if ( isset( $parsed['path'] ) ) {
$path .= $parsed['path']; }
if ( isset( $parsed['query'] ) ) {
$path .= '?' . $parsed['query']; }
$url = site_url( $path );
}
return $url;
}
/**
* Change
tag to
*
* @param string $html Has img tag strings.
*
* @since 1.0.0
*/
public function transform_image_tag_to_amp( $html ) {
return preg_replace( '/<\s*img\s+/i', 'modify_attributes( $image );
$is_anim = $this->is_animated_image_url( $image['src'] );
if ( $is_anim ) {
$this->enable_enqueue_scripts = true;
$tag_name = 'amp-anim';
} else {
$tag_name = 'amp-img';
}
$instance = new Amp_WP_Html_Util();
$node = $instance->create_node( $tag_name, $image );
$output = $instance->saveXML( $node, LIBXML_NOEMPTYTAG );
if ( $echo ) {
echo $output;
} else {
return $output;
}
}
/**
* Handy function to check image url is animated image or not
*
* @param string $url
*
* @since 1.0.0
*
* @return bool
*/
public function is_animated_image_url( $url = '' ) {
$url = amp_wp_remove_query_string( $url );
return strtolower( substr( $url, - 4 ) ) === '.gif';
}
}
// Register component class.
amp_wp_register_component( 'Amp_WP_IMG_Component' );
if ( ! function_exists( 'amp_wp_create_image' ) ) {
/**
* Print AMP image from url
*
* @param $image
* @param bool $echo if true print image instead to return string.
*
* @since 1.0.0
*
* @return string
*/
function amp_wp_create_image( $image, $echo = true ) {
/**
* @var Amp_WP_IMG_Component $img_component
*/
$img_component = Amp_WP_Component::instance( 'Amp_WP_IMG_Component' );
if ( $echo ) {
echo $img_component->create_image( $image );
} else {
return $img_component->create_image( $image );
}
}
}