'check-circle',
'no' => 'x',
'soon' => 'clock',
'partial' => 'sliders-horizontal',
);
public static function boot() {
add_action( 'init', array( __CLASS__, 'register' ), 9 );
add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'editor' ), 11 );
add_filter( 'render_block_core/group', array( __CLASS__, 'section_sheet' ), 10, 2 );
add_filter( 'render_block_core/button', array( __CLASS__, 'button' ), 11, 2 );
add_filter( 'render_block_core/icon', array( __CLASS__, 'icon_block' ), 10, 2 );
add_filter( 'render_block_data', array( __CLASS__, 'tabs_data' ) );
add_filter( 'render_block_data', array( __CLASS__, 'tab_stubs' ), 15, 3 );
add_action( 'enqueue_block_assets', array( __CLASS__, 'section_sheet_editor' ) );
}
const BUTTON_ICON = 'rooster-button-icon-';
const BUTTON_NOTE = 'rooster-button-note';
public static function button( $content, $block ) {
$content = (string) $content;
$class = (string) ( $block['attrs']['className'] ?? '' );
$icon = str_contains( $class, self::BUTTON_ICON ) && preg_match( '/(?:^|\s)' . self::BUTTON_ICON . '([a-z0-9_-]+)(?:\s|$)/', $class, $m ) ? $m[1] : '';
$new_tab = str_contains( $content, 'target="_blank"' ) && ! str_contains( $content, 'screen-reader-text' );
$note = str_contains( $class, self::BUTTON_NOTE ) && ! str_contains( $content, 'rooster-button__words' );
if ( '' === $icon && ! $new_tab && ! $note ) {
return $content;
}
$glyph = '' !== $icon && function_exists( 'rooster_icon' ) ? (string) rooster_icon( $icon, array( 'width' => 18, 'height' => 18, 'class' => 'rooster-button__icon' ) ) : '';
$words = $new_tab ? ' ' . esc_html__( '(opens in a new tab)', 'ai-eshop-optimizer' ) . '' : '';
if ( '' === $glyph && '' === $words && ! $note ) {
return $content;
}
$out = preg_replace_callback(
'#(<(a|button)\b[^>]*\bwp-block-button__link\b[^>]*>)(.*?)(\2>)#s',
static function ( $m ) use ( $glyph, $words, $note ) {
$inner = $m[3];
if ( $note && preg_match( '#^(.*?)
(.*)$#s', $inner, $lines ) ) {
$inner = '' . $lines[1] . '' . $lines[2] . '';
}
return $m[1] . $glyph . $inner . $words . $m[4];
},
$content,
1
);
if ( null === $out || $out === $content ) {
return $content;
}
if ( wp_style_is( self::handle( self::SECTION_SHEET ), 'registered' ) ) {
wp_enqueue_style( self::handle( self::SECTION_SHEET ) );
}
return $out;
}
public static function icon_block( $content, $block ) {
$content = (string) $content;
if ( ! str_contains( $content, 'rooster-icon-stroke' ) ) {
return $content;
}
$out = preg_replace_callback(
'##s',
static fn ( $m ) => (string) preg_replace( '#<(path|circle|rect|ellipse|line|polyline|polygon)\b(?![^>]*\sfill=)#', '<$1 fill="none"', $m[0] ),
$content
);
return null === $out ? $content : $out;
}
public static function chips_items( $text, $layout ) {
$list = '';
foreach ( self::lines( (string) $text ) as $line ) {
$parts = array_map( 'trim', explode( '|', $line, 2 ) );
$words = (string) ( $parts[1] ?? $parts[0] );
if ( '' === $words ) {
continue;
}
$icon = isset( $parts[1] ) && 'pills' !== $layout ? sanitize_key( $parts[0] ) : '';
$glyph = '' !== $icon && 'none' !== $icon && function_exists( 'rooster_icon' ) ? rooster_icon( $icon, array( 'width' => 18, 'height' => 18, 'class' => 'rooster-chips__icon' ) ) : '';
$list .= '
' . $glyph . self::run( $words ) . '';
}
return $list;
}
const SECTION_SHEET = 'section';
public static function section_sheet( $content, $block ) {
$class = (string) ( $block['attrs']['className'] ?? '' );
if ( '' !== $class && ( str_contains( $class, 'rooster-section--tone-' ) || str_contains( $class, 'rooster-section__pill' ) || str_contains( $class, 'rooster-hero--center' ) || str_contains( $class, 'rooster-hero__aside' )
|| ( str_contains( $class, 'rooster-section__head' ) && str_contains( (string) $content, 'rooster-notice-pill' ) ) ) ) {
wp_enqueue_style( self::handle( self::SECTION_SHEET ) );
}
return $content;
}
public static function section_sheet_editor() {
if ( is_admin() && wp_style_is( self::handle( self::SECTION_SHEET ), 'registered' ) ) {
wp_enqueue_style( self::handle( self::SECTION_SHEET ) );
}
}
public static function mark( $kind, $words ) {
$icons = (array) apply_filters( 'rooster/blocks/mark_icons', self::MARK_ICONS );
$slug = (string) ( $icons[ $kind ] ?? self::MARK_ICONS['yes'] );
return rooster_icon( $slug, array( 'width' => 20, 'height' => 20, 'class' => 'rooster-mark rooster-mark--' . sanitize_html_class( (string) $kind ) ) )
. '' . esc_html( (string) $words ) . ' ';
}
public static function lines( $text ) {
return array_values( array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', (string) $text ) ?: array() ), 'strlen' ) );
}
const INLINE = '/(\*\*.+?\*\*|`[^`]+`|\[[^\]]+\]\([^)\s]+\))/su';
public static function inline( $text, $links = true ) {
$out = '';
foreach ( preg_split( self::INLINE, (string) $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ) ?: array() as $part ) {
if ( preg_match( '/^\*\*(.+)\*\*$/su', $part, $m ) ) {
$out .= '' . self::inline( $m[1], $links ) . '';
} elseif ( preg_match( '/^`([^`]+)`$/u', $part, $m ) ) {
$out .= '' . esc_html( $m[1] ) . '';
} elseif ( preg_match( '/^\[([^\]]+)\]\(([^)\s]+)\)$/u', $part, $m ) ) {
$href = $links ? esc_url( $m[2] ) : '';
$words = self::inline( $m[1], false );
$out .= '' !== $href ? '' . $words . '' : $words;
} else {
$out .= str_replace( ' // ', '
', esc_html( $part ) );
}
}
return $out;
}
public static function run( $text, $links = true ) {
$html = self::inline( $text, $links );
return str_contains( $html, '<' ) ? '' . $html . '' : $html;
}
public static function plain( $text ) {
$text = (string) preg_replace( '/\*\*(.+?)\*\*/su', '$1', (string) $text );
$text = (string) preg_replace( '/`([^`]+)`/u', '$1', $text );
$text = (string) preg_replace( '/\[([^\]]+)\]\([^)\s]+\)/u', '$1', $text );
return str_replace( ' // ', ' ', $text );
}
public static function text_html( $text, $p_class, $ul_class, array $args = array() ) {
$links = ! isset( $args['links'] ) || (bool) $args['links'];
$level = min( 6, max( 0, (int) ( $args['level'] ?? 0 ) ) );
$line = static fn ( $words ) => self::inline( $words, $links );
$out = '';
$lines = preg_split( '/\r\n|\r|\n/', trim( (string) $text ) ) ?: array();
$para = array();
$list = array();
$flush = static function () use ( &$para, &$list, &$out, $p_class, $ul_class, $line ) {
if ( $para ) {
$out .= '' . implode( '
', array_map( $line, $para ) ) . '
';
$para = array();
}
if ( $list ) {
$out .= '- ' . implode( '
- ', array_map( $line, $list ) ) . '
';
$list = array();
}
};
foreach ( $lines as $row ) {
$row = trim( $row );
if ( '' === $row ) {
$flush();
} elseif ( $level > 1 && preg_match( '/^#{2,4}\s+(.+)$/u', $row, $m ) ) {
$flush();
$out .= sprintf( '%3$s', $level, esc_attr( (string) ( $args['heading'] ?? '' ) ), $line( trim( $m[1] ) ) );
} elseif ( preg_match( '/^[-*]\s+(.+)$/u', $row, $m ) ) {
if ( $para ) {
$flush();
}
$list[] = $m[1];
} else {
if ( $list ) {
$flush();
}
$para[] = $row;
}
}
$flush();
return $out;
}
const LIBRARY = array(
'card' => array( 'feature' => 'stack', 'family' => 'card', 'grid' => 'cards', 'tagged' => 'panels', 'role' => '', 'link' => '', 'detail' => '', 'category' => '' ),
'plan' => array( 'price' => 'card', 'plan' => 'card' ),
'table' => array( 'compare' => '', 'data' => '', 'matrix' => '' ),
'action' => array( 'panel' => 'band', 'buttons' => '' ),
'list' => array( 'checks' => 'grid', 'facts' => 'list', 'stages' => '', 'chips' => 'tiles' ),
'media' => array( 'frame' => '', 'screens' => '', 'figure' => '', 'stack' => '', 'track' => '' ),
'mark' => array( 'crumb' => '', 'pill' => '', 'note' => 'card', 'nav' => '' ),
'questions' => array( 'rows' => '', 'boxed' => '' ),
);
const TONES = array( 'neutral', 'success', 'warning', 'danger' );
const OWN_SHEETS = array( 'tabs' );
const TABS = 'rooster/tabs';
const TAB = 'rooster/tab';
public static function tabs_state( array $parsed ) {
$tabs = array();
foreach ( (array) ( $parsed['innerBlocks'] ?? array() ) as $inner ) {
if ( self::TAB !== (string) ( $inner['blockName'] ?? '' ) ) {
continue;
}
$a = (array) ( $inner['attrs'] ?? array() );
$tabs[] = array(
'anchor' => sanitize_title( (string) ( $a['anchor'] ?? '' ) ),
'label' => trim( (string) ( $a['label'] ?? '' ) ),
'icon' => sanitize_key( (string) ( $a['icon'] ?? '' ) ),
'tone' => self::tone( (string) ( $a['tone'] ?? '' ) ),
);
}
$id = sanitize_title( (string) ( $parsed['attrs']['anchor'] ?? '' ) );
if ( '' === $id ) {
$id = 'rooster-tabs-' . substr( md5( implode( '|', array_column( $tabs, 'label' ) ) ), 0, 6 );
}
foreach ( $tabs as $n => $tab ) {
$tabs[ $n ]['id'] = '' !== $tab['anchor'] ? $tab['anchor'] : $id . '-' . ( $n + 1 );
if ( '' === $tab['label'] ) {
$tabs[ $n ]['label'] = sprintf( __( 'Tab %d', 'ai-eshop-optimizer' ), $n + 1 );
}
unset( $tabs[ $n ]['anchor'] );
}
return array( 'id' => $id, 'tabs' => $tabs );
}
public static function tabs_data( $parsed ) {
if ( ! is_array( $parsed ) || self::TABS !== (string) ( $parsed['blockName'] ?? '' ) ) {
return $parsed;
}
$state = self::tabs_state( $parsed );
$even = ! empty( $parsed['attrs']['even'] );
$n = 0;
foreach ( (array) ( $parsed['innerBlocks'] ?? array() ) as $i => $inner ) {
if ( self::TAB === (string) ( $inner['blockName'] ?? '' ) ) {
$parsed['innerBlocks'][ $i ]['roosterTab'] = array( 'id' => $state['tabs'][ $n ]['id'], 'first' => 0 === $n, 'even' => $even );
++$n;
}
}
$parsed['roosterTabs'] = $state;
return $parsed;
}
const TAB_ANCHOR = 'rooster/tab-anchor';
const TAB_ANCHOR_CLASS = 'rooster-tabs__anchor';
public static function tab_stubs( $parsed, $source = null, $parent = null ) {
if ( null !== $parent || ! is_array( $parsed ) || ! self::layer_defers() || ! self::has_tab( $parsed ) ) {
return $parsed;
}
return self::stub_tree( $parsed );
}
private static function layer_defers() {
return class_exists( 'Rooster\Core\Defer', false ) && \Rooster\Core\Defer::enabled() && ! \Rooster\Core\Defer::renders_whole();
}
private static function has_tab( array $block ) {
if ( self::TAB === (string) ( $block['blockName'] ?? '' ) ) {
return true;
}
foreach ( (array) ( $block['innerBlocks'] ?? array() ) as $child ) {
if ( is_array( $child ) && self::has_tab( $child ) ) {
return true;
}
}
return false;
}
private static function stub_tree( array $block ) {
$children = (array) ( $block['innerBlocks'] ?? array() );
$chunks = (array) ( $block['innerContent'] ?? array() );
if ( ! $children || count( array_filter( $chunks, 'is_null' ) ) !== count( $children ) ) {
return $block;
}
$tab = self::TAB === (string) ( $block['blockName'] ?? '' );
$blocks = array();
$content = array();
$n = 0;
foreach ( $chunks as $chunk ) {
if ( null !== $chunk ) {
$content[] = $chunk;
continue;
}
$child = (array) $children[ $n++ ];
if ( $tab && ! empty( $child['attrs']['roosterDefer']['enabled'] ) ) {
foreach ( self::ids_in( $child ) as $id ) {
$blocks[] = array( 'blockName' => self::TAB_ANCHOR, 'attrs' => array( 'id' => $id ), 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array() );
$content[] = null;
}
} elseif ( ! $tab && self::has_tab( $child ) ) {
$child = self::stub_tree( $child );
}
$blocks[] = $child;
$content[] = null;
}
$block['innerBlocks'] = $blocks;
$block['innerContent'] = $content;
return $block;
}
public static function ids_in( array $block ) {
$ids = array();
$walk = static function ( array $b ) use ( &$walk, &$ids ) {
$a = (array) ( $b['attrs'] ?? array() );
$name = (string) ( $b['blockName'] ?? '' );
if ( is_string( $a['anchor'] ?? null ) ) {
$ids[] = $a['anchor'];
}
if ( 'rooster/card' === $name && preg_match_all( '/\|\s*anchor:([a-z0-9][a-z0-9_-]*)\s*(?=\||$)/m', (string) ( $a['items'] ?? '' ), $m ) ) {
$ids = array_merge( $ids, $m[1] );
}
if ( 'rooster/questions' === $name && '' !== (string) ( $a['anchorPrefix'] ?? '' ) ) {
$count = 0;
foreach ( self::lines( (string) ( $a['items'] ?? '' ) ) as $line ) {
if ( ( ! $count || str_contains( $line, '|' ) ) && '' !== trim( explode( '|', $line, 2 )[0] ) ) {
$ids[] = $a['anchorPrefix'] . '-' . ( ++$count );
}
}
}
foreach ( (array) ( $b['innerBlocks'] ?? array() ) as $child ) {
if ( is_array( $child ) ) {
$walk( $child );
}
}
};
$walk( $block );
return array_values( array_unique( array_filter( array_map( 'sanitize_title', $ids ), 'strlen' ) ) );
}
public static function render_tab_anchor( $attributes ) {
$id = sanitize_title( (string) ( $attributes['id'] ?? '' ) );
return '' !== $id ? '' : '';
}
public static function variant( $block, array $attributes ) {
$variants = self::LIBRARY[ (string) $block ] ?? array( '' => '' );
$variant = (string) ( $attributes['variant'] ?? '' );
if ( ! isset( $variants[ $variant ] ) ) {
$variant = (string) array_key_first( $variants );
}
if ( '' === (string) ( $attributes['layout'] ?? '' ) && '' !== (string) $variants[ $variant ] ) {
$attributes['layout'] = (string) $variants[ $variant ];
}
$class = self::tone_classes( (string) ( $attributes['tone'] ?? '' ), true );
if ( '' !== $class && wp_style_is( self::handle( (string) $block ), 'registered' ) ) {
wp_enqueue_style( self::handle( (string) $block ) );
}
return array( $variant, $attributes, array( 'block' => (string) $block, 'class' => $class ) );
}
public static function wrapper( array $library, array $extra = array() ) {
$extra['class'] = trim( (string) ( $extra['class'] ?? '' ) . ' ' . (string) ( $library['class'] ?? '' ) );
$extra['data-rooster-ui'] = 'rooster-one/' . (string) ( $library['block'] ?? '' );
return get_block_wrapper_attributes( $extra );
}
public static function tone( $value, $fallback = '' ) {
$value = sanitize_key( (string) $value );
return '' !== $value && ! in_array( $value, array( 'green', 'red', 'amber', 'blue' ), true ) ? $value : (string) $fallback;
}
public static function is_tone( $value ) {
$value = trim( (string) $value );
if ( in_array( $value, self::TONES, true ) ) {
return true;
}
if ( ! preg_match( '/^[a-z][a-z0-9-]{0,23}$/', $value ) || '' === self::tone( $value ) ) {
return false;
}
if ( class_exists( '\Rooster\Core\Design' ) && method_exists( '\Rooster\Core\Design', 'accents' ) ) {
return in_array( $value, array_map( static fn ( $a ) => (string) ( $a['slug'] ?? '' ), (array) \Rooster\Core\Design::accents() ), true );
}
return true;
}
public static function tone_classes( $tone, $root = false ) {
$tone = sanitize_key( (string) $tone );
if ( '' === $tone ) {
return '';
}
$family = ! in_array( $tone, self::TONES, true ) && ( $root || 'one' !== $tone );
return 'is-tone-' . $tone . ( $family ? ' is-style-accent-' . $tone : '' );
}
public static function enqueue( $block, $variant ) {
$handle = self::handle( (string) $block, (string) $variant );
if ( wp_style_is( $handle, 'registered' ) ) {
wp_enqueue_style( $handle );
}
}
public static function editor() {
if ( ! wp_script_is( 'rooster-editor-blocks', 'enqueued' ) && ! wp_script_is( 'rooster-editor-blocks', 'registered' ) ) {
return;
}
$manifest = AIEO_ENGINE_DIR . '/blocks/blocks-manifest.php';
if ( ! is_file( $manifest ) ) {
return;
}
$theme = class_exists( 'Rooster\Core\BlockTypes' ) ? array_column( \Rooster\Core\BlockTypes::manifest(), 'name' ) : array();
$extra = array();
foreach ( (array) require $manifest as $dir => $meta ) {
if ( empty( $meta['name'] ) || in_array( $meta['name'], $theme, true ) ) {
continue;
}
$extra[] = array(
'name' => $meta['name'],
'dir' => $dir,
'innerBlocks' => ! empty( $meta['rooster']['innerBlocks'] ),
'template' => $meta['rooster']['template'] ?? null,
'ui' => $meta['rooster']['ui'] ?? array(),
'notice' => $meta['rooster']['notice'] ?? null,
);
}
if ( $extra ) {
wp_add_inline_script( 'rooster-editor-blocks', 'window.roosterBlocks = ( window.roosterBlocks || [] ).concat( ' . wp_json_encode( $extra ) . ' );', 'before' );
}
$editor = AIEO_ENGINE_DIR . '/blocks/mega-menu/editor.js';
if ( is_file( $editor ) ) {
wp_enqueue_script( 'aieo-mega-menu-editor', AIEO_ENGINE_URL . '/blocks/mega-menu/editor.js', array( 'wp-hooks', 'wp-compose', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n' ), (string) filemtime( $editor ), true );
wp_set_script_translations( 'aieo-mega-menu-editor', 'ai-eshop-optimizer', AIEO_ENGINE_LANGUAGES );
}
}
public static function none_rendered() {
static $none = null;
if ( null === $none ) {
$action = wp_doing_ajax() && isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( (string) $_REQUEST['action'] ) ) : '';
$none = '' !== $action && in_array( $action, array(
'aieo_swatch_sync_cart', 'aieo_swatch_batch_add_to_cart', 'aieo_swatch_quick_add_to_cart',
'aieo_swatch_get_cart_variations', 'aieo_swatch_get_all_variations', 'aieo_swatch_refresh_nonce',
'om_swatch_sync_cart', 'om_swatch_batch_add_to_cart', 'om_swatch_quick_add_to_cart',
'aieo_side_cart_get', 'aieo_side_cart_update_qty', 'aieo_side_cart_remove', 'aieo_side_cart_apply_coupon',
'aieo_side_cart_quick_view_add',
), true );
}
return $none;
}
public static function register() {
$dir = AIEO_ENGINE_DIR . '/blocks';
$manifest = $dir . '/blocks-manifest.php';
if ( ! is_file( $manifest ) ) {
return;
}
if ( function_exists( 'wp_register_block_metadata_collection' ) ) {
wp_register_block_metadata_collection( $dir, $manifest );
}
if ( is_file( $dir . '/' . self::SECTION_SHEET . '/style.css' ) ) {
self::register_sheet( self::handle( self::SECTION_SHEET ), self::SECTION_SHEET, 'style.css', '2.0.0' );
}
$registry = WP_Block_Type_Registry::get_instance();
if ( ! $registry->is_registered( self::TAB_ANCHOR ) ) {
register_block_type(
self::TAB_ANCHOR,
array(
'api_version' => 3,
'title' => __( 'Tab anchor', 'ai-eshop-optimizer' ),
'attributes' => array( 'id' => array( 'type' => 'string' ) ),
'supports' => array( 'inserter' => false, 'html' => false, 'className' => false, 'customClassName' => false ),
'render_callback' => array( __CLASS__, 'render_tab_anchor' ),
)
);
}
foreach ( (array) require $manifest as $folder => $meta ) {
$name = (string) ( $meta['name'] ?? '' );
if ( '' === $name || $registry->is_registered( $name ) || ! is_dir( $dir . '/' . $folder ) ) {
continue;
}
$args = array();
if ( ! empty( $meta['rooster']['when'] ) && ! self::when( (string) $meta['rooster']['when'] ) ) {
$args['render_callback'] = static function ( $attributes, $content ) {
return (string) $content;
};
}
if ( isset( self::LIBRARY[ $folder ] ) || in_array( (string) $folder, self::OWN_SHEETS, true ) ) {
self::register_sheet( self::handle( (string) $folder ), (string) $folder, 'style.css', (string) ( $meta['version'] ?? '' ) );
}
$type = register_block_type( $dir . '/' . $folder, $args );
if ( $type && isset( self::LIBRARY[ $folder ] ) && ! in_array( self::handle( (string) $folder ), (array) $type->style_handles, true ) ) {
$type->editor_style_handles = array_values( array_unique( array_merge( (array) $type->editor_style_handles, array( self::handle( (string) $folder ) ) ) ) );
}
if ( $type && ! empty( $meta['rooster']['variants'] ) ) {
self::register_variants( $type, (string) $folder, (array) $meta['rooster']['variants'], (string) ( $meta['version'] ?? '' ) );
}
}
}
public static function handle( $block, $variant = '' ) {
return 'rooster-lib-' . $block . ( '' !== (string) $variant ? '-' . $variant : '' );
}
private static function register_sheet( $handle, $folder, $file, $version ) {
if ( wp_style_is( $handle, 'registered' ) ) {
return;
}
wp_register_style( $handle, AIEO_ENGINE_URL . '/blocks/' . $folder . '/' . $file, array(), '' !== $version ? $version : null );
wp_style_add_data( $handle, 'path', wp_normalize_path( AIEO_ENGINE_DIR . '/blocks/' . $folder . '/' . $file ) );
}
private static function register_variants( $type, $folder, array $variants, $version ) {
foreach ( $variants as $variant ) {
$variant = sanitize_key( (string) $variant );
if ( '' === $variant ) {
continue;
}
$handle = self::handle( $folder, $variant );
self::register_sheet( $handle, $folder, 'variants/' . $variant . '.css', $version );
$type->editor_style_handles = array_values( array_unique( array_merge( (array) $type->editor_style_handles, array( $handle ) ) ) );
}
}
const OUT_GLOBAL = array(
'class', 'id', 'hidden', 'inert', 'role', 'tabindex', 'title', 'lang', 'dir', 'style', 'data-*', 'translate', 'popover',
'aria-label', 'aria-labelledby', 'aria-describedby', 'aria-description', 'aria-details', 'aria-hidden',
'aria-expanded', 'aria-controls', 'aria-current', 'aria-haspopup', 'aria-live', 'aria-atomic', 'aria-busy',
'aria-pressed', 'aria-selected', 'aria-checked', 'aria-disabled', 'aria-modal', 'aria-orientation',
'aria-roledescription', 'aria-sort', 'aria-level', 'aria-posinset', 'aria-setsize', 'aria-colcount',
'aria-colindex', 'aria-colspan', 'aria-rowcount', 'aria-rowindex', 'aria-rowspan', 'aria-valuenow',
'aria-valuemin', 'aria-valuemax', 'aria-valuetext', 'aria-invalid', 'aria-required', 'aria-errormessage',
'aria-readonly', 'aria-owns', 'aria-keyshortcuts', 'aria-relevant', 'aria-autocomplete',
'aria-activedescendant', 'aria-multiselectable',
);
const OUT_EXTRA = array(
'a' => array( 'href', 'target', 'rel', 'hreflang', 'download', 'type', 'name' ),
'img' => array( 'src', 'srcset', 'sizes', 'alt', 'width', 'height', 'loading', 'decoding', 'fetchpriority', 'usemap' ),
'picture' => array(),
'source' => array( 'srcset', 'sizes', 'media', 'type', 'width', 'height', 'src' ),
'form' => array( 'action', 'method', 'novalidate', 'autocomplete', 'name', 'target', 'enctype' ),
'input' => array( 'type', 'name', 'value', 'checked', 'disabled', 'readonly', 'required', 'placeholder', 'autocomplete', 'autofocus', 'spellcheck', 'maxlength', 'minlength', 'min', 'max', 'step', 'size', 'pattern', 'inputmode', 'list', 'form', 'multiple', 'accept', 'enterkeyhint' ),
'select' => array( 'name', 'disabled', 'required', 'multiple', 'size', 'form', 'autocomplete' ),
'option' => array( 'value', 'selected', 'disabled', 'label' ),
'optgroup' => array( 'label', 'disabled' ),
'textarea' => array( 'name', 'rows', 'cols', 'disabled', 'readonly', 'required', 'placeholder', 'maxlength', 'minlength', 'spellcheck', 'autocomplete', 'form', 'wrap' ),
'button' => array( 'type', 'name', 'value', 'disabled', 'form', 'autofocus', 'popovertarget', 'popovertargetaction', 'commandfor', 'command' ),
'label' => array( 'for', 'form' ),
'fieldset' => array( 'disabled', 'form', 'name' ),
'legend' => array(),
'output' => array( 'for', 'form', 'name' ),
'details' => array( 'open', 'name' ),
'summary' => array(),
'dialog' => array( 'open', 'closedby' ),
'template' => array(),
'noscript' => array(),
'time' => array( 'datetime' ),
'svg' => array( 'xmlns', 'xmlns:xlink', 'width', 'height', 'viewbox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'focusable', 'preserveaspectratio', 'version', 'x', 'y' ),
'g' => array( 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'transform', 'opacity', 'mask', 'clip-path', 'fill-rule', 'clip-rule' ),
'defs' => array(),
'symbol' => array( 'viewbox', 'preserveaspectratio', 'fill', 'stroke' ),
'use' => array( 'href', 'xlink:href', 'x', 'y', 'width', 'height', 'fill', 'stroke' ),
'title' => array(),
'desc' => array(),
'path' => array( 'd', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset', 'fill-rule', 'clip-rule', 'transform', 'opacity', 'fill-opacity', 'stroke-opacity', 'vector-effect', 'mask', 'clip-path', 'pathlength' ),
'circle' => array( 'cx', 'cy', 'r', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset', 'transform', 'opacity', 'fill-opacity', 'stroke-opacity', 'vector-effect', 'mask', 'pathlength' ),
'ellipse' => array( 'cx', 'cy', 'rx', 'ry', 'fill', 'stroke', 'stroke-width', 'transform', 'opacity', 'fill-opacity', 'stroke-opacity', 'vector-effect' ),
'rect' => array( 'x', 'y', 'width', 'height', 'rx', 'ry', 'fill', 'stroke', 'stroke-width', 'stroke-linejoin', 'stroke-dasharray', 'transform', 'opacity', 'fill-opacity', 'stroke-opacity', 'vector-effect', 'mask', 'clip-path' ),
'line' => array( 'x1', 'y1', 'x2', 'y2', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-dasharray', 'transform', 'opacity', 'stroke-opacity', 'vector-effect' ),
'polyline' => array( 'points', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'transform', 'opacity', 'vector-effect' ),
'polygon' => array( 'points', 'fill', 'stroke', 'stroke-width', 'stroke-linejoin', 'transform', 'opacity', 'fill-opacity', 'vector-effect' ),
'text' => array( 'x', 'y', 'dx', 'dy', 'text-anchor', 'dominant-baseline', 'transform', 'fill', 'font-size', 'font-weight', 'font-family', 'letter-spacing' ),
'tspan' => array( 'x', 'y', 'dx', 'dy', 'fill', 'font-weight' ),
'mask' => array( 'x', 'y', 'width', 'height', 'maskunits', 'maskcontentunits', 'fill' ),
'clippath' => array( 'clippathunits' ),
'lineargradient' => array( 'x1', 'y1', 'x2', 'y2', 'gradientunits', 'gradienttransform', 'spreadmethod', 'href', 'xlink:href' ),
'radialgradient' => array( 'cx', 'cy', 'r', 'fx', 'fy', 'fr', 'gradientunits', 'gradienttransform', 'spreadmethod', 'href', 'xlink:href' ),
'stop' => array( 'offset', 'stop-color', 'stop-opacity' ),
'pattern' => array( 'x', 'y', 'width', 'height', 'patternunits', 'patterncontentunits', 'patterntransform', 'viewbox' ),
);
private static $allowed = null;
public static function allowed() {
if ( null !== self::$allowed ) {
return self::$allowed;
}
$allowed = wp_kses_allowed_html( 'post' );
foreach ( self::OUT_EXTRA as $tag => $attrs ) {
$allowed[ $tag ] = array_merge( is_array( $allowed[ $tag ] ?? null ) ? $allowed[ $tag ] : array(), array_fill_keys( $attrs, true ) );
}
$global = array_fill_keys( self::OUT_GLOBAL, true );
foreach ( $allowed as $tag => $attrs ) {
$allowed[ $tag ] = array_merge( is_array( $attrs ) ? $attrs : array(), $global );
}
unset( $allowed['script'], $allowed['style'], $allowed['iframe'], $allowed['object'], $allowed['embed'] );
self::$allowed = $allowed;
return $allowed;
}
public static function out( string $html, array $protocols = array() ): void {
echo wp_kses( $html, self::allowed(), array() === $protocols ? wp_allowed_protocols() : array_merge( wp_allowed_protocols(), $protocols ) );
}
public static function when( $condition ) {
if ( 'elementor' === $condition ) {
return function_exists( 'rooster_setting' ) && (bool) rooster_setting( 'elementor', 'compatibility_mode', false ) && ( defined( 'ELEMENTOR_VERSION' ) || class_exists( '\Elementor\Plugin' ) );
}
return (bool) apply_filters( 'rooster/blocks/when', false, $condition );
}
}
}