$this->safeDate($query['date_from'] ?? ''), 'date_to' => $this->safeDate($query['date_to'] ?? ''), 'gubun' => $this->safeStr($query['gubun'] ?? '', 50), 'error_code' => $this->safeStr($query['error_code'] ?? '', 2), 'mem_no' => $this->safeInt($query['mem_no'] ?? null), 'email' => $this->safeStr($query['email'] ?? '', 80), 'ip4' => $this->safeIpPrefix($query['ip4'] ?? ''), 'ip4_c' => $this->safeIpPrefix($query['ip4_c'] ?? ''), // 사용자 입력 전화번호(plain) 'phone' => $this->safeStr($query['phone'] ?? '', 30), // 검색용 encrypt 결과(정확일치) 'phone_enc' => null, ]; // ✅ 기간 역전 방지 if ($filters['date_from'] && $filters['date_to']) { if (strcmp($filters['date_from'], $filters['date_to']) > 0) { [$filters['date_from'], $filters['date_to']] = [$filters['date_to'], $filters['date_from']]; } } // ✅ 전화번호 검색: encrypt(숫자) = cell_phone (정확일치) $phoneDigits = preg_replace('/\D+/', '', (string)$filters['phone']) ?: ''; if ($phoneDigits !== '' && preg_match('/^\d{10,11}$/', $phoneDigits)) { try { $seed = app(CiSeedCrypto::class); $filters['phone_enc'] = (string)$seed->encrypt($phoneDigits); } catch (\Throwable $e) { // encrypt 실패하면 검색조건 무시(리스트는 정상 출력) $filters['phone_enc'] = null; } } $page = $this->repo->paginate($filters, 30); // ✅ 리스트 표시용 가공(복호화/포맷) $seed = app(CiSeedCrypto::class); $items = []; foreach ($page->items() as $it) { $r = is_array($it) ? $it : (array)$it; [$corpLabel, $corpBadge] = $this->corpLabel((string)($r['cell_corp'] ?? 'n')); $phoneEnc = (string)($r['cell_phone'] ?? ''); $phoneDigits = $this->decryptPhoneDigits($seed, $phoneEnc); $phoneDisplay = $this->formatPhone($phoneDigits); $email = trim((string)($r['email'] ?? '')); if ($email === '' || $email === '-') $email = '-'; $memNo = (int)($r['mem_no'] ?? 0); $items[] = array_merge($r, [ 'corp_label' => $corpLabel, 'corp_badge' => $corpBadge, 'phone_plain' => $phoneDigits, 'phone_display' => ($phoneDisplay !== '' ? $phoneDisplay : ($phoneDigits !== '' ? $phoneDigits : '-')), 'email_display' => $email, 'mem_no_int' => $memNo, 'mem_link' => ($memNo > 0) ? ('/members/' . $memNo) : null, ]); } return [ 'page' => $page, 'items' => $items, 'filters' => $filters, 'corp_map' => $this->corpMapForUi(), ]; } private function safeStr(mixed $v, int $max): string { $s = trim((string)$v); if ($s === '') return ''; if (mb_strlen($s) > $max) $s = mb_substr($s, 0, $max); return $s; } private function safeInt(mixed $v): ?int { if ($v === null || $v === '') return null; if (!is_numeric($v)) return null; $n = (int)$v; return $n >= 0 ? $n : null; } private function safeDate(mixed $v): ?string { $s = trim((string)$v); if ($s === '') return null; if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $s)) return null; return $s; } private function safeIpPrefix(mixed $v): string { $s = trim((string)$v); if ($s === '') return ''; // prefix 검색 용도: 숫자/점만 허용 if (!preg_match('/^[0-9.]+$/', $s)) return ''; return $s; } private function decryptPhoneDigits(CiSeedCrypto $seed, string $enc): string { $enc = trim($enc); if ($enc === '') return ''; try { $plain = (string)$seed->decrypt($enc); $digits = preg_replace('/\D+/', '', $plain) ?: ''; return preg_match('/^\d{10,11}$/', $digits) ? $digits : ''; } catch (\Throwable $e) { return ''; } } private function formatPhone(string $digits): string { if (!preg_match('/^\d{10,11}$/', $digits)) return ''; if (strlen($digits) === 11) { return substr($digits, 0, 3) . '-' . substr($digits, 3, 4) . '-' . substr($digits, 7, 4); } return substr($digits, 0, 3) . '-' . substr($digits, 3, 3) . '-' . substr($digits, 6, 4); } private function corpLabel(string $code): array { $map = [ '01' => ['SKT', 'badge--skt'], '02' => ['KT', 'badge--kt'], '03' => ['LGU+', 'badge--lgu'], '04' => ['SKT(알뜰)', 'badge--mvno'], '05' => ['KT(알뜰)', 'badge--mvno'], '06' => ['LGU+(알뜰)', 'badge--mvno'], 'n' => ['-', 'badge--muted'], ]; return $map[$code] ?? ['-', 'badge--muted']; } private function corpMapForUi(): array { return [ '' => '전체', '01' => 'SKT', '02' => 'KT', '03' => 'LGU+', '04' => 'SKT(알뜰)', '05' => 'KT(알뜰)', '06' => 'LGU+(알뜰)', ]; } }