Skip to content

Instantly share code, notes, and snippets.

@crstauf
Last active March 24, 2023 21:55
Show Gist options
  • Save crstauf/030df6bd6c436620e96cb92a44c9772f to your computer and use it in GitHub Desktop.
Save crstauf/030df6bd6c436620e96cb92a44c9772f to your computer and use it in GitHub Desktop.
A PHP class used to generate an <img> tag, specifically for use within WordPress sites (uploaded images, theme images, and external images) and the awesome lazysizes script (but not necessary).
<?php
/**
* Classes for image tag elements.
*
* @version 0.0.7.0
* @link https://gist.github.com/crstauf/030df6bd6c436620e96cb92a44c9772f
*/
if ( !defined( 'ABSPATH' ) || !function_exists( 'add_filter' ) ) {
header( 'Status: 403 Forbidden' );
header( 'HTTP/1.1 403 Forbidden' );
exit;
}
/**
* Abstract object for image tag data.
*
* @link https://github.com/aFarkas/lazysizes Preferred lazy loading script.
* @todo Add image primary color detection and use.
*/
abstract class image_tag {
const VERSION = '0.0.8';
const DATAURI = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
/** @var mixed $_source Image source. **/
protected $_source;
/** @var bool $_noscript Switch to enable noscript tag. **/
protected $_noscript = null;
/** @var bool $_lazyload Switch to enable lazy load. **/
protected $_lazyload = true;
/** @var string $_orientation Orientation of the image. **/
protected $_orientation = 'unknown';
/** @var float $_ratio Ratio of the image (height divided by width). **/
protected $_ratio = 0;
/**
* @var string $id Image "id" atribute.
* @var string $alt Image "alt" attribute.
* @var string $src Image "src" attribute.
* @var string $class Image "class" attribute.
* @var string $sizes Image "sizes" attribute.
* @var string $style Image "style" attribute.
* @var string $title Image "title" attribute.
* @var int|string $width Image "width" attribute.
* @var int|string $height Image "height" attribute.
* @var string $srcet Image "srcset" attribute.
* @var array $data Image "data" attributes.
* array(
* 'src' => string (data-src attribute)
* 'sizes' => string (data-sizes attribute)
* 'expand' => string (data-expand attribute)
* 'srcset' => string (data-srcset attribute)
* )
*/
var $id,
$alt,
$src,
$class,
$sizes,
$style,
$title,
$width,
$height,
$srcset,
$data = array(
'src' => null,
'sizes' => null,
'expand' => null,
'srcset' => null,
);
/** @var image_tag $noscript Noscript image object. */
var $noscript;
/**
* Create.
*
* @param mixed $source Image source.
* @param array $args Image object arguments.
*
* @return image_tag
*/
public static function create( $source, $args = array() ) {
$cache_key = $source;
if ( empty( $args['skip_cache'] ) )
$cache = wp_cache_get( $cache_key, __CLASS__, false, $found );
if ( !empty( $found ) )
return $cache;
$self = new static( $source, $args );
wp_cache_add( $cache_key, $self, __CLASS__ );
return $self;
}
/**
* Construct.
*
* @param mixed $source Image source.
* @param array $args Image object arguments.
*
* @uses image_tag::_maybe_create_noscript_object()
* @uses image_tag::get_attributes()
*/
protected function __construct( $source, $args = array() ) {
$this->_source = $this->src = $source;
array_key_exists( 'noscript', $args ) && $this->_noscript = $args['noscript'];
array_key_exists( 'lazyload', $args ) && $this->_lazyload = $args['lazyload'];
if (
is_null( $this->_noscript )
&& $this->_lazyload
)
$this->_noscript = true;
$this->_maybe_create_noscript_object( $source, $args );
foreach ( array_keys( $this->get_attributes() ) as $attribute )
if (
array_key_exists( $attribute, $args )
&& property_exists( $this, $attribute )
)
$this->$attribute = $args[$attribute];
if ( array_key_exists( 'data', $args ) )
$this->data = wp_parse_args( $args['data'], $this->data );
}
/**
* Print the HTML for the `img` tag.
*
* @uses image_tag::get_html()
*
* @return string
*/
function __toString() {
return $this->get_html();
}
/**
* Get image property.
*
* @param string $property Property name.
*
* @uses image_tag::get_html()
*
* @return string
*/
function get( $property ) {
if ( 'html' === $property )
return $this->get_html();
return property_exists( $this, $property )
? $this->$property
: null;
}
/**
* Get image attributes.
*
* @return array
*/
protected function get_attributes() {
return array(
'id' => $this->id,
'alt' => $this->alt,
'src' => $this->src,
'class' => $this->class,
'sizes' => $this->sizes,
'style' => $this->style,
'title' => $this->title,
'width' => $this->width,
'height' => $this->height,
'srcset' => $this->srcset,
'data-src' => $this->data['src'],
'data-sizes' => $this->data['sizes'],
'data-expand' => $this->data['expand'],
'data-srcset' => $this->data['srcset'],
);
}
/**
* Maybe create noscript object.
*
* @param string $source Image source.
* @param array $args Image object arguments.
*
* @uses get_image_tag_object()
*/
protected function _maybe_create_noscript_object( $source, $args ) {
if (
!$this->_noscript
|| !empty( $this->noscript )
)
return;
$args['noscript'] = $args['lazyload'] = false;
$this->noscript = get_image_tag_object( $source, $args );
}
/**
* Get image tag.
*
* @uses image_tag::set_orientation()
* @uses image_tag::maybe_set_lazyload_attributes()
* @uses image_tag::get_attributes()
*
* @return string <img> tag.
*/
function get_html() {
$attributes = array();
$this->set_orientation();
$this->set_ratio();
$this->maybe_set_lazyload_attributes();
foreach ( array_filter( $this->get_attributes() ) as $attribute => $value )
$attributes[] = $attribute . '="' . $value . '"';
return '<img ' . implode( ' ', $attributes ) . ' />' . ( !empty( $this->noscript ) ? '<noscript>' . $this->noscript . '</noscript>' : '' );
}
/**
* Print image tag.
*
* @uses image_tag::get_html()
*/
function the_html() {
echo $this->get_html();
}
/**
* Set attributes if lazyloading.
*/
protected function maybe_set_lazyload_attributes() {
if ( !$this->_lazyload )
return;
if ( !empty( $this->srcset ) ) {
$this->data['srcset'] = $this->srcset;
$this->srcset = null;
} else
$this->data['src'] = $this->src;
if ( empty( $this->sizes ) )
$this->data['sizes'] = 'auto';
$this->src = $this::DATAURI;
$this->class .= ' lazyload hide-if-no-js';
}
/**
* Determine and store image orientation.
*/
protected function set_orientation() {
if ( $this->width > $this->height ) $this->_orientation = 'landscape';
else if ( $this->width < $this->height ) $this->_orientation = 'portrait';
else if ( $this->width === $this->height ) $this->_orientation = 'square';
}
/**
* Determine and store image ratio (height divided by width).
*/
protected function set_ratio() {
$this->_ratio = !empty( $this->width )
? $this->height / $this->width
: 0;
}
}
/*
######## ## ## ######## ######## ######## ## ## ### ##
## ## ## ## ## ## ## ### ## ## ## ##
## ## ## ## ## ## ## #### ## ## ## ##
###### ### ## ###### ######## ## ## ## ## ## ##
## ## ## ## ## ## ## ## #### ######### ##
## ## ## ## ## ## ## ## ### ## ## ##
######## ## ## ## ######## ## ## ## ## ## ## ########
*/
/**
* External image handler.
*/
class image_tag__external extends image_tag {}
/*
## ## ####### ######## ######## ######## ######## ######## ###### ######
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ######## ## ## ######## ######## ###### ###### ######
## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
### ### ####### ## ## ######## ## ## ## ######## ###### ######
*/
/**
* WordPress attachments handler.
*/
class image_tag__wp_attachment extends image_tag {
/** @var int $_source_id Attachment object ID. */
protected $_source_id = null;
/** @var WP_Post $_post Post object of the attachment. **/
protected $_post = null;
/** @var array $sizes List of registered image sizes. **/
protected $_sizes = array();
/** @var array $sizes_data Array of _image_tag__wp_attachment_image_size objects. **/
protected $_sizes_data = array(
'__largest' => null,
'__smallest' => null,
);
/**
* Construct.
*
* @param int $source_id Attachment image object ID.
* @param array $args Image object arguments.
*
* @uses image_tag__wp_attachment::_add_size_data()
* @uses image_tag::__construct()
*/
function __construct( $source_id, $args = array() ) {
$this->_source_id = $source_id;
$this->_post = get_post( $source_id );
$this->_sizes = !empty( $args['wordpress']['image_sizes'] )
? $args['wordpress']['image_sizes']
: array_merge( get_intermediate_image_sizes(), array( 'full' ) );
$this->_sizes_data['__largest'] = new _image_tag__wp_attachment_image_size__largest;
$this->_sizes_data['__smallest'] = new _image_tag__wp_attachment_image_size__smallest;
foreach ( $this->_sizes as $size )
$this->_add_size_data( $size );
uasort( $this->_sizes_data, array( &$this, '_sort_sizes_asc' ) );
if ( 1 === count( $this->_sizes ) )
$this->src = $this->_sizes_data['__smallest']->get( 'src' );
else {
$srcset = array();
foreach ( $this->_sizes_data as $size )
$srcset[] = $size->get( 'src' ) . ' ' . $size->get( 'width' ) . 'w';
$this->srcset = implode( ', ', $srcset );
}
unset( $args['wordpress'] );
parent::__construct( $this->_sizes_data['__smallest']->get( 'src' ), $args );
}
/**
* Get and store size data.
*
* @param string $size Image size name.
*
* @see _image_tag__wp_attachment_image_size
*/
protected function _add_size_data( $size ) {
static $_class = null;
if ( is_null( $_class ) )
$_class = apply_filters( 'image_tag/_image_tag__wp_attachment_image_size', '_image_tag__wp_attachment_image_size' );
$this->_sizes_data[$size] = new $_class( $this, $size );
if ( $this->_sizes_data[$size]->get( 'width' ) > $this->_sizes_data['__largest']->get( 'width' ) )
$this->_sizes_data['__largest'] = $this->_sizes_data[$size];
if ( $this->_sizes_data[$size]->get( 'width' ) < $this->_sizes_data['__smallest']->get( 'width' ) )
$this->_sizes_data['__smallest'] = $this->_sizes_data[$size];
}
/**
* Custom sort method to sort sizes by width descending.
*
* @param _image_tag__wp_attachment_image_size $a First image size object.
* @param _image_tag__wp_attachment_image_size $b Second image size object.
*
* @uses image_tag::get()
*/
protected function _sort_sizes_asc( $a, $b ) {
return $a->get( 'width' ) > $b->get( 'width' );
}
/**
* Maybe create noscript object.
*
* @param int $source Image attachment object ID.
* @param array $args Image object arguments.
*
* @uses imagee_tag::_maybe_create_noscript_object()
*/
protected function _maybe_create_noscript_object( $source, $args ) {
parent::_maybe_create_noscript_object( $this->_source_id, $args );
}
/**
* Get image sizes (except magicals).
*
* @return array of _image_tag__wp_attachment_image_size objects.
*/
function get_sizes_data() {
return array_filter(
$this->_sizes_data,
function ( $k ) {
return '__' !== substr( $k, 0, 2 );
},
ARRAY_FILTER_USE_KEY
);
}
/**
* Get largest image size.
*
* @return _image_tag__wp_attachment_image_size__largest
*/
function get_largest_size() {
return $this->_sizes_data['__largest'];
}
/**
* Get smallest image size.
*
* @return _image_tag__wp_attachment_image_size__smallest
*/
function get_smallest_size() {
return $this->_sizes_data['__smallest'];
}
/**
* Check if image has size.
*
* @param string $size Image size name.
*
* @return bool
*/
function has_size( $size ) {
return array_key_exists( $size, $this->get_sizes_data() );
}
/**
* Get image size object.
*
* @param string $size Image size name.
*
* @uses image_tag__wp_attachment::has_size()
* @uses image_tag__wp_attachment::get_sizes_data()
*
* @return _image_tag__wp_attachment_image_size|null
*/
function get_size( $size ) {
return $this->has_size( $size )
? $this->get_sizes_data()[$size]
: null;
}
/**
* Get image attachment object ID.
*/
function get_attachment_id() {
return $this->_source_id;
}
/**
* Get image attachment metdata.
*/
function get_metadata() {
return wp_get_attachment_metadata( $this->get_attachment_id() );
}
/**
* Get image tag.
*
* @return string <img> tag.
*/
function get_html() {
do_action_ref_array( 'imadge_tag/before_output', array( $this->_source, &$this ) );
$this->src = $this->get_smallest_size()->get( 'src' );
return parent::get_html();
}
/**
* Check if image has mode color.
*
* @uses $this::get_mode_color()
*
* @return bool
*/
function has_mode_color() {
return !empty( $this->get_mode_color() );
}
function get_mode_color() {
if ( !empty( get_post_meta( $this->get_attachment_id(), '_mode_color', true ) ) )
return get_post_meta( $this->get_attachment_id(), '_mode_color', true );
$class_filename = apply_filters( 'image_tag/get_mode_color/filepath', __DIR__ . '/class-get-image-most-common-colors.php' );
list( $class_name, $function_name ) = apply_filters( 'image_tag/get_mode_color/function', array( 'GetImageMostCommonColors', 'Get_Colors' ) );
if ( !file_exists( $class_filename ) )
return false;
require_once $class_filename;
if (
!empty( $class_name )
&& class_exists( $class_name )
) {
$class = new $class_name;
if ( !is_callable( array( $class, $function_name ) ) )
return false;
$callable = array( $class, $function_name );
} else if ( empty( $class_name ) )
$callable = $function_name;
if ( empty( $callable ) )
return false;
$colors = call_user_func( $callable, $this->get_largest_size()->get( 'path' ) );
if ( empty( $colors ) )
return false;
$colors = array_keys( $colors );
$color = '#' . array_shift( $colors );
add_post_meta( $this->get_attachment_id(), '_mode_color', $color );
return $color;
}
}
/*
## ## ######## #### ## ## ### ###### ######## ###### #### ######## ########
## ## ## ## ## ## ### ### ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## #### #### ## ## ## ## ## ## ## ##
## ## ## ######## ## ## ### ## ## ## ## #### ###### ###### ## ## ######
## ## ## ## ## ## ## ######### ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
### ### ## #### ## ## ## ## ###### ######## ###### #### ######## ########
*/
/**
* Object for WordPress attachment image size.
*/
class _image_tag__wp_attachment_image_size {
/**
* @var string $src Image size URI.
* @var int $width Image size width.
* @var int $height Image size height.
* @var string $orientation Image size orientation.
*/
protected $src = null,
$path = null,
$width = null,
$height = null,
$orientation = null;
/**
* Get the properties of the image size.
*
* @param int $source_id Source ID.
* @param string $size Size name.
*/
function __construct( image_tag__wp_attachment &$image, $size ) {
$attachment = wp_get_attachment_image_src( $image->get_attachment_id(), $size );
if ( empty( $attachment ) )
return;
list(
$this->src,
$this->width,
$this->height,
,
) = $attachment;
$metadata = $image->get_metadata();
$upload_dir = wp_upload_dir();
if ( array_key_exists( $size, $metadata['sizes'] ) )
$this->path = $upload_dir['basedir'] . '/' . trailingslashit( dirname( $metadata['file'] ) ) . $metadata['sizes'][$size]['file'];
else if ( file_exists( $upload_dir['basedir'] . '/' . $metadata['file'] ) )
$this->path = $upload_dir['basedir'] . '/' . $metadata['file'];
if ( $this->width > $this->height )
$this->orientation = 'landscape';
else if ( $this->width < $this->height )
$this->orientation = 'portrait';
else if ( $this->width == $this->height )
$this->orientation = 'square';
}
/**
* Get image size property.
*
* @param string $prop Property name.
*
* @return string|int
*/
function get( $prop ) {
return property_exists( $this, $prop )
? $this->$prop
: null;
}
/**
* Check if size has image URI.
*/
function exists() {
return !empty( $this->get( 'src' ) );
}
}
/**
* Special class for largest WordPress image size.
*/
class _image_tag__wp_attachment_image_size__largest extends _image_tag__wp_attachment_image_size {
/** Construct. */
function __construct() { $this->width = 0; }
}
/**
* Special class for smalleest WordPress image size.
*/
class _image_tag__wp_attachment_image_size__smallest extends _image_tag__wp_attachment_image_size {
/** Construct. */
function __construct() { $this->width = 9999; }
}
/*
######## ## ### ###### ######## ## ## ####### ## ######## ######## ########
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
######## ## ## ## ## ###### ######### ## ## ## ## ## ###### ########
## ## ######### ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ######## ## ## ###### ######## ## ## ####### ######## ######## ######## ## ##
*/
/**
* Placeholder.com (formerly placehold.it) handler.
* @link https://placeholder.com
*/
class image_tag__placeholder extends image_tag {
/**
* Construct.
*
* @param string $source Only 'placeholder'.
* @param array $args Image object arguments.
*/
function __construct( $source = 'placeholder', $args = array() ) {
$source = 'http://via.placeholder.com/';
!empty( $args['width'] ) && empty( $args['placeholder']['width'] ) && $args['placeholder']['width'] = $args['width'];
!empty( $args['height'] ) && empty( $args['placeholder']['height'] ) && $args['placeholder']['height'] = $args['height'];
// add width dimension
$source .= $args['placeholder']['width'];
// add height dimension
$source .= 'x' . $args['placeholder']['height'];
// add background color
if ( array_key_exists( 'color-bg', $args['placeholder'] ) ) {
$source .= '/' . $args['placeholder']['color-bg'];
// add text color (background color must be specified)
if ( array_key_exists( 'color-text', $args['placeholder'] ) )
$source .= '/' . $args['placeholder']['color-text'];
}
// add image format (gif, jpeg, jpg, png)
if ( array_key_exists( 'format', $args['placeholder'] ) )
$source .= '.' . $args['placeholder']['format'];
// add image text
if ( array_key_exists( 'text', $args['placeholder'] ) )
$source = add_query_arg( 'text', $args['placeholder']['text'], $source );
unset( $args['placeholder'] );
parent::__construct( $source, $args );
}
/**
* Maybe create noscript object.
*
* @param string $source "Placeholder".
* @param array $args Image object arguments.
*
* @uses imagee_tag::_maybe_create_noscript_object()
*/
protected function _maybe_create_noscript_object( $source = 'placeholder', $args ) {
parent::_maybe_create_noscript_object( 'placeholder', $args );
}
}
/*
######## #### ###### ###### ## ## ## ##
## ## ## ## ## ## ## ## ## ### ###
## ## ## ## ## ## ## #### ####
######## ## ## ###### ## ## ## ### ##
## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ##
## #### ###### ###### ####### ## ##
*/
/**
* Picsum.photos handler.
* @link https://picsum.photos
*/
class image_tag__picsum extends image_tag {
public static function create( $source, $args = array() ) {
static $_random = 1;
empty( $args['width'] ) && !empty( $args['picsum']['width'] ) && $args['width'] = $args['picsum']['width'];
empty( $args['height'] ) && !empty( $args['picsum']['height'] ) && $args['height'] = $args['picsum']['height'];
!empty( $args['width'] ) && empty( $args['picsum']['width'] ) && $args['picsum']['width'] = $args['width'];
!empty( $args['height'] ) && empty( $args['picsum']['height'] ) && $args['picsum']['height'] = $args['height'];
if ( empty( $args['picsum']['random'] ) ) {
$cache_key = md5( serialize( array( $source, $args['picsum'] ) ) );
$cache = wp_cache_get( $cache_key, __CLASS__, false, $found );
if ( empty( $args['skip_cache'] ) && $found )
return $cache;
} else
$args['picsum']['random'] = $_random++;
$self = new static( $source, $args );
if ( empty( $args['picsum']['random'] ) )
wp_cache_add( $cache_key, $self, __CLASS__ );
return $self;
}
/**
* Construct.
*
* @param string $source Only 'picsum'.
* @param array $args Image object arguments.
*/
protected function __construct( $source, $args = array() ) {
$source = 'https://picsum.photos/';
if ( !empty( $args['picsum']['gray'] ) )
$source .= 'g/';
$source .= $args['picsum']['width'];
$source .= '/' . $args['picsum']['height'];
if ( !empty( $args['picsum']['image'] ) )
$source = add_query_arg( 'image', $args['picsum']['image'], $source );
else if ( !empty( $args['picsum']['random'] ) )
$source = add_query_arg( 'random', $args['picsum']['random'], $source );
!empty( $args['picsum']['blur'] ) && $source = add_query_arg( 'blur', 1, $source );
!empty( $args['picsum']['gravity'] ) && $source = add_query_arg( 'gravity', $args['picsum']['gravity'], $source );
unset( $args['picsum'] );
parent::__construct( $source, $args );
}
/**
* Maybe create noscript object.
*
* @param int $source "Picsum".
* @param array $args Image object arguments.
*
* @uses imagee_tag::_maybe_create_noscript_object()
*/
protected function _maybe_create_noscript_object( $source = 'picsum', $args ) {
parent::_maybe_create_noscript_object( 'picsum', $args );
}
}
/**
* Get image tag object.
*
* @param int|string $source Image source.
* @param array $args Image object arguments.
*
* @return image_tag
*/
function get_image_tag_object( $source, $args = array() ) {
static $_class_names = array();
if ( 'http' === substr( $source, 0, 4 ) )
$class = 'image_tag__external';
else if (
is_numeric( $source )
&& intval( $source ) == $source
)
$class = 'image_tag__wp_attachment';
else if ( 'placeholder' === $source )
$class = 'image_tag__placeholder';
else if ( 'picsum' === $source )
$class = 'image_tag__picsum';
if ( empty( $class ) )
return;
if ( !array_key_exists( $class, $_class_names ) )
$_class_names[$class] = apply_filters( 'image_tag/' . $class, $class );
$_image_tag = $_class_names[$class]::create( $source, $args );
return $_image_tag;
}
/**
* Print image tag.
*
* @param int|string $source Image source.
* @param array $args Image object arguments.
*
* @uses get_image_tag_object()
*/
function image_tag( $source, $args = array() ) {
echo get_image_tag_object( $source, $args );
}
/**
* Debug elements for image_tag.
*/
function image_tag__debug() {
image_tag( 'https://images.unsplash.com/photo-1528485683898-7633212b3db6?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=dffe08428a166a76b5b6527aeae128ce&auto=format&fit=crop&w=4500&q=80', array( 'width' => 400 ) );
image_tag( 'placeholder', array( 'width' => 250, 'height' => 150, 'placeholder' => array( 'text' => 'Hello' ) ) );
image_tag( 'picsum', array( 'width' => 500, 'height' => 500, 'picsum' => array( 'random' => 1 ) ) );
echo ( $wp = get_image_tag_object( 11, array( 'width' => 300, 'style' => 'width: auto; height: 500px;', 'wordpress' => array( 'image_sizes' => array( 'thumbnail', 'full' ) ) ) ) );
echo $wp->get_mode_color();
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment