'pdf', 'content_type' => $this->content_type(), ); } return array( 'kind' => 'escpos', 'content_type' => 'application/octet-stream', ); } /** * Build a printer-friendly diagnostic PDF. * * @param string $printer_name Printer display name. * * @return array{content_type:string, payload:string} */ public function diagnostic( string $printer_name ): array { $html = ''; $html .= '

WCPOS — Cloud Print Test

'; $html .= '
Printer: ' . esc_html( $printer_name ) . '
'; $html .= '
Date (UTC): ' . esc_html( gmdate( 'Y-m-d H:i' ) ) . '
'; $html .= '
If you can read this, printing works!
'; return array( 'content_type' => $this->content_type(), 'payload' => base64_encode( ( new Pdf_Renderer() )->render_html( $html ) ), ); } /** * Resolve PrintNode live status with the existing transient cache contract. * * @param array $printer Printer configuration. * @param array $context Runtime status context. * * @return string */ public function status( array $printer, array $context ): string { $key = 'wcpos_cloud_print_pn_status_' . md5( (string) $printer['id'] ); $cached = get_transient( $key ); if ( false !== $cached ) { return (string) $cached; } $api_key = (string) ( $printer['printnode_api_key'] ?? '' ); $printer_id = (int) ( $printer['printnode_printer_id'] ?? 0 ); $status = '' === $api_key || 0 === $printer_id ? 'unknown' : ( new PrintNode_Client( $api_key ) )->printer_state( $printer_id ); set_transient( $key, $status, (int) $context['cache_ttl'] ); return $status; } /** * Submit a rendered PrintNode job and optional drawer kick. * * @param array $printer Printer configuration. * @param array $job Print job data. * @param string $payload Rendered payload bytes. * @param string $title Provider job title. * * @return array{success:bool, retryable:bool, error:string, external_job_id:string, drawer_error:string} */ public function submit( array $printer, array $job, string $payload, string $title ): array { $api_key = (string) ( $printer['printnode_api_key'] ?? '' ); $printer_id = (int) ( $printer['printnode_printer_id'] ?? 0 ); if ( '' === $api_key || 0 === $printer_id ) { return $this->failure( 'Cloud print: PrintNode printer is missing an API key or printer id.', false ); } if ( '' === $payload ) { return $this->failure( 'Cloud print: PrintNode job produced no printable content.', false ); } $content_type = 'pdf_base64'; if ( 'escpos' === ( $job['pn_kind'] ?? '' ) ) { $content_type = 'raw_base64'; } $client = new PrintNode_Client( $api_key ); $result = $client->submit_job( $printer_id, $title, $content_type, base64_encode( $payload ) ); if ( is_wp_error( $result ) ) { return $this->failure( $result->get_error_message(), true ); } $drawer_error = ''; if ( 'pdf' === ( $job['pn_kind'] ?? '' ) && ! empty( $job['auto_open_drawer'] ) ) { $drawer = $client->submit_job( $printer_id, $title . ' Cash Drawer', 'raw_base64', base64_encode( $this->drawer_kick_bytes( (string) ( $job['drawer_connector'] ?? 'pin2' ) ) ) ); $drawer_error = is_wp_error( $drawer ) ? $drawer->get_error_message() : ''; } return array( 'success' => true, 'retryable' => false, 'error' => '', 'external_job_id' => (string) $result['id'], 'drawer_error' => $drawer_error, ); } /** * Build the stable submit failure shape. * * @param string $error Provider error message. * @param bool $retryable Whether the service should retry. * * @return array{success:bool, retryable:bool, error:string, external_job_id:string, drawer_error:string} */ private function failure( string $error, bool $retryable ): array { return array( 'success' => false, 'retryable' => $retryable, 'error' => $error, 'external_job_id' => '', 'drawer_error' => '', ); } /** * Build ESC/POS drawer kick bytes. * * @param string $connector pin2 or pin5. * * @return string */ private function drawer_kick_bytes( string $connector ): string { $pin = 'pin5' === Print_Job_Service::normalize_drawer_connector( $connector ) ? "\x01" : "\x00"; return "\x1B\x70" . $pin . "\x19\xFA"; } }