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
← All changes | includes/Controllers/ClientController.php +31 -81 2.3.72.4.1 View file →
@@ -20,11 +20,20 @@
20 20 /**
21 21 * Initialize the controller
22 22 */
23 23 public function init() {
24 - // Register AJAX handlers for client management
25 - add_action('wp_ajax_easy_invoice_delete_client', array($this, 'deleteClient'));
26 - // Other client-related AJAX handlers can be added here
24 + // The `easy_invoice_delete_client` AJAX is owned by EasyInvoiceAjax, the
25 + // same convention already used for `easy_invoice_search_clients` (see the
26 + // tombstone in QuoteController::init). The duplicate registration that used
27 + // to live here raced with EasyInvoiceAjax::deleteClient(): both handlers ran
28 + // on one request and whichever replied first ended it, so which
29 + // implementation won depended on bootstrap order.
30 + //
31 + // EasyInvoiceAjax::deleteClient() is the one to keep — it checks the
32 + // `ei_manage_clients` capability, refuses to delete administrator accounts,
33 + // and cleans up the client's invoices, quotes, and payments. The version
34 + // below (::deleteClient) did none of that. Keep this tombstone so the
35 + // registration doesn't get added back.
27 36 }
28 37
29 38 /**
30 39 * Display method implementation
@@ -81,83 +90,24 @@
81 90 'pending_invoices' => 0
82 91 ];
83 92
84 93 // Calculate total revenue from all clients' invoices
94 + // Revenue and counts from SQL over the persisted totals; this used to load
95 + // every invoice on the site as a model to render three header cards.
85 96 $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();
86 - $all_invoices = $invoice_repository->all();
97 + $paid = \EasyInvoice\Services\InvoiceTotalsCache::paidRevenue();
87 98 $revenue_by_currency = [];
88 -
89 - // First, get all currencies that exist in the system
90 - $all_currencies = [];
91 -
92 - foreach ($all_invoices as $invoice) {
93 - $currency_code = $invoice->getCurrencyCode();
94 -
95 - // If currency is empty or "global", get the actual currency that was used
96 - if (empty($currency_code) || $currency_code === 'global') {
97 - // Get the actual currency from invoice meta
98 - $actual_currency = get_post_meta($invoice->getId(), '_easy_invoice_currency_code', true);
99 - $currency_code = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD');
100 - }
101 -
102 - // If currency is still "global", use the global setting
103 - if ($currency_code === 'global') {
104 - $currency_code = get_option('easy_invoice_currency_code', 'USD');
105 - }
106 -
107 - // Normalize currency code to uppercase for consistent grouping
108 - $currency_code = strtoupper($currency_code);
109 -
110 - if (!empty($currency_code)) {
111 - $all_currencies[$currency_code] = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
112 - }
113 - }
114 -
115 - // Initialize revenue for all currencies found
116 - foreach ($all_currencies as $currency_code => $currency_symbol) {
99 + foreach ($paid['revenue'] as $currency_code => $row) {
117 100 $revenue_by_currency[$currency_code] = [
118 - 'amount' => 0,
119 - 'symbol' => $currency_symbol,
120 - 'currency_code' => $currency_code
101 + 'amount' => (float) $row['amount'],
102 + 'symbol' => $row['symbol'],
103 + 'currency_code' => $currency_code,
121 104 ];
122 105 }
106 + $stats['total_revenue'] = $revenue_by_currency;
107 + $stats['total_invoices'] = (int) $invoice_repository->count();
108 + $stats['pending_invoices'] = (int) \EasyInvoice\Services\InvoiceTotalsCache::outstanding()['count'];
123 109
124 - // Now calculate revenue for paid invoices
125 - foreach ($all_invoices as $invoice) {
126 - if ($invoice->getStatus() === 'paid') {
127 - $invoice_total = $invoice->getTotal();
128 - if (!is_numeric($invoice_total)) {
129 - continue;
130 - }
131 -
132 - // Get the actual currency from the invoice
133 - $currency_code = $invoice->getCurrencyCode();
134 -
135 - // If currency is empty or "global", get the actual currency that was used
136 - if (empty($currency_code) || $currency_code === 'global') {
137 - // Get the actual currency from invoice meta
138 - $actual_currency = get_post_meta($invoice->getId(), '_easy_invoice_currency_code', true);
139 - $currency_code = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD');
140 - }
141 -
142 - // If currency is still "global", use the global setting
143 - if ($currency_code === 'global') {
144 - $currency_code = get_option('easy_invoice_currency_code', 'USD');
145 - }
146 -
147 - // Normalize currency code to uppercase for consistent grouping
148 - $currency_code = strtoupper($currency_code);
149 -
150 - if (isset($revenue_by_currency[$currency_code])) {
151 - $revenue_by_currency[$currency_code]['amount'] += $invoice_total;
152 - }
153 - }
154 - }
155 -
156 - $stats['total_revenue'] = $revenue_by_currency;
157 - $stats['total_invoices'] = count($all_invoices);
158 - $stats['pending_invoices'] = count($invoice_repository->findByStatus('pending'));
159 -
160 110 // Prepare template data with pagination
161 111 $template_data = [
162 112 'clients' => $clients,
163 113 'stats' => $stats,
@@ -177,17 +127,17 @@
177 127 /**
178 128 * Display client edit page
179 129 */
180 130 protected function displayClientEditPage() {
181 - $check = $this->checkCapability();
131 + $check = $this->checkCapability('ei_manage_clients');
182 132 if (is_wp_error($check)) {
183 - wp_die($check->get_error_message());
133 + wp_die(esc_html($check->get_error_message()));
184 134 }
185 135
186 136 $client_id = isset($_GET['client_id']) ? intval($_GET['client_id']) : 0;
187 137
188 138 if ($client_id <= 0) {
189 - wp_die(__('Invalid client ID', 'easy-invoice'));
139 + wp_die(esc_html__('Invalid client ID', 'easy-invoice'));
190 140 }
191 141
192 142 // Get client from repository
193 143 $repository = ClientServiceProvider::getClientRepository();
@@ -193,9 +143,9 @@
193 143 $repository = ClientServiceProvider::getClientRepository();
194 144 $client = $repository->find($client_id);
195 145
196 146 if (!$client) {
197 - wp_die(__('Client not found', 'easy-invoice'));
147 + wp_die(esc_html__('Client not found', 'easy-invoice'));
198 148 }
199 149
200 150 // Display the template
201 151 $this->displayTemplate(
@@ -207,17 +157,17 @@
207 157 /**
208 158 * Display client view page
209 159 */
210 160 protected function displayClientViewPage() {
211 - $check = $this->checkCapability();
161 + $check = $this->checkCapability('ei_view_clients');
212 162 if (is_wp_error($check)) {
213 - wp_die($check->get_error_message());
163 + wp_die(esc_html($check->get_error_message()));
214 164 }
215 165
216 166 $client_id = isset($_GET['client_id']) ? intval($_GET['client_id']) : 0;
217 167
218 168 if ($client_id <= 0) {
219 - wp_die(__('Invalid client ID', 'easy-invoice'));
169 + wp_die(esc_html__('Invalid client ID', 'easy-invoice'));
220 170 }
221 171
222 172 // Get client from repository
223 173 $repository = ClientServiceProvider::getClientRepository();
@@ -223,9 +173,9 @@
223 173 $repository = ClientServiceProvider::getClientRepository();
224 174 $client = $repository->find($client_id);
225 175
226 176 if (!$client) {
227 - wp_die(__('Client not found', 'easy-invoice'));
177 + wp_die(esc_html__('Client not found', 'easy-invoice'));
228 178 }
229 179
230 180 // Get client invoices
231 181 $invoices = [];
@@ -334,9 +284,9 @@
334 284 /**
335 285 * Delete a client
336 286 */
337 287 public function deleteClient() {
338 - if (!$this->handleAjaxSecurity($_POST['nonce'])) {
288 + if (!$this->handleAjaxSecurity(($_POST['nonce'] ?? ''))) {
339 289 return;
340 290 }
341 291
342 292 // Check client ID