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/Repositories/ClientRepository.php +73 -17 2.3.22.4.1 View file →
@@ -20,8 +20,15 @@
20 20 *
21 21 * Handles data access for client objects using WordPress users.
22 22 */
23 23 class ClientRepository implements ClientRepositoryInterface {
24 + /** @var string Why the last create() returned null, in words for the form. */
25 + protected $last_error = '';
26 +
27 + public function getLastError(): string {
28 + return (string) $this->last_error;
29 + }
30 +
24 31
25 32 /**
26 33 * Find a client by ID
27 34 *
@@ -99,19 +106,46 @@
99 106 'role' => 'customer',
100 107 ];
101 108
102 109 $user_id = wp_insert_user($user_data);
103 -
110 +
104 111 if (is_wp_error($user_id)) {
112 + // Keep the reason for the caller: "that email is already registered"
113 + // is something the person at the form can act on, "failed" is not.
114 + $this->last_error = $user_id->get_error_message();
105 115 return null;
106 116 }
107 -
117 + $this->last_error = '';
118 +
119 + // Deny backend access on new clients via a per-user capability
120 + // override. The user keeps the `customer` role (so Pro Client
121 + // Portal's `in_array('customer', $user->roles)` check still
122 + // recognises them) but `read` is explicitly set to false at the
123 + // user level, which beats the role's `read => true` when
124 + // WP_User::has_cap() resolves the merged capability map. Result:
125 + // `current_user_can('read')` is false, /wp-admin/ is blocked,
126 + // login still works (auth itself is cap-free).
127 + //
128 + // Per-user override only — existing customer-role users elsewhere
129 + // on the site (including pre-existing EI clients and WooCommerce
130 + // customers) are NOT affected; only the user we just created.
131 + $wp_user = new \WP_User($user_id);
132 + $wp_user->add_cap('read', false);
133 +
108 134 // Create client model
109 135 $client = new Client($user_id);
110 -
136 +
111 137 // Set the client data
112 138 $this->setClientData($client, $data);
113 -
139 +
140 + /**
141 + * Fires once a client (and their WordPress user) has been created.
142 + *
143 + * @param Client $client The new client; its id is the user id.
144 + * @param array $data The submitted client data.
145 + */
146 + do_action('easy_invoice_client_created', $client, $data);
147 +
114 148 return $client;
115 149 }
116 150
117 151 /**
@@ -200,21 +234,28 @@
200 234 foreach ($posts as $post) {
201 235 wp_delete_post($post->ID, true);
202 236 }
203 237
204 - // Delete all payments associated with this client
205 - $payments = $wpdb->get_col($wpdb->prepare(
206 - "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'easy_payment' AND ID IN (
207 - SELECT post_id FROM {$wpdb->postmeta}
208 - WHERE meta_key = '_easy_payment_client_id' AND meta_value = %d
209 - )",
210 - $id
211 - ));
238 + // Payments are deliberately NOT deleted here.
239 + //
240 + // There used to be a query for post type 'easy_payment' on meta
241 + // '_easy_payment_client_id'. Neither exists — payments are
242 + // 'easy_invoice_payment' and record '_invoice_id', with no client id at
243 + // all — so it matched nothing on every run. Deleting a client has never
244 + // removed a payment record.
245 + //
246 + // The query is gone rather than corrected. Resolving payments properly
247 + // (through ClientLedger, which knows the real relationship) would make
248 + // this destroy records it has never touched, on a path a user reaches by
249 + // clicking Delete on a client. Widening a destructive operation as a
250 + // side effect of fixing a broken query is not a safe trade: a payment is
251 + // the evidence money changed hands, and the same reasoning that stops
252 + // InvoiceRetention deleting an issued invoice applies to it.
253 + //
254 + // What changed is that the confirmation dialog now counts payments
255 + // correctly (see EasyInvoiceAjax::handleDeleteClient), so a merchant is
256 + // told what will be left behind instead of being shown zero.
212 257
213 - foreach ($payments as $payment_id) {
214 - wp_delete_post($payment_id, true);
215 - }
216 -
217 258 // Delete the WordPress user (this will also delete all user meta)
218 259 $result = wp_delete_user($id);
219 260
220 261 // Return the result
@@ -289,9 +330,24 @@
289 330 public function search($query) {
290 331 $query = trim((string) $query);
291 332
292 333 if ($query === '') {
293 - return $this->all();
334 + // The picker opens with an empty query to show "some" clients. Build
335 + // only the 50 it can show instead of a model for every user on the site.
336 + $recent = new WP_User_Query([
337 + 'number' => 50,
338 + 'orderby' => 'display_name',
339 + 'order' => 'ASC',
340 + 'role__not_in' => ['Administrator'],
341 + ]);
342 + $clients = [];
343 + foreach ($recent->get_results() as $user) {
344 + $client = $this->createClientFromUser($user);
345 + if ($client) {
346 + $clients[] = $client;
347 + }
348 + }
349 + return $clients;
294 350 }
295 351
296 352 $clients = [];
297 353 $seen_ids = [];