WooCommerce PDF Invoices & Packing Slips - Version 2.7.0

Version Description

  • New: Add per-order notes to invoices (requires template update if you have a custom template)
  • New: Show notice with instructions for protecting the invoice folder on NGINX setups
  • Fix: Show correct "next number" on settings page for sites using MySQL 8+
  • Tested up to WooCommerce 4.6
Download this release

Release Info

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

Code changes from version 2.6.1 to 2.7.0

assets/css/order-styles.css CHANGED
@@ -64,18 +64,20 @@
64
 
65
  /* Edit buttons Invoice, Proforma and Credit */
66
  .wcpdf-data-fields h4 .dashicons:first-of-type {
67
- margin-left:20px!important;
68
  }
69
 
70
  .wcpdf-data-fields .wpo-wcpdf-edit-date-number,
71
  .wcpdf-data-fields .wpo-wcpdf-delete-document,
72
- .wcpdf-data-fields .wpo-wcpdf-regenerate-document {
 
73
  opacity:0.5;
74
  }
75
 
76
  .wcpdf-data-fields .wpo-wcpdf-edit-date-number:hover,
77
  .wcpdf-data-fields .wpo-wcpdf-delete-document:hover,
78
- .wcpdf-data-fields .wpo-wcpdf-regenerate-document:hover {
 
79
  opacity:1;
80
  cursor:pointer;
81
  }
64
 
65
  /* Edit buttons Invoice, Proforma and Credit */
66
  .wcpdf-data-fields h4 .dashicons:first-of-type {
67
+ margin-left:20px !important;
68
  }
69
 
70
  .wcpdf-data-fields .wpo-wcpdf-edit-date-number,
71
  .wcpdf-data-fields .wpo-wcpdf-delete-document,
72
+ .wcpdf-data-fields .wpo-wcpdf-regenerate-document,
73
+ .wcpdf-data-fields .wpo-wcpdf-edit-document-notes {
74
  opacity:0.5;
75
  }
76
 
77
  .wcpdf-data-fields .wpo-wcpdf-edit-date-number:hover,
78
  .wcpdf-data-fields .wpo-wcpdf-delete-document:hover,
79
+ .wcpdf-data-fields .wpo-wcpdf-regenerate-document:hover,
80
+ .wcpdf-data-fields .wpo-wcpdf-edit-document-notes:hover {
81
  opacity:1;
82
  cursor:pointer;
83
  }
