WooCommerce PDF Invoices & Packing Slips - Version 2.2.14

Version Description

  • Fix: Set default PHPMailer validator to 'php' (fixing 'setFrom' errors on PHP 7.3)
  • Fix: Attachment path for file lock check
  • Tweak: Don't wait for file lock if locking disabled
  • Tweak: JIT loading of core documents for early requests (before init 15)
Download this release

Release Info

Developer pomegranate
Plugin Icon 128x128 WooCommerce PDF Invoices & Packing Slips
Version 2.2.14
Comparing to
See all releases

Code changes from version 2.2.12 to 2.2.14

includes/class-wcpdf-documents.php CHANGED
@@ -1,106 +1,110 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- if ( ! defined( 'ABSPATH' ) ) {
5
- exit; // Exit if accessed directly
6
- }
7
-
8
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents' ) ) :
9
-
10
- class Documents {
11
-
12
- /** @var array Array of document classes */
13
- public $documents = array();
14
-
15
- /** @var Documents The single instance of the class */
16
- protected static $_instance = null;
17
-
18
- /**
19
- * Main Documents Instance.
20
- *
21
- * Ensures only one instance of Documents is loaded or can be loaded.
22
- *
23
- * @since 2.0
24
- * @static
25
- * @return Documents Main instance
26
- */
27
- public static function instance() {
28
- if ( is_null( self::$_instance ) ) {
29
- self::$_instance = new self();
30
- }
31
- return self::$_instance;
32
- }
33
-
34
- /**
35
- * Constructor for the document class hooks in all documents that can be created.
36
- *
37
- */
38
- public function __construct() {
39
- add_action( 'init', array( $this, 'init' ), 15 ); // after regular 10 actions but before most 'follow-up' actions (usually 20+)
40
- }
41
-
42
- /**
43
- * Init document classes.
44
- */
45
- public function init() {
46
- // Include document abstracts
47
- include_once( dirname( __FILE__ ) . '/documents/abstract-wcpdf-order-document.php' );
48
- include_once( dirname( __FILE__ ) . '/documents/abstract-wcpdf-order-document-methods.php' );
49
- // Include bulk document
50
- include_once( dirname( __FILE__ ) . '/documents/class-wcpdf-bulk-document.php' );
51
- // Document number formatting class
52
- include_once( dirname( __FILE__ ) . '/documents/class-wcpdf-document-number.php' );
53
- // Sequential number handler
54
- include_once( dirname( __FILE__ ) . '/documents/class-wcpdf-sequential-number-store.php' );
55
-
56
- // Load Invoice & Packing Slip
57
- $this->documents['\WPO\WC\PDF_Invoices\Documents\Invoice'] = include( 'documents/class-wcpdf-invoice.php' );
58
- $this->documents['\WPO\WC\PDF_Invoices\Documents\Packing_Slip'] = include( 'documents/class-wcpdf-packing-slip.php' );
59
-
60
- // Allow plugins to add their own documents
61
- $this->documents = apply_filters( 'wpo_wcpdf_document_classes', $this->documents );
62
- }
63
-
64
- /**
65
- * Return the document classes - used in admin to load settings.
66
- *
67
- * @return array
68
- */
69
- public function get_documents( $filter = 'enabled' ) {
70
- if ( $filter == 'enabled' ) {
71
- $documents = array();
72
- foreach ($this->documents as $class_name => $document) {
73
- if ($document->is_enabled()) {
74
- $documents[$class_name] = $document;
75
- }
76
- }
77
- return $documents;
78
- } else {
79
- // return all documents
80
- return $this->documents;
81
- }
82
- }
83
-
84
- public function get_document( $document_type, $order ) {
85
- foreach ($this->documents as $class_name => $document) {
86
- if ($document->get_type() == $document_type) {
87
- return new $class_name( $order );
88
- }
89
- }
90
- // document not known, inject into legacy document
91
- $document = include( WPO_WCPDF()->plugin_path() . '/includes/legacy/class-wcpdf-legacy-document.php' );
92
- // set document properties, which will trigger parent construct and load data correctly
93
- $document->set_props( array(
94
- 'type' => $document_type,
95
- 'title' => '',
96
- 'order' => $order,
97
- ) );
98
-
99
- return $document;
100
- }
101
-
102
- }
103
-
104
- endif; // class_exists
105
-
 
 
 
 
106
  return new Documents();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit; // Exit if accessed directly
6
+ }
7
+
8
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents' ) ) :
9
+
10
+ class Documents {
11
+
12
+ /** @var array Array of document classes */
13
+ public $documents = array();
14
+
15
+ /** @var Documents The single instance of the class */
16
+ protected static $_instance = null;
17
+
18
+ /**
19
+ * Main Documents Instance.
20
+ *
21
+ * Ensures only one instance of Documents is loaded or can be loaded.
22
+ *
23
+ * @since 2.0
24
+ * @static
25
+ * @return Documents Main instance
26
+ */
27
+ public static function instance() {
28
+ if ( is_null( self::$_instance ) ) {
29
+ self::$_instance = new self();
30
+ }
31
+ return self::$_instance;
32
+ }
33
+
34
+ /**
35
+ * Constructor for the document class hooks in all documents that can be created.
36
+ *
37
+ */
38
+ public function __construct() {
39
+ // Include document abstracts
40
+ include_once( dirname( __FILE__ ) . '/documents/abstract-wcpdf-order-document.php' );
41
+ include_once( dirname( __FILE__ ) . '/documents/abstract-wcpdf-order-document-methods.php' );
42
+ // Include bulk document
43
+ include_once( dirname( __FILE__ ) . '/documents/class-wcpdf-bulk-document.php' );
44
+ // Document number formatting class
45
+ include_once( dirname( __FILE__ ) . '/documents/class-wcpdf-document-number.php' );
46
+ // Sequential number handler
47
+ include_once( dirname( __FILE__ ) . '/documents/class-wcpdf-sequential-number-store.php' );
48
+
49
+ add_action( 'init', array( $this, 'init' ), 15 ); // after regular 10 actions but before most 'follow-up' actions (usually 20+)
50
+ }
51
+
52
+ /**
53
+ * Init document classes.
54
+ */
55
+ public function init() {
56
+ // Load Invoice & Packing Slip
57
+ $this->documents['\WPO\WC\PDF_Invoices\Documents\Invoice'] = include( 'documents/class-wcpdf-invoice.php' );
58
+ $this->documents['\WPO\WC\PDF_Invoices\Documents\Packing_Slip'] = include( 'documents/class-wcpdf-packing-slip.php' );
59
+
60
+ }
61
+
62
+ /**
63
+ * Return the document classes - used in admin to load settings.
64
+ *
65
+ * @return array
66
+ */
67
+ public function get_documents( $filter = 'enabled' ) {
68
+ if ( empty($this->documents) ) {
69
+ $this->init();
70
+ }
71
+ // Allow plugins to add their own documents
72
+ $this->documents = apply_filters( 'wpo_wcpdf_document_classes', $this->documents );
73
+
74
+ if ( $filter == 'enabled' ) {
75
+ $documents = array();
76
+ foreach ($this->documents as $class_name => $document) {
77
+ if ($document->is_enabled()) {
78
+ $documents[$class_name] = $document;
79
+ }
80
+ }
81
+ return $documents;
82
+ } else {
83
+ // return all documents
84
+ return $this->documents;
85
+ }
86
+ }
87
+
88
+ public function get_document( $document_type, $order ) {
89
+ foreach ($this->get_documents('all') as $class_name => $document) {
90
+ if ($document->get_type() == $document_type) {
91
+ return new $class_name( $order );
92
+ }
93
+ }
94
+ // document not known, inject into legacy document
95
+ $document = include( WPO_WCPDF()->plugin_path() . '/includes/legacy/class-wcpdf-legacy-document.php' );
96
+ // set document properties, which will trigger parent construct and load data correctly
97
+ $document->set_props( array(
98
+ 'type' => $document_type,
99
+ 'title' => '',
100
+ 'order' => $order,
101
+ ) );
102
+
103
+ return $document;
104
+ }
105
+
106
+ }
107
+
108
+ endif; // class_exists
109
+
110
  return new Documents();
