PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
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 / Services / Providers / Printnode_Adapter.php

Printnode_Adapter.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.18, at includes/Services/Providers/Printnode_Adapter.php

247 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PrintNode provider adapter.
4 *
5 * @package WCPOS\WooCommercePOS\Services\Providers
6 */
7
8 namespace WCPOS\WooCommercePOS\Services\Providers;
9
10 use WCPOS\WooCommercePOS\Interfaces\Push_Provider_Adapter_Interface;
11 use WCPOS\WooCommercePOS\Logger;
12 use WP_Error;
13 use WP_REST_Response;
14 use WCPOS\WooCommercePOS\Services\Pdf_Renderer;
15 use WCPOS\WooCommercePOS\Services\Print_Job_Service;
16 use WCPOS\WooCommercePOS\Services\PrintNode_Client;
17
18 /**
19 * Printnode_Adapter class.
20 */
21 class Printnode_Adapter implements Push_Provider_Adapter_Interface {
22 /**
23 * Return the PrintNode default PDF content type.
24 *
25 * @return string
26 */
27 public function content_type(): string {
28 return 'application/pdf';
29 }
30
31 /**
32 * Resolve PrintNode PDF or raw ESC/POS format data.
33 *
34 * @param array $printer Printer configuration.
35 * @param array $template Template configuration.
36 *
37 * @return array{kind:string, content_type:string}
38 */
39 public function format( array $printer, array $template ): array {
40 if ( 'thermal' !== (string) ( $template['engine'] ?? '' ) || 'raw' !== (string) ( $printer['printnode_format'] ?? 'pdf' ) ) {
41 return array(
42 'kind' => 'pdf',
43 'content_type' => $this->content_type(),
44 );
45 }
46
47 return array(
48 'kind' => 'escpos',
49 'content_type' => 'application/octet-stream',
50 );
51 }
52
53 /**
54 * Build a printer-friendly diagnostic PDF.
55 *
56 * @param string $printer_name Printer display name.
57 *
58 * @return array{content_type:string, payload:string}
59 */
60 public function diagnostic( string $printer_name ): array {
61 $html = '<!DOCTYPE html><html><head><meta charset="utf-8"><style>';
62 $html .= 'body{font-family:"dejavu sans",sans-serif;color:#000;background:#fff;margin:24px;}'
63 . 'h1{font-size:18px;margin:0 0 12px;}.row{font-size:13px;margin:4px 0;}'
64 . '.label{font-weight:bold;}hr{border:none;border-top:1px solid #000;margin:16px 0;}'
65 . '.ok{font-size:14px;font-weight:bold;margin-top:16px;}</style></head><body>';
66 $html .= '<h1>WCPOS &mdash; Cloud Print Test</h1>';
67 $html .= '<div class="row"><span class="label">Printer:</span> ' . esc_html( $printer_name ) . '</div>';
68 $html .= '<div class="row"><span class="label">Date (UTC):</span> ' . esc_html( gmdate( 'Y-m-d H:i' ) ) . '</div>';
69 $html .= '<hr><div class="ok">If you can read this, printing works!</div></body></html>';
70
71 return array(
72 'content_type' => $this->content_type(),
73 'payload' => base64_encode( ( new Pdf_Renderer() )->render_html( $html ) ),
74 );
75 }
76
77 /**
78 * Resolve PrintNode live status with the existing transient cache contract.
79 *
80 * @param array $printer Printer configuration.
81 * @param array $context Runtime status context.
82 *
83 * @return string
84 */
85 public function status( array $printer, array $context ): string {
86 $key = 'wcpos_cloud_print_pn_status_' . md5( (string) $printer['id'] );
87 $cached = get_transient( $key );
88 if ( false !== $cached ) {
89 return (string) $cached;
90 }
91
92 $api_key = (string) ( $printer['printnode_api_key'] ?? '' );
93 $printer_id = (int) ( $printer['printnode_printer_id'] ?? 0 );
94 $status = '' === $api_key || 0 === $printer_id
95 ? 'unknown'
96 : ( new PrintNode_Client( $api_key ) )->printer_state( $printer_id );
97 set_transient( $key, $status, (int) $context['cache_ttl'] );
98
99 return $status;
100 }
101
102 /**
103 * Submit a rendered PrintNode job and optional drawer kick.
104 *
105 * @param array $printer Printer configuration.
106 * @param array $job Print job data.
107 * @param string $payload Rendered payload bytes.
108 * @param string $title Provider job title.
109 *
110 * @return array{success:bool, retryable:bool, error:string, external_job_id:string, drawer_error:string}
111 */
112 public function submit( array $printer, array $job, string $payload, string $title ): array {
113 $api_key = (string) ( $printer['printnode_api_key'] ?? '' );
114 $printer_id = (int) ( $printer['printnode_printer_id'] ?? 0 );
115 if ( '' === $api_key || 0 === $printer_id ) {
116 return $this->failure( 'Cloud print: PrintNode printer is missing an API key or printer id.', false );
117 }
118 if ( '' === $payload ) {
119 return $this->failure( 'Cloud print: PrintNode job produced no printable content.', false );
120 }
121
122 $content_type = 'pdf_base64';
123 if ( 'escpos' === ( $job['pn_kind'] ?? '' ) ) {
124 $content_type = 'raw_base64';
125 }
126
127 $client = new PrintNode_Client( $api_key );
128 $result = $client->submit_job(
129 $printer_id,
130 $title,
131 $content_type,
132 base64_encode( $payload )
133 );
134 if ( is_wp_error( $result ) ) {
135 return $this->failure( $result->get_error_message(), true );
136 }
137
138 $drawer_error = '';
139 if ( 'pdf' === ( $job['pn_kind'] ?? '' ) && ! empty( $job['auto_open_drawer'] ) ) {
140 $drawer = $client->submit_job(
141 $printer_id,
142 $title . ' Cash Drawer',
143 'raw_base64',
144 base64_encode( $this->drawer_kick_bytes( (string) ( $job['drawer_connector'] ?? 'pin2' ) ) )
145 );
146 $drawer_error = is_wp_error( $drawer ) ? $drawer->get_error_message() : '';
147 }
148
149 return array(
150 'success' => true,
151 'retryable' => false,
152 'error' => '',
153 'external_job_id' => (string) $result['id'],
154 'drawer_error' => $drawer_error,
155 );
156 }
157
158 /**
159 * Build the stable submit failure shape.
160 *
161 * @param string $error Provider error message.
162 * @param bool $retryable Whether the service should retry.
163 *
164 * @return array{success:bool, retryable:bool, error:string, external_job_id:string, drawer_error:string}
165 */
166 private function failure( string $error, bool $retryable ): array {
167 return array(
168 'success' => false,
169 'retryable' => $retryable,
170 'error' => $error,
171 'external_job_id' => '',
172 'drawer_error' => '',
173 );
174 }
175
176 /**
177 * Build ESC/POS drawer kick bytes.
178 *
179 * @param string $connector pin2 or pin5.
180 *
181 * @return string
182 */
183 private function drawer_kick_bytes( string $connector ): string {
184 $pin = 'pin5' === Print_Job_Service::normalize_drawer_connector( $connector ) ? "\x01" : "\x00";
185
186 return "\x1B\x70" . $pin . "\x19\xFA";
187 }
188
189 /**
190 * Submit a diagnostic PDF to a PrintNode printer.
191 *
192 * @param array $printer Registered PrintNode printer.
193 *
194 * @return \WP_REST_Response|WP_Error
195 */
196 public function test_print( array $printer ) {
197 $api_key = (string) ( $printer['printnode_api_key'] ?? '' );
198 $pn_printer_id = (int) ( $printer['printnode_printer_id'] ?? 0 );
199 if ( '' === $api_key || 0 === $pn_printer_id ) {
200 return new WP_Error(
201 'wcpos_print_job_printnode_unconfigured',
202 __( 'This PrintNode printer is missing its API key or printer id.', 'woocommerce-pos' ),
203 array( 'status' => 400 )
204 );
205 }
206
207 try {
208 $pdf = base64_decode( $this->diagnostic( (string) $printer['name'] )['payload'], true );
209 } catch ( \Throwable $e ) {
210 // Defense in depth: a Dompdf/font-cache/temp-dir failure must not
211 // surface as an uncaught 500. Mirror the render_payload() guard.
212 Logger::log( 'Cloud print: PrintNode diagnostic PDF render failed: ' . $e->getMessage() );
213
214 return new WP_Error(
215 'wcpos_print_job_diagnostic_failed',
216 __( 'Could not generate the test print.', 'woocommerce-pos' ),
217 array( 'status' => 500 )
218 );
219 }
220
221 $result = ( new PrintNode_Client( $api_key ) )->submit_job(
222 $pn_printer_id,
223 'WCPOS Test Print',
224 'pdf_base64',
225 base64_encode( $pdf )
226 );
227
228 if ( is_wp_error( $result ) ) {
229 return new WP_Error(
230 'wcpos_print_job_printnode_failed',
231 $result->get_error_message(),
232 array( 'status' => 502 )
233 );
234 }
235
236 return new WP_REST_Response(
237 array(
238 'submitted' => true,
239 'external_provider' => 'printnode',
240 'external_job_id' => (string) $result['id'],
241 'external_state' => 'submitted',
242 ),
243 201
244 );
245 }
246 }
247