PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.2
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.2
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / includes / Templates / Thermal / Thermal_Renderer.php

Thermal_Renderer.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.2, at includes/Templates/Thermal/Thermal_Renderer.php

176 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Thermal Renderer Orchestrator Class.
4 *
5 * Ties together the thermal pipeline shipped in earlier phases: it Mustache-renders
6 * a thermal template against canonical receipt data, parses the resulting markup
7 * into an AST, and emits the requested wire format — ESC/POS or StarPRNT command
8 * bytes, Epson ePOS-Print XML, Star Document Markup, plain text, or a PNG raster
9 * of the whole receipt.
10 *
11 * The Mustache engine configuration mirrors Logicless_Renderer so that data values
12 * containing XML-significant characters (`&`, `<`, `>`, quotes) are escaped to valid
13 * XML before the markup is parsed.
14 *
15 * @author Paul Kilmurray <paul@kilbot.com>
16 *
17 * @see http://wcpos.com
18 * @package WCPOS\WooCommercePOS
19 */
20
21 namespace WCPOS\WooCommercePOS\Templates\Thermal;
22
23 use InvalidArgumentException;
24 use Mustache\Engine as Mustache_Engine;
25 use WCPOS\WooCommercePOS\Services\Receipt_Data_Builder;
26 use WCPOS\WooCommercePOS\Services\Receipt_Data_Schema;
27 use WC_Abstract_Order;
28
29 /**
30 * Thermal_Renderer class.
31 */
32 class Thermal_Renderer {
33
34 /**
35 * Render a thermal template for an order into the requested wire format.
36 *
37 * @param array $template Template metadata/content.
38 * @param WC_Abstract_Order $order The order to render.
39 * @param string $wire_format The target wire format ('escpos', 'starprnt', 'epos-xml', 'star-markup', 'text' or 'png').
40 * @param array $options Render options.
41 *
42 * @throws InvalidArgumentException When the wire format is not supported.
43 *
44 * @return string The rendered wire-format payload.
45 */
46 public function render( array $template, WC_Abstract_Order $order, string $wire_format, array $options = array() ): string {
47 return $this->render_with_control( $template, $order, $wire_format, $options )['body'];
48 }
49
50 /**
51 * Render a thermal template, reporting peripherals the payload cannot carry.
52 *
53 * Command formats (ESC/POS, StarPRNT, ePOS-XML) express cut and cash-drawer
54 * in-band, so they report null for both. Command-free formats — `text` and
55 * `png` — cannot, and the transport has to ask for them instead:
56 * on Star CloudPRNT that means the `X-Star-Cut` / `X-Star-CashDrawer` headers
57 * on the job fetch. Callers serving those formats must forward what comes
58 * back here or the receipt will neither cut nor open the drawer.
59 *
60 * @param array $template Template metadata/content.
61 * @param WC_Abstract_Order $order The order to render.
62 * @param string $wire_format The target wire format.
63 * @param array $options Render options.
64 *
65 * @throws InvalidArgumentException When the wire format is not supported.
66 *
67 * @return array{body:string, cut:string|null, drawer:string|null}
68 */
69 public function render_with_control( array $template, WC_Abstract_Order $order, string $wire_format, array $options = array() ): array {
70 $ast = $this->build_ast( $template, $order );
71
72 switch ( $wire_format ) {
73 case 'escpos':
74 return self::in_band( ( new Escpos_Thermal_Emitter( $options ) )->emit( $ast ) );
75 case 'starprnt':
76 return self::in_band( ( new Starprnt_Thermal_Emitter( $options ) )->emit( $ast ) );
77 case 'epos-xml':
78 return self::in_band( ( new Epos_Xml_Thermal_Emitter( $options ) )->emit( $ast ) );
79 case 'star-markup':
80 return self::in_band( ( new Star_Markup_Thermal_Emitter() )->emit( $ast ) );
81 case 'text':
82 $emitter = new Text_Thermal_Emitter( $options );
83 $body = $emitter->emit( $ast );
84
85 return array(
86 'body' => $body,
87 'cut' => $emitter->cut_type(),
88 'drawer' => $emitter->drawer(),
89 );
90 case 'png':
91 $emitter = new Raster_Thermal_Emitter( $options );
92 $body = $emitter->emit( $ast );
93
94 return array(
95 'body' => $body,
96 'cut' => $emitter->cut_type(),
97 'drawer' => $emitter->drawer(),
98 );
99 default:
100 throw new InvalidArgumentException(
101 esc_html( "Unsupported thermal wire format: {$wire_format}" )
102 );
103 }
104 }
105
106 /**
107 * Wrap a payload that carries its own cut and drawer commands.
108 *
109 * @param string $body The rendered payload.
110 *
111 * @return array{body:string, cut:string|null, drawer:string|null}
112 */
113 private static function in_band( string $body ): array {
114 return array(
115 'body' => $body,
116 'cut' => null,
117 'drawer' => null,
118 );
119 }
120
121 /**
122 * Build the thermal AST for an order from a template.
123 *
124 * Shared pipeline used by both render() and the PDF path: Mustache-render the
125 * template against canonical receipt data, strip XML-illegal control characters,
126 * then parse the markup into an AST.
127 *
128 * @param array $template Template metadata/content.
129 * @param WC_Abstract_Order $order The order to render.
130 * @param array|null $receipt_data Optional canonical receipt payload.
131 *
132 * @return array The thermal AST root (a receipt node).
133 */
134 public function build_ast( array $template, WC_Abstract_Order $order, ?array $receipt_data = null ): array {
135 $content = (string) ( $template['content'] ?? '' );
136
137 $data = null === $receipt_data ? ( new Receipt_Data_Builder() )->build( $order, 'live' ) : $receipt_data;
138
139 // Pre-format money/display fields so {{*_display}} placeholders resolve,
140 // mirroring Logicless_Renderer.
141 $currency = $data['order']['currency'] ?? 'USD';
142 $data = Receipt_Data_Schema::format_money_fields( $data, $currency );
143
144 // Safety net for templates that wrap content in {{#t}}...{{/t}} markers.
145 $data['t'] = true;
146
147 $flags = ENT_QUOTES | ENT_SUBSTITUTE;
148 $mustache = new Mustache_Engine(
149 array(
150 'entity_flags' => $flags,
151 'escape' => function ( $value ) use ( $flags ) {
152 if ( \is_array( $value ) ) {
153 return '';
154 }
155
156 return htmlspecialchars( (string) $value, $flags, 'UTF-8' );
157 },
158 )
159 );
160
161 $xml = $mustache->render( $content, $data );
162
163 // Strip control characters that XML 1.0 forbids (everything below 0x20
164 // except tab, LF and CR). Order data can carry these — e.g. a customer
165 // note pasted with a form-feed — and Mustache's HTML escaping leaves them
166 // intact, so they would make DOMDocument::loadXML() fail downstream. They
167 // can never print meaningfully, so removing them is safe.
168 $stripped = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/u', '', $xml );
169 if ( null !== $stripped ) {
170 $xml = $stripped;
171 }
172
173 return ( new Thermal_Markup_Parser() )->parse( $xml );
174 }
175 }
176