| @@ -10,10 +10,8 @@ | ||
| 10 | 10 | defined( 'ABSPATH' ) || exit(); |
| 11 | 11 | |
| 12 | 12 | use RadiusTheme\SB\Helpers\Fns; |
| 13 | 13 | use RadiusTheme\SB\Helpers\BuilderFns; |
| 14 | -use RadiusTheme\SB\Helpers\ElementorDataMap; | |
| 15 | -use RadiusTheme\SB\Models\TemplateSettings; | |
| 16 | 14 | use RadiusTheme\SB\Traits\SingletonTrait; |
| 17 | 15 | |
| 18 | 16 | /** |
| 19 | 17 | * Builder Frontend. |
| @@ -33,15 +31,8 @@ | ||
| 33 | 31 | */ |
| 34 | 32 | private $page_edit_with = ''; |
| 35 | 33 | |
| 36 | 34 | /** |
| 37 | - * Internal cache. | |
| 38 | - * | |
| 39 | - * @var array | |
| 40 | - */ | |
| 41 | - private static $cache = []; | |
| 42 | - | |
| 43 | - /** | |
| 44 | 35 | * Singleton. |
| 45 | 36 | */ |
| 46 | 37 | use SingletonTrait; |
| 47 | 38 | |
| @@ -52,106 +43,11 @@ | ||
| 52 | 43 | // Template Redirect hook run after the global query. |
| 53 | 44 | add_action( 'template_redirect', [ $this, 'frontend_init' ] ); |
| 54 | 45 | add_filter( 'rtsb/builder/set/current/page/type', [ $this, 'builder_page_type' ] ); |
| 55 | 46 | add_filter( 'body_class', [ $this, 'product_page_body_class' ] ); |
| 56 | - add_action( 'pre_get_posts', [ $this, 'override_archive_posts_per_page' ] ); | |
| 57 | 47 | } |
| 58 | 48 | |
| 59 | 49 | /** |
| 60 | - * Override posts_per_page for shop/archive pages based on widget setting. | |
| 61 | - * | |
| 62 | - * @param \WP_Query $query The WP_Query instance. | |
| 63 | - * | |
| 64 | - * @return void | |
| 65 | - */ | |
| 66 | - public function override_archive_posts_per_page( $query ) { | |
| 67 | - if ( is_admin() || ! $query->is_main_query() ) { | |
| 68 | - return; | |
| 69 | - } | |
| 70 | - | |
| 71 | - if ( ! ( is_shop() || is_product_taxonomy() ) ) { | |
| 72 | - return; | |
| 73 | - } | |
| 74 | - | |
| 75 | - $type = is_shop() ? 'shop' : 'archive'; | |
| 76 | - $option_name = BuilderFns::option_name( $type ); | |
| 77 | - $builder_page_id = TemplateSettings::instance()->get_option( $option_name ); | |
| 78 | - | |
| 79 | - if ( empty( $builder_page_id ) ) { | |
| 80 | - $option_name = BuilderFns::option_name( $type, true ); | |
| 81 | - $builder_page_id = TemplateSettings::instance()->get_option( $option_name ); | |
| 82 | - } | |
| 83 | - | |
| 84 | - // Fallback: use shop template for archive pages when no archive template exists. | |
| 85 | - if ( empty( $builder_page_id ) && 'archive' === $type ) { | |
| 86 | - $option_name = BuilderFns::option_name( 'shop' ); | |
| 87 | - $builder_page_id = TemplateSettings::instance()->get_option( $option_name ); | |
| 88 | - | |
| 89 | - if ( empty( $builder_page_id ) ) { | |
| 90 | - $option_name = BuilderFns::option_name( 'shop', true ); | |
| 91 | - $builder_page_id = TemplateSettings::instance()->get_option( $option_name ); | |
| 92 | - } | |
| 93 | - } | |
| 94 | - | |
| 95 | - if ( empty( $builder_page_id ) ) { | |
| 96 | - return; | |
| 97 | - } | |
| 98 | - | |
| 99 | - $per_page = $this->get_archive_widget_per_page( $builder_page_id ); | |
| 100 | - | |
| 101 | - if ( $per_page ) { | |
| 102 | - $query->set( 'posts_per_page', $per_page ); | |
| 103 | - } | |
| 104 | - } | |
| 105 | - | |
| 106 | - /** | |
| 107 | - * Get posts_per_page from the ProductsArchive widget in Elementor data. | |
| 108 | - * | |
| 109 | - * @param int $post_id Builder page ID. | |
| 110 | - * | |
| 111 | - * @return int|false The per page value or false if not set. | |
| 112 | - */ | |
| 113 | - private function get_archive_widget_per_page( $post_id ) { | |
| 114 | - $cache_key = 'rtsb_archive_per_page_' . $post_id; | |
| 115 | - | |
| 116 | - if ( isset( self::$cache[ $cache_key ] ) ) { | |
| 117 | - return self::$cache[ $cache_key ]; | |
| 118 | - } | |
| 119 | - | |
| 120 | - // Check default layout widget first. | |
| 121 | - $widgets = ElementorDataMap::instance()->get_widget_data( 'rtsb-products-archive', null, $post_id ); | |
| 122 | - | |
| 123 | - if ( ! empty( $widgets ) ) { | |
| 124 | - $widget = reset( $widgets ); | |
| 125 | - $per_page = isset( $widget['settings']['posts_per_page'] ) && '' !== $widget['settings']['posts_per_page'] | |
| 126 | - ? absint( $widget['settings']['posts_per_page'] ) | |
| 127 | - : false; | |
| 128 | - | |
| 129 | - self::$cache[ $cache_key ] = $per_page; | |
| 130 | - | |
| 131 | - return $per_page; | |
| 132 | - } | |
| 133 | - | |
| 134 | - // Check custom layout widget. | |
| 135 | - $widgets = ElementorDataMap::instance()->get_widget_data( 'rtsb-products-archive-custom', null, $post_id ); | |
| 136 | - | |
| 137 | - if ( ! empty( $widgets ) ) { | |
| 138 | - $widget = reset( $widgets ); | |
| 139 | - $per_page = isset( $widget['settings']['posts_per_page'] ) && '' !== $widget['settings']['posts_per_page'] | |
| 140 | - ? absint( $widget['settings']['posts_per_page'] ) | |
| 141 | - : false; | |
| 142 | - | |
| 143 | - self::$cache[ $cache_key ] = $per_page; | |
| 144 | - | |
| 145 | - return $per_page; | |
| 146 | - } | |
| 147 | - | |
| 148 | - self::$cache[ $cache_key ] = false; | |
| 149 | - | |
| 150 | - return false; | |
| 151 | - } | |
| 152 | - | |
| 153 | - /** | |
| 154 | 50 | * Builder page Body class. |
| 155 | 51 | * |
| 156 | 52 | * @param array $classes class list. |
| 157 | 53 | * @return array. |
| @@ -156,17 +52,9 @@ | ||
| 156 | 52 | * @param array $classes class list. |
| 157 | 53 | * @return array. |
| 158 | 54 | */ |
| 159 | 55 | public function product_page_body_class( $classes ) { |
| 160 | - $classes[] = 'rtsb-shopbuilder-plugin rtsb-shopbuilder-v-' . RTSB_VERSION . ' rtsb_theme_' . rtsb()->current_theme; | |
| 161 | - | |
| 162 | - if ( defined( 'RTSBPRO_VERSION' ) ) { | |
| 163 | - $classes[] = 'rtsb-shopbuilder-pro-v-' . RTSBPRO_VERSION; | |
| 164 | - } | |
| 165 | - | |
| 166 | - if ( defined( 'ELEMENTOR_PRO_VERSION' ) ) { | |
| 167 | - $classes[] = 'rtsb-elementor-pro-v-' . ELEMENTOR_PRO_VERSION; | |
| 168 | - } | |
| 56 | + $classes[] = 'rtsb-shopbuilder-plugin rtsb_theme_' . rtsb()->current_theme; | |
| 169 | 57 | if ( Fns::is_woocommerce() || BuilderFns::is_builder_preview() ) { |
| 170 | 58 | $classes[] = 'woocommerce-page'; |
| 171 | 59 | } |
| 172 | 60 | |
| @@ -226,99 +114,13 @@ | ||
| 226 | 114 | * |
| 227 | 115 | * @return void |
| 228 | 116 | */ |
| 229 | 117 | public function elementor_frontend() { |
| 230 | - // Priority 15: after Elementor registers style handles (priority 5) and before | |
| 231 | - // its enqueue_styles / after_enqueue_post_styles pass (priority 20) that renders | |
| 232 | - // the atomic-widget (V4) styles collected via the elementor/post/render action. | |
| 233 | - add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_elementor_template_css' ], 15 ); | |
| 234 | 118 | add_action( 'rtsb/builder/before/header', [ $this, 'el_body_class' ] ); |
| 235 | 119 | add_action( 'rtsb/builder/template/main/content', [ $this, 'elementor_template_main_content' ] ); |
| 236 | 120 | } |
| 237 | 121 | |
| 238 | 122 | /** |
| 239 | - * Enqueue the Elementor builder template CSS in the document head. | |
| 240 | - * | |
| 241 | - * ShopBuilder renders the Elementor template on a WooCommerce object (e.g. a product), | |
| 242 | - * so Elementor's own wp_enqueue_scripts pass only fires elementor/post/render for the | |
| 243 | - * queried object — never for the builder template. As a result the template's | |
| 244 | - * document CSS (post-{id}.css) and, on Elementor V4, its atomic-widget styles | |
| 245 | - * (local-{id}-*.css rendered on elementor/frontend/after_enqueue_post_styles) are | |
| 246 | - * never loaded on builder pages although they load on Elementor's own preview. | |
| 247 | - * | |
| 248 | - * Firing elementor/post/render for the template id here — before Elementor's | |
| 249 | - * priority-20 style pass — registers the template with the atomic styles manager so | |
| 250 | - * its styles are generated and enqueued. enqueue_styles() loads the classic post CSS. | |
| 251 | - * | |
| 252 | - * @return void | |
| 253 | - */ | |
| 254 | - public function enqueue_elementor_template_css() { | |
| 255 | - if ( ! $this->builder_page_id || ! class_exists( '\Elementor\Plugin' ) ) { | |
| 256 | - return; | |
| 257 | - } | |
| 258 | - | |
| 259 | - $documents = \Elementor\Plugin::$instance->documents; | |
| 260 | - | |
| 261 | - if ( ! $documents ) { | |
| 262 | - return; | |
| 263 | - } | |
| 264 | - | |
| 265 | - $document = $documents->get( $this->builder_page_id ); | |
| 266 | - | |
| 267 | - if ( ! $document ) { | |
| 268 | - return; | |
| 269 | - } | |
| 270 | - | |
| 271 | - // Register the template with Elementor's render pipeline (atomic V4 styles manager | |
| 272 | - // collects these post ids and renders their styles on after_enqueue_post_styles). | |
| 273 | - do_action( 'elementor/post/render', $this->builder_page_id ); | |
| 274 | - | |
| 275 | - // Classic per-post document CSS (post-{id}.css) and font dependencies. | |
| 276 | - if ( method_exists( $document, 'enqueue_styles' ) ) { | |
| 277 | - $document->enqueue_styles(); | |
| 278 | - } | |
| 279 | - | |
| 280 | - // Elementor hooks its own frontend enqueue_styles() pass only when the | |
| 281 | - // *queried* object is a singular Elementor document (Frontend::init). | |
| 282 | - // On ShopBuilder frontend pages the queried object (shop/archive/cart/ | |
| 283 | - // checkout/product) is never the builder template, so that pass never | |
| 284 | - // runs — leaving the elementor-frontend base stylesheet and, critically, | |
| 285 | - // the V4 atomic styles manager (which renders the template's flexbox/ | |
| 286 | - // container CSS on elementor/frontend/after_enqueue_post_styles) | |
| 287 | - // un-triggered, so containers collapse to full width. We drive the pass | |
| 288 | - // manually below, but ONLY on the real frontend: in the Elementor editor | |
| 289 | - // preview and the ShopBuilder single-template preview the queried object | |
| 290 | - // *is* the template (singular + built with Elementor), so Elementor runs | |
| 291 | - // its own pass and firing ours early would interfere with the editor | |
| 292 | - // preview load (the panel waits on that iframe). Skip both cases. | |
| 293 | - $preview = \Elementor\Plugin::$instance->preview ?? null; | |
| 294 | - $is_editor_view = $preview && method_exists( $preview, 'is_editor_or_preview' ) && $preview->is_editor_or_preview(); | |
| 295 | - $queried_document = $documents->get( get_the_ID() ); | |
| 296 | - $elementor_will_load = is_singular() && $queried_document && $queried_document->is_built_with_elementor(); | |
| 297 | - $frontend = \Elementor\Plugin::$instance->frontend ?? null; | |
| 298 | - | |
| 299 | - if ( $is_editor_view || $elementor_will_load || ! $frontend || ! method_exists( $frontend, 'enqueue_styles' ) ) { | |
| 300 | - return; | |
| 301 | - } | |
| 302 | - | |
| 303 | - // Enqueues the elementor-frontend base stylesheet; one-shot (internal | |
| 304 | - // static guard) so it is a no-op if something already ran it. | |
| 305 | - $frontend->enqueue_styles(); | |
| 306 | - | |
| 307 | - // The V4 Atomic_Styles_Manager only renders CSS for the post ids collected | |
| 308 | - // via elementor/post/render *before* the style pass runs. When another | |
| 309 | - // plugin triggers that pass first — e.g. Elementor Pro's Theme Builder | |
| 310 | - // rendering its own archive location — our builder template id is | |
| 311 | - // registered too late, so its per-template file (local-{id}-frontend-*.css) | |
| 312 | - // is written to disk but never enqueued, and containers stay full width. | |
| 313 | - // Re-firing the atomic trigger after registering the template above makes | |
| 314 | - // the manager enqueue that file. This action's only listener is the atomic | |
| 315 | - // styles manager, and wp_enqueue_style() de-duplicates handles, so the | |
| 316 | - // extra pass is safe. | |
| 317 | - do_action( 'elementor/frontend/after_enqueue_post_styles' ); | |
| 318 | - } | |
| 319 | - | |
| 320 | - /** | |
| 321 | 123 | * Appl for Elementor. |
| 322 | 124 | * |
| 323 | 125 | * @return void |
| 324 | 126 | */ |
| @@ -356,14 +158,9 @@ | ||
| 356 | 158 | * |
| 357 | 159 | * @return void |
| 358 | 160 | */ |
| 359 | 161 | public function el_body_class() { |
| 360 | - if ( ! class_exists( '\Elementor\Plugin' ) || empty( \Elementor\Plugin::$instance->frontend ) ) { | |
| 361 | - return; | |
| 362 | - } | |
| 363 | - | |
| 364 | 162 | $page_template = get_post_meta( $this->builder_page_id, '_wp_page_template', true ); |
| 365 | - | |
| 366 | 163 | if ( 'elementor_canvas' === $page_template ) { |
| 367 | 164 | \Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-canvas' ); |
| 368 | 165 | } elseif ( 'elementor_header_footer' === $page_template ) { |
| 369 | 166 | \Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-full-width' ); |