WooCommerce PDF Invoices & Packing Slips - Version 2.2.5

Version Description

  • Feature: Check marks to indicate whether a document exists
  • Feature: Test mode to automatically apply updated settings to existing documents
  • Feature: Admin bar indicator for debug mode setting
  • Fix: always use latest email settings
  • Fix: WooCommerce Composit Products item name compatibility
  • Fix: Use woocommerce_thumbnail for WC3.3+
  • Tweak: apply woocommerce_order_item_name filter (fixes compatibility with WooCommerce Product Addons 3.0)
  • Tweak: Use WooCommerce date format instead of WP date format
Download this release

Release Info

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

Code changes from version 2.2.4 to 2.2.5

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