WooCommerce PDF Invoices & Packing Slips - Version 2.2.4

Version Description

  • Fix: excluding some display options from historical settings
  • Fix: fix notices when requesting properties as custom fields (in a custom template)
Download this release

Release Info

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

Code changes from version 2.2.3 to 2.2.4

includes/documents/abstract-wcpdf-order-document-methods.php CHANGED
@@ -1,1109 +1,1109 @@
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
- $address_comparison_fields = apply_filters( 'wpo_wcpdf_address_comparison_fields', array(
71
- 'first_name',
72
- 'last_name',
73
- 'company',
74
- 'address_1',
75
- 'address_2',
76
- 'city',
77
- 'state',
78
- 'postcode',
79
- 'country'
80
- ), $this );
81
-
82
- foreach ($address_comparison_fields as $address_field) {
83
- $billing_field = WCX_Order::get_prop( $order, "billing_{$address_field}", 'view');
84
- $shipping_field = WCX_Order::get_prop( $order, "shipping_{$address_field}", 'view');
85
- if ( $shipping_field != $billing_field ) {
86
- // this address field is different -> ships to different address!
87
- return true;
88
- }
89
- }
90
-
91
- //if we got here, it means the addresses are equal -> doesn't ship to different address!
92
- return apply_filters( 'wpo_wcpdf_ships_to_different_address', false, $order, $this );
93
- }
94
-
95
- /**
96
- * Return/Show billing address
97
- */
98
- public function get_billing_address() {
99
- // always prefer parent billing address for refunds
100
- if ( $this->is_refund( $this->order ) ) {
101
- // temporarily switch order to make all filters / order calls work correctly
102
- $refund = $this->order;
103
- $this->order = $this->get_refund_parent( $this->order );
104
- $address = apply_filters( 'wpo_wcpdf_billing_address', $this->order->get_formatted_billing_address(), $this );
105
- // switch back & unset
106
- $this->order = $refund;
107
- unset($refund);
108
- } elseif ( $address = $this->order->get_formatted_billing_address() ) {
109
- // regular shop_order
110
- $address = apply_filters( 'wpo_wcpdf_billing_address', $address, $this );
111
- } else {
112
- // no address
113
- $address = apply_filters( 'wpo_wcpdf_billing_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
114
- }
115
-
116
- return $address;
117
- }
118
- public function billing_address() {
119
- echo $this->get_billing_address();
120
- }
121
-
122
- /**
123
- * Return/Show billing email
124
- */
125
- public function get_billing_email() {
126
- $billing_email = WCX_Order::get_prop( $this->order, 'billing_email', 'view' );
127
-
128
- if ( !$billing_email && $this->is_refund( $this->order ) ) {
129
- // try parent
130
- $parent_order = $this->get_refund_parent( $this->order );
131
- $billing_email = WCX_Order::get_prop( $parent_order, 'billing_email', 'view' );
132
- }
133
-
134
- return apply_filters( 'wpo_wcpdf_billing_email', $billing_email, $this );
135
- }
136
- public function billing_email() {
137
- echo $this->get_billing_email();
138
- }
139
-
140
- /**
141
- * Return/Show billing phone
142
- */
143
- public function get_billing_phone() {
144
- $billing_phone = WCX_Order::get_prop( $this->order, 'billing_phone', 'view' );
145
-
146
- if ( !$billing_phone && $this->is_refund( $this->order ) ) {
147
- // try parent
148
- $parent_order = $this->get_refund_parent( $this->order );
149
- $billing_phone = WCX_Order::get_prop( $parent_order, 'billing_phone', 'view' );
150
- }
151
-
152
- return apply_filters( 'wpo_wcpdf_billing_phone', $billing_phone, $this );
153
- }
154
- public function billing_phone() {
155
- echo $this->get_billing_phone();
156
- }
157
-
158
- /**
159
- * Return/Show shipping address
160
- */
161
- public function get_shipping_address() {
162
- // always prefer parent shipping address for refunds
163
- if ( $this->is_refund( $this->order ) ) {
164
- // temporarily switch order to make all filters / order calls work correctly
165
- $refund = $this->order;
166
- $this->order = $this->get_refund_parent( $this->order );
167
- $address = apply_filters( 'wpo_wcpdf_shipping_address', $this->order->get_formatted_shipping_address(), $this );
168
- // switch back & unset
169
- $this->order = $refund;
170
- unset($refund);
171
- } elseif ( $address = $this->order->get_formatted_shipping_address() ) {
172
- // regular shop_order
173
- $address = apply_filters( 'wpo_wcpdf_shipping_address', $address, $this );
174
- } else {
175
- // no address
176
- $address = apply_filters( 'wpo_wcpdf_shipping_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
177
- }
178
-
179
- return $address;
180
- }
181
- public function shipping_address() {
182
- echo $this->get_shipping_address();
183
- }
184
-
185
- /**
186
- * Return/Show a custom field
187
- */
188
- public function get_custom_field( $field_name ) {
189
- if ( !$this->is_order_prop( $field_name ) ) {
190
- $custom_field = WCX_Order::get_meta( $this->order, $field_name, true );
191
- }
192
- // if not found, try prefixed with underscore (not when ACF is active!)
193
- if ( !$custom_field && substr( $field_name, 0, 1 ) !== '_' && !$this->is_order_prop( "_{$field_name}" ) && !class_exists('ACF') ) {
194
- $custom_field = WCX_Order::get_meta( $this->order, "_{$field_name}", true );
195
- }
196
-
197
- // WC3.0 fallback to properties
198
- $property = str_replace('-', '_', sanitize_title( ltrim($field_name, '_') ) );
199
- if ( !$custom_field && is_callable( array( $this->order, "get_{$property}" ) ) ) {
200
- $custom_field = $this->order->{"get_{$property}"}( 'view' );
201
- }
202
-
203
- // fallback to parent for refunds
204
- if ( !$custom_field && $this->is_refund( $this->order ) ) {
205
- $parent_order = $this->get_refund_parent( $this->order );
206
- if ( !$this->is_order_prop( $field_name ) ) {
207
- $custom_field = WCX_Order::get_meta( $parent_order, $field_name, true );
208
- }
209
-
210
- // WC3.0 fallback to properties
211
- if ( !$custom_field && is_callable( array( $parent_order, "get_{$property}" ) ) ) {
212
- $custom_field = $parent_order->{"get_{$property}"}( 'view' );
213
- }
214
- }
215
-
216
- return apply_filters( 'wpo_wcpdf_billing_custom_field', $custom_field, $this );
217
- }
218
- public function custom_field( $field_name, $field_label = '', $display_empty = false ) {
219
- $custom_field = $this->get_custom_field( $field_name );
220
- if (!empty($field_label)){
221
- // add a a trailing space to the label
222
- $field_label .= ' ';
223
- }
224
-
225
- if (!empty($custom_field) || $display_empty) {
226
- echo $field_label . nl2br ($custom_field);
227
- }
228
- }
229
-
230
- public function is_order_prop( $key ) {
231
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '<' ) ) {
232
- return false; // WC 2.X didn't have CRUD
233
- }
234
- // Taken from WC class
235
- $order_props = array(
236
- // Abstract order props
237
- 'parent_id',
238
- 'status',
239
- 'currency',
240
- 'version',
241
- 'prices_include_tax',
242
- 'date_created',
243
- 'date_modified',
244
- 'discount_total',
245
- 'discount_tax',
246
- 'shipping_total',
247
- 'shipping_tax',
248
- 'cart_tax',
249
- 'total',
250
- 'total_tax',
251
- // Order props
252
- 'customer_id',
253
- 'order_key',
254
- 'billing_first_name',
255
- 'billing_last_name',
256
- 'billing_company',
257
- 'billing_address_1',
258
- 'billing_address_2',
259
- 'billing_city',
260
- 'billing_state',
261
- 'billing_postcode',
262
- 'billing_country',
263
- 'billing_email',
264
- 'billing_phone',
265
- 'shipping_first_name',
266
- 'shipping_last_name',
267
- 'shipping_company',
268
- 'shipping_address_1',
269
- 'shipping_address_2',
270
- 'shipping_city',
271
- 'shipping_state',
272
- 'shipping_postcode',
273
- 'shipping_country',
274
- 'payment_method',
275
- 'payment_method_title',
276
- 'transaction_id',
277
- 'customer_ip_address',
278
- 'customer_user_agent',
279
- 'created_via',
280
- 'customer_note',
281
- 'date_completed',
282
- 'date_paid',
283
- 'cart_hash',
284
- );
285
- return in_array($key, $order_props);
286
- }
287
-
288
- /**
289
- * Return/show product attribute
290
- */
291
- public function get_product_attribute( $attribute_name, $product ) {
292
- // first, check the text attributes
293
- $attributes = $product->get_attributes();
294
- $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
295
- if (array_key_exists( sanitize_title( $attribute_name ), $attributes) ) {
296
- $attribute = $product->get_attribute ( $attribute_name );
297
- } elseif (array_key_exists( sanitize_title( $attribute_key ), $attributes) ) {
298
- $attribute = $product->get_attribute ( $attribute_key );
299
- }
300
-
301
- if (empty($attribute)) {
302
- // not a text attribute, try attribute taxonomy
303
- $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
304
- $product_id = WCX_Product::get_prop($product, 'id');
305
- $product_terms = @wc_get_product_terms( $product_id, $attribute_key, array( 'fields' => 'names' ) );
306
- // check if not empty, then display
307
- if ( !empty($product_terms) ) {
308
- $attribute = array_shift( $product_terms );
309
- }
310
- }
311
-
312
- // WC3.0+ fallback parent product for variations
313
- if ( empty($attribute) && version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) && $product->is_type( 'variation' ) ) {
314
- $product = wc_get_product( $product->get_parent_id() );
315
- $attribute = $this->get_product_attribute( $attribute_name, $product );
316
- }
317
-
318
- return isset($attribute) ? $attribute : false;
319
- }
320
- public function product_attribute( $attribute_name, $product ) {
321
- echo $this->get_product_attribute( $attribute_name, $product );
322
- }
323
-
324
- /**
325
- * Return/Show order notes
326
- * could use $order->get_customer_order_notes(), but that filters out private notes already
327
- */
328
- public function get_order_notes( $filter = 'customer' ) {
329
- if ( $this->is_refund( $this->order ) ) {
330
- $post_id = $this->get_refund_parent_id( $this->order );
331
- } else {
332
- $post_id = $this->order_id;
333
- }
334
-
335
- if ( empty( $post_id ) ) {
336
- return; // prevent order notes from all orders showing when document is not loaded properly
337
- }
338
-
339
- $args = array(
340
- 'post_id' => $post_id,
341
- 'approve' => 'approve',
342
- 'type' => 'order_note'
343
- );
344
-
345
- remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
346
-
347
- $notes = get_comments( $args );
348
-
349
- add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
350
-
351
- if ( $notes ) {
352
- foreach( $notes as $key => $note ) {
353
- if ( $filter == 'customer' && !get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
354
- unset($notes[$key]);
355
- }
356
- if ( $filter == 'private' && get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
357
- unset($notes[$key]);
358
- }
359
- }
360
- return $notes;
361
- }
362
- }
363
- public function order_notes( $filter = 'customer' ) {
364
- $notes = $this->get_order_notes( $filter );
365
- if ( $notes ) {
366
- foreach( $notes as $note ) {
367
- ?>
368
- <div class="note_content">
369
- <?php echo wpautop( wptexturize( wp_kses_post( $note->comment_content ) ) ); ?>
370
- </div>
371
- <?php
372
- }
373
- }
374
- }
375
-
376
- /**
377
- * Return/Show the current date
378
- */
379
- public function get_current_date() {
380
- return apply_filters( 'wpo_wcpdf_date', date_i18n( get_option( 'date_format' ) ) );
381
- }
382
- public function current_date() {
383
- echo $this->get_current_date();
384
- }
385
-
386
- /**
387
- * Return/Show payment method
388
- */
389
- public function get_payment_method() {
390
- $payment_method_label = __( 'Payment method', 'woocommerce-pdf-invoices-packing-slips' );
391
-
392
- if ( $this->is_refund( $this->order ) ) {
393
- $parent_order = $this->get_refund_parent( $this->order );
394
- $payment_method_title = WCX_Order::get_prop( $parent_order, 'payment_method_title', 'view' );
395
- } else {
396
- $payment_method_title = WCX_Order::get_prop( $this->order, 'payment_method_title', 'view' );
397
- }
398
-
399
- $payment_method = __( $payment_method_title, 'woocommerce' );
400
-
401
- return apply_filters( 'wpo_wcpdf_payment_method', $payment_method, $this );
402
- }
403
- public function payment_method() {
404
- echo $this->get_payment_method();
405
- }
406
-
407
- /**
408
- * Return/Show shipping method
409
- */
410
- public function get_shipping_method() {
411
- $shipping_method_label = __( 'Shipping method', 'woocommerce-pdf-invoices-packing-slips' );
412
- $shipping_method = __( $this->order->get_shipping_method(), 'woocommerce' );
413
- return apply_filters( 'wpo_wcpdf_shipping_method', $shipping_method, $this );
414
- }
415
- public function shipping_method() {
416
- echo $this->get_shipping_method();
417
- }
418
-
419
- /**
420
- * Return/Show order number
421
- */
422
- public function get_order_number() {
423
- // try parent first
424
- if ( $this->is_refund( $this->order ) ) {
425
- $parent_order = $this->get_refund_parent( $this->order );
426
- $order_number = $parent_order->get_order_number();
427
- } else {
428
- $order_number = $this->order->get_order_number();
429
- }
430
-
431
- // Trim the hash to have a clean number but still
432
- // support any filters that were applied before.
433
- $order_number = ltrim($order_number, '#');
434
- return apply_filters( 'wpo_wcpdf_order_number', $order_number, $this );
435
- }
436
- public function order_number() {
437
- echo $this->get_order_number();
438
- }
439
-
440
- /**
441
- * Return/Show the order date
442
- */
443
- public function get_order_date() {
444
- if ( $this->is_refund( $this->order ) ) {
445
- $parent_order = $this->get_refund_parent( $this->order );
446
- $order_date = WCX_Order::get_prop( $parent_order, 'date_created' );
447
- } else {
448
- $order_date = WCX_Order::get_prop( $this->order, 'date_created' );
449
- }
450
-
451
- $date = $order_date->date_i18n( get_option( 'date_format' ) );
452
- $mysql_date = $order_date->date( "Y-m-d H:i:s" );
453
- return apply_filters( 'wpo_wcpdf_order_date', $date, $mysql_date, $this );
454
- }
455
- public function order_date() {
456
- echo $this->get_order_date();
457
- }
458
-
459
- /**
460
- * Return the order items
461
- */
462
- public function get_order_items() {
463
- $items = $this->order->get_items();
464
- $data_list = array();
465
-
466
- if( sizeof( $items ) > 0 ) {
467
- foreach ( $items as $item_id => $item ) {
468
- // Array with data for the pdf template
469
- $data = array();
470
-
471
- // Set the item_id
472
- $data['item_id'] = $item_id;
473
-
474
- // Set the id
475
- $data['product_id'] = $item['product_id'];
476
- $data['variation_id'] = $item['variation_id'];
477
-
478
- // Set item name
479
- $data['name'] = $item['name'];
480
-
481
- // Set item quantity
482
- $data['quantity'] = $item['qty'];
483
-
484
- // Set the line total (=after discount)
485
- $data['line_total'] = $this->format_price( $item['line_total'] );
486
- $data['single_line_total'] = $this->format_price( $item['line_total'] / max( 1, abs( $item['qty'] ) ) );
487
- $data['line_tax'] = $this->format_price( $item['line_tax'] );
488
- $data['single_line_tax'] = $this->format_price( $item['line_tax'] / max( 1, abs( $item['qty'] ) ) );
489
-
490
- $line_tax_data = maybe_unserialize( isset( $item['line_tax_data'] ) ? $item['line_tax_data'] : '' );
491
- $data['tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, true );
492
- $data['calculated_tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, false );
493
-
494
- // Set the line subtotal (=before discount)
495
- $data['line_subtotal'] = $this->format_price( $item['line_subtotal'] );
496
- $data['line_subtotal_tax'] = $this->format_price( $item['line_subtotal_tax'] );
497
- $data['ex_price'] = $this->get_formatted_item_price( $item, 'total', 'excl' );
498
- $data['price'] = $this->get_formatted_item_price( $item, 'total' );
499
- $data['order_price'] = $this->order->get_formatted_line_subtotal( $item ); // formatted according to WC settings
500
-
501
- // Calculate the single price with the same rules as the formatted line subtotal (!)
502
- // = before discount
503
- $data['ex_single_price'] = $this->get_formatted_item_price( $item, 'single', 'excl' );
504
- $data['single_price'] = $this->get_formatted_item_price( $item, 'single' );
505
-
506
- // Pass complete item array
507
- $data['item'] = $item;
508
-
509
- // Get the product to add more info
510
- $product = $this->order->get_product_from_item( $item );
511
-
512
- // Checking fo existance, thanks to MDesigner0
513
- if( !empty( $product ) ) {
514
- // Thumbnail (full img tag)
515
- $data['thumbnail'] = $this->get_thumbnail( $product );
516
-
517
- // Set item SKU
518
- $data['sku'] = $product->get_sku();
519
-
520
- // Set item weight
521
- $data['weight'] = $product->get_weight();
522
-
523
- // Set item dimensions
524
- $data['dimensions'] = WCX_Product::get_dimensions( $product );
525
-
526
- // Pass complete product object
527
- $data['product'] = $product;
528
-
529
- } else {
530
- $data['product'] = null;
531
- }
532
-
533
- // Set item meta
534
- if (function_exists('wc_display_item_meta')) { // WC3.0+
535
- $data['meta'] = wc_display_item_meta( $item, array(
536
- 'echo' => false,
537
- ) );
538
- } else {
539
- if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '<' ) ) {
540
- $meta = new \WC_Order_Item_Meta( $item['item_meta'], $product );
541
- } else { // pass complete item for WC2.4+
542
- $meta = new \WC_Order_Item_Meta( $item, $product );
543
- }
544
- $data['meta'] = $meta->display( false, true );
545
- }
546
-
547
- $data_list[$item_id] = apply_filters( 'wpo_wcpdf_order_item_data', $data, $this->order, $this->get_type() );
548
- }
549
- }
550
-
551
- return apply_filters( 'wpo_wcpdf_order_items_data', $data_list, $this->order, $this->get_type() );
552
- }
553
-
554
- /**
555
- * Get the tax rates/percentages for a given tax class
556
- * @param string $tax_class tax class slug
557
- * @return string $tax_rates imploded list of tax rates
558
- */
559
- public function get_tax_rate( $tax_class, $line_total, $line_tax, $line_tax_data = '', $force_calculation = false ) {
560
- // first try the easy wc2.2+ way, using line_tax_data
561
- if ( !empty( $line_tax_data ) && isset($line_tax_data['total']) ) {
562
- $tax_rates = array();
563
-
564
- $line_taxes = $line_tax_data['subtotal'];
565
- foreach ( $line_taxes as $tax_id => $tax ) {
566
- if ( isset($tax) && $tax !== '' ) {
567
- $tax_rate = $this->get_tax_rate_by_id( $tax_id );
568
- if ( $tax_rate !== false && $force_calculation !== false ) {
569
- $tax_rates[] = $tax_rate . ' %';
570
- } else {
571
- $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
572
- }
573
- }
574
- }
575
-
576
- // apply decimal setting
577
- if (function_exists('wc_get_price_decimal_separator')) {
578
- foreach ($tax_rates as &$tax_rate) {
579
- $tax_rate = str_replace('.', wc_get_price_decimal_separator(), strval($tax_rate) );
580
- }
581
- }
582
-
583
- $tax_rates = implode(' ,', $tax_rates );
584
- return $tax_rates;
585
- }
586
-
587
- if ( $line_tax == 0 ) {
588
- return '-'; // no need to determine tax rate...
589
- }
590
-
591
- if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 && !apply_filters( 'wpo_wcpdf_calculate_tax_rate', false ) ) {
592
- // WC 2.1 or newer is used
593
- $tax = new \WC_Tax();
594
- $taxes = $tax->get_rates( $tax_class );
595
-
596
- $tax_rates = array();
597
-
598
- foreach ($taxes as $tax) {
599
- $tax_rates[$tax['label']] = round( $tax['rate'], 2 ).' %';
600
- }
601
-
602
- if (empty($tax_rates)) {
603
- // one last try: manually calculate
604
- $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
605
- }
606
-
607
- $tax_rates = implode(' ,', $tax_rates );
608
- } else {
609
- // Backwards compatibility/fallback: calculate tax from line items
610
- $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
611
- }
612
-
613
- return $tax_rates;
614
- }
615
-
616
- public function calculate_tax_rate( $price_ex_tax, $tax ) {
617
- $precision = apply_filters( 'wpo_wcpdf_calculate_tax_rate_precision', 1 );
618
- if ( $price_ex_tax != 0) {
619
- $tax_rate = round( ($tax / $price_ex_tax)*100, $precision ).' %';
620
- } else {
621
- $tax_rate = '-';
622
- }
623
- return $tax_rate;
624
- }
625
-
626
- /**
627
- * Returns the percentage rate (float) for a given tax rate ID.
628
- * @param int $rate_id woocommerce tax rate id
629
- * @return float $rate percentage rate
630
- */
631
- public function get_tax_rate_by_id( $rate_id ) {
632
- global $wpdb;
633
- $rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $rate_id ) );
634
- if ($rate === NULL) {
635
- return false;
636
- } else {
637
- return (float) $rate;
638
- }
639
- }
640
-
641
- /**
642
- * Returns a an array with rate_id => tax rate data (array) of all tax rates in woocommerce
643
- * @return array $tax_rate_ids keyed by id
644
- */
645
- public function get_tax_rate_ids() {
646
- global $wpdb;
647
- $rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates" );
648
-
649
- $tax_rate_ids = array();
650
- foreach ($rates as $rate) {
651
- $rate_id = $rate->tax_rate_id;
652
- unset($rate->tax_rate_id);
653
- $tax_rate_ids[$rate_id] = (array) $rate;
654
- }
655
-
656
- return $tax_rate_ids;
657
- }
658
-
659
- /**
660
- * Returns the main product image ID
661
- * Adapted from the WC_Product class
662
- * (does not support thumbnail sizes)
663
- *
664
- * @access public
665
- * @return string
666
- */
667
- public function get_thumbnail_id ( $product ) {
668
- global $woocommerce;
669
-
670
- $product_id = WCX_Product::get_id( $product );
671
-
672
- if ( has_post_thumbnail( $product_id ) ) {
673
- $thumbnail_id = get_post_thumbnail_id ( $product_id );
674
- } elseif ( ( $parent_id = wp_get_post_parent_id( $product_id ) ) && has_post_thumbnail( $parent_id ) ) {
675
- $thumbnail_id = get_post_thumbnail_id ( $parent_id );
676
- } else {
677
- $thumbnail_id = false;
678
- }
679
-
680
- return $thumbnail_id;
681
- }
682
-
683
- /**
684
- * Returns the thumbnail image tag
685
- *
686
- * uses the internal WooCommerce/WP functions and extracts the image url or path
687
- * rather than the thumbnail ID, to simplify the code and make it possible to
688
- * filter for different thumbnail sizes
689
- *
690
- * @access public
691
- * @return string
692
- */
693
- public function get_thumbnail ( $product ) {
694
- // Get default WooCommerce img tag (url/http)
695
- $size = apply_filters( 'wpo_wcpdf_thumbnail_size', 'shop_thumbnail' );
696
- $thumbnail_img_tag_url = $product->get_image( $size, array( 'title' => '' ) );
697
-
698
- // Extract the url from img
699
- preg_match('/<img(.*)src(.*)=(.*)"(.*)"/U', $thumbnail_img_tag_url, $thumbnail_url );
700
- $thumbnail_url = array_pop($thumbnail_url);
701
- // remove http/https from image tag url to avoid mixed origin conflicts
702
- $contextless_thumbnail_url = ltrim( str_replace(array('http://','https://'), '', $thumbnail_url ), '/' );
703
-
704
- // convert url to path
705
- if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
706
- $forwardslash_basepath = str_replace('\\','/', ABSPATH);
707
- $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(get_site_url()));
708
- } else {
709
- // bedrock e.a
710
- $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
711
- $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(WP_CONTENT_URL));
712
- }
713
- $thumbnail_path = str_replace( $contextless_site_url, trailingslashit( $forwardslash_basepath ), $contextless_thumbnail_url);
714
-
715
- // fallback if thumbnail file doesn't exist
716
- if (apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path)) {
717
- if ($thumbnail_id = $this->get_thumbnail_id( $product ) ) {
718
- $thumbnail_path = get_attached_file( $thumbnail_id );
719
- }
720
- }
721
-
722
- // Thumbnail (full img tag)
723
- if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($thumbnail_path) ) {
724
- // load img with server path by default
725
- $thumbnail = sprintf('<img width="90" height="90" src="%s" class="attachment-shop_thumbnail wp-post-image">', $thumbnail_path );
726
- } elseif ( apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path) ) {
727
- // should use paths but file not found, replace // with http(s):// for dompdf compatibility
728
- if ( substr( $thumbnail_url, 0, 2 ) === "//" ) {
729
- $prefix = is_ssl() ? 'https://' : 'http://';
730
- $https_thumbnail_url = $prefix . ltrim( $thumbnail_url, '/' );
731
- $thumbnail_img_tag_url = str_replace($thumbnail_url, $https_thumbnail_url, $thumbnail_img_tag_url);
732
- }
733
- $thumbnail = $thumbnail_img_tag_url;
734
- } else {
735
- // load img with http url when filtered
736
- $thumbnail = $thumbnail_img_tag_url;
737
- }
738
-
739
- // die($thumbnail);
740
- return $thumbnail;
741
- }
742
-
743
- /**
744
- * Return the order totals listing
745
- */
746
- public function get_woocommerce_totals() {
747
- // get totals and remove the semicolon
748
- $totals = apply_filters( 'wpo_wcpdf_raw_order_totals', $this->order->get_order_item_totals(), $this->order );
749
-
750
- // remove the colon for every label
751
- foreach ( $totals as $key => $total ) {
752
- $label = $total['label'];
753
- $colon = strrpos( $label, ':' );
754
- if( $colon !== false ) {
755
- $label = substr_replace( $label, '', $colon, 1 );
756
- }
757
- $totals[$key]['label'] = $label;
758
- }
759
-
760
- // WC2.4 fix order_total for refunded orders
761
- // not if this is the actual refund!
762
- if ( ! $this->is_refund( $this->order ) ) {
763
- $total_refunded = method_exists($this->order, 'get_total_refunded') ? $this->order->get_total_refunded() : 0;
764
- if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '>=' ) && isset($totals['order_total']) && $total_refunded ) {
765
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
766
- $tax_display = get_option( 'woocommerce_tax_display_cart' );
767
- } else {
768
- $tax_display = WCX_Order::get_prop( $this->order, 'tax_display_cart' );
769
- }
770
-
771
- $totals['order_total']['value'] = wc_price( $this->order->get_total(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) );
772
- $order_total = $this->order->get_total();
773
- $tax_string = '';
774
-
775
- // Tax for inclusive prices
776
- if ( wc_tax_enabled() && 'incl' == $tax_display ) {
777
- $tax_string_array = array();
778
- if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
779
- foreach ( $this->order->get_tax_totals() as $code => $tax ) {
780
- $tax_amount = $tax->formatted_amount;
781
- $tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
782
- }
783
- } else {
784
- $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() );
785
- }
786
- if ( ! empty( $tax_string_array ) ) {
787
- if ( version_compare( WOOCOMMERCE_VERSION, '2.6', '>=' ) ) {
788
- $tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
789
- } else {
790
- // use old capitalized string
791
- $tax_string = ' ' . sprintf( __( '(Includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
792
- }
793
- }
794
- }
795
-
796
- $totals['order_total']['value'] .= $tax_string;
797
- }
798
-
799
- // remove refund lines (shouldn't be in invoice)
800
- foreach ( $totals as $key => $total ) {
801
- if ( strpos($key, 'refund_') !== false ) {
802
- unset( $totals[$key] );
803
- }
804
- }
805
-
806
- }
807
-
808
- return apply_filters( 'wpo_wcpdf_woocommerce_totals', $totals, $this->order, $this->get_type() );
809
- }
810
-
811
- /**
812
- * Return/show the order subtotal
813
- */
814
- public function get_order_subtotal( $tax = 'excl', $discount = 'incl' ) { // set $tax to 'incl' to include tax, same for $discount
815
- //$compound = ($discount == 'incl')?true:false;
816
- $subtotal = $this->order->get_subtotal_to_display( false, $tax );
817
-
818
- $subtotal = ($pos = strpos($subtotal, ' <small')) ? substr($subtotal, 0, $pos) : $subtotal; //removing the 'excluding tax' text
819
-
820
- $subtotal = array (
821
- 'label' => __('Subtotal', 'woocommerce-pdf-invoices-packing-slips' ),
822
- 'value' => $subtotal,
823
- );
824
-
825
- return apply_filters( 'wpo_wcpdf_order_subtotal', $subtotal, $tax, $discount, $this );
826
- }
827
- public function order_subtotal( $tax = 'excl', $discount = 'incl' ) {
828
- $subtotal = $this->get_order_subtotal( $tax, $discount );
829
- echo $subtotal['value'];
830
- }
831
-
832
- /**
833
- * Return/show the order shipping costs
834
- */
835
- public function get_order_shipping( $tax = 'excl' ) { // set $tax to 'incl' to include tax
836
- $shipping_cost = WCX_Order::get_prop( $this->order, 'shipping_total', 'view' );
837
- $shipping_tax = WCX_Order::get_prop( $this->order, 'shipping_tax', 'view' );
838
-
839
- if ($tax == 'excl' ) {
840
- $formatted_shipping_cost = $this->format_price( $shipping_cost );
841
- } else {
842
- $formatted_shipping_cost = $this->format_price( $shipping_cost + $shipping_tax );
843
- }
844
-
845
- $shipping = array (
846
- 'label' => __('Shipping', 'woocommerce-pdf-invoices-packing-slips' ),
847
- 'value' => $formatted_shipping_cost,
848
- 'tax' => $this->format_price( $shipping_tax ),
849
- );
850
- return apply_filters( 'wpo_wcpdf_order_shipping', $shipping, $tax, $this );
851
- }
852
- public function order_shipping( $tax = 'excl' ) {
853
- $shipping = $this->get_order_shipping( $tax );
854
- echo $shipping['value'];
855
- }
856
-
857
- /**
858
- * Return/show the total discount
859
- */
860
- public function get_order_discount( $type = 'total', $tax = 'incl' ) {
861
- if ( $tax == 'incl' ) {
862
- switch ($type) {
863
- case 'cart':
864
- // Cart Discount - pre-tax discounts. (deprecated in WC2.3)
865
- $discount_value = $this->order->get_cart_discount();
866
- break;
867
- case 'order':
868
- // Order Discount - post-tax discounts. (deprecated in WC2.3)
869
- $discount_value = $this->order->get_order_discount();
870
- break;
871
- case 'total':
872
- // Total Discount
873
- if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
874
- $discount_value = $this->order->get_total_discount( false ); // $ex_tax = false
875
- } else {
876
- // WC2.2 and older: recalculate to include tax
877
- $discount_value = 0;
878
- $items = $this->order->get_items();;
879
- if( sizeof( $items ) > 0 ) {
880
- foreach( $items as $item ) {
881
- $discount_value += ($item['line_subtotal'] + $item['line_subtotal_tax']) - ($item['line_total'] + $item['line_tax']);
882
- }
883
- }
884
- }
885
-
886
- break;
887
- default:
888
- // Total Discount - Cart & Order Discounts combined
889
- $discount_value = $this->order->get_total_discount();
890
- break;
891
- }
892
- } else { // calculate discount excluding tax
893
- if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
894
- $discount_value = $this->order->get_total_discount( true ); // $ex_tax = true
895
- } else {
896
- // WC2.2 and older: recalculate to exclude tax
897
- $discount_value = 0;
898
-
899
- $items = $this->order->get_items();;
900
- if( sizeof( $items ) > 0 ) {
901
- foreach( $items as $item ) {
902
- $discount_value += ($item['line_subtotal'] - $item['line_total']);
903
- }
904
- }
905
- }
906
- }
907
-
908
- $discount = array (
909
- 'label' => __('Discount', 'woocommerce-pdf-invoices-packing-slips' ),
910
- 'value' => $this->format_price( $discount_value ),
911
- 'raw_value' => $discount_value,
912
- );
913
-
914
- if ( round( $discount_value, 3 ) != 0 ) {
915
- return apply_filters( 'wpo_wcpdf_order_discount', $discount, $type, $tax, $this );
916
- }
917
- }
918
- public function order_discount( $type = 'total', $tax = 'incl' ) {
919
- $discount = $this->get_order_discount( $type, $tax );
920
- echo $discount['value'];
921
- }
922
-
923
- /**
924
- * Return the order fees
925
- */
926
- public function get_order_fees( $tax = 'excl' ) {
927
- if ( $_fees = $this->order->get_fees() ) {
928
- foreach( $_fees as $id => $fee ) {
929
- if ($tax == 'excl' ) {
930
- $fee_price = $this->format_price( $fee['line_total'] );
931
- } else {
932
- $fee_price = $this->format_price( $fee['line_total'] + $fee['line_tax'] );
933
- }
934
-
935
- $fees[ $id ] = array(
936
- 'label' => $fee['name'],
937
- 'value' => $fee_price,
938
- 'line_total' => $this->format_price( $fee['line_total'] ),
939
- 'line_tax' => $this->format_price( $fee['line_tax'] )
940
- );
941
- }
942
- return $fees;
943
- }
944
- }
945
-
946
- /**
947
- * Return the order taxes
948
- */
949
- public function get_order_taxes() {
950
- $tax_label = __( 'VAT', 'woocommerce-pdf-invoices-packing-slips' ); // register alternate label translation
951
- $tax_label = __( 'Tax rate', 'woocommerce-pdf-invoices-packing-slips' );
952
- $tax_rate_ids = $this->get_tax_rate_ids();
953
- if ( $order_taxes = $this->order->get_taxes() ) {
954
- foreach ( $order_taxes as $key => $tax ) {
955
- if ( WCX::is_wc_version_gte_3_0() ) {
956
- $taxes[ $key ] = array(
957
- 'label' => $tax->get_label(),
958
- 'value' => $this->format_price( $tax->get_tax_total() + $tax->get_shipping_tax_total() ),
959
- 'rate_id' => $tax->get_rate_id(),
960
- 'tax_amount' => $tax->get_tax_total(),
961
- 'shipping_tax_amount' => $tax->get_shipping_tax_total(),
962
- 'rate' => isset( $tax_rate_ids[ $tax->get_rate_id() ] ) ? ( (float) $tax_rate_ids[$tax->get_rate_id()]['tax_rate'] ) . ' %': '',
963
- );
964
- } else {
965
- $taxes[ $key ] = array(
966
- 'label' => isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ],
967
- 'value' => $this->format_price( ( $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ] ) ),
968
- 'rate_id' => $tax['rate_id'],
969
- 'tax_amount' => $tax['tax_amount'],
970
- 'shipping_tax_amount' => $tax['shipping_tax_amount'],
971
- 'rate' => isset( $tax_rate_ids[ $tax['rate_id'] ] ) ? ( (float) $tax_rate_ids[$tax['rate_id']]['tax_rate'] ) . ' %': '',
972
- );
973
- }
974
-
975
- }
976
-
977
- return apply_filters( 'wpo_wcpdf_order_taxes', $taxes, $this );
978
- }
979
- }
980
-
981
- /**
982
- * Return/show the order grand total
983
- */
984
- public function get_order_grand_total( $tax = 'incl' ) {
985
- if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 ) {
986
- // WC 2.1 or newer is used
987
- $total_unformatted = $this->order->get_total();
988
- } else {
989
- // Backwards compatibility
990
- $total_unformatted = $this->order->get_order_total();
991
- }
992
-
993
- if ($tax == 'excl' ) {
994
- $total = $this->format_price( $total_unformatted - $this->order->get_total_tax() );
995
- $label = __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' );
996
- } else {
997
- $total = $this->format_price( ( $total_unformatted ) );
998
- $label = __( 'Total', 'woocommerce-pdf-invoices-packing-slips' );
999
- }
1000
-
1001
- $grand_total = array(
1002
- 'label' => $label,
1003
- 'value' => $total,
1004
- );
1005
-
1006
- return apply_filters( 'wpo_wcpdf_order_grand_total', $grand_total, $tax, $this );
1007
- }
1008
- public function order_grand_total( $tax = 'incl' ) {
1009
- $grand_total = $this->get_order_grand_total( $tax );
1010
- echo $grand_total['value'];
1011
- }
1012
-
1013
-
1014
- /**
1015
- * Return/Show shipping notes
1016
- */
1017
- public function get_shipping_notes() {
1018
- if ( $this->is_refund( $this->order ) ) {
1019
- // return reason for refund if order is a refund
1020
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
1021
- $shipping_notes = $this->order->get_reason();
1022
- } elseif ( method_exists($this->order, 'get_refund_reason') ) {
1023
- $shipping_notes = $this->order->get_refund_reason();
1024
- } else {
1025
- $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1026
- }
1027
- } else {
1028
- $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1029
- }
1030
- return apply_filters( 'wpo_wcpdf_shipping_notes', $shipping_notes, $this );
1031
- }
1032
- public function shipping_notes() {
1033
- echo $this->get_shipping_notes();
1034
- }
1035
-
1036
- /**
1037
- * wrapper for wc_price, ensuring currency is always passed
1038
- */
1039
- public function format_price( $price, $args = array() ) {
1040
- if ( function_exists( 'wc_price' ) ) { // WC 2.1+
1041
- $args['currency'] = WCX_Order::get_prop( $this->order, 'currency' );
1042
- $formatted_price = wc_price( $price, $args );
1043
- } else {
1044
- $formatted_price = woocommerce_price( $price );
1045
- }
1046
-
1047
- return $formatted_price;
1048
- }
1049
- public function wc_price( $price, $args = array() ) {
1050
- return $this->format_price( $price, $args );
1051
- }
1052
-
1053
- /**
1054
- * Gets price - formatted for display.
1055
- *
1056
- * @access public
1057
- * @param mixed $item
1058
- * @return string
1059
- */
1060
- public function get_formatted_item_price ( $item, $type, $tax_display = '' ) {
1061
- if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1062
- return;
1063
- }
1064
-
1065
- $divide_by = ($type == 'single' && $item['qty'] != 0 )?abs($item['qty']):1; //divide by 1 if $type is not 'single' (thus 'total')
1066
- if ( $tax_display == 'excl' ) {
1067
- $item_price = $this->format_price( ($this->order->get_line_subtotal( $item )) / $divide_by );
1068
- } else {
1069
- $item_price = $this->format_price( ($this->order->get_line_subtotal( $item, true )) / $divide_by );
1070
- }
1071
-
1072
- return $item_price;
1073
- }
1074
-
1075
- public function get_invoice_number() {
1076
- // Call the woocommerce_invoice_number filter and let third-party plugins set a number.
1077
- // Default is null, so we can detect whether a plugin has set the invoice number
1078
- $third_party_invoice_number = apply_filters( 'woocommerce_invoice_number', null, $this->order_id );
1079
- if ($third_party_invoice_number !== null) {
1080
- return $third_party_invoice_number;
1081
- }
1082
-
1083
- if ( $invoice_number = $this->get_number('invoice') ) {
1084
- return $formatted_invoice_number = $invoice_number->get_formatted();
1085
- } else {
1086
- return '';
1087
- }
1088
- }
1089
-
1090
- public function invoice_number() {
1091
- echo $this->get_invoice_number();
1092
- }
1093
-
1094
- public function get_invoice_date() {
1095
- if ( $invoice_date = $this->get_date('invoice') ) {
1096
- return $invoice_date->date_i18n( apply_filters( 'wpo_wcpdf_date_format', wc_date_format(), $this ) );
1097
- } else {
1098
- return '';
1099
- }
1100
- }
1101
-
1102
- public function invoice_date() {
1103
- echo $this->get_invoice_date();
1104
- }
1105
-
1106
-
1107
- }
1108
-
1109
  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
