PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.21
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.21
1.10.25 1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 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 All 169 releases
← All changes | includes/Services/Providers/Epson_Sdp_Adapter.php +174 -5 1.10.13 → 1.10.21 View file →
@@ -6,15 +6,28 @@
6 6 */
7 7
8 8 namespace WCPOS\WooCommercePOS\Services\Providers;
9 9
10 -use WCPOS\WooCommercePOS\Interfaces\Provider_Adapter_Interface;
10 +use WCPOS\WooCommercePOS\Interfaces\Poll_Provider_Adapter_Interface;
11 11
12 12 /**
13 13 * Epson_Sdp_Adapter class.
14 14 */
15 -class Epson_Sdp_Adapter implements Provider_Adapter_Interface {
15 +class Epson_Sdp_Adapter implements Poll_Provider_Adapter_Interface {
16 16 /**
17 + * Milliseconds an Epson Server Direct Print printer waits for the local
18 + * print device to become printable before it gives up and reports
19 + * EX_TIMEOUT.
20 + *
21 + * Epson allows 5000–300000. The previous 10000 (near the floor) was
22 + * unforgiving for printers that briefly go not-ready — a paper change, a
23 + * sleep/wake, or a momentary network blip — declaring the job failed
24 + * before the printer could recover. 60000 gives those transient states
25 + * room to clear without letting a genuinely offline printer hang for long.
26 + */
27 + const EPSON_SDP_PRINT_TIMEOUT_MS = 60000;
28 +
29 + /**
17 30 * Return the ePOS-Print XML content type.
18 31 *
19 32 * @return string
20 33 */
@@ -32,15 +45,15 @@
32 45 */
33 46 public function format( array $printer, array $template ): array {
34 47 if ( 'thermal' !== (string) ( $template['engine'] ?? '' ) ) {
35 48 return array(
36 - 'kind' => '',
49 + 'kind' => '',
37 50 'content_type' => '',
38 51 );
39 52 }
40 53
41 54 return array(
42 - 'kind' => 'epos-xml',
55 + 'kind' => 'epos-xml',
43 56 'content_type' => $this->content_type(),
44 57 );
45 58 }
46 59
@@ -61,9 +74,9 @@
61 74 . '<text>' . esc_html( $text ) . '</text><feed line="3"/><cut type="feed"/></epos-print>';
62 75
63 76 return array(
64 77 'content_type' => $this->content_type(),
65 - 'payload' => base64_encode( $xml ),
78 + 'payload' => base64_encode( $xml ),
66 79 );
67 80 }
68 81
69 82 /**
@@ -88,6 +101,162 @@
88 101 return 'waiting';
89 102 }
90 103
91 104 return ( (int) $context['now'] - $seen ) <= (int) $context['seen_ttl'] ? 'connected' : 'offline';
105 + }
106 +
107 + /**
108 + * Parse the vendor request.
109 + *
110 + * @param array $request Request data.
111 + */
112 + public function parse( array $request ): array {
113 + $params = $request['params'];
114 + // Server Direct Print multiplexes three different request types onto the
115 + // one configured URL, as URL-encoded form data distinguished by
116 + // ConnectionType (User's Manual Rev.K, ch.3 and the Test_print.php
117 + // reference implementation):
118 + //
119 + // GetRequest — poll for a job
120 + // SetResponse — printing result; the XML rides in the ResponseFile field
121 + // SetStatus — status notification; the XML rides in the Status field
122 + //
123 + // Answering a status notification with print data hands the job to a
124 + // request that discards it, so the printer never prints and the job stays
125 + // claimed. Dispatching on ConnectionType is what keeps the job on the
126 + // GetRequest that is actually asking for one.
127 + $connection_type = (string) ( $params['ConnectionType'] ?? '' );
128 + $result_xml = (string) ( $params['ResponseFile'] ?? '' );
129 + if ( '' === $result_xml && false !== strpos( $request['body'], '<response' ) ) {
130 + $result_xml = $request['body'];
131 + }
132 + // Idle PrintResponseInfo and SetStatus posts must never consume a job.
133 + $phase = false !== strpos( $result_xml, '<response' ) ? 'result' : ( 'SetStatus' === $connection_type || 'SetResponse' === $connection_type || '' !== $result_xml ? 'advertise' : 'fetch' );
134 + return array(
135 + 'phase' => $phase,
136 + 'route' => $request['route'],
137 + 'result_xml' => $result_xml,
138 + 'busy' => 'advertise' === $phase,
139 + );
140 + }
141 +
142 + /**
143 + * Build an offer or acknowledgement.
144 + *
145 + * @param array $printer Printer data.
146 + * @param array $poll Parsed request.
147 + * @param array|null $next_job Available job.
148 + */
149 + public function advertise( array $printer, array $poll, ?array $next_job ): array {
150 + return array(
151 + 'status' => 200,
152 + 'headers' => array( 'Content-Type' => 'text/xml; charset=utf-8' ),
153 + 'body' => '<response success="true" code="" status=""/>',
154 + );
155 + }
156 +
157 + /**
158 + * Choose a format before claiming.
159 + *
160 + * @param array $printer Printer data.
161 + * @param array $poll Parsed request.
162 + * @param array $job Candidate job.
163 + */
164 + public function negotiate( array $printer, array $poll, array $job ): array {
165 + // SDP uses the provider default render, not an HTTP Accept media type.
166 + return array( 'media_type' => '' );
167 + }
168 +
169 + /**
170 + * Build the print-data response, including empty renders.
171 + *
172 + * @param array $job Claimed job.
173 + * @param array $render Rendered bytes.
174 + * @param array $poll Parsed request.
175 + */
176 + public function deliver( array $job, array $render, array $poll ): array {
177 + if ( '' === $render['body'] ) {
178 + return $this->advertise( array(), $poll, null );
179 + }
180 + // SDP 1.00 is supported by every printer family; this is not a SOAP envelope.
181 + $envelope = '<?xml version="1.0" encoding="utf-8"?>';
182 + $envelope .= '<PrintRequestInfo Version="1.00"><ePOSPrint>';
183 + $envelope .= '<Parameter><devid>local_printer</devid><timeout>' . self::EPSON_SDP_PRINT_TIMEOUT_MS . '</timeout></Parameter>';
184 + $envelope .= '<PrintData>' . $render['body'] . '</PrintData>';
185 + $envelope .= '</ePOSPrint></PrintRequestInfo>';
186 + return array(
187 + 'status' => 200,
188 + 'headers' => array( 'Content-Type' => 'text/xml; charset=utf-8' ),
189 + 'body' => $envelope,
190 + );
191 + }
192 +
193 + /**
194 + * Identify the result target.
195 + *
196 + * @param array $poll Parsed request.
197 + */
198 + public function match_result( array $poll ): array {
199 + // SDP 1.00 carries no job token: match the active or latest unconfirmed claim.
200 + return array( 'job_id' => null );
201 + }
202 +
203 + /**
204 + * Decode the printer result.
205 + *
206 + * @param array $poll Parsed request.
207 + */
208 + public function interpret_result( array $poll ): array {
209 + $result_xml = $poll['result_xml'];
210 + $code = 'unknown';
211 + if ( 1 === preg_match( '/\bcode="([^"]*)"/', $result_xml, $matches ) ) {
212 + $code = sanitize_text_field( $matches[1] );
213 + }
214 + $status = null;
215 + if ( 1 === preg_match( '/\bstatus="(\d+)"/', $result_xml, $matches ) ) {
216 + // Do not misdecode unsigned bit 31 on a 32-bit PHP build.
217 + $status = (float) $matches[1] <= PHP_INT_MAX ? (int) $matches[1] : null;
218 + }
219 + $flags = null === $status ? '' : implode( ', ', self::describe_epson_status( $status ) );
220 + $detail = null === $status ? '' : sprintf( ' (0x%08X%s)', $status, '' === $flags ? '' : ': ' . $flags );
221 + return array(
222 + 'ok' => false !== strpos( $result_xml, 'success="true"' ),
223 + 'code' => $code,
224 + 'detail' => $detail,
225 + );
226 + }
227 +
228 + /**
229 + * Decode an Epson ePOS-Print response status bitmask.
230 + *
231 + * @param int $status Decimal ASB status bitmask.
232 + *
233 + * @return array<int, string>
234 + */
235 + private static function describe_epson_status( int $status ): array {
236 + // Epson ePOS-Print XML User's Manual, response `status` table (ASB bits).
237 + // Fault bits only: informational ones (print complete, drawer pin, feed
238 + // button, panel switch, buzzer) say nothing about why a print failed.
239 + $labels = array(
240 + 0x00000001 => __( 'no response from printer', 'woocommerce-pos' ),
241 + 0x00000008 => __( 'offline', 'woocommerce-pos' ),
242 + 0x00000020 => __( 'cover open', 'woocommerce-pos' ),
243 + 0x00000100 => __( 'waiting for online recovery', 'woocommerce-pos' ),
244 + 0x00000400 => __( 'mechanical error', 'woocommerce-pos' ),
245 + 0x00000800 => __( 'autocutter error', 'woocommerce-pos' ),
246 + 0x00002000 => __( 'unrecoverable error', 'woocommerce-pos' ),
247 + 0x00004000 => __( 'auto-recoverable error', 'woocommerce-pos' ),
248 + 0x00020000 => __( 'paper near end', 'woocommerce-pos' ),
249 + 0x00080000 => __( 'paper end', 'woocommerce-pos' ),
250 + 0x80000000 => __( 'spooler stopped', 'woocommerce-pos' ),
251 + );
252 +
253 + $descriptions = array();
254 + foreach ( $labels as $bit => $label ) {
255 + if ( 0 !== ( $status & $bit ) ) {
256 + $descriptions[] = $label;
257 + }
258 + }
259 +
260 + return $descriptions;
92 261 }
93 262 }