<?
/**
* Synopsis - phone number prefixes facility. Validation, information etc.
* Information about 3 digit prefixes and 3+3 digit prefixes.
*
*
*/
class Phone_Prefix {
//////////////////////////////////////////
///
/// Static data
///
//////////////////////////////////////////
/**
* Get list of Area Codes in $state
* @param string $state - 2 chars ST
* @return int[]
* @example Phone_Prefix::stateCodes("MA")
*/
static function stateCodes(string $state) {
return @Phone_PrefixAdmin::$STATE2AREACODE[Phone_PrefixAdmin::COUNTRY_CODE_US][$state];
}
/*
static function fix202Prefixes() {
$loc = [
"region" => 51,
"city_main" => 1,
"rc" => 3342337,
"city" => 1,
"rm" => 3342337,
"geo" => [
38.9068429, -77.0058123
],
"state" => "DC",
"region_name" => "DC",
"city_name" => "Washington",
"enriched" => 16,
"county_id" => 2062,
"county" => "District of Columbia",
"county_name" => "District of Columbia"];
$list = M("geo.phone")->findA(["_id" => ['$gte' => 202000, '$lte' => 202999]]);
foreach ($list as $rec) {
M("geo.phone")->upsertSet((int)$rec["_id"], ["loc" => $loc]);
}
$list = M("geo.phone")->findA(["_id" => ['$gte' => 202, '$lte' => 202]]);
foreach ($list as $rec) {
M("geo.phone")->upsertSet((int)$rec["_id"], ["loc" => $loc]);
}
}
static function fixPrefix6Records() {
ini_set("memory_limit", -1);
$list = M("geo.phone")->findA(["_id" => ['$gt' => 200000]]);
$updated = $processed = 0;
foreach ($list as $rec) {
$update_required = false;
if ($usage = @$rec["details"]["usage"]) {
$d = [];
if ($usage == "Landline") {
$d["type"] = "landline";
} elseif ($usage == "Unknown") {
//$d["type"] = "Unknown";
} elseif ($usage == "Cell Number") {
$d["type"] = "wireless";
} else {
vd($rec);
}
if ($rec["details"]["company"]) {
$d["carrier"] = $rec["details"]["company"];
}
if ($rec["details"]["introduced"]) {
$d["introduced"] = $rec["details"]["introduced"];
}
$rec["details"] = $d;
}
if (@$rec["loc"]["city"] && !@$rec["loc"]["city_name"]) {
$new_loc = \Geo::parseLoc($rec["loc"], \Geo::LOC_ENRICH_ALL);
if ($new_loc["rc"]) {
$rec["loc"] = $new_loc;
$update_required = true;
}
}
$processed++;
if ($update_required) {
$updated++;
if (!@$rec["loc"] && !$rec["details"]) {
vd("Empty record: ", $rec);
}
echo("\n$processed/$updated");
M("geo.phone")->upsertSet((int)$rec["_id"], ["loc" => $rec["loc"], "details" => $rec["details"]]);
}
}
return $list;
}
*/
/**
* Extract all possible "timezone" field values from allareacodes.com data
* @return array
* @example Phone_Prefix::listAllTimezones()
*/
/*
static function listAllTimezones() {
$list = [];
for ($prefix = 201; $prefix<=999; $prefix++) {
if ($prefix_info = self::prefix3($prefix) ) {
@$list[$prefix_info["details"]["timezone"]]["cnt"]++;
@$list[$prefix_info["details"]["timezone"]]["prefixes"][] = $prefix;
}
}
return $list;
}
*/
//////////////////////////////////////////
///
/// Mongo collection from allareacodes.com methods
/// See: /rd/lib.framework/Geo/PhoneAdmin.php - data collection/update script
///
//////////////////////////////////////////
/**
* Get prefix-3 info
* @param string|int $phone at least 3 digits of US phone number WITHOUT leading 1.
* @return array|object|null
* @example Phone_Prefix::prefix3("781-204-4673")
* Phone_Prefix::prefix3("1 781-204-4673")
* Phone_Prefix::prefix3("781")
*/
static function prefix3($phone) {
return self::_prefix($phone, 3);
}
/**
* Get prefix-6 info
* @param string|int $phone at least 6 digits of US phone number WITHOUT leading 1.
* @return array|object|null
* @example Phone_Prefix::prefix6("781-204-4673")
* Phone_Prefix::prefix6("1 781-204-4673")
* Phone_Prefix::prefix6(781204)
*/
static function prefix6($phone) {
return self::_prefix($phone, 6);
}
static function _prefix($phone, $length=3) {
$phone = Phone::clean($phone);
$prefix = substr(self::_no1($phone), 0, $length);
$res = M("geo.phone")->findOne((int)$prefix);
if ($res) {
if (empty($res["loc"]["rc"])) {
if ($tmp = ($res["details"]["top_cities"]??'')) {
$tmp = reset($tmp);
$res["loc"] = $tmp["loc"];
} elseif ($res["loc"]["rm"]??0) {
$res["loc"]["rc"] = $res["loc"]["rm"];
list($res["loc"]["city"], $res["loc"]["region"]) = Geo::rc($res["loc"]["rc"]);
$res["loc"]["city_name"] = Geo::city_name($res["loc"]["region"], $res["loc"]["city"], false);
}
}
} else {
// No information on prefix $prefix found in mongo
$res = [];
}
/*
{
"_id" => 202),
"loc": {
"region" => 47),
"rm" => 3080192▼),
"state": "WA",
"region_name": "Washington"
},
"details": {
"timezone": "Eastern",
"is_first_86" => 1),
"nearby_prefixes": [
{
"loc": {
"region" => 20),
"city_main" => 214),
"rc" => 1310934),
"city" => 214),
"rm" => 1310934),
"geo": [
39.1731621,
-77.2716502
],
"state": "MD",
"region_name": "Maryland",
"city_name": "Germantown",
"enriched" => 16),
"county_id" => 1824),
"county": "Montgomery",
"county_name": "Montgomery County"
},
"prefixes": [
"240",
"301"
]
},
{
"loc": {
"region" => 20),
"city_main" => 286),
"rc" => 1311006),
"city" => 286),
"rm" => 1311006),
"geo": [
39.282544,
-76.6201184
],
"state": "MD",
"region_name": "Maryland",
"city_name": "Baltimore",
"enriched" => 16),
"county_id" => 3228),
"county": "Baltimore City",
"county_name": "Baltimore City County"
},
"prefixes": [
"410",
"443",
"667"
]
},
{
"loc": {
"region" => 45),
"city_main" => 829),
"rc" => 2949949),
"city" => 829),
"rm" => 2949949),
"geo": [
37.27,
-79.94
],
"state": "VA",
"region_name": "Virginia",
"city_name": "Roanoke",
"enriched" => 16),
"county_id" => 3232),
"county": "Roanoke City",
"county_name": "Roanoke City County"
},
"prefixes": [
"540"
]
},
{
"loc": {
"region" => 45),
"city_main" => 106),
"rc" => 2949226),
"city" => 106),
"rm" => 2949226),
"geo": [
38.8011056,
-77.0703853
],
"state": "VA",
"region_name": "Virginia",
"city_name": "Arlington",
"enriched" => 16),
"county_id" => 1939),
"county": "Arlington",
"county_name": "Arlington County"
},
"prefixes": [
"571",
"703"
]
}
],
"top_counties": [
{
"loc": {
"region" => 51),
"rm" => 3342336),
"county_id" => 2062),
"state": "DC",
"region_name": "DC",
"enriched" => 16),
"county": "District of Columbia",
"county_name": "District of Columbia"
},
"population": "601723",
"percent_of_prefix": "100",
"percent_of_county": "100"
}
],
"top_cities": [
{
"loc": {
"region" => 51),
"city_main" => 1),
"rc" => 3342337),
"city" => 1),
"rm" => 3342337),
"geo": [
38.9068429,
-77.0058123
],
"state": "DC",
"region_name": "DC",
"city_name": "Washington",
"enriched" => 16),
"county_id" => 2062),
"county": "District of Columbia",
"county_name": "District of Columbia"
},
"population": "601723",
"percent_of_prefix": "100",
"percent_of_city": "100"
}
],
"top_carriers": [
{
"carrier": "Verizon",
"type": "landline",
"percent_of_prefix": "49"
},
{
"carrier": "Xo Dc",
"type": "landline",
"percent_of_prefix": "2"
},
{
"carrier": "Bandwidthcom Clec",
"type": "landline",
"percent_of_prefix": "2"
},
{
"carrier": "Teleport America",
"type": "landline",
"percent_of_prefix": "2"
},
{
"carrier": "Mcimetro Access Transmission Services",
"type": "landline",
"percent_of_prefix": "2"
},
{
"carrier": "Level 3",
"type": "landline",
"percent_of_prefix": "2"
},
{
"carrier": "Paetec",
"type": "landline",
"percent_of_prefix": "1"
},
{
"carrier": "Other",
"type": "landline",
"percent_of_prefix": "8"
},
{
"carrier": "Verizon Wireless",
"type": "wireless",
"percent_of_prefix": "10"
},
{
"carrier": "Sprint",
"type": "wireless",
"percent_of_prefix": "7"
},
{
"carrier": "Cingular",
"type": "wireless",
"percent_of_prefix": "6"
},
{
"carrier": "Omnipoint Cap Operations",
"type": "wireless",
"percent_of_prefix": "3"
},
{
"carrier": "Usa Mobility Wireless",
"type": "wireless",
"percent_of_prefix": "3"
},
{
"carrier": "Verizon",
"type": "wireless",
"percent_of_prefix": "3"
},
{
"carrier": "Cricket Comm",
"type": "wireless",
"percent_of_prefix": "1"
},
{
"carrier": "Other",
"type": "wireless",
"percent_of_prefix": "1"
}
],
"assigned_percent": "84",
"landline_prefix6_cnt" => 439),
"wireless_prefix6_cnt" => 221),
"unknown_prefix6_cnt" => 3)
}
}
*/
return $res;
}
/**
* List of prefix3 records
* @param array $criteria
* "region"
* "city"
* "city_main"
* "county_id"
* @return array
* @example Phone_Prefix::prefixes3list(["region" => 19])
*/
static function prefixes3list($criteria = []) {
$q = ["_id" => ['$lt' => 1000]];
if (!empty($criteria["region"])) {
$q["loc.region"] = (int) $criteria["region"];
if (!empty($criteria["city"])) {
$q["loc.county_id"] = (int)$criteria["city"];
} elseif (!empty($criteria["city_main"])) {
$q["loc.city_main"] = (int)$criteria["city_main"];
}
}
if (!empty($criteria["county_id"])) {
$q["loc.county_id"] = (int)$criteria["county_id"];
}
return M("geo.phone")->findA($q);
}
/**
* List of prefix6 records
* @param array $criteria
* "prefix3"
* "region"
* "city"
* "city_main"
* "county_id"
* @return array
* @example Phone_Prefix::prefixes6list(["prefix3" => 781])
*/
static function prefixes6list($criteria = []) {
$q = ["_id" => ['$gt' => 1000]];
if ($prefix3 = (int)($criteria["prefix3"] ?? 0)) {
$q["_id"] = ['$gte' => $prefix3 * 1000, '$lt' => $prefix3 * 1000 + 1000];
}
if ($criteria["region"]??0) {
$q["loc.region"] = (int) $criteria["region"];
if ($criteria["county_id"]??0) {
$q["loc.county_id"] = (int)$criteria["county_id"];
}
if ($criteria["city"]??0) {
$q["loc.city"] = (int)$criteria["city"];
} elseif ($criteria["city_main"]??0) {
$q["loc.city_main"] = (int)$criteria["city_main"];
}
}
return M("geo.phone")->findA($q);
}
/**
* @param $phone
* @param bool $as_string
* @return array|bool|bool[]
* @example Phone_Prefix::location("1 (617) 123-4567")
*/
static function location($phone, $as_string = false){ # false | loc structure | location string
$p = (string)M_Type::apply($phone, "phone");
if (strlen($p) == 11 && $p[0] == "1") {
$p = substr($p, 1);
} else {
//Bad formatted or non-US phone
return false;
}
$d3 = substr($p, 0, 3);
$d6 = substr($p, 0, 6);
$loc6 = M("geo.phone")->findOne((int)$d6);
$loc3 = M("geo.phone")->findOne((int)$d3);
$loc = (array)($loc6["loc"] ?? []) + (array)($loc3["loc"] ?? []);
if ($loc) {
return $as_string?HB::loc2str($loc):($loc+["range" => true]);
}
return false;
}
//////////////////////////////////////////
///
/// Stats methods
///
//////////////////////////////////////////
/**
* Return prefix3 distribution in $city_id or $county_id
* @param int $region
* @param int $county_id
* @param int $city_id
* @return array [<area_code> => <percent>]. <area_code> is XXX, XXX / YYY ( for overlay codes ) or "Other"
*/
static function prefix3Distribution(int $region, int $county_id, int $city_id = 0) {
$distribution = [];
if ($p3list = Phone_Prefix::prefixes3list(["region" => $region])) {
foreach ($p3list as $p3 => $p3info) {
if ($county_id) {
if (!empty($p3info["details"]["top_counties"])) {
foreach ($p3info["details"]["top_counties"] as $top_county) {
if (($top_county["loc"]["county_id"] ?? null) === $county_id) {
if (!empty($p3info["details"]["overlaid_by"])) {
if (is_array($p3info["details"]["overlaid_by"])) {
$pkey = $p3 . " / " . join(" / ", $p3info["details"]["overlaid_by"]);
} else {
$pkey = $p3 . " / " . $p3info["details"]["overlaid_by"];
}
} elseif (!empty($p3info["details"]["overlay_for"])) {
$pkey = $p3info["details"]["overlay_for"] . " / " . $p3;
} else {
$pkey = $p3;
}
$distribution[$pkey] = $top_county["percent_of_county"];
}
}
}
} elseif ($city_id) {
if (!empty($p3info["details"]["top_cities"])) {
foreach ($p3info["details"]["top_cities"] as $top_city) {
if (($top_city["loc"]["city"] ?? 0) == $city_id) {
if (!empty($p3info["details"]["overlaid_by"])) {
if (is_array($p3info["details"]["overlaid_by"])) {
$pkey = $p3 . " / " . implode(" / ", $p3info["details"]["overlaid_by"]);
} else {
$pkey = $p3 . " / " . $p3info["details"]["overlaid_by"];
}
} elseif (!empty($p3info["details"]["overlay_for"])) {
$pkey = $p3info["details"]["overlay_for"] . " / " . $p3;
} else {
$pkey = $p3;
}
$distribution[$pkey] = $top_city["percent_of_city"];
}
}
}
}
}
}
$sum = 0;
$clean = [];
foreach ($distribution as $pkey => $percent) {
$pcanonical = explode(" / ", $pkey)[0];
if (empty($clean[$pcanonical])) {
$sum += $percent;
$clean[$pcanonical] = 1;
} else {
unset($distribution[$pkey]);
}
}
if ($sum < 100) {
$distribution["Other"] = (100 - $sum);
}
return $distribution;
}
//////////////////////////////////////////
///
/// KRDB_Phone based methods
///
//////////////////////////////////////////
/**
* @param type $prefix
* @return type
*/
static function info7($prefix) {
//fetch prefix 6 info
return self::info6($prefix);
}
static function info6($prefix) {
$prefix = self::_no1($prefix);
$prefix6 = substr($prefix, 0, 6);
$prefix7 = KRDB::i('phone', "1".$prefix6)->info;
if ($prefix7["zips"]??[]) {
foreach ($prefix7["zips"] as $k=>$v){
if (!(int)$v) {
unset($prefix7["zips"][$k]);
}
}
}
return $prefix7;
}
static function info3($prefix) {
$prefix = self::_no1($prefix);
$prefix3 = substr($prefix, 0, 3);
return KRDB::i('phone', "1".$prefix3)->info;
}
static function _no1($phone) {
$phone = "".$phone;
if ((strlen($phone) == 11) && ($phone[0] == "1")) {
$phone = substr($phone, 1);
}
return $phone;
}
static function cityByPrefix($prefix_info) {
$pfx_city = "";
if ($locs = $prefix_info["locations"] ?? null) {
//remove locations without phones
foreach ($locs as $lid=>$loc) {
if (empty($loc["phones_cnt"]) && empty($loc["primary"])) {
unset($locs[$lid]);
}
}
$locs = AH::rsort($locs, "primary", "phones_cnt");
foreach ($locs as $loc) {
$city_name = $loc["city_name"].cs(", %s", NVL($loc["state"] ?? null, $loc["state_name"] ?? null));
if (!empty($loc["primary"])) {
}
}
}
return $pfx_city;
}
}