| @@ -241,13 +241,43 @@ | ||
| 241 | 241 | ['id' => $customerId], |
| 242 | 242 | null, |
| 243 | 243 | ['%d'] |
| 244 | 244 | ); |
| 245 | - | |
| 245 | + | |
| 246 | 246 | return $result !== false; |
| 247 | 247 | } |
| 248 | 248 | |
| 249 | 249 | /** |
| 250 | + * Link a WordPress user to a customer, ONLY when the customer has none yet. | |
| 251 | + * | |
| 252 | + * Deliberately narrow: the WHERE clause requires the current user_id to be | |
| 253 | + * NULL/0, so this can add a login link but can never reassign or overwrite an | |
| 254 | + * existing one — a customer's login is never silently switched to a different | |
| 255 | + * account. Returns true only when a row was actually linked. | |
| 256 | + */ | |
| 257 | + public function linkUserIfUnlinked(int $customerId, int $userId): bool | |
| 258 | + { | |
| 259 | + global $wpdb; | |
| 260 | + | |
| 261 | + if ($customerId <= 0 || $userId <= 0) { | |
| 262 | + return false; | |
| 263 | + } | |
| 264 | + | |
| 265 | + $table = $this->getTableName(); | |
| 266 | + | |
| 267 | + $result = $wpdb->query($wpdb->prepare( | |
| 268 | + "UPDATE `{$table}` | |
| 269 | + SET user_id = %d, updated_at = %s | |
| 270 | + WHERE id = %d AND (user_id IS NULL OR user_id = 0)", | |
| 271 | + $userId, | |
| 272 | + current_time('mysql'), | |
| 273 | + $customerId | |
| 274 | + )); | |
| 275 | + | |
| 276 | + return $result > 0; | |
| 277 | + } | |
| 278 | + | |
| 279 | + /** | |
| 250 | 280 | * Update customer from admin form |
| 251 | 281 | * |
| 252 | 282 | * This is used by the CustomerService::updateCustomer method when saving |
| 253 | 283 | * changes from the admin Edit Customer screen. |
| @@ -400,8 +430,23 @@ | ||
| 400 | 430 | return $result !== false; |
| 401 | 431 | } |
| 402 | 432 | |
| 403 | 433 | /** |
| 434 | + * Delete a customer record | |
| 435 | + * | |
| 436 | + * CustomerService calls this method when deleting customers via the REST API. | |
| 437 | + * BaseRepository already implements delete(int $id), so this is a thin wrapper | |
| 438 | + * for backward/semantic compatibility. | |
| 439 | + * | |
| 440 | + * @param int $customerId | |
| 441 | + * @return bool | |
| 442 | + */ | |
| 443 | + public function deleteCustomer(int $customerId): bool | |
| 444 | + { | |
| 445 | + return $this->delete($customerId); | |
| 446 | + } | |
| 447 | + | |
| 448 | + /** | |
| 404 | 449 | * Get gateway customer ID for a customer |
| 405 | 450 | * |
| 406 | 451 | * @param int $customerId |
| 407 | 452 | * @param string $gateway |
| @@ -585,11 +630,17 @@ | ||
| 585 | 630 | } |
| 586 | 631 | |
| 587 | 632 | $whereClause = implode(' AND ', $where); |
| 588 | 633 | |
| 589 | - // Count total | |
| 634 | + // Count total. With no status/search filters the WHERE clause is all | |
| 635 | + // literals, so there is nothing to bind — and prepare() on a | |
| 636 | + // placeholder-free query is exactly what WordPress warns about. The data | |
| 637 | + // query below always binds its LIMIT/OFFSET, so only this one needs the | |
| 638 | + // guard. | |
| 590 | 639 | $countQuery = "SELECT COUNT(*) FROM {$table} WHERE {$whereClause}"; |
| 591 | - $total = (int) $wpdb->get_var($wpdb->prepare($countQuery, $params)); | |
| 640 | + $total = (int) (empty($params) | |
| 641 | + ? $wpdb->get_var($countQuery) | |
| 642 | + : $wpdb->get_var($wpdb->prepare($countQuery, $params))); | |
| 592 | 643 | |
| 593 | 644 | // Get data |
| 594 | 645 | $orderBy = sanitize_sql_orderby($args['orderby'] ?? 'created_at') ?: 'created_at'; |
| 595 | 646 | $order = strtoupper($args['order'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC'; |