PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
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 1.9.13 1.9.12 1.9.11 1.9.10 All 159 releases
woocommerce-pos / includes / Services / PrintNode_Client.php

PrintNode_Client.php in WCPOS – Point of Sale (POS) plugin for WooCommerce trunk, at includes/Services/PrintNode_Client.php

255 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Thin PrintNode REST API client built on the WordPress HTTP API.
4 *
5 * Wraps the PrintNode cloud print service. Every public method is total: it
6 * returns a WP_Error on any failure rather than throwing, and the API key is
7 * never placed in a return value, error message, or log entry.
8 *
9 * @package WCPOS\WooCommercePOS\Services
10 */
11
12 namespace WCPOS\WooCommercePOS\Services;
13
14 use WP_Error;
15 use WCPOS\WooCommercePOS\Logger;
16
17 /**
18 * PrintNode_Client class.
19 */
20 class PrintNode_Client {
21 /**
22 * PrintNode REST base URL (no trailing slash).
23 */
24 const BASE_URL = 'https://api.printnode.com';
25
26 /**
27 * Request timeout in seconds.
28 */
29 const TIMEOUT = 15;
30
31 /**
32 * PrintNode API key. Treated as a secret; never exposed.
33 *
34 * @var string
35 */
36 private $api_key;
37
38 /**
39 * Constructor.
40 *
41 * @param string $api_key PrintNode API key.
42 */
43 public function __construct( string $api_key ) {
44 $this->api_key = $api_key;
45 }
46
47 /**
48 * Fetch the account associated with the API key.
49 *
50 * @return array|WP_Error Decoded account object, or WP_Error on failure.
51 */
52 public function whoami() {
53 $response = wp_remote_get(
54 self::BASE_URL . '/whoami',
55 array(
56 'timeout' => self::TIMEOUT,
57 'headers' => $this->headers(),
58 )
59 );
60
61 return $this->handle( $response );
62 }
63
64 /**
65 * List the printers available to the account.
66 *
67 * @return array|WP_Error Decoded array of printers, or WP_Error on failure.
68 */
69 public function printers() {
70 $response = wp_remote_get(
71 self::BASE_URL . '/printers',
72 array(
73 'timeout' => self::TIMEOUT,
74 'headers' => $this->headers(),
75 )
76 );
77
78 return $this->handle( $response );
79 }
80
81 /**
82 * Submit a print job.
83 *
84 * @param int $printer_id Target printer id.
85 * @param string $title Human-readable job title.
86 * @param string $content_type PrintNode contentType ('pdf_base64' or 'raw_base64').
87 * @param string $base64 Base64-encoded job content.
88 *
89 * @return array|WP_Error array( 'id' => <int> ) on success, or WP_Error on failure.
90 */
91 public function submit_job( int $printer_id, string $title, string $content_type, string $base64 ) {
92 $body = array(
93 'printerId' => $printer_id,
94 'title' => $title,
95 'contentType' => $content_type,
96 'content' => $base64,
97 'source' => 'WCPOS',
98 );
99
100 $response = wp_remote_post(
101 self::BASE_URL . '/printjobs',
102 array(
103 'timeout' => self::TIMEOUT,
104 'headers' => array_merge(
105 $this->headers(),
106 array( 'Content-Type' => 'application/json' )
107 ),
108 'body' => wp_json_encode( $body ),
109 )
110 );
111
112 $result = $this->handle( $response );
113
114 if ( is_wp_error( $result ) ) {
115 return $result;
116 }
117
118 return $this->normalize_job_id( $result );
119 }
120
121 /**
122 * Resolve the live state of a single printer.
123 *
124 * Never returns a WP_Error; any failure collapses to 'unknown'.
125 *
126 * @param int $printer_id Printer id to query.
127 *
128 * @return string 'online', 'offline', or 'unknown'.
129 */
130 public function printer_state( int $printer_id ): string {
131 $response = wp_remote_get(
132 self::BASE_URL . '/printers/' . $printer_id,
133 array(
134 'timeout' => self::TIMEOUT,
135 'headers' => $this->headers(),
136 )
137 );
138
139 $result = $this->handle( $response );
140
141 if ( is_wp_error( $result ) || ! is_array( $result ) || empty( $result[0] ) || ! is_array( $result[0] ) ) {
142 return 'unknown';
143 }
144
145 $state = isset( $result[0]['state'] ) ? $result[0]['state'] : '';
146
147 if ( 'online' === $state ) {
148 return 'online';
149 }
150
151 if ( 'offline' === $state ) {
152 return 'offline';
153 }
154
155 return 'unknown';
156 }
157
158 /**
159 * Build the common request headers, including HTTP Basic auth.
160 *
161 * The API key is the username with an empty password.
162 *
163 * @return array<string, string>
164 */
165 private function headers(): array {
166 return array(
167 'Authorization' => 'Basic ' . base64_encode( $this->api_key . ':' ),
168 'Accept' => 'application/json',
169 );
170 }
171
172 /**
173 * Convert a raw WordPress HTTP response into decoded data or a WP_Error.
174 *
175 * @param array|WP_Error $response Result of a wp_remote_* call.
176 *
177 * @return array|WP_Error
178 */
179 private function handle( $response ) {
180 if ( is_wp_error( $response ) ) {
181 return $response;
182 }
183
184 $code = (int) wp_remote_retrieve_response_code( $response );
185 $body = wp_remote_retrieve_body( $response );
186
187 if ( 200 === $code || 201 === $code ) {
188 $decoded = json_decode( $body, true );
189
190 if ( null === $decoded && 'null' !== trim( (string) $body ) ) {
191 return $this->http_error( $code );
192 }
193
194 return $decoded;
195 }
196
197 if ( 401 === $code ) {
198 return new WP_Error(
199 'wcpos_printnode_unauthorized',
200 __( 'PrintNode authentication failed.', 'woocommerce-pos' ),
201 array( 'status' => 401 )
202 );
203 }
204
205 return $this->http_error( $code );
206 }
207
208 /**
209 * Build a generic HTTP error that includes the status code but never the key.
210 *
211 * @param int $code HTTP status code.
212 *
213 * @return WP_Error
214 */
215 private function http_error( int $code ): WP_Error {
216 Logger::error( 'PrintNode request failed', array( 'status' => $code ) );
217
218 return new WP_Error(
219 'wcpos_printnode_http_error',
220 sprintf(
221 /* translators: %d: HTTP status code. */
222 __( 'PrintNode request failed with status %d.', 'woocommerce-pos' ),
223 $code
224 ),
225 array( 'status' => $code )
226 );
227 }
228
229 /**
230 * Normalize a submit_job response into array( 'id' => <int> ).
231 *
232 * PrintNode may return the new job id as a bare integer, or as an object
233 * containing an `id` field.
234 *
235 * @param mixed $decoded Decoded response body.
236 *
237 * @return array|WP_Error
238 */
239 private function normalize_job_id( $decoded ) {
240 if ( is_int( $decoded ) || ( is_string( $decoded ) && ctype_digit( $decoded ) ) ) {
241 return array( 'id' => (int) $decoded );
242 }
243
244 if ( is_array( $decoded ) && isset( $decoded['id'] ) ) {
245 return array( 'id' => (int) $decoded['id'] );
246 }
247
248 return new WP_Error(
249 'wcpos_printnode_http_error',
250 __( 'PrintNode returned an unexpected print job response.', 'woocommerce-pos' ),
251 array( 'status' => 0 )
252 );
253 }
254 }
255