'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";
}
}