PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 trunk 1.2.0 All 47 releases
fluent-cart / app / Services / PDF / DefaultPdfStructures.php

DefaultPdfStructures.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Services/PDF/DefaultPdfStructures.php

330 lines 12.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\Services\PDF;
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 /**
10 * Default block-editor PDF structures for FluentCart receipts.
11 *
12 * Design system (A4 Portrait) — minimal:
13 * TEXT #111111 primary text
14 * META #666666 labels / secondary text
15 * BORDER #DDDDDD dividers
16 * WHITE #FFFFFF
17 */
18 class DefaultPdfStructures
19 {
20 private const TEXT = '#111111';
21 private const META = '#666666';
22 private const BORDER = '#DDDDDD';
23 private const WHITE = '#FFFFFF';
24
25 /**
26 * Default meta for all PDF templates.
27 */
28 public static function getDefaultMeta(): array
29 {
30 return [
31 '_fluent_cart_paper_size' => 'A4',
32 '_fluent_cart_orientation' => 'Portrait',
33 '_fluent_cart_font' => 'DejaVuSans',
34 '_fluent_cart_watermark_enabled' => 0,
35 '_fluent_cart_watermark_text' => 'PAID',
36 ];
37 }
38
39 // ── Order Receipt ────────────────────────────────────────────────────────
40
41 /**
42 * @return array{content:string, blocks:array, meta:array}
43 */
44 public static function getDefaultReceiptStructure(): array
45 {
46 $blocks = self::buildReceiptBlocks();
47 return [
48 'content' => self::blocksToContent($blocks),
49 'blocks' => $blocks,
50 'meta' => self::getDefaultMeta(),
51 ];
52 }
53
54 private static function buildReceiptBlocks(): array
55 {
56 $blocks = [];
57
58 $blocks[] = self::header(__('RECEIPT', 'fluent-cart'));
59 $blocks[] = self::addressColumns();
60 $blocks[] = self::metaTable([
61 __('Order Number', 'fluent-cart') => '{{order.invoice_no}}',
62 __('Order Date', 'fluent-cart') => '{{order.created_at}}',
63 __('Payment Method', 'fluent-cart') => '{{order.payment_method_title}}',
64 __('Payment Status', 'fluent-cart') => '{{order.payment_status}}',
65 ]);
66 $blocks[] = self::itemsTable();
67 $blocks[] = self::summaryTable([
68 __('Subtotal', 'fluent-cart') => '{{order.subtotal_formatted}}',
69 __('Discount', 'fluent-cart') => '{{order.discount_total_formatted}}',
70 __('Fees', 'fluent-cart') => '{{order.fee_lines}}',
71 __('Tax', 'fluent-cart') => '{{order.tax_breakdown}}',
72 __('Shipping', 'fluent-cart') => '{{order.shipping_total_formatted}}',
73 ], __('Total', 'fluent-cart'), '{{order.total_amount_formatted}}');
74 $blocks[] = self::footer(__('Thank you for your purchase!', 'fluent-cart'));
75
76 return $blocks;
77 }
78
79 // ── Renewal Receipt ───────────────────────────────────────────────────────
80
81 /**
82 * @return array{content:string, blocks:array, meta:array}
83 */
84 public static function getDefaultRenewalReceiptStructure(): array
85 {
86 $blocks = self::buildRenewalReceiptBlocks();
87 return [
88 'content' => self::blocksToContent($blocks),
89 'blocks' => $blocks,
90 'meta' => self::getDefaultMeta(),
91 ];
92 }
93
94 private static function buildRenewalReceiptBlocks(): array
95 {
96 $blocks = [];
97
98 $blocks[] = self::header(__('RENEWAL RECEIPT', 'fluent-cart'));
99 $blocks[] = self::addressColumns();
100 $blocks[] = self::metaTable([
101 __('Order Number', 'fluent-cart') => '{{order.invoice_no}}',
102 __('Renewal Date', 'fluent-cart') => '{{order.created_at}}',
103 __('Payment Method', 'fluent-cart') => '{{order.payment_method_title}}',
104 __('Payment Status', 'fluent-cart') => '{{order.payment_status}}',
105 ]);
106 $blocks[] = self::itemsTable();
107 $blocks[] = self::summaryTable([
108 __('Subtotal', 'fluent-cart') => '{{order.subtotal_formatted}}',
109 __('Discount', 'fluent-cart') => '{{order.discount_total_formatted}}',
110 __('Fees', 'fluent-cart') => '{{order.fee_lines}}',
111 __('Tax', 'fluent-cart') => '{{order.tax_breakdown}}',
112 ], __('Total', 'fluent-cart'), '{{order.total_amount_formatted}}');
113 $blocks[] = self::footer(__('Thank you for your continued subscription!', 'fluent-cart'));
114
115 return $blocks;
116 }
117
118 // ── Refund Notice ─────────────────────────────────────────────────────────
119
120 /**
121 * @return array{content:string, blocks:array, meta:array}
122 */
123 public static function getDefaultRefundNoticeStructure(): array
124 {
125 $blocks = self::buildRefundNoticeBlocks();
126 $meta = self::getDefaultMeta();
127 $meta['_fluent_cart_watermark_text'] = 'REFUNDED';
128 return [
129 'content' => self::blocksToContent($blocks),
130 'blocks' => $blocks,
131 'meta' => $meta,
132 ];
133 }
134
135 private static function buildRefundNoticeBlocks(): array
136 {
137 $blocks = [];
138
139 $blocks[] = self::header(__('REFUND NOTICE', 'fluent-cart'));
140 $blocks[] = self::addressColumns(__('REFUND TO', 'fluent-cart'));
141 $blocks[] = self::metaTable([
142 __('Order Number', 'fluent-cart') => '{{order.invoice_no}}',
143 __('Order Date', 'fluent-cart') => '{{order.created_at}}',
144 __('Payment Method', 'fluent-cart') => '{{order.payment_method_title}}',
145 ]);
146 $blocks[] = self::itemsTable();
147 $blocks[] = self::summaryTable([
148 __('Original Total', 'fluent-cart') => '{{order.total_amount_formatted}}',
149 ], __('Refund Amount', 'fluent-cart'), '{{order.total_refund}}');
150 $blocks[] = self::footer(__('Your refund has been processed. Please allow a few business days for the amount to appear in your account.', 'fluent-cart'));
151
152 return $blocks;
153 }
154
155 // ── Proforma Invoice ──────────────────────────────────────────────────────
156
157 /**
158 * @return array{content:string, blocks:array, meta:array}
159 */
160 public static function getDefaultProformaInvoiceStructure(): array
161 {
162 $blocks = self::buildProformaInvoiceBlocks();
163 $meta = self::getDefaultMeta();
164 $meta['_fluent_cart_watermark_enabled'] = 0;
165 $meta['_fluent_cart_watermark_text'] = '';
166 return [
167 'content' => self::blocksToContent($blocks),
168 'blocks' => $blocks,
169 'meta' => $meta,
170 ];
171 }
172
173 private static function buildProformaInvoiceBlocks(): array
174 {
175 $blocks = [];
176
177 $blocks[] = self::header(__('INVOICE', 'fluent-cart'));
178 $blocks[] = self::addressColumns();
179 $blocks[] = self::metaTable([
180 __('Invoice Number', 'fluent-cart') => '{{order.invoice_no}}',
181 __('Invoice Date', 'fluent-cart') => '{{order.created_at}}',
182 __('Payment Method', 'fluent-cart') => '{{order.payment_method_title}}',
183 __('Payment Status', 'fluent-cart') => '{{order.payment_status}}',
184 ]);
185 $blocks[] = self::itemsTable();
186 $blocks[] = self::summaryTable([
187 __('Subtotal', 'fluent-cart') => '{{order.subtotal_formatted}}',
188 __('Discount', 'fluent-cart') => '{{order.discount_total_formatted}}',
189 __('Fees', 'fluent-cart') => '{{order.fee_lines}}',
190 __('Tax', 'fluent-cart') => '{{order.tax_breakdown}}',
191 __('Shipping', 'fluent-cart') => '{{order.shipping_total_formatted}}',
192 ], __('Amount Due', 'fluent-cart'), '{{order.total_amount_formatted}}');
193 $blocks[] = self::footer(__('Thank you for your order. Please complete the payment to process your order.', 'fluent-cart'));
194
195 return $blocks;
196 }
197
198 // ── Shared block builders ─────────────────────────────────────────────────
199
200 /**
201 * Document header: logo left, title right, bottom rule.
202 */
203 private static function header(string $title): array
204 {
205 return self::block('fluent-cart/receipt-header', ['title' => $title]);
206 }
207
208 /**
209 * Two-column FROM / BILL TO address block.
210 */
211 private static function addressColumns(?string $billingLabel = null): array
212 {
213 if ($billingLabel === null) {
214 $billingLabel = __('BILL TO', 'fluent-cart');
215 }
216
217 return self::block('fluent-cart/receipt-addresses', ['billingLabel' => $billingLabel]);
218 }
219
220 /**
221 * Key/value meta rows (order number, date, etc.).
222 *
223 * @param array<string,string> $rows label => smartcode
224 */
225 private static function metaTable(array $rows): array
226 {
227 $rowsData = [];
228 foreach ($rows as $label => $value) {
229 $rowsData[] = ['label' => $label, 'value' => $value];
230 }
231
232 return self::block('fluent-cart/receipt-meta', ['rows' => $rowsData]);
233 }
234
235 /**
236 * Line-items table (delegated to the order variable).
237 */
238 private static function itemsTable(): array
239 {
240 // No inner content. The block is dynamic — it registers with
241 // `save: () => null` and renders through
242 // ReceiptItemTableBlockEditor::render(), which ignores $content and emits
243 // its own {{order.pdf_items_table}} placeholder built from these
244 // attributes. Passing content here made blocksToContent() serialise an
245 // open/close pair with `{{order.items_table}}` inside it, and the editor
246 // then failed to validate the saved markup against a save() that produces
247 // nothing. Every other receipt block already passes null and serialises
248 // self-closing.
249 return self::block('fluent-cart/receipt-item-table', [
250 'headerBg' => self::WHITE,
251 'headerColor' => self::TEXT,
252 'bodyColor' => self::TEXT,
253 'borderColor' => self::BORDER,
254 'fontSize' => 11,
255 ]);
256 }
257
258 /**
259 * Payment summary with optional sub-rows and a bold total row.
260 *
261 * @param array<string,string> $rows label => smartcode
262 */
263 private static function summaryTable(array $rows, string $totalLabel, string $totalValue): array
264 {
265 $rowsData = [];
266 foreach ($rows as $label => $value) {
267 $rowsData[] = ['label' => $label, 'value' => $value];
268 }
269
270 return self::block('fluent-cart/receipt-payment-summary', [
271 'rows' => $rowsData,
272 'totalLabel' => $totalLabel,
273 'totalValue' => $totalValue,
274 ]);
275 }
276
277 /**
278 * Footer note, centred, small text.
279 */
280 private static function footer(string $text): array
281 {
282 return self::block('fluent-cart/receipt-footer', ['text' => $text]);
283 }
284
285 // ── Low-level helpers ─────────────────────────────────────────────────────
286
287 private static function block(string $name, array $attrs = [], ?string $content = null, array $innerBlocks = []): array
288 {
289 return [
290 'blockName' => $name,
291 'attrs' => $attrs,
292 'innerHTML' => $content ?? '',
293 'innerContent' => $content !== null ? [$content] : [],
294 'innerBlocks' => $innerBlocks,
295 ];
296 }
297
298 /**
299 * Serialize blocks array to Gutenberg block comment format.
300 */
301 public static function blocksToContent(array $blocks): string
302 {
303 $out = '';
304 foreach ($blocks as $block) {
305 $name = $block['blockName'];
306 $attrs = $block['attrs'] ?? [];
307 $innerBlocks = $block['innerBlocks'] ?? [];
308 $innerHTML = $block['innerHTML'] ?? '';
309
310 $attrJson = !empty($attrs) ? ' ' . wp_json_encode($attrs) : '';
311
312 if (!empty($innerBlocks)) {
313 $out .= "<!-- wp:{$name}{$attrJson} -->\n";
314 $out .= $innerHTML ? "{$innerHTML}\n" : '';
315 $out .= self::blocksToContent($innerBlocks);
316 $out .= "<!-- /wp:{$name} -->\n\n";
317 } else {
318 if ($innerHTML) {
319 $out .= "<!-- wp:{$name}{$attrJson} -->\n";
320 $out .= "{$innerHTML}\n";
321 $out .= "<!-- /wp:{$name} -->\n\n";
322 } else {
323 $out .= "<!-- wp:{$name}{$attrJson} /-->\n\n";
324 }
325 }
326 }
327 return $out;
328 }
329 }
330