+ $address_comparison_fields = apply_filters( 'wpo_wcpdf_address_comparison_fields', array(
71
+ 'first_name',
72
+ 'last_name',
73
+ 'company',
74
+ 'address_1',
75
+ 'address_2',
76
+ 'city',
77
+ 'state',
78
+ 'postcode',
79
+ 'country'
80
+ ), $this );
81
+
82
+ foreach ($address_comparison_fields as $address_field) {
83
+ $billing_field = WCX_Order::get_prop( $order, "billing_{$address_field}", 'view');
84
+ $shipping_field = WCX_Order::get_prop( $order, "shipping_{$address_field}", 'view');
85
+ if ( $shipping_field != $billing_field ) {
86
+ // this address field is different -> ships to different address!
87
+ return true;
88
+ }
89
+ }
90
+
91
+ //if we got here, it means the addresses are equal -> doesn't ship to different address!
92
+ return apply_filters( 'wpo_wcpdf_ships_to_different_address', false, $order, $this );
93
+ }
94
+
95
+ /**
96
+ * Return/Show billing address
97
+ */
98
+ public function get_billing_address() {
99
+ // always prefer parent billing address for refunds
100
+ if ( $this->is_refund( $this->order ) ) {
101
+ // temporarily switch order to make all filters / order calls work correctly
102
+ $refund = $this->order;
103
+ $this->order = $this->get_refund_parent( $this->order );
104
+ $address = apply_filters( 'wpo_wcpdf_billing_address', $this->order->get_formatted_billing_address(), $this );
105
+ // switch back & unset
106
+ $this->order = $refund;
107
+ unset($refund);
108
+ } elseif ( $address = $this->order->get_formatted_billing_address() ) {
109
+ // regular shop_order
110
+ $address = apply_filters( 'wpo_wcpdf_billing_address', $address, $this );
111
+ } else {
112
+ // no address
113
+ $address = apply_filters( 'wpo_wcpdf_billing_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
114
+ }
115
+
116
+ return $address;
117
+ }
118
+ public function billing_address() {
119
+ echo $this->get_billing_address();
120
+ }
121
+
122
+ /**
123
+ * Return/Show billing email
124
+ */
125
+ public function get_billing_email() {
126
+ $billing_email = WCX_Order::get_prop( $this->order, 'billing_email', 'view' );
127
+
128
+ if ( !$billing_email && $this->is_refund( $this->order ) ) {
129
+ // try parent
130
+ $parent_order = $this->get_refund_parent( $this->order );
131
+ $billing_email = WCX_Order::get_prop( $parent_order, 'billing_email', 'view' );
132
+ }
133
+
134
+ return apply_filters( 'wpo_wcpdf_billing_email', $billing_email, $this );
135
+ }
136
+ public function billing_email() {
137
+ echo $this->get_billing_email();
138
+ }
139
+
140
+ /**
141
+ * Return/Show billing phone
142
+ */
143
+ public function get_billing_phone() {
144
+ $billing_phone = WCX_Order::get_prop( $this->order, 'billing_phone', 'view' );
145
+
146
+ if ( !$billing_phone && $this->is_refund( $this->order ) ) {
147
+ // try parent
148
+ $parent_order = $this->get_refund_parent( $this->order );
149
+ $billing_phone = WCX_Order::get_prop( $parent_order, 'billing_phone', 'view' );
150
+ }
151
+
152
+ return apply_filters( 'wpo_wcpdf_billing_phone', $billing_phone, $this );
153
+ }
154
+ public function billing_phone() {
155
+ echo $this->get_billing_phone();
156
+ }
157
+
158
+ /**
159
+ * Return/Show shipping address
160
+ */
161
+ public function get_shipping_address() {
162
+ // always prefer parent shipping address for refunds
163
+ if ( $this->is_refund( $this->order ) ) {
164
+ // temporarily switch order to make all filters / order calls work correctly
165
+ $refund = $this->order;
166
+ $this->order = $this->get_refund_parent( $this->order );
167
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', $this->order->get_formatted_shipping_address(), $this );
168
+ // switch back & unset
169
+ $this->order = $refund;
170
+ unset($refund);
171
+ } elseif ( $address = $this->order->get_formatted_shipping_address() ) {
172
+ // regular shop_order
173
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', $address, $this );
174
+ } else {
175
+ // no address
176
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
177
+ }
178
+
179
+ return $address;
180
+ }
181
+ public function shipping_address() {
182
+ echo $this->get_shipping_address();
183
+ }
184
+
185
+ /**
186
+ * Return/Show a custom field
187
+ */
188
+ public function get_custom_field( $field_name ) {
189
+ if ( !$this->is_order_prop( $field_name ) ) {
190
+ $custom_field = WCX_Order::get_meta( $this->order, $field_name, true );
191
+ }
192
+ // if not found, try prefixed with underscore (not when ACF is active!)
193
+ if ( empty( $custom_field ) && substr( $field_name, 0, 1 ) !== '_' && !$this->is_order_prop( "_{$field_name}" ) && !class_exists('ACF') ) {
194
+ $custom_field = WCX_Order::get_meta( $this->order, "_{$field_name}", true );
195
+ }
196
+
197
+ // WC3.0 fallback to properties
198
+ $property = str_replace('-', '_', sanitize_title( ltrim($field_name, '_') ) );
199
+ if ( empty( $custom_field ) && is_callable( array( $this->order, "get_{$property}" ) ) ) {
200
+ $custom_field = $this->order->{"get_{$property}"}( 'view' );
201
+ }
202
+
203
+ // fallback to parent for refunds
204
+ if ( empty( $custom_field ) && $this->is_refund( $this->order ) ) {
205
+ $parent_order = $this->get_refund_parent( $this->order );
206
+ if ( !$this->is_order_prop( $field_name ) ) {
207
+ $custom_field = WCX_Order::get_meta( $parent_order, $field_name, true );
208
+ }
209
+
210
+ // WC3.0 fallback to properties
211
+ if ( empty( $custom_field ) && is_callable( array( $parent_order, "get_{$property}" ) ) ) {
212
+ $custom_field = $parent_order->{"get_{$property}"}( 'view' );
213
+ }
214
+ }
215
+
216
+ return apply_filters( 'wpo_wcpdf_billing_custom_field', $custom_field, $this );
217
+ }
218
+ public function custom_field( $field_name, $field_label = '', $display_empty = false ) {
219
+ $custom_field = $this->get_custom_field( $field_name );
220
+ if (!empty($field_label)){
221
+ // add a a trailing space to the label
222
+ $field_label .= ' ';
223
+ }
224
+
225
+ if (!empty($custom_field) || $display_empty) {
226
+ echo $field_label . nl2br ($custom_field);
227
+ }
228
+ }
229
+
230
+ public function is_order_prop( $key ) {
231
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '<' ) ) {
232
+ return false; // WC 2.X didn't have CRUD
233
+ }
234
+ // Taken from WC class
235
+ $order_props = array(
236
+ // Abstract order props
237
+ 'parent_id',
238
+ 'status',
239
+ 'currency',
240
+ 'version',
241
+ 'prices_include_tax',
242
+ 'date_created',
243
+ 'date_modified',
244
+ 'discount_total',
245
+ 'discount_tax',
246
+ 'shipping_total',
247
+ 'shipping_tax',
248
+ 'cart_tax',
249
+ 'total',
250
+ 'total_tax',
251
+ // Order props
252
+ 'customer_id',
253
+ 'order_key',
254
+ 'billing_first_name',
255
+ 'billing_last_name',
256
+ 'billing_company',
257
+ 'billing_address_1',
258
+ 'billing_address_2',
259
+ 'billing_city',
260
+ 'billing_state',
261
+ 'billing_postcode',
262
+ 'billing_country',
263
+ 'billing_email',
264
+ 'billing_phone',
265
+ 'shipping_first_name',
266
+ 'shipping_last_name',
267
+ 'shipping_company',
268
+ 'shipping_address_1',
269
+ 'shipping_address_2',
270
+ 'shipping_city',
271
+ 'shipping_state',
272
+ 'shipping_postcode',
273
+ 'shipping_country',
274
+ 'payment_method',
275
+ 'payment_method_title',
276
+ 'transaction_id',
277
+ 'customer_ip_address',
278
+ 'customer_user_agent',
279
+ 'created_via',
280
+ 'customer_note',
281
+ 'date_completed',
282
+ 'date_paid',
283
+ 'cart_hash',
284
+ );
285
+ return in_array($key, $order_props);
286
+ }
287
+
288
+ /**
289
+ * Return/show product attribute
290
+ */
291
+ public function get_product_attribute( $attribute_name, $product ) {
292
+ // first, check the text attributes
293
+ $attributes = $product->get_attributes();
294
+ $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
295
+ if (array_key_exists( sanitize_title( $attribute_name ), $attributes) ) {
296
+ $attribute = $product->get_attribute ( $attribute_name );
297
+ } elseif (array_key_exists( sanitize_title( $attribute_key ), $attributes) ) {
298
+ $attribute = $product->get_attribute ( $attribute_key );
299
+ }
300
+
301
+ if (empty($attribute)) {
302
+ // not a text attribute, try attribute taxonomy
303
+ $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
304
+ $product_id = WCX_Product::get_prop($product, 'id');
305
+ $product_terms = @wc_get_product_terms( $product_id, $attribute_key, array( 'fields' => 'names' ) );
306
+ // check if not empty, then display
307
+ if ( !empty($product_terms) ) {
308
+ $attribute = array_shift( $product_terms );
309
+ }
310
+ }
311
+
312
+ // WC3.0+ fallback parent product for variations
313
+ if ( empty($attribute) && version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) && $product->is_type( 'variation' ) ) {
314
+ $product = wc_get_product( $product->get_parent_id() );
315
+ $attribute = $this->get_product_attribute( $attribute_name, $product );
316
+ }
317
+
318
+ return isset($attribute) ? $attribute : false;
319
+ }
320
+ public function product_attribute( $attribute_name, $product ) {
321
+ echo $this->get_product_attribute( $attribute_name, $product );
322
+ }
323
+
324
+ /**
325
+ * Return/Show order notes
326
+ * could use $order->get_customer_order_notes(), but that filters out private notes already
327
+ */
328
+ public function get_order_notes( $filter = 'customer' ) {
329
+ if ( $this->is_refund( $this->order ) ) {
330
+ $post_id = $this->get_refund_parent_id( $this->order );
331
+ } else {
332
+ $post_id = $this->order_id;
333
+ }
334
+
335
+ if ( empty( $post_id ) ) {
336
+ return; // prevent order notes from all orders showing when document is not loaded properly
337
+ }
338
+
339
+ $args = array(
340
+ 'post_id' => $post_id,
341
+ 'approve' => 'approve',
342
+ 'type' => 'order_note'
343
+ );
344
+
345
+ remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
346
+
347
+ $notes = get_comments( $args );
348
+
349
+ add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
350
+
351
+ if ( $notes ) {
352
+ foreach( $notes as $key => $note ) {
353
+ if ( $filter == 'customer' && !get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
354
+ unset($notes[$key]);
355
+ }
356
+ if ( $filter == 'private' && get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
357
+ unset($notes[$key]);
358
+ }
359
+ }
360
+ return $notes;
361
+ }
362
+ }
363
+ public function order_notes( $filter = 'customer' ) {
364
+ $notes = $this->get_order_notes( $filter );
365
+ if ( $notes ) {
366
+ foreach( $notes as $note ) {
367
+ ?>
368
+ <div class="note_content">
369
+ <?php echo wpautop( wptexturize( wp_kses_post( $note->comment_content ) ) ); ?>
370
+ </div>
371
+ <?php
372
+ }
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Return/Show the current date
378
+ */
379
+ public function get_current_date() {
380
+ return apply_filters( 'wpo_wcpdf_date', date_i18n( get_option( 'date_format' ) ) );
381
+ }
382
+ public function current_date() {
383
+ echo $this->get_current_date();
384
+ }
385
+
386
+ /**
387
+ * Return/Show payment method
388
+ */
389
+ public function get_payment_method() {
390
+ $payment_method_label = __( 'Payment method', 'woocommerce-pdf-invoices-packing-slips' );
391
+
392
+ if ( $this->is_refund( $this->order ) ) {
393
+ $parent_order = $this->get_refund_parent( $this->order );
394
+ $payment_method_title = WCX_Order::get_prop( $parent_order, 'payment_method_title', 'view' );
395
+ } else {
396
+ $payment_method_title = WCX_Order::get_prop( $this->order, 'payment_method_title', 'view' );
397
+ }
398
+
399
+ $payment_method = __( $payment_method_title, 'woocommerce' );
400
+
401
+ return apply_filters( 'wpo_wcpdf_payment_method', $payment_method, $this );
402
+ }
403
+ public function payment_method() {
404
+ echo $this->get_payment_method();
405
+ }
406
+
407
+ /**
408
+ * Return/Show shipping method
409
+ */
410
+ public function get_shipping_method() {
411
+ $shipping_method_label = __( 'Shipping method', 'woocommerce-pdf-invoices-packing-slips' );
412
+ $shipping_method = __( $this->order->get_shipping_method(), 'woocommerce' );
413
+ return apply_filters( 'wpo_wcpdf_shipping_method', $shipping_method, $this );
414
+ }
415
+ public function shipping_method() {
416
+ echo $this->get_shipping_method();
417
+ }
418
+
419
+ /**
420
+ * Return/Show order number
421
+ */
422
+ public function get_order_number() {
423
+ // try parent first
424
+ if ( $this->is_refund( $this->order ) ) {
425
+ $parent_order = $this->get_refund_parent( $this->order );
426
+ $order_number = $parent_order->get_order_number();
427
+ } else {
428
+ $order_number = $this->order->get_order_number();
429
+ }
430
+
431
+ // Trim the hash to have a clean number but still
432
+ // support any filters that were applied before.
433
+ $order_number = ltrim($order_number, '#');
434
+ return apply_filters( 'wpo_wcpdf_order_number', $order_number, $this );
435
+ }
436
+ public function order_number() {
437
+ echo $this->get_order_number();
438
+ }
439
+
440
+ /**
441
+ * Return/Show the order date
442
+ */
443
+ public function get_order_date() {
444
+ if ( $this->is_refund( $this->order ) ) {
445
+ $parent_order = $this->get_refund_parent( $this->order );
446
+ $order_date = WCX_Order::get_prop( $parent_order, 'date_created' );
447
+ } else {
448
+ $order_date = WCX_Order::get_prop( $this->order, 'date_created' );
449
+ }
450
+
451
+ $date = $order_date->date_i18n( get_option( 'date_format' ) );
452
+ $mysql_date = $order_date->date( "Y-m-d H:i:s" );
453
+ return apply_filters( 'wpo_wcpdf_order_date', $date, $mysql_date, $this );
454
+ }
455
+ public function order_date() {
456
+ echo $this->get_order_date();
457
+ }
458
+
459
+ /**
460
+ * Return the order items
461
+ */
462
+ public function get_order_items() {
463
+ $items = $this->order->get_items();
464
+ $data_list = array();
465
+
466
+ if( sizeof( $items ) > 0 ) {
467
+ foreach ( $items as $item_id => $item ) {
468
+ // Array with data for the pdf template
469
+ $data = array();
470
+
471
+ // Set the item_id
472
+ $data['item_id'] = $item_id;
473
+
474
+ // Set the id
475
+ $data['product_id'] = $item['product_id'];
476
+ $data['variation_id'] = $item['variation_id'];
477
+
478
+ // Set item name
479
+ $data['name'] = $item['name'];
480
+
481
+ // Set item quantity
482
+ $data['quantity'] = $item['qty'];
483
+
484
+ // Set the line total (=after discount)
485
+ $data['line_total'] = $this->format_price( $item['line_total'] );
486
+ $data['single_line_total'] = $this->format_price( $item['line_total'] / max( 1, abs( $item['qty'] ) ) );
487
+ $data['line_tax'] = $this->format_price( $item['line_tax'] );
488
+ $data['single_line_tax'] = $this->format_price( $item['line_tax'] / max( 1, abs( $item['qty'] ) ) );
489
+
490
+ $line_tax_data = maybe_unserialize( isset( $item['line_tax_data'] ) ? $item['line_tax_data'] : '' );
491
+ $data['tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, true );
492
+ $data['calculated_tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data, false );
493
+
494
+ // Set the line subtotal (=before discount)
495
+ $data['line_subtotal'] = $this->format_price( $item['line_subtotal'] );
496
+ $data['line_subtotal_tax'] = $this->format_price( $item['line_subtotal_tax'] );
497
+ $data['ex_price'] = $this->get_formatted_item_price( $item, 'total', 'excl' );
498
+ $data['price'] = $this->get_formatted_item_price( $item, 'total' );
499
+ $data['order_price'] = $this->order->get_formatted_line_subtotal( $item ); // formatted according to WC settings
500
+
501
+ // Calculate the single price with the same rules as the formatted line subtotal (!)
502
+ // = before discount
503
+ $data['ex_single_price'] = $this->get_formatted_item_price( $item, 'single', 'excl' );
504
+ $data['single_price'] = $this->get_formatted_item_price( $item, 'single' );
505
+
506
+ // Pass complete item array
507
+ $data['item'] = $item;
508
+
509
+ // Get the product to add more info
510
+ $product = $this->order->get_product_from_item( $item );
511
+
512
+ // Checking fo existance, thanks to MDesigner0
513
+ if( !empty( $product ) ) {
514
+ // Thumbnail (full img tag)
515
+ $data['thumbnail'] = $this->get_thumbnail( $product );
516
+
517
+ // Set item SKU
518
+ $data['sku'] = $product->get_sku();
519
+
520
+ // Set item weight
521
+ $data['weight'] = $product->get_weight();
522
+
523
+ // Set item dimensions
524
+ $data['dimensions'] = WCX_Product::get_dimensions( $product );
525
+
526
+ // Pass complete product object
527
+ $data['product'] = $product;
528
+
529
+ } else {
530
+ $data['product'] = null;
531
+ }
532
+
533
+ // Set item meta
534
+ if (function_exists('wc_display_item_meta')) { // WC3.0+
535
+ $data['meta'] = wc_display_item_meta( $item, array(
536
+ 'echo' => false,
537
+ ) );
538
+ } else {
539
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '<' ) ) {
540
+ $meta = new \WC_Order_Item_Meta( $item['item_meta'], $product );
541
+ } else { // pass complete item for WC2.4+
542
+ $meta = new \WC_Order_Item_Meta( $item, $product );
543
+ }
544
+ $data['meta'] = $meta->display( false, true );
545
+ }
546
+
547
+ $data_list[$item_id] = apply_filters( 'wpo_wcpdf_order_item_data', $data, $this->order, $this->get_type() );
548
+ }
549
+ }
550
+
551
+ return apply_filters( 'wpo_wcpdf_order_items_data', $data_list, $this->order, $this->get_type() );
552
+ }
553
+
554
+ /**
555
+ * Get the tax rates/percentages for a given tax class
556
+ * @param string $tax_class tax class slug
557
+ * @return string $tax_rates imploded list of tax rates
558
+ */
559
+ public function get_tax_rate( $tax_class, $line_total, $line_tax, $line_tax_data = '', $force_calculation = false ) {
560
+ // first try the easy wc2.2+ way, using line_tax_data
561
+ if ( !empty( $line_tax_data ) && isset($line_tax_data['total']) ) {
562
+ $tax_rates = array();
563
+
564
+ $line_taxes = $line_tax_data['subtotal'];
565
+ foreach ( $line_taxes as $tax_id => $tax ) {
566
+ if ( isset($tax) && $tax !== '' ) {
567
+ $tax_rate = $this->get_tax_rate_by_id( $tax_id );
568
+ if ( $tax_rate !== false && $force_calculation !== false ) {
569
+ $tax_rates[] = $tax_rate . ' %';
570
+ } else {
571
+ $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
572
+ }
573
+ }
574
+ }
575
+
576
+ // apply decimal setting
577
+ if (function_exists('wc_get_price_decimal_separator')) {
578
+ foreach ($tax_rates as &$tax_rate) {
579
+ $tax_rate = str_replace('.', wc_get_price_decimal_separator(), strval($tax_rate) );
580
+ }
581
+ }
582
+
583
+ $tax_rates = implode(' ,', $tax_rates );
584
+ return $tax_rates;
585
+ }
586
+
587
+ if ( $line_tax == 0 ) {
588
+ return '-'; // no need to determine tax rate...
589
+ }
590
+
591
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 && !apply_filters( 'wpo_wcpdf_calculate_tax_rate', false ) ) {
592
+ // WC 2.1 or newer is used
593
+ $tax = new \WC_Tax();
594
+ $taxes = $tax->get_rates( $tax_class );
595
+
596
+ $tax_rates = array();
597
+
598
+ foreach ($taxes as $tax) {
599
+ $tax_rates[$tax['label']] = round( $tax['rate'], 2 ).' %';
600
+ }
601
+
602
+ if (empty($tax_rates)) {
603
+ // one last try: manually calculate
604
+ $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
605
+ }
606
+
607
+ $tax_rates = implode(' ,', $tax_rates );
608
+ } else {
609
+ // Backwards compatibility/fallback: calculate tax from line items
610
+ $tax_rates[] = $this->calculate_tax_rate( $line_total, $line_tax );
611
+ }
612
+
613
+ return $tax_rates;
614
+ }
615
+
616
+ public function calculate_tax_rate( $price_ex_tax, $tax ) {
617
+ $precision = apply_filters( 'wpo_wcpdf_calculate_tax_rate_precision', 1 );
618
+ if ( $price_ex_tax != 0) {
619
+ $tax_rate = round( ($tax / $price_ex_tax)*100, $precision ).' %';
620
+ } else {
621
+ $tax_rate = '-';
622
+ }
623
+ return $tax_rate;
624
+ }
625
+
626
+ /**
627
+ * Returns the percentage rate (float) for a given tax rate ID.
628
+ * @param int $rate_id woocommerce tax rate id
629
+ * @return float $rate percentage rate
630
+ */
631
+ public function get_tax_rate_by_id( $rate_id ) {
632
+ global $wpdb;
633
+ $rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $rate_id ) );
634
+ if ($rate === NULL) {
635
+ return false;
636
+ } else {
637
+ return (float) $rate;
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Returns a an array with rate_id => tax rate data (array) of all tax rates in woocommerce
643
+ * @return array $tax_rate_ids keyed by id
644
+ */
645
+ public function get_tax_rate_ids() {
646
+ global $wpdb;
647
+ $rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates" );
648
+
649
+ $tax_rate_ids = array();
650
+ foreach ($rates as $rate) {
651
+ $rate_id = $rate->tax_rate_id;
652
+ unset($rate->tax_rate_id);
653
+ $tax_rate_ids[$rate_id] = (array) $rate;
654
+ }
655
+
656
+ return $tax_rate_ids;
657
+ }
658
+
659
+ /**
660
+ * Returns the main product image ID
661
+ * Adapted from the WC_Product class
662
+ * (does not support thumbnail sizes)
663
+ *
664
+ * @access public
665
+ * @return string
666
+ */
667
+ public function get_thumbnail_id ( $product ) {
668
+ global $woocommerce;
669
+
670
+ $product_id = WCX_Product::get_id( $product );
671
+
672
+ if ( has_post_thumbnail( $product_id ) ) {
673
+ $thumbnail_id = get_post_thumbnail_id ( $product_id );
674
+ } elseif ( ( $parent_id = wp_get_post_parent_id( $product_id ) ) && has_post_thumbnail( $parent_id ) ) {
675
+ $thumbnail_id = get_post_thumbnail_id ( $parent_id );
676
+ } else {
677
+ $thumbnail_id = false;
678
+ }
679
+
680
+ return $thumbnail_id;
681
+ }
682
+
683
+ /**
684
+ * Returns the thumbnail image tag
685
+ *
686
+ * uses the internal WooCommerce/WP functions and extracts the image url or path
687
+ * rather than the thumbnail ID, to simplify the code and make it possible to
688
+ * filter for different thumbnail sizes
689
+ *
690
+ * @access public
691
+ * @return string
692
+ */
693
+ public function get_thumbnail ( $product ) {
694
+ // Get default WooCommerce img tag (url/http)
695
+ $size = apply_filters( 'wpo_wcpdf_thumbnail_size', 'shop_thumbnail' );
696
+ $thumbnail_img_tag_url = $product->get_image( $size, array( 'title' => '' ) );
697
+
698
+ // Extract the url from img
699
+ preg_match('/<img(.*)src(.*)=(.*)"(.*)"/U', $thumbnail_img_tag_url, $thumbnail_url );
700
+ $thumbnail_url = array_pop($thumbnail_url);
701
+ // remove http/https from image tag url to avoid mixed origin conflicts
702
+ $contextless_thumbnail_url = ltrim( str_replace(array('http://','https://'), '', $thumbnail_url ), '/' );
703
+
704
+ // convert url to path
705
+ if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
706
+ $forwardslash_basepath = str_replace('\\','/', ABSPATH);
707
+ $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(get_site_url()));
708
+ } else {
709
+ // bedrock e.a
710
+ $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
711
+ $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(WP_CONTENT_URL));
712
+ }
713
+ $thumbnail_path = str_replace( $contextless_site_url, trailingslashit( $forwardslash_basepath ), $contextless_thumbnail_url);
714
+
715
+ // fallback if thumbnail file doesn't exist
716
+ if (apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path)) {
717
+ if ($thumbnail_id = $this->get_thumbnail_id( $product ) ) {
718
+ $thumbnail_path = get_attached_file( $thumbnail_id );
719
+ }
720
+ }
721
+
722
+ // Thumbnail (full img tag)
723
+ if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($thumbnail_path) ) {
724
+ // load img with server path by default
725
+ $thumbnail = sprintf('<img width="90" height="90" src="%s" class="attachment-shop_thumbnail wp-post-image">', $thumbnail_path );
726
+ } elseif ( apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path) ) {
727
+ // should use paths but file not found, replace // with http(s):// for dompdf compatibility
728
+ if ( substr( $thumbnail_url, 0, 2 ) === "//" ) {
729
+ $prefix = is_ssl() ? 'https://' : 'http://';
730
+ $https_thumbnail_url = $prefix . ltrim( $thumbnail_url, '/' );
731
+ $thumbnail_img_tag_url = str_replace($thumbnail_url, $https_thumbnail_url, $thumbnail_img_tag_url);
732
+ }
733
+ $thumbnail = $thumbnail_img_tag_url;
734
+ } else {
735
+ // load img with http url when filtered
736
+ $thumbnail = $thumbnail_img_tag_url;
737
+ }
738
+
739
+ // die($thumbnail);
740
+ return $thumbnail;
741
+ }
742
+
743
+ /**
744
+ * Return the order totals listing
745
+ */
746
+ public function get_woocommerce_totals() {
747
+ // get totals and remove the semicolon
748
+ $totals = apply_filters( 'wpo_wcpdf_raw_order_totals', $this->order->get_order_item_totals(), $this->order );
749
+
750
+ // remove the colon for every label
751
+ foreach ( $totals as $key => $total ) {
752
+ $label = $total['label'];
753
+ $colon = strrpos( $label, ':' );
754
+ if( $colon !== false ) {
755
+ $label = substr_replace( $label, '', $colon, 1 );
756
+ }
757
+ $totals[$key]['label'] = $label;
758
+ }
759
+
760
+ // WC2.4 fix order_total for refunded orders
761
+ // not if this is the actual refund!
762
+ if ( ! $this->is_refund( $this->order ) ) {
763
+ $total_refunded = method_exists($this->order, 'get_total_refunded') ? $this->order->get_total_refunded() : 0;
764
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '>=' ) && isset($totals['order_total']) && $total_refunded ) {
765
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
766
+ $tax_display = get_option( 'woocommerce_tax_display_cart' );
767
+ } else {
768
+ $tax_display = WCX_Order::get_prop( $this->order, 'tax_display_cart' );
769
+ }
770
+
771
+ $totals['order_total']['value'] = wc_price( $this->order->get_total(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) );
772
+ $order_total = $this->order->get_total();
773
+ $tax_string = '';
774
+
775
+ // Tax for inclusive prices
776
+ if ( wc_tax_enabled() && 'incl' == $tax_display ) {
777
+ $tax_string_array = array();
778
+ if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
779
+ foreach ( $this->order->get_tax_totals() as $code => $tax ) {
780
+ $tax_amount = $tax->formatted_amount;
781
+ $tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
782
+ }
783
+ } else {
784
+ $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() );
785
+ }
786
+ if ( ! empty( $tax_string_array ) ) {
787
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.6', '>=' ) ) {
788
+ $tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
789
+ } else {
790
+ // use old capitalized string
791
+ $tax_string = ' ' . sprintf( __( '(Includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
792
+ }
793
+ }
794
+ }
795
+
796
+ $totals['order_total']['value'] .= $tax_string;
797
+ }
798
+
799
+ // remove refund lines (shouldn't be in invoice)
800
+ foreach ( $totals as $key => $total ) {
801
+ if ( strpos($key, 'refund_') !== false ) {
802
+ unset( $totals[$key] );
803
+ }
804
+ }
805
+
806
+ }
807
+
808
+ return apply_filters( 'wpo_wcpdf_woocommerce_totals', $totals, $this->order, $this->get_type() );
809
+ }
810
+
811
+ /**
812
+ * Return/show the order subtotal
813
+ */
814
+ public function get_order_subtotal( $tax = 'excl', $discount = 'incl' ) { // set $tax to 'incl' to include tax, same for $discount
815
+ //$compound = ($discount == 'incl')?true:false;
816
+ $subtotal = $this->order->get_subtotal_to_display( false, $tax );
817
+
818
+ $subtotal = ($pos = strpos($subtotal, ' <small')) ? substr($subtotal, 0, $pos) : $subtotal; //removing the 'excluding tax' text
819
+
820
+ $subtotal = array (
821
+ 'label' => __('Subtotal', 'woocommerce-pdf-invoices-packing-slips' ),
822
+ 'value' => $subtotal,
823
+ );
824
+
825
+ return apply_filters( 'wpo_wcpdf_order_subtotal', $subtotal, $tax, $discount, $this );
826
+ }
827
+ public function order_subtotal( $tax = 'excl', $discount = 'incl' ) {
828
+ $subtotal = $this->get_order_subtotal( $tax, $discount );
829
+ echo $subtotal['value'];
830
+ }
831
+
832
+ /**
833
+ * Return/show the order shipping costs
834
+ */
835
+ public function get_order_shipping( $tax = 'excl' ) { // set $tax to 'incl' to include tax
836
+ $shipping_cost = WCX_Order::get_prop( $this->order, 'shipping_total', 'view' );
837
+ $shipping_tax = WCX_Order::get_prop( $this->order, 'shipping_tax', 'view' );
838
+
839
+ if ($tax == 'excl' ) {
840
+ $formatted_shipping_cost = $this->format_price( $shipping_cost );
841
+ } else {
842
+ $formatted_shipping_cost = $this->format_price( $shipping_cost + $shipping_tax );
843
+ }
844
+
845
+ $shipping = array (
846
+ 'label' => __('Shipping', 'woocommerce-pdf-invoices-packing-slips' ),
847
+ 'value' => $formatted_shipping_cost,
848
+ 'tax' => $this->format_price( $shipping_tax ),
849
+ );
850
+ return apply_filters( 'wpo_wcpdf_order_shipping', $shipping, $tax, $this );
851
+ }
852
+ public function order_shipping( $tax = 'excl' ) {
853
+ $shipping = $this->get_order_shipping( $tax );
854
+ echo $shipping['value'];
855
+ }
856
+
857
+ /**
858
+ * Return/show the total discount
859
+ */
860
+ public function get_order_discount( $type = 'total', $tax = 'incl' ) {
861
+ if ( $tax == 'incl' ) {
862
+ switch ($type) {
863
+ case 'cart':
864
+ // Cart Discount - pre-tax discounts. (deprecated in WC2.3)
865
+ $discount_value = $this->order->get_cart_discount();
866
+ break;
867
+ case 'order':
868
+ // Order Discount - post-tax discounts. (deprecated in WC2.3)
869
+ $discount_value = $this->order->get_order_discount();
870
+ break;
871
+ case 'total':
872
+ // Total Discount
873
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
874
+ $discount_value = $this->order->get_total_discount( false ); // $ex_tax = false
875
+ } else {
876
+ // WC2.2 and older: recalculate to include tax
877
+ $discount_value = 0;
878
+ $items = $this->order->get_items();;
879
+ if( sizeof( $items ) > 0 ) {
880
+ foreach( $items as $item ) {
881
+ $discount_value += ($item['line_subtotal'] + $item['line_subtotal_tax']) - ($item['line_total'] + $item['line_tax']);
882
+ }
883
+ }
884
+ }
885
+
886
+ break;
887
+ default:
888
+ // Total Discount - Cart & Order Discounts combined
889
+ $discount_value = $this->order->get_total_discount();
890
+ break;
891
+ }
892
+ } else { // calculate discount excluding tax
893
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
894
+ $discount_value = $this->order->get_total_discount( true ); // $ex_tax = true
895
+ } else {
896
+ // WC2.2 and older: recalculate to exclude tax
897
+ $discount_value = 0;
898
+
899
+ $items = $this->order->get_items();;
900
+ if( sizeof( $items ) > 0 ) {
901
+ foreach( $items as $item ) {
902
+ $discount_value += ($item['line_subtotal'] - $item['line_total']);
903
+ }
904
+ }
905
+ }
906
+ }
907
+
908
+ $discount = array (
909
+ 'label' => __('Discount', 'woocommerce-pdf-invoices-packing-slips' ),
910
+ 'value' => $this->format_price( $discount_value ),
911
+ 'raw_value' => $discount_value,
912
+ );
913
+
914
+ if ( round( $discount_value, 3 ) != 0 ) {
915
+ return apply_filters( 'wpo_wcpdf_order_discount', $discount, $type, $tax, $this );
916
+ }
917
+ }
918
+ public function order_discount( $type = 'total', $tax = 'incl' ) {
919
+ $discount = $this->get_order_discount( $type, $tax );
920
+ echo $discount['value'];
921
+ }
922
+
923
+ /**
924
+ * Return the order fees
925
+ */
926
+ public function get_order_fees( $tax = 'excl' ) {
927
+ if ( $_fees = $this->order->get_fees() ) {
928
+ foreach( $_fees as $id => $fee ) {
929
+ if ($tax == 'excl' ) {
930
+ $fee_price = $this->format_price( $fee['line_total'] );
931
+ } else {
932
+ $fee_price = $this->format_price( $fee['line_total'] + $fee['line_tax'] );
933
+ }
934
+
935
+ $fees[ $id ] = array(
936
+ 'label' => $fee['name'],
937
+ 'value' => $fee_price,
938
+ 'line_total' => $this->format_price( $fee['line_total'] ),
939
+ 'line_tax' => $this->format_price( $fee['line_tax'] )
940
+ );
941
+ }
942
+ return $fees;
943
+ }
944
+ }
945
+
946
+ /**
947
+ * Return the order taxes
948
+ */
949
+ public function get_order_taxes() {
950
+ $tax_label = __( 'VAT', 'woocommerce-pdf-invoices-packing-slips' ); // register alternate label translation
951
+ $tax_label = __( 'Tax rate', 'woocommerce-pdf-invoices-packing-slips' );
952
+ $tax_rate_ids = $this->get_tax_rate_ids();
953
+ if ( $order_taxes = $this->order->get_taxes() ) {
954
+ foreach ( $order_taxes as $key => $tax ) {
955
+ if ( WCX::is_wc_version_gte_3_0() ) {
956
+ $taxes[ $key ] = array(
957
+ 'label' => $tax->get_label(),
958
+ 'value' => $this->format_price( $tax->get_tax_total() + $tax->get_shipping_tax_total() ),
959
+ 'rate_id' => $tax->get_rate_id(),
960
+ 'tax_amount' => $tax->get_tax_total(),
961
+ 'shipping_tax_amount' => $tax->get_shipping_tax_total(),
962
+ 'rate' => isset( $tax_rate_ids[ $tax->get_rate_id() ] ) ? ( (float) $tax_rate_ids[$tax->get_rate_id()]['tax_rate'] ) . ' %': '',
963
+ );
964
+ } else {
965
+ $taxes[ $key ] = array(
966
+ 'label' => isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ],
967
+ 'value' => $this->format_price( ( $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ] ) ),
968
+ 'rate_id' => $tax['rate_id'],
969
+ 'tax_amount' => $tax['tax_amount'],
970
+ 'shipping_tax_amount' => $tax['shipping_tax_amount'],
971
+ 'rate' => isset( $tax_rate_ids[ $tax['rate_id'] ] ) ? ( (float) $tax_rate_ids[$tax['rate_id']]['tax_rate'] ) . ' %': '',
972
+ );
973
+ }
974
+
975
+ }
976
+
977
+ return apply_filters( 'wpo_wcpdf_order_taxes', $taxes, $this );
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Return/show the order grand total
983
+ */
984
+ public function get_order_grand_total( $tax = 'incl' ) {
985
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 ) {
986
+ // WC 2.1 or newer is used
987
+ $total_unformatted = $this->order->get_total();
988
+ } else {
989
+ // Backwards compatibility
990
+ $total_unformatted = $this->order->get_order_total();
991
+ }
992
+
993
+ if ($tax == 'excl' ) {
994
+ $total = $this->format_price( $total_unformatted - $this->order->get_total_tax() );
995
+ $label = __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' );
996
+ } else {
997
+ $total = $this->format_price( ( $total_unformatted ) );
998
+ $label = __( 'Total', 'woocommerce-pdf-invoices-packing-slips' );
999
+ }
1000
+
1001
+ $grand_total = array(
1002
+ 'label' => $label,
1003
+ 'value' => $total,
1004
+ );
1005
+
1006
+ return apply_filters( 'wpo_wcpdf_order_grand_total', $grand_total, $tax, $this );
1007
+ }
1008
+ public function order_grand_total( $tax = 'incl' ) {
1009
+ $grand_total = $this->get_order_grand_total( $tax );
1010
+ echo $grand_total['value'];
1011
+ }
1012
+
1013
+
1014
+ /**
1015
+ * Return/Show shipping notes
1016
+ */
1017
+ public function get_shipping_notes() {
1018
+ if ( $this->is_refund( $this->order ) ) {
1019
+ // return reason for refund if order is a refund
1020
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
1021
+ $shipping_notes = $this->order->get_reason();
1022
+ } elseif ( method_exists($this->order, 'get_refund_reason') ) {
1023
+ $shipping_notes = $this->order->get_refund_reason();
1024
+ } else {
1025
+ $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1026
+ }
1027
+ } else {
1028
+ $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
1029
+ }
1030
+ return apply_filters( 'wpo_wcpdf_shipping_notes', $shipping_notes, $this );
1031
+ }
1032
+ public function shipping_notes() {
1033
+ echo $this->get_shipping_notes();
1034
+ }
1035
+
1036
+ /**
1037
+ * wrapper for wc_price, ensuring currency is always passed
1038
+ */
1039
+ public function format_price( $price, $args = array() ) {
1040
+ if ( function_exists( 'wc_price' ) ) { // WC 2.1+
1041
+ $args['currency'] = WCX_Order::get_prop( $this->order, 'currency' );
1042
+ $formatted_price = wc_price( $price, $args );
1043
+ } else {
1044
+ $formatted_price = woocommerce_price( $price );
1045
+ }
1046
+
1047
+ return $formatted_price;
1048
+ }
1049
+ public function wc_price( $price, $args = array() ) {
1050
+ return $this->format_price( $price, $args );
1051
+ }
1052
+
1053
+ /**
1054
+ * Gets price - formatted for display.
1055
+ *
1056
+ * @access public
1057
+ * @param mixed $item
1058
+ * @return string
1059
+ */
1060
+ public function get_formatted_item_price ( $item, $type, $tax_display = '' ) {
1061
+ if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1062
+ return;
1063
+ }
1064
+
1065
+ $divide_by = ($type == 'single' && $item['qty'] != 0 )?abs($item['qty']):1; //divide by 1 if $type is not 'single' (thus 'total')
1066
+ if ( $tax_display == 'excl' ) {
1067
+ $item_price = $this->format_price( ($this->order->get_line_subtotal( $item )) / $divide_by );
1068
+ } else {
1069
+ $item_price = $this->format_price( ($this->order->get_line_subtotal( $item, true )) / $divide_by );
1070
+ }
1071
+
1072
+ return $item_price;
1073
+ }
1074
+
1075
+ public function get_invoice_number() {
1076
+ // Call the woocommerce_invoice_number filter and let third-party plugins set a number.
1077
+ // Default is null, so we can detect whether a plugin has set the invoice number
1078
+ $third_party_invoice_number = apply_filters( 'woocommerce_invoice_number', null, $this->order_id );
1079
+ if ($third_party_invoice_number !== null) {
1080
+ return $third_party_invoice_number;
1081
+ }
1082
+
1083
+ if ( $invoice_number = $this->get_number('invoice') ) {
1084
+ return $formatted_invoice_number = $invoice_number->get_formatted();
1085
+ } else {
1086
+ return '';
1087
+ }
1088
+ }
1089
+
1090
+ public function invoice_number() {
1091
+ echo $this->get_invoice_number();
1092
+ }
1093
+
1094
+ public function get_invoice_date() {
1095
+ if ( $invoice_date = $this->get_date('invoice') ) {
1096
+ return $invoice_date->date_i18n( apply_filters( 'wpo_wcpdf_date_format', wc_date_format(), $this ) );
1097
+ } else {
1098
+ return '';
1099
+ }
1100
+ }
1101
+
1102
+ public function invoice_date() {
1103
+ echo $this->get_invoice_date();
1104
+ }
1105
+
1106
+
1107
+ }
1108
+
1109
  endif; // class_exists
