$surface ) { if ( ! self::overrides_theme( $id ) ) { continue; } self::suppress_surface( $id ); } } private static $drawn = array(); public static function draw( $surface, $how = '' ) { self::$drawn[ (string) $surface ] = (string) $how; } public static function drawn() { return (array) apply_filters( 'aieo_theme_surfaces_drawn', self::$drawn ); } public static function class_map() { static $map = null; if ( null === $map ) { $map = (array) apply_filters( 'aieo_theme_class_map', array( 'header_actions' => array( 'rooster-header__actions', 'ff-header__actions' ), 'header_account' => array( 'rooster-header__account', '' ), 'header_nav' => array( 'rooster-header__nav', 'ff-menu-strip' ), 'footer' => array( 'rooster-footer', 'ff-footer' ), 'store_features' => array( 'rooster-store-features', 'ff-trust-strip' ), 'recos_cluster' => array( 'rooster-recos-cluster', 'ff-recommendations-cluster' ), 'product_info' => array( 'rooster-product__info', 'ff-product-info' ), 'product_card' => array( 'rooster-product-card', 'ff-product-card' ), 'product_cluster' => array( 'rooster-product__cluster', 'ff-product-display-cluster' ), 'under_media' => array( 'rooster-product__under-media', 'ff-product-under-image-cluster' ), 'order' => array( 'is-style-rooster-order-%s', 'rooster-order--%s', 'ff-order--%s' ), ) ); } return $map; } public static function classes( $key, $part = '' ) { $map = self::class_map(); if ( empty( $map[ $key ] ) ) { return ''; } $names = array(); foreach ( (array) $map[ $key ] as $name ) { $name = (string) $name; if ( '' === $name ) { continue; } $names[] = ( false !== strpos( $name, '%s' ) ) ? sprintf( $name, $part ) : $name; } return implode( ' ', $names ); } public static function selector( $key, $part = '' ) { $classes = self::classes( $key, $part ); if ( '' === $classes ) { return ''; } return ':is(.' . implode( ',.', explode( ' ', $classes ) ) . ')'; } public static function adapters() { if ( null !== self::$adapters ) { return self::$adapters; } $bundled = array(); $dir = __DIR__ . '/adapters'; $rey = $dir . '/class-aieo-theme-adapter-rey.php'; if ( is_readable( $rey ) ) { require_once $rey; if ( class_exists( 'AIEO_Theme_Adapter_Rey' ) ) { $bundled['rey'] = AIEO_Theme_Adapter_Rey::describe(); } } self::$adapters = (array) apply_filters( 'aieo_theme_adapters', $bundled ); return self::$adapters; } public static function active_adapter() { if ( null !== self::$active ) { return self::$active; } foreach ( self::adapters() as $id => $adapter ) { if ( empty( $adapter['active'] ) || ! is_callable( $adapter['active'] ) ) { continue; } $ok = false; try { $ok = (bool) call_user_func( $adapter['active'] ); } catch ( Throwable $e ) { $ok = false; } if ( $ok ) { $adapter['id'] = (string) $id; self::$active = $adapter; return self::$active; } } $theme = wp_get_theme(); self::$active = array( 'id' => 'generic', 'label' => $theme ? $theme->get( 'Name' ) : __( 'Active theme', 'ai-eshop-optimizer' ), 'verified_against' => '', 'generic' => true, 'surfaces' => array(), ); return self::$active; } public static function adapter_surface( $surface, $key = '', $default = null ) { $adapter = self::active_adapter(); $block = isset( $adapter['surfaces'][ $surface ] ) ? $adapter['surfaces'][ $surface ] : array(); if ( '' === $key ) { return $block; } return isset( $block[ $key ] ) ? $block[ $key ] : $default; } public static function surfaces() { if ( null !== self::$surfaces ) { return self::$surfaces; } $surfaces = array(); $surfaces['swatches'] = array( 'label' => __( 'Product variation swatches', 'ai-eshop-optimizer' ), 'summary' => __( 'The colour, image and size pickers on a variable product.', 'ai-eshop-optimizer' ), 'modes' => array( '' => __( 'Not set', 'ai-eshop-optimizer' ), 'theme' => __( 'Theme renders them', 'ai-eshop-optimizer' ), 'inherit' => __( 'Rooster One renders, theme design', 'ai-eshop-optimizer' ), 'aieo' => __( 'Rooster One renders, our design', 'ai-eshop-optimizer' ), ), 'get' => static function () { $v = get_option( 'aieo_dmm_swatches_design_source', '' ); return is_string( $v ) ? $v : ''; }, 'set' => static function ( $mode ) { return update_option( 'aieo_dmm_swatches_design_source', (string) $mode ); }, 'detect' => class_exists( 'AIEO_Swatch_Type_Detector' ) ? array( 'AIEO_Swatch_Type_Detector', 'detect' ) : null, ); if ( class_exists( 'AIEO_DMM_Product_Card' ) ) { $surfaces['recos'] = array( 'label' => __( 'Recommendation rails', 'ai-eshop-optimizer' ), 'summary' => __( 'The related and recommended product carousels.', 'ai-eshop-optimizer' ), 'modes' => array( 'theme' => __( 'Theme renders them', 'ai-eshop-optimizer' ), 'neutral' => __( 'Rooster One renders, no design', 'ai-eshop-optimizer' ), 'aieo' => __( 'Rooster One renders, our design', 'ai-eshop-optimizer' ), ), 'get' => array( 'AIEO_DMM_Product_Card', 'design_source' ), 'set' => static function ( $mode ) { $s = get_option( 'aieo_dmm_product_card_settings', array() ); if ( ! is_array( $s ) ) { $s = array(); } $s['design_source'] = (string) $mode; return update_option( 'aieo_dmm_product_card_settings', $s ); }, ); } if ( class_exists( 'AIEO_DMM_Product_Display' ) ) { $surfaces['gallery'] = array( 'label' => __( 'Product image gallery', 'ai-eshop-optimizer' ), 'summary' => __( 'The main product image and its thumbnail strip on the single product page.', 'ai-eshop-optimizer' ), 'modes' => array( '' => __( 'Not set', 'ai-eshop-optimizer' ), 'theme' => __( 'Theme renders it', 'ai-eshop-optimizer' ), 'aieo' => __( 'Rooster One renders, our design', 'ai-eshop-optimizer' ), ), 'get' => static function () { $v = get_option( 'aieo_dmm_gallery_design_source', '' ); return is_string( $v ) ? $v : ''; }, 'set' => static function ( $mode ) { return update_option( 'aieo_dmm_gallery_design_source', (string) $mode ); }, ); } $surfaces['lightbox'] = array( 'label' => __( 'Product image lightbox', 'ai-eshop-optimizer' ), 'summary' => __( 'The fullscreen viewer that opens when a product image is clicked.', 'ai-eshop-optimizer' ), 'modes' => array( '' => __( 'Not set', 'ai-eshop-optimizer' ), 'theme' => __( 'Theme / WooCommerce default', 'ai-eshop-optimizer' ), 'aieo' => __( 'Rooster One renders, our design', 'ai-eshop-optimizer' ), ), 'get' => static function () { $v = get_option( 'aieo_dmm_lightbox_design_source', '' ); return is_string( $v ) ? $v : ''; }, 'set' => static function ( $mode ) { return update_option( 'aieo_dmm_lightbox_design_source', (string) $mode ); }, ); $surfaces['archive_cards'] = array( 'label' => __( 'Product archive cards', 'ai-eshop-optimizer' ), 'summary' => __( 'The extras Rooster One can add to the theme\'s product cards on shop and category listings: the wishlist heart, the rating stars, the NEW badge.', 'ai-eshop-optimizer' ), 'modes' => array( '' => __( 'Not set', 'ai-eshop-optimizer' ), 'theme' => __( 'Theme renders them, untouched', 'ai-eshop-optimizer' ), 'aieo' => __( 'Rooster One adds its extras', 'ai-eshop-optimizer' ), ), 'get' => static function () { $v = get_option( 'aieo_dmm_archive_cards_design_source', '' ); return is_string( $v ) ? $v : ''; }, 'set' => static function ( $mode ) { return update_option( 'aieo_dmm_archive_cards_design_source', (string) $mode ); }, ); self::$surfaces = (array) apply_filters( 'aieo_theme_surfaces', $surfaces ); return self::$surfaces; } public static function mode( $surface ) { $surfaces = self::surfaces(); if ( ! isset( $surfaces[ $surface ] ) ) { return ''; } $getter = $surfaces[ $surface ]['get']; $value = ''; if ( is_callable( $getter ) ) { try { $value = (string) call_user_func( $getter ); } catch ( Throwable $e ) { $value = ''; } } if ( ! isset( $surfaces[ $surface ]['modes'][ $value ] ) ) { $value = ''; } return (string) apply_filters( 'aieo_theme_surface_mode', $value, $surface ); } public static function set_mode( $surface, $mode ) { $surfaces = self::surfaces(); if ( ! isset( $surfaces[ $surface ] ) ) { return false; } $mode = (string) $mode; if ( ! isset( $surfaces[ $surface ]['modes'][ $mode ] ) ) { return false; } $setter = $surfaces[ $surface ]['set']; if ( ! is_callable( $setter ) ) { return false; } return (bool) call_user_func( $setter, $mode ); } public static function effective_mode( $surface ) { $mode = self::mode( $surface ); if ( '' !== $mode ) { return $mode; } $surfaces = self::surfaces(); if ( isset( $surfaces[ $surface ]['unset_mode'] ) ) { return (string) $surfaces[ $surface ]['unset_mode']; } return ''; } public static function overrides_theme( $surface ) { $mode = self::effective_mode( $surface ); return ( '' !== $mode && 'theme' !== $mode ); } public static function host_renders( $surface ) { $present = self::adapter_surface( $surface, 'present' ); if ( ! is_callable( $present ) ) { return false; } try { return (bool) call_user_func( $present ); } catch ( Throwable $e ) { return false; } } public static function should_stand_down( $surface ) { if ( self::overrides_theme( $surface ) ) { return false; } return self::host_renders( $surface ); } public static function suppress_surface( $surface ) { if ( ! empty( self::$suppressed[ $surface ] ) ) { return; } self::$suppressed[ $surface ] = true; $suppress = self::adapter_surface( $surface, 'suppress' ); if ( ! is_callable( $suppress ) ) { return; } try { call_user_func( $suppress ); } catch ( Throwable $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( '[AIEO theme adapter] suppress failed for ' . $surface . ': ' . $e->getMessage() ); } } } public static function remove_callbacks_by_class( $hook, $prefix ) { global $wp_filter; if ( empty( $wp_filter[ $hook ] ) ) { return 0; } $registered = $wp_filter[ $hook ]; if ( ! isset( $registered->callbacks ) || ! is_array( $registered->callbacks ) ) { return 0; } $removed = 0; $snapshot = $registered->callbacks; foreach ( $snapshot as $priority => $callbacks ) { foreach ( $callbacks as $id => $entry ) { $class = self::callback_class( isset( $entry['function'] ) ? $entry['function'] : null ); if ( '' === $class || 0 !== strpos( $class, $prefix ) ) { continue; } unset( $wp_filter[ $hook ]->callbacks[ $priority ][ $id ] ); if ( empty( $wp_filter[ $hook ]->callbacks[ $priority ] ) ) { unset( $wp_filter[ $hook ]->callbacks[ $priority ] ); } $removed++; } } return $removed; } public static function count_callbacks_by_class( $hook, $prefix ) { global $wp_filter; if ( empty( $wp_filter[ $hook ] ) || ! isset( $wp_filter[ $hook ]->callbacks ) ) { return 0; } $n = 0; foreach ( (array) $wp_filter[ $hook ]->callbacks as $callbacks ) { foreach ( (array) $callbacks as $entry ) { $class = self::callback_class( isset( $entry['function'] ) ? $entry['function'] : null ); if ( '' !== $class && 0 === strpos( $class, $prefix ) ) { $n++; } } } return $n; } private static function callback_class( $fn ) { if ( is_array( $fn ) && isset( $fn[0] ) ) { return is_object( $fn[0] ) ? get_class( $fn[0] ) : (string) $fn[0]; } if ( is_object( $fn ) && ! ( $fn instanceof Closure ) ) { return get_class( $fn ); } if ( is_string( $fn ) && false !== strpos( $fn, '::' ) ) { return substr( $fn, 0, strpos( $fn, '::' ) ); } return ''; } public static function status() { $adapter = self::active_adapter(); $theme = wp_get_theme(); $surfaces = array(); foreach ( self::surfaces() as $id => $surface ) { $surfaces[ $id ] = array( 'label' => $surface['label'], 'mode' => self::mode( $id ), 'overrides' => self::overrides_theme( $id ), 'known' => (bool) self::adapter_surface( $id ), ); } return array( 'theme' => $theme ? $theme->get( 'Name' ) : '', 'theme_version' => $theme ? $theme->get( 'Version' ) : '', 'adapter' => $adapter['id'], 'adapter_label' => isset( $adapter['label'] ) ? $adapter['label'] : '', 'generic' => ! empty( $adapter['generic'] ), 'verified_against' => isset( $adapter['verified_against'] ) ? $adapter['verified_against'] : '', 'surfaces' => $surfaces, ); } }