PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
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 / Cart / InnerBlocks / InnerBlocks.php

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

272 lines 8.6 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\Cart\InnerBlocks;
4
5 use FluentCart\Api\Contracts\CanEnqueue;
6 use FluentCart\App\Helpers\CartHelper;
7 use FluentCart\App\Services\Renderer\CartRenderer;
8 use FluentCart\App\Services\Translations\TransStrings;
9 use FluentCart\Framework\Support\Arr;
10
11 /**
12 * Child blocks of fluent-cart/cart.
13 *
14 * The cart page is three regions — the item rows, the totals, and the checkout
15 * button — and CartRenderer already exposes each as its own method. Registering
16 * them as separate blocks lets an editor reorder or drop a region, or put their
17 * own blocks between them, the same way the Checkout block works.
18 */
19 class InnerBlocks
20 {
21 use CanEnqueue;
22
23 public static $parentBlock = 'fluent-cart/cart';
24
25 /**
26 * No style supports on any cart block.
27 *
28 * The cart's appearance is owned end to end by core's cart stylesheet, which
29 * both this block and the [fluent_cart_cart] shortcode render through. Block
30 * supports would layer wrapper-level colour, spacing and border on top of
31 * that, so the Styles tab is deliberately empty: `html => false` is a
32 * capability switch, not a style.
33 */
34 private static function blockSupports(): array
35 {
36 return [
37 'html' => false,
38 ];
39 }
40
41 private $cart = null;
42
43 private $renderer = null;
44
45 private function getCart()
46 {
47 if ($this->cart === null) {
48 $this->cart = CartHelper::getCart();
49 }
50
51 return $this->cart;
52 }
53
54 /**
55 * One renderer per request, shared by all three children. Building a fresh
56 * CartRenderer per block would re-run loadBundleChild() on every region.
57 */
58 private function getRenderer(): CartRenderer
59 {
60 if ($this->renderer === null) {
61 $cart = $this->getCart();
62 $this->renderer = new CartRenderer($cart->cart_data ?? []);
63 }
64
65 return $this->renderer;
66 }
67
68 private function hasItems(): bool
69 {
70 $cart = $this->getCart();
71
72 return $cart && !empty($cart->cart_data);
73 }
74
75 public static function register()
76 {
77 $self = new self();
78
79 foreach ($self->getInnerBlocks() as $block) {
80 register_block_type($block['slug'], [
81 'apiVersion' => 3,
82 'api_version' => 3,
83 'version' => 3,
84 'title' => $block['title'],
85 'category' => 'fluent-cart',
86 'parent' => array_merge($block['parent'] ?? [], [static::$parentBlock]),
87 'render_callback' => $block['callback'],
88 'supports' => Arr::get($block, 'supports', []),
89 'attributes' => Arr::get($block, 'attributes', []),
90 ]);
91 }
92
93 add_action('enqueue_block_editor_assets', function () use ($self) {
94 $self->enqueueScripts();
95 });
96 }
97
98 public function getInnerBlocks(): array
99 {
100 return [
101 [
102 'title' => __('Cart Items', 'fluent-cart'),
103 'slug' => 'fluent-cart/cart-items',
104 'callback' => [$this, 'renderItems'],
105 'component' => 'CartItemsBlock',
106 'icon' => 'list-view',
107 'supports' => self::blockSupports(),
108 ],
109 [
110 'title' => __('Cart Total', 'fluent-cart'),
111 'slug' => 'fluent-cart/cart-total',
112 'callback' => [$this, 'renderTotal'],
113 'component' => 'CartTotalBlock',
114 'icon' => 'money-alt',
115 // Empty default, not the translated string: an empty value means
116 // "use whatever CartRenderer says", so the label keeps following
117 // the site language until someone deliberately overrides it.
118 'attributes' => [
119 'total_label' => [
120 'type' => 'string',
121 'default' => '',
122 ],
123 ],
124 'supports' => self::blockSupports(),
125 ],
126 [
127 'title' => __('Cart Checkout Button', 'fluent-cart'),
128 'slug' => 'fluent-cart/cart-checkout-button',
129 'callback' => [$this, 'renderCheckoutButton'],
130 'component' => 'CartCheckoutButtonBlock',
131 'icon' => 'button',
132 'attributes' => [
133 'button_text' => [
134 'type' => 'string',
135 'default' => '',
136 ],
137 ],
138 'supports' => self::blockSupports(),
139 ],
140 ];
141 }
142
143 public function renderItems($attributes = [], $content = '', $block = null)
144 {
145 if (!$this->hasItems()) {
146 return '';
147 }
148
149 ob_start();
150 $this->getRenderer()->renderItems();
151
152 return ob_get_clean();
153 }
154
155 public function renderTotal($attributes = [], $content = '', $block = null)
156 {
157 if (!$this->hasItems()) {
158 return '';
159 }
160
161 return $this->renderWithLabelOverride(
162 'fluent_cart/cart/total_label',
163 Arr::get((array) $attributes, 'total_label', ''),
164 function () {
165 $this->getRenderer()->renderTotal();
166 }
167 );
168 }
169
170 /**
171 * Render a region with its label temporarily overridden.
172 *
173 * The strings live in CartRenderer so the block and the [fluent_cart_cart]
174 * shortcode stay identical; a custom label is applied through the renderer's
175 * own filter rather than by rebuilding its markup here, which would drift.
176 * The filter is removed straight after so it cannot leak into anything else
177 * rendered later in the request.
178 *
179 * @param string $hook
180 * @param string $label
181 * @param callable $render
182 * @return string
183 */
184 private function renderWithLabelOverride($hook, $label, callable $render)
185 {
186 $label = sanitize_text_field((string) $label);
187 $override = null;
188
189 if ($label !== '') {
190 $override = function () use ($label) {
191 return $label;
192 };
193 add_filter($hook, $override);
194 }
195
196 ob_start();
197 $render();
198 $html = ob_get_clean();
199
200 if ($override) {
201 remove_filter($hook, $override);
202 }
203
204 return $html;
205 }
206
207 public function renderCheckoutButton($attributes = [], $content = '', $block = null)
208 {
209 if (!$this->hasItems()) {
210 return '';
211 }
212
213 return $this->renderWithLabelOverride(
214 'fluent_cart/cart/checkout_button_text',
215 Arr::get((array) $attributes, 'button_text', ''),
216 function () {
217 // The wrapper carries the data attribute the cart JS uses to
218 // swap the button state, so it has to stay around the button.
219 ?>
220 <div class="fluent-cart-cart-cart-button-wrap" data-fluent-cart-cart-checkout-button-wrap>
221 <?php $this->getRenderer()->renderCheckoutButton(); ?>
222 </div>
223 <?php
224 }
225 );
226 }
227
228 public function localizeData(): array
229 {
230 return [
231 $this->getLocalizationKey() => [
232 'blocks' => array_map(function ($block) {
233 return Arr::except($block, ['callback']);
234 }, $this->getInnerBlocks()),
235 ],
236 // The child components in this bundle call blocktranslate(), so the
237 // bundle carries the string map itself rather than relying on the
238 // container's script having printed the same global first.
239 'fluent_cart_block_translation' => TransStrings::blockStrings(),
240 ];
241 }
242
243 public function getScripts(): array
244 {
245 return [
246 [
247 'source' => 'admin/BlockEditor/ReactSupport.js',
248 'dependencies' => ['wp-blocks', 'wp-components']
249 ],
250 [
251 'source' => 'admin/BlockEditor/Cart/InnerBlocks/InnerBlocks.jsx',
252 'dependencies' => ['wp-blocks', 'wp-components', 'wp-block-editor']
253 ]
254 ];
255 }
256
257 protected function generateEnqueueSlug(): string
258 {
259 return 'fluent_cart_cart_inner_blocks';
260 }
261
262 /**
263 * Pinned rather than derived: the trait's default appends '_data' to the
264 * enqueue slug, and the JS reads this global by name. Checkout's inner
265 * blocks pin theirs the same way.
266 */
267 protected function getLocalizationKey(): string
268 {
269 return 'fluent_cart_cart_inner_blocks';
270 }
271 }
272