PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
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/DashboardController.php +42 -55 2.3.42.4.0 View file →
@@ -47,11 +47,11 @@
47 47 * Display dashboard page
48 48 */
49 49 protected function displayDashboardPage() {
50 50 // Check user capability
51 - $error = $this->checkCapability();
51 + $error = $this->checkCapability('ei_view_dashboard');
52 52 if (is_wp_error($error)) {
53 - wp_die($error);
53 + wp_die(esc_html($error->get_error_message()));
54 54 }
55 55
56 56 // Get data for dashboard
57 57 $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();
@@ -57,14 +57,23 @@
57 57 $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();
58 58 $client_repository = ClientServiceProvider::getClientRepository();
59 59
60 60 // Get counts
61 - $total_invoices = count($invoice_repository->all());
62 - $paid_invoices = count($invoice_repository->findByStatus('paid'));
63 - $unpaid_invoices = count($invoice_repository->findByStatus('unpaid'));
64 - $overdue_invoices = count($invoice_repository->findByStatus('overdue'));
61 + // Counts are SQL counts; only the invoices that can still carry a
62 + // balance are loaded as models. Loading every invoice (three times)
63 + // put a 3,000-invoice site at ~40 seconds per dashboard view.
64 + $total_invoices = (int) $invoice_repository->count();
65 + $paid_invoices = (int) $invoice_repository->count(['meta_key' => '_easy_invoice_status', 'meta_value' => 'paid']); // phpcs:ignore WordPress.DB.SlowDBQuery
66 + // "Unpaid" is every issued invoice still carrying a balance — a sent
67 + // invoice is 'available' (or 'partial') until paid, so counting the
68 + // literal 'unpaid' / 'overdue' statuses showed 0 on nearly every site.
69 + // Outstanding balances come from SQL over the persisted totals; loading
70 + // every open invoice as a model does not scale past a few thousand.
71 + $outstanding = \EasyInvoice\Services\InvoiceTotalsCache::outstanding();
72 + $unpaid_invoices = $outstanding['count'];
73 + $overdue_invoices = $outstanding['overdue_count'];
65 74
66 - $total_clients = count($client_repository->all());
75 + $total_clients = (int) (new \WP_User_Query(['role__not_in' => ['Administrator'], 'fields' => 'ID', 'number' => 1, 'count_total' => true]))->get_total();
67 76 $active_clients = $this->getActiveClientCount($client_repository, $invoice_repository);
68 77
69 78 // Get recent invoices
70 79 $recent_invoices = $this->getRecentInvoices($invoice_repository);
@@ -80,8 +89,10 @@
80 89 'total_invoices' => $total_invoices,
81 90 'paid_invoices' => $paid_invoices,
82 91 'unpaid_invoices' => $unpaid_invoices,
83 92 'overdue_invoices' => $overdue_invoices,
93 + 'unpaid_amount' => $outstanding['amount'],
94 + 'overdue_amount' => $outstanding['overdue_amount'],
84 95 'total_clients' => $total_clients,
85 96 'active_clients' => $active_clients,
86 97 'recent_invoices' => $recent_invoices,
87 98 'total_revenue' => $total_revenue,
@@ -97,44 +108,25 @@
97 108 * @param object $invoice_repository
98 109 * @return int Count of active clients
99 110 */
100 111 private function getActiveClientCount($client_repository, $invoice_repository) {
101 - $clients = $client_repository->all();
102 - $active_count = 0;
103 -
104 - foreach ($clients as $client) {
105 - try {
106 - $client_id = $client->getId();
107 - if (!$client_id) {
108 - continue;
109 - }
110 -
111 - $client_invoices = $invoice_repository->findByCustomer($client_id);
112 -
113 - // Consider a client active if they have an invoice in the last 90 days
114 - $has_recent_invoice = false;
115 - $ninety_days_ago = strtotime('-90 days');
116 -
117 - foreach ($client_invoices as $invoice) {
118 - $invoice_date = strtotime($invoice->getIssueDate());
119 - if ($invoice_date && $invoice_date >= $ninety_days_ago) {
120 - $has_recent_invoice = true;
121 - break;
122 - }
123 - }
124 -
125 - if ($has_recent_invoice) {
126 - $active_count++;
127 - }
128 - } catch (\Exception $e) {
129 - // Log the error and continue with the next client
130 - continue;
131 - }
132 - }
133 -
134 - return $active_count;
112 + // Distinct clients billed in the last 90 days — one query instead of
113 + // one full invoice load per client.
114 + global $wpdb;
115 + $since = wp_date('Y-m-d', strtotime('-90 days'));
116 + $count = $wpdb->get_var($wpdb->prepare(
117 + "SELECT COUNT(DISTINCT c.meta_value)
118 + FROM {$wpdb->posts} p
119 + INNER JOIN {$wpdb->postmeta} c ON c.post_id = p.ID AND c.meta_key = '_easy_invoice_client_id'
120 + INNER JOIN {$wpdb->postmeta} d ON d.post_id = p.ID AND d.meta_key = '_easy_invoice_issue_date'
121 + WHERE p.post_type = %s AND p.post_status = 'publish'
122 + AND c.meta_value <> '' AND c.meta_value <> '0' AND d.meta_value >= %s",
123 + \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
124 + $since
125 + ));
126 + return (int) $count;
135 127 }
136 -
128 +
137 129 /**
138 130 * Get recent invoices
139 131 *
140 132 * @param object $invoice_repository
@@ -141,19 +133,14 @@
141 133 * @return array Recent invoices
142 134 */
143 135 private function getRecentInvoices($invoice_repository) {
144 136 try {
145 - $invoices = $invoice_repository->all();
146 -
147 - // Sort invoices by date (newest first)
148 - usort($invoices, function($a, $b) {
149 - $date_a = $a->getIssueDate() ? strtotime($a->getIssueDate()) : 0;
150 - $date_b = $b->getIssueDate() ? strtotime($b->getIssueDate()) : 0;
151 - return $date_b - $date_a;
152 - });
153 -
154 - // Return the 5 most recent invoices
155 - return array_slice($invoices, 0, 5);
137 + // Five most recently issued: let the database sort and limit.
138 + return $invoice_repository->all([
139 + 'posts_per_page' => 5,
140 + 'meta_key' => '_easy_invoice_issue_date', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
141 + 'orderby' => ['meta_value' => 'DESC', 'ID' => 'DESC'],
142 + ]);
156 143 } catch (\Exception $e) {
157 144 // Log the error and return an empty array
158 145 return [];
159 146 }
@@ -220,9 +207,9 @@
220 207 AND CAST(m_amt.meta_value AS DECIMAL(20,4)) > 0
221 208 GROUP BY currency_code",
222 209 $global_currency
223 210 );
224 - $rows = $wpdb->get_results($sql);
211 + $rows = $wpdb->get_results($sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is built with $wpdb->prepare() above.
225 212 if ($rows === null) {
226 213 return null; // hard DB error → caller falls back to PHP path
227 214 }
228 215
@@ -461,9 +448,9 @@
461 448 $global_currency,
462 449 $start_iso,
463 450 $end_iso
464 451 );
465 - $rows = $wpdb->get_results($sql);
452 + $rows = $wpdb->get_results($sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is built with $wpdb->prepare() above.
466 453 if ($rows === null) {
467 454 return null; // hard DB error → caller falls back to PHP path
468 455 }
469 456