mc-cms-namelessmc/core/classes/Minecraft/MCQuery.php

459 lines
18 KiB
PHP
Raw Permalink Normal View History

2017-06-06 22:31:13 +01:00
<?php
use xPaw\MinecraftPing;
use xPaw\MinecraftQuery;
/**
* Abstraction over xPaw\MinecraftQuery & xPaw\MinecraftPing to make them fit with our needs.
*
* @package NamelessMC\Minecraft
* @author Samerton
2022-04-30 17:18:07 -07:00
* @version 2.0.0-pr13
* @license MIT
*/
class MCQuery
{
private const COLOUR_CHAR = '§';
private const COLOURS = [
'AA0000' => '4',
'FF5555' => 'c',
'FFAA00' => '6',
'FFFF55' => 'e',
'00AA00' => '2',
'55FF55' => 'a',
'55FFFF' => 'b',
'00AAAA' => '3',
'0000AA' => '1',
'5555FF' => '9',
'FF55FF' => 'd',
'AA00AA' => '5',
'FFFFFF' => 'f',
'AAAAAA' => '7',
'555555' => '8',
'000000' => '0',
];
2021-04-13 14:51:39 -07:00
/**
* Query a single server.
2021-04-13 14:51:39 -07:00
*
* @param array $ip Array ['ip' => string, 'pre' => int] - 'ip' contains ip:port, 'pre' 1 for pre-Minecraft 1.7 otherwise 0
* @param string $type Type of query to use (`internal` or `external`).
* @param bool $bedrock Whether this is a Bedrock server or not.
* @param Language $language Query language object.
* @return array Array containing query result.
2021-04-13 14:51:39 -07:00
*/
public static function singleQuery(array $ip, string $type, bool $bedrock, Language $language): array
{
2021-10-08 21:29:01 +02:00
try {
2021-11-04 08:34:53 -07:00
$query_ip = explode(':', $ip['ip']);
2021-10-08 21:29:01 +02:00
if ($type == 'internal') {
// Internal query
if (count($query_ip) == 1 || (strlen($query_ip[1]) == 2 && empty($query_ip[1]))) {
$query_ip[1] = 25565;
}
if (count($query_ip) != 2) {
2021-10-29 22:00:05 -07:00
return [
2021-10-08 21:29:01 +02:00
'error' => true,
'value' => 'split IP by : must contain exactly two components',
2021-10-29 22:00:05 -07:00
];
2021-10-08 21:29:01 +02:00
}
if (!$bedrock) {
$ping = new MinecraftPing($query_ip[0], $query_ip[1], 5);
if ($ip['pre'] == 1) {
$query = $ping->QueryOldPre17();
} else {
$query = $ping->Query();
}
2021-10-08 21:29:01 +02:00
$ping->close();
2021-10-08 21:29:01 +02:00
if (isset($query['players'])) {
$player_list = $query['players']['sample'] ?? [];
return [
'status_value' => 1,
'status' => $language->get('general', 'online'),
'player_count' => Output::getClean($query['players']['online']),
'player_count_max' => Output::getClean($query['players']['max']),
'player_list' => $player_list,
'format_player_list' => self::formatPlayerList($player_list),
Use i18n for translations (#2658) * Dependency updates * Start i18n conversion * Switch to ellipsis character * Add Dutch language json * Remove Dutch PHP language files + fix variables * Further variable fixes * WIP i18n Language class conversion * Convert Cookie Consent module to new language format * Further progress including timeago translations * Misc * Replace {x} * Replace more {x} usage * {{time}} this took way too long * replace last {x} * Remove comments So searching for {x} produces relevant results * forgot to save * move to onPageLoad * Accept both string and int * types in doc * Remove unused (broken?) function * Don't convert to string first * Last arg doesn't seem to be used anywhere * Time `{x}` replacements * Email `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * remove comments (makes searching for "{x}" nicer) * misc `{x}` replacements * JS `{x}` replacements * Cleanup language initialization * Change language constant + add pluralForm support * Misc language update things * Add script to find unused terms * Check all quote types * Remove most english translations from non-english languages * Also match with more args * Missed some apparently * Missed some (more) apparently * Misc work * Add script to convert *all* language files to json * Fix language codes * Remove a lot of unused terms * Remove a lot more unused terms * Convert everything to JSON, delete old files, get installer working with new system * Fix general configuration language dropdown + email messages * Fix page load time, fix language term `set` method * Fix discord integration command * Misc * Misc * Uncomment RTL definition * Misc * Convert links * Convert links * Convert links * Convert links * Misc work (bolding, fixes, etc) * Style fix * Fix variable * Fix #2668 * Fix old page load timers * Fix installer group sync injector error * Convert integration language terms * Fix more integration language things * Re-add upgrade script language things * Resolve PR comments * Remove unnecessary date() * Resolve some PR issues * Fix endpoint * Style * Fix Semantic -> Fomantic * Fix Semantic -> Fomantic * Return locale short code, not language id/name Only the short code is interesting to the API, return short code in user info endpoint just like in the website info endpoint. Also renamed to locale because it's language + region * Revert "warn people to not translate old language files" This reverts commit 690b77afff5ff7cc2f931d3b49feaa9a9072002b. * Fix query when there are no filters * Update 200-pr12.php * Update footer.php * insert time into string using placeholder * Fix getDisplayname case * Clean username * PR suggestions * Include newly created language in converted languages (migration) * PR suggestions * PR suggestions * Update init.php * Fix default language Co-authored-by: samerton <samerton@users.noreply.github.com> Co-authored-by: Robin <robinslot@hotmail.nl> Co-authored-by: Robin <robin+git@rkslot.nl> Co-authored-by: Partydragen <adrian_kili@outlook.com>
2022-04-23 06:27:10 -07:00
'x_players_online' => $language->get('general', 'currently_x_players_online', ['count' => Output::getClean($query['players']['online'])]),
'motd' => self::getMotd(
2022-06-16 22:28:58 -06:00
is_string($text = $query['description']) ? $text : $text['text'],
$query['description']['extra'] ?? [],
),
'version' => $query['version']['name'],
];
}
} else {
$querier = new MinecraftQuery();
$querier->ConnectBedrock($query_ip[0], $query_ip[1], 5);
$query = $querier->GetInfo();
2021-10-29 22:00:05 -07:00
return [
2021-10-08 21:29:01 +02:00
'status_value' => 1,
'status' => $language->get('general', 'online'),
'player_count' => Output::getClean($query['Players']),
'player_count_max' => Output::getClean($query['MaxPlayers']),
Use i18n for translations (#2658) * Dependency updates * Start i18n conversion * Switch to ellipsis character * Add Dutch language json * Remove Dutch PHP language files + fix variables * Further variable fixes * WIP i18n Language class conversion * Convert Cookie Consent module to new language format * Further progress including timeago translations * Misc * Replace {x} * Replace more {x} usage * {{time}} this took way too long * replace last {x} * Remove comments So searching for {x} produces relevant results * forgot to save * move to onPageLoad * Accept both string and int * types in doc * Remove unused (broken?) function * Don't convert to string first * Last arg doesn't seem to be used anywhere * Time `{x}` replacements * Email `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * remove comments (makes searching for "{x}" nicer) * misc `{x}` replacements * JS `{x}` replacements * Cleanup language initialization * Change language constant + add pluralForm support * Misc language update things * Add script to find unused terms * Check all quote types * Remove most english translations from non-english languages * Also match with more args * Missed some apparently * Missed some (more) apparently * Misc work * Add script to convert *all* language files to json * Fix language codes * Remove a lot of unused terms * Remove a lot more unused terms * Convert everything to JSON, delete old files, get installer working with new system * Fix general configuration language dropdown + email messages * Fix page load time, fix language term `set` method * Fix discord integration command * Misc * Misc * Uncomment RTL definition * Misc * Convert links * Convert links * Convert links * Convert links * Misc work (bolding, fixes, etc) * Style fix * Fix variable * Fix #2668 * Fix old page load timers * Fix installer group sync injector error * Convert integration language terms * Fix more integration language things * Re-add upgrade script language things * Resolve PR comments * Remove unnecessary date() * Resolve some PR issues * Fix endpoint * Style * Fix Semantic -> Fomantic * Fix Semantic -> Fomantic * Return locale short code, not language id/name Only the short code is interesting to the API, return short code in user info endpoint just like in the website info endpoint. Also renamed to locale because it's language + region * Revert "warn people to not translate old language files" This reverts commit 690b77afff5ff7cc2f931d3b49feaa9a9072002b. * Fix query when there are no filters * Update 200-pr12.php * Update footer.php * insert time into string using placeholder * Fix getDisplayname case * Clean username * PR suggestions * Include newly created language in converted languages (migration) * PR suggestions * PR suggestions * Update init.php * Fix default language Co-authored-by: samerton <samerton@users.noreply.github.com> Co-authored-by: Robin <robinslot@hotmail.nl> Co-authored-by: Robin <robin+git@rkslot.nl> Co-authored-by: Partydragen <adrian_kili@outlook.com>
2022-04-23 06:27:10 -07:00
'x_players_online' => $language->get('general', 'currently_x_players_online', ['count' => Output::getClean($query['Players'])]),
'motd' => $query['HostName'],
'version' => $query['Version'],
2021-10-29 22:00:05 -07:00
];
2021-10-08 21:29:01 +02:00
}
2022-01-15 15:18:05 -08:00
return [
'status_value' => 0,
'status' => $language->get('general', 'offline'),
'server_offline' => $language->get('general', 'server_offline'),
2022-01-15 15:18:05 -08:00
];
2022-02-01 20:15:35 -08:00
}
2022-02-01 20:15:35 -08:00
// External query
if (count($query_ip) > 2) {
return [
'error' => true,
'value' => 'split IP by : contains more than two components',
2022-02-01 20:15:35 -08:00
];
}
2021-10-08 21:29:01 +02:00
$query = ExternalMCQuery::query($query_ip[0], $query_ip[1] ?? ($bedrock ? 19132 : 25565), $bedrock);
2021-10-08 21:29:01 +02:00
2022-04-30 17:18:07 -07:00
if ($query !== false && !$query->error && isset($query->response)) {
2022-02-01 20:15:35 -08:00
$player_list = $query->response->players->list ?? [];
2022-01-15 15:18:05 -08:00
return [
2022-02-01 20:15:35 -08:00
'status_value' => 1,
'status' => $language->get('general', 'online'),
'player_count' => Output::getClean($query->response->players->online),
'player_count_max' => Output::getClean($query->response->players->max),
'player_list' => $player_list,
'format_player_list' => self::formatPlayerList($player_list),
Use i18n for translations (#2658) * Dependency updates * Start i18n conversion * Switch to ellipsis character * Add Dutch language json * Remove Dutch PHP language files + fix variables * Further variable fixes * WIP i18n Language class conversion * Convert Cookie Consent module to new language format * Further progress including timeago translations * Misc * Replace {x} * Replace more {x} usage * {{time}} this took way too long * replace last {x} * Remove comments So searching for {x} produces relevant results * forgot to save * move to onPageLoad * Accept both string and int * types in doc * Remove unused (broken?) function * Don't convert to string first * Last arg doesn't seem to be used anywhere * Time `{x}` replacements * Email `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * misc `{x}` replacements * remove comments (makes searching for "{x}" nicer) * misc `{x}` replacements * JS `{x}` replacements * Cleanup language initialization * Change language constant + add pluralForm support * Misc language update things * Add script to find unused terms * Check all quote types * Remove most english translations from non-english languages * Also match with more args * Missed some apparently * Missed some (more) apparently * Misc work * Add script to convert *all* language files to json * Fix language codes * Remove a lot of unused terms * Remove a lot more unused terms * Convert everything to JSON, delete old files, get installer working with new system * Fix general configuration language dropdown + email messages * Fix page load time, fix language term `set` method * Fix discord integration command * Misc * Misc * Uncomment RTL definition * Misc * Convert links * Convert links * Convert links * Convert links * Misc work (bolding, fixes, etc) * Style fix * Fix variable * Fix #2668 * Fix old page load timers * Fix installer group sync injector error * Convert integration language terms * Fix more integration language things * Re-add upgrade script language things * Resolve PR comments * Remove unnecessary date() * Resolve some PR issues * Fix endpoint * Style * Fix Semantic -> Fomantic * Fix Semantic -> Fomantic * Return locale short code, not language id/name Only the short code is interesting to the API, return short code in user info endpoint just like in the website info endpoint. Also renamed to locale because it's language + region * Revert "warn people to not translate old language files" This reverts commit 690b77afff5ff7cc2f931d3b49feaa9a9072002b. * Fix query when there are no filters * Update 200-pr12.php * Update footer.php * insert time into string using placeholder * Fix getDisplayname case * Clean username * PR suggestions * Include newly created language in converted languages (migration) * PR suggestions * PR suggestions * Update init.php * Fix default language Co-authored-by: samerton <samerton@users.noreply.github.com> Co-authored-by: Robin <robinslot@hotmail.nl> Co-authored-by: Robin <robin+git@rkslot.nl> Co-authored-by: Partydragen <adrian_kili@outlook.com>
2022-04-23 06:27:10 -07:00
'x_players_online' => $language->get('general', 'currently_x_players_online', ['count' => Output::getClean($query->response->players->online)]),
// TODO: external query does not return bedrock MOTD at all
'motd' => self::getMotd(
json_decode(json_encode($query->response->description->text), true) ?? '',
json_decode(json_encode($query->response->description->extra), true) ?? []
),
2022-01-15 15:18:05 -08:00
];
2017-06-06 22:31:13 +01:00
}
2022-02-01 20:15:35 -08:00
return [
'status_value' => 0,
'status' => $language->get('general', 'offline'),
'server_offline' => $language->get('general', 'server_offline'),
2022-02-01 20:15:35 -08:00
];
2021-10-08 21:29:01 +02:00
} catch (Exception $e) {
$error = $e->getMessage();
$query_ip = explode(':', $ip['ip']);
2022-05-28 14:17:01 +02:00
DB::getInstance()->insert('query_errors', [
'date' => date('U'),
'error' => $error,
'ip' => $query_ip[0],
'port' => $query_ip[1] ?? 25565,
2022-02-01 20:15:35 -08:00
]);
2021-10-08 21:29:01 +02:00
2021-10-29 22:00:05 -07:00
return [
2021-10-08 21:29:01 +02:00
'error' => true,
'value' => $error,
2021-10-29 22:00:05 -07:00
];
2017-06-06 22:31:13 +01:00
}
}
2017-06-08 20:48:13 +01:00
2020-11-03 20:27:17 +01:00
/**
2021-12-07 22:14:12 -08:00
* Formats a list of players into something useful for the frontend.
*
* @param array $player_list Unformatted array of players in format 'id' => string (UUID), 'name' => string (username)
2021-12-07 22:14:12 -08:00
* @return array Array of formatted players
**/
public static function formatPlayerList(array $player_list): array
{
2021-12-07 22:14:12 -08:00
$formatted = [];
2020-11-03 20:27:17 +01:00
2022-04-24 16:53:22 +02:00
$integration = Integrations::getInstance()->getIntegration('Minecraft');
2021-12-07 22:14:12 -08:00
foreach ($player_list as $player) {
$player = (array) $player;
2021-12-07 22:14:12 -08:00
2022-04-24 16:53:22 +02:00
$integration_user = new IntegrationUser($integration, str_replace('-', '', $player['id']), 'identifier');
if ($integration_user->exists()) {
$user = $integration_user->getUser();
if ($user->exists()) {
$avatar = $user->getAvatar();
$profile = $user->getProfileURL();
} else {
$avatar = AvatarSource::getAvatarFromUUID($player['id']);
$profile = '#';
}
} else {
$avatar = AvatarSource::getAvatarFromUUID($player['id']);
2021-12-07 22:14:12 -08:00
$profile = '#';
}
$formatted[] = [
'username' => Output::getClean($player['name']),
'uuid' => Output::getClean($player['id']),
'avatar' => $avatar,
'profile' => $profile,
2021-12-07 22:14:12 -08:00
];
}
return $formatted;
}
/**
* Query multiple servers.
2022-05-08 22:33:15 -06:00
*
* @param array $servers Servers
* @param string $type Type of query to use (internal or external)
* @param Language $language Query language object
* @param bool $accumulate Whether to return as one accumulated result or not
* @return array Array containing query result
2020-11-03 20:27:17 +01:00
*/
public static function multiQuery(array $servers, string $type, Language $language, bool $accumulate): array
{
2022-05-08 22:33:15 -06:00
$to_return = [];
$total_count = 0;
$status = 0;
if ($type === 'internal') {
foreach ($servers as $server) {
$query_ip = explode(':', $server['ip']);
if (count($query_ip) > 2) {
continue;
}
try {
if ($server['bedrock']) {
$ping = new MinecraftQuery();
$ping->ConnectBedrock($query_ip[0], $query_ip[1] ?? 19132, 5);
2022-05-08 22:33:15 -06:00
$query = $ping->GetInfo();
} else {
$ping = new MinecraftPing($query_ip[0], $query_ip[1] ?? 25565, 5);
2022-05-08 22:33:15 -06:00
if ($server['pre'] == 1) {
$query = $ping->QueryOldPre17();
} else {
$query = $ping->Query();
2017-06-08 20:48:13 +01:00
}
2022-05-08 22:33:15 -06:00
}
} catch (Exception $e) {
$query = [];
2022-05-28 14:31:43 +02:00
DB::getInstance()->insert('query_errors', [
2022-05-08 22:33:15 -06:00
'date' => date('U'),
'error' => $e->getMessage(),
'ip' => $query_ip[0],
'port' => ($query_ip[1] ?? ($server['bedrock'] ? 19132 : 25565)),
2022-05-08 22:33:15 -06:00
]);
}
2017-06-08 20:48:13 +01:00
2022-05-08 22:33:15 -06:00
// bedrock
if ($server['bedrock']) {
if ($accumulate === false) {
$to_return[] = [
'name' => Output::getClean($server['name']),
'status_value' => 1,
'status' => $language->get('general', 'online'),
'player_count' => Output::getClean($query['Players']),
'player_count_max' => Output::getClean($query['MaxPlayers']),
'x_players_online' => $language->get('general', 'currently_x_players_online', ['count' => Output::getClean($query['Players'])]),
];
} else {
if ($status == 0) {
$status = 1;
}
$total_count += $query['Players'];
}
} elseif (isset($query['players'])) {
2022-05-08 22:33:15 -06:00
if ($accumulate === false) {
$to_return[] = [
'name' => Output::getClean($server['name']),
'status_value' => 1,
'status' => $language->get('general', 'online'),
'player_count' => Output::getClean($query['players']['online']),
'player_count_max' => Output::getClean($query['players']['max']),
'x_players_online' => $language->get('general', 'currently_x_players_online', ['count' => Output::getClean($query['players']['online'])]),
];
} else {
if ($status == 0) {
$status = 1;
2017-06-08 20:48:13 +01:00
}
2022-05-08 22:33:15 -06:00
$total_count += $query['players']['online'];
2017-06-08 20:48:13 +01:00
}
} elseif ($accumulate === true) {
2022-05-08 22:33:15 -06:00
$to_return[] = [
'name' => Output::getClean($server['name']),
'status_value' => 0,
'status' => $language->get('general', 'offline'),
'server_offline' => $language->get('general', 'server_offline'),
2022-05-08 22:33:15 -06:00
];
2017-06-08 20:48:13 +01:00
}
2022-05-08 22:33:15 -06:00
}
2020-11-03 20:27:17 +01:00
2022-05-08 22:33:15 -06:00
if (isset($ping) && $ping instanceof MinecraftPing) {
$ping->close();
}
} else {
// External query
foreach ($servers as $server) {
$query_ip = explode(':', $server['ip']);
if (count($query_ip) > 2) {
continue;
2020-11-03 20:27:17 +01:00
}
2017-06-08 20:48:13 +01:00
$is_bedrock = isset($server['bedrock']) && $server['bedrock'] === true;
$query = ExternalMCQuery::query($query_ip[0], $query_ip[1] ?? ($is_bedrock ? 19132 : 25565), $is_bedrock);
2022-05-08 22:33:15 -06:00
if ($query !== false && !$query->error && isset($query->response)) {
if ($accumulate === false) {
$to_return[] = [
'name' => Output::getClean($server['name']),
'status_value' => 1,
'status' => $language->get('general', 'online'),
'player_count' => Output::getClean($query->response->players->online),
'player_count_max' => Output::getClean($query->response->players->max),
'x_players_online' => $language->get('general', 'currently_x_players_online', ['count' => Output::getClean($query->response->players->online)]),
];
} else {
if ($status == 0) {
$status = 1;
2020-11-03 20:27:17 +01:00
}
2022-05-08 22:33:15 -06:00
$total_count += $query->response->players->online;
2020-11-03 20:27:17 +01:00
}
} elseif ($accumulate === true) {
2022-05-08 22:33:15 -06:00
$to_return[] = [
'name' => Output::getClean($server['name']),
'status_value' => 0,
'status' => $language->get('general', 'offline'),
'server_offline' => $language->get('general', 'server_offline'),
2022-05-08 22:33:15 -06:00
];
2020-11-03 20:27:17 +01:00
}
2017-06-08 20:48:13 +01:00
}
2022-05-08 22:33:15 -06:00
}
2022-02-01 20:15:35 -08:00
2022-05-08 22:33:15 -06:00
if ($accumulate === true) {
$to_return = [
'status_value' => $status,
'status' => $status == 1
? $language->get('general', 'online')
: $language->get('general', 'offline'),
'status_full' => $status == 1
? $language->get('general', 'currently_x_players_online', ['count' => $total_count])
: $language->get('general', 'server_offline'),
'player_count' => $total_count,
];
2017-06-08 20:48:13 +01:00
}
2022-02-01 20:15:35 -08:00
2022-05-08 22:33:15 -06:00
return $to_return;
2017-06-08 20:48:13 +01:00
}
/**
* Convert a Minecraft MOTD to its legacy colour codes.
*
* @param string $text Legacy MOTD single-line text
* @param array $modern_format Array of modern MOTD format strings
* @return string MOTD as legacy MC colours
*/
private static function getMotd(string $text, array $modern_format): string
{
if ($text !== '') {
return $text;
}
// some servers (originrealms) return a weird MOTD
if (count($modern_format) === 1 && is_array($modern_format[0])) {
$modern_format = $modern_format[0]['extra'] ?? [];
}
// and sometimes it's doubly nested...
if (count($modern_format) === 1 && is_array($modern_format[0])) {
$modern_format = $modern_format[0]['extra'] ?? [];
}
$motd = '';
foreach ($modern_format as $word) {
if (!is_array($word)) {
continue;
}
$motd .= self::COLOUR_CHAR . 'r';
if (isset($word['color'])) {
$motd .= self::getColor($word['color']);
}
if (isset($word['bold']) && $word['bold'] === true) {
$motd .= self::COLOUR_CHAR . 'l';
}
$motd .= trim($word['text'], ' ');
}
return trim($motd);
}
/**
* Find the closest MC colour to a given hex colour.
*
* @param string $rgb RGB colour code
* @return string The closest Minecraft colour code to the given RGB value
*/
private static function getColor(string $rgb): string
{
if (strpos($rgb, '#') === 0) {
$rgb = substr($rgb, 1);
}
$smallestDiff = null;
$closestColor = '';
foreach (self::COLOURS as $hex => $char) {
$diff = self::colorDiff($hex, $rgb);
if ($smallestDiff === null || $diff < $smallestDiff) {
$smallestDiff = $diff;
$closestColor = $char;
}
}
return self::COLOUR_CHAR . $closestColor;
}
/**
* Find the numerical difference between two RGB colours.
*
* @param mixed $rgb1 RGB colour code
* @param mixed $rgb2 RGB colour code
* @return int The difference between two RGB colours
*/
private static function colorDiff($rgb1, $rgb2): int
{
$red1 = hexdec(substr($rgb1, 0, 2));
$green1 = hexdec(substr($rgb1, 2, 2));
$blue1 = hexdec(substr($rgb1, 4, 2));
$red2_substr = substr($rgb2, 0, 2);
$green2_substr = substr($rgb2, 2, 2);
$blue2_substr = substr($rgb2, 4, 2);
$red2 = 0;
2022-07-06 15:39:30 -07:00
if (ctype_xdigit($red2_substr)) {
$red2 = hexdec($red2_substr);
}
$green2 = 0;
2022-07-06 15:39:30 -07:00
if (ctype_xdigit($green2_substr)) {
$green2 = hexdec($green2_substr);
}
$blue2 = 0;
2022-07-06 15:39:30 -07:00
if (ctype_xdigit($blue2_substr)) {
$blue2 = hexdec($blue2_substr);
}
return abs($red1 - $red2) + abs($green1 - $green2) + abs($blue1 - $blue2);
}
2020-11-03 20:27:17 +01:00
}