assets/images/nginx.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="97" height="21" viewBox="0 0 97 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M18.7764 20.6272C18.2368 20.6272 17.7113 20.4189 17.309 20.0213L5.06764 7.77995V18.5112C5.06764 19.6473 4.13983 20.5799 2.99901 20.5799C1.85819 20.5799 0.930389 19.7041 0.930389 18.5112V2.79537C0.930389 1.95751 1.43689 1.20485 2.20849 0.882961C2.98008 0.56107 3.86528 0.74095 4.46172 1.33266L16.7031 13.574V2.79537C16.7031 1.65929 17.6309 0.72675 18.7717 0.72675C19.9125 0.72675 20.8403 1.65455 20.8403 2.79537V18.5112C20.8403 19.6473 19.9125 20.5799 18.7717 20.5799L18.7764 20.6272ZM28.3479 0.698347L27.7799 1.75869L23.3776 9.74917L22.8095 10.7574L23.3776 11.7657L27.7799 19.4958L28.3716 20.5562H39.8745L40.4425 19.3917L43.7277 12.774L45.1951 9.77284H41.8626L33.4603 9.7965C32.3716 9.7823 31.3633 10.7716 31.3633 11.8651C31.3633 12.9586 32.3668 13.948 33.4603 13.9338L38.5254 13.9101L37.2851 16.4189H30.7811L27.5479 10.729L30.8047 4.83559H37.7348L39.3443 8.14918H43.5099L40.8875 1.86283L40.3194 0.698347H28.3432H28.3479ZM49.2945 0.674678C48.2058 0.688879 47.2401 1.68769 47.259 2.76697V8.16338H51.3963V2.76697C51.4105 1.66875 50.4495 0.660477 49.304 0.674678H49.2945ZM55.9312 20.6272C54.7951 20.6272 53.8625 19.6994 53.8625 18.5586V2.82851C53.8625 1.69242 54.7903 0.759885 55.9312 0.759885C57.072 0.759885 57.9998 1.68769 57.9998 2.82851V13.6071L70.2411 1.3658C70.8328 0.774085 71.7228 0.59894 72.4943 0.916097C73.2659 1.23325 73.7724 1.99064 73.7724 2.82851V18.6059C73.7724 19.742 72.8446 20.6745 71.7038 20.6745C70.563 20.6745 69.6352 19.7467 69.6352 18.6059V7.82255L57.3939 20.0639C57.0057 20.452 56.4803 20.6698 55.9264 20.6698L55.9312 20.6272ZM89.0859 10.7006L95.5238 4.29122C96.3285 3.48649 96.3285 2.17526 95.5285 1.3658C94.7285 0.556336 93.4125 0.56107 92.6031 1.36106L86.1653 7.78468L79.7274 1.36106C78.9227 0.556336 77.6067 0.556336 76.802 1.3658C75.9973 2.17526 75.9973 3.48649 76.8067 4.29122L83.2446 10.7006L76.8115 17.0911C76.0068 17.8959 76.0067 19.2071 76.8067 20.0165C77.1949 20.4094 77.7203 20.6319 78.2269 20.6319C78.7334 20.6319 79.2825 20.4331 79.6848 20.026L86.1084 13.6213L92.5321 20.026C92.9202 20.4142 93.4457 20.6319 93.99 20.6319C94.5344 20.6319 95.0504 20.4284 95.4101 20.0165C96.2149 19.2118 96.2149 17.9006 95.4054 17.0911L88.9913 10.7006H89.0859ZM49.1999 20.613C48.1111 20.5988 47.1454 19.6 47.1644 18.5207V9.85804H51.3016V18.5112C51.3158 19.6094 50.3549 20.6177 49.2093 20.6035" fill="#009900"/>
3
+ </svg>
assets/js/order-script.js CHANGED
@@ -32,11 +32,18 @@ jQuery(document).ready(function($) {
32
  $('#wpo_wcpdf-data-input-box').insertAfter('#woocommerce-order-data');
33
 
34
  // enable invoice number edit if user initiated
35
- $( ".wpo-wcpdf-set-date-number, .wpo-wcpdf-edit-date-number" ).click(function() {
36
  $form = $(this).closest('.wcpdf-data-fields');
37
- $form.find(".read-only").hide();
38
- $form.find(".editable").show();
39
- $form.find(':input').prop('disabled', false);
 
 
 
 
 
 
 
40
  });
41
 
42
  $( ".wcpdf-data-fields .wpo-wcpdf-delete-document" ).click(function() {
32
  $('#wpo_wcpdf-data-input-box').insertAfter('#woocommerce-order-data');
33
 
34
  // enable invoice number edit if user initiated
35
+ $( ".wpo-wcpdf-set-date-number, .wpo-wcpdf-edit-date-number, .wpo-wcpdf-edit-document-notes" ).click(function() {
36
  $form = $(this).closest('.wcpdf-data-fields');
37
+ // check visibility
38
+ if( $form.find(".read-only").is(":visible") ) {
39
+ $form.find(".read-only").hide();
40
+ $form.find(".editable").show();
41
+ $form.find(':input').prop('disabled', false);
42
+ } else {
43
+ $form.find(".read-only").show();
44
+ $form.find(".editable").hide();
45
+ $form.find(':input').prop('disabled', true);
46
+ }
47
  });
48
 
49
  $( ".wcpdf-data-fields .wpo-wcpdf-delete-document" ).click(function() {
includes/class-wcpdf-admin.php CHANGED
@@ -372,7 +372,10 @@ class Admin {
372
  if ( $invoice = wcpdf_get_invoice( $order ) ) {
373
  $invoice_number = $invoice->get_number();
374
  $invoice_date = $invoice->get_date();
 
 
375
  ?>
 
376
  <div class="wcpdf-data-fields" data-document="invoice" data-order_id="<?php echo WCX_Order::get_id( $order ); ?>">
377
  <h4>
378
  <?php echo $invoice->get_title(); ?><?php if ($invoice->exists()) : ?>
@@ -430,6 +433,38 @@ class Admin {
430
  </p>
431
  </div>
432
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  <?php
434
  }
435
 
@@ -522,6 +557,42 @@ class Admin {
522
  $invoice->set_number( $invoice_number );
523
  }
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  $invoice->save();
526
  }
527
  }
372
  if ( $invoice = wcpdf_get_invoice( $order ) ) {
373
  $invoice_number = $invoice->get_number();
374
  $invoice_date = $invoice->get_date();
375
+ $invoice_notes = !empty($invoice->get_document_notes()) ? $invoice->get_document_notes() : null;
376
+
377
  ?>
378
+
379
  <div class="wcpdf-data-fields" data-document="invoice" data-order_id="<?php echo WCX_Order::get_id( $order ); ?>">
380
  <h4>
381
  <?php echo $invoice->get_title(); ?><?php if ($invoice->exists()) : ?>
433
  </p>
434
  </div>
435
  </div>
436
+
437
+ <?php do_action( 'wpo_wcpdf_meta_box_before_document_notes', $invoice, $order ); ?>
438
+
439
+ <div class="wcpdf-data-fields" data-document="invoice" data-order_id="<?php echo WCX_Order::get_id( $order ); ?>">
440
+ <div class="invoice-notes">
441
+ <p class="form-field form-field-wide">
442
+ <div>
443
+ <span><strong><?php _e( 'Notes (printed in the invoice):', 'woocommerce-pdf-invoices-packing-slips' ); ?></strong></span>
444
+ <span class="wpo-wcpdf-edit-document-notes dashicons dashicons-edit"></span>
445
+ </div>
446
+ <!-- Read only -->
447
+ <div class="read-only">
448
+ <?php if ( $invoice->exists() ) : ?>
449
+ <p><?php if (!empty($invoice_notes)) echo $invoice_notes; ?></p>
450
+ <?php endif; ?>
451
+ </div>
452
+ <!-- Editable -->
453
+ <div class="editable">
454
+ <p class="form-field form-field-wide">
455
+ <?php if ( $invoice->exists() ) : ?>
456
+ <p><textarea name="wcpdf_invoice_notes" cols="60" rows="5" disabled="disabled"><?php if (!empty($invoice_notes)) echo $invoice_notes; ?></textarea></p>
457
+ <?php else : ?>
458
+ <p><textarea name="wcpdf_invoice_notes" cols="60" rows="5" disabled="disabled"></textarea></p>
459
+ <?php endif; ?>
460
+ </p>
461
+ </div>
462
+ </p>
463
+ </div>
464
+ </div>
465
+
466
+ <?php do_action( 'wpo_wcpdf_meta_box_after_document_notes', $invoice, $order ); ?>
467
+
468
  <?php
469
  }
470
 
557
  $invoice->set_number( $invoice_number );
558
  }
559
 
560
+ if ( isset( $_POST['wcpdf_invoice_notes'] ) ) {
561
+ // allowed HTML
562
+ $allowed_html = array(
563
+ 'a' => array(
564
+ 'href' => array(),
565
+ 'title' => array(),
566
+ 'id' => array(),
567
+ 'class' => array(),
568
+ 'style' => array(),
569
+ ),
570
+ 'br' => array(),
571
+ 'em' => array(),
572
+ 'strong'=> array(),
573
+ 'div' => array(
574
+ 'id' => array(),
575
+ 'class' => array(),
576
+ 'style' => array(),
577
+ ),
578
+ 'span' => array(
579
+ 'id' => array(),
580
+ 'class' => array(),
581
+ 'style' => array(),
582
+ ),
583
+ 'p' => array(
584
+ 'id' => array(),
585
+ 'class' => array(),
586
+ 'style' => array(),
587
+ ),
588
+ 'b' => array(),
589
+ );
590
+ // sanitize
591
+ $invoice_notes = wp_kses( stripslashes($_POST['wcpdf_invoice_notes']), $allowed_html );
592
+ // set notes
593
+ $invoice->set_notes( $invoice_notes );
594
+ }
595
+
596
  $invoice->save();
597
  }
598
  }
includes/documents/abstract-wcpdf-order-document-methods.php CHANGED
@@ -1207,6 +1207,18 @@ abstract class Order_Document_Methods extends Order_Document {
1207
  echo $this->get_invoice_date();
1208
  }
1209
 
 
 
 
 
 
 
 
 
 
 
 
 
1210
 
1211
  }
1212
 
1207
  echo $this->get_invoice_date();
1208
  }
1209
 
1210
+ public function get_document_notes() {
1211
+ if ( $document_notes = $this->get_notes( $this->get_type() ) ) {
1212
+ return $document_notes;
1213
+ } else {
1214
+ return '';
1215
+ }
1216
+ }
1217
+
1218
+ public function document_notes() {
1219
+ echo $this->get_document_notes();
1220
+ }
1221
+
1222
 
1223
  }
