PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.7
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.7
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 / Repositories / ClientRepository.php

ClientRepository.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.3.7, at includes/Repositories/ClientRepository.php

459 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Client Repository Class
4 *
5 * @package Easy_Invoice
6 * @subpackage Repositories
7 */
8
9 namespace EasyInvoice\Repositories;
10
11 use EasyInvoice\Constants\ClientFields;
12 use EasyInvoice\Interfaces\ClientRepositoryInterface;
13 use EasyInvoice\Models\Client;
14 use WP_User;
15 use WP_User_Query;
16 use WP_Query;
17
18 /**
19 * ClientRepository Class
20 *
21 * Handles data access for client objects using WordPress users.
22 */
23 class ClientRepository implements ClientRepositoryInterface {
24
25 /**
26 * Find a client by ID
27 *
28 * @param int $id The client ID
29 * @return Client|null The client model or null if not found
30 */
31 public function find($id) {
32 // Regular client (WordPress user)
33 $user = get_user_by('id', $id);
34
35 if (!$user) {
36 return null;
37 }
38
39 return $this->createClientFromUser($user);
40 }
41
42 /**
43 * Get all clients
44 *
45 * @param array $args Optional arguments to filter the results
46 * @return array Array of Client models
47 */
48 public function all($args = []) {
49 $clients = [];
50
51 // Get all users to include administrators and other users
52 $user_query = new WP_User_Query([
53 'number' => -1,
54 'orderby' => 'ID',
55 'order' => 'DESC',
56 'role__not_in' => ['Administrator'], // exclude admins
57 ]);
58
59 foreach ($user_query->get_results() as $user) {
60 $client = $this->createClientFromUser($user);
61 if ($client) {
62 $clients[] = $client;
63 }
64 }
65
66 return $clients;
67 }
68
69 /**
70 * Create a new client
71 *
72 * @param array $data The client data
73 * @return Client The created client model
74 */
75 public function create($data) {
76 // Create regular client as WordPress user
77 return $this->createRegularClient($data);
78 }
79
80 /**
81 * Create a regular client (WordPress user)
82 *
83 * @param array $data The client data
84 * @return Client|null The created client model
85 */
86 protected function createRegularClient($data) {
87 // Create a new user if email is provided
88 if (empty($data[ClientFields::EMAIL])) {
89 return null;
90 }
91
92 $user_data = [
93 'user_email' => $data[ClientFields::EMAIL],
94 'user_login' => $data[ClientFields::USERNAME],
95 'user_pass' => !empty($data[ClientFields::PASSWORD]) ? $data[ClientFields::PASSWORD] : wp_generate_password(),
96 'display_name' => $data[ClientFields::FIRST_NAME] . ' ' . $data[ClientFields::LAST_NAME],
97 'first_name' => $data[ClientFields::FIRST_NAME],
98 'last_name' => $data[ClientFields::LAST_NAME],
99 'role' => 'customer',
100 ];
101
102 $user_id = wp_insert_user($user_data);
103
104 if (is_wp_error($user_id)) {
105 return null;
106 }
107
108 // Deny backend access on new clients via a per-user capability
109 // override. The user keeps the `customer` role (so Pro Client
110 // Portal's `in_array('customer', $user->roles)` check still
111 // recognises them) but `read` is explicitly set to false at the
112 // user level, which beats the role's `read => true` when
113 // WP_User::has_cap() resolves the merged capability map. Result:
114 // `current_user_can('read')` is false, /wp-admin/ is blocked,
115 // login still works (auth itself is cap-free).
116 //
117 // Per-user override only — existing customer-role users elsewhere
118 // on the site (including pre-existing EI clients and WooCommerce
119 // customers) are NOT affected; only the user we just created.
120 $wp_user = new \WP_User($user_id);
121 $wp_user->add_cap('read', false);
122
123 // Create client model
124 $client = new Client($user_id);
125
126 // Set the client data
127 $this->setClientData($client, $data);
128
129 return $client;
130 }
131
132 /**
133 * Update an existing client
134 *
135 * @param int $id The client ID
136 * @param array $data The client data
137 * @return Client|null The updated client model or null if not found
138 */
139 public function update($id, $data) {
140 $client = $this->find($id);
141
142 if (!$client) {
143 return null;
144 }
145
146 // Regular client - update user data if email is provided
147 $user_data = ['ID' => $id];
148 if (!empty($data[ClientFields::EMAIL])) {
149 $user_data['user_email'] = $data[ClientFields::EMAIL];
150 }
151 if (!empty($data[ClientFields::USERNAME])) {
152 $user_data['user_login'] = $data[ClientFields::USERNAME];
153 }
154 if (!empty($data[ClientFields::PASSWORD])) {
155 $user_data['user_pass'] = $data[ClientFields::PASSWORD];
156 }
157 if (!empty($data[ClientFields::FIRST_NAME]) && !empty($data[ClientFields::LAST_NAME])) {
158 $user_data['display_name'] = $data[ClientFields::FIRST_NAME] . ' ' . $data[ClientFields::LAST_NAME];
159 }
160 if (!empty($data[ClientFields::FIRST_NAME])) {
161 $user_data['first_name'] = $data[ClientFields::FIRST_NAME];
162 }
163 if (!empty($data[ClientFields::LAST_NAME])) {
164 $user_data['last_name'] = $data[ClientFields::LAST_NAME];
165 }
166
167 // Only update user data if we have more than just the ID
168 if(count($user_data) > 1) {
169 $result = wp_update_user($user_data);
170 if (is_wp_error($result)) {
171 // Log the error but continue with client data update
172 }
173 }
174
175 // Set the client data
176 $this->setClientData($client, $data);
177
178 return $client;
179 }
180
181 /**
182 * Delete a client
183 *
184 * @param int $id The client ID
185 * @return bool True if successful, false otherwise
186 */
187 public function delete($id) {
188 // Check if client exists
189 $client = $this->find($id);
190
191 if (!$client) {
192 return false;
193 }
194
195 // Check if user exists and is not an administrator
196 $user = get_user_by('ID', $id);
197 if (!$user || in_array('administrator', $user->roles)) {
198 return false;
199 }
200
201 // Delete all invoices and quotes associated with this client
202 global $wpdb;
203
204 // Get all invoices and quotes for this client
205 $posts = $wpdb->get_results($wpdb->prepare(
206 "SELECT ID, post_type FROM {$wpdb->posts} WHERE post_type IN ('easy_invoice', 'easy_invoice_quote') AND ID IN (
207 SELECT post_id FROM {$wpdb->postmeta}
208 WHERE (meta_key = '_easy_invoice_client_id' OR meta_key = '_easy_invoice_quote_client_id')
209 AND meta_value = %d
210 )",
211 $id
212 ));
213
214 // Delete each post and its meta
215 foreach ($posts as $post) {
216 wp_delete_post($post->ID, true);
217 }
218
219 // Delete all payments associated with this client
220 $payments = $wpdb->get_col($wpdb->prepare(
221 "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'easy_payment' AND ID IN (
222 SELECT post_id FROM {$wpdb->postmeta}
223 WHERE meta_key = '_easy_payment_client_id' AND meta_value = %d
224 )",
225 $id
226 ));
227
228 foreach ($payments as $payment_id) {
229 wp_delete_post($payment_id, true);
230 }
231
232 // Delete the WordPress user (this will also delete all user meta)
233 $result = wp_delete_user($id);
234
235 // Return the result
236 return $result;
237
238 return $result;
239 }
240
241 /**
242 * Find clients by email
243 *
244 * @param string $email The client email
245 * @return array Array of Client models
246 */
247 public function findByEmail($email) {
248 $clients = [];
249
250 // Find regular clients by email
251 $user = get_user_by('email', $email);
252
253 if ($user) {
254 $clients[] = $this->createClientFromUser($user);
255 }
256
257 return $clients;
258 }
259
260 /**
261 * Find clients by business/client name
262 *
263 * @param string $business_client_name The business/client name
264 * @return array Array of Client models
265 */
266 public function findByBusinessClientName($business_client_name) {
267 $args = [
268 'meta_query' => [
269 [
270 'key' => ClientFields::BUSINESS_CLIENT_NAME,
271 'value' => $business_client_name,
272 'compare' => 'LIKE',
273 ],
274 ],
275 ];
276
277 return $this->all($args);
278 }
279
280 /**
281 * Search clients by name, email, company, or phone.
282 *
283 * Matches against:
284 * - User table columns: display_name, user_login, user_email,
285 * user_nicename, user_url (these are what WP_User_Query's
286 * `search` + `search_columns` can actually index).
287 * - WP standard meta: first_name, last_name, nickname.
288 * - WooCommerce billing meta: billing_first_name, billing_last_name,
289 * billing_email, billing_company, billing_phone — so customers
290 * imported via WooCommerce can be found by their billing details.
291 * - Easy Invoice's own meta: first/last/email/business name.
292 *
293 * The previous implementation gated every query on a meta_query
294 * requiring `_easy_invoice_client_business_client_name` OR
295 * `_easy_invoice_client_email` to EXIST, which silently excluded
296 * every WooCommerce customer (they don't have those EI-specific
297 * keys until they've been edited inside Easy Invoice). On stores
298 * with hundreds of imported WC customers, search returned nothing
299 * even though the unfiltered list showed every client.
300 *
301 * @param string $query The search query
302 * @return array Array of Client models
303 */
304 public function search($query) {
305 $query = trim((string) $query);
306
307 if ($query === '') {
308 return $this->all();
309 }
310
311 $clients = [];
312 $seen_ids = [];
313 $like = '*' . $query . '*';
314
315 // ── 1) User table columns ──────────────────────────────────────
316 // These are the only columns WP_User_Query's `search_columns`
317 // accepts; passing `first_name` etc. is silently ignored, so we
318 // pick those up via meta in step 2 below.
319 $user_query = new WP_User_Query([
320 'number' => 50,
321 'orderby' => 'display_name',
322 'order' => 'ASC',
323 'search' => $like,
324 'search_columns' => ['user_login', 'user_email', 'user_nicename', 'display_name', 'user_url'],
325 'role__not_in' => ['Administrator'],
326 ]);
327 foreach ($user_query->get_results() as $user) {
328 if (isset($seen_ids[$user->ID])) {
329 continue;
330 }
331 $seen_ids[$user->ID] = true;
332 $client = $this->createClientFromUser($user);
333 if ($client) {
334 $clients[] = $client;
335 }
336 }
337
338 // ── 2) User-meta columns ───────────────────────────────────────
339 // Search across every meta key we know a client's name / email /
340 // company / phone could be stored under. WP-standard, WooCommerce
341 // billing_*, and Easy Invoice's own keys are all OR'd together so
342 // the user only has to match ONE for a row to qualify.
343 $meta_keys = [
344 // WP standard
345 'first_name', 'last_name', 'nickname',
346 // WooCommerce billing — covers imported store customers
347 'billing_first_name', 'billing_last_name', 'billing_email',
348 'billing_company', 'billing_phone',
349 // Easy Invoice's own — note PHONE here too so EI-native clients
350 // (whose phone lives in _easy_invoice_client_phone, not billing_*)
351 // are searchable by phone number on equal footing with WC customers.
352 ClientFields::FIRST_NAME, ClientFields::LAST_NAME,
353 ClientFields::EMAIL, ClientFields::BUSINESS_CLIENT_NAME,
354 ClientFields::PHONE,
355 ];
356
357 $meta_query = ['relation' => 'OR'];
358 foreach ($meta_keys as $key) {
359 $meta_query[] = [
360 'key' => $key,
361 'value' => $query,
362 'compare' => 'LIKE',
363 ];
364 }
365
366 $meta_user_query = new WP_User_Query([
367 'number' => 50,
368 'orderby' => 'display_name',
369 'order' => 'ASC',
370 'role__not_in' => ['Administrator'],
371 'meta_query' => $meta_query,
372 ]);
373 foreach ($meta_user_query->get_results() as $user) {
374 if (isset($seen_ids[$user->ID])) {
375 continue;
376 }
377 $seen_ids[$user->ID] = true;
378 $client = $this->createClientFromUser($user);
379 if ($client) {
380 $clients[] = $client;
381 }
382 }
383
384 // Cap the dropdown at 50 results so the autocomplete stays
385 // responsive on stores with thousands of customers. The user
386 // can refine the query to narrow further.
387 return array_slice($clients, 0, 50);
388 }
389
390 /**
391 * Create a client model from a WordPress user
392 *
393 * @param WP_User $user The WordPress user
394 * @return Client The client model
395 */
396 protected function createClientFromUser(WP_User $user) {
397 try {
398 $client = new Client($user->ID);
399 return $client;
400 } catch (\Exception $e) {
401 return null;
402 }
403 }
404
405 /**
406 * Set the client data
407 *
408 * @param Client $client The client model
409 * @param array $data The client data
410 */
411 protected function setClientData(Client $client, array $data) {
412 if (isset($data[ClientFields::BUSINESS_CLIENT_NAME])) {
413 $client->business_client_name = $data[ClientFields::BUSINESS_CLIENT_NAME];
414 update_user_meta($client->getId(), ClientFields::BUSINESS_CLIENT_NAME, $data[ClientFields::BUSINESS_CLIENT_NAME]);
415 }
416
417 if (isset($data[ClientFields::EMAIL])) {
418 $client->email = $data[ClientFields::EMAIL];
419 update_user_meta($client->getId(), ClientFields::EMAIL, $data[ClientFields::EMAIL]);
420 }
421
422 if (isset($data[ClientFields::USERNAME])) {
423 $client->username = $data[ClientFields::USERNAME];
424 update_user_meta($client->getId(), ClientFields::USERNAME, $data[ClientFields::USERNAME]);
425 }
426
427 if (isset($data[ClientFields::ADDRESS])) {
428 $client->address = $data[ClientFields::ADDRESS];
429 update_user_meta($client->getId(), ClientFields::ADDRESS, $data[ClientFields::ADDRESS]);
430 }
431
432 if (isset($data[ClientFields::EXTRA_INFO])) {
433 $client->extra_info = $data[ClientFields::EXTRA_INFO];
434 update_user_meta($client->getId(), ClientFields::EXTRA_INFO, $data[ClientFields::EXTRA_INFO]);
435 }
436
437 if (isset($data[ClientFields::FIRST_NAME])) {
438 $client->first_name = $data[ClientFields::FIRST_NAME];
439 update_user_meta($client->getId(), ClientFields::FIRST_NAME, $data[ClientFields::FIRST_NAME]);
440 }
441
442 if (isset($data[ClientFields::LAST_NAME])) {
443 $client->last_name = $data[ClientFields::LAST_NAME];
444 update_user_meta($client->getId(), ClientFields::LAST_NAME, $data[ClientFields::LAST_NAME]);
445 }
446
447 if (isset($data[ClientFields::WEBSITE])) {
448 $client->website = $data[ClientFields::WEBSITE];
449 update_user_meta($client->getId(), ClientFields::WEBSITE, $data[ClientFields::WEBSITE]);
450 }
451 if (isset($data[ClientFields::PHONE])) {
452 $client->phone = $data[ClientFields::PHONE];
453 update_user_meta($client->getId(), ClientFields::PHONE, $data[ClientFields::PHONE]);
454 }
455
456 // Reset the dirty flag after saving
457 $client->resetDirty();
458 }
459 }