PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.0
ShopBuilder – WooCommerce Builder For Elementor v3.4.0
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
← All changes | app/Controllers/Hooks/BuilderHooks.php +165 -9 2.2.0 → 3.4.0 View file →
@@ -10,8 +10,10 @@
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;
14 16 use RadiusTheme\SB\Traits\SingletonTrait;
15 17
16 18 /**
17 19 * Builder Frontend.
@@ -31,8 +33,15 @@
31 33 */
32 34 private $page_edit_with = '';
33 35
34 36 /**
37 + * Internal cache.
38 + *
39 + * @var array
40 + */
41 + private static $cache = [];
42 +
43 + /**
35 44 * Singleton.
36 45 */
37 46 use SingletonTrait;
38 47
@@ -43,11 +52,106 @@
43 52 // Template Redirect hook run after the global query.
44 53 add_action( 'template_redirect', [ $this, 'frontend_init' ] );
45 54 add_filter( 'rtsb/builder/set/current/page/type', [ $this, 'builder_page_type' ] );
46 55 add_filter( 'body_class', [ $this, 'product_page_body_class' ] );
56 + add_action( 'pre_get_posts', [ $this, 'override_archive_posts_per_page' ] );
47 57 }
48 58
49 59 /**
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 + /**
50 154 * Builder page Body class.
51 155 *
52 156 * @param array $classes class list.
53 157 * @return array.
@@ -52,9 +156,13 @@
52 156 * @param array $classes class list.
53 157 * @return array.
54 158 */
55 159 public function product_page_body_class( $classes ) {
56 - $classes[] = 'rtsb-shopbuilder-plugin rtsb_theme_' . rtsb()->current_theme;
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 + }
57 165 if ( Fns::is_woocommerce() || BuilderFns::is_builder_preview() ) {
58 166 $classes[] = 'woocommerce-page';
59 167 }
60 168
@@ -67,13 +175,9 @@
67 175 }
68 176
69 177 if ( BuilderFns::is_cart() ) {
70 178 $classes[] = 'woocommerce-cart';
71 - // $indexToRemove = array_search( 'woocommerce' , $classes);
72 - // if ($indexToRemove !== false) {
73 - // unset($classes[$indexToRemove]);
74 - // }
75 - } else{
179 + } else {
76 180 $classes[] = 'woocommerce';
77 181 }
78 182
79 183 if ( BuilderFns::is_checkout() ) {
@@ -107,9 +211,9 @@
107 211 $this->page_edit_with = Fns::page_edit_with( $this->builder_page_id );
108 212 if ( $this->builder_page_id ) {
109 213 if ( did_action( 'elementor/loaded' ) && 'elementor' === $this->page_edit_with ) {
110 214 $this->elementor_frontend();
111 - } else {
215 + } elseif ( 'gutenberg' === $this->page_edit_with ) {
112 216 $this->gutenberg_frontend();
113 217 }
114 218 }
115 219 }
@@ -118,13 +222,60 @@
118 222 *
119 223 * @return void
120 224 */
121 225 public function elementor_frontend() {
226 + // Priority 15: after Elementor registers style handles (priority 5) and before
227 + // its enqueue_styles / after_enqueue_post_styles pass (priority 20) that renders
228 + // the atomic-widget (V4) styles collected via the elementor/post/render action.
229 + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_elementor_template_css' ], 15 );
122 230 add_action( 'rtsb/builder/before/header', [ $this, 'el_body_class' ] );
123 231 add_action( 'rtsb/builder/template/main/content', [ $this, 'elementor_template_main_content' ] );
124 232 }
125 233
126 234 /**
235 + * Enqueue the Elementor builder template CSS in the document head.
236 + *
237 + * ShopBuilder renders the Elementor template on a WooCommerce object (e.g. a product),
238 + * so Elementor's own wp_enqueue_scripts pass only fires elementor/post/render for the
239 + * queried object — never for the builder template. As a result the template's
240 + * document CSS (post-{id}.css) and, on Elementor V4, its atomic-widget styles
241 + * (local-{id}-*.css rendered on elementor/frontend/after_enqueue_post_styles) are
242 + * never loaded on builder pages although they load on Elementor's own preview.
243 + *
244 + * Firing elementor/post/render for the template id here — before Elementor's
245 + * priority-20 style pass — registers the template with the atomic styles manager so
246 + * its styles are generated and enqueued. enqueue_styles() loads the classic post CSS.
247 + *
248 + * @return void
249 + */
250 + public function enqueue_elementor_template_css() {
251 + if ( ! $this->builder_page_id || ! class_exists( '\Elementor\Plugin' ) ) {
252 + return;
253 + }
254 +
255 + $documents = \Elementor\Plugin::$instance->documents;
256 +
257 + if ( ! $documents ) {
258 + return;
259 + }
260 +
261 + $document = $documents->get( $this->builder_page_id );
262 +
263 + if ( ! $document ) {
264 + return;
265 + }
266 +
267 + // Register the template with Elementor's render pipeline (atomic V4 styles manager
268 + // collects these post ids and renders their styles on after_enqueue_post_styles).
269 + do_action( 'elementor/post/render', $this->builder_page_id );
270 +
271 + // Classic per-post document CSS (post-{id}.css) and font dependencies.
272 + if ( method_exists( $document, 'enqueue_styles' ) ) {
273 + $document->enqueue_styles();
274 + }
275 + }
276 +
277 + /**
127 278 * Appl for Elementor.
128 279 *
129 280 * @return void
130 281 */
@@ -150,10 +301,10 @@
150 301 wp_enqueue_script( 'wc-checkout' );
151 302 } elseif ( BuilderFns::is_cart() ) {
152 303 $type = 'cart';
153 304 }
154 - wp_enqueue_style( 'rtsb-frontend' );
155 - wp_enqueue_script( 'rtsb-public' );
305 + wp_enqueue_style( Fns::optimized_handle( 'rtsb-frontend' ) );
306 + wp_enqueue_script( Fns::optimized_handle( 'rtsb-public' ) );
156 307
157 308 return apply_filters( 'rtsb/builder/set/current/page/type/external', $type );
158 309 }
159 310
@@ -162,9 +313,14 @@
162 313 *
163 314 * @return void
164 315 */
165 316 public function el_body_class() {
317 + if ( ! class_exists( '\Elementor\Plugin' ) || empty( \Elementor\Plugin::$instance->frontend ) ) {
318 + return;
319 + }
320 +
166 321 $page_template = get_post_meta( $this->builder_page_id, '_wp_page_template', true );
322 +
167 323 if ( 'elementor_canvas' === $page_template ) {
168 324 \Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-canvas' );
169 325 } elseif ( 'elementor_header_footer' === $page_template ) {
170 326 \Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-full-width' );