includes/class-wcpdf-main.php CHANGED
@@ -1,667 +1,714 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Main' ) ) :
13
-
14
- class Main {
15
-
16
- function __construct() {
17
- add_action( 'wp_ajax_generate_wpo_wcpdf', array($this, 'generate_pdf_ajax' ) );
18
- add_action( 'wp_ajax_nopriv_generate_wpo_wcpdf', array($this, 'generate_pdf_ajax' ) );
19
- add_filter( 'woocommerce_email_attachments', array( $this, 'attach_pdf_to_email' ), 99, 3 );
20
- add_filter( 'wpo_wcpdf_custom_attachment_condition', array( $this, 'disable_free_attachment'), 1001, 4 );
21
-
22
- if ( isset(WPO_WCPDF()->settings->debug_settings['enable_debug']) ) {
23
- $this->enable_debug();
24
- }
25
-
26
- // include template specific custom functions
27
- $template_path = WPO_WCPDF()->settings->get_template_path();
28
- if ( file_exists( $template_path . '/template-functions.php' ) ) {
29
- require_once( $template_path . '/template-functions.php' );
30
- }
31
-
32
- // test mode
33
- add_filter( 'wpo_wcpdf_document_use_historical_settings', array( $this, 'test_mode_settings' ), 15, 2 );
34
-
35
- // page numbers & currency filters
36
- add_action( 'wpo_wcpdf_get_html', array($this, 'format_page_number_placeholders' ), 10, 2 );
37
- add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 2 );
38
- if ( isset( WPO_WCPDF()->settings->general_settings['currency_font'] ) ) {
39
- add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ), 10, 2 );
40
- }
41
-
42
- // scheduled attachments cleanup (following settings on Status tab)
43
- add_action( 'wp_scheduled_delete', array( $this, 'attachments_cleanup') );
44
-
45
- // remove private data
46
- add_action( 'woocommerce_privacy_remove_order_personal_data_meta', array( $this, 'remove_order_personal_data_meta' ), 10, 1 );
47
- add_action( 'woocommerce_privacy_remove_order_personal_data', array( $this, 'remove_order_personal_data' ), 10, 1 );
48
- // export private data
49
- add_action( 'woocommerce_privacy_export_order_personal_data_meta', array( $this, 'export_order_personal_data_meta' ), 10, 1 );
50
- }
51
-
52
- /**
53
- * Attach PDF to WooCommerce email
54
- */
55
- public function attach_pdf_to_email ( $attachments, $email_id, $order ) {
56
- // check if all variables properly set
57
- if ( !is_object( $order ) || !isset( $email_id ) ) {
58
- return $attachments;
59
- }
60
-
61
- // Skip User emails
62
- if ( get_class( $order ) == 'WP_User' ) {
63
- return $attachments;
64
- }
65
-
66
- $order_id = WCX_Order::get_id( $order );
67
-
68
- if ( get_class( $order ) !== 'WC_Order' && $order_id == false ) {
69
- return $attachments;
70
- }
71
-
72
- // WooCommerce Booking compatibility
73
- if ( get_post_type( $order_id ) == 'wc_booking' && isset($order->order) ) {
74
- // $order is actually a WC_Booking object!
75
- $order = $order->order;
76
- }
77
-
78
- // do not process low stock notifications, user emails etc!
79
- if ( in_array( $email_id, array( 'no_stock', 'low_stock', 'backorder', 'customer_new_account', 'customer_reset_password' ) ) || get_post_type( $order_id ) != 'shop_order' ) {
80
- return $attachments;
81
- }
82
-
83
- $tmp_path = $this->get_tmp_path('attachments');
84
-
85
- // clear pdf files from temp folder (from http://stackoverflow.com/a/13468943/1446634)
86
- // array_map('unlink', ( glob( $tmp_path.'*.pdf' ) ? glob( $tmp_path.'*.pdf' ) : array() ) );
87
-
88
- // disable deprecation notices during email sending
89
- add_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
90
-
91
- // reload translations because WC may have switched to site locale (by setting the plugin_locale filter to site locale in wc_switch_to_site_locale())
92
- WPO_WCPDF()->translations();
93
- do_action( 'wpo_wcpdf_reload_attachment_translations' );
94
-
95
- $attach_to_document_types = $this->get_documents_for_email( $email_id, $order );
96
- foreach ( $attach_to_document_types as $document_type ) {
97
- do_action( 'wpo_wcpdf_before_attachment_creation', $order, $email_id, $document_type );
98
-
99
- try {
100
- // prepare document
101
- $document = wcpdf_get_document( $document_type, (array) $order_id, true );
102
- if ( !$document ) { // something went wrong, continue trying with other documents
103
- continue;
104
- }
105
- $filename = $document->get_filename();
106
- $pdf_path = $tmp_path . $filename;
107
-
108
- // if this file already exists in the temp path, we'll reuse it if it's not older than 60 seconds
109
- if (file_exists($pdf_path)) {
110
- // get last modification date
111
- if ($filemtime = filemtime($pdf_path)) {
112
- $time_difference = time() - $filemtime;
113
- if ( $time_difference < apply_filters( 'wpo_wcpdf_reuse_attachment_age', 60 ) ) {
114
- // check if file is still being written to
115
- $fp = fopen($pdf_path, 'r+');
116
- if ( $locked = $this->file_is_locked( $fp ) ) {
117
- // optional delay (ms) to double check if the write process is finished
118
- $delay = intval( apply_filters( 'wpo_wcpdf_attachment_locked_file_delay', 250 ) );
119
- if ( $delay > 0 ) {
120
- usleep( $delay * 1000 );
121
- $locked = $this->file_is_locked( $fp );
122
- }
123
- }
124
- fclose($fp);
125
-
126
- if ( !$locked ) {
127
- $attachments[] = $pdf_path;
128
- continue;
129
- } else {
130
- // make sure this gets logged
131
- throw new \Exception("Failed attachment, file locked");
132
- }
133
- }
134
- }
135
- }
136
-
137
- // get pdf data & store
138
- $pdf_data = $document->get_pdf();
139
- file_put_contents ( $pdf_path, $pdf_data, LOCK_EX );
140
- $attachments[] = $pdf_path;
141
-
142
- do_action( 'wpo_wcpdf_email_attachment', $pdf_path, $document_type, $document );
143
- } catch ( \Exception $e ) {
144
- wcpdf_log_error( $e->getMessage(), 'critical', $e );
145
- continue;
146
- } catch ( \Dompdf\Exception $e ) {
147
- wcpdf_log_error( 'DOMPDF exception: '.$e->getMessage(), 'critical', $e );
148
- continue;
149
- } catch ( \Error $e ) {
150
- wcpdf_log_error( $e->getMessage(), 'critical', $e );
151
- continue;
152
- }
153
- }
154
-
155
- remove_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
156
-
157
- return $attachments;
158
- }
159
-
160
- public function file_is_locked( $fp ) {
161
- if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
162
- if ($wouldblock) {
163
- return true; // file is locked
164
- } else {
165
- return true; // can't lock for whatever reason (could be locked in Windows + PHP5.3)
166
- }
167
- } else {
168
- return false; // not locked
169
- }
170
- }
171
-
172
- public function get_documents_for_email( $email_id, $order ) {
173
- $documents = WPO_WCPDF()->documents->get_documents();
174
-
175
- $attach_documents = array();
176
- foreach ($documents as $document) {
177
- $attach_documents[ $document->get_type() ] = $document->get_attach_to_email_ids();
178
- }
179
- $attach_documents = apply_filters('wpo_wcpdf_attach_documents', $attach_documents );
180
-
181
- $document_types = array();
182
- foreach ($attach_documents as $document_type => $attach_to_email_ids ) {
183
- // legacy settings: convert abbreviated email_ids
184
- foreach ($attach_to_email_ids as $key => $attach_to_email_id) {
185
- if ($attach_to_email_id == 'completed' || $attach_to_email_id == 'processing') {
186
- $attach_to_email_ids[$key] = "customer_" . $attach_to_email_id . "_order";
187
- }
188
- }
189
-
190
- $extra_condition = apply_filters('wpo_wcpdf_custom_attachment_condition', true, $order, $email_id, $document_type );
191
- if ( in_array( $email_id, $attach_to_email_ids ) && $extra_condition === true ) {
192
- $document_types[] = $document_type;
193
- }
194
- }
195
-
196
- return $document_types;
197
- }
198
-
199
- /**
200
- * Load and generate the template output with ajax
201
- */
202
- public function generate_pdf_ajax() {
203
- $guest_access = isset( WPO_WCPDF()->settings->debug_settings['guest_access'] );
204
- if ( !$guest_access && current_filter() == 'wp_ajax_nopriv_generate_wpo_wcpdf') {
205
- wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
206
- }
207
-
208
- // Check the nonce - guest access doesn't use nonces but checks the unique order key (hash)
209
- if( empty( $_GET['action'] ) || ( !$guest_access && !check_admin_referer( $_GET['action'] ) ) ) {
210
- wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
211
- }
212
-
213
- // Check if all parameters are set
214
- if ( empty( $_GET['document_type'] ) && !empty( $_GET['template_type'] ) ) {
215
- $_GET['document_type'] = $_GET['template_type'];
216
- }
217
-
218
- if ( empty( $_GET['order_ids'] ) ) {
219
- wp_die( __( "You haven't selected any orders", 'woocommerce-pdf-invoices-packing-slips' ) );
220
- }
221
-
222
- if( empty( $_GET['document_type'] ) ) {
223
- wp_die( __( 'Some of the export parameters are missing.', 'woocommerce-pdf-invoices-packing-slips' ) );
224
- }
225
-
226
- // debug enabled by URL
227
- if ( isset( $_GET['debug'] ) && !( $guest_access || isset( $_GET['my-account'] ) ) ) {
228
- $this->enable_debug();
229
- }
230
-
231
- // Generate the output
232
- $document_type = sanitize_text_field( $_GET['document_type'] );
233
-
234
- $order_ids = (array) array_map( 'absint', explode( 'x', $_GET['order_ids'] ) );
235
- // Process oldest first: reverse $order_ids array
236
- $order_ids = array_reverse( $order_ids );
237
-
238
- // set default is allowed
239
- $allowed = true;
240
-
241
-
242
- if ( $guest_access && isset( $_GET['order_key'] ) ) {
243
- // Guest access with order key
244
- if ( count( $order_ids ) > 1 ) {
245
- $allowed = false;
246
- } else {
247
- $order = wc_get_order( $order_ids[0] );
248
- if ( !$order || ! hash_equals( $order->get_order_key(), $_GET['order_key'] ) ) {
249
- $allowed = false;
250
- }
251
- }
252
- } else {
253
- // check if user is logged in
254
- if ( ! is_user_logged_in() ) {
255
- $allowed = false;
256
- }
257
-
258
- // Check the user privileges
259
- if( !( current_user_can( 'manage_woocommerce_orders' ) || current_user_can( 'edit_shop_orders' ) ) && !isset( $_GET['my-account'] ) ) {
260
- $allowed = false;
261
- }
262
-
263
- // User call from my-account page
264
- if ( !current_user_can('manage_options') && isset( $_GET['my-account'] ) ) {
265
- // Only for single orders!
266
- if ( count( $order_ids ) > 1 ) {
267
- $allowed = false;
268
- }
269
-
270
- // Check if current user is owner of order IMPORTANT!!!
271
- if ( ! current_user_can( 'view_order', $order_ids[0] ) ) {
272
- $allowed = false;
273
- }
274
- }
275
- }
276
-
277
- $allowed = apply_filters( 'wpo_wcpdf_check_privs', $allowed, $order_ids );
278
-
279
- if ( ! $allowed ) {
280
- wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
281
- }
282
-
283
- // if we got here, we're safe to go!
284
- try {
285
- $document = wcpdf_get_document( $document_type, $order_ids, true );
286
-
287
- if ( $document ) {
288
- $output_format = WPO_WCPDF()->settings->get_output_format( $document_type );
289
- // allow URL override
290
- if ( isset( $_GET['output'] ) && in_array( $_GET['output'], array( 'html', 'pdf' ) ) ) {
291
- $output_format = $_GET['output'];
292
- }
293
- switch ( $output_format ) {
294
- case 'html':
295
- add_filter( 'wpo_wcpdf_use_path', '__return_false' );
296
- $document->output_html();
297
- break;
298
- case 'pdf':
299
- default:
300
- if ( has_action( 'wpo_wcpdf_created_manually' ) ) {
301
- do_action( 'wpo_wcpdf_created_manually', $document->get_pdf(), $document->get_filename() );
302
- }
303
- $output_mode = WPO_WCPDF()->settings->get_output_mode( $document_type );
304
- $document->output_pdf( $output_mode );
305
- break;
306
- }
307
- } else {
308
- wp_die( sprintf( __( "Document of type '%s' for the selected order(s) could not be generated", 'woocommerce-pdf-invoices-packing-slips' ), $document_type ) );
309
- }
310
- } catch ( \Dompdf\Exception $e ) {
311
- $message = 'DOMPDF Exception: '.$e->getMessage();
312
- wcpdf_log_error( $message, 'critical', $e );
313
- wcpdf_output_error( $message, 'critical', $e );
314
- } catch ( \Exception $e ) {
315
- $message = 'Exception: '.$e->getMessage();
316
- wcpdf_log_error( $message, 'critical', $e );
317
- wcpdf_output_error( $message, 'critical', $e );
318
- } catch ( \Error $e ) {
319
- $message = 'Fatal error: '.$e->getMessage();
320
- wcpdf_log_error( $message, 'critical', $e );
321
- wcpdf_output_error( $message, 'critical', $e );
322
- }
323
- exit;
324
- }
325
-
326
- /**
327
- * Return tmp path for different plugin processes
328
- */
329
- public function get_tmp_path ( $type = '' ) {
330
- $tmp_base = $this->get_tmp_base();
331
-
332
- // don't continue if we don't have an upload dir
333
- if ($tmp_base === false) {
334
- return false;
335
- }
336
-
337
- // check if tmp folder exists => if not, initialize
338
- if ( !@is_dir( $tmp_base ) ) {
339
- $this->init_tmp( $tmp_base );
340
- }
341
-
342
- if ( empty( $type ) ) {
343
- return $tmp_base;
344
- }
345
-
346
- switch ( $type ) {
347
- case 'dompdf':
348
- $tmp_path = $tmp_base . 'dompdf';
349
- break;
350
- case 'font_cache':
351
- case 'fonts':
352
- $tmp_path = $tmp_base . 'fonts';
353
- break;
354
- case 'attachments':
355
- $tmp_path = $tmp_base . 'attachments/';
356
- break;
357
- default:
358
- $tmp_path = $tmp_base . $type;
359
- break;
360
- }
361
-
362
- // double check for existence, in case tmp_base was installed, but subfolder not created
363
- if ( !@is_dir( $tmp_path ) ) {
364
- @mkdir( $tmp_path );
365
- }
366
-
367
- return $tmp_path;
368
- }
369
-
370
- /**
371
- * return the base tmp folder (usually uploads)
372
- */
373
- public function get_tmp_base () {
374
- // wp_upload_dir() is used to set the base temp folder, under which a
375
- // 'wpo_wcpdf' folder and several subfolders are created
376
- //
377
- // wp_upload_dir() will:
378
- // * default to WP_CONTENT_DIR/uploads
379
- // * UNLESS the ‘UPLOADS’ constant is defined in wp-config (http://codex.wordpress.org/Editing_wp-config.php#Moving_uploads_folder)
380
- //
381
- // May also be overridden by the wpo_wcpdf_tmp_path filter
382
-
383
- $upload_dir = wp_upload_dir();
384
- if (!empty($upload_dir['error'])) {
385
- $tmp_base = false;
386
- } else {
387
- $upload_base = trailingslashit( $upload_dir['basedir'] );
388
- $tmp_base = $upload_base . 'wpo_wcpdf/';
389
- }
390
-
391
- $tmp_base = apply_filters( 'wpo_wcpdf_tmp_path', $tmp_base );
392
- if ($tmp_base !== false) {
393
- $tmp_base = trailingslashit( $tmp_base );
394
- }
395
-
396
- return $tmp_base;
397
- }
398
-
399
- /**
400
- * Install/create plugin tmp folders
401
- */
402
- public function init_tmp ( $tmp_base ) {
403
- // create plugin base temp folder
404
- mkdir( $tmp_base );
405
-
406
- if (!is_dir($tmp_base)) {
407
- wcpdf_log_error( "Unable to create temp folder {$tmp_base}", 'critical' );
408
- }
409
-
410
- // create subfolders & protect
411
- $subfolders = array( 'attachments', 'fonts', 'dompdf' );
412
- foreach ( $subfolders as $subfolder ) {
413
- $path = $tmp_base . $subfolder . '/';
414
- mkdir( $path );
415
-
416
- // copy font files
417
- if ( $subfolder == 'fonts' ) {
418
- $this->copy_fonts( $path, false );
419
- }
420
-
421
- // create .htaccess file and empty index.php to protect in case an open webfolder is used!
422
- file_put_contents( $path . '.htaccess', 'deny from all' );
423
- touch( $path . 'index.php' );
424
- }
425
-
426
- }
427
-
428
- /**
429
- * Copy DOMPDF fonts to wordpress tmp folder
430
- */
431
- public function copy_fonts ( $path, $merge_with_local = true ) {
432
- $path = trailingslashit( $path );
433
- $dompdf_font_dir = WPO_WCPDF()->plugin_path() . "/vendor/dompdf/dompdf/lib/fonts/";
434
-
435
- // get local font dir from filtered options
436
- $dompdf_options = apply_filters( 'wpo_wcpdf_dompdf_options', array(
437
- 'defaultFont' => 'dejavu sans',
438
- 'tempDir' => $this->get_tmp_path('dompdf'),
439
- 'logOutputFile' => $this->get_tmp_path('dompdf') . "/log.htm",
440
- 'fontDir' => $this->get_tmp_path('fonts'),
441
- 'fontCache' => $this->get_tmp_path('fonts'),
442
- 'isRemoteEnabled' => true,
443
- 'isFontSubsettingEnabled' => true,
444
- 'isHtml5ParserEnabled' => true,
445
- ) );
446
- $fontDir = $dompdf_options['fontDir'];
447
-
448
- // merge font family cache with local/custom if present
449
- $font_cache_files = array(
450
- 'cache' => 'dompdf_font_family_cache.php',
451
- 'cache_dist' => 'dompdf_font_family_cache.dist.php',
452
- );
453
- foreach ( $font_cache_files as $font_cache_name => $font_cache_filename ) {
454
- $plugin_fonts = @require $dompdf_font_dir . $font_cache_filename;
455
- if ( $merge_with_local && is_readable( $path . $font_cache_filename ) ) {
456
- $local_fonts = @require $path . $font_cache_filename;
457
- if (is_array($local_fonts) && is_array($plugin_fonts)) {
458
- // merge local & plugin fonts, plugin fonts overwrite (update) local fonts
459
- // while custom local fonts are retained
460
- $local_fonts = array_merge($local_fonts, $plugin_fonts);
461
- // create readable array with $fontDir in place of the actual folder for portability
462
- $fonts_export = var_export($local_fonts,true);
463
- $fonts_export = str_replace('\'' . $fontDir , '$fontDir . \'', $fonts_export);
464
- $cacheData = sprintf("<?php return %s;%s?>", $fonts_export, PHP_EOL );
465
- // write file with merged cache data
466
- file_put_contents($path . $font_cache_filename, $cacheData);
467
- } else { // empty local file
468
- copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
469
- }
470
- } else {
471
- // we couldn't read the local font cache file so we're simply copying over plugin cache file
472
- copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
473
- }
474
- }
475
-
476
- // first try the easy way with glob!
477
- if ( function_exists('glob') ) {
478
- $files = glob($dompdf_font_dir."*.*");
479
- foreach($files as $file){
480
- $filename = basename($file);
481
- if( !is_dir($file) && is_readable($file) && !in_array($filename, $font_cache_files)) {
482
- $dest = $path . $filename;
483
- copy($file, $dest);
484
- }
485
- }
486
- } else {
487
- // fallback method using font cache file (glob is disabled on some servers with disable_functions)
488
- $extensions = array( '.ttf', '.ufm', '.ufm.php', '.afm', '.afm.php' );
489
- $fontDir = untrailingslashit($dompdf_font_dir);
490
- $plugin_fonts = @require $dompdf_font_dir . $font_cache_files['cache'];
491
-
492
- foreach ($plugin_fonts as $font_family => $filenames) {
493
- foreach ($filenames as $filename) {
494
- foreach ($extensions as $extension) {
495
- $file = $filename.$extension;
496
- if (file_exists($file)) {
497
- $dest = $path . basename($file);
498
- copy($file, $dest);
499
- }
500
- }
501
- }
502
- }
503
- }
504
- }
505
-
506
- public function disable_free_attachment( $attach, $order, $email_id, $document_type ) {
507
- // prevent fatal error for non-order objects
508
- if ( !method_exists( $order, 'get_total' ) ) {
509
- return false;
510
- }
511
-
512
- $document_settings = WPO_WCPDF()->settings->get_document_settings( $document_type );
513
-
514
- // check order total & setting
515
- $order_total = $order->get_total();
516
- if ( $order_total == 0 && isset( $document_settings['disable_free'] ) ) {
517
- return false;
518
- }
519
-
520
- return $attach;
521
- }
522
-
523
- public function test_mode_settings( $use_historical_settings, $document ) {
524
- if ( isset( WPO_WCPDF()->settings->general_settings['test_mode'] ) ) {
525
- $use_historical_settings = false;
526
- }
527
- return $use_historical_settings;
528
- }
529
-
530
- /**
531
- * Adds spans around placeholders to be able to make replacement (page count) and css (page number)
532
- */
533
- public function format_page_number_placeholders ( $html, $document ) {
534
- $html = str_replace('{{PAGE_COUNT}}', '<span class="pagecount">^C^</span>', $html);
535
- $html = str_replace('{{PAGE_NUM}}', '<span class="pagenum"></span>', $html );
536
- return $html;
537
- }
538
-
539
- /**
540
- * Replace {{PAGE_COUNT}} placeholder with total page count
541
- */
542
- public function page_number_replacements ( $dompdf, $html ) {
543
- $placeholder = '^C^';
544
- // create placeholder version with ASCII 0 spaces (dompdf 0.8)
545
- $placeholder_0 = '';
546
- $placeholder_chars = str_split($placeholder);
547
- foreach ($placeholder_chars as $placeholder_char) {
548
- $placeholder_0 .= chr(0).$placeholder_char;
549
- }
550
-
551
- // check if placeholder is used
552
- if (strpos($html, $placeholder) !== false ) {
553
- foreach ($dompdf->get_canvas()->get_cpdf()->objects as &$object) {
554
- if (array_key_exists("c", $object) && strpos($object["c"], $placeholder) !== false ) {
555
- $object["c"] = str_replace( array($placeholder,$placeholder_0) , $dompdf->get_canvas()->get_page_count() , $object["c"] );
556
- } elseif (array_key_exists("c", $object) && strpos($object["c"], $placeholder_0) !== false ) {
557
- $object["c"] = str_replace( array($placeholder,$placeholder_0) , chr(0).$dompdf->get_canvas()->get_page_count() , $object["c"] );
558
- }
559
- }
560
- }
561
-
562
- return $dompdf;
563
- }
564
-
565
- /**
566
- * Use currency symbol font (when enabled in options)
567
- */
568
- public function use_currency_font ( $document_type, $document ) {
569
- add_filter( 'woocommerce_currency_symbol', array( $this, 'wrap_currency_symbol' ), 10001, 2);
570
- add_action( 'wpo_wcpdf_custom_styles', array($this, 'currency_symbol_font_styles' ) );
571
- }
572
-
573
- public function wrap_currency_symbol( $currency_symbol, $currency ) {
574
- $currency_symbol = sprintf( '<span class="wcpdf-currency-symbol">%s</span>', $currency_symbol );
575
- return $currency_symbol;
576
- }
577
-
578
- public function currency_symbol_font_styles () {
579
- ?>
580
- .wcpdf-currency-symbol { font-family: 'Currencies'; }
581
- <?php
582
- }
583
-
584
- /**
585
- * Remove attachments older than 1 week (daily, hooked into wp_scheduled_delete )
586
- */
587
- public function attachments_cleanup() {
588
- if ( !function_exists("glob") || !function_exists('filemtime') ) {
589
- // glob is required
590
- return;
591
- }
592
-
593
-
594
- if ( !isset( WPO_WCPDF()->settings->debug_settings['enable_cleanup'] ) ) {
595
- return;
596
- }
597
-
598
-
599
- $cleanup_age_days = isset(WPO_WCPDF()->settings->debug_settings['cleanup_days']) ? floatval(WPO_WCPDF()->settings->debug_settings['cleanup_days']) : 7.0;
600
- $delete_timestamp = time() - ( intval ( DAY_IN_SECONDS * $cleanup_age_days ) );
601
-
602
- $tmp_path = $this->get_tmp_path('attachments');
603
-
604
- if ( $files = glob( $tmp_path.'*.pdf' ) ) { // get all pdf files
605
- foreach( $files as $file ) {
606
- if( is_file( $file ) ) {
607
- $file_timestamp = filemtime( $file );
608
- if ( !empty( $file_timestamp ) && $file_timestamp < $delete_timestamp ) {
609
- @unlink($file);
610
- }
611
- }
612
- }
613
- }
614
- }
615
-
616
- /**
617
- * Remove all invoice data when requested
618
- */
619
- public function remove_order_personal_data_meta( $meta_to_remove ) {
620
- $wcpdf_private_meta = array(
621
- '_wcpdf_invoice_number' => 'numeric_id',
622
- '_wcpdf_invoice_number_data' => 'array',
623
- '_wcpdf_invoice_date' => 'timestamp',
624
- '_wcpdf_invoice_date_formatted' => 'date',
625
- );
626
- return $meta_to_remove + $wcpdf_private_meta;
627
- }
628
-
629
- /**
630
- * Remove references to order in number store tables when removing WC data
631
- */
632
- public function remove_order_personal_data( $order ) {
633
- global $wpdb;
634
- // remove order ID from number stores
635
- $number_stores = apply_filters( "wpo_wcpdf_privacy_number_stores", array( 'invoice_number' ) );
636
- foreach ( $number_stores as $store_name ) {
637
- $order_id = $order->get_id();
638
- $table_name = apply_filters( "wpo_wcpdf_number_store_table_name", "{$wpdb->prefix}wcpdf_{$store_name}", $store_name, 'auto_increment' ); // i.e. wp_wcpdf_invoice_number
639
- $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET order_id = 0 WHERE order_id = %s", $order_id ) );
640
- }
641
- }
642
-
643
- /**
644
- * Export all invoice data when requested
645
- */
646
- public function export_order_personal_data_meta( $meta_to_export ) {
647
- $private_address_meta = array(
648
- // _wcpdf_invoice_number_data & _wcpdf_invoice_date are duplicates of the below and therefor not included
649
- '_wcpdf_invoice_number' => __( 'Invoice Number', 'woocommerce-pdf-invoices-packing-slips' ),
650
- '_wcpdf_invoice_date_formatted' => __( 'Invoice Date', 'woocommerce-pdf-invoices-packing-slips' ),
651
- );
652
- return $meta_to_export + $private_address_meta;
653
- }
654
-
655
- /**
656
- * Enable PHP error output
657
- */
658
- public function enable_debug () {
659
- error_reporting( E_ALL );
660
- ini_set( 'display_errors', 1 );
661
- }
662
-
663
- }
664
-
665
- endif; // class_exists
666
-
667
- return new Main();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Main' ) ) :
13
+
14
+ class Main {
15
+
16
+ function __construct() {
17
+ add_action( 'wp_ajax_generate_wpo_wcpdf', array($this, 'generate_pdf_ajax' ) );
18
+ add_action( 'wp_ajax_nopriv_generate_wpo_wcpdf', array($this, 'generate_pdf_ajax' ) );
19
+
20
+ // email
21
+ add_filter( 'woocommerce_email_attachments', array( $this, 'attach_pdf_to_email' ), 99, 4 );
22
+ add_filter( 'wpo_wcpdf_custom_attachment_condition', array( $this, 'disable_free_attachment'), 1001, 4 );
23
+ add_filter( 'wp_mail', array( $this, 'set_phpmailer_validator'), 10, 1 );
24
+
25
+ if ( isset(WPO_WCPDF()->settings->debug_settings['enable_debug']) ) {
26
+ $this->enable_debug();
27
+ }
28
+
29
+ // include template specific custom functions
30
+ $template_path = WPO_WCPDF()->settings->get_template_path();
31
+ if ( file_exists( $template_path . '/template-functions.php' ) ) {
32
+ require_once( $template_path . '/template-functions.php' );
33
+ }
34
+
35
+ // test mode
36
+ add_filter( 'wpo_wcpdf_document_use_historical_settings', array( $this, 'test_mode_settings' ), 15, 2 );
37
+
38
+ // page numbers & currency filters
39
+ add_action( 'wpo_wcpdf_get_html', array($this, 'format_page_number_placeholders' ), 10, 2 );
40
+ add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 2 );
41
+ if ( isset( WPO_WCPDF()->settings->general_settings['currency_font'] ) ) {
42
+ add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ), 10, 2 );
43
+ }
44
+
45
+ // scheduled attachments cleanup (following settings on Status tab)
46
+ add_action( 'wp_scheduled_delete', array( $this, 'attachments_cleanup') );
47
+
48
+ // remove private data
49
+ add_action( 'woocommerce_privacy_remove_order_personal_data_meta', array( $this, 'remove_order_personal_data_meta' ), 10, 1 );
50
+ add_action( 'woocommerce_privacy_remove_order_personal_data', array( $this, 'remove_order_personal_data' ), 10, 1 );
51
+ // export private data
52
+ add_action( 'woocommerce_privacy_export_order_personal_data_meta', array( $this, 'export_order_personal_data_meta' ), 10, 1 );
53
+ }
54
+
55
+ /**
56
+ * Attach PDF to WooCommerce email
57
+ */
58
+ public function attach_pdf_to_email ( $attachments, $email_id, $order, $email = null ) {
59
+ $order = apply_filters( 'wpo_wcpdf_email_attachment_order', $order, $email );
60
+ // check if all variables properly set
61
+ if ( !is_object( $order ) || !isset( $email_id ) ) {
62
+ return $attachments;
63
+ }
64
+
65
+ // Skip User emails
66
+ if ( get_class( $order ) == 'WP_User' ) {
67
+ return $attachments;
68
+ }
69
+
70
+ $order_id = WCX_Order::get_id( $order );
71
+
72
+ if ( ! ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) && $order_id == false ) {
73
+ return $attachments;
74
+ }
75
+
76
+ // WooCommerce Booking compatibility
77
+ if ( get_post_type( $order_id ) == 'wc_booking' && isset($order->order) ) {
78
+ // $order is actually a WC_Booking object!
79
+ $order = $order->order;
80
+ }
81
+
82
+ // do not process low stock notifications, user emails etc!
83
+ if ( in_array( $email_id, array( 'no_stock', 'low_stock', 'backorder', 'customer_new_account', 'customer_reset_password' ) ) ) {
84
+ return $attachments;
85
+ }
86
+
87
+ // final check on order object
88
+ if ( ! ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) ) {
89
+ return $attachments;
90
+ }
91
+
92
+ $tmp_path = $this->get_tmp_path('attachments');
93
+
94
+ // clear pdf files from temp folder (from http://stackoverflow.com/a/13468943/1446634)
95
+ // array_map('unlink', ( glob( $tmp_path.'*.pdf' ) ? glob( $tmp_path.'*.pdf' ) : array() ) );
96
+
97
+ // disable deprecation notices during email sending
98
+ add_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
99
+
100
+ // reload translations because WC may have switched to site locale (by setting the plugin_locale filter to site locale in wc_switch_to_site_locale())
101
+ WPO_WCPDF()->translations();
102
+ do_action( 'wpo_wcpdf_reload_attachment_translations' );
103
+
104
+ $attach_to_document_types = $this->get_documents_for_email( $email_id, $order );
105
+ foreach ( $attach_to_document_types as $document_type ) {
106
+ do_action( 'wpo_wcpdf_before_attachment_creation', $order, $email_id, $document_type );
107
+
108
+ try {
109
+ // prepare document
110
+ $document = wcpdf_get_document( $document_type, (array) $order_id, true );
111
+ if ( !$document ) { // something went wrong, continue trying with other documents
112
+ continue;
113
+ }
114
+ $filename = $document->get_filename();
115
+ $pdf_path = $tmp_path . $filename;
116
+
117
+ $lock_file = apply_filters( 'wpo_wcpdf_lock_attachment_file', true );
118
+
119
+ // if this file already exists in the temp path, we'll reuse it if it's not older than 60 seconds
120
+ $max_reuse_age = apply_filters( 'wpo_wcpdf_reuse_attachment_age', 60 );
121
+ if ( file_exists($pdf_path) && $max_reuse_age > 0 ) {
122
+ // get last modification date
123
+ if ($filemtime = filemtime($pdf_path)) {
124
+ $time_difference = time() - $filemtime;
125
+ if ( $time_difference < $max_reuse_age ) {
126
+ // check if file is still being written to
127
+ if ( $lock_file && $this->wait_for_file_lock( $pdf_path ) === false ) {
128
+ $attachments[] = $pdf_path;
129
+ continue;
130
+ } else {
131
+ // make sure this gets logged, but don't abort process
132
+ wcpdf_log_error( "Attachment file locked (reusing: {$pdf_path})", 'critical' );
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ // get pdf data & store
139
+ $pdf_data = $document->get_pdf();
140
+
141
+ if ( $lock_file ) {
142
+ file_put_contents ( $pdf_path, $pdf_data, LOCK_EX );
143
+ } else {
144
+ file_put_contents ( $pdf_path, $pdf_data );
145
+ }
146
+
147
+ // wait for file lock
148
+ if ( $lock_file && $this->wait_for_file_lock( $pdf_path ) === true ) {
149
+ wcpdf_log_error( "Attachment file locked ({$pdf_path})", 'critical' );
150
+ }
151
+
152
+ $attachments[] = $pdf_path;
153
+
154
+ do_action( 'wpo_wcpdf_email_attachment', $pdf_path, $document_type, $document );
155
+ } catch ( \Exception $e ) {
156
+ wcpdf_log_error( $e->getMessage(), 'critical', $e );
157
+ continue;
158
+ } catch ( \Dompdf\Exception $e ) {
159
+ wcpdf_log_error( 'DOMPDF exception: '.$e->getMessage(), 'critical', $e );
160
+ continue;
161
+ } catch ( \Error $e ) {
162
+ wcpdf_log_error( $e->getMessage(), 'critical', $e );
163
+ continue;
164
+ }
165
+ }
166
+
167
+ remove_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
168
+
169
+ return $attachments;
170
+ }
171
+
172
+ public function file_is_locked( $fp ) {
173
+ if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
174
+ if ($wouldblock) {
175
+ return true; // file is locked
176
+ } else {
177
+ return true; // can't lock for whatever reason (could be locked in Windows + PHP5.3)
178
+ }
179
+ } else {
180
+ flock($fp,LOCK_UN); // release lock
181
+ return false; // not locked
182
+ }
183
+ }
184
+
185
+ public function wait_for_file_lock( $path ) {
186
+ $fp = fopen($path, 'r+');
187
+ if ( $locked = $this->file_is_locked( $fp ) ) {
188
+ // optional delay (ms) to double check if the write process is finished
189
+ $delay = intval( apply_filters( 'wpo_wcpdf_attachment_locked_file_delay', 250 ) );
190
+ if ( $delay > 0 ) {
191
+ usleep( $delay * 1000 );
192
+ $locked = $this->file_is_locked( $fp );
193
+ }
194
+ }
195
+ fclose($fp);
196
+
197
+ return $locked;
198
+ }
199
+
200
+ public function get_documents_for_email( $email_id, $order ) {
201
+ $documents = WPO_WCPDF()->documents->get_documents();
202
+
203
+ $attach_documents = array();
204
+ foreach ($documents as $document) {
205
+ $attach_documents[ $document->get_type() ] = $document->get_attach_to_email_ids();
206
+ }
207
+ $attach_documents = apply_filters('wpo_wcpdf_attach_documents', $attach_documents );
208
+
209
+ $document_types = array();
210
+ foreach ($attach_documents as $document_type => $attach_to_email_ids ) {
211
+ // legacy settings: convert abbreviated email_ids
212
+ foreach ($attach_to_email_ids as $key => $attach_to_email_id) {
213
+ if ($attach_to_email_id == 'completed' || $attach_to_email_id == 'processing') {
214
+ $attach_to_email_ids[$key] = "customer_" . $attach_to_email_id . "_order";
215
+ }
216
+ }
217
+
218
+ $extra_condition = apply_filters('wpo_wcpdf_custom_attachment_condition', true, $order, $email_id, $document_type );
219
+ if ( in_array( $email_id, $attach_to_email_ids ) && $extra_condition === true ) {
220
+ $document_types[] = $document_type;
221
+ }
222
+ }
223
+
224
+ return $document_types;
225
+ }
226
+
227
+ /**
228
+ * Load and generate the template output with ajax
229
+ */
230
+ public function generate_pdf_ajax() {
231
+ $guest_access = isset( WPO_WCPDF()->settings->debug_settings['guest_access'] );
232
+ if ( !$guest_access && current_filter() == 'wp_ajax_nopriv_generate_wpo_wcpdf') {
233
+ wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
234
+ }
235
+
236
+ // Check the nonce - guest access doesn't use nonces but checks the unique order key (hash)
237
+ if( empty( $_GET['action'] ) || ( !$guest_access && !check_admin_referer( $_GET['action'] ) ) ) {
238
+ wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
239
+ }
240
+
241
+ // Check if all parameters are set
242
+ if ( empty( $_GET['document_type'] ) && !empty( $_GET['template_type'] ) ) {
243
+ $_GET['document_type'] = $_GET['template_type'];
244
+ }
245
+
246
+ if ( empty( $_GET['order_ids'] ) ) {
247
+ wp_die( __( "You haven't selected any orders", 'woocommerce-pdf-invoices-packing-slips' ) );
248
+ }
249
+
250
+ if( empty( $_GET['document_type'] ) ) {
251
+ wp_die( __( 'Some of the export parameters are missing.', 'woocommerce-pdf-invoices-packing-slips' ) );
252
+ }
253
+
254
+ // debug enabled by URL
255
+ if ( isset( $_GET['debug'] ) && !( $guest_access || isset( $_GET['my-account'] ) ) ) {
256
+ $this->enable_debug();
257
+ }
258
+
259
+ // Generate the output
260
+ $document_type = sanitize_text_field( $_GET['document_type'] );
261
+
262
+ $order_ids = (array) array_map( 'absint', explode( 'x', $_GET['order_ids'] ) );
263
+ // Process oldest first: reverse $order_ids array
264
+ $order_ids = array_reverse( $order_ids );
265
+
266
+ // set default is allowed
267
+ $allowed = true;
268
+
269
+
270
+ if ( $guest_access && isset( $_GET['order_key'] ) ) {
271
+ // Guest access with order key
272
+ if ( count( $order_ids ) > 1 ) {
273
+ $allowed = false;
274
+ } else {
275
+ $order = wc_get_order( $order_ids[0] );
276
+ if ( !$order || ! hash_equals( $order->get_order_key(), $_GET['order_key'] ) ) {
277
+ $allowed = false;
278
+ }
279
+ }
280
+ } else {
281
+ // check if user is logged in
282
+ if ( ! is_user_logged_in() ) {
283
+ $allowed = false;
284
+ }
285
+
286
+ // Check the user privileges
287
+ if( !( current_user_can( 'manage_woocommerce_orders' ) || current_user_can( 'edit_shop_orders' ) ) && !isset( $_GET['my-account'] ) ) {
288
+ $allowed = false;
289
+ }
290
+
291
+ // User call from my-account page
292
+ if ( !current_user_can('manage_options') && isset( $_GET['my-account'] ) ) {
293
+ // Only for single orders!
294
+ if ( count( $order_ids ) > 1 ) {
295
+ $allowed = false;
296
+ }
297
+
298
+ // Check if current user is owner of order IMPORTANT!!!
299
+ if ( ! current_user_can( 'view_order', $order_ids[0] ) ) {
300
+ $allowed = false;
301
+ }
302
+ }
303
+ }
304
+
305
+ $allowed = apply_filters( 'wpo_wcpdf_check_privs', $allowed, $order_ids );
306
+
307
+ if ( ! $allowed ) {
308
+ wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
309
+ }
310
+
311
+ // if we got here, we're safe to go!
312
+ try {
313
+ $document = wcpdf_get_document( $document_type, $order_ids, true );
314
+
315
+ if ( $document ) {
316
+ $output_format = WPO_WCPDF()->settings->get_output_format( $document_type );
317
+ // allow URL override
318
+ if ( isset( $_GET['output'] ) && in_array( $_GET['output'], array( 'html', 'pdf' ) ) ) {
319
+ $output_format = $_GET['output'];
320
+ }
321
+ switch ( $output_format ) {
322
+ case 'html':
323
+ add_filter( 'wpo_wcpdf_use_path', '__return_false' );
324
+ $document->output_html();
325
+ break;
326
+ case 'pdf':
327
+ default:
328
+ if ( has_action( 'wpo_wcpdf_created_manually' ) ) {
329
+ do_action( 'wpo_wcpdf_created_manually', $document->get_pdf(), $document->get_filename() );
330
+ }
331
+ $output_mode = WPO_WCPDF()->settings->get_output_mode( $document_type );
332
+ $document->output_pdf( $output_mode );
333
+ break;
334
+ }
335
+ } else {
336
+ wp_die( sprintf( __( "Document of type '%s' for the selected order(s) could not be generated", 'woocommerce-pdf-invoices-packing-slips' ), $document_type ) );
337
+ }
338
+ } catch ( \Dompdf\Exception $e ) {
339
+ $message = 'DOMPDF Exception: '.$e->getMessage();
340
+ wcpdf_log_error( $message, 'critical', $e );
341
+ wcpdf_output_error( $message, 'critical', $e );
342
+ } catch ( \Exception $e ) {
343
+ $message = 'Exception: '.$e->getMessage();
344
+ wcpdf_log_error( $message, 'critical', $e );
345
+ wcpdf_output_error( $message, 'critical', $e );
346
+ } catch ( \Error $e ) {
347
+ $message = 'Fatal error: '.$e->getMessage();
348
+ wcpdf_log_error( $message, 'critical', $e );
349
+ wcpdf_output_error( $message, 'critical', $e );
350
+ }
351
+ exit;
352
+ }
353
+
354
+ /**
355
+ * Return tmp path for different plugin processes
356
+ */
357
+ public function get_tmp_path ( $type = '' ) {
358
+ $tmp_base = $this->get_tmp_base();
359
+
360
+ // don't continue if we don't have an upload dir
361
+ if ($tmp_base === false) {
362
+ return false;
363
+ }
364
+
365
+ // check if tmp folder exists => if not, initialize
366
+ if ( !@is_dir( $tmp_base ) ) {
367
+ $this->init_tmp( $tmp_base );
368
+ }
369
+
370
+ if ( empty( $type ) ) {
371
+ return $tmp_base;
372
+ }
373
+
374
+ switch ( $type ) {
375
+ case 'dompdf':
376
+ $tmp_path = $tmp_base . 'dompdf';
377
+ break;
378
+ case 'font_cache':
379
+ case 'fonts':
380
+ $tmp_path = $tmp_base . 'fonts';
381
+ break;
382
+ case 'attachments':
383
+ $tmp_path = $tmp_base . 'attachments/';
384
+ break;
385
+ default:
386
+ $tmp_path = $tmp_base . $type;
387
+ break;
388
+ }
389
+
390
+ // double check for existence, in case tmp_base was installed, but subfolder not created
391
+ if ( !@is_dir( $tmp_path ) ) {
392
+ @mkdir( $tmp_path );
393
+ }
394
+
395
+ return $tmp_path;
396
+ }
397
+
398
+ /**
399
+ * return the base tmp folder (usually uploads)
400
+ */
401
+ public function get_tmp_base () {
402
+ // wp_upload_dir() is used to set the base temp folder, under which a
403
+ // 'wpo_wcpdf' folder and several subfolders are created
404
+ //
405
+ // wp_upload_dir() will:
406
+ // * default to WP_CONTENT_DIR/uploads
407
+ // * UNLESS the ‘UPLOADS’ constant is defined in wp-config (http://codex.wordpress.org/Editing_wp-config.php#Moving_uploads_folder)
408
+ //
409
+ // May also be overridden by the wpo_wcpdf_tmp_path filter
410
+
411
+ $upload_dir = wp_upload_dir();
412
+ if (!empty($upload_dir['error'])) {
413
+ $tmp_base = false;
414
+ } else {
415
+ $upload_base = trailingslashit( $upload_dir['basedir'] );
416
+ $tmp_base = $upload_base . 'wpo_wcpdf/';
417
+ }
418
+
419
+ $tmp_base = apply_filters( 'wpo_wcpdf_tmp_path', $tmp_base );
420
+ if ($tmp_base !== false) {
421
+ $tmp_base = trailingslashit( $tmp_base );
422
+ }
423
+
424
+ return $tmp_base;
425
+ }
426
+
427
+ /**
428
+ * Install/create plugin tmp folders
429
+ */
430
+ public function init_tmp ( $tmp_base ) {
431
+ // create plugin base temp folder
432
+ mkdir( $tmp_base );
433
+
434
+ if (!is_dir($tmp_base)) {
435
+ wcpdf_log_error( "Unable to create temp folder {$tmp_base}", 'critical' );
436
+ }
437
+
438
+ // create subfolders & protect
439
+ $subfolders = array( 'attachments', 'fonts', 'dompdf' );
440
+ foreach ( $subfolders as $subfolder ) {
441
+ $path = $tmp_base . $subfolder . '/';
442
+ mkdir( $path );
443
+
444
+ // copy font files
445
+ if ( $subfolder == 'fonts' ) {
446
+ $this->copy_fonts( $path, false );
447
+ }
448
+
449
+ // create .htaccess file and empty index.php to protect in case an open webfolder is used!
450
+ file_put_contents( $path . '.htaccess', 'deny from all' );
451
+ touch( $path . 'index.php' );
452
+ }
453
+
454
+ }
455
+
456
+ /**
457
+ * Copy DOMPDF fonts to wordpress tmp folder
458
+ */
459
+ public function copy_fonts ( $path, $merge_with_local = true ) {
460
+ $path = trailingslashit( $path );
461
+ $dompdf_font_dir = WPO_WCPDF()->plugin_path() . "/vendor/dompdf/dompdf/lib/fonts/";
462
+
463
+ // get local font dir from filtered options
464
+ $dompdf_options = apply_filters( 'wpo_wcpdf_dompdf_options', array(
465
+ 'defaultFont' => 'dejavu sans',
466
+ 'tempDir' => $this->get_tmp_path('dompdf'),
467
+ 'logOutputFile' => $this->get_tmp_path('dompdf') . "/log.htm",
468
+ 'fontDir' => $this->get_tmp_path('fonts'),
469
+ 'fontCache' => $this->get_tmp_path('fonts'),
470
+ 'isRemoteEnabled' => true,
471
+ 'isFontSubsettingEnabled' => true,
472
+ 'isHtml5ParserEnabled' => true,
473
+ ) );
474
+ $fontDir = $dompdf_options['fontDir'];
475
+
476
+ // merge font family cache with local/custom if present
477
+ $font_cache_files = array(
478
+ 'cache' => 'dompdf_font_family_cache.php',
479
+ 'cache_dist' => 'dompdf_font_family_cache.dist.php',
480
+ );
481
+ foreach ( $font_cache_files as $font_cache_name => $font_cache_filename ) {
482
+ $plugin_fonts = @require $dompdf_font_dir . $font_cache_filename;
483
+ if ( $merge_with_local && is_readable( $path . $font_cache_filename ) ) {
484
+ $local_fonts = @require $path . $font_cache_filename;
485
+ if (is_array($local_fonts) && is_array($plugin_fonts)) {
486
+ // merge local & plugin fonts, plugin fonts overwrite (update) local fonts
487
+ // while custom local fonts are retained
488
+ $local_fonts = array_merge($local_fonts, $plugin_fonts);
489
+ // create readable array with $fontDir in place of the actual folder for portability
490
+ $fonts_export = var_export($local_fonts,true);
491
+ $fonts_export = str_replace('\'' . $fontDir , '$fontDir . \'', $fonts_export);
492
+ $cacheData = sprintf("<?php return %s;%s?>", $fonts_export, PHP_EOL );
493
+ // write file with merged cache data
494
+ file_put_contents($path . $font_cache_filename, $cacheData);
495
+ } else { // empty local file
496
+ copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
497
+ }
498
+ } else {
499
+ // we couldn't read the local font cache file so we're simply copying over plugin cache file
500
+ copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
501
+ }
502
+ }
503
+
504
+ // first try the easy way with glob!
505
+ if ( function_exists('glob') ) {
506
+ $files = glob($dompdf_font_dir."*.*");
507
+ foreach($files as $file){
508
+ $filename = basename($file);
509
+ if( !is_dir($file) && is_readable($file) && !in_array($filename, $font_cache_files)) {
510
+ $dest = $path . $filename;
511
+ copy($file, $dest);
512
+ }
513
+ }
514
+ } else {
515
+ // fallback method using font cache file (glob is disabled on some servers with disable_functions)
516
+ $extensions = array( '.ttf', '.ufm', '.ufm.php', '.afm', '.afm.php' );
517
+ $fontDir = untrailingslashit($dompdf_font_dir);
518
+ $plugin_fonts = @require $dompdf_font_dir . $font_cache_files['cache'];
519
+
520
+ foreach ($plugin_fonts as $font_family => $filenames) {
521
+ foreach ($filenames as $filename) {
522
+ foreach ($extensions as $extension) {
523
+ $file = $filename.$extension;
524
+ if (file_exists($file)) {
525
+ $dest = $path . basename($file);
526
+ copy($file, $dest);
527
+ }
528
+ }
529
+ }
530
+ }
531
+ }
532
+ }
533
+
534
+ public function disable_free_attachment( $attach, $order, $email_id, $document_type ) {
535
+ // prevent fatal error for non-order objects
536
+ if ( !method_exists( $order, 'get_total' ) ) {
537
+ return false;
538
+ }
539
+
540
+ $document_settings = WPO_WCPDF()->settings->get_document_settings( $document_type );
541
+
542
+ // check order total & setting
543
+ $order_total = $order->get_total();
544
+ if ( $order_total == 0 && isset( $document_settings['disable_free'] ) ) {
545
+ return false;
546
+ }
547
+
548
+ return $attach;
549
+ }
550
+
551
+ public function test_mode_settings( $use_historical_settings, $document ) {
552
+ if ( isset( WPO_WCPDF()->settings->general_settings['test_mode'] ) ) {
553
+ $use_historical_settings = false;
554
+ }
555
+ return $use_historical_settings;
556
+ }
557
+
558
+ /**
559
+ * Adds spans around placeholders to be able to make replacement (page count) and css (page number)
560
+ */
561
+ public function format_page_number_placeholders ( $html, $document ) {
562
+ $html = str_replace('{{PAGE_COUNT}}', '<span class="pagecount">^C^</span>', $html);
563
+ $html = str_replace('{{PAGE_NUM}}', '<span class="pagenum"></span>', $html );
564
+ return $html;
565
+ }
566
+
567
+ /**
568
+ * Replace {{PAGE_COUNT}} placeholder with total page count
569
+ */
570
+ public function page_number_replacements ( $dompdf, $html ) {
571
+ $placeholder = '^C^';
572
+ // create placeholder version with ASCII 0 spaces (dompdf 0.8)
573
+ $placeholder_0 = '';
574
+ $placeholder_chars = str_split($placeholder);
575
+ foreach ($placeholder_chars as $placeholder_char) {
576
+ $placeholder_0 .= chr(0).$placeholder_char;
577
+ }
578
+
579
+ // check if placeholder is used
580
+ if (strpos($html, $placeholder) !== false ) {
581
+ foreach ($dompdf->get_canvas()->get_cpdf()->objects as &$object) {
582
+ if (array_key_exists("c", $object) && strpos($object["c"], $placeholder) !== false ) {
583
+ $object["c"] = str_replace( array($placeholder,$placeholder_0) , $dompdf->get_canvas()->get_page_count() , $object["c"] );
584
+ } elseif (array_key_exists("c", $object) && strpos($object["c"], $placeholder_0) !== false ) {
585
+ $object["c"] = str_replace( array($placeholder,$placeholder_0) , chr(0).$dompdf->get_canvas()->get_page_count() , $object["c"] );
586
+ }
587
+ }
588
+ }
589
+
590
+ return $dompdf;
591
+ }
592
+
593
+ /**
594
+ * Use currency symbol font (when enabled in options)
595
+ */
596
+ public function use_currency_font ( $document_type, $document ) {
597
+ add_filter( 'woocommerce_currency_symbol', array( $this, 'wrap_currency_symbol' ), 10001, 2);
598
+ add_action( 'wpo_wcpdf_custom_styles', array($this, 'currency_symbol_font_styles' ) );
599
+ }
600
+
601
+ public function wrap_currency_symbol( $currency_symbol, $currency ) {
602
+ $currency_symbol = sprintf( '<span class="wcpdf-currency-symbol">%s</span>', $currency_symbol );
603
+ return $currency_symbol;
604
+ }
605
+
606
+ public function currency_symbol_font_styles () {
607
+ ?>
608
+ .wcpdf-currency-symbol { font-family: 'Currencies'; }
609
+ <?php
610
+ }
611
+
612
+ /**
613
+ * Remove attachments older than 1 week (daily, hooked into wp_scheduled_delete )
614
+ */
615
+ public function attachments_cleanup() {
616
+ if ( !function_exists("glob") || !function_exists('filemtime') ) {
617
+ // glob is required
618
+ return;
619
+ }
620
+
621
+
622
+ if ( !isset( WPO_WCPDF()->settings->debug_settings['enable_cleanup'] ) ) {
623
+ return;
624
+ }
625
+
626
+
627
+ $cleanup_age_days = isset(WPO_WCPDF()->settings->debug_settings['cleanup_days']) ? floatval(WPO_WCPDF()->settings->debug_settings['cleanup_days']) : 7.0;
628
+ $delete_timestamp = time() - ( intval ( DAY_IN_SECONDS * $cleanup_age_days ) );
629
+
630
+ $tmp_path = $this->get_tmp_path('attachments');
631
+
632
+ if ( $files = glob( $tmp_path.'*.pdf' ) ) { // get all pdf files
633
+ foreach( $files as $file ) {
634
+ if( is_file( $file ) ) {
635
+ $file_timestamp = filemtime( $file );
636
+ if ( !empty( $file_timestamp ) && $file_timestamp < $delete_timestamp ) {
637
+ @unlink($file);
638
+ }
639
+ }
640
+ }
641
+ }
642
+ }
643
+
644
+ /**
645
+ * Remove all invoice data when requested
646
+ */
647
+ public function remove_order_personal_data_meta( $meta_to_remove ) {
648
+ $wcpdf_private_meta = array(
649
+ '_wcpdf_invoice_number' => 'numeric_id',
650
+ '_wcpdf_invoice_number_data' => 'array',
651
+ '_wcpdf_invoice_date' => 'timestamp',
652
+ '_wcpdf_invoice_date_formatted' => 'date',
653
+ );
654
+ return $meta_to_remove + $wcpdf_private_meta;
655
+ }
656
+
657
+ /**
658
+ * Remove references to order in number store tables when removing WC data
659
+ */
660
+ public function remove_order_personal_data( $order ) {
661
+ global $wpdb;
662
+ // remove order ID from number stores
663
+ $number_stores = apply_filters( "wpo_wcpdf_privacy_number_stores", array( 'invoice_number' ) );
664
+ foreach ( $number_stores as $store_name ) {
665
+ $order_id = $order->get_id();
666
+ $table_name = apply_filters( "wpo_wcpdf_number_store_table_name", "{$wpdb->prefix}wcpdf_{$store_name}", $store_name, 'auto_increment' ); // i.e. wp_wcpdf_invoice_number
667
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET order_id = 0 WHERE order_id = %s", $order_id ) );
668
+ }
669
+ }
670
+
671
+ /**
672
+ * Export all invoice data when requested
673
+ */
674
+ public function export_order_personal_data_meta( $meta_to_export ) {
675
+ $private_address_meta = array(
676
+ // _wcpdf_invoice_number_data & _wcpdf_invoice_date are duplicates of the below and therefor not included
677
+ '_wcpdf_invoice_number' => __( 'Invoice Number', 'woocommerce-pdf-invoices-packing-slips' ),
678
+ '_wcpdf_invoice_date_formatted' => __( 'Invoice Date', 'woocommerce-pdf-invoices-packing-slips' ),
679
+ );
680
+ return $meta_to_export + $private_address_meta;
681
+ }
682
+
683
+ /**
684
+ * Set the default PHPMailer validator to 'php' ( which uses filter_var($address, FILTER_VALIDATE_EMAIL) )
685
+ * This avoids issues with the presence of attachments affecting email address validation in some distros of PHP 7.3
686
+ * See: https://wordpress.org/support/topic/invalid-address-setfrom/#post-11583815
687
+ */
688
+ public function set_phpmailer_validator( $mailArray ) {
689
+ if ( version_compare( PHP_VERSION, '7.3', '>=' ) ) {
690
+ global $phpmailer;
691
+ if ( ! ( $phpmailer instanceof \PHPMailer ) ) {
692
+ require_once ABSPATH . WPINC . '/class-phpmailer.php';
693
+ require_once ABSPATH . WPINC . '/class-smtp.php';
694
+ $phpmailer = new \PHPMailer( true );
695
+ }
696
+ $phpmailer::$validator = 'php';
697
+ }
698
+
699
+ return $mailArray;
700
+ }
701
+
702
+ /**
703
+ * Enable PHP error output
704
+ */
705
+ public function enable_debug () {
706
+ error_reporting( E_ALL );
707
+ ini_set( 'display_errors', 1 );
708
+ }
709
+
710
+ }
711
+
712
+ endif; // class_exists
713
+
714
+ return new Main();
includes/compatibility/class-wcpdf-compatibility-third-party-plugins.php CHANGED
@@ -1,222 +1,225 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Compatibility;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
- use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
-
9
- defined( 'ABSPATH' ) or exit;
10
-
11
- if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices\\Compatibility\\Third_Party_Plugins' ) ) :
12
-
13
- /**
14
- * Third party plugin compatibility class.
15
- *
16
- * @since 2.0
17
- */
18
- class Third_Party_Plugins {
19
- function __construct() {
20
- // WooCommerce Subscriptions compatibility
21
- if ( class_exists('WC_Subscriptions') ) {
22
- if ( version_compare( \WC_Subscriptions::$version, '2.0', '<' ) ) {
23
- add_action( 'woocommerce_subscriptions_renewal_order_created', array( $this, 'woocommerce_subscriptions_renewal_order_created' ), 10, 4 );
24
- } else {
25
- add_action( 'wcs_renewal_order_created', array( $this, 'wcs_renewal_order_created' ), 10, 2 );
26
- }
27
- }
28
-
29
- // WooCommerce Product Bundles compatibility (add row classes)
30
- if ( class_exists('WC_Bundles') ) {
31
- add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_product_bundles_classes' ), 10, 4 );
32
- }
33
-
34
- // WooCommerce Chained Products compatibility (add row classes)
35
- if ( class_exists('SA_WC_Chained_Products') || class_exists('WC_Chained_Products') ) {
36
- add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_chained_product_class' ), 10, 4 );
37
- }
38
-
39
- // WooCommerce Composite Products compatibility (add row classes)
40
- if ( class_exists('WC_Composite_Products') ) {
41
- add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_composite_product_class' ), 10, 4 );
42
- }
43
-
44
- // WooCommerce Order Status & Actions Manager emails compatibility
45
- if (class_exists('WC_Custom_Status')) {
46
- add_filter( 'wpo_wcpdf_wc_emails', array( $this, 'wc_order_status_actions_emails' ), 10, 1 );
47
- }
48
-
49
- // Aelia Currency Switcher compatibility
50
- $currency_switcher_active = !empty($GLOBALS['woocommerce-aelia-currencyswitcher']);
51
- if ( $currency_switcher_active ) {
52
- add_action( 'wpo_wcpdf_before_html', array( $this, 'aelia_currency_formatting' ), 10, 2 );
53
- }
54
-
55
-
56
- }
57
-
58
- /**
59
- * Reset invoice data for WooCommerce subscription renewal orders
60
- * https://wordpress.org/support/topic/subscription-renewal-duplicate-invoice-number?replies=6#post-6138110
61
- */
62
- public function woocommerce_subscriptions_renewal_order_created ( $renewal_order, $original_order, $product_id, $new_order_role ) {
63
- $this->reset_invoice_data( $renewal_order );
64
- return $renewal_order;
65
- }
66
-
67
- public function wcs_renewal_order_created ( $renewal_order, $subscription ) {
68
- $this->reset_invoice_data( $renewal_order );
69
- return $renewal_order;
70
- }
71
-
72
- public function reset_invoice_data ( $order ) {
73
- if ( ! is_object( $order ) ) {
74
- $order = wc_get_order( $order );
75
- }
76
- // delete invoice number, invoice date & invoice exists meta
77
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number' );
78
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number_data' );
79
- WCX_Order::delete_meta_data( $order, '_wcpdf_formatted_invoice_number' );
80
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_date' );
81
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_exists' );
82
- }
83
-
84
- /**
85
- * WooCommerce Product Bundles
86
- * @param string $classes CSS classes for item row (tr)
87
- * @param string $document_type PDF Document type
88
- * @param object $order WC_Order order
89
- * @param int $item_id WooCommerce Item ID
90
- */
91
- public function add_product_bundles_classes ( $classes, $document_type, $order, $item_id = '' ) {
92
- $item_id = !empty($item_id) ? $item_id : $this->get_item_id_from_classes( $classes );
93
- if ( empty($item_id) ) {
94
- return $classes;
95
- }
96
-
97
- if ( $bundled_by = WCX_Order::get_item_meta( $order, $item_id, '_bundled_by', true ) ) {
98
- $classes = $classes . ' bundled-item';
99
-
100
- // check bundled item visibility
101
- if ( $hidden = WCX_Order::get_item_meta( $order, $item_id, '_bundled_item_hidden', true ) ) {
102
- $classes = $classes . ' hidden';
103
- }
104
-
105
- return $classes;
106
- } elseif ( $bundled_items = WCX_Order::get_item_meta( $order, $item_id, '_bundled_items', true ) ) {
107
- return $classes . ' product-bundle';
108
- }
109
-
110
- return $classes;
111
- }
112
-
113
- /**
114
- * WooCommerce Chanined Products
115
- * @param string $classes CSS classes for item row (tr)
116
- * @param string $document_type PDF Document type
117
- * @param object $order WC_Order order
118
- * @param int $item_id WooCommerce Item ID
119
- */
120
- public function add_chained_product_class ( $classes, $document_type, $order, $item_id = '' ) {
121
- $item_id = !empty($item_id) ? $item_id : $this->get_item_id_from_classes( $classes );
122
- if ( empty($item_id) ) {
123
- return $classes;
124
- }
125
-
126
- if ( $chained_product_of = WCX_Order::get_item_meta( $order, $item_id, '_chained_product_of', true ) ) {
127
- return $classes . ' chained-product';
128
- }
129
-
130
- return $classes;
131
- }
132
-
133
- /**
134
- * WooCommerce Composite Products
135
- * @param string $classes CSS classes for item row (tr)
136
- * @param string $document_type PDF Document type
137
- * @param object $order WC_Order order
138
- * @param int $item_id WooCommerce Item ID
139
- */
140
- public function add_composite_product_class ( $classes, $document_type, $order, $item_id = '' ) {
141
- if ( !function_exists('wc_cp_is_composited_order_item') || !function_exists('wc_cp_is_composite_container_order_item') ) {
142
- return $classes;
143
- }
144
- $item_id = !empty($item_id) ? $item_id : $this->get_item_id_from_classes( $classes );
145
- if ( empty($item_id) ) {
146
- return $classes;
147
- }
148
-
149
- // get order item object
150
- $order_items = $order->get_items();
151
- foreach ($order_items as $order_item_id => $order_item) {
152
- if ($order_item_id == $item_id) {
153
- if ( wc_cp_is_composited_order_item( $order_item, $order ) ) {
154
- $classes .= ' component_table_item';
155
- } elseif ( wc_cp_is_composite_container_order_item( $order_item ) ) {
156
- $classes .= ' component_container_table_item';
157
- }
158
- break;
159
- }
160
- }
161
-
162
- return $classes;
163
- }
164
-
165
- /**
166
- * Backwards compatibility helper function: try to get item ID from row class
167
- * @param string $classes CSS classes for item row (tr)
168
- */
169
- public function get_item_id_from_classes ( $classes ) {
170
- $class_array = explode(' ', $classes);
171
- foreach ($class_array as $class) {
172
- if (is_numeric($class)) {
173
- $item_id = $class;
174
- break;
175
- }
176
- }
177
-
178
- // if still empty, we lost the item id somewhere :(
179
- if (empty($item_id)) {
180
- return false;
181
- } else {
182
- return $item_id;
183
- }
184
- }
185
-
186
- /**
187
- * WooCommerce Order Status & Actions Manager emails compatibility
188
- */
189
- public function wc_order_status_actions_emails ( $emails ) {
190
- // get list of custom statuses from WooCommerce Custom Order Status & Actions
191
- // status slug => status name
192
- $custom_statuses = \WC_Custom_Status::get_status_list_names();
193
- // append _email to slug (=email_id) and add to emails list
194
- foreach ($custom_statuses as $status_slug => $status_name) {
195
- $emails[$status_slug.'_email'] = $status_name;
196
- }
197
- return $emails;
198
- }
199
-
200
-
201
- /**
202
- * Aelia Currency Switcher compatibility
203
- * Applies decimal & Thousand separator settings
204
- */
205
- function aelia_currency_formatting( $document_type, $document ) {
206
- add_filter( 'wc_price_args', array( $this, 'aelia_currency_price_args' ), 10, 1 );
207
- }
208
-
209
- function aelia_currency_price_args( $args ) {
210
- if ( !empty( $args['currency'] ) && class_exists("\\Aelia\\WC\\CurrencySwitcher\\WC_Aelia_CurrencySwitcher") ) {
211
- $cs_settings = \Aelia\WC\CurrencySwitcher\WC_Aelia_CurrencySwitcher::settings();
212
- $args['decimal_separator'] = $cs_settings->get_currency_decimal_separator( $args['currency'] );
213
- $args['thousand_separator'] = $cs_settings->get_currency_thousand_separator( $args['currency'] );
214
- }
215
- return $args;
216
- }
217
- }
218
-
219
-
220
- endif; // Class exists check
221
-
222
- return new Third_Party_Plugins();
 
 
 
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Compatibility;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+ use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
+
9
+ defined( 'ABSPATH' ) or exit;
10
+
11
+ if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices\\Compatibility\\Third_Party_Plugins' ) ) :
12
+
13
+ /**
14
+ * Third party plugin compatibility class.
15
+ *
16
+ * @since 2.0
17
+ */
18
+ class Third_Party_Plugins {
19
+ function __construct() {
20
+ // WooCommerce Subscriptions compatibility
21
+ if ( class_exists('WC_Subscriptions') ) {
22
+ if ( version_compare( \WC_Subscriptions::$version, '2.0', '<' ) ) {
23
+ add_action( 'woocommerce_subscriptions_renewal_order_created', array( $this, 'woocommerce_subscriptions_renewal_order_created' ), 10, 4 );
24
+ } else {
25
+ add_action( 'wcs_renewal_order_created', array( $this, 'wcs_renewal_order_created' ), 10, 2 );
26
+ }
27
+ }
28
+
29
+ // WooCommerce Product Bundles compatibility (add row classes)
30
+ add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_product_bundles_classes' ), 10, 4 );
31
+
32
+ // WooCommerce Chained Products compatibility (add row classes)
33
+ add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_chained_product_class' ), 10, 4 );
34
+
35
+ // WooCommerce Composite Products compatibility (add row classes)
36
+ add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_composite_product_class' ), 10, 4 );
37
+
38
+ // WooCommerce Order Status & Actions Manager emails compatibility
39
+ if (class_exists('WC_Custom_Status')) {
40
+ add_filter( 'wpo_wcpdf_wc_emails', array( $this, 'wc_order_status_actions_emails' ), 10, 1 );
41
+ }
42
+
43
+ // Aelia Currency Switcher compatibility
44
+ $currency_switcher_active = !empty($GLOBALS['woocommerce-aelia-currencyswitcher']);
45
+ if ( $currency_switcher_active ) {
46
+ add_action( 'wpo_wcpdf_before_html', array( $this, 'aelia_currency_formatting' ), 10, 2 );
47
+ }
48
+
49
+
50
+ }
51
+
52
+ /**
53
+ * Reset invoice data for WooCommerce subscription renewal orders
54
+ * https://wordpress.org/support/topic/subscription-renewal-duplicate-invoice-number?replies=6#post-6138110
55
+ */
56
+ public function woocommerce_subscriptions_renewal_order_created ( $renewal_order, $original_order, $product_id, $new_order_role ) {
57
+ $this->reset_invoice_data( $renewal_order );
58
+ return $renewal_order;
59
+ }
60
+
61
+ public function wcs_renewal_order_created ( $renewal_order, $subscription ) {
62
+ $this->reset_invoice_data( $renewal_order );
63
+ return $renewal_order;
64
+ }
65
+
66
+ public function reset_invoice_data ( $order ) {
67
+ if ( ! is_object( $order ) ) {
68
+ $order = wc_get_order( $order );
69
+ }
70
+ // delete invoice number, invoice date & invoice exists meta
71
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number' );
72
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number_data' );
73
+ WCX_Order::delete_meta_data( $order, '_wcpdf_formatted_invoice_number' );
74
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_date' );
75
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_exists' );
76
+ }
77
+
78
+ /**
79
+ * WooCommerce Product Bundles
80
+ * @param string $classes CSS classes for item row (tr)
81
+ * @param string $document_type PDF Document type
82
+ * @param object $order WC_Order order
83
+ * @param int $item_id WooCommerce Item ID
84
+ */
85
+ public function add_product_bundles_classes ( $classes, $document_type, $order, $item_id = '' ) {
86
+ if ( !class_exists('WC_Bundles') ) {
87
+ return $classes;
88
+ }
89
+
90
+ $item_id = !empty($item_id) ? $item_id : $this->get_item_id_from_classes( $classes );
91
+ if ( empty($item_id) ) {
92
+ return $classes;
93
+ }
94
+
95
+ if ( $bundled_by = WCX_Order::get_item_meta( $order, $item_id, '_bundled_by', true ) ) {
96
+ $classes = $classes . ' bundled-item';
97
+
98
+ // check bundled item visibility
99
+ if ( $hidden = WCX_Order::get_item_meta( $order, $item_id, '_bundled_item_hidden', true ) ) {
100
+ $classes = $classes . ' hidden';
101
+ }
102
+
103
+ return $classes;
104
+ } elseif ( $bundled_items = WCX_Order::get_item_meta( $order, $item_id, '_bundled_items', true ) ) {
105
+ return $classes . ' product-bundle';
106
+ }
107
+
108
+ return $classes;
109
+ }
110
+
111
+ /**
112
+ * WooCommerce Chanined Products
113
+ * @param string $classes CSS classes for item row (tr)
114
+ * @param string $document_type PDF Document type
115
+ * @param object $order WC_Order order
116
+ * @param int $item_id WooCommerce Item ID
117
+ */
118
+ public function add_chained_product_class ( $classes, $document_type, $order, $item_id = '' ) {
119
+ if ( !class_exists('SA_WC_Chained_Products') && !class_exists('WC_Chained_Products') ) {
120
+ return $classes;
121
+ }
122
+
123
+ $item_id = !empty($item_id) ? $item_id : $this->get_item_id_from_classes( $classes );
124
+ if ( empty($item_id) ) {
125
+ return $classes;
126
+ }
127
+
128
+ if ( $chained_product_of = WCX_Order::get_item_meta( $order, $item_id, '_chained_product_of', true ) ) {
129
+ return $classes . ' chained-product';
130
+ }
131
+
132
+ return $classes;
133
+ }
134
+
135
+ /**
136
+ * WooCommerce Composite Products
137
+ * @param string $classes CSS classes for item row (tr)
138
+ * @param string $document_type PDF Document type
139
+ * @param object $order WC_Order order
140
+ * @param int $item_id WooCommerce Item ID
141
+ */
142
+ public function add_composite_product_class ( $classes, $document_type, $order, $item_id = '' ) {
143
+ if ( !function_exists('wc_cp_is_composited_order_item') || !function_exists('wc_cp_is_composite_container_order_item') ) {
144
+ return $classes;
145
+ }
146
+
147
+ $item_id = !empty($item_id) ? $item_id : $this->get_item_id_from_classes( $classes );
148
+ if ( empty($item_id) ) {
149
+ return $classes;
150
+ }
151
+
152
+ // get order item object
153
+ $order_items = $order->get_items();
154
+ foreach ($order_items as $order_item_id => $order_item) {
155
+ if ($order_item_id == $item_id) {
156
+ if ( wc_cp_is_composited_order_item( $order_item, $order ) ) {
157
+ $classes .= ' component_table_item';
158
+ } elseif ( wc_cp_is_composite_container_order_item( $order_item ) ) {
159
+ $classes .= ' component_container_table_item';
160
+ }
161
+ break;
162
+ }
163
+ }
164
+
165
+ return $classes;
166
+ }
167
+
168
+ /**
169
+ * Backwards compatibility helper function: try to get item ID from row class
170
+ * @param string $classes CSS classes for item row (tr)
171
+ */
172
+ public function get_item_id_from_classes ( $classes ) {
173
+ $class_array = explode(' ', $classes);
174
+ foreach ($class_array as $class) {
175
+ if (is_numeric($class)) {
176
+ $item_id = $class;
177
+ break;
178
+ }
179
+ }
180
+
181
+ // if still empty, we lost the item id somewhere :(
182
+ if (empty($item_id)) {
183
+ return false;
184
+ } else {
185
+ return $item_id;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * WooCommerce Order Status & Actions Manager emails compatibility
191
+ */
192
+ public function wc_order_status_actions_emails ( $emails ) {
193
+ // get list of custom statuses from WooCommerce Custom Order Status & Actions
194
+ // status slug => status name
195
+ $custom_statuses = \WC_Custom_Status::get_status_list_names();
196
+ // append _email to slug (=email_id) and add to emails list
197
+ foreach ($custom_statuses as $status_slug => $status_name) {
198
+ $emails[$status_slug.'_email'] = $status_name;
199
+ }
200
+ return $emails;
201
+ }
202
+
203
+
204
+ /**
205
+ * Aelia Currency Switcher compatibility
206
+ * Applies decimal & Thousand separator settings
207
+ */
208
+ function aelia_currency_formatting( $document_type, $document ) {
209
+ add_filter( 'wc_price_args', array( $this, 'aelia_currency_price_args' ), 10, 1 );
210
+ }
211
+
212
+ function aelia_currency_price_args( $args ) {
213
+ if ( !empty( $args['currency'] ) && class_exists("\\Aelia\\WC\\CurrencySwitcher\\WC_Aelia_CurrencySwitcher") ) {
214
+ $cs_settings = \Aelia\WC\CurrencySwitcher\WC_Aelia_CurrencySwitcher::settings();
215
+ $args['decimal_separator'] = $cs_settings->get_currency_decimal_separator( $args['currency'] );
216
+ $args['thousand_separator'] = $cs_settings->get_currency_thousand_separator( $args['currency'] );
217
+ }
218
+ return $args;
219
+ }
220
+ }
221
+
222
+
223
+ endif; // Class exists check
224
+
225
+ return new Third_Party_Plugins();
includes/documents/abstract-wcpdf-order-document-methods.php CHANGED
@@ -1,1129 +1,1154 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Documents;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document_Methods' ) ) :
13
-
14
- /**
15
- * Abstract Order Methods
16
- *
17
- * Collection of methods to be used on orders within a Document
18
- * Created as abstract rather than traits to support PHP versions older than 5.4
19
- *
20
- * @class \WPO\WC\PDF_Invoices\Documents\Order_Document_Methods
21
- * @version 2.0
22
- * @category Class
23
- * @author Ewout Fernhout
24
- */
25
-
26
- abstract class Order_Document_Methods extends Order_Document {
27
- public function is_refund( $order ) {
28
- if ( method_exists( $order, 'get_type') ) { // WC 3.0+
29
- $is_refund = $order->get_type() == 'shop_order_refund';
30
- } else {
31
- $is_refund = get_post_type( WCX_Order::get_id( $order ) ) == 'shop_order_refund';
32
- }
33
-
34
- return $is_refund;
35
- }
36
-
37
- public function get_refund_parent_id( $order ) {
38
- if ( method_exists( $order, 'get_parent_id') ) { // WC3.0+
39
- $parent_order_id = $order->get_parent_id();
40
- } else {
41
- $parent_order_id = wp_get_post_parent_id( WCX_Order::get_id( $order ) );
42
- }
43
-
44
- return $parent_order_id;
45
- }
46
-
47
-
48
- public function get_refund_parent( $order ) {
49
- // only try if this is actually a refund
50
- if ( ! $this->is_refund( $order ) ) {
51
- return $order;
52
- }
53
-
54
- $parent_order_id = $this->get_refund_parent_id( $order );
55
- $order = WCX::get_order( $parent_order_id );
56
- return $order;
57
- }
58
-
59
- /**
60
- * Check if billing address and shipping address are equal
61
- */
62
- public function ships_to_different_address() {
63
- // always prefer parent address for refunds
64
- if ( $this->is_refund( $this->order ) ) {
65
- $order = $this->get_refund_parent( $this->order );
66
- } else {
67
- $order = $this->order;
68
- }
69
-
70
- // only check if there is a shipping address at all
71
- if ( $formatted_shipping_address = $order->get_formatted_shipping_address() ) {
72
- $address_comparison_fields = apply_filters( 'wpo_wcpdf_address_comparison_fields', array(
73
- 'first_name',
74
- 'last_name',
75
- 'company',
76
- 'address_1',
77
- 'address_2',
78
- 'city',
79
- 'state',
80
- 'postcode',
81
- 'country'
82
- ), $this );
83
-
84
- foreach ($address_comparison_fields as $address_field) {
85
- $billing_field = WCX_Order::get_prop( $order, "billing_{$address_field}", 'view');
86
- $shipping_field = WCX_Order::get_prop( $order, "shipping_{$address_field}", 'view');
87
- if ( $shipping_field != $billing_field ) {
88
- // this address field is different -> ships to different address!
89
- return true;
90
- }
91
- }
92
- }
93
-
94
- //if we got here, it means the addresses are equal -> doesn't ship to different address!
95
- return apply_filters( 'wpo_wcpdf_ships_to_different_address', false, $order, $this );
96
- }
97
-
98
- /**
99
- * Return/Show billing address
100
- */
101
- public function get_billing_address() {
102
- // always prefer parent billing address for refunds
103
- if ( $this->is_refund( $this->order ) ) {
104
- // temporarily switch order to make all filters / order calls work correctly
105
- $refund = $this->order;
106
- $this->order = $this->get_refund_parent( $this->order );
107
- $address = apply_filters( 'wpo_wcpdf_billing_address', $this->order->get_formatted_billing_address(), $this );
108
- // switch back & unset
109
- $this->order = $refund;
110
- unset($refund);
111
- } elseif ( $address = $this->order->get_formatted_billing_address() ) {
112
- // regular shop_order
113
- $address = apply_filters( 'wpo_wcpdf_billing_address', $address, $this );
114
- } else {
115
- // no address
116
- $address = apply_filters( 'wpo_wcpdf_billing_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
117
- }
118
-
119
- return $address;
120
- }
121
- public function billing_address() {
122
- echo $this->get_billing_address();
123
- }
124
-
125
- /**
126
- * Return/Show billing email
127
- */
128
- public function get_billing_email() {
129
- $billing_email = WCX_Order::get_prop( $this->order, 'billing_email', 'view' );
130
-
131
- if ( !$billing_email && $this->is_refund( $this->order ) ) {
132
- // try parent
133
- $parent_order = $this->get_refund_parent( $this->order );
134
- $billing_email = WCX_Order::get_prop( $parent_order, 'billing_email', 'view' );
135
- }
136
-
137
- return apply_filters( 'wpo_wcpdf_billing_email', $billing_email, $this );
138
- }
139
- public function billing_email() {
140
- echo $this->get_billing_email();
141
- }
142
-
143
- /**
144
- * Return/Show billing phone
145
- */
146
- public function get_billing_phone() {
147
- $billing_phone = WCX_Order::get_prop( $this->order, 'billing_phone', 'view' );
148
-
149
- if ( !$billing_phone && $this->is_refund( $this->order ) ) {
150
- // try parent
151
- $parent_order = $this->get_refund_parent( $this->order );
152
- $billing_phone = WCX_Order::get_prop( $parent_order, 'billing_phone', 'view' );
153
- }
154
-
155
- return apply_filters( 'wpo_wcpdf_billing_phone', $billing_phone, $this );
156
- }
157
- public function billing_phone() {
158
- echo $this->get_billing_phone();
159
- }
160
-
161
- /**
162
- * Return/Show shipping address
163
- */
164
- public function get_shipping_address() {
165
- // always prefer parent shipping address for refunds
166
- if ( $this->is_refund( $this->order ) ) {
167
- // temporarily switch order to make all filters / order calls work correctly
168
- $refund = $this->order;
169
- $this->order = $this->get_refund_parent( $this->order );
170
- $address = apply_filters( 'wpo_wcpdf_shipping_address', $this->order->get_formatted_shipping_address(), $this );
171
- // switch back & unset
172
- $this->order = $refund;
173
- unset($refund);
174
- } elseif ( $address = $this->order->get_formatted_shipping_address() ) {
175
- // regular shop_order
176
- $address = apply_filters( 'wpo_wcpdf_shipping_address', $address, $this );
177
- } else {
178
- // no address
179
- // use fallback for packing slip
180
- if ( apply_filters( 'wpo_wcpdf_shipping_address_fallback', ( $this->get_type() == 'packing-slip' ), $this ) ) {
181
- $address = $this->get_billing_address();
182
- } else{
183
- $address = apply_filters( 'wpo_wcpdf_shipping_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
184
-
185
- }
186
- }
187
-
188
- return $address;
189
- }
190
- public function shipping_address() {
191
- echo $this->get_shipping_address();
192
- }
193
-
194
- /**
195
- * Return/Show a custom field
196
- */
197
- public function get_custom_field( $field_name ) {
198
- if ( !$this->is_order_prop( $field_name ) ) {
199
- $custom_field = WCX_Order::get_meta( $this->order, $field_name, true );
200
- }
201
- // if not found, try prefixed with underscore (not when ACF is active!)
202
- if ( empty( $custom_field ) && substr( $field_name, 0, 1 ) !== '_' && !$this->is_order_prop( "_{$field_name}" ) && !class_exists('ACF') ) {
203
- $custom_field = WCX_Order::get_meta( $this->order, "_{$field_name}", true );
204
- }
205
-
206
- // WC3.0 fallback to properties
207
- $property = str_replace('-', '_', sanitize_title( ltrim($field_name, '_') ) );
208
- if ( empty( $custom_field ) && is_callable( array( $this->order, "get_{$property}" ) ) ) {
209
- $custom_field = $this->order->{"get_{$property}"}( 'view' );
210
- }
211
-
212
- // fallback to parent for refunds
213
- if ( empty( $custom_field ) && $this->is_refund( $this->order ) ) {
214
- $parent_order = $this->get_refund_parent( $this->order );
215
- if ( !$this->is_order_prop( $field_name ) ) {
216
- $custom_field = WCX_Order::get_meta( $parent_order, $field_name, true );
217
- }
218
-
219
- // WC3.0 fallback to properties
220
- if ( empty( $custom_field ) && is_callable( array( $parent_order, "get_{$property}" ) ) ) {
221
- $custom_field = $parent_order->{"get_{$property}"}( 'view' );
222
- }
223
- }
224
-
225
- return apply_filters( 'wpo_wcpdf_billing_custom_field', $custom_field, $this );
226
- }
227
- public function custom_field( $field_name, $field_label = '', $display_empty = false ) {
228
- $custom_field = $this->get_custom_field( $field_name );
229
- if (!empty($field_label)){
230
- // add a a trailing space to the label
231
- $field_label .= ' ';
232
- }
233
-
234
- if (!empty($custom_field) || $display_empty) {
235
- echo $field_label . nl2br ($custom_field);
236
- }
237
- }
238
-
239
- public function is_order_prop( $key ) {
240
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '<' ) ) {
241
- return false; // WC 2.X didn't have CRUD
242
- }
243
- // Taken from WC class
244
- $order_props = array(
245
- // Abstract order props
246
- 'parent_id',
247
- 'status',
248
- 'currency',
249
- 'version',
250
- 'prices_include_tax',
251
- 'date_created',
252
- 'date_modified',
253
- 'discount_total',
254
- 'discount_tax',
255
- 'shipping_total',
256
- 'shipping_tax',
257
- 'cart_tax',
258
- 'total',
259
- 'total_tax',
260
- // Order props
261
- 'customer_id',
262
- 'order_key',
263
- 'billing_first_name',
264
- 'billing_last_name',
265
- 'billing_company',
266
- 'billing_address_1',
267
- 'billing_address_2',
268
- 'billing_city',
269
- 'billing_state',
270
- 'billing_postcode',
271
- 'billing_country',
272
- 'billing_email',
273
- 'billing_phone',
274
- 'shipping_first_name',
275
- 'shipping_last_name',
276
- 'shipping_company',
277
- 'shipping_address_1',
278
- 'shipping_address_2',
279
- 'shipping_city',
280
- 'shipping_state',
281
- 'shipping_postcode',
282
- 'shipping_country',
283
- 'payment_method',
284
- 'payment_method_title',
285
- 'transaction_id',
286
- 'customer_ip_address',
287
- 'customer_user_agent',
288
- 'created_via',
289
- 'customer_note',
290
- 'date_completed',
291
- 'date_paid',
292
- 'cart_hash',
293
- );
294
- return in_array($key, $order_props);
295
- }
296
-
297
- /**
298
- * Return/show product attribute
299
- */
300
- public function get_product_attribute( $attribute_name, $product ) {
301
- // first, check the text attributes
302
- $attributes = $product->get_attributes();
303
- $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
304
- if (array_key_exists( sanitize_title( $attribute_name ), $attributes) ) {
305
- $attribute = $product->get_attribute ( $attribute_name );
306
- } elseif (array_key_exists( sanitize_title( $attribute_key ), $attributes) ) {
307
- $attribute = $product->get_attribute ( $attribute_key );
308
- }
309
-
310
- if (empty($attribute)) {
311
- // not a text attribute, try attribute taxonomy
312
- $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
313
- $product_id = WCX_Product::get_prop($product, 'id');
314
- $product_terms = @wc_get_product_terms( $product_id, $attribute_key, array( 'fields' => 'names' ) );
315
- // check if not empty, then display
316
- if ( !empty($product_terms) ) {
317
- $attribute = array_shift( $product_terms );
318
- }
319
- }
320
-
321
- // WC3.0+ fallback parent product for variations
322
- if ( empty($attribute) && version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) && $product->is_type( 'variation' ) ) {
323
- $product = wc_get_product( $product->get_parent_id() );
324
- $attribute = $this->get_product_attribute( $attribute_name, $product );
325
- }
326
-
327
- return isset($attribute) ? $attribute : false;
328
- }
329
- public function product_attribute( $attribute_name, $product ) {
330
- echo $this->get_product_attribute( $attribute_name, $product );
331
- }
332
-
333
- /**
334
- * Return/Show order notes
335
- * could use $order->get_customer_order_notes(), but that filters out private notes already
336
- */
337
- public function get_order_notes( $filter = 'customer' ) {
338
- if ( $this->is_refund( $this->order ) ) {
339
- $post_id = $this->get_refund_parent_id( $this->order );
340
- } else {
341
- $post_id = $this->order_id;
342
- }
343
-
344
- if ( empty( $post_id ) ) {
345
- return; // prevent order notes from all orders showing when document is not loaded properly
346
- }
347
-
348
- $args = array(
349
- 'post_id' => $post_id,
350
- 'approve' => 'approve',
351
- 'type' => 'order_note'
352
- );
353
-
354
- remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
355
-
356
- $notes = get_comments( $args );
357
-
358
- add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
359
-
360
- if ( $notes ) {
361
- foreach( $notes as $key => $note ) {
362
- if ( $filter == 'customer' && !get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
363
- unset($notes[$key]);
364
- }
365
- if ( $filter == 'private' && get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
366
- unset($notes[$key]);
367
- }
368
- }
369
- return $notes;
370
- }
371
- }
372
- public function order_notes( $filter = 'customer' ) {
373
- $notes = $this->get_order_notes( $filter );
374
- if ( $notes ) {
375
- foreach( $notes as $note ) {
376
- ?>
377
- <div class="note_content">
378
- <?php echo wpautop( wptexturize( wp_kses_post( $note->comment_content ) ) ); ?>
379
- </div>
380
- <?php
381
- }
382
- }
383
- }
384
-
385
- /**
386
- * Return/Show the current date
387
- */
388
- public function get_current_date() {
389
- return apply_filters( 'wpo_wcpdf_date', date_i18n( wc_date_format() ) );
390
- }
391
- public function current_date() {
392
- echo $this->get_current_date();
393
- }
394
-
395
- /**
396
- * Return/Show payment method
397
- */
398
- public function get_payment_method() {
399
- $payment_method_label = __( 'Payment method', 'woocommerce-pdf-invoices-packing-slips' );
400
-
401
- if ( $this->is_refund( $this->order ) ) {
402
- $parent_order = $this->get_refund_parent( $this->order );
403
- $payment_method_title = WCX_Order::get_prop( $parent_order, 'payment_method_title', 'view' );
404
- } else {
405
- $payment_method_title = WCX_Order::get_prop( $this->order, 'payment_method_title', 'view' );
406
- }
407
-
408
- $payment_method = __( $payment_method_title, 'woocommerce' );
409
-
410
- return apply_filters( 'wpo_wcpdf_payment_method', $payment_method, $this );
411
- }
412
- public function payment_method() {
413
- echo $this->get_payment_method();
414
- }
415
-
416
- /**
417
- * Return/Show shipping method
418
- */
419
- public function get_shipping_method() {
420
- $shipping_method_label = __( 'Shipping method', 'woocommerce-pdf-invoices-packing-slips' );
421
- $shipping_method = __( $this->order->get_shipping_method(), 'woocommerce' );
422
- return apply_filters( 'wpo_wcpdf_shipping_method', $shipping_method, $this );
423
- }
424
- public function shipping_method() {
425
- echo $this->get_shipping_method();
426
- }
427
-
428
- /**
429
- * Return/Show order number
430
- */
431
- public function get_order_number() {
432
- // try parent first
433
- if ( $this->is_refund( $this->order ) ) {
434
- $parent_order = $this->get_refund_parent( $this->order );
435
- $order_number = $parent_order->get_order_number();
436
- } else {
437
- $order_number = $this->order->get_order_number();
438
- }
439
-
440
- // Trim the hash to have a clean number but still
441
- // support any filters that were applied before.
442
- $order_number = ltrim($order_number, '#');
443
- return apply_filters( 'wpo_wcpdf_order_number', $order_number, $this );
444
- }
445
- public function order_number() {
446
- echo $this->get_order_number();
447
- }
448
-
449
- /**
450
- * Return/Show the order date
451
- */
452
- public function get_order_date() {
453
- if ( $this->is_refund( $this->order ) ) {
454
- $parent_order = $this->get_refund_parent( $this->order );
455
- $order_date = WCX_Order::get_prop( $parent_order, 'date_created' );
456
- } else {
457
- $order_date = WCX_Order::get_prop( $this->order, 'date_created' );
458
- }
459
-
460
- $date = $order_date->date_i18n( wc_date_format() );
461
- $mysql_date = $order_date->date( "Y-m-d H:i:s" );
462
- return apply_filters( 'wpo_wcpdf_order_date', $date, $mysql_date, $this );
463
- }
464
- public function order_date() {
465
- echo $this->get_order_date();
466
- }
467
-
468
- /**
469
- * Return the order items
470
- */
471
- public function get_order_items() {
472
- $items = $this->order->get_items();
473
- $data_list = array();
474
-
475
- if( sizeof( $items ) > 0 ) {
476
- foreach ( $items as $item_id => $item ) {
477
- // Array with data for the pdf template
478
- $data = array();
479
-
480
- // Set the item_id
481
- $data['item_id'] = $item_id;
482
-
483
- // Set the id
484
- $data['product_id'] = $item['product_id'];
485
- $data['variation_id'] = $item['variation_id'];
486
-
487
- // Compatibility: WooCommerce Composit Products uses a workaround for
488
- // setting the order before the item name filter, so we run this first
489
- if ( class_exists('WC_Composite_Products') ) {
490
- $order_item_class = apply_filters( 'woocommerce_order_item_class', '', $item, $this->order );
491
- }
492
-
493
- // Set item name
494
- $data['name'] = apply_filters( 'woocommerce_order_item_name', $item['name'], $item, false );
495
-
496
- // Set item quantity
497
- $data['quantity'] = $item['qty'];
498
-
499
- // Set the line total (=after discount)
500
- $data['line_total'] = $this->format_price( $item['line_total'] );
501
- $data['single_line_total'] = $this->format_price( $item['line_total'] / max( 1, abs( $item['qty'] ) ) );
502
- $data['line_tax'] = $this->format_price( $item['line_tax'] );
503
- $data['single_line_tax'] = $this->format_price( $item['line_tax'] / max( 1, abs( $item['qty'] ) ) );
504
-
505
- $line_tax_data = maybe_unserialize( isset( $item['line_tax_data'] ) ? $item['line_tax_data'] : '' );
506
- $data['tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, true );
507
- $data['calculated_tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, false );
508
-
509
- // Set the line subtotal (=before discount)
510
- $data['line_subtotal'] = $this->format_price( $item['line_subtotal'] );
511
- $data['line_subtotal_tax'] = $this->format_price( $item['line_subtotal_tax'] );
512
- $data['ex_price'] = $this->get_formatted_item_price( $item, 'total', 'excl' );
513
- $data['price'] = $this->get_formatted_item_price( $item, 'total' );
514
- $data['order_price'] = $this->order->get_formatted_line_subtotal( $item ); // formatted according to WC settings
515
-
516
- // Calculate the single price with the same rules as the formatted line subtotal (!)
517
- // = before discount
518
- $data['ex_single_price'] = $this->get_formatted_item_price( $item, 'single', 'excl' );
519
- $data['single_price'] = $this->get_formatted_item_price( $item, 'single' );
520
-
521
- // Pass complete item array
522
- $data['item'] = $item;
523
-
524
- // Get the product to add more info
525
- $product = $this->order->get_product_from_item( $item );
526
-
527
- // Checking fo existance, thanks to MDesigner0
528
- if( !empty( $product ) ) {
529
- // Thumbnail (full img tag)
530
- $data['thumbnail'] = $this->get_thumbnail( $product );
531
-
532
- // Set item SKU
533
- $data['sku'] = $product->get_sku();
534
-
535
- // Set item weight
536
- $data['weight'] = $product->get_weight();
537
-
538
- // Set item dimensions
539
- $data['dimensions'] = WCX_Product::get_dimensions( $product );
540
-
541
- // Pass complete product object
542
- $data['product'] = $product;
543
-
544
- } else {
545
- $data['product'] = null;
546
- }
547
-
548
- // Set item meta
549
- if (function_exists('wc_display_item_meta')) { // WC3.0+
550
- $data['meta'] = wc_display_item_meta( $item, array(
551
- 'echo' => false,
552
- ) );
553
- } else {
554
- if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '<' ) ) {
555
- $meta = new \WC_Order_Item_Meta( $item['item_meta'], $product );
556
- } else { // pass complete item for WC2.4+
557
- $meta = new \WC_Order_Item_Meta( $item, $product );
558
- }
559
- $data['meta'] = $meta->display( false, true );
560
- }
561
-
562
- $data_list[$item_id] = apply_filters( 'wpo_wcpdf_order_item_data', $data, $this->order, $this->get_type() );
563
- }
564
- }
565
-
566
- return apply_filters( 'wpo_wcpdf_order_items_data', $data_list, $this->order, $this->get_type() );
567
- }
568
-
569
- /**
570
- * Get the tax rates/percentages for a given tax class
571
- * @param string $tax_class tax class slug
572
- * @return string $tax_rates imploded list of tax rates
573
- */
574
- public function get_tax_rate( $tax_class, $line_total, $line_tax, $line_tax_data = '', $force_calculation = false ) {
575
- // first try the easy wc2.2+ way, using line_tax_data
576
- if ( !empty( $line_tax_data ) && isset($line_tax_data['total']) ) {
577
- $tax_rates = array();
578
-
579
- $line_taxes = $line_tax_data['subtotal'];
580
- foreach ( $line_taxes as $tax_id => $tax ) {
581
- if ( isset($tax) && $tax !== '' ) {
582
- $tax_rate = $this->get_tax_rate_by_id( $tax_id );
583
- if ( $tax_rate !== false && $force_calculation !== false ) {
584
- $tax_rates[] = $tax_rate . ' %';
585
- } else {
586
- $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
587
- }
588
- }
589
- }
590
-
591
- // apply decimal setting
592
- if (function_exists('wc_get_price_decimal_separator')) {
593
- foreach ($tax_rates as &$tax_rate) {
594
- $tax_rate = str_replace('.', wc_get_price_decimal_separator(), strval($tax_rate) );
595
- }
596
- }
597
-
598
- $tax_rates = implode(' ,', $tax_rates );
599
- return $tax_rates;
600
- }
601
-
602
- if ( $line_tax == 0 ) {
603
- return '-'; // no need to determine tax rate...
604
- }
605
-
606
- if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 && !apply_filters( 'wpo_wcpdf_calculate_tax_rate', false ) ) {
607
- // WC 2.1 or newer is used
608
- $tax = new \WC_Tax();
609
- $taxes = $tax->get_rates( $tax_class );
610
-
611
- $tax_rates = array();
612
-
613
- foreach ($taxes as $tax) {
614
- $tax_rates[$tax['label']] = round( $tax['rate'], 2 ).' %';
615
- }
616
-
617
- if (empty($tax_rates)) {
618
- // one last try: manually calculate
619
- $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
620
- }
621
-
622
- $tax_rates = implode(' ,', $tax_rates );
623
- } else {
624
- // Backwards compatibility/fallback: calculate tax from line items
625
- $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
626
- }
627
-
628
- return $tax_rates;
629
- }
630
-
631
- public function calculate_tax_rate( $price_ex_tax, $tax ) {
632
- $precision = apply_filters( 'wpo_wcpdf_calculate_tax_rate_precision', 1 );
633
- if ( $price_ex_tax != 0) {
634
- $tax_rate = round( ($tax / $price_ex_tax)*100, $precision ).' %';
635
- } else {
636
- $tax_rate = '-';
637
- }
638
- return $tax_rate;
639
- }
640
-
641
- /**
642
- * Returns the percentage rate (float) for a given tax rate ID.
643
- * @param int $rate_id woocommerce tax rate id
644
- * @return float $rate percentage rate
645
- */
646
- public function get_tax_rate_by_id( $rate_id ) {
647
- global $wpdb;
648
- $rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $rate_id ) );
649
- if ($rate === NULL) {
650
- return false;
651
- } else {
652
- return (float) $rate;
653
- }
654
- }
655
-
656
- /**
657
- * Returns a an array with rate_id => tax rate data (array) of all tax rates in woocommerce
658
- * @return array $tax_rate_ids keyed by id
659
- */
660
- public function get_tax_rate_ids() {
661
- global $wpdb;
662
- $rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates" );
663
-
664
- $tax_rate_ids = array();
665
- foreach ($rates as $rate) {
666
- $rate_id = $rate->tax_rate_id;
667
- unset($rate->tax_rate_id);
668
- $tax_rate_ids[$rate_id] = (array) $rate;
669
- }
670
-
671
- return $tax_rate_ids;
672
- }
673
-
674
- /**
675
- * Returns the main product image ID
676
- * Adapted from the WC_Product class
677
- * (does not support thumbnail sizes)
678
- *
679
- * @access public
680
- * @return string
681
- */
682
- public function get_thumbnail_id ( $product ) {
683
- global $woocommerce;
684
-
685
- $product_id = WCX_Product::get_id( $product );
686
-
687
- if ( has_post_thumbnail( $product_id ) ) {
688
- $thumbnail_id = get_post_thumbnail_id ( $product_id );
689
- } elseif ( ( $parent_id = wp_get_post_parent_id( $product_id ) ) && has_post_thumbnail( $parent_id ) ) {
690
- $thumbnail_id = get_post_thumbnail_id ( $parent_id );
691
- } else {
692
- $thumbnail_id = false;
693
- }
694
-
695
- return $thumbnail_id;
696
- }
697
-
698
- /**
699
- * Returns the thumbnail image tag
700
- *
701
- * uses the internal WooCommerce/WP functions and extracts the image url or path
702
- * rather than the thumbnail ID, to simplify the code and make it possible to
703
- * filter for different thumbnail sizes
704
- *
705
- * @access public
706
- * @return string
707
- */
708
- public function get_thumbnail ( $product ) {
709
- // Get default WooCommerce img tag (url/http)
710
- if ( version_compare( WOOCOMMERCE_VERSION, '3.3', '>=' ) ) {
711
- $thumbnail_size = 'woocommerce_thumbnail';
712
- } else {
713
- $thumbnail_size = 'shop_thumbnail';
714
- }
715
- $size = apply_filters( 'wpo_wcpdf_thumbnail_size', $thumbnail_size );
716
- $thumbnail_img_tag_url = $product->get_image( $size, array( 'title' => '' ) );
717
-
718
- // Extract the url from img
719
- preg_match('/<img(.*)src(.*)=(.*)"(.*)"/U', $thumbnail_img_tag_url, $thumbnail_url );
720
- $thumbnail_url = array_pop($thumbnail_url);
721
- // remove http/https from image tag url to avoid mixed origin conflicts
722
- $contextless_thumbnail_url = ltrim( str_replace(array('http://','https://'), '', $thumbnail_url ), '/' );
723
-
724
- // convert url to path
725
- if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
726
- $forwardslash_basepath = str_replace('\\','/', ABSPATH);
727
- $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(get_site_url()));
728
- } else {
729
- // bedrock e.a
730
- $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
731
- $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(WP_CONTENT_URL));
732
- }
733
- $thumbnail_path = str_replace( $contextless_site_url, trailingslashit( $forwardslash_basepath ), $contextless_thumbnail_url);
734
-
735
- // fallback if thumbnail file doesn't exist
736
- if (apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path)) {
737
- if ($thumbnail_id = $this->get_thumbnail_id( $product ) ) {
738
- $thumbnail_path = get_attached_file( $thumbnail_id );
739
- }
740
- }
741
-
742
- // Thumbnail (full img tag)
743
- if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($thumbnail_path) ) {
744
- // load img with server path by default
745
- $thumbnail = sprintf('<img width="90" height="90" src="%s" class="attachment-shop_thumbnail wp-post-image">', $thumbnail_path );
746
- } elseif ( apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path) ) {
747
- // should use paths but file not found, replace // with http(s):// for dompdf compatibility
748
- if ( substr( $thumbnail_url, 0, 2 ) === "//" ) {
749
- $prefix = is_ssl() ? 'https://' : 'http://';
750
- $https_thumbnail_url = $prefix . ltrim( $thumbnail_url, '/' );
751
- $thumbnail_img_tag_url = str_replace($thumbnail_url, $https_thumbnail_url, $thumbnail_img_tag_url);
752
- }
753
- $thumbnail = $thumbnail_img_tag_url;
754
- } else {
755
- // load img with http url when filtered
756
- $thumbnail = $thumbnail_img_tag_url;
757
- }
758
-
759
- // die($thumbnail);
760
- return $thumbnail;
761
- }
762
-
763
- /**
764
- * Return the order totals listing
765
- */
766
- public function get_woocommerce_totals() {
767
- // get totals and remove the semicolon
768
- $totals = apply_filters( 'wpo_wcpdf_raw_order_totals', $this->order->get_order_item_totals(), $this->order );
769
-
770
- // remove the colon for every label
771
- foreach ( $totals as $key => $total ) {
772
- $label = $total['label'];
773
- $colon = strrpos( $label, ':' );
774
- if( $colon !== false ) {
775
- $label = substr_replace( $label, '', $colon, 1 );
776
- }
777
- $totals[$key]['label'] = $label;
778
- }
779
-
780
- // WC2.4 fix order_total for refunded orders
781
- // not if this is the actual refund!
782
- if ( ! $this->is_refund( $this->order ) ) {
783
- $total_refunded = method_exists($this->order, 'get_total_refunded') ? $this->order->get_total_refunded() : 0;
784
- if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '>=' ) && isset($totals['order_total']) && $total_refunded ) {
785
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
786
- $tax_display = get_option( 'woocommerce_tax_display_cart' );
787
- } else {
788
- $tax_display = WCX_Order::get_prop( $this->order, 'tax_display_cart' );
789
- }
790
-
791
- $totals['order_total']['value'] = wc_price( $this->order->get_total(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) );
792
- $order_total = $this->order->get_total();
793
- $tax_string = '';
794
-
795
- // Tax for inclusive prices
796
- if ( wc_tax_enabled() && 'incl' == $tax_display ) {
797
- $tax_string_array = array();
798
- if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
799
- foreach ( $this->order->get_tax_totals() as $code => $tax ) {
800
- $tax_amount = $tax->formatted_amount;
801
- $tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
802
- }
803
- } else {
804
- $tax_string_array[] = sprintf( '%s %s', wc_price( $this->order->get_total_tax() - $this->order->get_total_tax_refunded(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) ), WC()->countries->tax_or_vat() );
805
- }
806
- if ( ! empty( $tax_string_array ) ) {
807
- if ( version_compare( WOOCOMMERCE_VERSION, '2.6', '>=' ) ) {
808
- $tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
809
- } else {
810
- // use old capitalized string
811
- $tax_string = ' ' . sprintf( __( '(Includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
812
- }
813
- }
814
- }
815
-
816
- $totals['order_total']['value'] .= $tax_string;
817
- }
818
-
819
- // remove refund lines (shouldn't be in invoice)
820
- foreach ( $totals as $key => $total ) {
821
- if ( strpos($key, 'refund_') !== false ) {
822
- unset( $totals[$key] );
823
- }
824
- }
825
-
826
- }
827
-
828
- return apply_filters( 'wpo_wcpdf_woocommerce_totals', $totals, $this->order, $this->get_type() );
829
- }
830
-
831
- /**
832
- * Return/show the order subtotal
833
- */
834
- public function get_order_subtotal( $tax = 'excl', $discount = 'incl' ) { // set $tax to 'incl' to include tax, same for $discount
835
- //$compound = ($discount == 'incl')?true:false;
836
- $subtotal = $this->order->get_subtotal_to_display( false, $tax );
837
-
838
- $subtotal = ($pos = strpos($subtotal, ' <small')) ? substr($subtotal, 0, $pos) : $subtotal; //removing the 'excluding tax' text
839
-
840
- $subtotal = array (
841
- 'label' => __('Subtotal', 'woocommerce-pdf-invoices-packing-slips' ),
842
- 'value' => $subtotal,
843
- );
844
-
845
- return apply_filters( 'wpo_wcpdf_order_subtotal', $subtotal, $tax, $discount, $this );
846
- }
847
- public function order_subtotal( $tax = 'excl', $discount = 'incl' ) {
848
- $subtotal = $this->get_order_subtotal( $tax, $discount );
849
- echo $subtotal['value'];
850
- }
851
-
852
- /**
853
- * Return/show the order shipping costs
854
- */
855
- public function get_order_shipping( $tax = 'excl' ) { // set $tax to 'incl' to include tax
856
- $shipping_cost = WCX_Order::get_prop( $this->order, 'shipping_total', 'view' );
857
- $shipping_tax = WCX_Order::get_prop( $this->order, 'shipping_tax', 'view' );
858
-
859
- if ($tax == 'excl' ) {
860
- $formatted_shipping_cost = $this->format_price( $shipping_cost );
861
- } else {
862
- $formatted_shipping_cost = $this->format_price( $shipping_cost + $shipping_tax );
863
- }
864
-
865
- $shipping = array (
866
- 'label' => __('Shipping', 'woocommerce-pdf-invoices-packing-slips' ),
867
- 'value' => $formatted_shipping_cost,
868
- 'tax' => $this->format_price( $shipping_tax ),
869
- );
870
- return apply_filters( 'wpo_wcpdf_order_shipping', $shipping, $tax, $this );
871
- }
872
- public function order_shipping( $tax = 'excl' ) {
873
- $shipping = $this->get_order_shipping( $tax );
874
- echo $shipping['value'];
875
- }
876
-
877
- /**
878
- * Return/show the total discount
879
- */
880
- public function get_order_discount( $type = 'total', $tax = 'incl' ) {
881
- if ( $tax == 'incl' ) {
882
- switch ($type) {
883
- case 'cart':
884
- // Cart Discount - pre-tax discounts. (deprecated in WC2.3)
885
- $discount_value = $this->order->get_cart_discount();
886
- break;
887
- case 'order':
888
- // Order Discount - post-tax discounts. (deprecated in WC2.3)
889
- $discount_value = $this->order->get_order_discount();
890
- break;
891
- case 'total':
892
- // Total Discount
893
- if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
894
- $discount_value = $this->order->get_total_discount( false ); // $ex_tax = false
895
- } else {
896
- // WC2.2 and older: recalculate to include tax
897
- $discount_value = 0;
898
- $items = $this->order->get_items();;
899
- if( sizeof( $items ) > 0 ) {
900
- foreach( $items as $item ) {
901
- $discount_value += ($item['line_subtotal'] + $item['line_subtotal_tax']) - ($item['line_total'] + $item['line_tax']);
902
- }
903
- }
904
- }
905
-
906
- break;
907
- default:
908
- // Total Discount - Cart & Order Discounts combined
909
- $discount_value = $this->order->get_total_discount();
910
- break;
911
- }
912
- } else { // calculate discount excluding tax
913
- if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
914
- $discount_value = $this->order->get_total_discount( true ); // $ex_tax = true
915
- } else {
916
- // WC2.2 and older: recalculate to exclude tax
917
- $discount_value = 0;
918
-
919
- $items = $this->order->get_items();;
920
- if( sizeof( $items ) > 0 ) {
921
- foreach( $items as $item ) {
922
- $discount_value += ($item['line_subtotal'] - $item['line_total']);
923
- }
924
- }
925
- }
926
- }
927
-
928
- $discount = array (
929
- 'label' => __('Discount', 'woocommerce-pdf-invoices-packing-slips' ),
930
- 'value' => $this->format_price( $discount_value ),
931
- 'raw_value' => $discount_value,
932
- );
933
-
934
- if ( round( $discount_value, 3 ) != 0 ) {
935
- return apply_filters( 'wpo_wcpdf_order_discount', $discount, $type, $tax, $this );
936
- }
937
- }
938
- public function order_discount( $type = 'total', $tax = 'incl' ) {
939
- $discount = $this->get_order_discount( $type, $tax );
940
- echo $discount['value'];
941
- }
942
-
943
- /**
944
- * Return the order fees
945
- */
946
- public function get_order_fees( $tax = 'excl' ) {
947
- if ( $_fees = $this->order->get_fees() ) {
948
- foreach( $_fees as $id => $fee ) {
949
- if ($tax == 'excl' ) {
950
- $fee_price = $this->format_price( $fee['line_total'] );
951
- } else {
952
- $fee_price = $this->format_price( $fee['line_total'] + $fee['line_tax'] );
953
- }
954
-
955
- $fees[ $id ] = array(
956
- 'label' => $fee['name'],
957
- 'value' => $fee_price,
958
- 'line_total' => $this->format_price( $fee['line_total'] ),
959
- 'line_tax' => $this->format_price( $fee['line_tax'] )
960
- );
961
- }
962
- return $fees;
963
- }
964
- }
965
-
966
- /**
967
- * Return the order taxes
968
- */
969
- public function get_order_taxes() {
970
- $tax_label = __( 'VAT', 'woocommerce-pdf-invoices-packing-slips' ); // register alternate label translation
971
- $tax_label = __( 'Tax rate', 'woocommerce-pdf-invoices-packing-slips' );
972
- $tax_rate_ids = $this->get_tax_rate_ids();
973
- if ( $order_taxes = $this->order->get_taxes() ) {
974
- foreach ( $order_taxes as $key => $tax ) {
975
- if ( WCX::is_wc_version_gte_3_0() ) {
976
- $taxes[ $key ] = array(
977
- 'label' => $tax->get_label(),
978
- 'value' => $this->format_price( $tax->get_tax_total() + $tax->get_shipping_tax_total() ),
979
- 'rate_id' => $tax->get_rate_id(),
980
- 'tax_amount' => $tax->get_tax_total(),
981
- 'shipping_tax_amount' => $tax->get_shipping_tax_total(),
982
- 'rate' => isset( $tax_rate_ids[ $tax->get_rate_id() ] ) ? ( (float) $tax_rate_ids[$tax->get_rate_id()]['tax_rate'] ) . ' %': '',
983
- );
984
- } else {
985
- $taxes[ $key ] = array(
986
- 'label' => isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ],
987
- 'value' => $this->format_price( ( $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ] ) ),
988
- 'rate_id' => $tax['rate_id'],
989
- 'tax_amount' => $tax['tax_amount'],
990
- 'shipping_tax_amount' => $tax['shipping_tax_amount'],
991
- 'rate' => isset( $tax_rate_ids[ $tax['rate_id'] ] ) ? ( (float) $tax_rate_ids[$tax['rate_id']]['tax_rate'] ) . ' %': '',
992
- );
993
- }
994
-
995
- }
996
-
997
- return apply_filters( 'wpo_wcpdf_order_taxes', $taxes, $this );
998
- }
999
- }
1000
-
1001
- /**
1002
- * Return/show the order grand total
1003
- */
1004
- public function get_order_grand_total( $tax = 'incl' ) {
1005
- if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 ) {
1006
- // WC 2.1 or newer is used
1007
- $total_unformatted = $this->order->get_total();
1008
- } else {
1009
- // Backwards compatibility
1010
- $total_unformatted = $this->order->get_order_total();
1011
- }
1012
-
1013
- if ($tax == 'excl' ) {
1014
- $total = $this->format_price( $total_unformatted - $this->order->get_total_tax() );
1015
- $label = __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' );
1016
- } else {
1017
- $total = $this->format_price( ( $total_unformatted ) );
1018
- $label = __( 'Total', 'woocommerce-pdf-invoices-packing-slips' );
1019
- }
1020
-
1021
- $grand_total = array(
1022
- 'label' => $label,
1023
- 'value' => $total,
1024
- );
1025
-
1026
- return apply_filters( 'wpo_wcpdf_order_grand_total', $grand_total, $tax, $this );
1027
- }
1028
- public function order_grand_total( $tax = 'incl' ) {
1029
- $grand_total = $this->get_order_grand_total( $tax );
1030
- echo $grand_total['value'];
1031
- }
1032
-
1033
-
1034
- /**
1035
- * Return/Show shipping notes
1036
- */
1037
- public function get_shipping_notes() {
1038
- if ( $this->is_refund( $this->order ) ) {
1039
- // return reason for refund if order is a refund
1040
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
1041
- $shipping_notes = $this->order->get_reason();
1042
- } elseif ( method_exists($this->order, 'get_refund_reason') ) {
1043
- $shipping_notes = $this->order->get_refund_reason();
1044
- } else {
1045
- $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1046
- }
1047
- } else {
1048
- $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1049
- }
1050
- return apply_filters( 'wpo_wcpdf_shipping_notes', $shipping_notes, $this );
1051
- }
1052
- public function shipping_notes() {
1053
- echo $this->get_shipping_notes();
1054
- }
1055
-
1056
- /**
1057
- * wrapper for wc_price, ensuring currency is always passed
1058
- */
1059
- public function format_price( $price, $args = array() ) {
1060
- if ( function_exists( 'wc_price' ) ) { // WC 2.1+
1061
- $args['currency'] = WCX_Order::get_prop( $this->order, 'currency' );
1062
- $formatted_price = wc_price( $price, $args );
1063
- } else {
1064
- $formatted_price = woocommerce_price( $price );
1065
- }
1066
-
1067
- return $formatted_price;
1068
- }
1069
- public function wc_price( $price, $args = array() ) {
1070
- return $this->format_price( $price, $args );
1071
- }
1072
-
1073
- /**
1074
- * Gets price - formatted for display.
1075
- *
1076
- * @access public
1077
- * @param mixed $item
1078
- * @return string
1079
- */
1080
- public function get_formatted_item_price ( $item, $type, $tax_display = '' ) {
1081
- if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1082
- return;
1083
- }
1084
-
1085
- $divide_by = ($type == 'single' && $item['qty'] != 0 )?abs($item['qty']):1; //divide by 1 if $type is not 'single' (thus 'total')
1086
- if ( $tax_display == 'excl' ) {
1087
- $item_price = $this->format_price( ($this->order->get_line_subtotal( $item )) / $divide_by );
1088
- } else {
1089
- $item_price = $this->format_price( ($this->order->get_line_subtotal( $item, true )) / $divide_by );
1090
- }
1091
-
1092
- return $item_price;
1093
- }
1094
-
1095
- public function get_invoice_number() {
1096
- // Call the woocommerce_invoice_number filter and let third-party plugins set a number.
1097
- // Default is null, so we can detect whether a plugin has set the invoice number
1098
- $third_party_invoice_number = apply_filters( 'woocommerce_invoice_number', null, $this->order_id );
1099
- if ($third_party_invoice_number !== null) {
1100
- return $third_party_invoice_number;
1101
- }
1102
-
1103
- if ( $invoice_number = $this->get_number('invoice') ) {
1104
- return $formatted_invoice_number = $invoice_number->get_formatted();
1105
- } else {
1106
- return '';
1107
- }
1108
- }
1109
-
1110
- public function invoice_number() {
1111
- echo $this->get_invoice_number();
1112
- }
1113
-
1114
- public function get_invoice_date() {
1115
- if ( $invoice_date = $this->get_date('invoice') ) {
1116
- return $invoice_date->date_i18n( apply_filters( 'wpo_wcpdf_date_format', wc_date_format(), $this ) );
1117
- } else {
1118
- return '';
1119
- }
1120
- }
1121
-
1122
- public function invoice_date() {
1123
- echo $this->get_invoice_date();
1124
- }
1125
-
1126
-
1127
- }
1128
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1129
  endif; // class_exists
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Documents;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document_Methods' ) ) :
13
+
14
+ /**
15
+ * Abstract Order Methods
16
+ *
17
+ * Collection of methods to be used on orders within a Document
18
+ * Created as abstract rather than traits to support PHP versions older than 5.4
19
+ *
20
+ * @class \WPO\WC\PDF_Invoices\Documents\Order_Document_Methods
21
+ * @version 2.0
22
+ * @category Class
23
+ * @author Ewout Fernhout
24
+ */
25
+
26
+ abstract class Order_Document_Methods extends Order_Document {
27
+ public function is_refund( $order ) {
28
+ if ( method_exists( $order, 'get_type') ) { // WC 3.0+
29
+ $is_refund = $order->get_type() == 'shop_order_refund';
30
+ } else {
31
+ $is_refund = get_post_type( WCX_Order::get_id( $order ) ) == 'shop_order_refund';
32
+ }
33
+
34
+ return $is_refund;
35
+ }
36
+
37
+ public function get_refund_parent_id( $order ) {
38
+ if ( method_exists( $order, 'get_parent_id') ) { // WC3.0+
39
+ $parent_order_id = $order->get_parent_id();
40
+ } else {
41
+ $parent_order_id = wp_get_post_parent_id( WCX_Order::get_id( $order ) );
42
+ }
43
+
44
+ return $parent_order_id;
45
+ }
46
+
47
+
48
+ public function get_refund_parent( $order ) {
49
+ // only try if this is actually a refund
50
+ if ( ! $this->is_refund( $order ) ) {
51
+ return $order;
52
+ }
53
+
54
+ $parent_order_id = $this->get_refund_parent_id( $order );
55
+ $order = WCX::get_order( $parent_order_id );
56
+ return $order;
57
+ }
58
+
59
+ /**
60
+ * Check if billing address and shipping address are equal
61
+ */
62
+ public function ships_to_different_address() {
63
+ // always prefer parent address for refunds
64
+ if ( $this->is_refund( $this->order ) ) {
65
+ $order = $this->get_refund_parent( $this->order );
66
+ } else {
67
+ $order = $this->order;
68
+ }
69
+
70
+ // only check if there is a shipping address at all
71
+ if ( $formatted_shipping_address = $order->get_formatted_shipping_address() ) {
72
+ $address_comparison_fields = apply_filters( 'wpo_wcpdf_address_comparison_fields', array(
73
+ 'first_name',
74
+ 'last_name',
75
+ 'company',
76
+ 'address_1',
77
+ 'address_2',
78
+ 'city',
79
+ 'state',
80
+ 'postcode',
81
+ 'country'
82
+ ), $this );
83
+
84
+ foreach ($address_comparison_fields as $address_field) {
85
+ $billing_field = WCX_Order::get_prop( $order, "billing_{$address_field}", 'view');
86
+ $shipping_field = WCX_Order::get_prop( $order, "shipping_{$address_field}", 'view');
87
+ if ( $shipping_field != $billing_field ) {
88
+ // this address field is different -> ships to different address!
89
+ return true;
90
+ }
91
+ }
92
+ }
93
+
94
+ //if we got here, it means the addresses are equal -> doesn't ship to different address!
95
+ return apply_filters( 'wpo_wcpdf_ships_to_different_address', false, $order, $this );
96
+ }
97
+
98
+ /**
99
+ * Return/Show billing address
100
+ */
101
+ public function get_billing_address() {
102
+ // always prefer parent billing address for refunds
103
+ if ( $this->is_refund( $this->order ) ) {
104
+ // temporarily switch order to make all filters / order calls work correctly
105
+ $refund = $this->order;
106
+ $this->order = $this->get_refund_parent( $this->order );
107
+ $address = apply_filters( 'wpo_wcpdf_billing_address', $this->order->get_formatted_billing_address(), $this );
108
+ // switch back & unset
109
+ $this->order = $refund;
110
+ unset($refund);
111
+ } elseif ( $address = $this->order->get_formatted_billing_address() ) {
112
+ // regular shop_order
113
+ $address = apply_filters( 'wpo_wcpdf_billing_address', $address, $this );
114
+ } else {
115
+ // no address
116
+ $address = apply_filters( 'wpo_wcpdf_billing_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
117
+ }
118
+
119
+ return $address;
120
+ }
121
+ public function billing_address() {
122
+ echo $this->get_billing_address();
123
+ }
124
+
125
+ /**
126
+ * Return/Show billing email
127
+ */
128
+ public function get_billing_email() {
129
+ $billing_email = WCX_Order::get_prop( $this->order, 'billing_email', 'view' );
130
+
131
+ if ( !$billing_email && $this->is_refund( $this->order ) ) {
132
+ // try parent
133
+ $parent_order = $this->get_refund_parent( $this->order );
134
+ $billing_email = WCX_Order::get_prop( $parent_order, 'billing_email', 'view' );
135
+ }
136
+
137
+ return apply_filters( 'wpo_wcpdf_billing_email', $billing_email, $this );
138
+ }
139
+ public function billing_email() {
140
+ echo $this->get_billing_email();
141
+ }
142
+
143
+ /**
144
+ * Return/Show billing phone
145
+ */
146
+ public function get_billing_phone() {
147
+ $billing_phone = WCX_Order::get_prop( $this->order, 'billing_phone', 'view' );
148
+
149
+ if ( !$billing_phone && $this->is_refund( $this->order ) ) {
150
+ // try parent
151
+ $parent_order = $this->get_refund_parent( $this->order );
152
+ $billing_phone = WCX_Order::get_prop( $parent_order, 'billing_phone', 'view' );
153
+ }
154
+
155
+ return apply_filters( 'wpo_wcpdf_billing_phone', $billing_phone, $this );
156
+ }
157
+ public function billing_phone() {
158
+ echo $this->get_billing_phone();
159
+ }
160
+
161
+ /**
162
+ * Return/Show shipping address
163
+ */
164
+ public function get_shipping_address() {
165
+ // always prefer parent shipping address for refunds
166
+ if ( $this->is_refund( $this->order ) ) {
167
+ // temporarily switch order to make all filters / order calls work correctly
168
+ $refund = $this->order;
169
+ $this->order = $this->get_refund_parent( $this->order );
170
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', $this->order->get_formatted_shipping_address(), $this );
171
+ // switch back & unset
172
+ $this->order = $refund;
173
+ unset($refund);
174
+ } elseif ( $address = $this->order->get_formatted_shipping_address() ) {
175
+ // regular shop_order
176
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', $address, $this );
177
+ } else {
178
+ // no address
179
+ // use fallback for packing slip
180
+ if ( apply_filters( 'wpo_wcpdf_shipping_address_fallback', ( $this->get_type() == 'packing-slip' ), $this ) ) {
181
+ $address = $this->get_billing_address();
182
+ } else{
183
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
184
+
185
+ }
186
+ }
187
+
188
+ return $address;
189
+ }
190
+ public function shipping_address() {
191
+ echo $this->get_shipping_address();
192
+ }
193
+
194
+ /**
195
+ * Return/Show a custom field
196
+ */
197
+ public function get_custom_field( $field_name ) {
198
+ if ( !$this->is_order_prop( $field_name ) ) {
199
+ $custom_field = WCX_Order::get_meta( $this->order, $field_name, true );
200
+ }
201
+ // if not found, try prefixed with underscore (not when ACF is active!)
202
+ if ( empty( $custom_field ) && substr( $field_name, 0, 1 ) !== '_' && !$this->is_order_prop( "_{$field_name}" ) && !class_exists('ACF') ) {
203
+ $custom_field = WCX_Order::get_meta( $this->order, "_{$field_name}", true );
204
+ }
205
+
206
+ // WC3.0 fallback to properties
207
+ $property = str_replace('-', '_', sanitize_title( ltrim($field_name, '_') ) );
208
+ if ( empty( $custom_field ) && is_callable( array( $this->order, "get_{$property}" ) ) ) {
209
+ $custom_field = $this->order->{"get_{$property}"}( 'view' );
210
+ }
211
+
212
+ // fallback to parent for refunds
213
+ if ( empty( $custom_field ) && $this->is_refund( $this->order ) ) {
214
+ $parent_order = $this->get_refund_parent( $this->order );
215
+ if ( !$this->is_order_prop( $field_name ) ) {
216
+ $custom_field = WCX_Order::get_meta( $parent_order, $field_name, true );
217
+ }
218
+
219
+ // WC3.0 fallback to properties
220
+ if ( empty( $custom_field ) && is_callable( array( $parent_order, "get_{$property}" ) ) ) {
221
+ $custom_field = $parent_order->{"get_{$property}"}( 'view' );
222
+ }
223
+ }
224
+
225
+ return apply_filters( 'wpo_wcpdf_billing_custom_field', $custom_field, $this );
226
+ }
227
+ public function custom_field( $field_name, $field_label = '', $display_empty = false ) {
228
+ $custom_field = $this->get_custom_field( $field_name );
229
+ if (!empty($field_label)){
230
+ // add a a trailing space to the label
231
+ $field_label .= ' ';
232
+ }
233
+
234
+ if (!empty($custom_field) || $display_empty) {
235
+ echo $field_label . nl2br ($custom_field);
236
+ }
237
+ }
238
+
239
+ public function is_order_prop( $key ) {
240
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '<' ) ) {
241
+ return false; // WC 2.X didn't have CRUD
242
+ }
243
+ // Taken from WC class
244
+ $order_props = array(
245
+ // Abstract order props
246
+ 'parent_id',
247
+ 'status',
248
+ 'currency',
249
+ 'version',
250
+ 'prices_include_tax',
251
+ 'date_created',
252
+ 'date_modified',
253
+ 'discount_total',
254
+ 'discount_tax',
255
+ 'shipping_total',
256
+ 'shipping_tax',
257
+ 'cart_tax',
258
+ 'total',
259
+ 'total_tax',
260
+ // Order props
261
+ 'customer_id',
262
+ 'order_key',
263
+ 'billing_first_name',
264
+ 'billing_last_name',
265
+ 'billing_company',
266
+ 'billing_address_1',
267
+ 'billing_address_2',
268
+ 'billing_city',
269
+ 'billing_state',
270
+ 'billing_postcode',
271
+ 'billing_country',
272
+ 'billing_email',
273
+ 'billing_phone',
274
+ 'shipping_first_name',
275
+ 'shipping_last_name',
276
+ 'shipping_company',
277
+ 'shipping_address_1',
278
+ 'shipping_address_2',
279
+ 'shipping_city',
280
+ 'shipping_state',
281
+ 'shipping_postcode',
282
+ 'shipping_country',
283
+ 'payment_method',
284
+ 'payment_method_title',
285
+ 'transaction_id',
286
+ 'customer_ip_address',
287
+ 'customer_user_agent',
288
+ 'created_via',
289
+ 'customer_note',
290
+ 'date_completed',
291
+ 'date_paid',
292
+ 'cart_hash',
293
+ );
294
+ return in_array($key, $order_props);
295
+ }
296
+
297
+ /**
298
+ * Return/show product attribute
299
+ */
300
+ public function get_product_attribute( $attribute_name, $product ) {
301
+ // first, check the text attributes
302
+ $attributes = $product->get_attributes();
303
+ $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
304
+ if (array_key_exists( sanitize_title( $attribute_name ), $attributes) ) {
305
+ $attribute = $product->get_attribute ( $attribute_name );
306
+ } elseif (array_key_exists( sanitize_title( $attribute_key ), $attributes) ) {
307
+ $attribute = $product->get_attribute ( $attribute_key );
308
+ }
309
+
310
+ if (empty($attribute)) {
311
+ // not a text attribute, try attribute taxonomy
312
+ $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
313
+ $product_id = WCX_Product::get_prop($product, 'id');
314
+ $product_terms = @wc_get_product_terms( $product_id, $attribute_key, array( 'fields' => 'names' ) );
315
+ // check if not empty, then display
316
+ if ( !empty($product_terms) ) {
317
+ $attribute = array_shift( $product_terms );
318
+ }
319
+ }
320
+
321
+ // WC3.0+ fallback parent product for variations
322
+ if ( empty($attribute) && version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) && $product->is_type( 'variation' ) ) {
323
+ $product = wc_get_product( $product->get_parent_id() );
324
+ $attribute = $this->get_product_attribute( $attribute_name, $product );
325
+ }
326
+
327
+ return isset($attribute) ? $attribute : false;
328
+ }
329
+ public function product_attribute( $attribute_name, $product ) {
330
+ echo $this->get_product_attribute( $attribute_name, $product );
331
+ }
332
+
333
+ /**
334
+ * Return/Show order notes
335
+ * could use $order->get_customer_order_notes(), but that filters out private notes already
336
+ */
337
+ public function get_order_notes( $filter = 'customer', $include_system_notes = true ) {
338
+ if ( $this->is_refund( $this->order ) ) {
339
+ $post_id = $this->get_refund_parent_id( $this->order );
340
+ } else {
341
+ $post_id = $this->order_id;
342
+ }
343
+
344
+ if ( empty( $post_id ) ) {
345
+ return; // prevent order notes from all orders showing when document is not loaded properly
346
+ }
347
+
348
+ if ( function_exists('wc_get_order_notes') ) { // WC3.2+
349
+ $type = ( $filter == 'private' ) ? 'internal' : $filter;
350
+ $notes = wc_get_order_notes( array(
351
+ 'order_id' => $post_id,
352
+ 'type' => $type, // use 'internal' for admin and system notes, empty for all
353
+ ) );
354
+
355
+ if ( $include_system_notes === false ) {
356
+ foreach ($notes as $key => $note) {
357
+ if ( $note->added_by == 'system' ) {
358
+ unset($notes[$key]);
359
+ }
360
+ }
361
+ }
362
+
363
+ return $notes;
364
+ } else {
365
+
366
+ $args = array(
367
+ 'post_id' => $post_id,
368
+ 'approve' => 'approve',
369
+ 'type' => 'order_note'
370
+ );
371
+
372
+ remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
373
+
374
+ $notes = get_comments( $args );
375
+
376
+ add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
377
+
378
+ if ( $notes ) {
379
+ foreach( $notes as $key => $note ) {
380
+ if ( $filter == 'customer' && !get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
381
+ unset($notes[$key]);
382
+ }
383
+ if ( $filter == 'private' && get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
384
+ unset($notes[$key]);
385
+ }
386
+ }
387
+ return $notes;
388
+ }
389
+ }
390
+
391
+ }
392
+ public function order_notes( $filter = 'customer', $include_system_notes = true ) {
393
+ $notes = $this->get_order_notes( $filter, $include_system_notes );
394
+ if ( $notes ) {
395
+ foreach( $notes as $note ) {
396
+ $css_class = array( 'note', 'note_content' );
397
+ $css_class[] = $note->customer_note ? 'customer-note' : '';
398
+ $css_class[] = 'system' === $note->added_by ? 'system-note' : '';
399
+ $css_class = apply_filters( 'woocommerce_order_note_class', array_filter( $css_class ), $note );
400
+ $content = isset($note->content) ? $note->content : $note->comment_content;
401
+ ?>
402
+ <div class="<?php echo esc_attr( implode( ' ', $css_class ) ); ?>">
403
+ <?php echo wpautop( wptexturize( wp_kses_post( $content ) ) ); ?>
404
+ </div>
405
+ <?php
406
+ }
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Return/Show the current date
412
+ */
413
+ public function get_current_date() {
414
+ return apply_filters( 'wpo_wcpdf_date', date_i18n( wc_date_format() ) );
415
+ }
416
+ public function current_date() {
417
+ echo $this->get_current_date();
418
+ }
419
+
420
+ /**
421
+ * Return/Show payment method
422
+ */
423
+ public function get_payment_method() {
424
+ $payment_method_label = __( 'Payment method', 'woocommerce-pdf-invoices-packing-slips' );
425
+
426
+ if ( $this->is_refund( $this->order ) ) {
427
+ $parent_order = $this->get_refund_parent( $this->order );
428
+ $payment_method_title = WCX_Order::get_prop( $parent_order, 'payment_method_title', 'view' );
429
+ } else {
430
+ $payment_method_title = WCX_Order::get_prop( $this->order, 'payment_method_title', 'view' );
431
+ }
432
+
433
+ $payment_method = __( $payment_method_title, 'woocommerce' );
434
+
435
+ return apply_filters( 'wpo_wcpdf_payment_method', $payment_method, $this );
436
+ }
437
+ public function payment_method() {
438
+ echo $this->get_payment_method();
439
+ }
440
+
441
+ /**
442
+ * Return/Show shipping method
443
+ */
444
+ public function get_shipping_method() {
445
+ $shipping_method_label = __( 'Shipping method', 'woocommerce-pdf-invoices-packing-slips' );
446
+ $shipping_method = __( $this->order->get_shipping_method(), 'woocommerce' );
447
+ return apply_filters( 'wpo_wcpdf_shipping_method', $shipping_method, $this );
448
+ }
449
+ public function shipping_method() {
450
+ echo $this->get_shipping_method();
451
+ }
452
+
453
+ /**
454
+ * Return/Show order number
455
+ */
456
+ public function get_order_number() {
457
+ // try parent first
458
+ if ( $this->is_refund( $this->order ) ) {
459
+ $parent_order = $this->get_refund_parent( $this->order );
460
+ $order_number = $parent_order->get_order_number();
461
+ } else {
462
+ $order_number = $this->order->get_order_number();
463
+ }
464
+
465
+ // Trim the hash to have a clean number but still
466
+ // support any filters that were applied before.
467
+ $order_number = ltrim($order_number, '#');
468
+ return apply_filters( 'wpo_wcpdf_order_number', $order_number, $this );
469
+ }
470
+ public function order_number() {
471
+ echo $this->get_order_number();
472
+ }
473
+
474
+ /**
475
+ * Return/Show the order date
476
+ */
477
+ public function get_order_date() {
478
+ if ( $this->is_refund( $this->order ) ) {
479
+ $parent_order = $this->get_refund_parent( $this->order );
480
+ $order_date = WCX_Order::get_prop( $parent_order, 'date_created' );
481
+ } else {
482
+ $order_date = WCX_Order::get_prop( $this->order, 'date_created' );
483
+ }
484
+
485
+ $date = $order_date->date_i18n( wc_date_format() );
486
+ $mysql_date = $order_date->date( "Y-m-d H:i:s" );
487
+ return apply_filters( 'wpo_wcpdf_order_date', $date, $mysql_date, $this );
488
+ }
489
+ public function order_date() {
490
+ echo $this->get_order_date();
491
+ }
492
+
493
+ /**
494
+ * Return the order items
495
+ */
496
+ public function get_order_items() {
497
+ $items = $this->order->get_items();
498
+ $data_list = array();
499
+
500
+ if( sizeof( $items ) > 0 ) {
501
+ foreach ( $items as $item_id => $item ) {
502
+ // Array with data for the pdf template
503
+ $data = array();
504
+
505
+ // Set the item_id
506
+ $data['item_id'] = $item_id;
507
+
508
+ // Set the id
509
+ $data['product_id'] = $item['product_id'];
510
+ $data['variation_id'] = $item['variation_id'];
511
+
512
+ // Compatibility: WooCommerce Composit Products uses a workaround for
513
+ // setting the order before the item name filter, so we run this first
514
+ if ( class_exists('WC_Composite_Products') ) {
515
+ $order_item_class = apply_filters( 'woocommerce_order_item_class', '', $item, $this->order );
516
+ }
517
+
518
+ // Set item name
519
+ $data['name'] = apply_filters( 'woocommerce_order_item_name', $item['name'], $item, false );
520
+
521
+ // Set item quantity
522
+ $data['quantity'] = $item['qty'];
523
+
524
+ // Set the line total (=after discount)
525
+ $data['line_total'] = $this->format_price( $item['line_total'] );
526
+ $data['single_line_total'] = $this->format_price( $item['line_total'] / max( 1, abs( $item['qty'] ) ) );
527
+ $data['line_tax'] = $this->format_price( $item['line_tax'] );
528
+ $data['single_line_tax'] = $this->format_price( $item['line_tax'] / max( 1, abs( $item['qty'] ) ) );
529
+
530
+ $line_tax_data = maybe_unserialize( isset( $item['line_tax_data'] ) ? $item['line_tax_data'] : '' );
531
+ $data['tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, true );
532
+ $data['calculated_tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, false );
533
+
534
+ // Set the line subtotal (=before discount)
535
+ $data['line_subtotal'] = $this->format_price( $item['line_subtotal'] );
536
+ $data['line_subtotal_tax'] = $this->format_price( $item['line_subtotal_tax'] );
537
+ $data['ex_price'] = $this->get_formatted_item_price( $item, 'total', 'excl' );
538
+ $data['price'] = $this->get_formatted_item_price( $item, 'total' );
539
+ $data['order_price'] = $this->order->get_formatted_line_subtotal( $item ); // formatted according to WC settings
540
+
541
+ // Calculate the single price with the same rules as the formatted line subtotal (!)
542
+ // = before discount
543
+ $data['ex_single_price'] = $this->get_formatted_item_price( $item, 'single', 'excl' );
544
+ $data['single_price'] = $this->get_formatted_item_price( $item, 'single' );
545
+
546
+ // Pass complete item array
547
+ $data['item'] = $item;
548
+
549
+ // Get the product to add more info
550
+ $product = $this->order->get_product_from_item( $item );
551
+
552
+ // Checking fo existance, thanks to MDesigner0
553
+ if( !empty( $product ) ) {
554
+ // Thumbnail (full img tag)
555
+ $data['thumbnail'] = $this->get_thumbnail( $product );
556
+
557
+ // Set item SKU
558
+ $data['sku'] = $product->get_sku();
559
+
560
+ // Set item weight
561
+ $data['weight'] = $product->get_weight();
562
+
563
+ // Set item dimensions
564
+ $data['dimensions'] = WCX_Product::get_dimensions( $product );
565
+
566
+ // Pass complete product object
567
+ $data['product'] = $product;
568
+
569
+ } else {
570
+ $data['product'] = null;
571
+ }
572
+
573
+ // Set item meta
574
+ if (function_exists('wc_display_item_meta')) { // WC3.0+
575
+ $data['meta'] = wc_display_item_meta( $item, array(
576
+ 'echo' => false,
577
+ ) );
578
+ } else {
579
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '<' ) ) {
580
+ $meta = new \WC_Order_Item_Meta( $item['item_meta'], $product );
581
+ } else { // pass complete item for WC2.4+
582
+ $meta = new \WC_Order_Item_Meta( $item, $product );
583
+ }
584
+ $data['meta'] = $meta->display( false, true );
585
+ }
586
+
587
+ $data_list[$item_id] = apply_filters( 'wpo_wcpdf_order_item_data', $data, $this->order, $this->get_type() );
588
+ }
589
+ }
590
+
591
+ return apply_filters( 'wpo_wcpdf_order_items_data', $data_list, $this->order, $this->get_type() );
592
+ }
593
+
594
+ /**
595
+ * Get the tax rates/percentages for a given tax class
596
+ * @param string $tax_class tax class slug
597
+ * @return string $tax_rates imploded list of tax rates
598
+ */
599
+ public function get_tax_rate( $tax_class, $line_total, $line_tax, $line_tax_data = '', $force_calculation = false ) {
600
+ // first try the easy wc2.2+ way, using line_tax_data
601
+ if ( !empty( $line_tax_data ) && isset($line_tax_data['total']) ) {
602
+ $tax_rates = array();
603
+
604
+ $line_taxes = $line_tax_data['subtotal'];
605
+ foreach ( $line_taxes as $tax_id => $tax ) {
606
+ if ( isset($tax) && $tax !== '' ) {
607
+ $tax_rate = $this->get_tax_rate_by_id( $tax_id );
608
+ if ( $tax_rate !== false && $force_calculation !== false ) {
609
+ $tax_rates[] = $tax_rate . ' %';
610
+ } else {
611
+ $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
612
+ }
613
+ }
614
+ }
615
+
616
+ // apply decimal setting
617
+ if (function_exists('wc_get_price_decimal_separator')) {
618
+ foreach ($tax_rates as &$tax_rate) {
619
+ $tax_rate = str_replace('.', wc_get_price_decimal_separator(), strval($tax_rate) );
620
+ }
621
+ }
622
+
623
+ $tax_rates = implode(' ,', $tax_rates );
624
+ return $tax_rates;
625
+ }
626
+
627
+ if ( $line_tax == 0 ) {
628
+ return '-'; // no need to determine tax rate...
629
+ }
630
+
631
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 && !apply_filters( 'wpo_wcpdf_calculate_tax_rate', false ) ) {
632
+ // WC 2.1 or newer is used
633
+ $tax = new \WC_Tax();
634
+ $taxes = $tax->get_rates( $tax_class );
635
+
636
+ $tax_rates = array();
637
+
638
+ foreach ($taxes as $tax) {
639
+ $tax_rates[$tax['label']] = round( $tax['rate'], 2 ).' %';
640
+ }
641
+
642
+ if (empty($tax_rates)) {
643
+ // one last try: manually calculate
644
+ $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
645
+ }
646
+
647
+ $tax_rates = implode(' ,', $tax_rates );
648
+ } else {
649
+ // Backwards compatibility/fallback: calculate tax from line items
650
+ $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
651
+ }
652
+
653
+ return $tax_rates;
654
+ }
655
+
656
+ public function calculate_tax_rate( $price_ex_tax, $tax ) {
657
+ $precision = apply_filters( 'wpo_wcpdf_calculate_tax_rate_precision', 1 );
658
+ if ( $price_ex_tax != 0) {
659
+ $tax_rate = round( ($tax / $price_ex_tax)*100, $precision ).' %';
660
+ } else {
661
+ $tax_rate = '-';
662
+ }
663
+ return $tax_rate;
664
+ }
665
+
666
+ /**
667
+ * Returns the percentage rate (float) for a given tax rate ID.
668
+ * @param int $rate_id woocommerce tax rate id
669
+ * @return float $rate percentage rate
670
+ */
671
+ public function get_tax_rate_by_id( $rate_id ) {
672
+ global $wpdb;
673
+ $rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $rate_id ) );
674
+ if ($rate === NULL) {
675
+ return false;
676
+ } else {
677
+ return (float) $rate;
678
+ }
679
+ }
680
+
681
+ /**
682
+ * Returns a an array with rate_id => tax rate data (array) of all tax rates in woocommerce
683
+ * @return array $tax_rate_ids keyed by id
684
+ */
685
+ public function get_tax_rate_ids() {
686
+ global $wpdb;
687
+ $rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates" );
688
+
689
+ $tax_rate_ids = array();
690
+ foreach ($rates as $rate) {
691
+ $rate_id = $rate->tax_rate_id;
692
+ unset($rate->tax_rate_id);
693
+ $tax_rate_ids[$rate_id] = (array) $rate;
694
+ }
695
+
696
+ return $tax_rate_ids;
697
+ }
698
+
699
+ /**
700
+ * Returns the main product image ID
701
+ * Adapted from the WC_Product class
702
+ * (does not support thumbnail sizes)
703
+ *
704
+ * @access public
705
+ * @return string
706
+ */
707
+ public function get_thumbnail_id ( $product ) {
708
+ global $woocommerce;
709
+
710
+ $product_id = WCX_Product::get_id( $product );
711
+
712
+ if ( has_post_thumbnail( $product_id ) ) {
713
+ $thumbnail_id = get_post_thumbnail_id ( $product_id );
714
+ } elseif ( ( $parent_id = wp_get_post_parent_id( $product_id ) ) && has_post_thumbnail( $parent_id ) ) {
715
+ $thumbnail_id = get_post_thumbnail_id ( $parent_id );
716
+ } else {
717
+ $thumbnail_id = false;
718
+ }
719
+
720
+ return $thumbnail_id;
721
+ }
722
+
723
+ /**
724
+ * Returns the thumbnail image tag
725
+ *
726
+ * uses the internal WooCommerce/WP functions and extracts the image url or path
727
+ * rather than the thumbnail ID, to simplify the code and make it possible to
728
+ * filter for different thumbnail sizes
729
+ *
730
+ * @access public
731
+ * @return string
732
+ */
733
+ public function get_thumbnail ( $product ) {
734
+ // Get default WooCommerce img tag (url/http)
735
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.3', '>=' ) ) {
736
+ $thumbnail_size = 'woocommerce_thumbnail';
737
+ } else {
738
+ $thumbnail_size = 'shop_thumbnail';
739
+ }
740
+ $size = apply_filters( 'wpo_wcpdf_thumbnail_size', $thumbnail_size );
741
+ $thumbnail_img_tag_url = $product->get_image( $size, array( 'title' => '' ) );
742
+
743
+ // Extract the url from img
744
+ preg_match('/<img(.*)src(.*)=(.*)"(.*)"/U', $thumbnail_img_tag_url, $thumbnail_url );
745
+ $thumbnail_url = array_pop($thumbnail_url);
746
+ // remove http/https from image tag url to avoid mixed origin conflicts
747
+ $contextless_thumbnail_url = ltrim( str_replace(array('http://','https://'), '', $thumbnail_url ), '/' );
748
+
749
+ // convert url to path
750
+ if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
751
+ $forwardslash_basepath = str_replace('\\','/', ABSPATH);
752
+ $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(get_site_url()));
753
+ } else {
754
+ // bedrock e.a
755
+ $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
756
+ $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(WP_CONTENT_URL));
757
+ }
758
+ $thumbnail_path = str_replace( $contextless_site_url, trailingslashit( $forwardslash_basepath ), $contextless_thumbnail_url);
759
+
760
+ // fallback if thumbnail file doesn't exist
761
+ if (apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path)) {
762
+ if ($thumbnail_id = $this->get_thumbnail_id( $product ) ) {
763
+ $thumbnail_path = get_attached_file( $thumbnail_id );
764
+ }
765
+ }
766
+
767
+ // Thumbnail (full img tag)
768
+ if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($thumbnail_path) ) {
769
+ // load img with server path by default
770
+ $thumbnail = sprintf('<img width="90" height="90" src="%s" class="attachment-shop_thumbnail wp-post-image">', $thumbnail_path );
771
+ } elseif ( apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path) ) {
772
+ // should use paths but file not found, replace // with http(s):// for dompdf compatibility
773
+ if ( substr( $thumbnail_url, 0, 2 ) === "//" ) {
774
+ $prefix = is_ssl() ? 'https://' : 'http://';
775
+ $https_thumbnail_url = $prefix . ltrim( $thumbnail_url, '/' );
776
+ $thumbnail_img_tag_url = str_replace($thumbnail_url, $https_thumbnail_url, $thumbnail_img_tag_url);
777
+ }
778
+ $thumbnail = $thumbnail_img_tag_url;
779
+ } else {
780
+ // load img with http url when filtered
781
+ $thumbnail = $thumbnail_img_tag_url;
782
+ }
783
+
784
+ // die($thumbnail);
785
+ return $thumbnail;
786
+ }
787
+
788
+ /**
789
+ * Return the order totals listing
790
+ */
791
+ public function get_woocommerce_totals() {
792
+ // get totals and remove the semicolon
793
+ $totals = apply_filters( 'wpo_wcpdf_raw_order_totals', $this->order->get_order_item_totals(), $this->order );
794
+
795
+ // remove the colon for every label
796
+ foreach ( $totals as $key => $total ) {
797
+ $label = $total['label'];
798
+ $colon = strrpos( $label, ':' );
799
+ if( $colon !== false ) {
800
+ $label = substr_replace( $label, '', $colon, 1 );
801
+ }
802
+ $totals[$key]['label'] = $label;
803
+ }
804
+
805
+ // WC2.4 fix order_total for refunded orders
806
+ // not if this is the actual refund!
807
+ if ( ! $this->is_refund( $this->order ) ) {
808
+ $total_refunded = method_exists($this->order, 'get_total_refunded') ? $this->order->get_total_refunded() : 0;
809
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '>=' ) && isset($totals['order_total']) && $total_refunded ) {
810
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
811
+ $tax_display = get_option( 'woocommerce_tax_display_cart' );
812
+ } else {
813
+ $tax_display = WCX_Order::get_prop( $this->order, 'tax_display_cart' );
814
+ }
815
+
816
+ $totals['order_total']['value'] = wc_price( $this->order->get_total(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) );
817
+ $order_total = $this->order->get_total();
818
+ $tax_string = '';
819
+
820
+ // Tax for inclusive prices
821
+ if ( wc_tax_enabled() && 'incl' == $tax_display ) {
822
+ $tax_string_array = array();
823
+ if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
824
+ foreach ( $this->order->get_tax_totals() as $code => $tax ) {
825
+ $tax_amount = $tax->formatted_amount;
826
+ $tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
827
+ }
828
+ } else {
829
+ $tax_string_array[] = sprintf( '%s %s', wc_price( $this->order->get_total_tax() - $this->order->get_total_tax_refunded(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) ), WC()->countries->tax_or_vat() );
830
+ }
831
+ if ( ! empty( $tax_string_array ) ) {
832
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.6', '>=' ) ) {
833
+ $tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
834
+ } else {
835
+ // use old capitalized string
836
+ $tax_string = ' ' . sprintf( __( '(Includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
837
+ }
838
+ }
839
+ }
840
+
841
+ $totals['order_total']['value'] .= $tax_string;
842
+ }
843
+
844
+ // remove refund lines (shouldn't be in invoice)
845
+ foreach ( $totals as $key => $total ) {
846
+ if ( strpos($key, 'refund_') !== false ) {
847
+ unset( $totals[$key] );
848
+ }
849
+ }
850
+
851
+ }
852
+
853
+ return apply_filters( 'wpo_wcpdf_woocommerce_totals', $totals, $this->order, $this->get_type() );
854
+ }
855
+
856
+ /**
857
+ * Return/show the order subtotal
858
+ */
859
+ public function get_order_subtotal( $tax = 'excl', $discount = 'incl' ) { // set $tax to 'incl' to include tax, same for $discount
860
+ //$compound = ($discount == 'incl')?true:false;
861
+ $subtotal = $this->order->get_subtotal_to_display( false, $tax );
862
+
863
+ $subtotal = ($pos = strpos($subtotal, ' <small')) ? substr($subtotal, 0, $pos) : $subtotal; //removing the 'excluding tax' text
864
+
865
+ $subtotal = array (
866
+ 'label' => __('Subtotal', 'woocommerce-pdf-invoices-packing-slips' ),
867
+ 'value' => $subtotal,
868
+ );
869
+
870
+ return apply_filters( 'wpo_wcpdf_order_subtotal', $subtotal, $tax, $discount, $this );
871
+ }
872
+ public function order_subtotal( $tax = 'excl', $discount = 'incl' ) {
873
+ $subtotal = $this->get_order_subtotal( $tax, $discount );
874
+ echo $subtotal['value'];
875
+ }
876
+
877
+ /**
878
+ * Return/show the order shipping costs
879
+ */
880
+ public function get_order_shipping( $tax = 'excl' ) { // set $tax to 'incl' to include tax
881
+ $shipping_cost = WCX_Order::get_prop( $this->order, 'shipping_total', 'view' );
882
+ $shipping_tax = WCX_Order::get_prop( $this->order, 'shipping_tax', 'view' );
883
+
884
+ if ($tax == 'excl' ) {
885
+ $formatted_shipping_cost = $this->format_price( $shipping_cost );
886
+ } else {
887
+ $formatted_shipping_cost = $this->format_price( $shipping_cost + $shipping_tax );
888
+ }
889
+
890
+ $shipping = array (
891
+ 'label' => __('Shipping', 'woocommerce-pdf-invoices-packing-slips' ),
892
+ 'value' => $formatted_shipping_cost,
893
+ 'tax' => $this->format_price( $shipping_tax ),
894
+ );
895
+ return apply_filters( 'wpo_wcpdf_order_shipping', $shipping, $tax, $this );
896
+ }
897
+ public function order_shipping( $tax = 'excl' ) {
898
+ $shipping = $this->get_order_shipping( $tax );
899
+ echo $shipping['value'];
900
+ }
901
+
902
+ /**
903
+ * Return/show the total discount
904
+ */
905
+ public function get_order_discount( $type = 'total', $tax = 'incl' ) {
906
+ if ( $tax == 'incl' ) {
907
+ switch ($type) {
908
+ case 'cart':
909
+ // Cart Discount - pre-tax discounts. (deprecated in WC2.3)
910
+ $discount_value = $this->order->get_cart_discount();
911
+ break;
912
+ case 'order':
913
+ // Order Discount - post-tax discounts. (deprecated in WC2.3)
914
+ $discount_value = $this->order->get_order_discount();
915
+ break;
916
+ case 'total':
917
+ // Total Discount
918
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
919
+ $discount_value = $this->order->get_total_discount( false ); // $ex_tax = false
920
+ } else {
921
+ // WC2.2 and older: recalculate to include tax
922
+ $discount_value = 0;
923
+ $items = $this->order->get_items();;
924
+ if( sizeof( $items ) > 0 ) {
925
+ foreach( $items as $item ) {
926
+ $discount_value += ($item['line_subtotal'] + $item['line_subtotal_tax']) - ($item['line_total'] + $item['line_tax']);
927
+ }
928
+ }
929
+ }
930
+
931
+ break;
932
+ default:
933
+ // Total Discount - Cart & Order Discounts combined
934
+ $discount_value = $this->order->get_total_discount();
935
+ break;
936
+ }
937
+ } else { // calculate discount excluding tax
938
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
939
+ $discount_value = $this->order->get_total_discount( true ); // $ex_tax = true
940
+ } else {
941
+ // WC2.2 and older: recalculate to exclude tax
942
+ $discount_value = 0;
943
+
944
+ $items = $this->order->get_items();;
945
+ if( sizeof( $items ) > 0 ) {
946
+ foreach( $items as $item ) {
947
+ $discount_value += ($item['line_subtotal'] - $item['line_total']);
948
+ }
949
+ }
950
+ }
951
+ }
952
+
953
+ $discount = array (
954
+ 'label' => __('Discount', 'woocommerce-pdf-invoices-packing-slips' ),
955
+ 'value' => $this->format_price( $discount_value ),
956
+ 'raw_value' => $discount_value,
957
+ );
958
+
959
+ if ( round( $discount_value, 3 ) != 0 ) {
960
+ return apply_filters( 'wpo_wcpdf_order_discount', $discount, $type, $tax, $this );
961
+ }
962
+ }
963
+ public function order_discount( $type = 'total', $tax = 'incl' ) {
964
+ $discount = $this->get_order_discount( $type, $tax );
965
+ echo $discount['value'];
966
+ }
967
+
968
+ /**
969
+ * Return the order fees
970
+ */
971
+ public function get_order_fees( $tax = 'excl' ) {
972
+ if ( $_fees = $this->order->get_fees() ) {
973
+ foreach( $_fees as $id => $fee ) {
974
+ if ($tax == 'excl' ) {
975
+ $fee_price = $this->format_price( $fee['line_total'] );
976
+ } else {
977
+ $fee_price = $this->format_price( $fee['line_total'] + $fee['line_tax'] );
978
+ }
979
+
980
+ $fees[ $id ] = array(
981
+ 'label' => $fee['name'],
982
+ 'value' => $fee_price,
983
+ 'line_total' => $this->format_price( $fee['line_total'] ),
984
+ 'line_tax' => $this->format_price( $fee['line_tax'] )
985
+ );
986
+ }
987
+ return $fees;
988
+ }
989
+ }
990
+
991
+ /**
992
+ * Return the order taxes
993
+ */
994
+ public function get_order_taxes() {
995
+ $tax_label = __( 'VAT', 'woocommerce-pdf-invoices-packing-slips' ); // register alternate label translation
996
+ $tax_label = __( 'Tax rate', 'woocommerce-pdf-invoices-packing-slips' );
997
+ $tax_rate_ids = $this->get_tax_rate_ids();
998
+ if ( $order_taxes = $this->order->get_taxes() ) {
999
+ foreach ( $order_taxes as $key => $tax ) {
1000
+ if ( WCX::is_wc_version_gte_3_0() ) {
1001
+ $taxes[ $key ] = array(
1002
+ 'label' => $tax->get_label(),
1003
+ 'value' => $this->format_price( $tax->get_tax_total() + $tax->get_shipping_tax_total() ),
1004
+ 'rate_id' => $tax->get_rate_id(),
1005
+ 'tax_amount' => $tax->get_tax_total(),
1006
+ 'shipping_tax_amount' => $tax->get_shipping_tax_total(),
1007
+ 'rate' => isset( $tax_rate_ids[ $tax->get_rate_id() ] ) ? ( (float) $tax_rate_ids[$tax->get_rate_id()]['tax_rate'] ) . ' %': '',
1008
+ );
1009
+ } else {
1010
+ $taxes[ $key ] = array(
1011
+ 'label' => isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ],
1012
+ 'value' => $this->format_price( ( $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ] ) ),
1013
+ 'rate_id' => $tax['rate_id'],
1014
+ 'tax_amount' => $tax['tax_amount'],
1015
+ 'shipping_tax_amount' => $tax['shipping_tax_amount'],
1016
+ 'rate' => isset( $tax_rate_ids[ $tax['rate_id'] ] ) ? ( (float) $tax_rate_ids[$tax['rate_id']]['tax_rate'] ) . ' %': '',
1017
+ );
1018
+ }
1019
+
1020
+ }
1021
+
1022
+ return apply_filters( 'wpo_wcpdf_order_taxes', $taxes, $this );
1023
+ }
1024
+ }
1025
+
1026
+ /**
1027
+ * Return/show the order grand total
1028
+ */
1029
+ public function get_order_grand_total( $tax = 'incl' ) {
1030
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 ) {
1031
+ // WC 2.1 or newer is used
1032
+ $total_unformatted = $this->order->get_total();
1033
+ } else {
1034
+ // Backwards compatibility
1035
+ $total_unformatted = $this->order->get_order_total();
1036
+ }
1037
+
1038
+ if ($tax == 'excl' ) {
1039
+ $total = $this->format_price( $total_unformatted - $this->order->get_total_tax() );
1040
+ $label = __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' );
1041
+ } else {
1042
+ $total = $this->format_price( ( $total_unformatted ) );
1043
+ $label = __( 'Total', 'woocommerce-pdf-invoices-packing-slips' );
1044
+ }
1045
+
1046
+ $grand_total = array(
1047
+ 'label' => $label,
1048
+ 'value' => $total,
1049
+ );
1050
+
1051
+ return apply_filters( 'wpo_wcpdf_order_grand_total', $grand_total, $tax, $this );
1052
+ }
1053
+ public function order_grand_total( $tax = 'incl' ) {
1054
+ $grand_total = $this->get_order_grand_total( $tax );
1055
+ echo $grand_total['value'];
1056
+ }
1057
+
1058
+
1059
+ /**
1060
+ * Return/Show shipping notes
1061
+ */
1062
+ public function get_shipping_notes() {
1063
+ if ( $this->is_refund( $this->order ) ) {
1064
+ // return reason for refund if order is a refund
1065
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
1066
+ $shipping_notes = $this->order->get_reason();
1067
+ } elseif ( method_exists($this->order, 'get_refund_reason') ) {
1068
+ $shipping_notes = $this->order->get_refund_reason();
1069
+ } else {
1070
+ $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1071
+ }
1072
+ } else {
1073
+ $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1074
+ }
1075
+ return apply_filters( 'wpo_wcpdf_shipping_notes', $shipping_notes, $this );
1076
+ }
1077
+ public function shipping_notes() {
1078
+ echo $this->get_shipping_notes();
1079
+ }
1080
+
1081
+ /**
1082
+ * wrapper for wc_price, ensuring currency is always passed
1083
+ */
1084
+ public function format_price( $price, $args = array() ) {
1085
+ if ( function_exists( 'wc_price' ) ) { // WC 2.1+
1086
+ $args['currency'] = WCX_Order::get_prop( $this->order, 'currency' );
1087
+ $formatted_price = wc_price( $price, $args );
1088
+ } else {
1089
+ $formatted_price = woocommerce_price( $price );
1090
+ }
1091
+
1092
+ return $formatted_price;
1093
+ }
1094
+ public function wc_price( $price, $args = array() ) {
1095
+ return $this->format_price( $price, $args );
1096
+ }
1097
+
1098
+ /**
1099
+ * Gets price - formatted for display.
1100
+ *
1101
+ * @access public
1102
+ * @param mixed $item
1103
+ * @return string
1104
+ */
1105
+ public function get_formatted_item_price ( $item, $type, $tax_display = '' ) {
1106
+ if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1107
+ return;
1108
+ }
1109
+
1110
+ $divide_by = ($type == 'single' && $item['qty'] != 0 )?abs($item['qty']):1; //divide by 1 if $type is not 'single' (thus 'total')
1111
+ if ( $tax_display == 'excl' ) {
1112
+ $item_price = $this->format_price( ($this->order->get_line_subtotal( $item )) / $divide_by );
1113
+ } else {
1114
+ $item_price = $this->format_price( ($this->order->get_line_subtotal( $item, true )) / $divide_by );
1115
+ }
1116
+
1117
+ return $item_price;
1118
+ }
1119
+
1120
+ public function get_invoice_number() {
1121
+ // Call the woocommerce_invoice_number filter and let third-party plugins set a number.
1122
+ // Default is null, so we can detect whether a plugin has set the invoice number
1123
+ $third_party_invoice_number = apply_filters( 'woocommerce_invoice_number', null, $this->order_id );
1124
+ if ($third_party_invoice_number !== null) {
1125
+ return $third_party_invoice_number;
1126
+ }
1127
+
1128
+ if ( $invoice_number = $this->get_number('invoice') ) {
1129
+ return $formatted_invoice_number = $invoice_number->get_formatted();
1130
+ } else {
1131
+ return '';
1132
+ }
1133
+ }
1134
+
1135
+ public function invoice_number() {
1136
+ echo $this->get_invoice_number();
1137
+ }
1138
+
1139
+ public function get_invoice_date() {
1140
+ if ( $invoice_date = $this->get_date('invoice') ) {
1141
+ return $invoice_date->date_i18n( apply_filters( 'wpo_wcpdf_date_format', wc_date_format(), $this ) );
1142
+ } else {
1143
+ return '';
1144
+ }
1145
+ }
1146
+
1147
+ public function invoice_date() {
1148
+ echo $this->get_invoice_date();
1149
+ }
1150
+
1151
+
1152
+ }
1153
+
1154
  endif; // class_exists
