PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.9.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.9.16
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 / Adapters / Escpos_Output_Adapter.php

Escpos_Output_Adapter.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.9.16, at includes/Templates/Adapters/Escpos_Output_Adapter.php

206 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ESC/POS output adapter.
4 *
5 * @package WCPOS\WooCommercePOS\Templates\Adapters
6 */
7
8 namespace WCPOS\WooCommercePOS\Templates\Adapters;
9
10 use WCPOS\WooCommercePOS\Interfaces\Receipt_Output_Adapter_Interface;
11
12 /**
13 * Escpos_Output_Adapter class.
14 */
15 class Escpos_Output_Adapter implements Receipt_Output_Adapter_Interface {
16 /**
17 * ESC @ initialize printer command.
18 */
19 const ESC_INIT = "\x1B@";
20
21 /**
22 * ESC a 0 align left.
23 */
24 const ALIGN_LEFT = "\x1Ba\x00";
25
26 /**
27 * ESC a 1 align center.
28 */
29 const ALIGN_CENTER = "\x1Ba\x01";
30
31 /**
32 * ESC a 2 align right.
33 */
34 const ALIGN_RIGHT = "\x1Ba\x02";
35
36 /**
37 * ESC E 1 bold on.
38 */
39 const BOLD_ON = "\x1BE\x01";
40
41 /**
42 * ESC E 0 bold off.
43 */
44 const BOLD_OFF = "\x1BE\x00";
45
46 /**
47 * ESC t n select code page.
48 */
49 const CODEPAGE_PREFIX = "\x1Bt";
50
51 /**
52 * GS V A 1 partial cut command.
53 */
54 const CUT_PARTIAL = "\x1DV\x41\x01";
55
56 /**
57 * GS V A 0 full cut command.
58 */
59 const CUT_FULL = "\x1DV\x41\x00";
60
61 /**
62 * ESC p m t1 t2 kick cash drawer command.
63 */
64 const DRAWER_KICK = "\x1Bp\x00\x19\xFA";
65
66 /**
67 * Line feed.
68 */
69 const LF = "\n";
70
71 /**
72 * Transform receipt payload to ESC/POS command stream.
73 *
74 * @param array $receipt_data Canonical payload.
75 * @param array $context Optional context.
76 *
77 * @return string
78 */
79 public function transform( array $receipt_data, array $context = array() ): string {
80 $paper_width = isset( $context['paper_width_chars'] ) ? (int) $context['paper_width_chars'] : 48;
81 $paper_width = $paper_width > 0 ? $paper_width : 48;
82 $codepage = isset( $context['codepage'] ) ? (int) $context['codepage'] : 0;
83 $cut = isset( $context['cut'] ) ? (bool) $context['cut'] : true;
84 $partial_cut = isset( $context['partial_cut'] ) ? (bool) $context['partial_cut'] : false;
85 $open_drawer = isset( $context['open_drawer'] ) ? (bool) $context['open_drawer'] : false;
86 $print_qr = isset( $context['print_qr'] ) ? (bool) $context['print_qr'] : false;
87
88 $order_number = isset( $receipt_data['order']['number'] ) ? (string) $receipt_data['order']['number'] : '';
89 $total = isset( $receipt_data['totals']['total_incl'] ) ? (float) $receipt_data['totals']['total_incl'] : 0;
90 $currency = isset( $receipt_data['order']['currency'] ) ? (string) $receipt_data['order']['currency'] : get_woocommerce_currency();
91 $store_name = isset( $receipt_data['store']['name'] ) ? (string) $receipt_data['store']['name'] : get_bloginfo( 'name' );
92 $created_at = isset( $receipt_data['order']['created']['datetime'] ) ? (string) $receipt_data['order']['created']['datetime'] : '';
93
94 $output_lines = array(
95 self::ALIGN_CENTER . self::BOLD_ON . $this->fit_text( $store_name, $paper_width ) . self::LF . self::BOLD_OFF,
96 $this->fit_text( 'Receipt', $paper_width ) . self::LF,
97 self::ALIGN_LEFT,
98 $this->fit_text( 'Order #' . $order_number, $paper_width ) . self::LF,
99 );
100
101 if ( '' !== $created_at ) {
102 $output_lines[] = $this->fit_text( 'Created: ' . $created_at, $paper_width ) . self::LF;
103 }
104
105 $output_lines[] = str_repeat( '-', $paper_width ) . self::LF;
106
107 if ( isset( $receipt_data['lines'] ) && \is_array( $receipt_data['lines'] ) ) {
108 foreach ( $receipt_data['lines'] as $line ) {
109 $name = isset( $line['name'] ) ? (string) $line['name'] : '';
110 $qty = isset( $line['qty'] ) ? (float) $line['qty'] : 0;
111 $line_total = isset( $line['line_total_incl'] ) ? (float) $line['line_total_incl'] : 0;
112
113 $output_lines[] = $this->fit_text( $name, $paper_width ) . self::LF;
114 $output_lines[] = $this->two_column_line(
115 ' x' . wc_format_decimal( $qty, 2 ),
116 $this->format_money( $line_total, $currency ),
117 $paper_width
118 ) . self::LF;
119 }
120 }
121
122 $output_lines[] = str_repeat( '-', $paper_width ) . self::LF;
123 $output_lines[] = self::BOLD_ON . $this->two_column_line(
124 'TOTAL',
125 $this->format_money( $total, $currency ),
126 $paper_width
127 ) . self::BOLD_OFF . self::LF;
128 $output_lines[] = self::LF;
129
130 if ( $print_qr ) {
131 $qr_payload = isset( $receipt_data['fiscal']['qr_payload'] ) ? (string) $receipt_data['fiscal']['qr_payload'] : '';
132 if ( '' !== $qr_payload ) {
133 $output_lines[] = self::ALIGN_CENTER . '[QR] ' . $this->fit_text( $qr_payload, $paper_width ) . self::LF;
134 $output_lines[] = self::ALIGN_LEFT;
135 }
136 }
137
138 $output = self::ESC_INIT;
139 $output .= self::CODEPAGE_PREFIX . chr( max( 0, min( $codepage, 255 ) ) );
140 $output .= implode( '', $output_lines );
141
142 if ( $open_drawer ) {
143 $output .= self::DRAWER_KICK;
144 }
145
146 if ( $cut ) {
147 $output .= $partial_cut ? self::CUT_PARTIAL : self::CUT_FULL;
148 }
149
150 return $output;
151 }
152
153 /**
154 * Format money for thermal output.
155 *
156 * @param float $value Money value.
157 * @param string $currency Currency code.
158 *
159 * @return string
160 */
161 private function format_money( float $value, string $currency ): string {
162 return trim( $currency . ' ' . wc_format_decimal( $value, wc_get_price_decimals() ) );
163 }
164
165 /**
166 * Build a two-column thermal line.
167 *
168 * @param string $left Left text.
169 * @param string $right Right text.
170 * @param int $line_width Line width in characters.
171 *
172 * @return string
173 */
174 private function two_column_line( string $left, string $right, int $line_width ): string {
175 $left = $this->fit_text( $left, $line_width );
176 $right = $this->fit_text( $right, $line_width );
177
178 $available = $line_width - strlen( $right ) - 1;
179 if ( $available < 1 ) {
180 return substr( $left, 0, $line_width );
181 }
182
183 $left = $this->fit_text( $left, $available );
184 $space = str_repeat( ' ', max( 1, $line_width - strlen( $left ) - strlen( $right ) ) );
185
186 return $left . $space . $right;
187 }
188
189 /**
190 * Fit text to thermal line width.
191 *
192 * @param string $text Text value.
193 * @param int $line_width Line width in characters.
194 *
195 * @return string
196 */
197 private function fit_text( string $text, int $line_width ): string {
198 $sanitized = trim( preg_replace( '/\s+/', ' ', $text ) );
199 if ( strlen( $sanitized ) <= $line_width ) {
200 return $sanitized;
201 }
202
203 return substr( $sanitized, 0, max( 0, $line_width - 1 ) ) . '';
204 }
205 }
206