PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.17
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.17
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 1.9.13 All 162 releases
woocommerce-pos / includes / Services / Star_Online_Client.php

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

235 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Thin StarIO.Online (stario.online) Web API client built on the WordPress HTTP API.
4 *
5 * Stario.online is Star's hosted CloudPRNT relay: the printer polls Star's cloud,
6 * and we push jobs to it. Every public method returns a WP_Error on failure rather
7 * than throwing, and the API key is never placed in a return value, error message,
8 * or log entry.
9 *
10 * @package WCPOS\WooCommercePOS\Services
11 */
12
13 namespace WCPOS\WooCommercePOS\Services;
14
15 use WCPOS\WooCommercePOS\Logger;
16 use WP_Error;
17
18 /**
19 * Star_Online_Client class.
20 */
21 class Star_Online_Client {
22 /** Request timeout in seconds. */
23 const TIMEOUT = 15;
24
25 /**
26 * Allowlisted CloudPRNT device hosts to regional Application API base.
27 *
28 * @var array<string, string>
29 */
30 private const REGION_API_BASES = array(
31 'device.stario.online' => 'https://api.stario.online/v1',
32 'eu-device.stario.online' => 'https://eu-api.stario.online/v1',
33 );
34
35 /**
36 * Regional API base (no trailing slash).
37 *
38 * @var string
39 */
40 private string $api_base;
41
42 /**
43 * API key. Treated as a secret; never exposed.
44 *
45 * @var string
46 */
47 private string $api_key;
48
49 /**
50 * Constructor.
51 *
52 * @param string $api_base Regional API base (use api_base_from_cloudprnt_url()).
53 * @param string $api_key Star-Api-Key value.
54 */
55 public function __construct( string $api_base, string $api_key ) {
56 $this->api_base = rtrim( $api_base, '/' );
57 $this->api_key = $api_key;
58 }
59
60 /**
61 * Derive regional API base from a CloudPRNT URL, or null if not allowlisted.
62 *
63 * @param string $url CloudPRNT URL.
64 *
65 * @return string|null
66 */
67 public static function api_base_from_cloudprnt_url( string $url ): ?string {
68 $host = wp_parse_url( $url, PHP_URL_HOST );
69 if ( ! \is_string( $host ) ) {
70 return null;
71 }
72
73 return self::REGION_API_BASES[ strtolower( $host ) ] ?? null;
74 }
75
76 /**
77 * Parse the device-group path from a CloudPRNT URL (.../cloudprnt/<group>).
78 *
79 * @param string $url CloudPRNT URL.
80 *
81 * @return string Group path, or '' when not present.
82 */
83 public static function group_from_cloudprnt_url( string $url ): string {
84 $path = (string) wp_parse_url( $url, PHP_URL_PATH );
85 if ( preg_match( '#/cloudprnt/([^/]+)#', $path, $matches ) ) {
86 return rawurldecode( $matches[1] );
87 }
88
89 return '';
90 }
91
92 /**
93 * Submit a print job to a device.
94 *
95 * @param string $group_path Device-group path.
96 * @param string $device_id Device AccessIdentifier.
97 * @param string $title Human-readable job name.
98 * @param string $type Content type (e.g. text/vnd.star.markup).
99 * @param string $body Raw job body.
100 *
101 * @return array|WP_Error array( 'id' => <string> ) on success, or WP_Error.
102 */
103 public function submit_job( string $group_path, string $device_id, string $title, string $type, string $body ) {
104 $url = sprintf(
105 '%s/a/%s/d/%s/q',
106 $this->api_base,
107 rawurlencode( $group_path ),
108 rawurlencode( $device_id )
109 );
110
111 $response = wp_remote_post(
112 $url,
113 array(
114 'timeout' => self::TIMEOUT,
115 'headers' => array(
116 'Star-Api-Key' => $this->api_key,
117 'Content-Type' => $type,
118 'Star-Job-Name' => $title,
119 ),
120 'body' => $body,
121 )
122 );
123
124 $result = $this->handle( $response );
125 if ( is_wp_error( $result ) ) {
126 return $result;
127 }
128
129 $job_id = \is_array( $result ) && isset( $result['JobId'] ) ? (string) $result['JobId'] : '';
130 if ( '' === $job_id ) {
131 return new WP_Error(
132 'wcpos_star_online_bad_response',
133 __( 'Star Online returned an unexpected print job response.', 'woocommerce-pos' ),
134 array( 'status' => 0 )
135 );
136 }
137
138 return array( 'id' => $job_id );
139 }
140
141 /**
142 * List devices in a group (for the wizard picker).
143 *
144 * @param string $group_path Device-group path.
145 *
146 * @return array|WP_Error Decoded device list, or WP_Error.
147 */
148 public function devices( string $group_path ) {
149 $response = wp_remote_get(
150 sprintf( '%s/a/%s/d', $this->api_base, rawurlencode( $group_path ) ),
151 array(
152 'timeout' => self::TIMEOUT,
153 'headers' => array( 'Star-Api-Key' => $this->api_key ),
154 )
155 );
156
157 return $this->handle( $response );
158 }
159
160 /**
161 * Resolve live state of one device. Failures collapse to unknown.
162 *
163 * @param string $group_path Device-group path.
164 * @param string $device_id Device AccessIdentifier.
165 *
166 * @return string 'online', 'offline', or 'unknown'.
167 */
168 public function device_state( string $group_path, string $device_id ): string {
169 $response = wp_remote_get(
170 sprintf( '%s/a/%s/d/%s', $this->api_base, rawurlencode( $group_path ), rawurlencode( $device_id ) ),
171 array(
172 'timeout' => self::TIMEOUT,
173 'headers' => array( 'Star-Api-Key' => $this->api_key ),
174 )
175 );
176
177 $result = $this->handle( $response );
178 if ( is_wp_error( $result ) || ! \is_array( $result ) || ! isset( $result['Status']['Online'] ) ) {
179 return 'unknown';
180 }
181
182 return $result['Status']['Online'] ? 'online' : 'offline';
183 }
184
185 /**
186 * Convert a raw WordPress HTTP response into decoded data or a WP_Error.
187 *
188 * @param array|WP_Error $response Result of a wp_remote_* call.
189 *
190 * @return array|WP_Error
191 */
192 private function handle( $response ) {
193 if ( is_wp_error( $response ) ) {
194 return $response;
195 }
196
197 $code = (int) wp_remote_retrieve_response_code( $response );
198 $body = wp_remote_retrieve_body( $response );
199
200 if ( $code >= 200 && $code < 300 ) {
201 $decoded = json_decode( $body, true );
202
203 return \is_array( $decoded ) ? $decoded : array();
204 }
205
206 if ( 401 === $code ) {
207 return new WP_Error(
208 'wcpos_star_online_unauthorized',
209 __( 'Star Online authentication failed.', 'woocommerce-pos' ),
210 array( 'status' => $code )
211 );
212 }
213
214 if ( 403 === $code ) {
215 return new WP_Error(
216 'wcpos_star_online_forbidden',
217 __( 'Star Online rejected the request. Check that the API key has the required permissions.', 'woocommerce-pos' ),
218 array( 'status' => $code )
219 );
220 }
221
222 Logger::error( 'Star Online request failed', array( 'status' => $code ) );
223
224 return new WP_Error(
225 'wcpos_star_online_http_error',
226 sprintf(
227 /* translators: %d: HTTP status code. */
228 __( 'Star Online request failed with status %d.', 'woocommerce-pos' ),
229 $code
230 ),
231 array( 'status' => $code )
232 );
233 }
234 }
235