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.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 / Template_Pdf_Service.php

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

192 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Template PDF Service.
4 *
5 * Renders any receipt template to PDF bytes. Thermal templates are built into a
6 * thermal AST and emitted to receipt HTML via Html_Thermal_Emitter; all other
7 * engines (logicless, legacy-php) run their matching renderer and capture the
8 * echoed HTML via output buffering. The resulting HTML is rasterized to PDF with
9 * Pdf_Renderer (Dompdf).
10 *
11 * @author Paul Kilmurray <paul@kilbot.com>
12 *
13 * @see http://wcpos.com
14 * @package WCPOS\WooCommercePOS\Services
15 */
16
17 namespace WCPOS\WooCommercePOS\Services;
18
19 use WCPOS\WooCommercePOS\Templates\Thermal\Html_Thermal_Emitter;
20 use WCPOS\WooCommercePOS\Templates\Thermal\Thermal_Renderer;
21 use WC_Abstract_Order;
22
23 /**
24 * Template_Pdf_Service class.
25 */
26 class Template_Pdf_Service {
27
28 /**
29 * Render a receipt template for an order into PDF bytes.
30 *
31 * @param array $template Template metadata/content (must include 'engine').
32 * @param WC_Abstract_Order $order The order to render.
33 *
34 * @return string The PDF document bytes (begins with '%PDF-').
35 */
36 public function render( array $template, WC_Abstract_Order $order ): string {
37 $engine = isset( $template['engine'] ) ? (string) $template['engine'] : '';
38
39 $wp_overnight_pdf = $this->maybe_render_wp_overnight_pdf( $template, $order );
40 if ( null !== $wp_overnight_pdf ) {
41 return $wp_overnight_pdf;
42 }
43
44 if ( 'thermal' === $engine ) {
45 $paper_width_pt = $this->thermal_paper_width_pt( $template );
46
47 $ast = ( new Thermal_Renderer() )->build_ast( $template, $order );
48 $html = ( new Html_Thermal_Emitter() )->emit(
49 $ast,
50 array(
51 // CSS pixels (96dpi) for the paper width, so the emitter can
52 // scale the monospace character grid to fit the page.
53 'paper_width_px' => $paper_width_pt * 4 / 3,
54 )
55 );
56
57 // Narrow continuous-roll receipt page at the template's declared paper
58 // width (58/80mm). Render on a tall probe page, then let Pdf_Renderer
59 // fit the height to the content so downloaded PDFs and PrintNode PDFs
60 // avoid blank tails/overflow pages.
61 return ( new Pdf_Renderer() )->render_html(
62 $html,
63 array(
64 'paper' => array( 0, 0, $paper_width_pt, 14000.0 ),
65 'default_font' => 'dejavu sans mono',
66 'fit_height' => true,
67 'receipt_layout' => true,
68 )
69 );
70 }
71
72 $html = $this->render_html_engine( $engine, $template, $order );
73
74 // Non-thermal templates render to a standard A4 portrait page (Pdf_Renderer default).
75 return ( new Pdf_Renderer() )->render_html( $html, array( 'receipt_layout' => true ) );
76 }
77
78 /**
79 * Resolve a thermal template's paper width in points.
80 *
81 * Template metadata declares the physical roll ('58mm' / '80mm'); fall back
82 * to 80mm when absent so templates without metadata keep the previous page
83 * size.
84 *
85 * @param array $template Template metadata/content.
86 *
87 * @return float Paper width in pt.
88 */
89 private function thermal_paper_width_pt( array $template ): float {
90 $raw = isset( $template['paper_width'] ) ? (string) $template['paper_width'] : '';
91 $mm = (float) $raw; // Leading-number cast: '58mm' → 58.0.
92
93 if ( $mm < 25.0 || $mm > 250.0 ) {
94 $mm = 80.0;
95 }
96
97 return round( $mm * 72 / 25.4, 2 );
98 }
99
100 /**
101 * Render WP Overnight integration templates using the plugin's native PDF bytes.
102 *
103 * @param array $template Template metadata/content.
104 * @param WC_Abstract_Order $order The order to render.
105 *
106 * @return string|null Native PDF bytes for WP Overnight templates, null for all other templates.
107 * @throws \RuntimeException When the WP Overnight PDF document cannot be generated.
108 */
109 private function maybe_render_wp_overnight_pdf( array $template, WC_Abstract_Order $order ): ?string {
110 $document_type = $this->wp_overnight_document_type( $template );
111 if ( null === $document_type ) {
112 return null;
113 }
114
115 $document = apply_filters( 'woocommerce_pos_wp_overnight_pdf_document', null, $document_type, $order );
116 if ( null === $document && \function_exists( 'wcpdf_get_document' ) ) {
117 $document = wcpdf_get_document( $document_type, $order, true );
118 }
119
120 if ( ! $document || ! \is_callable( array( $document, 'get_pdf' ) ) ) {
121 throw new \RuntimeException(
122 esc_html(
123 sprintf(
124 /* translators: %s: WP Overnight document type. */
125 __( 'WP Overnight %s PDF could not be generated.', 'woocommerce-pos' ),
126 $document_type
127 )
128 )
129 );
130 }
131
132 $pdf = $document->get_pdf();
133 if ( ! \is_string( $pdf ) || '' === $pdf || 0 !== strpos( $pdf, '%PDF-' ) ) {
134 throw new \RuntimeException(
135 esc_html(
136 sprintf(
137 /* translators: %s: WP Overnight document type. */
138 __( 'WP Overnight %s PDF could not be generated.', 'woocommerce-pos' ),
139 $document_type
140 )
141 )
142 );
143 }
144
145 return $pdf;
146 }
147
148 /**
149 * Map WCPOS virtual template IDs to WP Overnight document types.
150 *
151 * @param array $template Template metadata/content.
152 *
153 * @return string|null invoice|packing-slip for WP Overnight templates, null otherwise.
154 */
155 private function wp_overnight_document_type( array $template ): ?string {
156 $id = isset( $template['id'] ) ? (string) $template['id'] : '';
157
158 if ( 'wp-overnight-invoice' === $id ) {
159 return 'invoice';
160 }
161
162 if ( 'wp-overnight-packing-slip' === $id ) {
163 return 'packing-slip';
164 }
165
166 return null;
167 }
168
169 /**
170 * Run an echo-based renderer (logicless / legacy-php) and capture its HTML.
171 *
172 * @param string $engine The template engine.
173 * @param array $template Template metadata/content.
174 * @param WC_Abstract_Order $order The order to render.
175 *
176 * @return string The captured receipt HTML.
177 */
178 private function render_html_engine( string $engine, array $template, WC_Abstract_Order $order ): string {
179 $receipt_data = ( new Receipt_Data_Builder() )->build( $order, 'live' );
180 $renderer = ( new Receipt_Renderer_Factory() )->create( $engine );
181
182 ob_start();
183 try {
184 $renderer->render( $template, $order, $receipt_data );
185 } finally {
186 $html = ob_get_clean();
187 }
188
189 return false === $html ? '' : $html;
190 }
191 }
192