` in a theme header) gets the count. * - Gutenberg blocks `aooshop/products` (blocks/products) + `aooshop/collections` (blocks/collections) — plain JS, no build * step, ServerSideRender preview. * - `[aooshop_content part="header|filters|grid|footer"]` (shop template pages) lives in AooShop_Renderer. * - Elementor (free) widgets in category "AOO Shop" (slug `aoo-shop`; only when Elementor is loaded · * includes/elementor/*): AOO Shop Part · AOO Filters · AOO Collections · AOO Chips · AOO Products · AOO Search · * AOO Cart Icon (widget names `aooshop-*`). Style tab = CSS variables `--aoo-*` on {{WRAPPER}} only. * - Names of earlier versions (`[aoo_shop_*]` / `[aoo_*]`, blocks `aoo-shop/*` / `aoo/*`, widgets `aoo-shop-*` / `aoo-*`) * are rewritten in existing content once by AooShop_Upgrade — nothing is registered under them. * * @package AooShop */ defined( 'ABSPATH' ) || exit; class AooShop_Shortcodes { /** @var AooShop_Cards */ private $cards; /** @var AooShop_Assets */ private $assets; /** @var AooShop_Browse */ private $browse; public function __construct( AooShop_Cards $cards, AooShop_Assets $assets, AooShop_Browse $browse ) { $this->cards = $cards; $this->assets = $assets; $this->browse = $browse; } public function hooks() { add_action( 'init', array( $this, 'register' ), 20 ); // Block editor (incl. its iframe): embed CSS so the ServerSideRender preview looks like the site. add_action( 'enqueue_block_assets', array( $this, 'editor_assets' ) ); // Elementor. add_action( 'elementor/elements/categories_registered', array( $this, 'elementor_category' ) ); add_action( 'elementor/widgets/register', array( $this, 'elementor_widget' ) ); add_action( 'elementor/controls/register', array( $this, 'elementor_controls' ) ); add_action( 'elementor/frontend/after_register_styles', array( $this, 'elementor_register_style' ) ); add_action( 'elementor/preview/enqueue_styles', array( $this->cards, 'enqueue' ) ); add_action( 'elementor/preview/enqueue_styles', array( $this, 'elementor_preview_style' ) ); } /** Shortcode tag → method. Canonical names (AOO Shop). */ const SHORTCODES = array( 'aooshop_products' => 'products', 'aooshop_product' => 'product', 'aooshop_collections' => 'collections', 'aooshop_chips' => 'chips', 'aooshop_search' => 'search', 'aooshop_filters' => 'filters', 'aooshop_cart_icon' => 'cart_icon', ); /** Every shortcode tag that renders product cards — card detection, host cache, converter. */ const CARD_TAGS = array( 'aooshop_products', 'aooshop_product' ); public function register() { foreach ( self::SHORTCODES as $tag => $method ) { if ( ! shortcode_exists( $tag ) ) { add_shortcode( $tag, array( $this, $method ) ); } } if ( function_exists( 'register_block_type' ) ) { register_block_type( AOOSHOP_DIR . 'blocks/products', array( 'render_callback' => array( $this, 'block' ) ) ); register_block_type( AOOSHOP_DIR . 'blocks/collections', array( 'render_callback' => array( $this, 'collections_block' ) ) ); if ( is_admin() ) { $buckets = array(); foreach ( aooshop()->settings->price_buckets() as $bk ) { $buckets[] = array( 'slug' => $bk['slug'], 'label' => $bk['label'] ); } $conf = 'window.aooShopBlocks=' . wp_json_encode( array( 'buckets' => $buckets ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';'; wp_add_inline_script( 'aooshop-products-editor-script', $conf, 'before' ); wp_add_inline_script( 'aooshop-collections-editor-script', $conf, 'before' ); } if ( function_exists( 'wp_set_script_translations' ) ) { wp_set_script_translations( 'aooshop-products-editor-script', 'aoo-shop', AOOSHOP_DIR . 'languages' ); wp_set_script_translations( 'aooshop-collections-editor-script', 'aoo-shop', AOOSHOP_DIR . 'languages' ); } } } // ── shortcodes ─────────────────────────────────────────────────── public function products( $atts ) { $atts = shortcode_atts( AooShop_Cards::defaults(), (array) $atts, 'aooshop_products' ); return $this->cards->render( $atts ); } public function product( $atts ) { $atts = shortcode_atts( array( 'slug' => '', 'id' => '', 'layout' => 'list', ), (array) $atts, 'aooshop_product' ); return $this->cards->render( array( 'slugs' => $atts['slug'], 'ids' => $atts['id'], 'limit' => 1, 'layout' => $atts['layout'], ) ); } public function collections( $atts ) { return $this->browse->collections( shortcode_atts( AooShop_Browse::collections_defaults(), (array) $atts, 'aooshop_collections' ) ); } public function chips( $atts ) { return $this->browse->chips( shortcode_atts( AooShop_Browse::chips_defaults(), (array) $atts, 'aooshop_chips' ) ); } public function search( $atts ) { return $this->browse->search( shortcode_atts( AooShop_Browse::search_defaults(), (array) $atts, 'aooshop_search' ) ); } public function filters( $atts ) { return $this->browse->filters( shortcode_atts( AooShop_Filters::defaults(), (array) $atts, 'aooshop_filters' ) ); } public function cart_icon() { $html = ''; return (string) apply_filters( 'aooshop_cart_icon_html', $html ); } // ── block ──────────────────────────────────────────────────────── /** Block attributes → card args (same rules as the shortcode). */ public static function block_args( array $attrs ) { $mode = isset( $attrs['mode'] ) ? (string) $attrs['mode'] : 'products'; $args = array( 'limit' => isset( $attrs['limit'] ) ? (int) $attrs['limit'] : AooShop_Cards::DEFAULT_LIMIT, 'sort' => isset( $attrs['sort'] ) ? (string) $attrs['sort'] : '', 'layout' => isset( $attrs['layout'] ) ? (string) $attrs['layout'] : 'grid', 'columns' => isset( $attrs['columns'] ) ? (int) $attrs['columns'] : 0, ); if ( 'category' === $mode ) { $args['category'] = isset( $attrs['category'] ) ? (string) $attrs['category'] : ''; } elseif ( 'brand' === $mode ) { $args['brand'] = isset( $attrs['brand'] ) ? (string) $attrs['brand'] : ''; } elseif ( 'collection' === $mode ) { $args['collection'] = isset( $attrs['collection'] ) ? (string) $attrs['collection'] : ''; $args['price'] = isset( $attrs['price'] ) ? (string) $attrs['price'] : ''; // Optional narrowing. } elseif ( 'price' === $mode ) { // Price bucket only (the whole shop in that budget). $args['price'] = isset( $attrs['price'] ) ? (string) $attrs['price'] : ''; } else { $slugs = array(); foreach ( ( isset( $attrs['products'] ) && is_array( $attrs['products'] ) ) ? $attrs['products'] : array() as $p ) { if ( is_array( $p ) && ! empty( $p['slug'] ) && is_scalar( $p['slug'] ) ) { $slugs[] = (string) $p['slug']; } } $args['slugs'] = $slugs; $args['limit'] = ''; // Picked products: show every pick. } return $args; } public function block( $attrs ) { $wrapper = function_exists( 'get_block_wrapper_attributes' ) ? get_block_wrapper_attributes( array( 'class' => 'aoo-shop-cards' ) ) : ''; return $this->cards->render( self::block_args( (array) $attrs ), $wrapper ); } /** Block `aooshop/collections` attributes → shortcode atts. */ public static function collections_block_args( array $attrs ) { $d = AooShop_Browse::collections_defaults(); $out = array(); foreach ( $d as $k => $v ) { if ( ! isset( $attrs[ $k ] ) ) { $out[ $k ] = $v; } elseif ( is_bool( $attrs[ $k ] ) ) { $out[ $k ] = $attrs[ $k ] ? '1' : '0'; } else { $out[ $k ] = is_scalar( $attrs[ $k ] ) ? (string) $attrs[ $k ] : $v; } } // 0.4: countdownStyle (badge|line|hidden) wins over the legacy boolean `countdown`. if ( isset( $attrs['countdownStyle'] ) && in_array( $attrs['countdownStyle'], AooShop_Browse::COUNTDOWNS, true ) ) { $out['countdown'] = $attrs['countdownStyle']; } return $out; } public function collections_block( $attrs ) { $wrapper = function_exists( 'get_block_wrapper_attributes' ) ? get_block_wrapper_attributes( array( 'class' => 'aoo-sc aoo-sc-collections' ) ) : ''; return $this->browse->collections( self::collections_block_args( (array) $attrs ), $wrapper ); } public function editor_assets() { if ( is_admin() && $this->cards->can_show() ) { $this->cards->enqueue(); aooshop()->cart->enqueue_style(); } } // ── Elementor ──────────────────────────────────────────────────── public function elementor_preview_style() { aooshop()->cart->enqueue_style(); } public function elementor_category( $manager ) { $manager->add_category( 'aoo-shop', array( 'title' => __( 'AOO Shop', 'aoo-shop' ), // Slug `aoo-shop` (saved widgets reference it). 'icon' => 'eicon-cart', ) ); } /** Product picker control of "AOO Products" (SELECT2 + AJAX search — Elementor free has no query control). */ public function elementor_controls( $controls_manager ) { if ( ! self::load_picks_control() ) { return; } $controls_manager->register( new AooShop_Elementor_Picks_Control() ); } private static function load_picks_control() { if ( ! class_exists( '\Elementor\Control_Select2' ) ) { return false; } require_once AOOSHOP_DIR . 'includes/elementor/class-elementor-picks-control.php'; return true; } private static function load_elementor_widgets() { self::load_picks_control(); $dir = AOOSHOP_DIR . 'includes/elementor/'; require_once $dir . 'trait-elementor-frame.php'; require_once $dir . 'class-elementor-shop-part.php'; require_once $dir . 'class-elementor-filters.php'; require_once $dir . 'class-elementor-collections.php'; require_once $dir . 'class-elementor-chips.php'; require_once $dir . 'class-elementor-products.php'; require_once $dir . 'class-elementor-search.php'; require_once $dir . 'class-elementor-cart-icon.php'; } /** * Register `aoo-shop-elementor` with Elementor's frontend styles — Elementor enqueues a page's saved widget styles * in before any widget renders; registered only from get_style_depends() it fell to the footer on every * visit after the first (columns/gap rules arriving late = layout shift). */ public function elementor_register_style() { if ( ! class_exists( '\Elementor\Widget_Base' ) ) { return; } self::load_elementor_widgets(); AooShop_Elementor_Shop_Part::aoo_register_style(); } public function elementor_widget( $widgets_manager ) { if ( ! class_exists( '\Elementor\Widget_Base' ) ) { return; } self::load_elementor_widgets(); $widgets_manager->register( new AooShop_Elementor_Shop_Part() ); $widgets_manager->register( new AooShop_Elementor_Filters() ); $widgets_manager->register( new AooShop_Elementor_Collections() ); $widgets_manager->register( new AooShop_Elementor_Chips() ); $widgets_manager->register( new AooShop_Elementor_Products() ); $widgets_manager->register( new AooShop_Elementor_Search() ); $widgets_manager->register( new AooShop_Elementor_Cart_Icon() ); } }