feature: Initial commit

This commit is contained in:
Keith Solomon
2026-04-13 20:06:32 -05:00
commit 351dc06ec4
38 changed files with 8138 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
<?php
/**
* Block registration.
*
* @package LogoSoup
*/
namespace LogoSoup\Blocks;
use LogoSoup\Plugin;
use LogoSoup\Settings;
use LogoSoup\Support\Logo_Sanitizer;
/**
* Registers and renders the Logo Soup block.
*/
class Logo_Soup_Block {
/**
* Register the block.
*
* @return void
*/
public static function register() {
register_block_type(
Plugin::asset_path( 'blocks/logo-soup/block.json' ),
array(
'render_callback' => array( __CLASS__, 'render' ),
)
);
}
/**
* Render callback.
*
* @param array<string, mixed> $attributes Block attributes.
* @return string
*/
public static function render( $attributes ) {
$settings = Settings::get();
$logos = Logo_Sanitizer::sanitize_collection( isset( $attributes['logos'] ) ? $attributes['logos'] : array() );
$layout = isset( $attributes['layout'] ) ? sanitize_key( $attributes['layout'] ) : $settings['default_layout'];
$layout = in_array( $layout, array( 'carousel', 'grid' ), true ) ? $layout : $settings['default_layout'];
$speed = isset( $attributes['speed'] ) ? absint( $attributes['speed'] ) : (int) $settings['default_speed'];
$speed = max( 8, min( 80, $speed ) );
$grayscale = isset( $attributes['grayscale'] ) ? (bool) $attributes['grayscale'] : ! empty( $settings['default_gray'] );
$pause_on_hover = isset( $attributes['pauseOnHover'] ) ? (bool) $attributes['pauseOnHover'] : true;
$base_size = isset( $attributes['baseSize'] ) ? absint( $attributes['baseSize'] ) : (int) $settings['base_size'];
$base_size = max( 16, min( 160, $base_size ) );
$scale_factor = self::sanitize_float(
isset( $attributes['scaleFactor'] ) ? $attributes['scaleFactor'] : $settings['scale_factor'],
0.5,
0.1,
2
);
$density_aware = isset( $attributes['densityAware'] ) ? (bool) $attributes['densityAware'] : ! empty( $settings['density_aware'] );
$density_factor = self::sanitize_float(
isset( $attributes['densityFactor'] ) ? $attributes['densityFactor'] : $settings['density_factor'],
0.5,
0,
1
);
$crop_to_content = isset( $attributes['cropToContent'] ) ? (bool) $attributes['cropToContent'] : ! empty( $settings['crop_to_content'] );
$align_by = isset( $attributes['alignBy'] ) ? sanitize_key( $attributes['alignBy'] ) : $settings['align_by'];
$align_by = in_array( $align_by, array( 'bounds', 'visual-center', 'visual-center-x', 'visual-center-y' ), true ) ? $align_by : $settings['align_by'];
$open_new_tab = ! empty( $settings['open_new_tab'] );
$size_multiplier = 0.75 + ( $scale_factor * 0.5 );
$gap_multiplier = 1;
$width_factor = 3 + $scale_factor;
$opacity_floor = 0.72;
if ( $density_aware ) {
$size_multiplier += ( $density_factor * 0.35 );
$gap_multiplier = max( 0.7, 1 - ( $density_factor * 0.22 ) );
$width_factor += ( $density_factor * 0.6 );
$opacity_floor = max( 0.45, 0.78 - ( $density_factor * 0.18 ) );
}
$effective_size = (int) round( $base_size * $size_multiplier );
if ( empty( $logos ) ) {
return '';
}
$wrapper_classes = array( 'logo-soup', 'layout-' . $layout );
if ( $grayscale ) {
$wrapper_classes[] = 'is-grayscale';
}
if ( $pause_on_hover ) {
$wrapper_classes[] = 'pause-on-hover';
}
if ( $density_aware ) {
$wrapper_classes[] = 'is-density-aware';
}
if ( $crop_to_content ) {
$wrapper_classes[] = 'crop-to-content';
}
$wrapper_classes[] = 'align-by-' . $align_by;
$style = sprintf(
'--logo-soup-speed:%1$ds;--logo-soup-base-size:%2$dpx;--logo-soup-scale-factor:%3$s;--logo-soup-density-factor:%4$s;--logo-soup-effective-size:%5$dpx;--logo-soup-gap-factor:%6$s;--logo-soup-width-factor:%7$s;--logo-soup-gray-opacity:%8$s;',
$speed,
$base_size,
self::format_float( $scale_factor ),
self::format_float( $density_factor ),
$effective_size,
self::format_float( $gap_multiplier ),
self::format_float( $width_factor ),
self::format_float( $opacity_floor )
);
ob_start();
?>
<div
class="<?php echo esc_attr( implode( ' ', $wrapper_classes ) ); ?>"
style="<?php echo esc_attr( $style ); ?>"
data-base-size="<?php echo esc_attr( (string) $base_size ); ?>"
data-scale-factor="<?php echo esc_attr( self::format_float( $scale_factor ) ); ?>"
data-density-aware="<?php echo esc_attr( $density_aware ? 'true' : 'false' ); ?>"
data-density-factor="<?php echo esc_attr( self::format_float( $density_factor ) ); ?>"
data-crop-to-content="<?php echo esc_attr( $crop_to_content ? 'true' : 'false' ); ?>"
data-align-by="<?php echo esc_attr( $align_by ); ?>"
>
<div class="logo-soup__track">
<?php self::render_logo_items( $logos, $open_new_tab ); ?>
<?php if ( 'carousel' === $layout ) : ?>
<?php self::render_logo_items( $logos, $open_new_tab, true ); ?>
<?php endif; ?>
</div>
</div>
<?php
return (string) ob_get_clean();
}
/**
* Render individual logo items.
*
* @param array<int, array<string, mixed>> $logos Logos.
* @param bool $open_new_tab Open in new tab.
* @param bool $duplicate Duplicate set.
* @return void
*/
private static function render_logo_items( array $logos, $open_new_tab, $duplicate = false ) {
foreach ( $logos as $index => $logo ) {
$tag = ! empty( $logo['link'] ) ? 'a' : 'div';
$href_attr = ! empty( $logo['link'] ) ? ' href="' . esc_url( $logo['link'] ) . '"' : '';
$rel_attr = ( ! empty( $logo['link'] ) && $open_new_tab ) ? ' rel="noreferrer noopener"' : '';
$target = ( ! empty( $logo['link'] ) && $open_new_tab ) ? ' target="_blank"' : '';
$key = $duplicate ? 'dup-' . $index : (string) $index;
?>
<<?php echo esc_html( $tag ); ?> class="logo-soup__item" data-key="<?php echo esc_attr( $key ); ?>"<?php echo $href_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php echo $target; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php echo $rel_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<img class="logo-soup__image" src="<?php echo esc_url( $logo['url'] ); ?>" alt="<?php echo esc_attr( (string) $logo['alt'] ); ?>" loading="lazy" decoding="async" />
</<?php echo esc_html( $tag ); ?>>
<?php
}
}
/**
* Sanitize a float-like block attribute.
*
* @param mixed $value Value.
* @param float $fallback Fallback.
* @param float $min Minimum.
* @param float $max Maximum.
* @return float
*/
private static function sanitize_float( $value, $fallback, $min, $max ) {
$value = is_numeric( $value ) ? (float) $value : $fallback;
$value = max( $min, min( $max, $value ) );
return round( $value, 2 );
}
/**
* Format a float for HTML attributes and CSS variables.
*
* @param float $value Value.
* @return string
*/
private static function format_float( $value ) {
return rtrim( rtrim( number_format( $value, 2, '.', '' ), '0' ), '.' );
}
}