<?php
/**
* BlockStyles.
*
* @since x.x.x
* @package Magazine Blocks
*/
namespace MagazineBlocks;
defined( 'ABSPATH' ) || exit;
/**
* Class BlockStyles.
*/
class BlockStyles {
const TABLET_BREAKPOINT = '62em';
const MOBILE_BREAKPOINT = '48em';
const DEVICES = array(
'desktop',
'tablet',
'mobile',
);
/**
* Array of blocks data.
*
* @var array Array of blocks data.
*/
private $blocks;
/**
* Current page or template id.
*
* @var int|string Current page or template id.
*/
private $id;
/**
* Force generate.
*
* @var bool Force generate.
*/
private $force_generate;
/**
* Has old button markup.
*
* @var bool Has old button markup.
*/
private $has_old_button_markup;
/**
* Stylesheet filename.
*
* @var string Stylesheet filename.
*/
private $filename;
/**
* Generated styles.
*
* @var string Generated styles.
*/
private $styles;
/**
* Generated css data.
*
* @var array Generated css.
*/
private $css = array();
/**
* Used fonts.
*
* @var array Used fonts.
*/
private $fonts = array();
/**
* Settings.
*
* @var \MagazineBlocks\Setting
*/
private $setting = null;
/**
* Constructor.
*
* @param array $blocks Block data.
* @param null|int|string $id Current page or template id.
* @param bool $force_generate Force generate styles.
*/
public function __construct( array &$blocks, $id = null, bool $force_generate = false ) {
$this->id = $id ?? magazine_blocks_get_the_id();
$this->force_generate = $force_generate;
$this->blocks = &$blocks;
$this->setting = magazine_blocks_get_setting();
if ( empty( $this->blocks ) || empty( $this->id ) ) {
return;
}
$this->has_old_button_markup = $this->has_old_button_markup();
$this->maybe_generate();
}
/**
* Enqueue.
*
* @param bool|string $version Version.
* @return void
*/
public function enqueue( $version = false ) {
if ( empty( $this->styles ) ) {
return;
}
$inline = true;
if ( ! magazine_blocks_is_preview() ) {
$inline = ! ( $this->setting->get( 'asset-generation.external-file', false ) && file_exists( MAGAZINE_BLOCKS_UPLOAD_DIR . "/$this->filename" ) );
}
if ( ! $inline ) {
wp_enqueue_style(
"magazine-blocks-blocks-css-$this->id",
MAGAZINE_BLOCKS_UPLOAD_DIR_URL . "/$this->filename",
array( 'magazine-blocks-blocks' ),
$version // Version is always false, on every build filename is different.
);
return;
}
wp_add_inline_style( 'magazine-blocks-blocks', $this->styles );
unset( $this->styles );
}
/**
* Enqueue Google fonts.
*
* @param array $additional_fonts Additional fonts.
*
* @return void
*/
public function enqueue_fonts( $additional_fonts = array() ) {
if ( ! empty( $additional_fonts ) ) {
foreach ( $additional_fonts as $family => $weight ) {
if ( isset( $this->fonts[ $family ] ) ) {
$this->fonts[ $family ] = array_unique( array_merge( $this->fonts[ $family ], $weight ) );
} else {
$this->fonts[ $family ] = $weight;
}
}
}
if ( empty( $this->fonts ) ) {
return;
}
array_walk(
$this->fonts,
function( &$value, $key ) {
$value = trim( $key ) . ':' . trim( rawurlencode( implode( ',', array_unique( $value ) ) ) );
}
);
$google_fonts_url = add_query_arg(
array(
'family' => implode( '|', array_values( $this->fonts ) ),
),
'https://fonts.googleapis.com/css'
);
if ( $this->setting->get( 'performance.local-google-fonts', false ) ) {
$preload = $this->setting->get( 'performance.preload-local-fonts', false );
$google_fonts_url = magazine_blocks_get_webfont_url( $google_fonts_url, 'woff2', $preload );
}
wp_enqueue_style(
'magazine-blocks-google-fonts',
add_query_arg( 'display', 'swap', $google_fonts_url ),
array(),
MAGAZINE_BLOCKS_VERSION
);
}
/**
* Get styles.
*
* @return string
*/
public function get_styles() {
return $this->styles;
}
/**
* Get fonts.
*
* @return array
*/
public function get_fonts() {
return $this->fonts;
}
/**
* Get style filename.
*
* @return string
*/
public function get_filename() {
return $this->filename;
}
/**
* Maybe generate.
*
* @return void
*/
private function maybe_generate() {
$saved = $this->get_saved_styles();
if ( ! $this->force_generate && ! empty( $saved ) && ! magazine_blocks_is_preview() ) {
$this->styles = magazine_blocks_array_get( $saved, 'stylesheet', '' );
$this->filename = magazine_blocks_array_get( $saved, 'filename', '' );
$this->fonts = magazine_blocks_array_get( $saved, 'fonts', array() );
unset( $this->blocks );
return;
}
try {
$this->create_style_file();
$this->generate();
$this->make_styles();
$this->update_styles();
$this->write();
} catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
// Do nothing.
}
}
/**
* Make styles.
*
* @return void
*/
private function make_styles() {
if ( empty( $this->css ) ) {
return;
}
$styles = $this->make_all_device_styles();
$tablet_breakpoint = $this->setting->get( 'editor.responsive-breakpoints.tablet', self::TABLET_BREAKPOINT ) . 'px';
$mobile_breakpoint = $this->setting->get( 'editor.responsive-breakpoints.mobile', self::MOBILE_BREAKPOINT ) . 'px';
$this->styles = $styles['desktop'];
$this->styles .= $this->make_media_query_styles( $styles['tablet'], "(max-width: $tablet_breakpoint)" );
$this->styles .= $this->make_media_query_styles( $styles['mobile'], "(max-width: $mobile_breakpoint)" );
$this->styles .= $this->make_media_query_styles(
$styles['tablet_only'],
"(min-width: $mobile_breakpoint) and (max-width: $tablet_breakpoint)"
);
$this->styles .= $this->make_media_query_styles( $styles['desktop_only'], "(min-width: $tablet_breakpoint)" );
unset( $this->css );
}
/**
* Make all device styles.
*
* @return array
*/
private function make_all_device_styles() {
$styles = array(
'desktop' => '',
'tablet' => '',
'mobile' => '',
'tablet_only' => '',
'desktop_only' => '',
);
foreach ( $this->css as $device => $css ) {
$css = magazine_blocks_array_combine_keys( $css );
$result = '';
array_walk(
$css,
function ( $declarations, $selector ) use ( &$result ) {
$styles = '';
foreach ( $declarations as $property => $value ) {
$styles .= "$property:$value;";
}
$result .= "$selector{{$styles}}";
}
);
$styles[ $device ] .= $result;
}
return $styles;
}
/**
* Make media query styles.
*
* @param string $styles CSS declarations.
* @param string $media_query Media query.
*
* @return string
*/
private function make_media_query_styles( string $styles, string $media_query ) {
if ( empty( $styles ) ) {
return '';
}
return "@media $media_query{{$styles}}";
}
/**
* Generate.
*
* @return void
*/
private function generate() {
foreach ( $this->blocks as $block ) {
$namespace = $block['blockName'];
$attrs = $block['attrs'];
if ( $this->has_old_button_markup ) {
$namespace = 'magazine-blocks/button' === $namespace ? 'magazine-blocks/button-inner' : $namespace;
}
$name = explode( '/', $namespace )[1];
$attrs_def = $this->get_attribute_def( $namespace );
if ( ! $attrs_def || ! isset( $attrs['clientId'] ) ) {
continue;
}
if ( ! $this->has_old_button_markup ) {
$name = 'button-inner' === $name ? 'button' : ( 'button' === $name ? 'buttons' : $name );
}
$wrapper_class = '.mzb-' . $name . '-' . $attrs['clientId'];
foreach ( $attrs_def as $setting_id => $data ) {
$styles_def = $data['style'] ?? false;
if ( empty( $styles_def ) ) {
continue;
}
$value = $this->get_setting_value( $data, $attrs[ $setting_id ] ?? null );
if ( empty( $value ) ) {
continue;
}
$this->css = magazine_blocks_parse_args(
$this->css,
$this->generate_style_by_prop( $value, $styles_def, $attrs, $attrs_def, $wrapper_class )
);
}
}
unset( $this->blocks );
}
/**
* Get setting value.
*
* @param mixed $data Setting definition.
* @param mixed $value Setting value.
*
* @return mixed
*/
private function get_setting_value( $data, $value ) {
$default = magazine_blocks_array_get( $data, 'default' );
return $value ?? $default;
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function generate_style_by_prop( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
if ( isset( $value['border'] ) ) {
unset( $value['border'] );
$callback = array( $this, 'border' );
} elseif ( isset( $value['background'] ) ) {
unset( $value['background'] );
$callback = array( $this, 'background' );
} elseif ( isset( $value['typography'] ) ) {
unset( $value['typography'] );
$this->generate_fonts( $value );
$callback = array( $this, 'typography' );
} elseif ( isset( $value['dimension'] ) ) {
unset( $value['dimension'] );
$callback = array( $this, 'dimension' );
} elseif ( isset( $value['boxShadow'] ) ) {
unset( $value['boxShadow'] );
$callback = [ $this, 'box_shadow' ];
} elseif ( isset( $value['topSeparator'] ) || isset( $value['bottomSeparator'] ) ) {
unset( $value['topSeparator'] );
unset( $value['bottomSeparator'] );
$callback = [ $this, 'separator' ];
} else {
$callback = array( $this, 'general' );
}
return call_user_func_array( $callback, array( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) );
}
/**
* Generate fonts from blocks.
*
* @param array $value Setting value.
*
* @return void
*/
private function generate_fonts( array $value ) {
if ( ! isset( $value['family'] ) || 'default' === strtolower( $value['family'] ) ) {
return;
}
$weight = (string) ( $value['weight'] ?? '400' );
$family = $value['family'];
if ( ! isset( $this->fonts[ $family ] ) ) {
$this->fonts[ $family ] = array( $weight );
} else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
if ( ! in_array( $weight, $this->fonts[ $family ], true ) ) {
$this->fonts[ $family ][] = $weight;
}
}
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function border( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = array();
if ( empty( $value ) ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? array(), $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
$style = magazine_blocks_array_get( $value, 'type', 'none' );
if ( 'none' !== $style ) {
$css['desktop'][ $selector ]['border-style'] = $style;
if ( isset( $value['color'] ) ) {
$css['desktop'][ $selector ]['border-color'] = $value['color'];
}
$this->process_border_size_styles( $value, $selector, $css );
}
$this->process_border_radius_styles( $value, $selector, $css );
}
return $css;
}
/**
* Process border size styles.
*
* @param array $value Setting value.
* @param string $selector CSS selector.
* @param array $css CSS.
*
* @return void
*/
private function process_border_size_styles( $value, $selector, &$css ) {
if ( ! isset( $value['size'] ) ) {
return;
}
foreach ( self::DEVICES as $device ) {
$device_val = magazine_blocks_array_get( $value, "size.$device", array() );
$val = $this->sides_style( $device_val );
if ( false !== $val ) {
$css[ $device ][ $selector ]['border-width'] = $val;
}
}
}
/**
* Process border radius styles.
*
* @param array $value Setting value.
* @param string $selector CSS selector.
* @param array $css CSS.
*
* @return void
*/
private function process_border_radius_styles( $value, $selector, &$css ) {
if ( ! isset( $value['radius'] ) ) {
return;
}
foreach ( self::DEVICES as $device ) {
$device_val = magazine_blocks_array_get( $value, "radius.$device", array() );
$val = $this->sides_style( $device_val );
if ( false !== $val ) {
$css[ $device ][ $selector ]['border-radius'] = $val;
}
}
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function dimension( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = array(
'desktop' => array(),
'tablet' => array(),
'mobile' => array(),
);
if ( empty( $value ) ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? array(), $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
if ( magazine_blocks_array_has_any( $value, self::DEVICES ) ) {
foreach ( self::DEVICES as $device ) {
$val = $this->sides_style( $value[ $device ] ?? array() );
if ( false === $val ) {
continue;
}
$css[ $device ] = magazine_blocks_parse_args(
$css[ $device ],
$this->parse_css_string(
str_replace( '{{VALUE}}', $val, $selector )
)
);
}
} else {
$val = $this->sides_style( $value );
if ( false !== $val ) {
$css['desktop'] = magazine_blocks_parse_args(
$css['desktop'],
$this->parse_css_string(
str_replace( '{{VALUE}}', $val, $selector )
)
);
}
}
}
return $css;
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function background( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = array();
if ( empty( $value ) ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? array(), $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
if ( isset( $value['color'] ) ) {
$css['desktop'][ $selector ]['background-color'] = $value['color'];
}
$url = magazine_blocks_array_get( $value, 'image.image.url' );
$url = $url ?? magazine_blocks_array_get( $value, 'image.image.local' );
$url = $url ?? magazine_blocks_array_get( $value, 'image.image.external' );
if ( empty( $url ) ) {
continue;
}
$css['desktop'][ $selector ]['background-image'] = "url($url)";
$attachment = magazine_blocks_array_get( $value, 'image.attachment', 'default' );
if ( 'default' !== $attachment ) {
$css['desktop'][ $selector ]['background-attachment'] = $attachment;
}
$this->process_responsive_background_styles( $value, $selector, $css );
}
return $css;
}
/**
* Process responsive background styles.
*
* @param array $value Setting value.
* @param string $selector CSS selector.
* @param array $css CSS.
*
* @return void
*/
private function process_responsive_background_styles( $value, $selector, &$css ) {
foreach ( self::DEVICES as $device ) {
$position = magazine_blocks_array_get( $value, "image.position.$device", 'default' );
$repeat = magazine_blocks_array_get( $value, "image.repeat.$device", 'default' );
$size = magazine_blocks_array_get( $value, "image.size.$device", 'default' );
if ( 'default' !== $position ) {
$css[ $device ][ $selector ]['background-position'] = $position;
}
if ( 'default' !== $repeat ) {
$css[ $device ][ $selector ]['background-repeat'] = $repeat;
}
if ( 'default' !== $size || 'custom' !== $size ) {
$css[ $device ][ $selector ]['background-size'] = $size;
}
if ( 'custom' === $size ) {
$key = 'customSize' . ucfirst( $device );
$custom_size = magazine_blocks_array_get( $value, "image.$key.value" );
$custom_unit = magazine_blocks_array_get( $value, "image.$key.unit", 'px' );
if ( $custom_size ) {
$css[ $device ][ $selector ]['background-size'] = "$custom_size$custom_unit auto";
}
}
}
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function box_shadow( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = array();
if ( ! isset( $value['enable'] ) || ! $value['enable'] ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? array(), $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
$position = magazine_blocks_array_get( $value, 'position', 'outline' );
$horizontal_x = magazine_blocks_array_get( $value, 'horizontalX', '0' );
$vertical_y = magazine_blocks_array_get( $value, 'verticalY', '0' );
$blur = magazine_blocks_array_get( $value, 'blur', '0' );
$spread = magazine_blocks_array_get( $value, 'spread', '0' );
$color = magazine_blocks_array_get( $value, 'color', 'rgba(0,0,0, 0.5)' );
$css['desktop'][ $selector ]['box-shadow'] = sprintf(
'%s%spx %spx %spx %spx %s',
'inset' === $position ? 'inset ' : '',
$horizontal_x,
$vertical_y,
$blur,
$spread,
$color
);
}
return $css;
}
/**
* Generate separator css.
*
* @param array $value Separator value.
* @param array $styles_def Styles definition.
* @param array $attrs Saved attributes.
* @param array $attrs_def Attributes definition.
* @param string $wrapper_class Wrapper class.
* @return void
*/
private function separator( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = [];
if ( ! isset( $value['enable'] ) || ! $value['enable'] ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? array(), $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
$fill = magazine_blocks_array_get( $value, 'color', '#fff' );
$height = magazine_blocks_array_get( $value, 'height', 100 );
$width = magazine_blocks_array_get( $value, 'width', 1 );
$shadow_color = magazine_blocks_array_get( $value, 'shadow_color', '#fff' );
$horizontal_x = magazine_blocks_array_get( $value, 'horizontalX', 0 );
$vertical_y = magazine_blocks_array_get( $value, 'verticalY', 0 );
$blur = magazine_blocks_array_get( $value, 'blur', 0 );
$css['desktop'][ $selector ]['fill'] = $fill;
$css['desktop'][ $selector ]['height'] = $height . 'px';
$css['desktop'][ $selector ]['transform'] = 'scaleX(' . $width . ')';
$css['desktop'][ $selector ]['filter'] = 'drop-shadow(' . $horizontal_x . 'px ' . $vertical_y . 'px ' . $blur . 'px ' . $shadow_color . ')';
}
return $css;
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function typography( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = array();
if ( empty( $value ) ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? array(), $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
$family = magazine_blocks_array_get( $value, 'family', 'Default' );
$weight = magazine_blocks_array_get( $value, 'weight' );
$transform = magazine_blocks_array_get( $value, 'transform', 'default' );
$decoration = magazine_blocks_array_get( $value, 'decoration', 'default' );
if ( 'default' !== strtolower( $family ) ) {
$css['desktop'][ $selector ]['font-family'] = $family;
}
if ( $weight ) {
$css['desktop'][ $selector ]['font-weight'] = $weight;
}
if ( 'default' !== strtolower( $transform ) ) {
$css['desktop'][ $selector ]['text-transform'] = $transform;
}
if ( 'default' !== strtolower( $decoration ) ) {
$css['desktop'][ $selector ]['text-decoration'] = $decoration;
}
$this->process_responsive_typography_styles( $value, $selector, $css );
}
return $css;
}
/**
* Process responsive typography styles.
*
* @param array $value Setting value.
* @param string $selector CSS selector.
* @param array $css CSS.
*
* @return void
*/
private function process_responsive_typography_styles( $value, $selector, &$css ) {
foreach ( self::DEVICES as $device ) {
$size = magazine_blocks_array_get( $value, "size.$device.value", false );
$size_unit = magazine_blocks_array_get( $value, "size.$device.unit", 'px' );
$line_height = magazine_blocks_array_get( $value, "lineHeight.$device.value", false );
$line_height_unit = magazine_blocks_array_get( $value, "lineHeight.$device.unit", 'px' );
$letter_spacing = magazine_blocks_array_get( $value, "letterSpacing.$device.value", false );
$letter_spacing_unit = magazine_blocks_array_get( $value, "letterSpacing.$device.unit", 'px' );
if ( $size ) {
$css[ $device ][ $selector ]['font-size'] = $size . $size_unit;
}
if ( $line_height ) {
$css[ $device ][ $selector ]['line-height'] = $line_height . $line_height_unit;
}
if ( $letter_spacing ) {
$css[ $device ][ $selector ]['letter-spacing'] = $letter_spacing . $letter_spacing_unit;
}
}
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function general( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = array(
'desktop' => array(),
'tablet' => array(),
'mobile' => array(),
'tablet_only' => array(),
'desktop_only' => array(),
);
if ( empty( $value ) ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? array(), $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
if ( false === strpos( $selector, '{{VALUE}}' ) ) {
$this->process_non_value_selector( $selector, $css );
continue;
}
if ( ! $value ) {
continue;
}
$this->process_value_selector( $value, $selector, $css );
}
return $css;
}
/**
* Generate style by prop.
*
* @param mixed $value Setting value.
* @param array $styles_def Styles definition.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes def.
* @param string $wrapper_class Wrapper id.
*
* @return array
*/
private function list_gap( $value, $styles_def, $attrs, $attrs_def, $wrapper_class ) {
$css = [];
if ( ! isset( $value['enable'] ) || ! $value['enable'] ) {
return $css;
}
foreach ( $styles_def as $style_def ) {
if ( ! $this->check_condition( $style_def['condition'] ?? [], $attrs, $attrs_def ) ) {
continue;
}
if ( ! isset( $style_def['selector'] ) ) {
continue;
}
$selector = str_replace( '{{WRAPPER}}', $wrapper_class, $style_def['selector'] );
// Retrieve the slider value for padding (divided by 2)
$slider_value = magazine_blocks_array_get( $value, 'sliderValue', '0' );
// Calculate the padding value based on the slider value
$padding_value = $slider_value / 2;
// Generate CSS rules for padding for the selector
$css['desktop'][ $selector ]['padding-top'] = $padding_value . 'px';
$css['desktop'][ $selector ]['padding-bottom'] = $padding_value . 'px';
}
return $css;
}
/**
* This function processes CSS selectors and assigns them to specific CSS arrays based on their media
* query properties.
*
* @param string $selector The CSS selector to be processed.
* @param array $css is an associative array that contains different CSS styles for different screen
* sizes. The keys of the array are 'mobile', 'tablet', 'desktop', 'tablet_only', and 'desktop_only'.
* The values of the array are CSS declarations in string format.
*/
private function process_non_value_selector( $selector, &$css ) {
if ( false !== strpos( $selector, '@media' ) ) {
$this->process_media_query_styles( $selector, $css );
} else {
$css['desktop'] = magazine_blocks_parse_args( $css['mobile'], $this->parse_css_string( $selector ) );
}
}
/**
* Process media query styles.
*
* @param mixed $selector CSS selector.
* @param mixed $css Process css.
*
* @return void
*/
private function process_media_query_styles( $selector, &$css ) {
preg_match( '/@media\s*\((.*?)\)\s*{(.*?)}/', $selector, $matches );
$desktop_only = 'min-width:62em';
$tablet_only = 'min-width:48em) and (max-width:62em';
$mobile = 'max-width:48em';
$tablet = 'max-width:62em';
if ( empty( $matches[1] ) || empty( $matches[2] ) ) {
return;
}
$media_query = $matches[1];
$declarations = $matches[2];
$declarations = substr( $declarations, -1 ) !== '}' ? $declarations . '}' : $declarations;
$declarations = $this->parse_css_string( $declarations );
if ( strpos( $media_query, $tablet_only ) !== false ) {
$css['tablet_only'] = magazine_blocks_parse_args( $css['tablet_only'], $declarations );
return;
}
if ( strpos( $media_query, $desktop_only ) !== false ) {
$css['desktop_only'] = magazine_blocks_parse_args( $css['desktop_only'], $declarations );
return;
}
if ( strpos( $media_query, $mobile ) !== false ) {
$css['mobile'] = magazine_blocks_parse_args( $css['mobile'], $declarations );
return;
}
if ( strpos( $media_query, $tablet ) !== false ) {
$css['tablet'] = magazine_blocks_parse_args( $css['tablet'], $declarations );
}
}
/**
* The method processes a value selector for CSS styling, taking into account different devices and
* units.
*
* @param mixed $value The value to be processed. It can be a string or an array containing values for different devices.
* @param string $selector The CSS selector to apply the value to.
* @param array $css is a reference to an array that contains CSS styles for different devices (desktop,
* tablet, mobile). The function updates this array with new styles based on the value and selector
* parameters.
*/
private function process_value_selector( $value, $selector, &$css ) {
if ( is_array( $value ) ) {
if ( count( array_intersect( array_keys( $value ), array( 'desktop', 'tablet', 'mobile' ) ) ) > 0 ) {
foreach ( self::DEVICES as $device ) {
if ( ! isset( $value[ $device ] ) ) {
continue;
}
if ( is_array( $value[ $device ] ) ) {
if ( isset( $value[ $device ]['value'] ) ) {
$unit = $value[ $device ]['unit'] ?? 'px';
$temp = str_replace( '{{VALUE}}', "{$value[ $device ]['value']}$unit", $selector );
$css[ $device ] = magazine_blocks_parse_args( $css[ $device ], $this->parse_css_string( $temp ) );
}
} else {
$css[ $device ] = magazine_blocks_parse_args(
$css[ $device ],
$this->parse_css_string( str_replace( '{{VALUE}}', $value[ $device ], $selector ) )
);
}
}
} elseif ( isset( $value['value'] ) ) {
$unit = $value['unit'] ?? 'px';
$css['desktop'] = magazine_blocks_parse_args(
$css['desktop'],
$this->parse_css_string( str_replace( '{{VALUE}}', "{$value['value']}$unit", $selector ) )
);
}
} else {
$css['desktop'] = magazine_blocks_parse_args(
$css['desktop'],
$this->parse_css_string( str_replace( '{{VALUE}}', $value, $selector ) )
);
}
}
/**
* Parses a CSS string and returns an array of selectors and their corresponding
* properties.
*
* @param string $css_string A string containing CSS rules and declarations.
*
* @return array An array containing parsed CSS rules, where each selector is a key and its
* corresponding properties are stored as an array.
*/
private function parse_css_string( string $css_string ): array {
$css = array();
$rules = explode( '}', $css_string );
foreach ( $rules as $rule ) {
$parts = explode( '{', $rule, 2 );
if ( ! isset( $parts[1] ) ) {
continue;
}
$selector = trim( $parts[0] );
$properties = array();
$pairs = explode( ';', trim( $parts[1] ) );
foreach ( $pairs as $pair ) {
$pos = strpos( $pair, ':' );
if ( false === $pos ) {
continue;
}
$prop = trim( substr( $pair, 0, $pos ) );
$val = trim( substr( $pair, $pos + 1 ) );
$properties[ $prop ] = $val;
}
$css[ $selector ] = $properties;
}
return $css;
}
/**
* Side styles.
*
* @param mixed $value Setting value.
*
* @return string
*/
private function sides_style( $value ) {
if ( magazine_blocks_array_has_any( $value, array( 'top', 'right', 'bottom', 'left' ) ) ) {
$unit = magazine_blocks_array_get( $value, 'unit', 'px' );
$top = magazine_blocks_array_get( $value, 'top', 0 );
$right = magazine_blocks_array_get( $value, 'right', 0 );
$bottom = magazine_blocks_array_get( $value, 'bottom', 0 );
$left = magazine_blocks_array_get( $value, 'left', 0 );
$is_equal = $top === $right && $right === $bottom && $bottom === $left;
$is_top_bottom_equal = $top === $bottom;
$is_left_right_equal = $left === $right;
if ( $is_equal ) {
return "$top$unit";
}
if ( $is_top_bottom_equal && $is_left_right_equal ) {
return "$top$unit $left$unit";
}
return "$top$unit $right$unit $bottom$unit $left$unit";
}
return false;
}
/**
* Get attribute definition.
*
* @param mixed $block_namespace Block namespace.
* @return array|bool
*/
private function get_attribute_def( $block_namespace ) {
return \WP_Block_Type_Registry::get_instance()
->get_all_registered()[ $block_namespace ]->attributes ?? false;
}
/**
* Get saved styles.
*
* @return array|false
*/
private function get_saved_styles() {
return is_int( $this->id ) ?
get_post_meta( $this->id, '_magazine_blocks_blocks_css', true ) :
get_option( '_magazine_blocks_blocks_css', array() )[ $this->id ] ?? [];
}
/**
* Updates the styles data.
*/
private function update_styles() {
$styles_data = array(
'filename' => $this->filename,
'fonts' => $this->fonts,
'stylesheet' => $this->styles,
);
if ( is_int( $this->id ) ) {
update_post_meta( $this->id, '_magazine_blocks_blocks_css', $styles_data );
return;
}
$saved = get_option( '_magazine_blocks_blocks_css', array() );
$saved[ $this->id ] = $styles_data;
update_option( '_magazine_blocks_blocks_css', $saved );
}
/**
* Check if has old button markup.
*
* @return bool
*/
private function has_old_button_markup() {
foreach ( $this->blocks as $block ) {
if ( 'magazine-blocks/button' === $block['blockName'] && empty( $block['innerBlocks'] ) ) {
return true;
}
}
return false;
}
/**
* This function creates a CSS file with a unique filename and deletes any existing CSS files with a
* similar name.
*
* @return false|true false or the result of the `touch` method of the `` object.
*/
private function create_style_file() {
$filesystem = magazine_blocks_get_filesystem();
if ( ! $filesystem ) {
return false;
}
if (
! $filesystem->is_dir( MAGAZINE_BLOCKS_UPLOAD_DIR ) &&
! $filesystem->mkdir( MAGAZINE_BLOCKS_UPLOAD_DIR, FS_CHMOD_DIR )
) {
return false;
}
$files = $filesystem->dirlist( MAGAZINE_BLOCKS_UPLOAD_DIR );
if ( $files ) {
foreach ( $files as $file ) {
if ( false !== strpos( $file['name'], "mzb-style-$this->id-" ) ) {
$filesystem->delete( MAGAZINE_BLOCKS_UPLOAD_DIR . '/' . $file['name'] );
}
}
}
$this->filename = "mzb-style-$this->id-" . time() . '.css';
return $filesystem->touch( MAGAZINE_BLOCKS_UPLOAD_DIR . '/' . $this->filename );
}
/**
* This function writes the contents of a file to a specified directory using the Magazine Blocks filesystem.
*/
private function write() {
$filesystem = magazine_blocks_get_filesystem();
if ( $filesystem ) {
$filesystem->put_contents( MAGAZINE_BLOCKS_UPLOAD_DIR . '/' . $this->filename, $this->styles );
}
}
/**
* Check condition.
*
* @param array $conditions Conditions to check.
* @param array $attrs Block attributes.
* @param array $attrs_def Block attributes definition.
*
* @return boolean
*/
private function check_condition( $conditions, $attrs, $attrs_def ) {
$result = true;
if ( empty( $conditions ) ) {
return $result;
}
foreach ( $conditions as $condition ) {
$previous = $result;
$key = $condition['key'];
$value = magazine_blocks_array_get( $attrs, $key );
$value = $value ?? magazine_blocks_array_get( $attrs_def, "$key.default", false );
if ( ! $value ) {
continue;
}
$relation = magazine_blocks_array_get( $condition, 'relation', '' );
$condition_value = magazine_blocks_array_get( $condition, 'value', '' );
$is_equal_relation = '==' === $relation || '===' === $relation;
$is_non_equal_relation = '!=' === $relation || '!==' === $relation;
if ( $is_equal_relation ) {
if ( is_scalar( $condition_value ) ) {
$result = $value === $condition_value;
} else {
$result = in_array( $value, $condition_value, true );
}
} elseif ( $is_non_equal_relation ) {
if ( is_scalar( $condition_value ) ) {
$result = $value !== $condition_value;
} else {
$result = ! in_array( $value, $condition_value, true );
}
}
if ( ! $previous ) {
$result = false;
}
}
return $result;
}
}