query($sql); self::ensureColumn( 'economic_barred', "ALTER TABLE `" . self::TABLE . "` ADD COLUMN `economic_barred` TINYINT(1) NULL AFTER `economic_mobile_phone`" ); self::$initialized = true; } /** * @param array $customerNumbers * @return array> */ public static function fetchContexts(array $customerNumbers): array { self::ensureTable(); $normalized = array_values(array_unique(array_filter( array_map('intval', $customerNumbers), static fn(int $value): bool => $value > 0 ))); if (empty($normalized)) { return []; } global $db; if (!is_object($db) || !method_exists($db, 'query')) { return []; } $result = $db->query( "SELECT `customer_number`, `user_id`, `local_display_name`, `local_email`, `local_phone`," . " `economic_name`, `economic_address`, `economic_city`, `economic_zip`, `economic_email`," . " `economic_cvr`, `economic_mobile_phone`, `economic_barred`" . " FROM `" . self::TABLE . "`" . " WHERE `customer_number` IN (" . implode(',', $normalized) . ")" ); if (!($result instanceof \mysqli_result)) { return []; } $contexts = []; while ($row = $result->fetch_assoc()) { $customerNumber = (int)($row['customer_number'] ?? 0); if ($customerNumber <= 0) { continue; } $barred = self::toNullableBool($row['economic_barred'] ?? null); $contexts[$customerNumber] = [ 'customer_number' => $customerNumber, 'user_id' => self::toNullableInt($row['user_id'] ?? null), 'name' => self::toNullableString($row['economic_name'] ?? null) ?? self::toNullableString($row['local_display_name'] ?? null), 'barred' => $barred, 'status' => self::barredStatus($barred), 'email' => self::toNullableString($row['economic_email'] ?? null) ?? self::toNullableString($row['local_email'] ?? null), 'phone' => self::toNullableString($row['economic_mobile_phone'] ?? null) ?? self::toNullableString($row['local_phone'] ?? null), 'cvr' => self::toNullableString($row['economic_cvr'] ?? null), 'address' => self::toNullableString($row['economic_address'] ?? null), 'city' => self::toNullableString($row['economic_city'] ?? null), 'zip' => self::toNullableString($row['economic_zip'] ?? null), ]; } return $contexts; } /** * Rebuild local e-conomic customer index from local users + cached/live e-conomic snapshots. * * @return array */ public static function refreshIndex(bool $refreshEconomicData = false): array { self::ensureTable(); global $db; if (!is_object($db) || !method_exists($db, 'query') || !property_exists($db, 'conn')) { return [ 'processed' => 0, 'upserted' => 0, 'deleted' => 0, 'errors' => 0, ]; } $stats = [ 'processed' => 0, 'upserted' => 0, 'deleted' => 0, 'errors' => 0, ]; $result = $db->query("SELECT `id`, `customer_number`, `display_name`, `email`, `phone` FROM `users` WHERE `customer_number` IS NOT NULL AND `customer_number` <> 0"); if (!($result instanceof \mysqli_result)) { return $stats; } $rows = $db->fetch_all($result); $seenCustomerNumbers = []; foreach ($rows as $row) { $stats['processed']++; $customerNumber = (int)($row['customer_number'] ?? 0); if ($customerNumber <= 0) { continue; } $seenCustomerNumbers[$customerNumber] = true; $economic = []; try { $tmpUser = new users_o(); $tmpUser->getUserByCustomerNumber($customerNumber); if ($refreshEconomicData) { $tmpUser->getCustomerEcocomicData($customerNumber); } $cached = $tmpUser->getCached('economic_customer'); if (!$cached && !$refreshEconomicData) { $tmpUser->getCustomerEcocomicData($customerNumber); $cached = $tmpUser->getCached('economic_customer'); } $economic = self::normalizeEconomicSnapshot($cached); } catch (Throwable) { $stats['errors']++; } $localDisplayName = self::toNullableString($row['display_name'] ?? null); $localEmail = self::toNullableString($row['email'] ?? null); $localPhone = self::toNullableString($row['phone'] ?? null); $economicName = self::toNullableString($economic['name'] ?? null); $economicAddress = self::toNullableString($economic['address'] ?? null); $economicCity = self::toNullableString($economic['city'] ?? null); $economicZip = self::toNullableString($economic['zip'] ?? null); $economicEmail = self::toNullableString($economic['email'] ?? null); $economicCvr = self::toNullableString($economic['corporateIdentificationNumber'] ?? null); $economicMobilePhone = self::toNullableString($economic['mobilePhone'] ?? null); $economicBarred = self::toNullableBool($economic['barred'] ?? null); $searchText = trim(implode(' ', array_values(array_filter([ $customerNumber > 0 ? (string)$customerNumber : null, $localDisplayName, $localEmail, $localPhone, $economicName, $economicAddress, $economicCity, $economicZip, $economicEmail, $economicCvr, $economicMobilePhone, ], static fn($v) => is_string($v) && trim($v) !== '')))); if ($searchText === '') { $searchText = null; } $sql = "INSERT INTO `" . self::TABLE . "` ( `customer_number`, `user_id`, `local_display_name`, `local_email`, `local_phone`, `economic_name`, `economic_address`, `economic_city`, `economic_zip`, `economic_email`, `economic_cvr`, `economic_mobile_phone`, `economic_barred`, `search_text` ) VALUES ( " . (int)$customerNumber . ", " . (int)($row['id'] ?? 0) . ", " . self::sqlNullableString($localDisplayName) . ", " . self::sqlNullableString($localEmail) . ", " . self::sqlNullableString($localPhone) . ", " . self::sqlNullableString($economicName) . ", " . self::sqlNullableString($economicAddress) . ", " . self::sqlNullableString($economicCity) . ", " . self::sqlNullableString($economicZip) . ", " . self::sqlNullableString($economicEmail) . ", " . self::sqlNullableString($economicCvr) . ", " . self::sqlNullableString($economicMobilePhone) . ", " . self::sqlNullableBool($economicBarred) . ", " . self::sqlNullableString($searchText) . " ) ON DUPLICATE KEY UPDATE `user_id` = VALUES(`user_id`), `local_display_name` = VALUES(`local_display_name`), `local_email` = VALUES(`local_email`), `local_phone` = VALUES(`local_phone`), `economic_name` = VALUES(`economic_name`), `economic_address` = VALUES(`economic_address`), `economic_city` = VALUES(`economic_city`), `economic_zip` = VALUES(`economic_zip`), `economic_email` = VALUES(`economic_email`), `economic_cvr` = VALUES(`economic_cvr`), `economic_mobile_phone` = VALUES(`economic_mobile_phone`), `economic_barred` = VALUES(`economic_barred`), `search_text` = VALUES(`search_text`), `updated_at` = CURRENT_TIMESTAMP"; $db->query($sql); $stats['upserted']++; } $seen = array_keys($seenCustomerNumbers); if (empty($seen)) { $db->query("DELETE FROM `" . self::TABLE . "`"); $stats['deleted'] = self::safeAffectedRows(); return $stats; } $in = implode(',', array_map('intval', $seen)); $db->query("DELETE FROM `" . self::TABLE . "` WHERE `customer_number` NOT IN (" . $in . ")"); $stats['deleted'] = self::safeAffectedRows(); return $stats; } /** * @return array */ private static function normalizeEconomicSnapshot(mixed $snapshot): array { if (is_object($snapshot)) { return get_object_vars($snapshot); } if (is_array($snapshot)) { return $snapshot; } return []; } private static function toNullableString(mixed $value): ?string { if ($value === null) { return null; } $string = trim((string)$value); return $string === '' ? null : $string; } private static function toNullableInt(mixed $value): ?int { if (is_int($value)) { return $value; } if (is_numeric($value) && (string)(int)$value === trim((string)$value)) { return (int)$value; } return null; } private static function toNullableBool(mixed $value): ?bool { if ($value === null || $value === '') { return null; } if (is_bool($value)) { return $value; } if (is_int($value)) { return $value !== 0; } if (is_string($value)) { $normalized = trim(mb_strtolower($value)); if ($normalized === '') { return null; } if (in_array($normalized, ['1', 'true', 'yes'], true)) { return true; } if (in_array($normalized, ['0', 'false', 'no'], true)) { return false; } } return null; } private static function sqlNullableString(?string $value): string { global $db; if ($value === null) { return 'NULL'; } return "'" . $db->escape_string($value) . "'"; } private static function sqlNullableBool(?bool $value): string { if ($value === null) { return 'NULL'; } return $value ? '1' : '0'; } private static function ensureColumn(string $column, string $alterSql): void { global $db; if (!is_object($db) || !method_exists($db, 'query')) { return; } $result = $db->query( "SHOW COLUMNS FROM `" . self::TABLE . "` LIKE '" . $db->escape_string($column) . "'" ); if ($result instanceof \mysqli_result && $result->num_rows === 0) { $db->query($alterSql); } } private static function barredStatus(?bool $barred): string { return match ($barred) { true => 'barred', false => 'active', default => 'unknown', }; } private static function safeAffectedRows(): int { global $db; if (!is_object($db) || !property_exists($db, 'conn') || !is_object($db->conn)) { return 0; } return max(0, (int)($db->conn->affected_rows ?? 0)); } }