readme.txt CHANGED
@@ -1,345 +1,357 @@
1
- === WooCommerce PDF Invoices & Packing Slips ===
2
- Contributors: pomegranate
3
- Donate link: https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/
4
- Tags: woocommerce, pdf, invoices, packing slips, print, delivery notes, invoice, packing slip, export, email, bulk, automatic
5
- Requires at least: 3.5
6
- Tested up to: 5.1
7
- Requires PHP: 5.3
8
- Stable tag: 2.2.12
9
- License: GPLv2 or later
10
- License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
-
12
- Create, print & automatically email PDF invoices & packing slips for WooCommerce orders.
13
-
14
- == Description ==
15
-
16
- This WooCommerce extension automatically adds a PDF invoice to the order confirmation emails sent out to your customers. Includes a basic template (additional templates are available from [WP Overnight](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)) as well as the possibility to modify/create your own templates. In addition, you can choose to download or print invoices and packing slips from the WooCommerce order admin.
17
-
18
- = Main features =
19
- * Automatically attach invoice PDF to WooCommerce emails of your choice
20
- * Download the PDF invoice / packing slip from the order admin page
21
- * Generate PDF invoices / packings slips in bulk
22
- * **Fully customizable** HTML/CSS invoice templates
23
- * Download invoices from the My Account page
24
- * Sequential invoice numbers - with custom formatting
25
- * **Available in: Czech, Dutch, English, Finnish, French, German, Hungarian, Italian, Japanese (see FAQ for adding custom fonts!), Norwegian, Polish, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish & Ukrainian**
26
-
27
- In addition to this, we offer several premium extensions:
28
-
29
- * Create/email PDF Proforma Invoices, Credit Notes (for Refunds), email Packing Slips & more with [WooCommerce PDF Invoices & Packing Slips Professional](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
30
- * Upload all invoices automatically to Dropbox with [WooCommerce PDF Invoices & Packing Slips to Dropbox](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-dropbox/)
31
- * Automatically send new orders or packing slips to your printer, as soon as the customer orders! [WooCommerce Automatic Order Printing](https://www.simbahosting.co.uk/s3/product/woocommerce-automatic-order-printing/?affiliates=2) (from our partners at Simba Hosting)
32
- * More advanced & stylish templates with [WooCommerce PDF Invoices & Packing Slips Premium Templates](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)
33
-
34
- = Fully customizable =
35
- In addition to a number of default settings (including a custom header/logo) and several layout fields that you can use out of the box, the plugin contains HTML/CSS based templates that allow for customization & full control over the PDF output. Copy the templates to your theme folder and you don't have to worry that your customizations will be overwritten when you update the plugin.
36
-
37
- * Insert customer header image/logo
38
- * Modify shop data / footer / disclaimer etc. on the invoices & packing slips
39
- * Select paper size (Letter or A4)
40
- * Translation ready
41
-
42
- == Installation ==
43
-
44
- = Minimum Requirements =
45
-
46
- * WooCommerce 2.2 or later
47
- * WordPress 3.5 or later
48
-
49
- = Automatic installation =
50
- Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't even need to leave your web browser. To do an automatic install of WooCommerce PDF Invoices & Packing Slips, log in to your WordPress admin panel, navigate to the Plugins menu and click Add New.
51
-
52
- In the search field type "WooCommerce PDF Invoices & Packing Slips" and click Search Plugins. You can install it by simply clicking Install Now. After clicking that link you will be asked if you're sure you want to install the plugin. Click yes and WordPress will automatically complete the installation. After installation has finished, click the 'activate plugin' link.
53
-
54
- = Manual installation via the WordPress interface =
55
- 1. Download the plugin zip file to your computer
56
- 2. Go to the WordPress admin panel menu Plugins > Add New
57
- 3. Choose upload
58
- 4. Upload the plugin zip file, the plugin will now be installed
59
- 5. After installation has finished, click the 'activate plugin' link
60
-
61
- = Manual installation via FTP =
62
- 1. Download the plugin file to your computer and unzip it
63
- 2. Using an FTP program, or your hosting control panel, upload the unzipped plugin folder to your WordPress installation's wp-content/plugins/ directory.
64
- 3. Activate the plugin from the Plugins menu within the WordPress admin.
65
-
66
- == Frequently Asked Questions ==
67
-
68
- = Where can I find the documentation? =
69
-
70
- [WooCommerce PDF Invoices & Packing Slips documentation](http://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/)
71
-
72
- = It's not working! =
73
-
74
- Check out our step by step diagnostic instructions here: https://wordpress.org/support/topic/read-this-first-9/
75
-
76
-
77
-
78
-
79
-
80
- = Where can I find more templates? =
81
-
82
- Go to [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/) to checkout more templates! These include templates with more tax details and product thumbnails. Need a custom templates? Contact us at support@wpovernight.com for more information.
83
-
84
- = Can I create/send a proforma invoice or a credit note? =
85
- This is a feature of our Professional extension, which can be found at [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
86
-
87
- = Can I contribute to the code? =
88
- You're more than welcome! This plugin is hosted on github, where you can post issues or make pull requests.
89
- https://github.com/wpovernight/woocommerce-pdf-invoices-packing-slips
90
-
91
- = How can I display the HTML/CSS source for debugging/developing templates? =
92
- There's a setting on the Status tab of the settings page that allows you to toggle HTML output. Don't forget to turn if off after you're done testing!
93
-
94
-
95
- == Screenshots ==
96
-
97
- 1. Simple invoice PDF
98
- 2. Simple packing slip PDF
99
- 3. Quickly print individual invoices or packing slips from the order list
100
- 4. Print invoices or packing slips in bulk
101
- 5. Attach invoices to any WooCommerce email
102
- 6. Set shop name, address, header logo, etc.
103
-
104
- == Changelog ==
105
-
106
- = 2.2.12 =
107
- * Tested up to WC3.6
108
- * Fix: Prevent infinite loop on temporary folder creation for partially migrated sites or write permission issues
109
- * Tweak: Removed height & width attributes from logo image (+filter `wpo_wcpdf_header_logo_img_element`)
110
- * Dev: Enable guest access to PDF with order key in URL
111
-
112
- = 2.2.11 =
113
- * Fix: Fatal error on orders with multiple refunds
114
-
115
- = 2.2.10 =
116
- * Fix: Possible conflict with latest Subscriptions
117
- * Fix: Load correct translations when admin user profile language is set to different locale
118
- * Fix: Use file lock to prevent parallel processes creating the same attachment file
119
- * Fix: Prevent notices for incorrectly loaded email classes
120
- * Feature: Allow different invoice number column sorting methods by filter
121
- * Feature: Filter for global prevention of creating specific document (`wpo_wcpdf_document_is_allowed`)
122
-
123
- = 2.2.9 =
124
- * Feature: Added customer note email to attachment options
125
- * Fix: Prevent empty invoice dates from being saved as 1970 (fallback to current date/time)
126
-
127
- = 2.2.8 =
128
- * Tested up to WP5.1
129
- * Tweak: Re-use attachment file if not older than 60 seconds (tentative fix for parallel read & write issues)
130
- * Dev: Added URL overrides to switch between output mode (`&output=html`) and debug (`&debug=true`)
131
-
132
- = 2.2.7 =
133
- * Fix: Hardened permissions & security checks on several admin actions (audit by pluginvulnerabilities.com)
134
- * Feature: Show checkmarks for existing documents on order details page buttons too
135
- * Tweak: Product Bundles compatibility, hide items by default, following bundle settings (Simple Template)
136
- * Tweak: Fallback to billing address on packing slip for orders without shipping address
137
-
138
- = 2.2.6 =
139
- * Fix: ship to different address check for empty shipping addresses
140
- * Fix: Fix notice when using invoice number by plugin
141
- * Fix: Underline position
142
- * Fix: PHP 7.3 compatibility
143
- * Tweak: Updated dompdf to 0.8.3
144
- * Tweak: move admin menu item to the end of WooCommerce menu
145
- * Tweak: pass document object to paper format & orientation filters
146
-
147
- = 2.2.5 =
148
- * Feature: Check marks to indicate whether a document exists
149
- * Feature: Test mode to automatically apply updated settings to existing documents
150
- * Feature: Admin bar indicator for debug mode setting
151
- * Fix: always use latest email settings
152
- * Fix: WooCommerce Composit Products item name compatibility
153
- * Fix: Use woocommerce_thumbnail for WC3.3+
154
- * Tweak: apply woocommerce_order_item_name filter (fixes compatibility with WooCommerce Product Addons 3.0)
155
- * Tweak: Use WooCommerce date format instead of WP date format
156
-
157
- = 2.2.4 =
158
- * Fix: excluding some display options from historical settings
159
- * Fix: fix notices when requesting properties as custom fields (in a custom template)
160
-
161
- = 2.2.3 =
162
- * Fix: issues reading shop settings
163
-
164
- = 2.2.2 =
165
- * Feature: Added option to always use most current settings for the invoice
166
- * Fix: Double check for empty document numbers on initialization
167
- * New filter: `wpo_wcpdf_output_format` to set output per document type
168
-
169
- = 2.2.1 =
170
- * Fix: potential number formatting issues with `wpo_wcpdf_raw_document_number` filter
171
- * Fix: prevent direct loading of template files
172
-
173
- = 2.2.0 =
174
- * Feature: Document settings are now saved per order - changing settings after a PDF has been created will no longer affect the output
175
- * Feature: Button to delete invoice or packing slip
176
- * Feature: Better error handling and logging via WC Logger (WooCommerce > Status > Logs)
177
- * Fix: Broader payment gateway compatibility (lower priority for documents initialization)
178
- * Fix: undefined variable in construct when loading document programmatically (props to Christopher)
179
- * Fix: compatibility with renamed WooCommerce plugins (settings page detection)
180
- * Tweak: Reload translations before creating attachment
181
- * Translations: Updated translations POT
182
-
183
- = 2.1.10 =
184
- * Feature: Include invoice number and date in WooCommerce data remover and exporter
185
- * Fix: Row class for Chained Products compatibility
186
- * Fix: Improved compatibility with Advanced Custom Fields
187
- * Fix: Setting for diabling for free invoices should be applied even when other plugins are applying rules
188
-
189
- = 2.1.9 =
190
- * Feature: Automatic cleanup of temporary attachments folder (settings in Status tab)
191
- * Fix: prevent infinite loop on sites without uploads folder
192
- * Fix: tag replacements for externally hosted images (CDN)
193
-
194
- = 2.1.8 =
195
- * Fix: Fatal error on PHP 5.X
196
-
197
- = 2.1.7 =
198
- * Feature: add [order_number] placeholder for number format
199
- * Feature: $order and $order_id variables now available directly template (without needing the document object)
200
- * Feature: add actions before & after addresses
201
- * Fix: Sorting orders by invoice number
202
- * Fix: Aelia Currency Switcher - use decimal & Thousand separator settings
203
- * Fix: fix jquery migrate warnings for media upload script
204
- * Tweak: add calculated tax rate to item data
205
-
206
- = 2.1.6 =
207
- * Fix: Extended currency symbol setting for WooCommerce Currency Switcher by realmag777
208
- * Fix: Apply WooCommerce decimal settings to tax rates with decimals
209
- * Tweak: Pass document object to `wpo_wcpdf_email_attachment` filter
210
-
211
- = 2.1.5 =
212
- * Feature: Filter for number store table (wpo_wcpdf_number_store_table_name)
213
- * Fix: prevent accessing order properties as custom field/order meta
214
- * Fix: prevent wrong application of wpo_wcpdf_filename filter
215
- * Fix: Improved tax rate calculation fallback
216
-
217
- = 2.1.4 =
218
- * Fix: WooCommerce 3.3 action buttons
219
- * Feature: Added row classes for WooCommerce Composite Products
220
-
221
- = 2.1.3 =
222
- * Fix: Fatal PHP error on My Account page.
223
-
224
- = 2.1.2 =
225
- * Feature: New action wpo_wcpdf_init_document
226
- * Fix: Use title getters for my-account and backend buttons
227
- * Fix: Legacy Premium Templates reference
228
- * Tweak: Skip documents overview in settings, default to invoice
229
-
230
- = 2.1.1 =
231
- * Fix: WooCommerce Order Status & Actions Manager emails compatibility
232
- * Feature: sort orders by invoice number column
233
- * Tweak: pass document object to title filters
234
- * Tweak: use title getter in template files (instead of title string)
235
-
236
- = 2.1.0 =
237
- * Feature: WooCommerce Order Status & Actions Manager emails compatibility
238
- * Fix: Better url fallback for images stored in cloud
239
- * Update: dompdf library updated to 0.8.2 - DOMDocument parser set to default again
240
-
241
- = 2.0.15 =
242
- * Fix: Prevent saving invoice number/date from order details page when not edited
243
-
244
- = 2.0.14 =
245
- * Feature: Manually resend specific order emails in WooCommerce 3.2+
246
- * Tweak: Show full size logo preview in settings
247
- * Tweak: Custom field fallback to underscore prefixed meta key
248
- * Dev: added `wpo_wcpdf_before_sequential_number_increment` action
249
-
250
- = 2.0.13 =
251
- * Fix: Minor XSS issue on settings screens by escaping and sanitizing 'tab' & 'section' GET variables. Discovered by Detectify.
252
- * Fix: Pakistani Rupee Symbol
253
- * Feature: Automatically enable extended currency symbol support for currencies not supported by Open Sans
254
- * Dev: added `wpo_wcpdf_document_number_settings` filter
255
-
256
- = 2.0.12 =
257
- * Option: Use different HTML parser (debug settings)
258
-
259
- = 2.0.11 =
260
- * Fix: Improved fonts update routine (now preserves custom fonts)
261
- * Fix: Enable HTML5 parser by default (fixes issues with libxml)
262
- * Tweak: Show both PHP & WP Memory limit in Status tab
263
-
264
- = 2.0.10 =
265
- * Fix: Set invoice number backend button
266
- * Fix: Thumbail paths
267
- * Tweak: Make dompdf options filterable
268
-
269
- = 2.0.9 =
270
- * Feature: use `[invoice_date="ymd"]` in invoice number prefix or suffix to include a specific date format in the invoice number
271
- * Fix: Postmeta table prefix for invoice counter
272
- * Fix: 0% tax rates
273
-
274
- = 2.0.8 =
275
- * Feature: Add support for Bedrock / alternative folder structures
276
- * Dev: Filter for merged documents
277
- * Fix: Better attributes fallback for product variations
278
-
279
- = 2.0.7 =
280
- * Feature: Added button to delete legacy settings
281
- * Feature: Option to enable font subsetting
282
- * Fix: Invoice number sequence for databases with alternative auto_increment_increment settings
283
- * Fix: Fallback function for MB String (mb_stripos)
284
-
285
- = 2.0.6 =
286
- * Feature: Improved third party invoice number filters (`wpo_wcpdf_external_invoice_number_enabled` & `wpo_wcpdf_external_invoice_number`)
287
- * Fix: Underline position for Open Sans font
288
- * Fix: Invoice number auto_increment for servers that restarted frequently
289
- * Fix: Dompdf log file location (preventing open base_dir notices breaking PDF header)
290
- * Fix: 1.6.6 Settings migration duplicates merging
291
- * Tweak: Clear fonts folder when manually reinstalling fonts
292
-
293
- = 2.0.5 =
294
- * Feature: Remove temporary files (Status tab)
295
- * Fix: Page number replacement
296
- * Tweak: Fallback functions for MB String extension
297
- * Tweak: Improved wpo_wcpdf_check_privs usability for my account privileges
298
- * Legacy support: added wc_price alias for format_price method in document
299
-
300
- = 2.0.4 =
301
- * Fix: Apply filters for custom invoice number formatting in document too
302
- * Fix: Parent fallback for missing dates from refunds
303
-
304
- = 2.0.3 =
305
- * Fix: Better support for legacy invoice number filter (`wpo_wcpdf_invoice_number` - replaced by `wpo_wcpdf_formatted_document_number`)
306
- * Fix: Document number formatting fallback to order date if no document date available
307
- * Fix: Updated classmap: PSR loading didn't work on some installations
308
- * Fix: Prevent order notes from all orders showing when document is not loaded properly in filter
309
- * Tweak: Disable deprecation notices during email sending
310
- * Tweak: ignore outdated language packs
311
-
312
- = 2.0.2 =
313
- * Fix: order notes using correct order_id
314
- * Fix: WC3.0 deprecation notice for currency
315
- * Fix: Avoid crashing on PHP5.2 and older
316
- * Fix: Only use PHP MB String when present
317
- * Fix: Remote images
318
- * Fix: Download option
319
-
320
- = 2.0.1 =
321
- * Fix: PHP 5.4 issue
322
-
323
- = 2.0.0 =
324
- * New: Better structured & more advanced settings for documents
325
- * New: Option to enable & disable Packing Slips or Invoices
326
- * New: Invoice number sequence stored separately for improved speed & performance
327
- * New: Completely rewritten codebase for more flexibility & better reliability
328
- * New: Updated PDF library to DOMPDF 0.8
329
- * New: PDF Library made pluggable (by using the `wpo_wcpdf_pdf_maker` filter)
330
- * New: lots of new functions & filters to allow developers to hook into the plugin
331
- * Changed: **$wpo_wcpdf variable is now deprecated** (legacy mode available & automatically enabled on update)
332
- * Fix: Improved PHP 7 & 7.1 support
333
- * Fix: Positive prices for refunds
334
- * Fix: Use parent for attributes retrieved for product variations
335
- * Fix: Set content type to PDF for download
336
-
337
- = 1.6.6 =
338
- * Feature: Facilitate downgrading from 2.0 (re-installing fonts & resetting version)
339
- * Fix: Update currencies font (added Georgian Lari)
340
- * Translations: Added Indonesian
341
-
342
- == Upgrade Notice ==
343
-
344
- = 2.1.10 =
 
 
 
 
 
 
 
 
 
 
 
 
345
  2.X is a BIG update! Make a full site backup before upgrading if you were using version 1.X!
1
+ === WooCommerce PDF Invoices & Packing Slips ===
2
+ Contributors: pomegranate
3
+ Donate link: https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/
4
+ Tags: woocommerce, pdf, invoices, packing slips, print, delivery notes, invoice, packing slip, export, email, bulk, automatic
5
+ Requires at least: 3.5
6
+ Tested up to: 5.2
7
+ Requires PHP: 5.3
8
+ Stable tag: 2.2.14
9
+ License: GPLv2 or later
10
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
+
12
+ Create, print & automatically email PDF invoices & packing slips for WooCommerce orders.
13
+
14
+ == Description ==
15
+
16
+ This WooCommerce extension automatically adds a PDF invoice to the order confirmation emails sent out to your customers. Includes a basic template (additional templates are available from [WP Overnight](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)) as well as the possibility to modify/create your own templates. In addition, you can choose to download or print invoices and packing slips from the WooCommerce order admin.
17
+
18
+ = Main features =
19
+ * Automatically attach invoice PDF to WooCommerce emails of your choice
20
+ * Download the PDF invoice / packing slip from the order admin page
21
+ * Generate PDF invoices / packings slips in bulk
22
+ * **Fully customizable** HTML/CSS invoice templates
23
+ * Download invoices from the My Account page
24
+ * Sequential invoice numbers - with custom formatting
25
+ * **Available in: Czech, Dutch, English, Finnish, French, German, Hungarian, Italian, Japanese (see FAQ for adding custom fonts!), Norwegian, Polish, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish & Ukrainian**
26
+
27
+ In addition to this, we offer several premium extensions:
28
+
29
+ * Create/email PDF Proforma Invoices, Credit Notes (for Refunds), email Packing Slips & more with [WooCommerce PDF Invoices & Packing Slips Professional](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
30
+ * Upload all invoices automatically to Dropbox with [WooCommerce PDF Invoices & Packing Slips to Dropbox](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-dropbox/)
31
+ * Automatically send new orders or packing slips to your printer, as soon as the customer orders! [WooCommerce Automatic Order Printing](https://www.simbahosting.co.uk/s3/product/woocommerce-automatic-order-printing/?affiliates=2) (from our partners at Simba Hosting)
32
+ * More advanced & stylish templates with [WooCommerce PDF Invoices & Packing Slips Premium Templates](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)
33
+
34
+ = Fully customizable =
35
+ In addition to a number of default settings (including a custom header/logo) and several layout fields that you can use out of the box, the plugin contains HTML/CSS based templates that allow for customization & full control over the PDF output. Copy the templates to your theme folder and you don't have to worry that your customizations will be overwritten when you update the plugin.
36
+
37
+ * Insert customer header image/logo
38
+ * Modify shop data / footer / disclaimer etc. on the invoices & packing slips
39
+ * Select paper size (Letter or A4)
40
+ * Translation ready
41
+
42
+ == Installation ==
43
+
44
+ = Minimum Requirements =
45
+
46
+ * WooCommerce 2.2 or later
47
+ * WordPress 3.5 or later
48
+
49
+ = Automatic installation =
50
+ Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't even need to leave your web browser. To do an automatic install of WooCommerce PDF Invoices & Packing Slips, log in to your WordPress admin panel, navigate to the Plugins menu and click Add New.
51
+
52
+ In the search field type "WooCommerce PDF Invoices & Packing Slips" and click Search Plugins. You can install it by simply clicking Install Now. After clicking that link you will be asked if you're sure you want to install the plugin. Click yes and WordPress will automatically complete the installation. After installation has finished, click the 'activate plugin' link.
53
+
54
+ = Manual installation via the WordPress interface =
55
+ 1. Download the plugin zip file to your computer
56
+ 2. Go to the WordPress admin panel menu Plugins > Add New
57
+ 3. Choose upload
58
+ 4. Upload the plugin zip file, the plugin will now be installed
59
+ 5. After installation has finished, click the 'activate plugin' link
60
+
61
+ = Manual installation via FTP =
62
+ 1. Download the plugin file to your computer and unzip it
63
+ 2. Using an FTP program, or your hosting control panel, upload the unzipped plugin folder to your WordPress installation's wp-content/plugins/ directory.
64
+ 3. Activate the plugin from the Plugins menu within the WordPress admin.
65
+
66
+ == Frequently Asked Questions ==
67
+
68
+ = Where can I find the documentation? =
69
+
70
+ [WooCommerce PDF Invoices & Packing Slips documentation](http://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/)
71
+
72
+ = It's not working! =
73
+
74
+ Check out our step by step diagnostic instructions here: https://wordpress.org/support/topic/read-this-first-9/
75
+
76
+
77
+
78
+
79
+
80
+ = Where can I find more templates? =
81
+
82
+ Go to [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/) to checkout more templates! These include templates with more tax details and product thumbnails. Need a custom templates? Contact us at support@wpovernight.com for more information.
83
+
84
+ = Can I create/send a proforma invoice or a credit note? =
85
+ This is a feature of our Professional extension, which can be found at [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
86
+
87
+ = Can I contribute to the code? =
88
+ You're more than welcome! This plugin is hosted on github, where you can post issues or make pull requests.
89
+ https://github.com/wpovernight/woocommerce-pdf-invoices-packing-slips
90
+
91
+ = How can I display the HTML/CSS source for debugging/developing templates? =
92
+ There's a setting on the Status tab of the settings page that allows you to toggle HTML output. Don't forget to turn if off after you're done testing!
93
+
94
+
95
+ == Screenshots ==
96
+
97
+ 1. Simple invoice PDF
98
+ 2. Simple packing slip PDF
99
+ 3. Quickly print individual invoices or packing slips from the order list
100
+ 4. Print invoices or packing slips in bulk
101
+ 5. Attach invoices to any WooCommerce email
102
+ 6. Set shop name, address, header logo, etc.
103
+
104
+ == Changelog ==
105
+
106
+ = 2.2.14 =
107
+ * Fix: Set default PHPMailer validator to 'php' (fixing 'setFrom' errors on PHP 7.3)
108
+ * Fix: Attachment path for file lock check
109
+ * Tweak: Don't wait for file lock if locking disabled
110
+ * Tweak: JIT loading of core documents for early requests (before init 15)
111
+
112
+ = 2.2.13 =
113
+ * Feature: Better order notes formatting & optional filter for system notes
114
+ * Feature: add email object to attachment hook and allow order object filtering
115
+ * Fix: WooCommerce Chained Products row classes
116
+ * Fix: Issues with locked attachment files preventing the email from being sent correctly
117
+
118
+ = 2.2.12 =
119
+ * Tested up to WC3.6
120
+ * Fix: Prevent infinite loop on temporary folder creation for partially migrated sites or write permission issues
121
+ * Tweak: Removed height & width attributes from logo image (+filter `wpo_wcpdf_header_logo_img_element`)
122
+ * Dev: Enable guest access to PDF with order key in URL
123
+
124
+ = 2.2.11 =
125
+ * Fix: Fatal error on orders with multiple refunds
126
+
127
+ = 2.2.10 =
128
+ * Fix: Possible conflict with latest Subscriptions
129
+ * Fix: Load correct translations when admin user profile language is set to different locale
130
+ * Fix: Use file lock to prevent parallel processes creating the same attachment file
131
+ * Fix: Prevent notices for incorrectly loaded email classes
132
+ * Feature: Allow different invoice number column sorting methods by filter
133
+ * Feature: Filter for global prevention of creating specific document (`wpo_wcpdf_document_is_allowed`)
134
+
135
+ = 2.2.9 =
136
+ * Feature: Added customer note email to attachment options
137
+ * Fix: Prevent empty invoice dates from being saved as 1970 (fallback to current date/time)
138
+
139
+ = 2.2.8 =
140
+ * Tested up to WP5.1
141
+ * Tweak: Re-use attachment file if not older than 60 seconds (tentative fix for parallel read & write issues)
142
+ * Dev: Added URL overrides to switch between output mode (`&output=html`) and debug (`&debug=true`)
143
+
144
+ = 2.2.7 =
145
+ * Fix: Hardened permissions & security checks on several admin actions (audit by pluginvulnerabilities.com)
146
+ * Feature: Show checkmarks for existing documents on order details page buttons too
147
+ * Tweak: Product Bundles compatibility, hide items by default, following bundle settings (Simple Template)
148
+ * Tweak: Fallback to billing address on packing slip for orders without shipping address
149
+
150
+ = 2.2.6 =
151
+ * Fix: ship to different address check for empty shipping addresses
152
+ * Fix: Fix notice when using invoice number by plugin
153
+ * Fix: Underline position
154
+ * Fix: PHP 7.3 compatibility
155
+ * Tweak: Updated dompdf to 0.8.3
156
+ * Tweak: move admin menu item to the end of WooCommerce menu
157
+ * Tweak: pass document object to paper format & orientation filters
158
+
159
+ = 2.2.5 =
160
+ * Feature: Check marks to indicate whether a document exists
161
+ * Feature: Test mode to automatically apply updated settings to existing documents
162
+ * Feature: Admin bar indicator for debug mode setting
163
+ * Fix: always use latest email settings
164
+ * Fix: WooCommerce Composit Products item name compatibility
165
+ * Fix: Use woocommerce_thumbnail for WC3.3+
166
+ * Tweak: apply woocommerce_order_item_name filter (fixes compatibility with WooCommerce Product Addons 3.0)
167
+ * Tweak: Use WooCommerce date format instead of WP date format
168
+
169
+ = 2.2.4 =
170
+ * Fix: excluding some display options from historical settings
171
+ * Fix: fix notices when requesting properties as custom fields (in a custom template)
172
+
173
+ = 2.2.3 =
174
+ * Fix: issues reading shop settings
175
+
176
+ = 2.2.2 =
177
+ * Feature: Added option to always use most current settings for the invoice
178
+ * Fix: Double check for empty document numbers on initialization
179
+ * New filter: `wpo_wcpdf_output_format` to set output per document type
180
+
181
+ = 2.2.1 =
182
+ * Fix: potential number formatting issues with `wpo_wcpdf_raw_document_number` filter
183
+ * Fix: prevent direct loading of template files
184
+
185
+ = 2.2.0 =
186
+ * Feature: Document settings are now saved per order - changing settings after a PDF has been created will no longer affect the output
187
+ * Feature: Button to delete invoice or packing slip
188
+ * Feature: Better error handling and logging via WC Logger (WooCommerce > Status > Logs)
189
+ * Fix: Broader payment gateway compatibility (lower priority for documents initialization)
190
+ * Fix: undefined variable in construct when loading document programmatically (props to Christopher)
191
+ * Fix: compatibility with renamed WooCommerce plugins (settings page detection)
192
+ * Tweak: Reload translations before creating attachment
193
+ * Translations: Updated translations POT
194
+
195
+ = 2.1.10 =
196
+ * Feature: Include invoice number and date in WooCommerce data remover and exporter
197
+ * Fix: Row class for Chained Products compatibility
198
+ * Fix: Improved compatibility with Advanced Custom Fields
199
+ * Fix: Setting for diabling for free invoices should be applied even when other plugins are applying rules
200
+
201
+ = 2.1.9 =
202
+ * Feature: Automatic cleanup of temporary attachments folder (settings in Status tab)
203
+ * Fix: prevent infinite loop on sites without uploads folder
204
+ * Fix: tag replacements for externally hosted images (CDN)
205
+
206
+ = 2.1.8 =
207
+ * Fix: Fatal error on PHP 5.X
208
+
209
+ = 2.1.7 =
210
+ * Feature: add [order_number] placeholder for number format
211
+ * Feature: $order and $order_id variables now available directly template (without needing the document object)
212
+ * Feature: add actions before & after addresses
213
+ * Fix: Sorting orders by invoice number
214
+ * Fix: Aelia Currency Switcher - use decimal & Thousand separator settings
215
+ * Fix: fix jquery migrate warnings for media upload script
216
+ * Tweak: add calculated tax rate to item data
217
+
218
+ = 2.1.6 =
219
+ * Fix: Extended currency symbol setting for WooCommerce Currency Switcher by realmag777
220
+ * Fix: Apply WooCommerce decimal settings to tax rates with decimals
221
+ * Tweak: Pass document object to `wpo_wcpdf_email_attachment` filter
222
+
223
+ = 2.1.5 =
224
+ * Feature: Filter for number store table (wpo_wcpdf_number_store_table_name)
225
+ * Fix: prevent accessing order properties as custom field/order meta
226
+ * Fix: prevent wrong application of wpo_wcpdf_filename filter
227
+ * Fix: Improved tax rate calculation fallback
228
+
229
+ = 2.1.4 =
230
+ * Fix: WooCommerce 3.3 action buttons
231
+ * Feature: Added row classes for WooCommerce Composite Products
232
+
233
+ = 2.1.3 =
234
+ * Fix: Fatal PHP error on My Account page.
235
+
236
+ = 2.1.2 =
237
+ * Feature: New action wpo_wcpdf_init_document
238
+ * Fix: Use title getters for my-account and backend buttons
239
+ * Fix: Legacy Premium Templates reference
240
+ * Tweak: Skip documents overview in settings, default to invoice
241
+
242
+ = 2.1.1 =
243
+ * Fix: WooCommerce Order Status & Actions Manager emails compatibility
244
+ * Feature: sort orders by invoice number column
245
+ * Tweak: pass document object to title filters
246
+ * Tweak: use title getter in template files (instead of title string)
247
+
248
+ = 2.1.0 =
249
+ * Feature: WooCommerce Order Status & Actions Manager emails compatibility
250
+ * Fix: Better url fallback for images stored in cloud
251
+ * Update: dompdf library updated to 0.8.2 - DOMDocument parser set to default again
252
+
253
+ = 2.0.15 =
254
+ * Fix: Prevent saving invoice number/date from order details page when not edited
255
+
256
+ = 2.0.14 =
257
+ * Feature: Manually resend specific order emails in WooCommerce 3.2+
258
+ * Tweak: Show full size logo preview in settings
259
+ * Tweak: Custom field fallback to underscore prefixed meta key
260
+ * Dev: added `wpo_wcpdf_before_sequential_number_increment` action
261
+
262
+ = 2.0.13 =
263
+ * Fix: Minor XSS issue on settings screens by escaping and sanitizing 'tab' & 'section' GET variables. Discovered by Detectify.
264
+ * Fix: Pakistani Rupee Symbol
265
+ * Feature: Automatically enable extended currency symbol support for currencies not supported by Open Sans
266
+ * Dev: added `wpo_wcpdf_document_number_settings` filter
267
+
268
+ = 2.0.12 =
269
+ * Option: Use different HTML parser (debug settings)
270
+
271
+ = 2.0.11 =
272
+ * Fix: Improved fonts update routine (now preserves custom fonts)
273
+ * Fix: Enable HTML5 parser by default (fixes issues with libxml)
274
+ * Tweak: Show both PHP & WP Memory limit in Status tab
275
+
276
+ = 2.0.10 =
277
+ * Fix: Set invoice number backend button
278
+ * Fix: Thumbail paths
279
+ * Tweak: Make dompdf options filterable
280
+
281
+ = 2.0.9 =
282
+ * Feature: use `[invoice_date="ymd"]` in invoice number prefix or suffix to include a specific date format in the invoice number
283
+ * Fix: Postmeta table prefix for invoice counter
284
+ * Fix: 0% tax rates
285
+
286
+ = 2.0.8 =
287
+ * Feature: Add support for Bedrock / alternative folder structures
288
+ * Dev: Filter for merged documents
289
+ * Fix: Better attributes fallback for product variations
290
+
291
+ = 2.0.7 =
292
+ * Feature: Added button to delete legacy settings
293
+ * Feature: Option to enable font subsetting
294
+ * Fix: Invoice number sequence for databases with alternative auto_increment_increment settings
295
+ * Fix: Fallback function for MB String (mb_stripos)
296
+
297
+ = 2.0.6 =
298
+ * Feature: Improved third party invoice number filters (`wpo_wcpdf_external_invoice_number_enabled` & `wpo_wcpdf_external_invoice_number`)
299
+ * Fix: Underline position for Open Sans font
300
+ * Fix: Invoice number auto_increment for servers that restarted frequently
301
+ * Fix: Dompdf log file location (preventing open base_dir notices breaking PDF header)
302
+ * Fix: 1.6.6 Settings migration duplicates merging
303
+ * Tweak: Clear fonts folder when manually reinstalling fonts
304
+
305
+ = 2.0.5 =
306
+ * Feature: Remove temporary files (Status tab)
307
+ * Fix: Page number replacement
308
+ * Tweak: Fallback functions for MB String extension
309
+ * Tweak: Improved wpo_wcpdf_check_privs usability for my account privileges
310
+ * Legacy support: added wc_price alias for format_price method in document
311
+
312
+ = 2.0.4 =
313
+ * Fix: Apply filters for custom invoice number formatting in document too
314
+ * Fix: Parent fallback for missing dates from refunds
315
+
316
+ = 2.0.3 =
317
+ * Fix: Better support for legacy invoice number filter (`wpo_wcpdf_invoice_number` - replaced by `wpo_wcpdf_formatted_document_number`)
318
+ * Fix: Document number formatting fallback to order date if no document date available
319
+ * Fix: Updated classmap: PSR loading didn't work on some installations
320
+ * Fix: Prevent order notes from all orders showing when document is not loaded properly in filter
321
+ * Tweak: Disable deprecation notices during email sending
322
+ * Tweak: ignore outdated language packs
323
+
324
+ = 2.0.2 =
325
+ * Fix: order notes using correct order_id
326
+ * Fix: WC3.0 deprecation notice for currency
327
+ * Fix: Avoid crashing on PHP5.2 and older
328
+ * Fix: Only use PHP MB String when present
329
+ * Fix: Remote images
330
+ * Fix: Download option
331
+
332
+ = 2.0.1 =
333
+ * Fix: PHP 5.4 issue
334
+
335
+ = 2.0.0 =
336
+ * New: Better structured & more advanced settings for documents
337
+ * New: Option to enable & disable Packing Slips or Invoices
338
+ * New: Invoice number sequence stored separately for improved speed & performance
339
+ * New: Completely rewritten codebase for more flexibility & better reliability
340
+ * New: Updated PDF library to DOMPDF 0.8
341
+ * New: PDF Library made pluggable (by using the `wpo_wcpdf_pdf_maker` filter)
342
+ * New: lots of new functions & filters to allow developers to hook into the plugin
343
+ * Changed: **$wpo_wcpdf variable is now deprecated** (legacy mode available & automatically enabled on update)
344
+ * Fix: Improved PHP 7 & 7.1 support
345
+ * Fix: Positive prices for refunds
346
+ * Fix: Use parent for attributes retrieved for product variations
347
+ * Fix: Set content type to PDF for download
348
+
349
+ = 1.6.6 =
350
+ * Feature: Facilitate downgrading from 2.0 (re-installing fonts & resetting version)
351
+ * Fix: Update currencies font (added Georgian Lari)
352
+ * Translations: Added Indonesian
353
+
354
+ == Upgrade Notice ==
355
+
356
+ = 2.1.10 =
357
  2.X is a BIG update! Make a full site backup before upgrading if you were using version 1.X!
woocommerce-pdf-invoices-packingslips.php CHANGED
@@ -1,359 +1,359 @@
1
- <?php
2
- /**
3
- * Plugin Name: WooCommerce PDF Invoices & Packing Slips
4
- * Plugin URI: http://www.wpovernight.com
5
- * Description: Create, print & email PDF invoices & packing slips for WooCommerce orders.
6
- * Version: 2.2.12
7
- * Author: Ewout Fernhout
8
- * Author URI: http://www.wpovernight.com
9
- * License: GPLv2 or later
10
- * License URI: http://www.opensource.org/licenses/gpl-license.php
11
- * Text Domain: woocommerce-pdf-invoices-packing-slips
12
- * WC requires at least: 2.2.0
13
- * WC tested up to: 3.6.0
14
- */
15
-
16
- if ( ! defined( 'ABSPATH' ) ) {
17
- exit; // Exit if accessed directly
18
- }
19
-
20
- if ( !class_exists( 'WPO_WCPDF' ) ) :
21
-
22
- class WPO_WCPDF {
23
-
24
- public $version = '2.2.12';
25
- public $plugin_basename;
26
- public $legacy_mode;
27
-
28
- protected static $_instance = null;
29
-
30
- /**
31
- * Main Plugin Instance
32
- *
33
- * Ensures only one instance of plugin is loaded or can be loaded.
34
- */
35
- public static function instance() {
36
- if ( is_null( self::$_instance ) ) {
37
- self::$_instance = new self();
38
- }
39
- return self::$_instance;
40
- }
41
-
42
- /**
43
- * Constructor
44
- */
45
- public function __construct() {
46
- $this->plugin_basename = plugin_basename(__FILE__);
47
-
48
- $this->define( 'WPO_WCPDF_VERSION', $this->version );
49
-
50
- // load the localisation & classes
51
- add_action( 'plugins_loaded', array( $this, 'translations' ) );
52
- add_filter( 'load_textdomain_mofile', array( $this, 'textdomain_fallback' ), 10, 2 );
53
- add_action( 'plugins_loaded', array( $this, 'load_classes' ), 9 );
54
- add_action( 'in_plugin_update_message-'.$this->plugin_basename, array( $this, 'in_plugin_update_message' ) );
55
- }
56
-
57
- /**
58
- * Define constant if not already set
59
- * @param string $name
60
- * @param string|bool $value
61
- */
62
- private function define( $name, $value ) {
63
- if ( ! defined( $name ) ) {
64
- define( $name, $value );
65
- }
66
- }
67
-
68
-
69
- /**
70
- * Load the translation / textdomain files
71
- *
72
- * Note: the first-loaded translation file overrides any following ones if the same translation is present
73
- */
74
- public function translations() {
75
- $locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
76
- $locale = apply_filters( 'plugin_locale', $locale, 'woocommerce-pdf-invoices-packing-slips' );
77
- $dir = trailingslashit( WP_LANG_DIR );
78
-
79
- $textdomains = array( 'woocommerce-pdf-invoices-packing-slips' );
80
- if ( $this->legacy_mode_enabled() === true ) {
81
- $textdomains[] = 'wpo_wcpdf';
82
- }
83
-
84
- /**
85
- * Frontend/global Locale. Looks in:
86
- *
87
- * - WP_LANG_DIR/woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
88
- * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
89
- * - woocommerce-pdf-invoices-packing-slips-pro/languages/woocommerce-pdf-invoices-packing-slips-LOCALE.mo (which if not found falls back to:)
90
- * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
91
- */
92
- foreach ( $textdomains as $textdomain ) {
93
- unload_textdomain( $textdomain );
94
- load_textdomain( $textdomain, $dir . 'woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
95
- load_textdomain( $textdomain, $dir . 'plugins/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
96
- load_plugin_textdomain( $textdomain, false, dirname( plugin_basename(__FILE__) ) . '/languages' );
97
- }
98
- }
99
-
100
- /**
101
- * Maintain backwards compatibility with old translation files
102
- * Uses old .mo file if it exists in any of the override locations
103
- */
104
- public function textdomain_fallback( $mofile, $textdomain ) {
105
- $plugin_domain = 'woocommerce-pdf-invoices-packing-slips';
106
- $old_domain = 'wpo_wcpdf';
107
-
108
- if ($textdomain == $old_domain) {
109
- $textdomain = $plugin_domain;
110
- $mofile = str_replace( "{$old_domain}-", "{$textdomain}-", $mofile ); // with trailing dash to target file and not folder
111
- }
112
-
113
- if ( $textdomain === $plugin_domain ) {
114
- $old_mofile = str_replace( "{$textdomain}-", "{$old_domain}-", $mofile ); // with trailing dash to target file and not folder
115
- if ( file_exists( $old_mofile ) ) {
116
- // we have an old override - use it
117
- return $old_mofile;
118
- }
119
-
120
- // prevent loading outdated language packs
121
- $pofile = str_replace('.mo', '.po', $mofile);
122
- if ( file_exists( $pofile ) ) {
123
- // load po file
124
- $podata = file_get_contents($pofile);
125
- // set revision date threshold
126
- $block_before = strtotime( '2017-05-15' );
127
- // read revision date
128
- preg_match('~PO-Revision-Date: (.*?)\\\n~s',$podata,$matches);
129
- if (isset($matches[1])) {
130
- $revision_date = $matches[1];
131
- if ( $revision_timestamp = strtotime($revision_date) ) {
132
- // check if revision is before threshold date
133
- if ( $revision_timestamp < $block_before ) {
134
- // try bundled
135
- $bundled_file = $this->plugin_path() . '/languages/'. basename( $mofile );
136
- if (file_exists($bundled_file)) {
137
- return $bundled_file;
138
- } else {
139
- return '';
140
- }
141
- // delete po & mo file if possible
142
- // @unlink($pofile);
143
- // @unlink($mofile);
144
- }
145
- }
146
- }
147
- }
148
- }
149
-
150
- return $mofile;
151
- }
152
-
153
- /**
154
- * Load the main plugin classes and functions
155
- */
156
- public function includes() {
157
- // WooCommerce compatibility classes
158
- include_once( $this->plugin_path() . '/includes/compatibility/abstract-wc-data-compatibility.php' );
159
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-date-compatibility.php' );
160
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-core-compatibility.php' );
161
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-order-compatibility.php' );
162
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-product-compatibility.php' );
163
- include_once( $this->plugin_path() . '/includes/compatibility/wc-datetime-functions-compatibility.php' );
164
-
165
- // Third party compatibility
166
- include_once( $this->plugin_path() . '/includes/compatibility/class-wcpdf-compatibility-third-party-plugins.php' );
167
-
168
- // Plugin classes
169
- include_once( $this->plugin_path() . '/includes/wcpdf-functions.php' );
170
- $this->settings = include_once( $this->plugin_path() . '/includes/class-wcpdf-settings.php' );
171
- $this->documents = include_once( $this->plugin_path() . '/includes/class-wcpdf-documents.php' );
172
- $this->main = include_once( $this->plugin_path() . '/includes/class-wcpdf-main.php' );
173
- include_once( $this->plugin_path() . '/includes/class-wcpdf-assets.php' );
174
- include_once( $this->plugin_path() . '/includes/class-wcpdf-admin.php' );
175
- include_once( $this->plugin_path() . '/includes/class-wcpdf-frontend.php' );
176
- include_once( $this->plugin_path() . '/includes/class-wcpdf-install.php' );
177
-
178
- // Backwards compatibility with self
179
- include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy.php' );
180
- include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy-deprecated-hooks.php' );
181
-
182
- // PHP MB String fallback functions
183
- include_once( $this->plugin_path() . '/includes/compatibility/mb-string-compatibility.php' );
184
- }
185
-
186
-
187
- /**
188
- * Instantiate classes when woocommerce is activated
189
- */
190
- public function load_classes() {
191
- if ( $this->is_woocommerce_activated() === false ) {
192
- add_action( 'admin_notices', array ( $this, 'need_woocommerce' ) );
193
- return;
194
- }
195
-
196
- if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
197
- add_action( 'admin_notices', array ( $this, 'required_php_version' ) );
198
- return;
199
- }
200
-
201
- // all systems ready - GO!
202
- $this->includes();
203
- }
204
-
205
- /**
206
- * Check if legacy mode is enabled
207
- */
208
- public function legacy_mode_enabled() {
209
- if (!isset($this->legacy_mode)) {
210
- $debug_settings = get_option( 'wpo_wcpdf_settings_debug' );
211
- $this->legacy_mode = isset($debug_settings['legacy_mode']);
212
- }
213
- return $this->legacy_mode;
214
- }
215
-
216
-
217
- /**
218
- * Check if woocommerce is activated
219
- */
220
- public function is_woocommerce_activated() {
221
- $blog_plugins = get_option( 'active_plugins', array() );
222
- $site_plugins = is_multisite() ? (array) maybe_unserialize( get_site_option('active_sitewide_plugins' ) ) : array();
223
-
224
- if ( in_array( 'woocommerce/woocommerce.php', $blog_plugins ) || isset( $site_plugins['woocommerce/woocommerce.php'] ) ) {
225
- return true;
226
- } else {
227
- return false;
228
- }
229
- }
230
-
231
- /**
232
- * WooCommerce not active notice.
233
- *
234
- * @return string Fallack notice.
235
- */
236
-
237
- public function need_woocommerce() {
238
- $error = sprintf( __( 'WooCommerce PDF Invoices & Packing Slips requires %sWooCommerce%s to be installed & activated!' , 'woocommerce-pdf-invoices-packing-slips' ), '<a href="http://wordpress.org/extend/plugins/woocommerce/">', '</a>' );
239
-
240
- $message = '<div class="error"><p>' . $error . '</p></div>';
241
-
242
- echo $message;
243
- }
244
-
245
- /**
246
- * PHP version requirement notice
247
- */
248
-
249
- public function required_php_version() {
250
- $error = __( 'WooCommerce PDF Invoices & Packing Slips requires PHP 5.3 or higher (5.6 or higher recommended).', 'woocommerce-pdf-invoices-packing-slips' );
251
- $how_to_update = __( 'How to update your PHP version', 'woocommerce-pdf-invoices-packing-slips' );
252
- $message = sprintf('<div class="error"><p>%s</p><p><a href="%s">%s</a></p></div>', $error, 'http://docs.wpovernight.com/general/how-to-update-your-php-version/', $how_to_update);
253
-
254
- echo $message;
255
- }
256
-
257
- /**
258
- * Show plugin changes. Code adapted from W3 Total Cache.
259
- */
260
- public function in_plugin_update_message( $args ) {
261
- $transient_name = 'wpo_wcpdf_upgrade_notice_' . $args['Version'];
262
-
263
- if ( false === ( $upgrade_notice = get_transient( $transient_name ) ) ) {
264
- $response = wp_safe_remote_get( 'https://plugins.svn.wordpress.org/woocommerce-pdf-invoices-packing-slips/trunk/readme.txt' );
265
-
266
- if ( ! is_wp_error( $response ) && ! empty( $response['body'] ) ) {
267
- $upgrade_notice = self::parse_update_notice( $response['body'], $args['new_version'] );
268
- set_transient( $transient_name, $upgrade_notice, DAY_IN_SECONDS );
269
- }
270
- }
271
-
272
- echo wp_kses_post( $upgrade_notice );
273
- }
274
-
275
- /**
276
- * Parse update notice from readme file.
277
- *
278
- * @param string $content
279
- * @param string $new_version
280
- * @return string
281
- */
282
- private function parse_update_notice( $content, $new_version ) {
283
- // Output Upgrade Notice.
284
- $matches = null;
285
- $regexp = '~==\s*Upgrade Notice\s*==\s*=\s*(.*)\s*=(.*)(=\s*' . preg_quote( $new_version ) . '\s*=|$)~Uis';
286
- $upgrade_notice = '';
287
-
288
-
289
- if ( preg_match( $regexp, $content, $matches ) ) {
290
- $notices = (array) preg_split( '~[\r\n]+~', trim( $matches[2] ) );
291
-
292
- // Convert the full version strings to minor versions.
293
- $notice_version_parts = explode( '.', trim( $matches[1] ) );
294
- $current_version_parts = explode( '.', $this->version );
295
-
296
- if ( 3 !== sizeof( $notice_version_parts ) ) {
297
- return;
298
- }
299
-
300
- $notice_version = $notice_version_parts[0] . '.' . $notice_version_parts[1];
301
- $current_version = $current_version_parts[0] . '.' . $current_version_parts[1];
302
-
303
- // Check the latest stable version and ignore trunk.
304
- if ( version_compare( $current_version, $notice_version, '<' ) ) {
305
-
306
- $upgrade_notice .= '</p><p class="wpo_wcpdf_upgrade_notice">';
307
-
308
- foreach ( $notices as $index => $line ) {
309
- $upgrade_notice .= preg_replace( '~\[([^\]]*)\]\(([^\)]*)\)~', '<a href="${2}">${1}</a>', $line );
310
- }
311
- }
312
- }
313
-
314
- return wp_kses_post( $upgrade_notice );
315
- }
316
-
317
- /**
318
- * Get the plugin url.
319
- * @return string
320
- */
321
- public function plugin_url() {
322
- return untrailingslashit( plugins_url( '/', __FILE__ ) );
323
- }
324
-
325
- /**
326
- * Get the plugin path.
327
- * @return string
328
- */
329
- public function plugin_path() {
330
- return untrailingslashit( plugin_dir_path( __FILE__ ) );
331
- }
332
-
333
- } // class WPO_WCPDF
334
-
335
- endif; // class_exists
336
-
337
- /**
338
- * Returns the main instance of WooCommerce PDF Invoices & Packing Slips to prevent the need to use globals.
339
- *
340
- * @since 1.6
341
- * @return WPO_WCPDF
342
- */
343
- function WPO_WCPDF() {
344
- return WPO_WCPDF::instance();
345
- }
346
-
347
- WPO_WCPDF(); // load plugin
348
-
349
- // legacy class for plugin detecting
350
- if ( !class_exists( 'WooCommerce_PDF_Invoices' ) ) {
351
- class WooCommerce_PDF_Invoices{
352
- public static $version;
353
-
354
- public function __construct() {
355
- self::$version = WPO_WCPDF()->version;
356
- }
357
- }
358
- new WooCommerce_PDF_Invoices();
359
- }
1
+ <?php
2
+ /**
3
+ * Plugin Name: WooCommerce PDF Invoices & Packing Slips
4
+ * Plugin URI: http://www.wpovernight.com
5
+ * Description: Create, print & email PDF invoices & packing slips for WooCommerce orders.
6
+ * Version: 2.2.14
7
+ * Author: Ewout Fernhout
8
+ * Author URI: http://www.wpovernight.com
9
+ * License: GPLv2 or later
10
+ * License URI: http://www.opensource.org/licenses/gpl-license.php
11
+ * Text Domain: woocommerce-pdf-invoices-packing-slips
12
+ * WC requires at least: 2.2.0
13
+ * WC tested up to: 3.6.0
14
+ */
15
+
16
+ if ( ! defined( 'ABSPATH' ) ) {
17
+ exit; // Exit if accessed directly
18
+ }
19
+
20
+ if ( !class_exists( 'WPO_WCPDF' ) ) :
21
+
22
+ class WPO_WCPDF {
23
+
24
+ public $version = '2.2.14';
25
+ public $plugin_basename;
26
+ public $legacy_mode;
27
+
28
+ protected static $_instance = null;
29
+
30
+ /**
31
+ * Main Plugin Instance
32
+ *
33
+ * Ensures only one instance of plugin is loaded or can be loaded.
34
+ */
35
+ public static function instance() {
36
+ if ( is_null( self::$_instance ) ) {
37
+ self::$_instance = new self();
38
+ }
39
+ return self::$_instance;
40
+ }
41
+
42
+ /**
43
+ * Constructor
44
+ */
45
+ public function __construct() {
46
+ $this->plugin_basename = plugin_basename(__FILE__);
47
+
48
+ $this->define( 'WPO_WCPDF_VERSION', $this->version );
49
+
50
+ // load the localisation & classes
51
+ add_action( 'plugins_loaded', array( $this, 'translations' ) );
52
+ add_filter( 'load_textdomain_mofile', array( $this, 'textdomain_fallback' ), 10, 2 );
53
+ add_action( 'plugins_loaded', array( $this, 'load_classes' ), 9 );
54
+ add_action( 'in_plugin_update_message-'.$this->plugin_basename, array( $this, 'in_plugin_update_message' ) );
55
+ }
56
+
57
+ /**
58
+ * Define constant if not already set
59
+ * @param string $name
60
+ * @param string|bool $value
61
+ */
62
+ private function define( $name, $value ) {
63
+ if ( ! defined( $name ) ) {
64
+ define( $name, $value );
65
+ }
66
+ }
67
+
68
+
69
+ /**
70
+ * Load the translation / textdomain files
71
+ *
72
+ * Note: the first-loaded translation file overrides any following ones if the same translation is present
73
+ */
74
+ public function translations() {
75
+ $locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
76
+ $locale = apply_filters( 'plugin_locale', $locale, 'woocommerce-pdf-invoices-packing-slips' );
77
+ $dir = trailingslashit( WP_LANG_DIR );
78
+
79
+ $textdomains = array( 'woocommerce-pdf-invoices-packing-slips' );
80
+ if ( $this->legacy_mode_enabled() === true ) {
81
+ $textdomains[] = 'wpo_wcpdf';
82
+ }
83
+
84
+ /**
85
+ * Frontend/global Locale. Looks in:
86
+ *
87
+ * - WP_LANG_DIR/woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
88
+ * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
89
+ * - woocommerce-pdf-invoices-packing-slips-pro/languages/woocommerce-pdf-invoices-packing-slips-LOCALE.mo (which if not found falls back to:)
90
+ * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
91
+ */
92
+ foreach ( $textdomains as $textdomain ) {
93
+ unload_textdomain( $textdomain );
94
+ load_textdomain( $textdomain, $dir . 'woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
95
+ load_textdomain( $textdomain, $dir . 'plugins/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
96
+ load_plugin_textdomain( $textdomain, false, dirname( plugin_basename(__FILE__) ) . '/languages' );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Maintain backwards compatibility with old translation files
102
+ * Uses old .mo file if it exists in any of the override locations
103
+ */
104
+ public function textdomain_fallback( $mofile, $textdomain ) {
105
+ $plugin_domain = 'woocommerce-pdf-invoices-packing-slips';
106
+ $old_domain = 'wpo_wcpdf';
107
+
108
+ if ($textdomain == $old_domain) {
109
+ $textdomain = $plugin_domain;
110
+ $mofile = str_replace( "{$old_domain}-", "{$textdomain}-", $mofile ); // with trailing dash to target file and not folder
111
+ }
112
+
113
+ if ( $textdomain === $plugin_domain ) {
114
+ $old_mofile = str_replace( "{$textdomain}-", "{$old_domain}-", $mofile ); // with trailing dash to target file and not folder
115
+ if ( file_exists( $old_mofile ) ) {
116
+ // we have an old override - use it
117
+ return $old_mofile;
118
+ }
119
+
120
+ // prevent loading outdated language packs
121
+ $pofile = str_replace('.mo', '.po', $mofile);
122
+ if ( file_exists( $pofile ) ) {
123
+ // load po file
124
+ $podata = file_get_contents($pofile);
125
+ // set revision date threshold
126
+ $block_before = strtotime( '2017-05-15' );
127
+ // read revision date
128
+ preg_match('~PO-Revision-Date: (.*?)\\\n~s',$podata,$matches);
129
+ if (isset($matches[1])) {
130
+ $revision_date = $matches[1];
131
+ if ( $revision_timestamp = strtotime($revision_date) ) {
132
+ // check if revision is before threshold date
133
+ if ( $revision_timestamp < $block_before ) {
134
+ // try bundled
135
+ $bundled_file = $this->plugin_path() . '/languages/'. basename( $mofile );
136
+ if (file_exists($bundled_file)) {
137
+ return $bundled_file;
138
+ } else {
139
+ return '';
140
+ }
141
+ // delete po & mo file if possible
142
+ // @unlink($pofile);
143
+ // @unlink($mofile);
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ return $mofile;
151
+ }
152
+
153
+ /**
154
+ * Load the main plugin classes and functions
155
+ */
156
+ public function includes() {
157
+ // WooCommerce compatibility classes
158
+ include_once( $this->plugin_path() . '/includes/compatibility/abstract-wc-data-compatibility.php' );
159
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-date-compatibility.php' );
160
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-core-compatibility.php' );
161
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-order-compatibility.php' );
162
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-product-compatibility.php' );
163
+ include_once( $this->plugin_path() . '/includes/compatibility/wc-datetime-functions-compatibility.php' );
164
+
165
+ // Third party compatibility
166
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wcpdf-compatibility-third-party-plugins.php' );
167
+
168
+ // Plugin classes
169
+ include_once( $this->plugin_path() . '/includes/wcpdf-functions.php' );
170
+ $this->settings = include_once( $this->plugin_path() . '/includes/class-wcpdf-settings.php' );
171
+ $this->documents = include_once( $this->plugin_path() . '/includes/class-wcpdf-documents.php' );
172
+ $this->main = include_once( $this->plugin_path() . '/includes/class-wcpdf-main.php' );
173
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-assets.php' );
174
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-admin.php' );
175
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-frontend.php' );
176
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-install.php' );
177
+
178
+ // Backwards compatibility with self
179
+ include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy.php' );
180
+ include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy-deprecated-hooks.php' );
181
+
182
+ // PHP MB String fallback functions
183
+ include_once( $this->plugin_path() . '/includes/compatibility/mb-string-compatibility.php' );
184
+ }
185
+
186
+
187
+ /**
188
+ * Instantiate classes when woocommerce is activated
189
+ */
190
+ public function load_classes() {
191
+ if ( $this->is_woocommerce_activated() === false ) {
192
+ add_action( 'admin_notices', array ( $this, 'need_woocommerce' ) );
193
+ return;
194
+ }
195
+
196
+ if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
197
+ add_action( 'admin_notices', array ( $this, 'required_php_version' ) );
198
+ return;
199
+ }
200
+
201
+ // all systems ready - GO!
202
+ $this->includes();
203
+ }
204
+
205
+ /**
206
+ * Check if legacy mode is enabled
207
+ */
208
+ public function legacy_mode_enabled() {
209
+ if (!isset($this->legacy_mode)) {
210
+ $debug_settings = get_option( 'wpo_wcpdf_settings_debug' );
211
+ $this->legacy_mode = isset($debug_settings['legacy_mode']);
212
+ }
213
+ return $this->legacy_mode;
214
+ }
215
+
216
+
217
+ /**
218
+ * Check if woocommerce is activated
219
+ */
220
+ public function is_woocommerce_activated() {
221
+ $blog_plugins = get_option( 'active_plugins', array() );
222
+ $site_plugins = is_multisite() ? (array) maybe_unserialize( get_site_option('active_sitewide_plugins' ) ) : array();
223
+
224
+ if ( in_array( 'woocommerce/woocommerce.php', $blog_plugins ) || isset( $site_plugins['woocommerce/woocommerce.php'] ) ) {
225
+ return true;
226
+ } else {
227
+ return false;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * WooCommerce not active notice.
233
+ *
234
+ * @return string Fallack notice.
235
+ */
236
+
237
+ public function need_woocommerce() {
238
+ $error = sprintf( __( 'WooCommerce PDF Invoices & Packing Slips requires %sWooCommerce%s to be installed & activated!' , 'woocommerce-pdf-invoices-packing-slips' ), '<a href="http://wordpress.org/extend/plugins/woocommerce/">', '</a>' );
239
+
240
+ $message = '<div class="error"><p>' . $error . '</p></div>';
241
+
242
+ echo $message;
243
+ }
244
+
245
+ /**
246
+ * PHP version requirement notice
247
+ */
248
+
249
+ public function required_php_version() {
250
+ $error = __( 'WooCommerce PDF Invoices & Packing Slips requires PHP 5.3 or higher (5.6 or higher recommended).', 'woocommerce-pdf-invoices-packing-slips' );
251
+ $how_to_update = __( 'How to update your PHP version', 'woocommerce-pdf-invoices-packing-slips' );
252
+ $message = sprintf('<div class="error"><p>%s</p><p><a href="%s">%s</a></p></div>', $error, 'http://docs.wpovernight.com/general/how-to-update-your-php-version/', $how_to_update);
253
+
254
+ echo $message;
255
+ }
256
+
257
+ /**
258
+ * Show plugin changes. Code adapted from W3 Total Cache.
259
+ */
260
+ public function in_plugin_update_message( $args ) {
261
+ $transient_name = 'wpo_wcpdf_upgrade_notice_' . $args['Version'];
262
+
263
+ if ( false === ( $upgrade_notice = get_transient( $transient_name ) ) ) {
264
+ $response = wp_safe_remote_get( 'https://plugins.svn.wordpress.org/woocommerce-pdf-invoices-packing-slips/trunk/readme.txt' );
265
+
266
+ if ( ! is_wp_error( $response ) && ! empty( $response['body'] ) ) {
267
+ $upgrade_notice = self::parse_update_notice( $response['body'], $args['new_version'] );
268
+ set_transient( $transient_name, $upgrade_notice, DAY_IN_SECONDS );
269
+ }
270
+ }
271
+
272
+ echo wp_kses_post( $upgrade_notice );
273
+ }
274
+
275
+ /**
276
+ * Parse update notice from readme file.
277
+ *
278
+ * @param string $content
279
+ * @param string $new_version
280
+ * @return string
281
+ */
282
+ private function parse_update_notice( $content, $new_version ) {
283
+ // Output Upgrade Notice.
284
+ $matches = null;
285
+ $regexp = '~==\s*Upgrade Notice\s*==\s*=\s*(.*)\s*=(.*)(=\s*' . preg_quote( $new_version ) . '\s*=|$)~Uis';
286
+ $upgrade_notice = '';
287
+
288
+
289
+ if ( preg_match( $regexp, $content, $matches ) ) {
290
+ $notices = (array) preg_split( '~[\r\n]+~', trim( $matches[2] ) );
291
+
292
+ // Convert the full version strings to minor versions.
293
+ $notice_version_parts = explode( '.', trim( $matches[1] ) );
294
+ $current_version_parts = explode( '.', $this->version );
295
+
296
+ if ( 3 !== sizeof( $notice_version_parts ) ) {
297
+ return;
298
+ }
299
+
300
+ $notice_version = $notice_version_parts[0] . '.' . $notice_version_parts[1];
301
+ $current_version = $current_version_parts[0] . '.' . $current_version_parts[1];
302
+
303
+ // Check the latest stable version and ignore trunk.
304
+ if ( version_compare( $current_version, $notice_version, '<' ) ) {
305
+
306
+ $upgrade_notice .= '</p><p class="wpo_wcpdf_upgrade_notice">';
307
+
308
+ foreach ( $notices as $index => $line ) {
309
+ $upgrade_notice .= preg_replace( '~\[([^\]]*)\]\(([^\)]*)\)~', '<a href="${2}">${1}</a>', $line );
310
+ }
311
+ }
312
+ }
313
+
314
+ return wp_kses_post( $upgrade_notice );
315
+ }
316
+
317
+ /**
318
+ * Get the plugin url.
319
+ * @return string
320
+ */
321
+ public function plugin_url() {
322
+ return untrailingslashit( plugins_url( '/', __FILE__ ) );
323
+ }
324
+
325
+ /**
326
+ * Get the plugin path.
327
+ * @return string
328
+ */
329
+ public function plugin_path() {
330
+ return untrailingslashit( plugin_dir_path( __FILE__ ) );
331
+ }
332
+
333
+ } // class WPO_WCPDF
334
+
335
+ endif; // class_exists
336
+
337
+ /**
338
+ * Returns the main instance of WooCommerce PDF Invoices & Packing Slips to prevent the need to use globals.
339
+ *
340
+ * @since 1.6
341
+ * @return WPO_WCPDF
342
+ */
343
+ function WPO_WCPDF() {
344
+ return WPO_WCPDF::instance();
345
+ }
346
+
347
+ WPO_WCPDF(); // load plugin
348
+
349
+ // legacy class for plugin detecting
350
+ if ( !class_exists( 'WooCommerce_PDF_Invoices' ) ) {
351
+ class WooCommerce_PDF_Invoices{
352
+ public static $version;
353
+
354
+ public function __construct() {
355
+ self::$version = WPO_WCPDF()->version;
356
+ }
357
+ }
358
+ new WooCommerce_PDF_Invoices();
359
+ }