includes/documents/abstract-wcpdf-order-document.php CHANGED
@@ -1,751 +1,765 @@
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
- use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit; // Exit if accessed directly
11
- }
12
-
13
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document' ) ) :
14
-
15
- /**
16
- * Abstract Document
17
- *
18
- * Handles generic pdf document & order data and database interaction
19
- * which is extended by both Invoices & Packing Slips
20
- *
21
- * @class \WPO\WC\PDF_Invoices\Documents\Order_Document
22
- * @version 2.0
23
- * @category Class
24
- * @author Ewout Fernhout
25
- */
26
-
27
- abstract class Order_Document {
28
- /**
29
- * Document type.
30
- * @var String
31
- */
32
- public $type;
33
-
34
- /**
35
- * Document slug.
36
- * @var String
37
- */
38
- public $slug;
39
-
40
- /**
41
- * Document title.
42
- * @var string
43
- */
44
- public $title;
45
-
46
- /**
47
- * Document icon.
48
- * @var string
49
- */
50
- public $icon;
51
-
52
- /**
53
- * WC Order object
54
- * @var object
55
- */
56
- public $order;
57
-
58
- /**
59
- * WC Order ID
60
- * @var object
61
- */
62
- public $order_id;
63
-
64
- /**
65
- * Document settings.
66
- * @var array
67
- */
68
- public $settings;
69
-
70
- /**
71
- * TRUE if document is enabled.
72
- * @var bool
73
- */
74
- public $enabled;
75
-
76
- /**
77
- * Linked documents, used for data retrieval
78
- * @var array
79
- */
80
- protected $linked_documents = array();
81
-
82
- /**
83
- * Core data for this object. Name value pairs (name + default value).
84
- * @var array
85
- */
86
- protected $data = array();
87
-
88
- /**
89
- * Init/load the order object.
90
- *
91
- * @param int|object|WC_Order $order Order to init.
92
- */
93
- public function __construct( $order = 0 ) {
94
- if ( is_numeric( $order ) && $order > 0 ) {
95
- $this->order_id = $order;
96
- $this->order = WCX::get_order( $this->order_id );
97
- } elseif ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) {
98
- $this->order_id = WCX_Order::get_id( $order );
99
- $this->order = $order;
100
- }
101
-
102
- // set properties
103
- $this->slug = str_replace('-', '_', $this->type);
104
-
105
- // load data
106
- if ( $this->order ) {
107
- $this->read_data( $this->order );
108
- if ( WPO_WCPDF()->legacy_mode_enabled() ) {
109
- global $wpo_wcpdf;
110
- $wpo_wcpdf->export->order = $this->order;
111
- $wpo_wcpdf->export->document = $this;
112
- $wpo_wcpdf->export->order_id = $this->order_id;
113
- $wpo_wcpdf->export->template_type = $this->type;
114
- }
115
- }
116
-
117
- // load settings
118
- $this->settings = $this->get_settings();
119
- $this->enabled = $this->get_setting( 'enabled', false );
120
- }
121
-
122
- public function init_settings() {
123
- return;
124
- }
125
-
126
- public function get_settings( $latest = false ) {
127
- // get most current settings
128
- $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
129
- $document_settings = get_option( 'wpo_wcpdf_documents_settings_'.$this->get_type() );
130
- $settings = (array) $document_settings + (array) $common_settings;
131
-
132
- // return only most current if forced
133
- if ( $latest == true ) {
134
- return $settings;
135
- }
136
-
137
- // get historical settings if enabled
138
- if ( !empty( $this->order ) && $this->use_historical_settings() == true ) {
139
- $order_settings = WCX_Order::get_meta( $this->order, "_wcpdf_{$this->slug}_settings" );
140
- // not sure what happens if combining with current settings will have unwanted side effects
141
- // like unchecked options being enabled because missing = unchecked in historical - disabled for now
142
- if (!empty($order_settings)) {
143
- // $settings = (array) $order_settings + (array) $settings;
144
- $settings = $order_settings;
145
- }
146
- }
147
- if ( empty( $order_settings ) && !empty( $this->order ) ) {
148
- // this is either the first time the document is generated, or historical settings are disabled
149
- // in both cases, we store the document settings
150
- WCX_Order::update_meta_data( $this->order, "_wcpdf_{$this->slug}_settings", $settings );
151
- }
152
-
153
- return $settings;
154
- }
155
-
156
- public function use_historical_settings() {
157
- return apply_filters( 'wpo_wcpdf_document_use_historical_settings', false, $this );
158
- }
159
-
160
- public function get_setting( $key, $default = '' ) {
161
- $setting = isset( $this->settings[$key] ) ? $this->settings[$key] : $default;
162
- return $setting;
163
- }
164
-
165
- public function get_attach_to_email_ids() {
166
- $email_ids = isset( $this->settings['attach_to_email_ids'] ) ? array_keys( $this->settings['attach_to_email_ids'] ) : array();
167
- return $email_ids;
168
- }
169
-
170
- public function get_type() {
171
- return $this->type;
172
- }
173
-
174
- public function is_enabled() {
175
- return apply_filters( 'wpo_wcpdf_document_is_enabled', $this->enabled, $this->type );
176
- }
177
-
178
- public function get_hook_prefix() {
179
- return 'wpo_wcpdf_' . $this->slug . '_get_';
180
- }
181
-
182
- public function read_data( $order ) {
183
- $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number_data", true );
184
- // fallback to legacy data for number
185
- if ( empty( $number ) ) {
186
- $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number", true );
187
- $formatted_number = WCX_Order::get_meta( $order, "_wcpdf_formatted_{$this->slug}_number", true );
188
- if (!empty($formatted_number)) {
189
- $number = compact( 'number', 'formatted_number' );
190
- }
191
- }
192
-
193
- // pass data to setter functions
194
- $this->set_data( array(
195
- // always load date before number, because date is used in number formatting
196
- 'date' => WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_date", true ),
197
- 'number' => $number,
198
- ), $order );
199
-
200
- return;
201
- }
202
-
203
- public function init() {
204
- // store settings in order
205
- if ( !empty( $this->order ) ) {
206
- $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
207
- $document_settings = get_option( 'wpo_wcpdf_documents_settings_'.$this->get_type() );
208
- $settings = (array) $document_settings + (array) $common_settings;
209
- WCX_Order::update_meta_data( $this->order, "_wcpdf_{$this->slug}_settings", $settings );
210
- }
211
-
212
- $this->set_date( current_time( 'timestamp', true ) );
213
- do_action( 'wpo_wcpdf_init_document', $this );
214
- }
215
-
216
- public function save( $order = null ) {
217
- $order = empty( $order ) ? $this->order : $order;
218
- if ( empty( $order ) ) {
219
- return; // nowhere to save to...
220
- }
221
-
222
- foreach ($this->data as $key => $value) {
223
- if ( empty( $value ) ) {
224
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}" );
225
- if ( $key == 'date' ) {
226
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted" );
227
- } elseif ( $key == 'number' ) {
228
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data" );
229
- // deleting the number = deleting the document, so also delete document settings
230
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_settings" );
231
- }
232
- } else {
233
- if ( $key == 'date' ) {
234
- // store dates as timestamp and formatted as mysql time
235
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->getTimestamp() );
236
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted", $value->date( 'Y-m-d H:i:s' ) );
237
- } elseif ( $key == 'number' ) {
238
- // store both formatted number and number data
239
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->formatted_number );
240
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data", $value->to_array() );
241
- }
242
- }
243
- }
244
- }
245
-
246
- public function delete( $order = null ) {
247
- $order = empty( $order ) ? $this->order : $order;
248
- if ( empty( $order ) ) {
249
- return; // nothing to delete
250
- }
251
-
252
- $data_to_remove = apply_filters( 'wpo_wcpdf_delete_document_data_keys', array(
253
- 'settings',
254
- 'date',
255
- 'date_formatted',
256
- 'number',
257
- 'number_data',
258
- ), $this );
259
- foreach ($data_to_remove as $data_key) {
260
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$data_key}" );
261
- }
262
-
263
- do_action( 'wpo_wcpdf_delete_document', $this );
264
- }
265
-
266
- public function exists() {
267
- return !empty( $this->data['number'] );
268
- }
269
-
270
- /*
271
- |--------------------------------------------------------------------------
272
- | Data getters
273
- |--------------------------------------------------------------------------
274
- */
275
-
276
- public function get_data( $key, $document_type = '', $order = null, $context = 'view' ) {
277
- $document_type = empty( $document_type ) ? $this->type : $document_type;
278
- $order = empty( $order ) ? $this->order : $order;
279
-
280
- // redirect get_data call for linked documents
281
- if ( $document_type != $this->type ) {
282
- if ( !isset( $this->linked_documents[ $document_type ] ) ) {
283
- // always assume parent for documents linked to credit notes
284
- if ($this->type == 'credit-note') {
285
- $order = $this->get_refund_parent( $order );
286
- }
287
- // order is not loaded to avoid overhead - we pass this by reference directly to the read_data method instead
288
- $this->linked_documents[ $document_type ] = wcpdf_get_document( $document_type, null );
289
- $this->linked_documents[ $document_type ]->read_data( $order );
290
- }
291
- return $this->linked_documents[ $document_type ]->get_data( $key, $document_type );
292
- }
293
-
294
- $value = null;
295
-
296
- if ( array_key_exists( $key, $this->data ) ) {
297
- $value = $this->data[ $key ];
298
-
299
- if ( 'view' === $context ) {
300
- $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
301
- }
302
- }
303
-
304
- return $value;
305
- }
306
-
307
- public function get_number( $document_type = '', $order = null, $context = 'view' ) {
308
- return $this->get_data( 'number', $document_type, $order, $context );
309
- }
310
-
311
- public function get_date( $document_type = '', $order = null, $context = 'view' ) {
312
- return $this->get_data( 'date', $document_type, $order, $context );
313
- }
314
-
315
- public function get_title() {
316
- return apply_filters( "wpo_wcpdf_{$this->slug}_title", $this->title, $this );
317
- }
318
-
319
- /*
320
- |--------------------------------------------------------------------------
321
- | Data setters
322
- |--------------------------------------------------------------------------
323
- |
324
- | Functions for setting order data. These should not update anything in the
325
- | order itself and should only change what is stored in the class
326
- | object.
327
- */
328
-
329
- public function set_data( $data, $order ) {
330
- $order = empty( $order ) ? $this->order : $order;
331
- foreach ($data as $key => $value) {
332
- $setter = "set_$key";
333
- if ( is_callable( array( $this, $setter ) ) ) {
334
- $this->$setter( $value, $order );
335
- } else {
336
- $this->data[ $key ] = $value;
337
- }
338
- }
339
- }
340
-
341
- public function set_date( $value, $order = null ) {
342
- $order = empty( $order ) ? $this->order : $order;
343
- try {
344
- if ( empty( $value ) ) {
345
- $this->data[ 'date' ] = null;
346
- return;
347
- }
348
-
349
- if ( is_a( $value, 'WC_DateTime' ) ) {
350
- $datetime = $value;
351
- } elseif ( is_numeric( $value ) ) {
352
- // Timestamps are handled as UTC timestamps in all cases.
353
- $datetime = new WC_DateTime( "@{$value}", new \DateTimeZone( 'UTC' ) );
354
- } else {
355
- // Strings are defined in local WP timezone. Convert to UTC.
356
- if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $value, $date_bits ) ) {
357
- $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
358
- $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
359
- } else {
360
- $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
361
- }
362
- $datetime = new WC_DateTime( "@{$timestamp}", new \DateTimeZone( 'UTC' ) );
363
- }
364
-
365
- // Set local timezone or offset.
366
- if ( get_option( 'timezone_string' ) ) {
367
- $datetime->setTimezone( new \DateTimeZone( wc_timezone_string() ) );
368
- } else {
369
- $datetime->set_utc_offset( wc_timezone_offset() );
370
- }
371
-
372
- $this->data[ 'date' ] = $datetime;
373
- } catch ( \Exception $e ) {
374
- wcpdf_log_error( $e->getMessage() );
375
- } catch ( \Error $e ) {
376
- wcpdf_log_error( $e->getMessage() );
377
- }
378
-
379
- }
380
-
381
- public function set_number( $value, $order = null ) {
382
- $order = empty( $order ) ? $this->order : $order;
383
-
384
- if ( is_array( $value ) ) {
385
- $filtered_value = array_filter( $value );
386
- }
387
-
388
- if ( empty( $value ) || ( is_array( $value ) && empty( $filtered_value ) ) ) {
389
- $document_number = null;
390
- } elseif ( $value instanceof Document_Number ) {
391
- // WCPDF 2.0 number data
392
- $document_number = $value;
393
- } elseif ( is_array( $value ) ) {
394
- // WCPDF 2.0 number data as array
395
- $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
396
- } else {
397
- // plain number
398
- $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
399
- }
400
-
401
- $this->data[ 'number' ] = $document_number;
402
- }
403
-
404
- /*
405
- |--------------------------------------------------------------------------
406
- | Settings getters / outputters
407
- |--------------------------------------------------------------------------
408
- */
409
-
410
- public function get_number_settings() {
411
- if (empty($this->settings)) {
412
- $settings = $this->get_settings();
413
- $number_settings = isset($settings['number_format'])?$settings['number_format']:array();
414
- } else {
415
- $number_settings = isset($this->settings['number_format'])?$this->settings['number_format']:array();
416
- }
417
- return apply_filters( 'wpo_wcpdf_document_number_settings', $number_settings, $this );
418
- }
419
-
420
- /**
421
- * Output template styles
422
- */
423
- public function template_styles() {
424
- $css = apply_filters( 'wpo_wcpdf_template_styles_file', $this->locate_template_file( "style.css" ) );
425
-
426
- ob_start();
427
- if (file_exists($css)) {
428
- include($css);
429
- }
430
- $css = ob_get_clean();
431
- $css = apply_filters( 'wpo_wcpdf_template_styles', $css, $this );
432
-
433
- echo $css;
434
- }
435
-
436
- public function has_header_logo() {
437
- return !empty( $this->settings['header_logo'] );
438
- }
439
-
440
- /**
441
- * Return logo id
442
- */
443
- public function get_header_logo_id() {
444
- if ( !empty( $this->settings['header_logo'] ) ) {
445
- return apply_filters( 'wpo_wcpdf_header_logo_id', $this->settings['header_logo'], $this );
446
- }
447
- }
448
-
449
- /**
450
- * Show logo html
451
- */
452
- public function header_logo() {
453
- if ($this->get_header_logo_id()) {
454
- $attachment_id = $this->get_header_logo_id();
455
- $company = $this->get_shop_name();
456
- if( $attachment_id ) {
457
- $attachment = wp_get_attachment_image_src( $attachment_id, 'full', false );
458
-
459
- $attachment_src = $attachment[0];
460
- $attachment_width = $attachment[1];
461
- $attachment_height = $attachment[2];
462
-
463
- $attachment_path = get_attached_file( $attachment_id );
464
-
465
- if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($attachment_path) ) {
466
- $src = $attachment_path;
467
- } else {
468
- $src = $attachment_src;
469
- }
470
-
471
- printf('<img src="%1$s" width="%2$d" height="%3$d" alt="%4$s" />', $src, $attachment_width, $attachment_height, esc_attr( $company ) );
472
- }
473
- }
474
- }
475
-
476
- public function get_settings_text( $settings_key, $default = false, $autop = true ) {
477
- if ( !empty( $this->settings[$settings_key]['default'] ) ) {
478
- $text = wptexturize( trim( $this->settings[$settings_key]['default'] ) );
479
- if ($autop === true) {
480
- $text = wpautop( $text );
481
- }
482
- } else {
483
- $text = $default;
484
- }
485
- // legacy filters
486
- if ( in_array( $settings_key, array( 'shop_name', 'shop_address', 'footer', 'extra_1', 'extra_2', 'extra_3' ) ) ) {
487
- $text = apply_filters( "wpo_wcpdf_{$settings_key}", $text, $this );
488
- }
489
-
490
- return apply_filters( "wpo_wcpdf_{$settings_key}_settings_text", $text, $this );
491
- }
492
-
493
- /**
494
- * Return/Show custom company name or default to blog name
495
- */
496
- public function get_shop_name() {
497
- $default = get_bloginfo( 'name' );
498
- return $this->get_settings_text( 'shop_name', $default, false );
499
- }
500
- public function shop_name() {
501
- echo $this->get_shop_name();
502
- }
503
-
504
- /**
505
- * Return/Show shop/company address if provided
506
- */
507
- public function get_shop_address() {
508
- return $this->get_settings_text( 'shop_address' );
509
- }
510
- public function shop_address() {
511
- echo $this->get_shop_address();
512
- }
513
-
514
- /**
515
- * Return/Show shop/company footer imprint, copyright etc.
516
- */
517
- public function get_footer() {
518
- return $this->get_settings_text( 'footer' );
519
- }
520
- public function footer() {
521
- echo $this->get_footer();
522
- }
523
-
524
- /**
525
- * Return/Show Extra field 1
526
- */
527
- public function get_extra_1() {
528
- return $this->get_settings_text( 'extra_1' );
529
-
530
- }
531
- public function extra_1() {
532
- echo $this->get_extra_1();
533
- }
534
-
535
- /**
536
- * Return/Show Extra field 2
537
- */
538
- public function get_extra_2() {
539
- return $this->get_settings_text( 'extra_2' );
540
- }
541
- public function extra_2() {
542
- echo $this->get_extra_2();
543
- }
544
-
545
- /**
546
- * Return/Show Extra field 3
547
- */
548
- public function get_extra_3() {
549
- return $this->get_settings_text( 'extra_3' );
550
- }
551
- public function extra_3() {
552
- echo $this->get_extra_3();
553
- }
554
-
555
- /*
556
- |--------------------------------------------------------------------------
557
- | Output functions
558
- |--------------------------------------------------------------------------
559
- */
560
-
561
- public function get_pdf() {
562
- do_action( 'wpo_wcpdf_before_pdf', $this->get_type(), $this );
563
-
564
- $pdf_settings = array(
565
- 'paper_size' => apply_filters( 'wpo_wcpdf_paper_format', $this->get_setting( 'paper_size', 'A4' ), $this->get_type() ),
566
- 'paper_orientation' => apply_filters( 'wpo_wcpdf_paper_orientation', 'portrait', $this->get_type() ),
567
- 'font_subsetting' => $this->get_setting( 'font_subsetting', false ),
568
- );
569
- $pdf_maker = wcpdf_get_pdf_maker( $this->get_html(), $pdf_settings );
570
- $pdf = $pdf_maker->output();
571
-
572
- do_action( 'wpo_wcpdf_after_pdf', $this->get_type(), $this );
573
- do_action( 'wpo_wcpdf_pdf_created', $pdf, $this );
574
-
575
- return apply_filters( 'wpo_wcpdf_get_pdf', $pdf, $this );
576
- }
577
-
578
- public function get_html( $args = array() ) {
579
- do_action( 'wpo_wcpdf_before_html', $this->get_type(), $this );
580
- $default_args = array (
581
- 'wrap_html_content' => true,
582
- );
583
- $args = $args + $default_args;
584
-
585
- $html = $this->render_template( $this->locate_template_file( "{$this->type}.php" ), array(
586
- 'order' => $this->order,
587
- 'order_id' => $this->order_id,
588
- )
589
- );
590
- if ($args['wrap_html_content']) {
591
- $html = $this->wrap_html_content( $html );
592
- }
593
-
594
- // clean up special characters
595
- if ( function_exists('utf8_decode') && function_exists('mb_convert_encoding') ) {
596
- $html = utf8_decode(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
597
- }
598
-
599
- do_action( 'wpo_wcpdf_after_html', $this->get_type(), $this );
600
-
601
- return apply_filters( 'wpo_wcpdf_get_html', $html, $this );
602
- }
603
-
604
- public function output_pdf( $output_mode = 'download' ) {
605
- $pdf = $this->get_pdf();
606
- wcpdf_pdf_headers( $this->get_filename(), $output_mode, $pdf );
607
- echo $pdf;
608
- die();
609
- }
610
-
611
- public function output_html() {
612
- echo $this->get_html();
613
- die();
614
- }
615
-
616
- public function wrap_html_content( $content ) {
617
- if ( WPO_WCPDF()->legacy_mode_enabled() ) {
618
- $GLOBALS['wpo_wcpdf']->export->output_body = $content;
619
- }
620
-
621
- $html = $this->render_template( $this->locate_template_file( "html-document-wrapper.php" ), array(
622
- 'content' => $content,
623
- )
624
- );
625
- return $html;
626
- }
627
-
628
- public function get_filename( $context = 'download', $args = array() ) {
629
- $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
630
-
631
- $name = $this->get_type();
632
- if ( get_post_type( $this->order_id ) == 'shop_order_refund' ) {
633
- $number = $this->order_id;
634
- } else {
635
- $number = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
636
- }
637
-
638
- if ( $order_count == 1 ) {
639
- $suffix = $number;
640
- } else {
641
- $suffix = date('Y-m-d'); // 2020-11-11
642
- }
643
-
644
- $filename = $name . '-' . $suffix . '.pdf';
645
-
646
- // Filter filename
647
- $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
648
- $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
649
-
650
- // sanitize filename (after filters to prevent human errors)!
651
- return sanitize_file_name( $filename );
652
- }
653
-
654
- public function get_template_path() {
655
- return WPO_WCPDF()->settings->get_template_path();
656
- }
657
-
658
- public function locate_template_file( $file ) {
659
- if (empty($file)) {
660
- $file = $this->type.'.php';
661
- }
662
- $path = WPO_WCPDF()->settings->get_template_path( $file );
663
- $file_path = "{$path}/{$file}";
664
-
665
- $fallback_file_path = WPO_WCPDF()->plugin_path() . '/templates/Simple/' . $file;
666
- if ( !file_exists( $file_path ) && file_exists( $fallback_file_path ) ) {
667
- $file_path = $fallback_file_path;
668
- }
669
-
670
- $file_path = apply_filters( 'wpo_wcpdf_template_file', $file_path, $this->type, $this->order );
671
-
672
- return $file_path;
673
- }
674
-
675
- public function render_template( $file, $args = array() ) {
676
- do_action( 'wpo_wcpdf_process_template', $this->get_type(), $this );
677
-
678
- if ( ! empty( $args ) && is_array( $args ) ) {
679
- extract( $args );
680
- }
681
- ob_start();
682
- if (file_exists($file)) {
683
- include($file);
684
- }
685
- return ob_get_clean();
686
- }
687
-
688
- /*
689
- |--------------------------------------------------------------------------
690
- | Settings helper functions
691
- |--------------------------------------------------------------------------
692
- */
693
-
694
- /**
695
- * get all emails registered in WooCommerce
696
- * @param boolean $remove_defaults switch to remove default woocommerce emails
697
- * @return array $emails list of all email ids/slugs and names
698
- */
699
- public function get_wc_emails() {
700
- // get emails from WooCommerce
701
- global $woocommerce;
702
- $mailer = $woocommerce->mailer();
703
- $wc_emails = $mailer->get_emails();
704
-
705
- $non_order_emails = array(
706
- 'customer_note',
707
- 'customer_reset_password',
708
- 'customer_new_account'
709
- );
710
-
711
- $emails = array();
712
- foreach ($wc_emails as $class => $email) {
713
- if ( !in_array( $email->id, $non_order_emails ) ) {
714
- switch ($email->id) {
715
- case 'new_order':
716
- $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Admin email', 'woocommerce-pdf-invoices-packing-slips' ) );
717
- break;
718
- case 'customer_invoice':
719
- $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Manual email', 'woocommerce-pdf-invoices-packing-slips' ) );
720
- break;
721
- default:
722
- $emails[$email->id] = $email->title;
723
- break;
724
- }
725
- }
726
- }
727
-
728
- return apply_filters( 'wpo_wcpdf_wc_emails', $emails );
729
- }
730
-
731
- // get list of WooCommerce statuses
732
- public function get_wc_order_status_list() {
733
- if ( version_compare( WOOCOMMERCE_VERSION, '2.2', '<' ) ) {
734
- $statuses = (array) get_terms( 'shop_order_status', array( 'hide_empty' => 0, 'orderby' => 'id' ) );
735
- foreach ( $statuses as $status ) {
736
- $order_statuses[esc_attr( $status->slug )] = esc_html__( $status->name, 'woocommerce' );
737
- }
738
- } else {
739
- $statuses = wc_get_order_statuses();
740
- foreach ( $statuses as $status_slug => $status ) {
741
- $status_slug = 'wc-' === substr( $status_slug, 0, 3 ) ? substr( $status_slug, 3 ) : $status_slug;
742
- $order_statuses[$status_slug] = $status;
743
- }
744
- }
745
- return $order_statuses;
746
- }
747
-
748
-
749
- }
750
-
751
- 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
+ use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit; // Exit if accessed directly
11
+ }
12
+
13
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document' ) ) :
14
+
15
+ /**
16
+ * Abstract Document
17
+ *
18
+ * Handles generic pdf document & order data and database interaction
19
+ * which is extended by both Invoices & Packing Slips
20
+ *
21
+ * @class \WPO\WC\PDF_Invoices\Documents\Order_Document
22
+ * @version 2.0
23
+ * @category Class
24
+ * @author Ewout Fernhout
25
+ */
26
+
27
+ abstract class Order_Document {
28
+ /**
29
+ * Document type.
30
+ * @var String
31
+ */
32
+ public $type;
33
+
34
+ /**
35
+ * Document slug.
36
+ * @var String
37
+ */
38
+ public $slug;
39
+
40
+ /**
41
+ * Document title.
42
+ * @var string
43
+ */
44
+ public $title;
45
+
46
+ /**
47
+ * Document icon.
48
+ * @var string
49
+ */
50
+ public $icon;
51
+
52
+ /**
53
+ * WC Order object
54
+ * @var object
55
+ */
56
+ public $order;
57
+
58
+ /**
59
+ * WC Order ID
60
+ * @var object
61
+ */
62
+ public $order_id;
63
+
64
+ /**
65
+ * Document settings.
66
+ * @var array
67
+ */
68
+ public $settings;
69
+
70
+ /**
71
+ * TRUE if document is enabled.
72
+ * @var bool
73
+ */
74
+ public $enabled;
75
+
76
+ /**
77
+ * Linked documents, used for data retrieval
78
+ * @var array
79
+ */
80
+ protected $linked_documents = array();
81
+
82
+ /**
83
+ * Core data for this object. Name value pairs (name + default value).
84
+ * @var array
85
+ */
86
+ protected $data = array();
87
+
88
+ /**
89
+ * Init/load the order object.
90
+ *
91
+ * @param int|object|WC_Order $order Order to init.
92
+ */
93
+ public function __construct( $order = 0 ) {
94
+ if ( is_numeric( $order ) && $order > 0 ) {
95
+ $this->order_id = $order;
96
+ $this->order = WCX::get_order( $this->order_id );
97
+ } elseif ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) {
98
+ $this->order_id = WCX_Order::get_id( $order );
99
+ $this->order = $order;
100
+ }
101
+
102
+ // set properties
103
+ $this->slug = str_replace('-', '_', $this->type);
104
+
105
+ // load data
106
+ if ( $this->order ) {
107
+ $this->read_data( $this->order );
108
+ if ( WPO_WCPDF()->legacy_mode_enabled() ) {
109
+ global $wpo_wcpdf;
110
+ $wpo_wcpdf->export->order = $this->order;
111
+ $wpo_wcpdf->export->document = $this;
112
+ $wpo_wcpdf->export->order_id = $this->order_id;
113
+ $wpo_wcpdf->export->template_type = $this->type;
114
+ }
115
+ }
116
+
117
+ // load settings
118
+ $this->settings = $this->get_settings();
119
+ $this->latest_settings = $this->get_settings( true );
120
+ $this->enabled = $this->get_setting( 'enabled', false );
121
+ }
122
+
123
+ public function init_settings() {
124
+ return;
125
+ }
126
+
127
+ public function get_settings( $latest = false ) {
128
+ // get most current settings
129
+ $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
130
+ $document_settings = get_option( 'wpo_wcpdf_documents_settings_'.$this->get_type() );
131
+ $settings = (array) $document_settings + (array) $common_settings;
132
+
133
+ // return only most current if forced
134
+ if ( $latest == true ) {
135
+ return $settings;
136
+ }
137
+
138
+ // get historical settings if enabled
139
+ if ( !empty( $this->order ) && $this->use_historical_settings() == true ) {
140
+ $order_settings = WCX_Order::get_meta( $this->order, "_wcpdf_{$this->slug}_settings" );
141
+ if (!empty($order_settings)) {
142
+ // not sure what happens if combining with current settings will have unwanted side effects
143
+ // like unchecked options being enabled because missing = unchecked in historical - disabled for now
144
+ // $settings = (array) $order_settings + (array) $settings;
145
+ $settings = $order_settings;
146
+ }
147
+ }
148
+ if ( empty( $order_settings ) && !empty( $this->order ) ) {
149
+ // this is either the first time the document is generated, or historical settings are disabled
150
+ // in both cases, we store the document settings
151
+ WCX_Order::update_meta_data( $this->order, "_wcpdf_{$this->slug}_settings", $settings );
152
+ }
153
+
154
+ return $settings;
155
+ }
156
+
157
+ public function use_historical_settings() {
158
+ return apply_filters( 'wpo_wcpdf_document_use_historical_settings', false, $this );
159
+ }
160
+
161
+ public function get_setting( $key, $default = '' ) {
162
+ $non_historical_settings = apply_filters( 'wpo_wcpdf_non_historical_settings', array(
163
+ 'enabled',
164
+ 'number_format', // this is stored in the number data already!
165
+ 'my_account_buttons',
166
+ 'my_account_restrict',
167
+ 'invoice_number_column',
168
+ 'paper_size',
169
+ 'font_subsetting',
170
+ ) );
171
+ if ( in_array( $key, $non_historical_settings ) && isset($this->latest_settings) ) {
172
+ $setting = isset( $this->latest_settings[$key] ) ? $this->latest_settings[$key] : $default;
173
+ } else {
174
+ $setting = isset( $this->settings[$key] ) ? $this->settings[$key] : $default;
175
+ }
176
+ return $setting;
177
+ }
178
+
179
+ public function get_attach_to_email_ids() {
180
+ $email_ids = isset( $this->settings['attach_to_email_ids'] ) ? array_keys( $this->settings['attach_to_email_ids'] ) : array();
181
+ return $email_ids;
182
+ }
183
+
184
+ public function get_type() {
185
+ return $this->type;
186
+ }
187
+
188
+ public function is_enabled() {
189
+ return apply_filters( 'wpo_wcpdf_document_is_enabled', $this->enabled, $this->type );
190
+ }
191
+
192
+ public function get_hook_prefix() {
193
+ return 'wpo_wcpdf_' . $this->slug . '_get_';
194
+ }
195
+
196
+ public function read_data( $order ) {
197
+ $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number_data", true );
198
+ // fallback to legacy data for number
199
+ if ( empty( $number ) ) {
200
+ $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number", true );
201
+ $formatted_number = WCX_Order::get_meta( $order, "_wcpdf_formatted_{$this->slug}_number", true );
202
+ if (!empty($formatted_number)) {
203
+ $number = compact( 'number', 'formatted_number' );
204
+ }
205
+ }
206
+
207
+ // pass data to setter functions
208
+ $this->set_data( array(
209
+ // always load date before number, because date is used in number formatting
210
+ 'date' => WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_date", true ),
211
+ 'number' => $number,
212
+ ), $order );
213
+
214
+ return;
215
+ }
216
+
217
+ public function init() {
218
+ // store settings in order
219
+ if ( !empty( $this->order ) ) {
220
+ $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
221
+ $document_settings = get_option( 'wpo_wcpdf_documents_settings_'.$this->get_type() );
222
+ $settings = (array) $document_settings + (array) $common_settings;
223
+ WCX_Order::update_meta_data( $this->order, "_wcpdf_{$this->slug}_settings", $settings );
224
+ }
225
+
226
+ $this->set_date( current_time( 'timestamp', true ) );
227
+ do_action( 'wpo_wcpdf_init_document', $this );
228
+ }
229
+
230
+ public function save( $order = null ) {
231
+ $order = empty( $order ) ? $this->order : $order;
232
+ if ( empty( $order ) ) {
233
+ return; // nowhere to save to...
234
+ }
235
+
236
+ foreach ($this->data as $key => $value) {
237
+ if ( empty( $value ) ) {
238
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}" );
239
+ if ( $key == 'date' ) {
240
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted" );
241
+ } elseif ( $key == 'number' ) {
242
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data" );
243
+ // deleting the number = deleting the document, so also delete document settings
244
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_settings" );
245
+ }
246
+ } else {
247
+ if ( $key == 'date' ) {
248
+ // store dates as timestamp and formatted as mysql time
249
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->getTimestamp() );
250
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted", $value->date( 'Y-m-d H:i:s' ) );
251
+ } elseif ( $key == 'number' ) {
252
+ // store both formatted number and number data
253
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->formatted_number );
254
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data", $value->to_array() );
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ public function delete( $order = null ) {
261
+ $order = empty( $order ) ? $this->order : $order;
262
+ if ( empty( $order ) ) {
263
+ return; // nothing to delete
264
+ }
265
+
266
+ $data_to_remove = apply_filters( 'wpo_wcpdf_delete_document_data_keys', array(
267
+ 'settings',
268
+ 'date',
269
+ 'date_formatted',
270
+ 'number',
271
+ 'number_data',
272
+ ), $this );
273
+ foreach ($data_to_remove as $data_key) {
274
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$data_key}" );
275
+ }
276
+
277
+ do_action( 'wpo_wcpdf_delete_document', $this );
278
+ }
279
+
280
+ public function exists() {
281
+ return !empty( $this->data['number'] );
282
+ }
283
+
284
+ /*
285
+ |--------------------------------------------------------------------------
286
+ | Data getters
287
+ |--------------------------------------------------------------------------
288
+ */
289
+
290
+ public function get_data( $key, $document_type = '', $order = null, $context = 'view' ) {
291
+ $document_type = empty( $document_type ) ? $this->type : $document_type;
292
+ $order = empty( $order ) ? $this->order : $order;
293
+
294
+ // redirect get_data call for linked documents
295
+ if ( $document_type != $this->type ) {
296
+ if ( !isset( $this->linked_documents[ $document_type ] ) ) {
297
+ // always assume parent for documents linked to credit notes
298
+ if ($this->type == 'credit-note') {
299
+ $order = $this->get_refund_parent( $order );
300
+ }
301
+ // order is not loaded to avoid overhead - we pass this by reference directly to the read_data method instead
302
+ $this->linked_documents[ $document_type ] = wcpdf_get_document( $document_type, null );
303
+ $this->linked_documents[ $document_type ]->read_data( $order );
304
+ }
305
+ return $this->linked_documents[ $document_type ]->get_data( $key, $document_type );
306
+ }
307
+
308
+ $value = null;
309
+
310
+ if ( array_key_exists( $key, $this->data ) ) {
311
+ $value = $this->data[ $key ];
312
+
313
+ if ( 'view' === $context ) {
314
+ $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
315
+ }
316
+ }
317
+
318
+ return $value;
319
+ }
320
+
321
+ public function get_number( $document_type = '', $order = null, $context = 'view' ) {
322
+ return $this->get_data( 'number', $document_type, $order, $context );
323
+ }
324
+
325
+ public function get_date( $document_type = '', $order = null, $context = 'view' ) {
326
+ return $this->get_data( 'date', $document_type, $order, $context );
327
+ }
328
+
329
+ public function get_title() {
330
+ return apply_filters( "wpo_wcpdf_{$this->slug}_title", $this->title, $this );
331
+ }
332
+
333
+ /*
334
+ |--------------------------------------------------------------------------
335
+ | Data setters
336
+ |--------------------------------------------------------------------------
337
+ |
338
+ | Functions for setting order data. These should not update anything in the
339
+ | order itself and should only change what is stored in the class
340
+ | object.
341
+ */
342
+
343
+ public function set_data( $data, $order ) {
344
+ $order = empty( $order ) ? $this->order : $order;
345
+ foreach ($data as $key => $value) {
346
+ $setter = "set_$key";
347
+ if ( is_callable( array( $this, $setter ) ) ) {
348
+ $this->$setter( $value, $order );
349
+ } else {
350
+ $this->data[ $key ] = $value;
351
+ }
352
+ }
353
+ }
354
+
355
+ public function set_date( $value, $order = null ) {
356
+ $order = empty( $order ) ? $this->order : $order;
357
+ try {
358
+ if ( empty( $value ) ) {
359
+ $this->data[ 'date' ] = null;
360
+ return;
361
+ }
362
+
363
+ if ( is_a( $value, 'WC_DateTime' ) ) {
364
+ $datetime = $value;
365
+ } elseif ( is_numeric( $value ) ) {
366
+ // Timestamps are handled as UTC timestamps in all cases.
367
+ $datetime = new WC_DateTime( "@{$value}", new \DateTimeZone( 'UTC' ) );
368
+ } else {
369
+ // Strings are defined in local WP timezone. Convert to UTC.
370
+ if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $value, $date_bits ) ) {
371
+ $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
372
+ $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
373
+ } else {
374
+ $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
375
+ }
376
+ $datetime = new WC_DateTime( "@{$timestamp}", new \DateTimeZone( 'UTC' ) );
377
+ }
378
+
379
+ // Set local timezone or offset.
380
+ if ( get_option( 'timezone_string' ) ) {
381
+ $datetime->setTimezone( new \DateTimeZone( wc_timezone_string() ) );
382
+ } else {
383
+ $datetime->set_utc_offset( wc_timezone_offset() );
384
+ }
385
+
386
+ $this->data[ 'date' ] = $datetime;
387
+ } catch ( \Exception $e ) {
388
+ wcpdf_log_error( $e->getMessage() );
389
+ } catch ( \Error $e ) {
390
+ wcpdf_log_error( $e->getMessage() );
391
+ }
392
+
393
+ }
394
+
395
+ public function set_number( $value, $order = null ) {
396
+ $order = empty( $order ) ? $this->order : $order;
397
+
398
+ if ( is_array( $value ) ) {
399
+ $filtered_value = array_filter( $value );
400
+ }
401
+
402
+ if ( empty( $value ) || ( is_array( $value ) && empty( $filtered_value ) ) ) {
403
+ $document_number = null;
404
+ } elseif ( $value instanceof Document_Number ) {
405
+ // WCPDF 2.0 number data
406
+ $document_number = $value;
407
+ } elseif ( is_array( $value ) ) {
408
+ // WCPDF 2.0 number data as array
409
+ $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
410
+ } else {
411
+ // plain number
412
+ $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
413
+ }
414
+
415
+ $this->data[ 'number' ] = $document_number;
416
+ }
417
+
418
+ /*
419
+ |--------------------------------------------------------------------------
420
+ | Settings getters / outputters
421
+ |--------------------------------------------------------------------------
422
+ */
423
+
424
+ public function get_number_settings() {
425
+ if (empty($this->settings)) {
426
+ $settings = $this->get_settings( true ); // we always want the latest settings
427
+ $number_settings = isset($settings['number_format'])?$settings['number_format']:array();
428
+ } else {
429
+ $number_settings = $this->get_setting( 'number_format', array() );
430
+ }
431
+ return apply_filters( 'wpo_wcpdf_document_number_settings', $number_settings, $this );
432
+ }
433
+
434
+ /**
435
+ * Output template styles
436
+ */
437
+ public function template_styles() {
438
+ $css = apply_filters( 'wpo_wcpdf_template_styles_file', $this->locate_template_file( "style.css" ) );
439
+
440
+ ob_start();
441
+ if (file_exists($css)) {
442
+ include($css);
443
+ }
444
+ $css = ob_get_clean();
445
+ $css = apply_filters( 'wpo_wcpdf_template_styles', $css, $this );
446
+
447
+ echo $css;
448
+ }
449
+
450
+ public function has_header_logo() {
451
+ return !empty( $this->settings['header_logo'] );
452
+ }
453
+
454
+ /**
455
+ * Return logo id
456
+ */
457
+ public function get_header_logo_id() {
458
+ if ( !empty( $this->settings['header_logo'] ) ) {
459
+ return apply_filters( 'wpo_wcpdf_header_logo_id', $this->settings['header_logo'], $this );
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Show logo html
465
+ */
466
+ public function header_logo() {
467
+ if ($this->get_header_logo_id()) {
468
+ $attachment_id = $this->get_header_logo_id();
469
+ $company = $this->get_shop_name();
470
+ if( $attachment_id ) {
471
+ $attachment = wp_get_attachment_image_src( $attachment_id, 'full', false );
472
+
473
+ $attachment_src = $attachment[0];
474
+ $attachment_width = $attachment[1];
475
+ $attachment_height = $attachment[2];
476
+
477
+ $attachment_path = get_attached_file( $attachment_id );
478
+
479
+ if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($attachment_path) ) {
480
+ $src = $attachment_path;
481
+ } else {
482
+ $src = $attachment_src;
483
+ }
484
+
485
+ printf('<img src="%1$s" width="%2$d" height="%3$d" alt="%4$s" />', $src, $attachment_width, $attachment_height, esc_attr( $company ) );
486
+ }
487
+ }
488
+ }
489
+
490
+ public function get_settings_text( $settings_key, $default = false, $autop = true ) {
491
+ if ( !empty( $this->settings[$settings_key]['default'] ) ) {
492
+ $text = wptexturize( trim( $this->settings[$settings_key]['default'] ) );
493
+ if ($autop === true) {
494
+ $text = wpautop( $text );
495
+ }
496
+ } else {
497
+ $text = $default;
498
+ }
499
+ // legacy filters
500
+ if ( in_array( $settings_key, array( 'shop_name', 'shop_address', 'footer', 'extra_1', 'extra_2', 'extra_3' ) ) ) {
501
+ $text = apply_filters( "wpo_wcpdf_{$settings_key}", $text, $this );
502
+ }
503
+
504
+ return apply_filters( "wpo_wcpdf_{$settings_key}_settings_text", $text, $this );
505
+ }
506
+
507
+ /**
508
+ * Return/Show custom company name or default to blog name
509
+ */
510
+ public function get_shop_name() {
511
+ $default = get_bloginfo( 'name' );
512
+ return $this->get_settings_text( 'shop_name', $default, false );
513
+ }
514
+ public function shop_name() {
515
+ echo $this->get_shop_name();
516
+ }
517
+
518
+ /**
519
+ * Return/Show shop/company address if provided
520
+ */
521
+ public function get_shop_address() {
522
+ return $this->get_settings_text( 'shop_address' );
523
+ }
524
+ public function shop_address() {
525
+ echo $this->get_shop_address();
526
+ }
527
+
528
+ /**
529
+ * Return/Show shop/company footer imprint, copyright etc.
530
+ */
531
+ public function get_footer() {
532
+ return $this->get_settings_text( 'footer' );
533
+ }
534
+ public function footer() {
535
+ echo $this->get_footer();
536
+ }
537
+
538
+ /**
539
+ * Return/Show Extra field 1
540
+ */
541
+ public function get_extra_1() {
542
+ return $this->get_settings_text( 'extra_1' );
543
+
544
+ }
545
+ public function extra_1() {
546
+ echo $this->get_extra_1();
547
+ }
548
+
549
+ /**
550
+ * Return/Show Extra field 2
551
+ */
552
+ public function get_extra_2() {
553
+ return $this->get_settings_text( 'extra_2' );
554
+ }
555
+ public function extra_2() {
556
+ echo $this->get_extra_2();
557
+ }
558
+
559
+ /**
560
+ * Return/Show Extra field 3
561
+ */
562
+ public function get_extra_3() {
563
+ return $this->get_settings_text( 'extra_3' );
564
+ }
565
+ public function extra_3() {
566
+ echo $this->get_extra_3();
567
+ }
568
+
569
+ /*
570
+ |--------------------------------------------------------------------------
571
+ | Output functions
572
+ |--------------------------------------------------------------------------
573
+ */
574
+
575
+ public function get_pdf() {
576
+ do_action( 'wpo_wcpdf_before_pdf', $this->get_type(), $this );
577
+
578
+ $pdf_settings = array(
579
+ 'paper_size' => apply_filters( 'wpo_wcpdf_paper_format', $this->get_setting( 'paper_size', 'A4' ), $this->get_type() ),
580
+ 'paper_orientation' => apply_filters( 'wpo_wcpdf_paper_orientation', 'portrait', $this->get_type() ),
581
+ 'font_subsetting' => $this->get_setting( 'font_subsetting', false ),
582
+ );
583
+ $pdf_maker = wcpdf_get_pdf_maker( $this->get_html(), $pdf_settings );
584
+ $pdf = $pdf_maker->output();
585
+
586
+ do_action( 'wpo_wcpdf_after_pdf', $this->get_type(), $this );
587
+ do_action( 'wpo_wcpdf_pdf_created', $pdf, $this );
588
+
589
+ return apply_filters( 'wpo_wcpdf_get_pdf', $pdf, $this );
590
+ }
591
+
592
+ public function get_html( $args = array() ) {
593
+ do_action( 'wpo_wcpdf_before_html', $this->get_type(), $this );
594
+ $default_args = array (
595
+ 'wrap_html_content' => true,
596
+ );
597
+ $args = $args + $default_args;
598
+
599
+ $html = $this->render_template( $this->locate_template_file( "{$this->type}.php" ), array(
600
+ 'order' => $this->order,
601
+ 'order_id' => $this->order_id,
602
+ )
603
+ );
604
+ if ($args['wrap_html_content']) {
605
+ $html = $this->wrap_html_content( $html );
606
+ }
607
+
608
+ // clean up special characters
609
+ if ( function_exists('utf8_decode') && function_exists('mb_convert_encoding') ) {
610
+ $html = utf8_decode(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
611
+ }
612
+
613
+ do_action( 'wpo_wcpdf_after_html', $this->get_type(), $this );
614
+
615
+ return apply_filters( 'wpo_wcpdf_get_html', $html, $this );
616
+ }
617
+
618
+ public function output_pdf( $output_mode = 'download' ) {
619
+ $pdf = $this->get_pdf();
620
+ wcpdf_pdf_headers( $this->get_filename(), $output_mode, $pdf );
621
+ echo $pdf;
622
+ die();
623
+ }
624
+
625
+ public function output_html() {
626
+ echo $this->get_html();
627
+ die();
628
+ }
629
+
630
+ public function wrap_html_content( $content ) {
631
+ if ( WPO_WCPDF()->legacy_mode_enabled() ) {
632
+ $GLOBALS['wpo_wcpdf']->export->output_body = $content;
633
+ }
634
+
635
+ $html = $this->render_template( $this->locate_template_file( "html-document-wrapper.php" ), array(
636
+ 'content' => $content,
637
+ )
638
+ );
639
+ return $html;
640
+ }
641
+
642
+ public function get_filename( $context = 'download', $args = array() ) {
643
+ $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
644
+
645
+ $name = $this->get_type();
646
+ if ( get_post_type( $this->order_id ) == 'shop_order_refund' ) {
647
+ $number = $this->order_id;
648
+ } else {
649
+ $number = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
650
+ }
651
+
652
+ if ( $order_count == 1 ) {
653
+ $suffix = $number;
654
+ } else {
655
+ $suffix = date('Y-m-d'); // 2020-11-11
656
+ }
657
+
658
+ $filename = $name . '-' . $suffix . '.pdf';
659
+
660
+ // Filter filename
661
+ $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
662
+ $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
663
+
664
+ // sanitize filename (after filters to prevent human errors)!
665
+ return sanitize_file_name( $filename );
666
+ }
667
+
668
+ public function get_template_path() {
669
+ return WPO_WCPDF()->settings->get_template_path();
670
+ }
671
+
672
+ public function locate_template_file( $file ) {
673
+ if (empty($file)) {
674
+ $file = $this->type.'.php';
675
+ }
676
+ $path = WPO_WCPDF()->settings->get_template_path( $file );
677
+ $file_path = "{$path}/{$file}";
678
+
679
+ $fallback_file_path = WPO_WCPDF()->plugin_path() . '/templates/Simple/' . $file;
680
+ if ( !file_exists( $file_path ) && file_exists( $fallback_file_path ) ) {
681
+ $file_path = $fallback_file_path;
682
+ }
683
+
684
+ $file_path = apply_filters( 'wpo_wcpdf_template_file', $file_path, $this->type, $this->order );
685
+
686
+ return $file_path;
687
+ }
688
+
689
+ public function render_template( $file, $args = array() ) {
690
+ do_action( 'wpo_wcpdf_process_template', $this->get_type(), $this );
691
+
692
+ if ( ! empty( $args ) && is_array( $args ) ) {
693
+ extract( $args );
694
+ }
695
+ ob_start();
696
+ if (file_exists($file)) {
697
+ include($file);
698
+ }
699
+ return ob_get_clean();
700
+ }
701
+
702
+ /*
703
+ |--------------------------------------------------------------------------
704
+ | Settings helper functions
705
+ |--------------------------------------------------------------------------
706
+ */
707
+
708
+ /**
709
+ * get all emails registered in WooCommerce
710
+ * @param boolean $remove_defaults switch to remove default woocommerce emails
711
+ * @return array $emails list of all email ids/slugs and names
712
+ */
713
+ public function get_wc_emails() {
714
+ // get emails from WooCommerce
715
+ global $woocommerce;
716
+ $mailer = $woocommerce->mailer();
717
+ $wc_emails = $mailer->get_emails();
718
+
719
+ $non_order_emails = array(
720
+ 'customer_note',
721
+ 'customer_reset_password',
722
+ 'customer_new_account'
723
+ );
724
+
725
+ $emails = array();
726
+ foreach ($wc_emails as $class => $email) {
727
+ if ( !in_array( $email->id, $non_order_emails ) ) {
728
+ switch ($email->id) {
729
+ case 'new_order':
730
+ $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Admin email', 'woocommerce-pdf-invoices-packing-slips' ) );
731
+ break;
732
+ case 'customer_invoice':
733
+ $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Manual email', 'woocommerce-pdf-invoices-packing-slips' ) );
734
+ break;
735
+ default:
736
+ $emails[$email->id] = $email->title;
737
+ break;
738
+ }
739
+ }
740
+ }
741
+
742
+ return apply_filters( 'wpo_wcpdf_wc_emails', $emails );
743
+ }
744
+
745
+ // get list of WooCommerce statuses
746
+ public function get_wc_order_status_list() {
747
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.2', '<' ) ) {
748
+ $statuses = (array) get_terms( 'shop_order_status', array( 'hide_empty' => 0, 'orderby' => 'id' ) );
749
+ foreach ( $statuses as $status ) {
750
+ $order_statuses[esc_attr( $status->slug )] = esc_html__( $status->name, 'woocommerce' );
751
+ }
752
+ } else {
753
+ $statuses = wc_get_order_statuses();
754
+ foreach ( $statuses as $status_slug => $status ) {
755
+ $status_slug = 'wc-' === substr( $status_slug, 0, 3 ) ? substr( $status_slug, 3 ) : $status_slug;
756
+ $order_statuses[$status_slug] = $status;
757
+ }
758
+ }
759
+ return $order_statuses;
760
+ }
761
+
762
+
763
+ }
764
+
765
+ endif; // class_exists
readme.txt CHANGED
@@ -1,290 +1,294 @@
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: 4.9
7
- Requires PHP: 5.3
8
- Stable tag: 2.2.3
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.3 =
107
- * Fix: issues reading shop settings
108
-
109
- = 2.2.2 =
110
- * Feature: Added option to always use most current settings for the invoice
111
- * Fix: Double check for empty document numbers on initialization
112
- * New filter: `wpo_wcpdf_output_format` to set output per document type
113
-
114
- = 2.2.1 =
115
- * Fix: potential number formatting issues with `wpo_wcpdf_raw_document_number` filter
116
- * Fix: prevent direct loading of template files
117
-
118
- = 2.2.0 =
119
- * Feature: Document settings are now saved per order - changing settings after a PDF has been created will no longer affect the output
120
- * Feature: Button to delete invoice or packing slip
121
- * Feature: Better error handling and logging via WC Logger (WooCommerce > Status > Logs)
122
- * Fix: Broader payment gateway compatibility (lower priority for documents initialization)
123
- * Fix: undefined variable in construct when loading document programmatically (props to Christopher)
124
- * Fix: compatibility with renamed WooCommerce plugins (settings page detection)
125
- * Tweak: Reload translations before creating attachment
126
- * Translations: Updated translations POT
127
-
128
- = 2.1.10 =
129
- * Feature: Include invoice number and date in WooCommerce data remover and exporter
130
- * Fix: Row class for Chained Products compatibility
131
- * Fix: Improved compatibility with Advanced Custom Fields
132
- * Fix: Setting for diabling for free invoices should be applied even when other plugins are applying rules
133
-
134
- = 2.1.9 =
135
- * Feature: Automatic cleanup of temporary attachments folder (settings in Status tab)
136
- * Fix: prevent infinite loop on sites without uploads folder
137
- * Fix: tag replacements for externally hosted images (CDN)
138
-
139
- = 2.1.8 =
140
- * Fix: Fatal error on PHP 5.X
141
-
142
- = 2.1.7 =
143
- * Feature: add [order_number] placeholder for number format
144
- * Feature: $order and $order_id variables now available directly template (without needing the document object)
145
- * Feature: add actions before & after addresses
146
- * Fix: Sorting orders by invoice number
147
- * Fix: Aelia Currency Switcher - use decimal & Thousand separator settings
148
- * Fix: fix jquery migrate warnings for media upload script
149
- * Tweak: add calculated tax rate to item data
150
-
151
- = 2.1.6 =
152
- * Fix: Extended currency symbol setting for WooCommerce Currency Switcher by realmag777
153
- * Fix: Apply WooCommerce decimal settings to tax rates with decimals
154
- * Tweak: Pass document object to `wpo_wcpdf_email_attachment` filter
155
-
156
- = 2.1.5 =
157
- * Feature: Filter for number store table (wpo_wcpdf_number_store_table_name)
158
- * Fix: prevent accessing order properties as custom field/order meta
159
- * Fix: prevent wrong application of wpo_wcpdf_filename filter
160
- * Fix: Improved tax rate calculation fallback
161
-
162
- = 2.1.4 =
163
- * Fix: WooCommerce 3.3 action buttons
164
- * Feature: Added row classes for WooCommerce Composite Products
165
-
166
- = 2.1.3 =
167
- * Fix: Fatal PHP error on My Account page.
168
-
169
- = 2.1.2 =
170
- * Feature: New action wpo_wcpdf_init_document
171
- * Fix: Use title getters for my-account and backend buttons
172
- * Fix: Legacy Premium Templates reference
173
- * Tweak: Skip documents overview in settings, default to invoice
174
-
175
- = 2.1.1 =
176
- * Fix: WooCommerce Order Status & Actions Manager emails compatibility
177
- * Feature: sort orders by invoice number column
178
- * Tweak: pass document object to title filters
179
- * Tweak: use title getter in template files (instead of title string)
180
-
181
- = 2.1.0 =
182
- * Feature: WooCommerce Order Status & Actions Manager emails compatibility
183
- * Fix: Better url fallback for images stored in cloud
184
- * Update: dompdf library updated to 0.8.2 - DOMDocument parser set to default again
185
-
186
- = 2.0.15 =
187
- * Fix: Prevent saving invoice number/date from order details page when not edited
188
-
189
- = 2.0.14 =
190
- * Feature: Manually resend specific order emails in WooCommerce 3.2+
191
- * Tweak: Show full size logo preview in settings
192
- * Tweak: Custom field fallback to underscore prefixed meta key
193
- * Dev: added `wpo_wcpdf_before_sequential_number_increment` action
194
-
195
- = 2.0.13 =
196
- * Fix: Minor XSS issue on settings screens by escaping and sanitizing 'tab' & 'section' GET variables. Discovered by Detectify.
197
- * Fix: Pakistani Rupee Symbol
198
- * Feature: Automatically enable extended currency symbol support for currencies not supported by Open Sans
199
- * Dev: added `wpo_wcpdf_document_number_settings` filter
200
-
201
- = 2.0.12 =
202
- * Option: Use different HTML parser (debug settings)
203
-
204
- = 2.0.11 =
205
- * Fix: Improved fonts update routine (now preserves custom fonts)
206
- * Fix: Enable HTML5 parser by default (fixes issues with libxml)
207
- * Tweak: Show both PHP & WP Memory limit in Status tab
208
-
209
- = 2.0.10 =
210
- * Fix: Set invoice number backend button
211
- * Fix: Thumbail paths
212
- * Tweak: Make dompdf options filterable
213
-
214
- = 2.0.9 =
215
- * Feature: use `[invoice_date="ymd"]` in invoice number prefix or suffix to include a specific date format in the invoice number
216
- * Fix: Postmeta table prefix for invoice counter
217
- * Fix: 0% tax rates
218
-
219
- = 2.0.8 =
220
- * Feature: Add support for Bedrock / alternative folder structures
221
- * Dev: Filter for merged documents
222
- * Fix: Better attributes fallback for product variations
223
-
224
- = 2.0.7 =
225
- * Feature: Added button to delete legacy settings
226
- * Feature: Option to enable font subsetting
227
- * Fix: Invoice number sequence for databases with alternative auto_increment_increment settings
228
- * Fix: Fallback function for MB String (mb_stripos)
229
-
230
- = 2.0.6 =
231
- * Feature: Improved third party invoice number filters (`wpo_wcpdf_external_invoice_number_enabled` & `wpo_wcpdf_external_invoice_number`)
232
- * Fix: Underline position for Open Sans font
233
- * Fix: Invoice number auto_increment for servers that restarted frequently
234
- * Fix: Dompdf log file location (preventing open base_dir notices breaking PDF header)
235
- * Fix: 1.6.6 Settings migration duplicates merging
236
- * Tweak: Clear fonts folder when manually reinstalling fonts
237
-
238
- = 2.0.5 =
239
- * Feature: Remove temporary files (Status tab)
240
- * Fix: Page number replacement
241
- * Tweak: Fallback functions for MB String extension
242
- * Tweak: Improved wpo_wcpdf_check_privs usability for my account privileges
243
- * Legacy support: added wc_price alias for format_price method in document
244
-
245
- = 2.0.4 =
246
- * Fix: Apply filters for custom invoice number formatting in document too
247
- * Fix: Parent fallback for missing dates from refunds
248
-
249
- = 2.0.3 =
250
- * Fix: Better support for legacy invoice number filter (`wpo_wcpdf_invoice_number` - replaced by `wpo_wcpdf_formatted_document_number`)
251
- * Fix: Document number formatting fallback to order date if no document date available
252
- * Fix: Updated classmap: PSR loading didn't work on some installations
253
- * Fix: Prevent order notes from all orders showing when document is not loaded properly in filter
254
- * Tweak: Disable deprecation notices during email sending
255
- * Tweak: ignore outdated language packs
256
-
257
- = 2.0.2 =
258
- * Fix: order notes using correct order_id
259
- * Fix: WC3.0 deprecation notice for currency
260
- * Fix: Avoid crashing on PHP5.2 and older
261
- * Fix: Only use PHP MB String when present
262
- * Fix: Remote images
263
- * Fix: Download option
264
-
265
- = 2.0.1 =
266
- * Fix: PHP 5.4 issue
267
-
268
- = 2.0.0 =
269
- * New: Better structured & more advanced settings for documents
270
- * New: Option to enable & disable Packing Slips or Invoices
271
- * New: Invoice number sequence stored separately for improved speed & performance
272
- * New: Completely rewritten codebase for more flexibility & better reliability
273
- * New: Updated PDF library to DOMPDF 0.8
274
- * New: PDF Library made pluggable (by using the `wpo_wcpdf_pdf_maker` filter)
275
- * New: lots of new functions & filters to allow developers to hook into the plugin
276
- * Changed: **$wpo_wcpdf variable is now deprecated** (legacy mode available & automatically enabled on update)
277
- * Fix: Improved PHP 7 & 7.1 support
278
- * Fix: Positive prices for refunds
279
- * Fix: Use parent for attributes retrieved for product variations
280
- * Fix: Set content type to PDF for download
281
-
282
- = 1.6.6 =
283
- * Feature: Facilitate downgrading from 2.0 (re-installing fonts & resetting version)
284
- * Fix: Update currencies font (added Georgian Lari)
285
- * Translations: Added Indonesian
286
-
287
- == Upgrade Notice ==
288
-
289
- = 2.1.10 =
 
 
 
 
290
  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: 4.9