1224
 
includes/documents/abstract-wcpdf-order-document.php CHANGED
@@ -227,6 +227,7 @@ abstract class Order_Document {
227
  // always load date before number, because date is used in number formatting
228
  'date' => WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_date", true ),
229
  'number' => $number,
 
230
  ), $order );
231
 
232
  return;
@@ -260,6 +261,8 @@ abstract class Order_Document {
260
  WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data" );
261
  // deleting the number = deleting the document, so also delete document settings
262
  WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_settings" );
 
 
263
  }
264
  } else {
265
  if ( $key == 'date' ) {
@@ -270,6 +273,9 @@ abstract class Order_Document {
270
  // store both formatted number and number data
271
  WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->formatted_number );
272
  WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data", $value->to_array() );
 
 
 
273
  }
274
  }
275
  }
@@ -289,6 +295,7 @@ abstract class Order_Document {
289
  'date_formatted',
290
  'number',
291
  'number_data',
 
292
  ), $this );
293
  foreach ($data_to_remove as $data_key) {
294
  WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$data_key}" );
@@ -396,6 +403,10 @@ abstract class Order_Document {
396
  return $this->get_data( 'date', $document_type, $order, $context );
397
  }
398
 
 
 
 
 
399
  public function get_title() {
400
  return apply_filters( "wpo_wcpdf_{$this->slug}_title", $this->title, $this );
401
  }
@@ -487,6 +498,23 @@ abstract class Order_Document {
487
  $this->data[ 'number' ] = $document_number;
488
  }
