This repository has been archived on 2026-10-23. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

621 lines
23 KiB
PHP

<?php
// Include the class file containing methods for rounding constrained array elements.
// Here the constrained array element is the dimension of a row, group or an image in the tiled gallery.
include_once dirname( __FILE__ ) . '/math/class-constrained-array-rounding.php';
class Jetpack_Tiled_Gallery {
public function __construct() {
add_action( 'admin_init', array( $this, 'settings_api_init' ) );
add_filter( 'jetpack_gallery_types', array( $this, 'jetpack_gallery_types' ), 9 );
add_filter( 'jetpack_default_gallery_type', array( $this, 'jetpack_default_gallery_type' ) );
}
public function tiles_enabled() {
// Check the setting status
return '' != get_option( 'tiled_galleries' );
}
public function set_atts( $atts ) {
global $post;
$this->atts = shortcode_atts( array(
'order' => 'ASC',
'orderby' => 'menu_order ID',
'id' => isset( $post->ID ) ? $post->ID : 0,
'include' => '',
'exclude' => '',
'type' => '',
'grayscale' => false,
'link' => '',
), $atts );
$this->atts['id'] = (int) $this->atts['id'];
$this->float = is_rtl() ? 'right' : 'left';
// Default to rectangular is tiled galleries are checked
if ( $this->tiles_enabled() && ( ! $this->atts['type'] || 'default' == $this->atts['type'] ) )
$this->atts['type'] = 'rectangular';
if ( !$this->atts['orderby'] ) {
$this->atts['orderby'] = sanitize_sql_orderby( $this->atts['orderby'] );
if ( !$this->atts['orderby'] )
$this->atts['orderby'] = 'menu_order ID';
}
if ( 'RAND' == $this->atts['order'] )
$this->atts['orderby'] = 'none';
}
public function get_attachments() {
extract( $this->atts );
if ( !empty( $include ) ) {
$include = preg_replace( '/[^0-9,]+/', '', $include );
$_attachments = get_posts( array('include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby) );
$attachments = array();
foreach ( $_attachments as $key => $val ) {
$attachments[$val->ID] = $_attachments[$key];
}
} elseif ( 0 == $id ) {
// Should NEVER Happen but infinite_scroll_load_other_plugins_scripts means it does
// Querying with post_parent == 0 can generate stupidly memcache sets on sites with 10000's of unattached attachments as get_children puts every post in the cache.
// TODO Fix this properly
$attachments = array();
} elseif ( !empty( $exclude ) ) {
$exclude = preg_replace( '/[^0-9,]+/', '', $exclude );
$attachments = get_children( array('post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby) );
} else {
$attachments = get_children( array('post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
}
return $attachments;
}
public function get_attachment_link( $attachment_id, $orig_file ) {
if ( isset( $this->atts['link'] ) && $this->atts['link'] == 'file' )
return $orig_file;
else
return get_attachment_link( $attachment_id );
}
public static function default_scripts_and_styles() {
wp_enqueue_script( 'tiled-gallery', plugins_url( 'tiled-gallery/tiled-gallery.js', __FILE__ ), array( 'jquery' ) );
wp_enqueue_style( 'tiled-gallery', plugins_url( 'tiled-gallery/tiled-gallery.css', __FILE__ ), array(), '2012-09-21' );
}
public function gallery_shortcode( $val, $atts ) {
if ( ! empty( $val ) ) // something else is overriding post_gallery, like a custom VIP shortcode
return $val;
global $post;
$this->set_atts( $atts );
$attachments = $this->get_attachments();
if ( empty( $attachments ) )
return '';
if ( is_feed() || defined( 'IS_HTML_EMAIL' ) )
return '';
if ( method_exists( $this, $this->atts['type'] . '_talavera' ) ) {
// Enqueue styles and scripts
self::default_scripts_and_styles();
$gallery_html = call_user_func_array( array( $this, $this->atts['type'] . '_talavera' ), array( $attachments ) );
if ( $gallery_html && class_exists( 'Jetpack' ) && class_exists( 'Jetpack_Photon' ) ) {
// Tiled Galleries in Jetpack require that Photon be active.
// If it's not active, run it just on the gallery output.
if ( ! in_array( 'photon', Jetpack::get_active_modules() ) )
$gallery_html = Jetpack_Photon::filter_the_content( $gallery_html );
}
return $gallery_html;
}
return '';
}
public function rectangular_talavera( $attachments ) {
$grouper = new Jetpack_Tiled_Gallery_Grouper( $attachments );
Jetpack_Tiled_Gallery_Shape::reset_last_shape();
$output = $this->generate_carousel_container();
foreach ( $grouper->grouped_images as $row ) {
$output .= '<div class="gallery-row" style="' . esc_attr( 'width: ' . $row->width . 'px; height: ' . ( $row->height - 4 ) . 'px;' ) . '">';
foreach( $row->groups as $group ) {
$count = count( $group->images );
$output .= '<div class="gallery-group images-' . esc_attr( $count ) . '" style="' . esc_attr( 'width: ' . $group->width . 'px; height: ' . $group->height . 'px;' ) . '">';
foreach ( $group->images as $image ) {
$size = 'large';
if ( $image->width < 250 )
$size = 'small';
$image_title = $image->post_title;
$image_alt = get_post_meta( $image->ID, '_wp_attachment_image_alt', true );
$orig_file = wp_get_attachment_url( $image->ID );
$link = $this->get_attachment_link( $image->ID, $orig_file );
$img_src = add_query_arg( array( 'w' => $image->width, 'h' => $image->height ), $orig_file );
$output .= '<div class="tiled-gallery-item tiled-gallery-item-' . esc_attr( $size ) . '"><a href="' . esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' src="' . esc_url( $img_src ) . '" width="' . esc_attr( $image->width ) . '" height="' . esc_attr( $image->height ) . '" align="left" title="' . esc_attr( $image_title ) . '" alt="' . esc_attr( $image_alt ) . '" /></a>';
if ( $this->atts['grayscale'] == true ) {
$img_src_grayscale = jetpack_photon_url( $img_src, array( 'filter' => 'grayscale' ) );
$output .= '<a href="'. esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' class="grayscale" src="' . esc_url( $img_src_grayscale ) . '" width="' . esc_attr( $image->width ) . '" height="' . esc_attr( $image->height ) . '" align="left" title="' . esc_attr( $image_title ) . '" alt="' . esc_attr( $image_alt ) . '" /></a>';
}
if ( trim( $image->post_excerpt ) )
$output .= '<div class="tiled-gallery-caption">' . wptexturize( $image->post_excerpt ) . '</div>';
$output .= '</div>';
}
$output .= '</div>';
}
$output .= '</div>';
}
$output .= '</div>';
return $output;
}
public function square_talavera( $attachments ) {
$content_width = self::get_content_width();
$images_per_row = 3;
$margin = 2;
$margin_space = ( $images_per_row * $margin ) * 2;
$size = floor( ( $content_width - $margin_space ) / $images_per_row );
$remainder = count( $attachments ) % $images_per_row;
if ( $remainder > 0 ) {
$remainder_space = ( $remainder * $margin ) * 2;
$remainder_size = ceil( ( $content_width - $remainder_space - $margin ) / $remainder );
}
$output = $this->generate_carousel_container();
$c = 1;
foreach( $attachments as $image ) {
if ( $remainder > 0 && $c <= $remainder )
$img_size = $remainder_size;
else
$img_size = $size;
$orig_file = wp_get_attachment_url( $image->ID );
$link = $this->get_attachment_link( $image->ID, $orig_file );
$image_title = $image->post_title;
$img_src = add_query_arg( array( 'w' => $img_size, 'h' => $img_size, 'crop' => 1 ), $orig_file );
$output .= '<div class="tiled-gallery-item">';
$output .= '<a border="0" href="' . esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' style="' . esc_attr( 'margin: ' . $margin . 'px' ) . '" src="' . esc_url( $img_src ) . '" width=' . esc_attr( $img_size ) . ' height=' . esc_attr( $img_size ) . ' title="' . esc_attr( $image_title ) . '" /></a>';
// Grayscale effect
if ( $this->atts['grayscale'] == true ) {
$src = urlencode( $image->guid );
$output .= '<a border="0" href="' . esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' style="margin: 2px" class="grayscale" src="' . esc_url( 'http://en.wordpress.com/imgpress?url=' . urlencode( $image->guid ) . '&resize=' . $img_size . ',' . $img_size . '&filter=grayscale' ) . '" width=' . esc_attr( $img_size ) . ' height=' . esc_attr( $img_size ) . ' title="' . esc_attr( $image_title ) . '" /></a>';
}
// Captions
if ( trim( $image->post_excerpt ) )
$output .= '<div class="tiled-gallery-caption">' . wptexturize( $image->post_excerpt ) . '</div>';
$output .= '</div>';
$c ++;
}
$output .= '</div>';
return $output;
}
public function circle_talavera( $attachments ) {
return $this->square_talavera( $attachments );
}
public function rectangle_talavera( $attachments ) {
return $this->rectangular_talavera( $attachments );
}
function generate_carousel_container() {
global $post;
$html = '<div '. $this->gallery_classes() . ' data-original-width="' . esc_attr( self::get_content_width() ) . '">';
$blog_id = (int) get_current_blog_id();
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
$likes_blog_id = $blog_id;
} else {
$likes_blog_id = Jetpack_Options::get_option( 'id' );
}
$extra_data = array( 'data-carousel-extra' => array( 'blog_id' => $blog_id, 'permalink' => get_permalink( isset( $post->ID ) ? $post->ID : 0 ), 'likes_blog_id' => $likes_blog_id ) );
foreach ( (array) $extra_data as $data_key => $data_values ) {
$html = str_replace( '<div ', '<div ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' ", $html );
}
return $html;
}
function generate_carousel_image_args( $image ) {
$attachment_id = $image->ID;
$orig_file = wp_get_attachment_url( $attachment_id );
$meta = wp_get_attachment_metadata( $attachment_id );
$size = isset( $meta['width'] ) ? intval( $meta['width'] ) . ',' . intval( $meta['height'] ) : '';
$img_meta = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array();
$comments_opened = intval( comments_open( $attachment_id ) );
$medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' );
$medium_file = isset( $medium_file_info[0] ) ? $medium_file_info[0] : '';
$large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' );
$large_file = isset( $large_file_info[0] ) ? $large_file_info[0] : '';
$attachment_title = wptexturize( $image->post_title );
$attachment_desc = wpautop( wptexturize( $image->post_content ) );
// Not yet providing geo-data, need to "fuzzify" for privacy
if ( ! empty( $img_meta ) ) {
foreach ( $img_meta as $k => $v ) {
if ( 'latitude' == $k || 'longitude' == $k )
unset( $img_meta[$k] );
}
}
$img_meta = json_encode( array_map( 'strval', $img_meta ) );
$output = sprintf(
'data-attachment-id="%1$d" data-orig-file="%2$s" data-orig-size="%3$s" data-comments-opened="%4$s" data-image-meta="%5$s" data-image-title="%6$s" data-image-description="%7$s" data-medium-file="%8$s" data-large-file="%9$s"',
esc_attr( $attachment_id ),
esc_url( wp_get_attachment_url( $attachment_id ) ),
esc_attr( $size ),
esc_attr( $comments_opened ),
esc_attr( $img_meta ),
esc_attr( $attachment_title ),
esc_attr( $attachment_desc ),
esc_url( $medium_file ),
esc_url( $large_file )
);
return $output;
}
public function gallery_classes() {
$classes = 'class="tiled-gallery type-' . esc_attr( $this->atts['type'] ) . ' tiled-gallery-unresized"';
return $classes;
}
public static function gallery_already_redefined() {
global $shortcode_tags;
if ( ! isset( $shortcode_tags[ 'gallery' ] ) || $shortcode_tags[ 'gallery' ] !== 'gallery_shortcode' )
return true;
}
public static function init() {
if ( self::gallery_already_redefined() )
return;
$gallery = new Jetpack_Tiled_Gallery;
add_filter( 'post_gallery', array( $gallery, 'gallery_shortcode' ), 1001, 2 );
}
public static function get_content_width() {
$tiled_gallery_content_width = Jetpack::get_content_width();
if ( ! $tiled_gallery_content_width )
$tiled_gallery_content_width = 500;
return apply_filters( 'tiled_gallery_content_width', $tiled_gallery_content_width );
}
/**
* Media UI integration
*/
function jetpack_gallery_types( $types ) {
if ( get_option( 'tiled_galleries' ) && isset( $types['default'] ) ) {
// Tiled is set as the default, meaning that type='default'
// will still display the mosaic.
$types['thumbnails'] = $types['default'];
unset( $types['default'] );
}
$types['rectangular'] = __( 'Tiled Mosaic', 'jetpack' );
$types['square'] = __( 'Square Tiles', 'jetpack' );
$types['circle'] = __( 'Circles', 'jetpack' );
return $types;
}
function jetpack_default_gallery_type( $default ) {
return ( get_option( 'tiled_galleries' ) ? 'rectangular' : 'default' );
}
/**
* Add a checkbox field to the Carousel section in Settings > Media
* for setting tiled galleries as the default.
*/
function settings_api_init() {
global $wp_settings_sections;
// Add the setting field [tiled_galleries] and place it in Settings > Media
if ( isset( $wp_settings_sections['media']['carousel_section'] ) )
$section = 'carousel_section';
else
$section = 'default';
add_settings_field( 'tiled_galleries', __( 'Tiled Galleries', 'jetpack' ), array( $this, 'setting_html' ), 'media', $section );
register_setting( 'media', 'tiled_galleries', 'esc_attr' );
}
function setting_html() {
echo '<label><input name="tiled_galleries" type="checkbox" value="1" ' .
checked( 1, '' != get_option( 'tiled_galleries' ), false ) . ' /> ' .
__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ) . '</br></label>';
}
}
class Jetpack_Tiled_Gallery_Shape {
static $shapes_used = array();
public function __construct( $images ) {
$this->images = $images;
$this->images_left = count( $images );
}
public function sum_ratios( $number_of_images = 3 ) {
return array_sum( array_slice( wp_list_pluck( $this->images, 'ratio' ), 0, $number_of_images ) );
}
public function next_images_are_symmetric() {
return $this->images_left > 2 && $this->images[0]->ratio == $this->images[2]->ratio;
}
public function is_not_as_previous( $n = 1 ) {
return ! in_array( get_class( $this ), array_slice( self::$shapes_used, -$n ) );
}
public function is_wide_theme() {
return Jetpack::get_content_width() > 1000;
}
public static function set_last_shape( $last_shape ) {
self::$shapes_used[] = $last_shape;
}
public static function reset_last_shape() {
self::$shapes_used = array();
}
}
class Jetpack_Tiled_Gallery_Three extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 1, 1, 1 );
public function is_possible() {
$ratio = $this->sum_ratios( 3 );
return $this->images_left > 2 && $this->is_not_as_previous() &&
( ( $ratio < 2.5 ) || ( $ratio < 5 && $this->next_images_are_symmetric() ) || $this->is_wide_theme() );
}
}
class Jetpack_Tiled_Gallery_Four extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 1, 1, 1, 1 );
public function is_possible() {
return $this->is_not_as_previous() && $this->sum_ratios( 4 ) < 3.5 &&
( $this->images_left == 4 || ( $this->images_left != 8 && $this->images_left > 5 ) );
}
}
class Jetpack_Tiled_Gallery_Five extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 1, 1, 1, 1, 1 );
public function is_possible() {
return $this->is_wide_theme() && $this->is_not_as_previous() && $this->sum_ratios( 5 ) < 5 &&
( $this->images_left == 5 || ( $this->images_left != 10 && $this->images_left > 6 ) );
}
}
class Jetpack_Tiled_Gallery_Two_One extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 2, 1 );
public function is_possible() {
return $this->is_not_as_previous( 3 ) && $this->images_left >= 2 &&
$this->images[2]->ratio < 1.6 && $this->images[0]->ratio >= 0.9 && $this->images[0]->ratio < 2.0 && $this->images[1]->ratio >= 0.9 && $this->images[1]->ratio < 2.0;
}
}
class Jetpack_Tiled_Gallery_One_Two extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 1, 2 );
public function is_possible() {
return $this->is_not_as_previous( 3 ) && $this->images_left >= 2 &&
$this->images[0]->ratio < 1.6 && $this->images[1]->ratio >= 0.9 && $this->images[1]->ratio < 2.0 && $this->images[2]->ratio >= 0.9 && $this->images[2]->ratio < 2.0;
}
}
class Jetpack_Tiled_Gallery_One_Three extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 1, 3 );
public function is_possible() {
return $this->is_not_as_previous() && $this->images_left > 3 &&
$this->images[0]->ratio < 0.8 && $this->images[1]->ratio >= 0.9 && $this->images[1]->ratio < 2.0 && $this->images[2]->ratio >= 0.9 && $this->images[2]->ratio < 2.0 && $this->images[3]->ratio >= 0.9 && $this->images[3]->ratio < 2.0;
}
}
class Jetpack_Tiled_Gallery_Panoramic extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 1 );
public function is_possible() {
return $this->images[0]->ratio >= 2.0;
}
}
class Jetpack_Tiled_Gallery_Symmetric_Row extends Jetpack_Tiled_Gallery_Shape {
public $shape = array( 1, 2, 1 );
public function is_possible() {
return $this->is_not_as_previous() && $this->images_left >= 3 && $this->images_left != 5 &&
$this->images[0]->ratio < 0.8 && $this->images[0]->ratio == $this->images[3]->ratio;
}
}
class Jetpack_Tiled_Gallery_Grouper {
public $margin = 4;
public function __construct( $attachments ) {
$content_width = Jetpack_Tiled_Gallery::get_content_width();
$ua_info = new Jetpack_User_Agent_Info();
$this->last_shape = '';
$this->images = $this->get_images_with_sizes( $attachments );
$this->grouped_images = $this->get_grouped_images();
$this->apply_content_width( $content_width - 5 ); //reduce the margin hack to 5px. It will be further reduced when we fix more themes and the rounding error.
}
public function get_current_row_size() {
$images_left = count( $this->images );
if ( $images_left < 3 )
return array_fill( 0, $images_left, 1 );
foreach ( array( 'One_Three', 'One_Two', 'Five', 'Four', 'Three', 'Two_One', 'Symmetric_Row', 'Panoramic' ) as $shape_name ) {
$class_name = "Jetpack_Tiled_Gallery_$shape_name";
$shape = new $class_name( $this->images );
if ( $shape->is_possible() ) {
Jetpack_Tiled_Gallery_Shape::set_last_shape( $class_name );
return $shape->shape;
}
}
Jetpack_Tiled_Gallery_Shape::set_last_shape( 'Two' );
return array( 1, 1 );
}
public function get_images_with_sizes( $attachments ) {
$images_with_sizes = array();
foreach ( $attachments as $image ) {
$meta = wp_get_attachment_metadata( $image->ID );
$image->width_orig = ( isset( $meta['width'] ) && $meta['width'] > 0 )? $meta['width'] : 1;
$image->height_orig = ( isset( $meta['height'] ) && $meta['height'] > 0 )? $meta['height'] : 1;
$image->ratio = $image->width_orig / $image->height_orig;
$image->ratio = $image->ratio? $image->ratio : 1;
$images_with_sizes[] = $image;
}
return $images_with_sizes;
}
public function read_row() {
$vector = $this->get_current_row_size();
$row = array();
foreach ( $vector as $group_size ) {
$row[] = new Jetpack_Tiled_Gallery_Group( array_splice( $this->images, 0, $group_size ) );
}
return $row;
}
public function get_grouped_images() {
$grouped_images = array();
while( !empty( $this->images ) ) {
$grouped_images[] = new Jetpack_Tiled_Gallery_Row( $this->read_row() );
}
return $grouped_images;
}
// todo: split in functions
// todo: do not stretch images
public function apply_content_width( $width ) {
foreach ( $this->grouped_images as $row ) {
$row->width = $width;
$row->raw_height = 1 / $row->ratio * ( $width - $this->margin * ( count( $row->groups ) - $row->weighted_ratio ) );
$row->height = round( $row->raw_height );
$this->calculate_group_sizes( $row );
}
}
public function calculate_group_sizes( $row ) {
// Storing the calculated group heights in an array for rounding them later while preserving their sum
// This fixes the rounding error that can lead to a few ugly pixels sticking out in the gallery
$group_widths_array = array();
foreach ( $row->groups as $group ) {
$group->height = $row->height;
// Storing the raw calculations in a separate property to prevent rounding errors from cascading down and for diagnostics
$group->raw_width = ( $row->raw_height - $this->margin * count( $group->images ) ) * $group->ratio + $this->margin;
$group_widths_array[] = $group->raw_width;
}
$rounded_group_widths_array = Jetpack_Constrained_Array_Rounding::get_rounded_constrained_array( $group_widths_array, $row->width );
foreach ( $row->groups as $group ) {
$group->width = array_shift( $rounded_group_widths_array );
$this->calculate_image_sizes( $group );
}
}
public function calculate_image_sizes( $group ) {
// Storing the calculated image heights in an array for rounding them later while preserving their sum
// This fixes the rounding error that can lead to a few ugly pixels sticking out in the gallery
$image_heights_array = array();
foreach ( $group->images as $image ) {
$image->width = $group->width - $this->margin;
// Storing the raw calculations in a separate property for diagnostics
$image->raw_height = ( $group->raw_width - $this->margin ) / $image->ratio;
$image_heights_array[] = $image->raw_height;
}
$image_height_sum = $group->height - count( $image_heights_array ) * $this->margin;
$rounded_image_heights_array = Jetpack_Constrained_Array_Rounding::get_rounded_constrained_array( $image_heights_array, $image_height_sum );
foreach ( $group->images as $image ) {
$image->height = array_shift( $rounded_image_heights_array );
}
}
}
class Jetpack_Tiled_Gallery_Row {
public function __construct( $groups ) {
$this->groups = $groups;
$this->ratio = $this->get_ratio();
$this->weighted_ratio = $this->get_weighted_ratio();
}
public function get_ratio() {
$ratio = 0;
foreach ( $this->groups as $group ) {
$ratio += $group->ratio;
}
return $ratio > 0? $ratio : 1;
}
public function get_weighted_ratio() {
$weighted_ratio = 0;
foreach ( $this->groups as $group ) {
$weighted_ratio += $group->ratio * count( $group->images );
}
return $weighted_ratio > 0 ? $weighted_ratio : 1;
}
}
class Jetpack_Tiled_Gallery_Group {
public function __construct( $images ) {
$this->images = $images;
$this->ratio = $this->get_ratio();
}
public function get_ratio() {
$ratio = 0;
foreach ( $this->images as $image ) {
if ( $image->ratio )
$ratio += 1/$image->ratio;
}
if ( !$ratio )
return 1;
return 1/$ratio;
}
}
add_action( 'init', array( 'Jetpack_Tiled_Gallery', 'init' ) );