7
+ Requires PHP: 5.3
8
+ Stable tag: 2.2.4
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.4 =
107
+ * Fix: excluding some display options from historical settings
108
+ * Fix: fix notices when requesting properties as custom fields (in a custom template)
109
+
110
+ = 2.2.3 =
111
+ * Fix: issues reading shop settings
112
+
113
+ = 2.2.2 =
114
+ * Feature: Added option to always use most current settings for the invoice
115
+ * Fix: Double check for empty document numbers on initialization
116
+ * New filter: `wpo_wcpdf_output_format` to set output per document type
117
+
118
+ = 2.2.1 =
119
+ * Fix: potential number formatting issues with `wpo_wcpdf_raw_document_number` filter
120
+ * Fix: prevent direct loading of template files
121
+
122
+ = 2.2.0 =
123
+ * Feature: Document settings are now saved per order - changing settings after a PDF has been created will no longer affect the output
124
+ * Feature: Button to delete invoice or packing slip
125
+ * Feature: Better error handling and logging via WC Logger (WooCommerce > Status > Logs)
126
+ * Fix: Broader payment gateway compatibility (lower priority for documents initialization)
127
+ * Fix: undefined variable in construct when loading document programmatically (props to Christopher)
128
+ * Fix: compatibility with renamed WooCommerce plugins (settings page detection)
129
+ * Tweak: Reload translations before creating attachment
130
+ * Translations: Updated translations POT
131
+
132
+ = 2.1.10 =
133
+ * Feature: Include invoice number and date in WooCommerce data remover and exporter
134
+ * Fix: Row class for Chained Products compatibility
135
+ * Fix: Improved compatibility with Advanced Custom Fields
136
+ * Fix: Setting for diabling for free invoices should be applied even when other plugins are applying rules
137
+
138
+ = 2.1.9 =
139
+ * Feature: Automatic cleanup of temporary attachments folder (settings in Status tab)
140
+ * Fix: prevent infinite loop on sites without uploads folder
141
+ * Fix: tag replacements for externally hosted images (CDN)
142
+
143
+ = 2.1.8 =
144
+ * Fix: Fatal error on PHP 5.X
145
+
146
+ = 2.1.7 =
147
+ * Feature: add [order_number] placeholder for number format
148
+ * Feature: $order and $order_id variables now available directly template (without needing the document object)
149
+ * Feature: add actions before & after addresses
150
+ * Fix: Sorting orders by invoice number
151
+ * Fix: Aelia Currency Switcher - use decimal & Thousand separator settings
152
+ * Fix: fix jquery migrate warnings for media upload script
153
+ * Tweak: add calculated tax rate to item data
154
+
155
+ = 2.1.6 =
156
+ * Fix: Extended currency symbol setting for WooCommerce Currency Switcher by realmag777
157
+ * Fix: Apply WooCommerce decimal settings to tax rates with decimals
158
+ * Tweak: Pass document object to `wpo_wcpdf_email_attachment` filter
159
+
160
+ = 2.1.5 =
161
+ * Feature: Filter for number store table (wpo_wcpdf_number_store_table_name)
162
+ * Fix: prevent accessing order properties as custom field/order meta
163
+ * Fix: prevent wrong application of wpo_wcpdf_filename filter
164
+ * Fix: Improved tax rate calculation fallback
165
+
166
+ = 2.1.4 =
167
+ * Fix: WooCommerce 3.3 action buttons
168
+ * Feature: Added row classes for WooCommerce Composite Products
169
+
170
+ = 2.1.3 =
171
+ * Fix: Fatal PHP error on My Account page.
172
+
173
+ = 2.1.2 =
174
+ * Feature: New action wpo_wcpdf_init_document
175
+ * Fix: Use title getters for my-account and backend buttons
176
+ * Fix: Legacy Premium Templates reference
177
+ * Tweak: Skip documents overview in settings, default to invoice
178
+
179
+ = 2.1.1 =
180
+ * Fix: WooCommerce Order Status & Actions Manager emails compatibility
181
+ * Feature: sort orders by invoice number column
182
+ * Tweak: pass document object to title filters
183
+ * Tweak: use title getter in template files (instead of title string)
184
+
185
+ = 2.1.0 =
186
+ * Feature: WooCommerce Order Status & Actions Manager emails compatibility
187
+ * Fix: Better url fallback for images stored in cloud
188
+ * Update: dompdf library updated to 0.8.2 - DOMDocument parser set to default again
189
+
190
+ = 2.0.15 =
191
+ * Fix: Prevent saving invoice number/date from order details page when not edited
192
+
193
+ = 2.0.14 =
194
+ * Feature: Manually resend specific order emails in WooCommerce 3.2+
195
+ * Tweak: Show full size logo preview in settings
196
+ * Tweak: Custom field fallback to underscore prefixed meta key
197
+ * Dev: added `wpo_wcpdf_before_sequential_number_increment` action
198
+
199
+ = 2.0.13 =
200
+ * Fix: Minor XSS issue on settings screens by escaping and sanitizing 'tab' & 'section' GET variables. Discovered by Detectify.
201
+ * Fix: Pakistani Rupee Symbol
202
+ * Feature: Automatically enable extended currency symbol support for currencies not supported by Open Sans
203
+ * Dev: added `wpo_wcpdf_document_number_settings` filter
204
+
205
+ = 2.0.12 =
206
+ * Option: Use different HTML parser (debug settings)
207
+
208
+ = 2.0.11 =
209
+ * Fix: Improved fonts update routine (now preserves custom fonts)
210
+ * Fix: Enable HTML5 parser by default (fixes issues with libxml)
211
+ * Tweak: Show both PHP & WP Memory limit in Status tab
212
+
213
+ = 2.0.10 =
214
+ * Fix: Set invoice number backend button
215
+ * Fix: Thumbail paths
216
+ * Tweak: Make dompdf options filterable
217
+
218
+ = 2.0.9 =
219
+ * Feature: use `[invoice_date="ymd"]` in invoice number prefix or suffix to include a specific date format in the invoice number
220
+ * Fix: Postmeta table prefix for invoice counter
221
+ * Fix: 0% tax rates
222
+
223
+ = 2.0.8 =
224
+ * Feature: Add support for Bedrock / alternative folder structures
225
+ * Dev: Filter for merged documents
226
+ * Fix: Better attributes fallback for product variations
227
+
228
+ = 2.0.7 =
229
+ * Feature: Added button to delete legacy settings
230
+ * Feature: Option to enable font subsetting
231
+ * Fix: Invoice number sequence for databases with alternative auto_increment_increment settings
232
+ * Fix: Fallback function for MB String (mb_stripos)
233
+
234
+ = 2.0.6 =
235
+ * Feature: Improved third party invoice number filters (`wpo_wcpdf_external_invoice_number_enabled` & `wpo_wcpdf_external_invoice_number`)
236
+ * Fix: Underline position for Open Sans font
237
+ * Fix: Invoice number auto_increment for servers that restarted frequently
238
+ * Fix: Dompdf log file location (preventing open base_dir notices breaking PDF header)
239
+ * Fix: 1.6.6 Settings migration duplicates merging
240
+ * Tweak: Clear fonts folder when manually reinstalling fonts
241
+
242
+ = 2.0.5 =
243
+ * Feature: Remove temporary files (Status tab)
244
+ * Fix: Page number replacement
245
+ * Tweak: Fallback functions for MB String extension
246
+ * Tweak: Improved wpo_wcpdf_check_privs usability for my account privileges
247
+ * Legacy support: added wc_price alias for format_price method in document
248
+
249
+ = 2.0.4 =
250
+ * Fix: Apply filters for custom invoice number formatting in document too
251
+ * Fix: Parent fallback for missing dates from refunds
252
+
253
+ = 2.0.3 =
254
+ * Fix: Better support for legacy invoice number filter (`wpo_wcpdf_invoice_number` - replaced by `wpo_wcpdf_formatted_document_number`)
255
+ * Fix: Document number formatting fallback to order date if no document date available
256
+ * Fix: Updated classmap: PSR loading didn't work on some installations
257
+ * Fix: Prevent order notes from all orders showing when document is not loaded properly in filter
258
+ * Tweak: Disable deprecation notices during email sending
259
+ * Tweak: ignore outdated language packs
260
+
261
+ = 2.0.2 =
262
+ * Fix: order notes using correct order_id
263
+ * Fix: WC3.0 deprecation notice for currency
264
+ * Fix: Avoid crashing on PHP5.2 and older
265
+ * Fix: Only use PHP MB String when present
266
+ * Fix: Remote images
267
+ * Fix: Download option
268
+
269
+ = 2.0.1 =
270
+ * Fix: PHP 5.4 issue
271
+
272
+ = 2.0.0 =
273
+ * New: Better structured & more advanced settings for documents
274
+ * New: Option to enable & disable Packing Slips or Invoices
275
+ * New: Invoice number sequence stored separately for improved speed & performance
276
+ * New: Completely rewritten codebase for more flexibility & better reliability
277
+ * New: Updated PDF library to DOMPDF 0.8
278
+ * New: PDF Library made pluggable (by using the `wpo_wcpdf_pdf_maker` filter)
279
+ * New: lots of new functions & filters to allow developers to hook into the plugin
280
+ * Changed: **$wpo_wcpdf variable is now deprecated** (legacy mode available & automatically enabled on update)
281
+ * Fix: Improved PHP 7 & 7.1 support
282
+ * Fix: Positive prices for refunds
283
+ * Fix: Use parent for attributes retrieved for product variations
284
+ * Fix: Set content type to PDF for download
285
+
286
+ = 1.6.6 =
287
+ * Feature: Facilitate downgrading from 2.0 (re-installing fonts & resetting version)
288
+ * Fix: Update currencies font (added Georgian Lari)
289
+ * Translations: Added Indonesian
290
+
291
+ == Upgrade Notice ==
292
+
293
+ = 2.1.10 =
294
  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,358 +1,358 @@
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.3
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.5.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.3';
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 = apply_filters( 'plugin_locale', get_locale(), 'woocommerce-pdf-invoices-packing-slips' );
76
- $dir = trailingslashit( WP_LANG_DIR );
77
-
78
- $textdomains = array( 'woocommerce-pdf-invoices-packing-slips' );
79
- if ( $this->legacy_mode_enabled() === true ) {
80
- $textdomains[] = 'wpo_wcpdf';
81
- }
82
-
83
- /**
84
- * Frontend/global Locale. Looks in:
85
- *
86
- * - WP_LANG_DIR/woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
87
- * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
88
- * - woocommerce-pdf-invoices-packing-slips-pro/languages/woocommerce-pdf-invoices-packing-slips-LOCALE.mo (which if not found falls back to:)
89
- * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
90
- */
91
- foreach ( $textdomains as $textdomain ) {
92
- unload_textdomain( $textdomain );
93
- load_textdomain( $textdomain, $dir . 'woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
94
- load_textdomain( $textdomain, $dir . 'plugins/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
95
- load_plugin_textdomain( $textdomain, false, dirname( plugin_basename(__FILE__) ) . '/languages' );
96
- }
97
- }
98
-
99
- /**
100
- * Maintain backwards compatibility with old translation files
101
- * Uses old .mo file if it exists in any of the override locations
102
- */
103
- public function textdomain_fallback( $mofile, $textdomain ) {
104
- $plugin_domain = 'woocommerce-pdf-invoices-packing-slips';
105
- $old_domain = 'wpo_wcpdf';
106
-
107
- if ($textdomain == $old_domain) {
108
- $textdomain = $plugin_domain;
109
- $mofile = str_replace( "{$old_domain}-", "{$textdomain}-", $mofile ); // with trailing dash to target file and not folder
110
- }
111
-
112
- if ( $textdomain === $plugin_domain ) {
113
- $old_mofile = str_replace( "{$textdomain}-", "{$old_domain}-", $mofile ); // with trailing dash to target file and not folder
114
- if ( file_exists( $old_mofile ) ) {
115
- // we have an old override - use it
116
- return $old_mofile;
117
- }
118
-
119
- // prevent loading outdated language packs
120
- $pofile = str_replace('.mo', '.po', $mofile);
121
- if ( file_exists( $pofile ) ) {
122
- // load po file
123
- $podata = file_get_contents($pofile);
124
- // set revision date threshold
125
- $block_before = strtotime( '2017-05-15' );
126
- // read revision date
127
- preg_match('~PO-Revision-Date: (.*?)\\\n~s',$podata,$matches);
128
- if (isset($matches[1])) {
129
- $revision_date = $matches[1];
130
- if ( $revision_timestamp = strtotime($revision_date) ) {
131
- // check if revision is before threshold date
132
- if ( $revision_timestamp < $block_before ) {
133
- // try bundled
134
- $bundled_file = $this->plugin_path() . '/languages/'. basename( $mofile );
135
- if (file_exists($bundled_file)) {
136
- return $bundled_file;
137
- } else {
138
- return '';
139
- }
140
- // delete po & mo file if possible
141
- // @unlink($pofile);
142
- // @unlink($mofile);
143
- }
144
- }
145
- }
146
- }
147
- }
148
-
149
- return $mofile;
150
- }
151
-
152
- /**
153
- * Load the main plugin classes and functions
154
- */
155
- public function includes() {
156
- // WooCommerce compatibility classes
157
- include_once( $this->plugin_path() . '/includes/compatibility/abstract-wc-data-compatibility.php' );
158
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-date-compatibility.php' );
159
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-core-compatibility.php' );
160
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-order-compatibility.php' );
161
- include_once( $this->plugin_path() . '/includes/compatibility/class-wc-product-compatibility.php' );
162
- include_once( $this->plugin_path() . '/includes/compatibility/wc-datetime-functions-compatibility.php' );
163
-
164
- // Third party compatibility
165
- include_once( $this->plugin_path() . '/includes/compatibility/class-wcpdf-compatibility-third-party-plugins.php' );
166
-
167
- // Plugin classes
168
- include_once( $this->plugin_path() . '/includes/wcpdf-functions.php' );
169
- $this->settings = include_once( $this->plugin_path() . '/includes/class-wcpdf-settings.php' );
170
- $this->documents = include_once( $this->plugin_path() . '/includes/class-wcpdf-documents.php' );
171
- $this->main = include_once( $this->plugin_path() . '/includes/class-wcpdf-main.php' );
172
- include_once( $this->plugin_path() . '/includes/class-wcpdf-assets.php' );
173
- include_once( $this->plugin_path() . '/includes/class-wcpdf-admin.php' );
174
- include_once( $this->plugin_path() . '/includes/class-wcpdf-frontend.php' );
175
- include_once( $this->plugin_path() . '/includes/class-wcpdf-install.php' );
176
-
177
- // Backwards compatibility with self
178
- include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy.php' );
179
- include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy-deprecated-hooks.php' );
180
-
181
- // PHP MB String fallback functions
182
- include_once( $this->plugin_path() . '/includes/compatibility/mb-string-compatibility.php' );
183
- }
184
-
185
-
186
- /**
187
- * Instantiate classes when woocommerce is activated
188
- */
189
- public function load_classes() {
190
- if ( $this->is_woocommerce_activated() === false ) {
191
- add_action( 'admin_notices', array ( $this, 'need_woocommerce' ) );
192
- return;
193
- }
194
-
195
- if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
196
- add_action( 'admin_notices', array ( $this, 'required_php_version' ) );
197
- return;
198
- }
199
-
200
- // all systems ready - GO!
201
- $this->includes();
202
- }
203
-
204
- /**
205
- * Check if legacy mode is enabled
206
- */
207
- public function legacy_mode_enabled() {
208
- if (!isset($this->legacy_mode)) {
209
- $debug_settings = get_option( 'wpo_wcpdf_settings_debug' );
210
- $this->legacy_mode = isset($debug_settings['legacy_mode']);
211
- }
212
- return $this->legacy_mode;
213
- }
214
-
215
-
216
- /**
217
- * Check if woocommerce is activated
218
- */
219
- public function is_woocommerce_activated() {
220
- $blog_plugins = get_option( 'active_plugins', array() );
221
- $site_plugins = is_multisite() ? (array) maybe_unserialize( get_site_option('active_sitewide_plugins' ) ) : array();
222
-
223
- if ( in_array( 'woocommerce/woocommerce.php', $blog_plugins ) || isset( $site_plugins['woocommerce/woocommerce.php'] ) ) {
224
- return true;
225
- } else {
226
- return false;
227
- }
228
- }
229
-
230
- /**
231
- * WooCommerce not active notice.
232
- *
233
- * @return string Fallack notice.
234
- */
235
-
236
- public function need_woocommerce() {
237
- $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>' );
238
-
239
- $message = '<div class="error"><p>' . $error . '</p></div>';
240
-
241
- echo $message;
242
- }
243
-
244
- /**
245
- * PHP version requirement notice
246
- */
247
-
248
- public function required_php_version() {
249
- $error = __( 'WooCommerce PDF Invoices & Packing Slips requires PHP 5.3 or higher (5.6 or higher recommended).', 'woocommerce-pdf-invoices-packing-slips' );
250
- $how_to_update = __( 'How to update your PHP version', 'woocommerce-pdf-invoices-packing-slips' );
251
- $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);
252
-
253
- echo $message;
254
- }
255
-
256
- /**
257
- * Show plugin changes. Code adapted from W3 Total Cache.
258
- */
259
- public function in_plugin_update_message( $args ) {
260
- $transient_name = 'wpo_wcpdf_upgrade_notice_' . $args['Version'];
261
-
262
- if ( false === ( $upgrade_notice = get_transient( $transient_name ) ) ) {
263
- $response = wp_safe_remote_get( 'https://plugins.svn.wordpress.org/woocommerce-pdf-invoices-packing-slips/trunk/readme.txt' );
264
-
265
- if ( ! is_wp_error( $response ) && ! empty( $response['body'] ) ) {
266
- $upgrade_notice = self::parse_update_notice( $response['body'], $args['new_version'] );
267
- set_transient( $transient_name, $upgrade_notice, DAY_IN_SECONDS );
268
- }
269
- }
270
-
271
- echo wp_kses_post( $upgrade_notice );
272
- }
273
-
274
- /**
275
- * Parse update notice from readme file.
276
- *
277
- * @param string $content
278
- * @param string $new_version
279
- * @return string
280
- */
281
- private function parse_update_notice( $content, $new_version ) {
282
- // Output Upgrade Notice.
283
- $matches = null;
284
- $regexp = '~==\s*Upgrade Notice\s*==\s*=\s*(.*)\s*=(.*)(=\s*' . preg_quote( $new_version ) . '\s*=|$)~Uis';
285
- $upgrade_notice = '';
286
-
287
-
288
- if ( preg_match( $regexp, $content, $matches ) ) {
289
- $notices = (array) preg_split( '~[\r\n]+~', trim( $matches[2] ) );
290
-
291
- // Convert the full version strings to minor versions.
292
- $notice_version_parts = explode( '.', trim( $matches[1] ) );
293
- $current_version_parts = explode( '.', $this->version );
294
-
295
- if ( 3 !== sizeof( $notice_version_parts ) ) {
296
- return;
297
- }
298
-
299
- $notice_version = $notice_version_parts[0] . '.' . $notice_version_parts[1];
300
- $current_version = $current_version_parts[0] . '.' . $current_version_parts[1];
301
-
302
- // Check the latest stable version and ignore trunk.
303
- if ( version_compare( $current_version, $notice_version, '<' ) ) {
304
-
305
- $upgrade_notice .= '</p><p class="wpo_wcpdf_upgrade_notice">';
306
-
307
- foreach ( $notices as $index => $line ) {
308
- $upgrade_notice .= preg_replace( '~\[([^\]]*)\]\(([^\)]*)\)~', '<a href="${2}">${1}</a>', $line );
309
- }
310
- }
311
- }
312
-
313
- return wp_kses_post( $upgrade_notice );
314
- }
315
-
316
- /**
317
- * Get the plugin url.
318
- * @return string
319
- */
320
- public function plugin_url() {
321
- return untrailingslashit( plugins_url( '/', __FILE__ ) );
322
- }
323
-
324
- /**
325
- * Get the plugin path.
326
- * @return string
327
- */
328
- public function plugin_path() {
329
- return untrailingslashit( plugin_dir_path( __FILE__ ) );
330
- }
331
-
332
- } // class WPO_WCPDF
333
-
334
- endif; // class_exists
335
-
336
- /**
337
- * Returns the main instance of WooCommerce PDF Invoices & Packing Slips to prevent the need to use globals.
338
- *
339
- * @since 1.6
340
- * @return WPO_WCPDF
341
- */
342
- function WPO_WCPDF() {
343
- return WPO_WCPDF::instance();
344
- }
345
-
346
- WPO_WCPDF(); // load plugin
347
-
348
- // legacy class for plugin detecting
349
- if ( !class_exists( 'WooCommerce_PDF_Invoices' ) ) {
350
- class WooCommerce_PDF_Invoices{
351
- public static $version;
352
-
353
- public function __construct() {
354
- self::$version = WPO_WCPDF()->version;
355
- }
356
- }
357
- new WooCommerce_PDF_Invoices();
358
- }
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.4
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.5.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.4';
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 = apply_filters( 'plugin_locale', get_locale(), 'woocommerce-pdf-invoices-packing-slips' );
76
+ $dir = trailingslashit( WP_LANG_DIR );
77
+
78
+ $textdomains = array( 'woocommerce-pdf-invoices-packing-slips' );
79
+ if ( $this->legacy_mode_enabled() === true ) {
80
+ $textdomains[] = 'wpo_wcpdf';
81
+ }
82
+
83
+ /**
84
+ * Frontend/global Locale. Looks in:
85
+ *
86
+ * - WP_LANG_DIR/woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
87
+ * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
88
+ * - woocommerce-pdf-invoices-packing-slips-pro/languages/woocommerce-pdf-invoices-packing-slips-LOCALE.mo (which if not found falls back to:)
89
+ * - WP_LANG_DIR/plugins/woocommerce-pdf-invoices-packing-slips-LOCALE.mo
90
+ */
91
+ foreach ( $textdomains as $textdomain ) {
92
+ unload_textdomain( $textdomain );
93
+ load_textdomain( $textdomain, $dir . 'woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
94
+ load_textdomain( $textdomain, $dir . 'plugins/woocommerce-pdf-invoices-packing-slips-' . $locale . '.mo' );
95
+ load_plugin_textdomain( $textdomain, false, dirname( plugin_basename(__FILE__) ) . '/languages' );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Maintain backwards compatibility with old translation files
101
+ * Uses old .mo file if it exists in any of the override locations
102
+ */
103
+ public function textdomain_fallback( $mofile, $textdomain ) {
104
+ $plugin_domain = 'woocommerce-pdf-invoices-packing-slips';
105
+ $old_domain = 'wpo_wcpdf';
106
+
107
+ if ($textdomain == $old_domain) {
108
+ $textdomain = $plugin_domain;
109
+ $mofile = str_replace( "{$old_domain}-", "{$textdomain}-", $mofile ); // with trailing dash to target file and not folder
110
+ }
111
+
112
+ if ( $textdomain === $plugin_domain ) {
113
+ $old_mofile = str_replace( "{$textdomain}-", "{$old_domain}-", $mofile ); // with trailing dash to target file and not folder
114
+ if ( file_exists( $old_mofile ) ) {
115
+ // we have an old override - use it
116
+ return $old_mofile;
117
+ }
118
+
119
+ // prevent loading outdated language packs
120
+ $pofile = str_replace('.mo', '.po', $mofile);
121
+ if ( file_exists( $pofile ) ) {
122
+ // load po file
123
+ $podata = file_get_contents($pofile);
124
+ // set revision date threshold
125
+ $block_before = strtotime( '2017-05-15' );
126
+ // read revision date
127
+ preg_match('~PO-Revision-Date: (.*?)\\\n~s',$podata,$matches);
128
+ if (isset($matches[1])) {
129
+ $revision_date = $matches[1];
130
+ if ( $revision_timestamp = strtotime($revision_date) ) {
131
+ // check if revision is before threshold date
132
+ if ( $revision_timestamp < $block_before ) {
133
+ // try bundled
134
+ $bundled_file = $this->plugin_path() . '/languages/'. basename( $mofile );
135
+ if (file_exists($bundled_file)) {
136
+ return $bundled_file;
137
+ } else {
138
+ return '';
139
+ }
140
+ // delete po & mo file if possible
141
+ // @unlink($pofile);
142
+ // @unlink($mofile);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ return $mofile;
150
+ }
151
+
152
+ /**
153
+ * Load the main plugin classes and functions
154
+ */
155
+ public function includes() {
156
+ // WooCommerce compatibility classes
157
+ include_once( $this->plugin_path() . '/includes/compatibility/abstract-wc-data-compatibility.php' );
158
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-date-compatibility.php' );
159
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-core-compatibility.php' );
160
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-order-compatibility.php' );
161
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wc-product-compatibility.php' );
162
+ include_once( $this->plugin_path() . '/includes/compatibility/wc-datetime-functions-compatibility.php' );
163
+
164
+ // Third party compatibility
165
+ include_once( $this->plugin_path() . '/includes/compatibility/class-wcpdf-compatibility-third-party-plugins.php' );
166
+
167
+ // Plugin classes
168
+ include_once( $this->plugin_path() . '/includes/wcpdf-functions.php' );
169
+ $this->settings = include_once( $this->plugin_path() . '/includes/class-wcpdf-settings.php' );
170
+ $this->documents = include_once( $this->plugin_path() . '/includes/class-wcpdf-documents.php' );
171
+ $this->main = include_once( $this->plugin_path() . '/includes/class-wcpdf-main.php' );
172
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-assets.php' );
173
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-admin.php' );
174
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-frontend.php' );
175
+ include_once( $this->plugin_path() . '/includes/class-wcpdf-install.php' );
176
+
177
+ // Backwards compatibility with self
178
+ include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy.php' );
179
+ include_once( $this->plugin_path() . '/includes/legacy/class-wcpdf-legacy-deprecated-hooks.php' );
180
+
181
+ // PHP MB String fallback functions
182
+ include_once( $this->plugin_path() . '/includes/compatibility/mb-string-compatibility.php' );
183
+ }
184
+
185
+
186
+ /**
187
+ * Instantiate classes when woocommerce is activated
188
+ */
189
+ public function load_classes() {
190
+ if ( $this->is_woocommerce_activated() === false ) {
191
+ add_action( 'admin_notices', array ( $this, 'need_woocommerce' ) );
192
+ return;
193
+ }
194
+
195
+ if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
196
+ add_action( 'admin_notices', array ( $this, 'required_php_version' ) );
197
+ return;
198
+ }
199
+
200
+ // all systems ready - GO!
201
+ $this->includes();
202
+ }
203
+
204
+ /**
205
+ * Check if legacy mode is enabled
206
+ */
207
+ public function legacy_mode_enabled() {
208
+ if (!isset($this->legacy_mode)) {
209
+ $debug_settings = get_option( 'wpo_wcpdf_settings_debug' );
210
+ $this->legacy_mode = isset($debug_settings['legacy_mode']);
211
+ }
212
+ return $this->legacy_mode;
213
+ }
214
+
215
+
216
+ /**
217
+ * Check if woocommerce is activated
218
+ */
219
+ public function is_woocommerce_activated() {
220
+ $blog_plugins = get_option( 'active_plugins', array() );
221
+ $site_plugins = is_multisite() ? (array) maybe_unserialize( get_site_option('active_sitewide_plugins' ) ) : array();
222
+
223
+ if ( in_array( 'woocommerce/woocommerce.php', $blog_plugins ) || isset( $site_plugins['woocommerce/woocommerce.php'] ) ) {
224
+ return true;
225
+ } else {
226
+ return false;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * WooCommerce not active notice.
232
+ *
233
+ * @return string Fallack notice.
234
+ */
235
+
236
+ public function need_woocommerce() {
237
+ $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>' );
238
+
239
+ $message = '<div class="error"><p>' . $error . '</p></div>';
240
+
241
+ echo $message;
242
+ }
243
+
244
+ /**
245
+ * PHP version requirement notice
246
+ */
247
+
248
+ public function required_php_version() {
249
+ $error = __( 'WooCommerce PDF Invoices & Packing Slips requires PHP 5.3 or higher (5.6 or higher recommended).', 'woocommerce-pdf-invoices-packing-slips' );
250
+ $how_to_update = __( 'How to update your PHP version', 'woocommerce-pdf-invoices-packing-slips' );
251
+ $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);
252
+
253
+ echo $message;
254
+ }
255
+
256
+ /**
257
+ * Show plugin changes. Code adapted from W3 Total Cache.
258
+ */
259
+ public function in_plugin_update_message( $args ) {
260
+ $transient_name = 'wpo_wcpdf_upgrade_notice_' . $args['Version'];
261
+
262
+ if ( false === ( $upgrade_notice = get_transient( $transient_name ) ) ) {
263
+ $response = wp_safe_remote_get( 'https://plugins.svn.wordpress.org/woocommerce-pdf-invoices-packing-slips/trunk/readme.txt' );
264
+
265
+ if ( ! is_wp_error( $response ) && ! empty( $response['body'] ) ) {
266
+ $upgrade_notice = self::parse_update_notice( $response['body'], $args['new_version'] );
267
+ set_transient( $transient_name, $upgrade_notice, DAY_IN_SECONDS );
268
+ }
269
+ }
270
+
271
+ echo wp_kses_post( $upgrade_notice );
272
+ }
273
+
274
+ /**
275
+ * Parse update notice from readme file.
276
+ *
277
+ * @param string $content
278
+ * @param string $new_version
279
+ * @return string
280
+ */
281
+ private function parse_update_notice( $content, $new_version ) {
282
+ // Output Upgrade Notice.
283
+ $matches = null;
284
+ $regexp = '~==\s*Upgrade Notice\s*==\s*=\s*(.*)\s*=(.*)(=\s*' . preg_quote( $new_version ) . '\s*=|$)~Uis';
285
+ $upgrade_notice = '';
286
+
287
+
288
+ if ( preg_match( $regexp, $content, $matches ) ) {
289
+ $notices = (array) preg_split( '~[\r\n]+~', trim( $matches[2] ) );
290
+
291
+ // Convert the full version strings to minor versions.
292
+ $notice_version_parts = explode( '.', trim( $matches[1] ) );
293
+ $current_version_parts = explode( '.', $this->version );
294
+
295
+ if ( 3 !== sizeof( $notice_version_parts ) ) {
296
+ return;
297
+ }
298
+
299
+ $notice_version = $notice_version_parts[0] . '.' . $notice_version_parts[1];
300
+ $current_version = $current_version_parts[0] . '.' . $current_version_parts[1];
301
+
302
+ // Check the latest stable version and ignore trunk.
303
+ if ( version_compare( $current_version, $notice_version, '<' ) ) {
304
+
305
+ $upgrade_notice .= '</p><p class="wpo_wcpdf_upgrade_notice">';
306
+
307
+ foreach ( $notices as $index => $line ) {
308
+ $upgrade_notice .= preg_replace( '~\[([^\]]*)\]\(([^\)]*)\)~', '<a href="${2}">${1}</a>', $line );
309
+ }
310
+ }
311
+ }
312
+
313
+ return wp_kses_post( $upgrade_notice );
314
+ }
315
+
316
+ /**
317
+ * Get the plugin url.
318
+ * @return string
319
+ */
320
+ public function plugin_url() {
321
+ return untrailingslashit( plugins_url( '/', __FILE__ ) );
322
+ }
323
+
324
+ /**
325
+ * Get the plugin path.
326
+ * @return string
327
+ */
328
+ public function plugin_path() {
329
+ return untrailingslashit( plugin_dir_path( __FILE__ ) );
330
+ }
331
+
332
+ } // class WPO_WCPDF
333
+
334
+ endif; // class_exists
335
+
336
+ /**
337
+ * Returns the main instance of WooCommerce PDF Invoices & Packing Slips to prevent the need to use globals.
338
+ *
339
+ * @since 1.6
340
+ * @return WPO_WCPDF
341
+ */
342
+ function WPO_WCPDF() {
343
+ return WPO_WCPDF::instance();
344
+ }
345
+
346
+ WPO_WCPDF(); // load plugin
347
+
348
+ // legacy class for plugin detecting
349
+ if ( !class_exists( 'WooCommerce_PDF_Invoices' ) ) {
350
+ class WooCommerce_PDF_Invoices{
351
+ public static $version;
352
+
353
+ public function __construct() {
354
+ self::$version = WPO_WCPDF()->version;
355
+ }
356
+ }
357
+ new WooCommerce_PDF_Invoices();
358
+ }