PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.1
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Services / DocumentAccessNoticeService.php

DocumentAccessNoticeService.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.1, at includes/Services/DocumentAccessNoticeService.php

159 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * One-time upgrade notice for the document access-key change.
4 *
5 * @package Easy_Invoice
6 * @subpackage Services
7 */
8
9 namespace EasyInvoice\Services;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Tells site owners, once, that invoice and quote links now require an access key.
17 *
18 * Why this exists
19 * ---------------
20 * From 2.4.0 an invoice or quote is only rendered to someone holding a valid
21 * per-document access key, an administrator, or the signed-in client the document
22 * is bound to (see TemplateLoader::enforceDocumentAccess). That closes a real
23 * exposure — previously anyone with, or guessing, a URL could read a customer's
24 * name, address, line items and totals, including on draft invoices.
25 *
26 * Documents emailed before per-document keys existed carry no `?ik=` / `?qk=`.
27 * Such a link now lands on a page (TemplateLoader::renderLinkRefreshPage) that
28 * shows nothing of the document and offers to email a fresh keyed link to the
29 * address it was issued to — so the customer is not stuck and the merchant need
30 * not act. The notice tells the merchant this is what their customers will see.
31 *
32 * Only shown where it is useful:
33 * - sites that already have documents (a fresh install has no old links to break);
34 * - once, until dismissed.
35 *
36 * It deliberately hooks plain `admin_notices` rather than trying to survive
37 * EasyInvoice::disableAdminNoticesOnEasyInvoicePages(), which clears that hook on
38 * Easy Invoice's own screens. The Dashboard and Plugins screens are where someone
39 * lands after updating, and those are unaffected.
40 */
41 class DocumentAccessNoticeService {
42
43 /** Option holding the notice state: absent | 'show' | 'dismissed'. */
44 const OPTION = 'easy_invoice_doc_access_notice';
45
46 /** AJAX action used by the dismiss button. */
47 const AJAX_DISMISS = 'easy_invoice_dismiss_doc_access_notice';
48
49 /**
50 * Wire up the notice.
51 *
52 * @return void
53 */
54 public static function init() {
55 add_action( 'admin_init', [ __CLASS__, 'maybeFlagUpgrade' ] );
56 add_action( 'admin_notices', [ __CLASS__, 'render' ] );
57 add_action( 'wp_ajax_' . self::AJAX_DISMISS, [ __CLASS__, 'ajaxDismiss' ] );
58 }
59
60 /**
61 * Decide, once, whether this site needs the notice.
62 *
63 * @return void
64 */
65 public static function maybeFlagUpgrade() {
66 if ( get_option( self::OPTION, '' ) !== '' ) {
67 return;
68 }
69
70 // A site with no documents has no previously-sent links to break, so there is
71 // nothing to warn about. Stamp it dismissed so this never runs again.
72 update_option( self::OPTION, self::hasExistingDocuments() ? 'show' : 'dismissed', false );
73 }
74
75 /**
76 * Are there any invoices or quotes on this site?
77 *
78 * @return bool
79 */
80 private static function hasExistingDocuments(): bool {
81 $found = get_posts( [
82 'post_type' => [
83 \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
84 \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
85 ],
86 'post_status' => 'any',
87 'numberposts' => 1,
88 'fields' => 'ids',
89 'suppress_filters' => true,
90 'no_found_rows' => true,
91 ] );
92
93 return ! empty( $found );
94 }
95
96 /**
97 * Output the notice.
98 *
99 * @return void
100 */
101 public static function render() {
102 if ( ! current_user_can( 'manage_options' ) ) {
103 return;
104 }
105 if ( get_option( self::OPTION, '' ) !== 'show' ) {
106 return;
107 }
108
109 $nonce = wp_create_nonce( self::AJAX_DISMISS );
110 ?>
111 <div class="notice notice-warning is-dismissible" id="easy-invoice-doc-access-notice">
112 <p>
113 <strong><?php esc_html_e( 'Easy Invoice: invoice and quote links are now protected by an access key.', 'easy-invoice' ); ?></strong>
114 </p>
115 <p>
116 <?php esc_html_e( 'Invoices and quotes used to be readable by anyone who had, or guessed, the URL — including drafts. They are now shown only to you, to the signed-in client the document belongs to, or to someone opening the link that was emailed to them.', 'easy-invoice' ); ?>
117 </p>
118 <p>
119 <?php esc_html_e( 'Links you sent before this update carry no key. Anyone opening one sees no document data — just a page offering to email a fresh link to the address the document was issued to, one click, no action needed from you. Anything sent from now on is protected automatically.', 'easy-invoice' ); ?>
120 </p>
121 <?php // phpcs:ignore -- inline script keeps the notice self-contained. ?>
122 <script>
123 (function () {
124 var el = document.getElementById('easy-invoice-doc-access-notice');
125 if (!el) { return; }
126 el.addEventListener('click', function (e) {
127 if (!e.target.classList.contains('notice-dismiss')) { return; }
128 var body = new FormData();
129 body.append('action', <?php echo wp_json_encode( self::AJAX_DISMISS ); ?>);
130 body.append('nonce', <?php echo wp_json_encode( $nonce ); ?>);
131 fetch(<?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>, {
132 method: 'POST', body: body, credentials: 'same-origin'
133 });
134 });
135 })();
136 </script>
137 </div>
138 <?php
139 }
140
141 /**
142 * Persist the dismissal.
143 *
144 * @return void
145 */
146 public static function ajaxDismiss() {
147 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
148 if ( ! wp_verify_nonce( $nonce, self::AJAX_DISMISS ) ) {
149 wp_send_json_error( [ 'message' => __( 'Security check failed', 'easy-invoice' ) ] );
150 }
151 if ( ! current_user_can( 'manage_options' ) ) {
152 wp_send_json_error( [ 'message' => __( 'You do not have permission to perform this action', 'easy-invoice' ) ] );
153 }
154
155 update_option( self::OPTION, 'dismissed', false );
156 wp_send_json_success();
157 }
158 }
159