489
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  /*
491
  |--------------------------------------------------------------------------
492
  | Settings getters / outputters
227
  // always load date before number, because date is used in number formatting
228
  'date' => WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_date", true ),
229
  'number' => $number,
230
+ 'notes' => WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_notes", true ),
231
  ), $order );
232
 
233
  return;
261
  WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data" );
262
  // deleting the number = deleting the document, so also delete document settings
263
  WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_settings" );
264
+ } elseif ( $key == 'notes' ) {
265
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}" );
266
  }
267
  } else {
268
  if ( $key == 'date' ) {
273
  // store both formatted number and number data
274
  WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->formatted_number );
275
  WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data", $value->to_array() );
276
+ } elseif ( $key == 'notes' ) {
277
+ // store notes
278
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value );
279
  }
280
  }
281
  }
295
  'date_formatted',
296
  'number',
297
  'number_data',
298
+ 'notes',
299
  ), $this );
300
  foreach ($data_to_remove as $data_key) {
301
  WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$data_key}" );
403
  return $this->get_data( 'date', $document_type, $order, $context );
404
  }
405
 
406
+ public function get_notes( $document_type = '', $order = null, $context = 'view' ) {
407
+ return $this->get_data( 'notes', $document_type, $order, $context );
408
+ }
409
+
410
  public function get_title() {
411
  return apply_filters( "wpo_wcpdf_{$this->slug}_title", $this->title, $this );
412
  }
498
  $this->data[ 'number' ] = $document_number;
499
  }
500
 
501
+ public function set_notes( $value, $order = null ) {
502
+ $order = empty( $order ) ? $this->order : $order;
503
+
504
+ try {
505
+ if ( empty( $value ) ) {
506
+ $this->data[ 'notes' ] = null;
507
+ return;
508
+ }
509
+
510
+ $this->data[ 'notes' ] = $value;
511
+ } catch ( \Exception $e ) {
512
+ wcpdf_log_error( $e->getMessage() );
513
+ } catch ( \Error $e ) {
514
+ wcpdf_log_error( $e->getMessage() );
515
+ }
516
+ }
517
+
518
  /*
519
  |--------------------------------------------------------------------------
520
  | Settings getters / outputters
includes/documents/class-wcpdf-sequential-number-store.php CHANGED
@@ -1,174 +1,178 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Documents;
3
-
4
- if ( ! defined( 'ABSPATH' ) ) {
5
- exit; // Exit if accessed directly
6
- }
7
-
8
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Sequential_Number_Store' ) ) :
9
-
10
- /**
11
- * Class handling database interaction for sequential numbers
12
- *
13
- * @class \WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store
14
- * @version 2.0
15
- * @category Class
16
- * @author Ewout Fernhout
17
- */
18
-
19
- class Sequential_Number_Store {
20
- /**
21
- * Name of the number store (used for table_name)
22
- * @var String
23
- */
24
- public $store_name;
25
-
26
- /**
27
- * Number store method, either 'auto_increment' or 'calculate'
28
- * @var String
29
- */
30
- public $method;
31
-
32
- /**
33
- * Name of the table that stores the number sequence (including the wp_wcpdf_ table prefix)
34
- * @var String
35
- */
36
- public $table_name;
37
-
38
- public function __construct( $store_name, $method = 'auto_increment' ) {
39
- global $wpdb;
40
- $this->store_name = $store_name;
41
- $this->method = $method;
42
- $this->table_name = apply_filters( "wpo_wcpdf_number_store_table_name", "{$wpdb->prefix}wcpdf_{$store_name}", $store_name, $method ); // i.e. wp_wcpdf_invoice_number
43
-
44
- $this->init();
45
- }
46
-
47
- public function init() {
48
- global $wpdb;
49
- // check if table exists
50
- if( $wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'") == $this->table_name) {
51
- // check calculated_number column if using 'calculate' method
52
- if ( $this->method == 'calculate' ) {
53
- $column_exists = $wpdb->get_var("SHOW COLUMNS FROM `{$this->table_name}` LIKE 'calculated_number'");
54
- if (empty($column_exists)) {
55
- $wpdb->query("ALTER TABLE {$this->table_name} ADD calculated_number int (16)");
56
- }
57
- }
58
- return; // no further business
59
- }
60
-
61
- // create table (in case of concurrent requests, this does no harm if it already exists)
62
- $charset_collate = $wpdb->get_charset_collate();
63
- // dbDelta is a sensitive kid, so we omit indentation
64
- $sql = "CREATE TABLE {$this->table_name} (
65
- id int(16) NOT NULL AUTO_INCREMENT,
66
- order_id int(16),
67
- date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
68
- calculated_number int (16),
69
- PRIMARY KEY (id)
70
- ) $charset_collate;";
71
-
72
- require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
73
- $result = dbDelta( $sql );
74
-
75
- return $result;
76
- }
77
-
78
- /**
79
- * Consume/create the next number and return it
80
- * @param integer $order_id WooCommerce Order ID
81
- * @param string $date Local date, formatted as Y-m-d H:i:s
82
- * @return int Number that was consumed/created
83
- */
84
- public function increment( $order_id = 0, $date = null ) {
85
- global $wpdb;
86
- if ( empty( $date ) ) {
87
- $date = get_date_from_gmt( date( 'Y-m-d H:i:s' ) );
88
- }
89
-
90
- do_action( 'wpo_wcpdf_before_sequential_number_increment', $this, $order_id, $date );
91
-
92
- $data = array(
93
- 'order_id' => (int) $order_id,
94
- 'date' => $date,
95
- );
96
-
97
- if ( $this->method == 'auto_increment' ) {
98
- $wpdb->insert( $this->table_name, $data );
99
- $number = $wpdb->insert_id;
100
- } elseif ( $this->method == 'calculate' ) {
101
- $number = $data['calculated_number'] = $this->get_next();
102
- $wpdb->insert( $this->table_name, $data );
103
- }
104
-
105
- // return generated number
106
- return $number;
107
- }
108
-
109
- /**
110
- * Get the number that will be used on the next increment
111
- * @return int next number
112
- */
113
- public function get_next() {
114
- global $wpdb;
115
- if ( $this->method == 'auto_increment' ) {
116
- // get next auto_increment value
117
- $table_status = $wpdb->get_row("SHOW TABLE STATUS LIKE '{$this->table_name}'");
118
- $next = $table_status->Auto_increment;
119
- } elseif ( $this->method == 'calculate' ) {
120
- $last_row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
121
- if ( empty( $last_row ) ) {
122
- $next = 1;
123
- } elseif ( !empty( $last_row->calculated_number ) ) {
124
- $next = (int) $last_row->calculated_number + 1;
125
- } else {
126
- $next = (int) $last_row->id + 1;
127
- }
128
- }
129
- return $next;
130
- }
131
-
132
- /**
133
- * Set the number that will be used on the next increment
134
- */
135
- public function set_next( $number = 1 ) {
136
- global $wpdb;
137
-
138
- // delete all rows
139
- $delete = $wpdb->query("TRUNCATE TABLE {$this->table_name}");
140
-
141
- // set auto_increment
142
- if ( $number > 1 ) {
143
- // if AUTO_INCREMENT is not 1, we need to make sure we have a 'highest value' in case of server restarts
144
- // https://serverfault.com/questions/228690/mysql-auto-increment-fields-resets-by-itself
145
- $highest_number = (int) $number - 1;
146
- $wpdb->query( $wpdb->prepare( "ALTER TABLE {$this->table_name} AUTO_INCREMENT=%d;", $highest_number ) );
147
- $data = array(
148
- 'order_id' => 0,
149
- 'date' => get_date_from_gmt( date( 'Y-m-d H:i:s' ) ),
150
- );
151
-
152
- if ( $this->method == 'calculate' ) {
153
- $data['calculated_number'] = $highest_number;
154
- }
155
-
156
- // after this insert, AUTO_INCREMENT will be equal to $number
157
- $wpdb->insert( $this->table_name, $data );
158
- } else {
159
- // simple scenario, no need to insert any rows
160
- $wpdb->query( $wpdb->prepare( "ALTER TABLE {$this->table_name} AUTO_INCREMENT=%d;", $number ) );
161
- }
162
- }
163
-
164
- public function get_last_date( $format = 'Y-m-d H:i:s' ) {
165
- global $wpdb;
166
- $row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
167
- $date = isset( $row->date ) ? $row->date : 'now';
168
- $formatted_date = date( $format, strtotime( $date ) );
169
-
170
- return $formatted_date;
171
- }
172
- }
173
-
174
- endif; // class_exists
 
 
 
 
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Documents;
3
+
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit; // Exit if accessed directly
6
+ }
7
+
8
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Sequential_Number_Store' ) ) :
9
+
10
+ /**
11
+ * Class handling database interaction for sequential numbers
12
+ *
13
+ * @class \WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store
14
+ * @version 2.0
15
+ * @category Class
16
+ * @author Ewout Fernhout
17
+ */
18
+
19
+ class Sequential_Number_Store {
20
+ /**
21
+ * Name of the number store (used for table_name)
22
+ * @var String
23
+ */
24
+ public $store_name;
25
+
26
+ /**
27
+ * Number store method, either 'auto_increment' or 'calculate'
28
+ * @var String
29
+ */
30
+ public $method;
31
+
32
+ /**
33
+ * Name of the table that stores the number sequence (including the wp_wcpdf_ table prefix)
34
+ * @var String
35
+ */
36
+ public $table_name;
37
+
38
+ public function __construct( $store_name, $method = 'auto_increment' ) {
39
+ global $wpdb;
40
+ $this->store_name = $store_name;
41
+ $this->method = $method;
42
+ $this->table_name = apply_filters( "wpo_wcpdf_number_store_table_name", "{$wpdb->prefix}wcpdf_{$store_name}", $store_name, $method ); // i.e. wp_wcpdf_invoice_number
43
+
44
+ $this->init();
45
+ }
46
+
47
+ public function init() {
48
+ global $wpdb;
49
+ // check if table exists
50
+ if( $wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'") == $this->table_name) {
51
+ // check calculated_number column if using 'calculate' method
52
+ if ( $this->method == 'calculate' ) {
53
+ $column_exists = $wpdb->get_var("SHOW COLUMNS FROM `{$this->table_name}` LIKE 'calculated_number'");
54
+ if (empty($column_exists)) {
55
+ $wpdb->query("ALTER TABLE {$this->table_name} ADD calculated_number int (16)");
56
+ }
57
+ }
58
+ return; // no further business
59
+ }
60
+
61
+ // create table (in case of concurrent requests, this does no harm if it already exists)
62
+ $charset_collate = $wpdb->get_charset_collate();
63
+ // dbDelta is a sensitive kid, so we omit indentation
64
+ $sql = "CREATE TABLE {$this->table_name} (
65
+ id int(16) NOT NULL AUTO_INCREMENT,
66
+ order_id int(16),
67
+ date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
68
+ calculated_number int (16),
69
+ PRIMARY KEY (id)
70
+ ) $charset_collate;";
71
+
72
+ require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
73
+ $result = dbDelta( $sql );
74
+
75
+ return $result;
76
+ }
77
+
78
+ /**
79
+ * Consume/create the next number and return it
80
+ * @param integer $order_id WooCommerce Order ID
81
+ * @param string $date Local date, formatted as Y-m-d H:i:s
82
+ * @return int Number that was consumed/created
83
+ */
84
+ public function increment( $order_id = 0, $date = null ) {
85
+ global $wpdb;
86
+ if ( empty( $date ) ) {
87
+ $date = get_date_from_gmt( date( 'Y-m-d H:i:s' ) );
88
+ }
89
+
90
+ do_action( 'wpo_wcpdf_before_sequential_number_increment', $this, $order_id, $date );
91
+
92
+ $data = array(
93
+ 'order_id' => (int) $order_id,
94
+ 'date' => $date,
95
+ );
96
+
97
+ if ( $this->method == 'auto_increment' ) {
98
+ $wpdb->insert( $this->table_name, $data );
99
+ $number = $wpdb->insert_id;
100
+ } elseif ( $this->method == 'calculate' ) {
101
+ $number = $data['calculated_number'] = $this->get_next();
102
+ $wpdb->insert( $this->table_name, $data );
103
+ }
104
+
105
+ // return generated number
106
+ return $number;
107
+ }
108
+
109
+ /**
110
+ * Get the number that will be used on the next increment
111
+ * @return int next number
112
+ */
113
+ public function get_next() {
114
+ global $wpdb;
115
+ if ( $this->method == 'auto_increment' ) {
116
+ // clear cache first on mysql 8.0+
117
+ if ( $wpdb->get_var( "SHOW VARIABLES LIKE 'information_schema_stats_expiry'" ) ) {
118
+ $wpdb->query( "SET SESSION information_schema_stats_expiry = 0" );
119
+ }
120
+ // get next auto_increment value
121
+ $table_status = $wpdb->get_row("SHOW TABLE STATUS LIKE '{$this->table_name}'");
122
+ $next = $table_status->Auto_increment;
123
+ } elseif ( $this->method == 'calculate' ) {
124
+ $last_row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
125
+ if ( empty( $last_row ) ) {
126
+ $next = 1;
127
+ } elseif ( !empty( $last_row->calculated_number ) ) {
128
+ $next = (int) $last_row->calculated_number + 1;
129
+ } else {
130
+ $next = (int) $last_row->id + 1;
131
+ }
132
+ }
133
+ return $next;
134
+ }
135
+
136
+ /**
137
+ * Set the number that will be used on the next increment
138
+ */
139
+ public function set_next( $number = 1 ) {
140
+ global $wpdb;
141
+
142
+ // delete all rows
143
+ $delete = $wpdb->query("TRUNCATE TABLE {$this->table_name}");
144
+
145
+ // set auto_increment
146
+ if ( $number > 1 ) {
147
+ // if AUTO_INCREMENT is not 1, we need to make sure we have a 'highest value' in case of server restarts
148
+ // https://serverfault.com/questions/228690/mysql-auto-increment-fields-resets-by-itself
149
+ $highest_number = (int) $number - 1;
150
+ $wpdb->query( $wpdb->prepare( "ALTER TABLE {$this->table_name} AUTO_INCREMENT=%d;", $highest_number ) );
151
+ $data = array(
152
+ 'order_id' => 0,
153
+ 'date' => get_date_from_gmt( date( 'Y-m-d H:i:s' ) ),
154
+ );
155
+
156
+ if ( $this->method == 'calculate' ) {
157
+ $data['calculated_number'] = $highest_number;
158
+ }
159
+
160
+ // after this insert, AUTO_INCREMENT will be equal to $number
161
+ $wpdb->insert( $this->table_name, $data );
162
+ } else {
163
+ // simple scenario, no need to insert any rows
164
+ $wpdb->query( $wpdb->prepare( "ALTER TABLE {$this->table_name} AUTO_INCREMENT=%d;", $number ) );
165
+ }
166
+ }
167
+
168
+ public function get_last_date( $format = 'Y-m-d H:i:s' ) {
169
+ global $wpdb;
170
+ $row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
171
+ $date = isset( $row->date ) ? $row->date : 'now';
172
+ $formatted_date = date( $format, strtotime( $date ) );
173
+
174
+ return $formatted_date;
175
+ }
176
+ }
177
+
178
+ endif; // class_exists
includes/views/wcpdf-extensions.php CHANGED
@@ -11,7 +11,7 @@ jQuery(document).ready(function() {
11
  </script>
12
 
13
  <div class="wcpdf-extensions-ad">
14
- <?php $no_pro = !class_exists('WooCommerce_PDF_IPS_Pro') && !class_exists('WooCommerce_PDF_IPS_Dropbox') && !class_exists('WPO_WCPDF_Templates'); ?>
15
  <img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/wpo-helper.png'; ?>" class="wpo-helper">
16
  <h3><?php _e( 'Check out these premium extensions!', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
17
  <i>(<?php _e( 'click items to read more', 'woocommerce-pdf-invoices-packing-slips' ); ?>)</i>
@@ -125,7 +125,7 @@ jQuery(document).ready(function() {
125
  </ul>
126
  <?php
127
  // link to hide message when one of the premium extensions is installed
128
- if ( class_exists('WooCommerce_PDF_IPS_Pro') || class_exists('WooCommerce_PDF_IPS_Dropbox') || class_exists('WPO_WCPDF_Templates') || class_exists('WooCommerce_PDF_IPS_Templates') || class_exists('WooCommerce_Ext_PrintOrders') || class_exists('WPO_WC_Smart_Reminder_Emails') ) {
129
  printf('<a href="%s" style="display:inline-block; margin-top: 10px;">%s</a>', add_query_arg( 'wpo_wcpdf_hide_extensions_ad', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) );
130
  }
131
  ?>
11
  </script>
12
 
13
  <div class="wcpdf-extensions-ad">
14
+ <?php $no_pro = !class_exists('WooCommerce_PDF_IPS_Pro') && !class_exists('WPO_WCPDF_Templates'); ?>
15
  <img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/wpo-helper.png'; ?>" class="wpo-helper">
16
  <h3><?php _e( 'Check out these premium extensions!', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
17
  <i>(<?php _e( 'click items to read more', 'woocommerce-pdf-invoices-packing-slips' ); ?>)</i>
125
  </ul>
126
  <?php
127
  // link to hide message when one of the premium extensions is installed
128
+ if ( class_exists('WooCommerce_PDF_IPS_Pro') || class_exists('WPO_WCPDF_Templates') || class_exists('WooCommerce_PDF_IPS_Templates') || class_exists('WooCommerce_Ext_PrintOrders') || class_exists('WPO_WC_Smart_Reminder_Emails') ) {
129
  printf('<a href="%s" style="display:inline-block; margin-top: 10px;">%s</a>', add_query_arg( 'wpo_wcpdf_hide_extensions_ad', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) );
130
  }
131
  ?>
includes/views/wcpdf-settings-page.php CHANGED
@@ -1,52 +1,52 @@
1
- <?php defined( 'ABSPATH' ) or exit; ?>
2
- <script type="text/javascript">
3
- jQuery( function( $ ) {
4
- $("#footer-thankyou").html("If you like <strong>WooCommerce PDF Invoices & Packing Slips</strong> please leave us a <a href='https://wordpress.org/support/view/plugin-reviews/woocommerce-pdf-invoices-packing-slips?rate=5#postform'>★★★★★</a> rating. A huge thank you in advance!");
5
- });
6
- </script>
7
- <div class="wrap">
8
- <div class="icon32" id="icon-options-general"><br /></div>
9
- <h2><?php _e( 'WooCommerce PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
10
- <h2 class="nav-tab-wrapper">
11
- <?php
12
- foreach ($settings_tabs as $tab_slug => $tab_title ) {
13
- $tab_link = esc_url("?page=wpo_wcpdf_options_page&tab={$tab_slug}");
14
- printf('<a href="%1$s" class="nav-tab nav-tab-%2$s %3$s">%4$s</a>', $tab_link, $tab_slug, (($active_tab == $tab_slug) ? 'nav-tab-active' : ''), $tab_title);
15
- }
16
- ?>
17
- </h2>
18
-
19
- <?php
20
- do_action( 'wpo_wcpdf_before_settings_page', $active_tab, $active_section );
21
-
22
- // save or check option to hide extensions ad
23
- if ( isset( $_GET['wpo_wcpdf_hide_extensions_ad'] ) ) {
24
- update_option( 'wpo_wcpdf_hide_extensions_ad', true );
25
- $hide_ad = true;
26
- } else {
27
- $hide_ad = get_option( 'wpo_wcpdf_hide_extensions_ad' );
28
- }
29
-
30
- if ( !$hide_ad && !( class_exists('WooCommerce_PDF_IPS_Pro') && class_exists('WooCommerce_PDF_IPS_Dropbox') && class_exists('WooCommerce_PDF_IPS_Templates') && class_exists('WooCommerce_Ext_PrintOrders') ) ) {
31
- include('wcpdf-extensions.php');
32
- }
33
-
34
- ?>
35
- <form method="post" action="options.php" id="wpo-wcpdf-settings" class="<?php echo "{$active_tab} {$active_section}"; ?>">
36
- <?php
37
- do_action( 'wpo_wcpdf_before_settings', $active_tab, $active_section );
38
- if ( has_action( 'wpo_wcpdf_settings_output_'.$active_tab ) ) {
39
- do_action( 'wpo_wcpdf_settings_output_'.$active_tab, $active_section );
40
- } else {
41
- // legacy settings
42
- settings_fields( "wpo_wcpdf_{$active_tab}_settings" );
43
- do_settings_sections( "wpo_wcpdf_{$active_tab}_settings" );
44
-
45
- submit_button();
46
- }
47
- do_action( 'wpo_wcpdf_after_settings', $active_tab, $active_section );
48
- ?>
49
-
50
- </form>
51
- <?php do_action( 'wpo_wcpdf_after_settings_page', $active_tab, $active_section ); ?>
52
- </div>
1
+ <?php defined( 'ABSPATH' ) or exit; ?>
2
+ <script type="text/javascript">
3
+ jQuery( function( $ ) {
4
+ $("#footer-thankyou").html("If you like <strong>WooCommerce PDF Invoices & Packing Slips</strong> please leave us a <a href='https://wordpress.org/support/view/plugin-reviews/woocommerce-pdf-invoices-packing-slips?rate=5#postform'>★★★★★</a> rating. A huge thank you in advance!");
5
+ });
6
+ </script>
7
+ <div class="wrap">
8
+ <div class="icon32" id="icon-options-general"><br /></div>
9
+ <h2><?php _e( 'WooCommerce PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
10
+ <h2 class="nav-tab-wrapper">
11
+ <?php
12
+ foreach ($settings_tabs as $tab_slug => $tab_title ) {
13
+ $tab_link = esc_url("?page=wpo_wcpdf_options_page&tab={$tab_slug}");
14
+ printf('<a href="%1$s" class="nav-tab nav-tab-%2$s %3$s">%4$s</a>', $tab_link, $tab_slug, (($active_tab == $tab_slug) ? 'nav-tab-active' : ''), $tab_title);
15
+ }
16
+ ?>
17
+ </h2>
18
+
19
+ <?php
20
+ do_action( 'wpo_wcpdf_before_settings_page', $active_tab, $active_section );
21
+
22
+ // save or check option to hide extensions ad
23
+ if ( isset( $_GET['wpo_wcpdf_hide_extensions_ad'] ) ) {
24
+ update_option( 'wpo_wcpdf_hide_extensions_ad', true );
25
+ $hide_ad = true;
26
+ } else {
27
+ $hide_ad = get_option( 'wpo_wcpdf_hide_extensions_ad' );
28
+ }
29
+
30
+ if ( !$hide_ad && !( class_exists('WooCommerce_PDF_IPS_Pro') && class_exists('WooCommerce_PDF_IPS_Templates') && class_exists('WooCommerce_Ext_PrintOrders') ) ) {
31
+ include('wcpdf-extensions.php');
32
+ }
33
+
34
+ ?>
35
+ <form method="post" action="options.php" id="wpo-wcpdf-settings" class="<?php echo "{$active_tab} {$active_section}"; ?>">
36
+ <?php
37
+ do_action( 'wpo_wcpdf_before_settings', $active_tab, $active_section );
38
+ if ( has_action( 'wpo_wcpdf_settings_output_'.$active_tab ) ) {
39
+ do_action( 'wpo_wcpdf_settings_output_'.$active_tab, $active_section );
40
+ } else {
41
+ // legacy settings
42
+ settings_fields( "wpo_wcpdf_{$active_tab}_settings" );
43
+ do_settings_sections( "wpo_wcpdf_{$active_tab}_settings" );
44
+
45
+ submit_button();
46
+ }
47
+ do_action( 'wpo_wcpdf_after_settings', $active_tab, $active_section );
48
+ ?>
49
+
50
+ </form>
51
+ <?php do_action( 'wpo_wcpdf_after_settings_page', $active_tab, $active_section ); ?>
52
+ </div>
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: woocommerce, pdf, invoices, packing slips, print, delivery notes, invoice,
5
  Requires at least: 3.5
6
  Tested up to: 5.5
7
  Requires PHP: 5.3
8
- Stable tag: 2.6.1
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -102,6 +102,12 @@ There's a setting on the Status tab of the settings page that allows you to togg
102
 
103
  == Changelog ==
104
 
 
 
 
 
 
 
105
  = 2.6.1 =
106
  * Fix: Load custom documents once rather than on every document request
107
  * Tweak: execute wpo_wcpdf_init_document action in invoice too
5
  Requires at least: 3.5
6
  Tested up to: 5.5
7
  Requires PHP: 5.3
8
+ Stable tag: 2.7.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
102
 
103
  == Changelog ==
104
 
105
+ = 2.7.0 =
106
+ * New: Add per-order notes to invoices (requires template update if you have a custom template)
107
+ * New: Show notice with instructions for protecting the invoice folder on NGINX setups
108
+ * Fix: Show correct "next number" on settings page for sites using MySQL 8+
109
+ * Tested up to WooCommerce 4.6
110
+
111
  = 2.6.1 =
112
  * Fix: Load custom documents once rather than on every document request
113
  * Tweak: execute wpo_wcpdf_init_document action in invoice too
templates/Simple/invoice.php CHANGED
@@ -113,6 +113,14 @@
113
  <tfoot>
114
  <tr class="no-borders">
115
  <td class="no-borders">
 
 
 
 
 
 
 
 
116
  <div class="customer-notes">
117
  <?php do_action( 'wpo_wcpdf_before_customer_notes', $this->type, $this->order ); ?>
118
  <?php if ( $this->get_shipping_notes() ) : ?>
113
  <tfoot>
114
  <tr class="no-borders">
115
  <td class="no-borders">
116
+ <div class="document-notes">
117
+ <?php do_action( 'wpo_wcpdf_before_document_notes', $this->type, $this->order ); ?>
118
+ <?php if ( $this->get_document_notes() ) : ?>
119
+ <h3><?php _e( 'Notes', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
120
+ <?php $this->document_notes(); ?>
121
+ <?php endif; ?>
122
+ <?php do_action( 'wpo_wcpdf_after_document_notes', $this->type, $this->order ); ?>
123
+ </div>
124
  <div class="customer-notes">
125
  <?php do_action( 'wpo_wcpdf_before_customer_notes', $this->type, $this->order ); ?>
126
  <?php if ( $this->get_shipping_notes() ) : ?>
templates/Simple/style.css CHANGED
@@ -221,6 +221,7 @@ dd:after {
221
  }
222
 
223
  /* Notes & Totals */
 
224
  .customer-notes {
225
  margin-top: 5mm;
226
  }
221
  }
222
 
223
  /* Notes & Totals */
224
+ .document-notes,
225
  .customer-notes {
226
  margin-top: 5mm;
227
  }
woocommerce-pdf-invoices-packingslips.php CHANGED
@@ -3,14 +3,14 @@
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.6.1
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: 4.5.0
14
  */
15
 
16
  if ( ! defined( 'ABSPATH' ) ) {
@@ -21,7 +21,7 @@ if ( !class_exists( 'WPO_WCPDF' ) ) :
21
 
22
  class WPO_WCPDF {
23
 
24
- public $version = '2.6.1';
25
  public $plugin_basename;
26
  public $legacy_mode;
27
 
@@ -52,6 +52,7 @@ class WPO_WCPDF {
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
  /**
@@ -325,6 +326,35 @@ class WPO_WCPDF {
325
  return wp_kses_post( $upgrade_notice );
326
  }
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  /**
329
  * Get the plugin url.
330
  * @return string
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.7.0
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: 4.6.0
14
  */
15
 
16
  if ( ! defined( 'ABSPATH' ) ) {
21
 
22
  class WPO_WCPDF {
23
 
24
+ public $version = '2.7.0';
25
  public $plugin_basename;
26
  public $legacy_mode;
27
 
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
+ add_action( 'admin_notices', array( $this, 'nginx_detected' ) );
56
  }
57
 
58
  /**
326
  return wp_kses_post( $upgrade_notice );
327
  }
328
 
329
+ public function nginx_detected()
330
+ {
331
+ if ( empty( $this->main ) ) {
332
+ return;
333
+ }
334
+ $tmp_path = $this->main->get_tmp_path('attachments');
335
+ $server_software = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : false;
336
+ if ( stristr( $server_software, 'nginx' ) && ( current_user_can( 'manage_shop_settings' ) || current_user_can( 'manage_woocommerce' ) ) && ! get_option('wpo_wcpdf_hide_nginx_notice') ) {
337
+ ob_start();
338
+ ?>
339
+ <div class="error">
340
+ <img src="<?php echo WPO_WCPDF()->plugin_url() . "/assets/images/nginx.svg"; ?>" style="margin-top:10px;">
341
+ <p><?php printf( __( 'The PDF files in %s are not currently protected due to your site running on <strong>NGINX</strong>.', 'woocommerce-pdf-invoices-packing-slips' ), '<strong>' . $tmp_path . '</strong>' ); ?></p>
342
+ <p><?php _e( 'To protect them, you must either use a filter to change the folder to a more secure location (outside of the site root folder) or add a Virtual Host location rule as explained in <a href="https://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/protect-the-attachments-directory-on-nginx/" target="_blank">this guide</a>.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
343
+ <p><?php _e( 'If you have already added the filter or the vhost rule, you may safely hide this message.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
344
+ <p><a href="<?php echo esc_url( add_query_arg( 'wpo_wcpdf_hide_nginx_notice', 'true' ) ); ?>"><?php _e( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ); ?></a></p>
345
+ </div>
346
+ <?php
347
+ echo ob_get_clean();
348
+ }
349
+
350
+ // save option to hide nginx notice
351
+ if ( isset( $_GET['wpo_wcpdf_hide_nginx_notice'] ) ) {
352
+ update_option( 'wpo_wcpdf_hide_nginx_notice', true );
353
+ wp_redirect( 'admin.php?page=wpo_wcpdf_options_page' );
354
+ exit;
355
+ }
356
+ }
357
+
358
  /**
359
  * Get the plugin url.
360
  * @return string