PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.21
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.21
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
fluent-cart / app / Hooks / Handlers / BlockEditors / ProductCarousel / InnerBlocks / InnerBlocks.php

InnerBlocks.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.21, at app/Hooks/Handlers/BlockEditors/ProductCarousel/InnerBlocks/InnerBlocks.php

538 lines 19.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Hooks\Handlers\BlockEditors\ProductCarousel\InnerBlocks;
4
5 use FluentCart\Api\Contracts\CanEnqueue;
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\App;
8 use FluentCart\App\Helpers\Helper;
9 use FluentCart\App\Models\Product;
10 use FluentCart\App\Services\TemplateService;
11 use FluentCart\App\Services\Translations\TransStrings;
12 use FluentCart\Framework\Support\Arr;
13 use FluentCart\App\Vite;
14
15 class InnerBlocks
16 {
17 use CanEnqueue;
18
19 public static $parentBlock = 'fluent-cart/product-carousel';
20
21 public array $blocks = [];
22
23 public static function textBlockSupport(): array
24 {
25 return [
26 'html' => false,
27 'align' => ['left', 'center', 'right'],
28 'typography' => [
29 'fontSize' => true,
30 'lineHeight' => true
31 ],
32 'spacing' => [
33 'margin' => true,
34 'padding' => true
35 ],
36 'color' => [
37 'text' => true,
38 ]
39 ];
40 }
41
42 public static function buttonBlockSupport(): array
43 {
44 return [
45 'html' => false,
46 'align' => ['left', 'center', 'right'],
47 'typography' => [
48 'fontSize' => true,
49 'lineHeight' => true,
50 'fontWeight' => true,
51 'textTransform' => true,
52 ],
53 'spacing' => [
54 'margin' => true,
55 'padding' => true,
56 ],
57 'color' => [
58 'text' => true,
59 'background' => true,
60 ],
61 'border' => [
62 'radius' => true,
63 'color' => true,
64 'width' => true,
65 ],
66 'shadow' => true,
67 ];
68 }
69
70 public static function register()
71 {
72 $self = new self();
73 $blocks = $self->getInnerBlocks();
74
75 foreach ($blocks as $block) {
76 // register_block_type($block['slug'], [
77 // 'apiVersion' => 3,
78 // 'api_version' => 3,
79 // 'version' => 3,
80 // 'title' => $block['title'],
81 // 'parent' => array_merge($block['parent'] ?? [], [static::$parentBlock]),
82 // 'render_callback' => $block['callback'],
83 // 'supports' => Arr::get($block, 'supports', []),
84 // 'attributes' => Arr::get($block, 'attributes', []),
85 // 'uses_context' => Arr::get($block, 'uses_context', []),
86 // ]);
87 $args = [
88 'apiVersion' => 3,
89 'api_version' => 3,
90 'version' => 3,
91 'title' => $block['title'],
92 'category' => 'fluent-cart',
93 'parent' => array_merge($block['parent'] ?? [], [static::$parentBlock]),
94 'supports' => Arr::get($block, 'supports', []),
95 'attributes' => Arr::get($block, 'attributes', []),
96 'uses_context' => Arr::get($block, 'uses_context', []),
97 ];
98
99 // Only add render_callback if it exists
100 if (!empty($block['callback']) && is_callable($block['callback'])) {
101 $args['render_callback'] = $block['callback'];
102 }
103
104 register_block_type($block['slug'], $args);
105
106 }
107
108 add_action('enqueue_block_editor_assets', function () use ($self) {
109 $self->enqueueScripts();
110 });
111 }
112
113 public function getInnerBlocks(): array
114 {
115 return [
116 [
117 'title' => __('Product Carousel Loop', 'fluent-cart'),
118 'slug' => 'fluent-cart/product-carousel-loop',
119 'callback' => [$this, 'renderProductCarouselLoop'],
120 'component' => 'ProductCarouselLoopBlock',
121 'icon' => 'screenoptions',
122 'parent' => ['fluent-cart/product-carousel'],
123 'supports' => [
124 'align' => ['wide', 'full'],
125 'html' => false,
126 ],
127 'attributes' => array_merge(
128 \WP_Block_Type_Registry::get_instance()->get_registered('core/group')->attributes,
129 [
130 'customAttr' => ['type' => 'string'],
131 ]
132 ),
133 'uses_context' => [
134 'fluent-cart/carousel_settings',
135 'fluent-cart/product_ids',
136 'fluent-cart/has_controls',
137 'fluent-cart/has_pagination',
138 ],
139 ],
140 [
141 'title' => __('Carousel Controls', 'fluent-cart'),
142 'slug' => 'fluent-cart/product-carousel-controls',
143 'component' => 'CarouselControlsBlock',
144 'icon' => 'controls-repeat',
145 'parent' => ['fluent-cart/product-carousel'],
146 'supports' => ['html' => false],
147 'uses_context' => [
148 'fluent-cart/carousel_settings',
149 ],
150 ],
151 [
152 'title' => __('Carousel Pagination', 'fluent-cart'),
153 'slug' => 'fluent-cart/product-carousel-pagination',
154 'component' => 'CarouselPaginationBlock',
155 'icon' => 'ellipsis',
156 'parent' => ['fluent-cart/product-carousel'],
157 'supports' => ['html' => false],
158 'uses_context' => [
159 'fluent-cart/carousel_settings',
160 ],
161 ],
162
163 ];
164 }
165
166 public function getProductFromBlockContext($block)
167 {
168 // Editor preview
169 if (!empty($block->context['fluent_cart_current_product'])) {
170 return $block->context['fluent_cart_current_product'];
171 }
172
173 // Frontend fallback
174 return fluent_cart_get_current_product();
175 }
176
177 protected static function getDefaultCarouselSettings(): array
178 {
179 return [
180 // Core
181 'slidesToShow' => 3,
182 'spaceBetween' => 4,
183 'infinite' => 'no',
184
185 // Autoplay
186 'autoplay' => 'yes',
187 'autoplayDelay' => 3000,
188
189 // Controls
190 'arrows' => 'yes',
191 'arrowsSize' => 'md', // sm | md | lg
192
193 // Pagination
194 'pagination' => 'yes',
195 'paginationType' => 'bullets', // bullets | fraction | progress | segmented
196 ];
197 }
198
199 protected function getCarouselSettingsFromContext($block): array
200 {
201 $contextSettings = Arr::get($block->context, 'fluent-cart/carousel_settings', []);
202
203 return array_merge(
204 static::getDefaultCarouselSettings(),
205 is_array($contextSettings) ? $contextSettings : []
206 );
207 }
208
209 public function renderTitle($attributes, $content, $block): string
210 {
211
212 $product = $this->getProductFromBlockContext($block);
213
214 $isLink = Arr::get($attributes, 'isLink', true);
215 $target = Arr::get($attributes, 'linkTarget', '_self');
216
217
218 if (empty($product)) {
219 return 'not found';
220 }
221
222 $wrapper_attributes = get_block_wrapper_attributes(
223 [
224 'class' => 'fct-product-card-title wc-block-grid__product-title',
225 ]
226 );
227
228 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
229 ob_start();
230 $render->renderTitle($wrapper_attributes, [
231 'isLink' => $isLink,
232 'target' => $target,
233 ]);
234 return ob_get_clean();
235 }
236
237 public function renderPrice($attributes, $content, $block): string
238 {
239 $product = $this->getProductFromBlockContext($block);
240
241 $defaultPriceFormat = 'starts_from';
242 if (is_singular('fluent-products') && TemplateService::getCurrentFcPageType() === 'single_product') {
243 $defaultPriceFormat = 'range';
244 }
245 $priceFormat = Arr::get($block->context, 'fluent-cart/price_format', $defaultPriceFormat);
246
247 if (empty($product)) {
248 return '';
249 }
250
251 $wrapper_attributes = get_block_wrapper_attributes(
252 [
253 'class' => 'fct-product-card-prices',
254 ]
255 );
256
257 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product, [
258 'price_format' => $priceFormat,
259 ]);
260 ob_start();
261 $render->renderPrices($wrapper_attributes);
262 return ob_get_clean();
263 }
264
265 public function renderImage($attributes, $content, $block): string
266 {
267 $product = $this->getProductFromBlockContext($block);
268 if (empty($product)) {
269 return '';
270 }
271 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
272 $renderedImage = '';
273
274 $innerBlocksContent = '';
275 if ($block instanceof \WP_Block && !empty($block->inner_blocks)) {
276 $blockContext = $block->context;
277 foreach ($block->inner_blocks as $inner_block) {
278 if (isset($inner_block->parsed_block)) {
279 $innerContext = array_merge($inner_block->context, $blockContext);
280 $instance = new \WP_Block($inner_block->parsed_block, $innerContext);
281 $innerBlocksContent .= $instance->render();
282 }
283 }
284 }
285 ob_start();
286 $render->renderProductImage();
287 $renderedImage = ob_get_clean();
288
289 if (!empty($innerBlocksContent)) {
290 return sprintf(
291 "<div class='fct-product-image-wrap' style='position: relative; overflow: clip;'>
292 %s
293 <div class='fct-product-image-inner-blocks'>
294 %s
295 </div>
296 </div>",
297 $renderedImage,
298 $innerBlocksContent
299 );
300 }
301
302 return $renderedImage;
303 }
304
305 public function renderButtons($attributes, $content, $block): string
306 {
307 $product = $this->getProductFromBlockContext($block);
308 if (empty($product)) {
309 return '';
310 }
311 $wrapper_attributes = get_block_wrapper_attributes();
312 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
313 ob_start();
314 $render->showBuyButton($wrapper_attributes);
315 return ob_get_clean();
316 }
317
318 public function renderExcerpt($attributes, $content, $block): string
319 {
320 $product = $this->getProductFromBlockContext($block);
321 if (empty($product) || empty($product->post_excerpt)) {
322 return '';
323 }
324 $wrapper_attributes = get_block_wrapper_attributes(
325 [
326 'class' => 'fct-product-card-excerpt',
327 ]
328 );
329 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
330 ob_start();
331 $render->renderExcerpt($wrapper_attributes);
332 return ob_get_clean();
333 }
334
335 protected function isSlideBlock($block): bool
336 {
337 return !in_array($block->name, [
338 'fluent-cart/product-carousel-controls',
339 'fluent-cart/product-carousel-pagination',
340 ], true);
341 }
342
343 public function renderProductCarouselLoop($attributes, $content, $block): string
344 {
345 $carouselSettings = $this->getCarouselSettingsFromContext($block);
346
347 $hasControls = Arr::get($block->context, 'fluent-cart/has_controls', 'yes');
348 $hasPagination = Arr::get($block->context, 'fluent-cart/has_pagination', 'yes');
349
350 if ($hasControls !== 'yes') {
351 $carouselSettings['arrows'] = 'no';
352 }
353
354 if ($hasPagination !== 'yes') {
355 $carouselSettings['pagination'] = 'no';
356 }
357
358 $productIds = Arr::get($block->context,'fluent-cart/product_ids', []);
359
360 $clientId = Arr::get($attributes, 'wp_client_id', '');
361
362 $products = Product::query()->whereIn('id', $productIds)->get();
363 if ($products->count() === 0) {
364 return '';
365 }
366
367 $blockContext = $block->context;
368
369 $innerBlocksContent = '<div class="fct-product-carousel-wrapper">';
370 $innerBlocksContent .= '<div class="swiper fct-product-carousel"
371 data-fluent-cart-product-carousel
372 data-carousel-settings="' . esc_attr(wp_json_encode($carouselSettings)) . '">';
373 $innerBlocksContent .= '<div class="swiper-wrapper">';
374 foreach ($products as $key => $product) {
375 setup_postdata($product->ID);
376 if ($block instanceof \WP_Block && !empty($block->inner_blocks)) {
377 $innerBlocksContent .= '<div class="swiper-slide" data-id="' . esc_attr($product->ID) . '">';
378 if ($key === 0) {
379 //add attribute provider with value wp-block
380 $innerBlocksContent .= '<div class="fct-product-card" data-template-provider="wp-block" data-fct-product-card data-fluent-client-id="' . esc_attr($clientId) . '">';
381 } else {
382 $innerBlocksContent .= '<div class="fct-product-card" data-fct-product-card>';
383 }
384
385 // foreach ($block->inner_blocks as $inner_block) {
386 // if (isset($inner_block->parsed_block)) {
387 // $innerContext = array_merge($inner_block->context, $blockContext);
388 // $instance = new \WP_Block($inner_block->parsed_block, $innerContext);
389 // $innerBlocksContent .= $instance->render();
390 // }
391 // }
392 foreach ($block->inner_blocks as $inner_block) {
393 if (
394 !isset($inner_block->parsed_block) ||
395 !$this->isSlideBlock($inner_block)
396 ) {
397 continue;
398 }
399
400 $innerContext = array_merge($inner_block->context, $blockContext);
401 $instance = new \WP_Block($inner_block->parsed_block, $innerContext);
402 $innerBlocksContent .= $instance->render();
403 }
404 $innerBlocksContent .= '</div></div>';
405 }
406 }
407 $innerBlocksContent .= '</div>';
408
409 // Navigation arrows (INSIDE swiper, OUTSIDE wrapper)
410 if ($hasControls === 'yes') {
411 $innerBlocksContent .= $this->renderCarouselControls([], '', $block);
412 }
413
414 // Pagination (INSIDE swiper, OUTSIDE wrapper)
415 if ($hasPagination === 'yes') {
416 $innerBlocksContent .= $this->renderCarouselPagination([], '', $block);
417 }
418
419 $innerBlocksContent .= '</div></div>';
420
421 wp_reset_postdata();
422
423 return $innerBlocksContent;
424 }
425
426 public function renderCarouselControls($attributes, $content, $block): string
427 {
428 $settings = $this->getCarouselSettingsFromContext($block);
429
430 if (Arr::get($settings, 'arrows') !== 'yes') {
431 return '';
432 }
433
434 $size = Arr::get($settings, 'arrowsSize');
435
436 // Navigation arrows (INSIDE swiper, OUTSIDE wrapper)
437 return sprintf(
438 '<div class="fct-carousel-controls fct-arrows-%s">
439 <div class="swiper-button-prev" aria-label="%s">
440 <svg width="12" height="12" viewBox="0 0 24 24" fill="none"
441 stroke="currentColor" stroke-width="2"
442 stroke-linecap="round" stroke-linejoin="round">
443 <polyline points="15 18 9 12 15 6"></polyline>
444 </svg>
445 </div>
446 <div class="swiper-button-next" aria-label="%s">
447 <svg width="12" height="12" viewBox="0 0 24 24" fill="none"
448 stroke="currentColor" stroke-width="2"
449 stroke-linecap="round" stroke-linejoin="round">
450 <polyline points="9 18 15 12 9 6"></polyline>
451 </svg>
452 </div>
453 </div>',
454 esc_attr($size),
455 esc_attr__('Previous slide', 'fluent-cart'),
456 esc_attr__('Next slide', 'fluent-cart')
457 );
458 }
459
460 public function renderCarouselPagination($attributes, $content, $block): string
461 {
462 $settings = $this->getCarouselSettingsFromContext($block);
463
464 // Backward compatibility: check both 'pagination' and legacy 'dots'
465 $hasPagination = Arr::get($settings, 'pagination', Arr::get($settings, 'dots', 'no'));
466 if ($hasPagination !== 'yes') {
467 return '';
468 }
469
470 $type = Arr::get($settings, 'paginationType');
471
472 return sprintf(
473 '<div class="fct-carousel-pagination fct-pagination-%s">
474 <div class="swiper-pagination"></div>
475 </div>',
476 esc_attr($type)
477 );
478 }
479
480 protected function getLocalizationKey(): string
481 {
482 return 'fluent_cart_product_carousel_inner_blocks';
483 }
484
485 public function localizeData(): array
486 {
487 return [
488 $this->getLocalizationKey() => [
489 'blocks' => Arr::except($this->getInnerBlocks(), ['callback']),
490 ],
491 'fluentcart_single_product_vars' => [
492 'trans' => TransStrings::singleProductPageString(),
493 'cart_button_text' => apply_filters('fluent_cart/product/add_to_cart_text', __('Add To Cart', 'fluent-cart'), []),
494 // App::storeSettings()->get('cart_button_text', __('Add to Cart', 'fluent-cart')),
495 'out_of_stock_button_text' => apply_filters('fluent_cart/product/out_of_stock_text', __('Not Available', 'fluent-cart'), []),
496 'in_stock_status' => Helper::IN_STOCK,
497 'out_of_stock_status' => Helper::OUT_OF_STOCK,
498 'enable_image_zoom' => (new StoreSettings())->get('enable_image_zoom_in_single_product'),
499 'enable_image_zoom_in_modal' => (new StoreSettings())->get('enable_image_zoom_in_modal')
500 ]
501 ];
502 }
503
504 public function getStyles(): array
505 {
506 return [
507 'public/single-product/single-product.scss',
508 ];
509 }
510
511 public function getScripts(): array
512 {
513 $scripts = [
514 [
515 'source' => 'admin/BlockEditor/ReactSupport.js',
516 'dependencies' => ['wp-blocks', 'wp-components']
517 ],
518 [
519 'source' => 'admin/BlockEditor/ProductCarousel/InnerBlocks/InnerBlocks.jsx',
520 'dependencies' => ['wp-blocks', 'wp-components', 'wp-block-editor']
521 ],
522 ];
523
524 if (App::isDevMode() || true) {
525 $scripts[] = [
526 'source' => 'admin/BlockEditor/ReactSupport.js',
527 'dependencies' => ['wp-blocks', 'wp-components']
528 ];
529 }
530 return $scripts;
531 }
532
533 protected function generateEnqueueSlug(): string
534 {
535 return 'fluent_cart_product_carousel_inner_blocks';
536 }
537 }
538