This commit is contained in:
Flatlogic Bot 2026-04-19 19:44:04 +00:00
parent 9063e6830c
commit 3565a88085
2 changed files with 791 additions and 10 deletions

View File

@ -69,6 +69,233 @@ function index_vanilla_description_html(?string $description): string
return nl2br(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'));
}
function index_vanilla_uex_normalize_whitespace(string $value): string
{
return trim((string) preg_replace('/\s+/u', ' ', html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8')));
}
function index_vanilla_uex_normalize_search_text(string $value): string
{
$value = function_exists('mb_strtolower')
? mb_strtolower($value, 'UTF-8')
: strtolower($value);
$value = preg_replace('/[^[:alnum:]]+/u', ' ', $value);
return trim((string) preg_replace('/\s+/u', ' ', $value));
}
function index_vanilla_uex_title_matches_query(string $title, string $queryName): bool
{
$normalizedTitle = index_vanilla_uex_normalize_search_text($title);
$normalizedQuery = index_vanilla_uex_normalize_search_text($queryName);
if ($normalizedTitle === '' || $normalizedQuery === '') {
return false;
}
if (strpos($normalizedTitle, $normalizedQuery) !== false) {
return true;
}
$queryTokens = array_values(array_filter(explode(' ', $normalizedQuery), static function (string $token): bool {
return preg_match('/\d/', $token) || strlen($token) >= 3;
}));
if ($queryTokens === []) {
return false;
}
foreach ($queryTokens as $token) {
if (strpos($normalizedTitle, $token) === false) {
return false;
}
}
return true;
}
function index_vanilla_uex_extract_price_value(string $rawPrice): ?int
{
$rawPrice = trim($rawPrice);
if ($rawPrice === '') {
return null;
}
if (!preg_match('/([0-9][0-9\s,\.]*)((?:\s*[KMB])?)\s*(?:A?UEC)\b/i', $rawPrice, $matches)) {
return null;
}
$numberPart = preg_replace('/\s+/', '', (string) ($matches[1] ?? ''));
$suffix = strtoupper(trim((string) ($matches[2] ?? '')));
if ($numberPart === '') {
return null;
}
if ($suffix !== '') {
if (substr_count($numberPart, ',') === 1 && strpos($numberPart, '.') === false) {
$numberPart = str_replace(',', '.', $numberPart);
} else {
$numberPart = str_replace(',', '', $numberPart);
}
} else {
if (strpos($numberPart, ',') !== false && strpos($numberPart, '.') !== false) {
if (strrpos($numberPart, ',') > strrpos($numberPart, '.')) {
$numberPart = str_replace('.', '', $numberPart);
$numberPart = str_replace(',', '.', $numberPart);
} else {
$numberPart = str_replace(',', '', $numberPart);
}
} else {
$numberPart = str_replace(',', '', $numberPart);
}
}
if (!is_numeric($numberPart)) {
return null;
}
$value = (float) $numberPart;
$multiplier = match ($suffix) {
'K' => 1000,
'M' => 1000000,
'B' => 1000000000,
default => 1,
};
return (int) round($value * $multiplier);
}
function index_vanilla_uex_parse_estimate_from_html(string $html, string $queryName, int $sampleLimit = 10): array
{
$values = [];
$chunks = preg_split('/<div\s+class="search-row\b[^>]*>/i', $html) ?: [];
foreach ($chunks as $chunk) {
if (count($values) >= $sampleLimit) {
break;
}
if (!preg_match('/<a\b[^>]*class="text-bold"[^>]*>(.*?)<\/a>/is', $chunk, $titleMatches)) {
continue;
}
$title = index_vanilla_uex_normalize_whitespace((string) ($titleMatches[1] ?? ''));
if ($title === '' || !preg_match('/^WTS\b/i', $title) || !index_vanilla_uex_title_matches_query($title, $queryName)) {
continue;
}
if (!preg_match('/<h4\b[^>]*>(.*?)<\/h4>/is', $chunk, $priceMatches)) {
continue;
}
$priceValue = index_vanilla_uex_extract_price_value(index_vanilla_uex_normalize_whitespace((string) ($priceMatches[1] ?? '')));
if ($priceValue === null) {
continue;
}
$values[] = $priceValue;
}
if ($values === []) {
return [
'has_estimate' => false,
'average' => null,
'formatted' => '—',
'sample_count' => 0,
];
}
$average = (int) round(array_sum($values) / count($values));
return [
'has_estimate' => true,
'average' => $average,
'formatted' => '~' . number_format($average, 0, ',', ' ') . ' UEC',
'sample_count' => count($values),
];
}
function index_vanilla_uex_fetch_estimates(array $names, int $sampleLimit = 10): array
{
$results = [];
$uniqueNames = [];
foreach ($names as $name) {
$name = trim((string) $name);
if ($name === '' || isset($uniqueNames[$name])) {
continue;
}
$uniqueNames[$name] = $name;
}
if ($uniqueNames === []) {
return $results;
}
$multiHandle = curl_multi_init();
$handles = [];
$userAgent = 'Mozilla/5.0 (compatible; FlatLogicVanillaDb/1.0; +https://uexcorp.space/)';
foreach ($uniqueNames as $name) {
$url = 'https://uexcorp.space/search?q=' . rawurlencode($name);
$handle = curl_init();
curl_setopt_array($handle, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 4,
CURLOPT_TIMEOUT => 8,
CURLOPT_USERAGENT => $userAgent,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_HTTPHEADER => [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: fr-FR,fr;q=0.9,en;q=0.8',
'Cache-Control: no-cache',
],
]);
curl_multi_add_handle($multiHandle, $handle);
$handles[$name] = $handle;
}
$running = null;
do {
$status = curl_multi_exec($multiHandle, $running);
if ($running) {
curl_multi_select($multiHandle, 1.0);
}
} while ($running && $status === CURLM_OK);
foreach ($handles as $name => $handle) {
$error = curl_error($handle);
$httpCode = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
$body = (string) curl_multi_getcontent($handle);
if ($error !== '' || $httpCode < 200 || $httpCode >= 300 || trim($body) === '') {
$results[$name] = [
'has_estimate' => false,
'average' => null,
'formatted' => 'Indisponible',
'sample_count' => 0,
'error' => true,
];
} else {
$results[$name] = index_vanilla_uex_parse_estimate_from_html($body, $name, $sampleLimit);
$results[$name]['error'] = false;
}
curl_multi_remove_handle($multiHandle, $handle);
curl_close($handle);
}
curl_multi_close($multiHandle);
return $results;
}
auth_start_session();
auth_bootstrap();
@ -78,6 +305,34 @@ $is_authenticated = $session_cl_auth_user !== '';
$has_member_access = $is_authenticated && in_array($session_cl_auth_right, ['member', 'admin'], true);
$has_vanilla_db_access = $is_authenticated && in_array($session_cl_auth_right, ['member', 'moderator', 'admin'], true);
if (isset($_GET['ajax']) && (string) $_GET['ajax'] === 'vanilla-price-estimates') {
header('Content-Type: application/json; charset=UTF-8');
if (!$has_vanilla_db_access) {
http_response_code(403);
echo json_encode([
'ok' => false,
'message' => 'Access denied.',
'estimates' => new stdClass(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$requestedNames = $_GET['names'] ?? [];
if (!is_array($requestedNames)) {
$requestedNames = [$requestedNames];
}
$requestedNames = array_slice(array_values(array_filter(array_map('strval', $requestedNames), static fn ($name): bool => trim($name) !== '')), 0, 10);
$estimates = index_vanilla_uex_fetch_estimates($requestedNames, 10);
echo json_encode([
'ok' => true,
'estimates' => $estimates,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$scan_reference_rows = [];
$scan_reference_max_occurrence = 0;
$scan_reference_error = null;
@ -97,7 +352,7 @@ $vanilla_db_current_page = 1;
$vanilla_db_result_start = 0;
$vanilla_db_result_end = 0;
$vanilla_db_base_query = ['open_modal' => 'vanilla-db'];
$vanilla_db_reset_url = 'index.php?open_modal=vanilla-db';
$vanilla_db_reset_url = 'index-en.php?open_modal=vanilla-db';
$should_open_vanilla_db_modal = false;
try {
@ -302,7 +557,7 @@ if ($has_vanilla_db_access) {
if ($vanilla_db_search !== '') {
$vanilla_db_base_query['vanilla_search'] = $vanilla_db_search;
}
$vanilla_db_reset_url = 'index.php?' . http_build_query(['open_modal' => 'vanilla-db']);
$vanilla_db_reset_url = 'index-en.php?' . http_build_query(['open_modal' => 'vanilla-db']);
try {
if (!isset($db) || !($db instanceof PDO)) {
@ -1421,6 +1676,28 @@ if ($has_vanilla_db_access) {
word-break: break-all;
}
.vanilla-db-card-estimate {
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.95em;
color: rgba(255, 224, 138, 0.92);
word-break: normal;
}
.vanilla-db-card-estimate strong {
font-weight: 600;
color: rgba(255, 236, 178, 0.98);
}
.vanilla-db-card-estimate-value.is-loading {
color: rgba(255, 255, 255, 0.62);
}
.vanilla-db-card-estimate-value.is-unavailable {
color: rgba(255, 180, 180, 0.88);
}
.vanilla-db-card-uuid {
display: inline-block;
font-size: 0.76em;
@ -1961,7 +2238,7 @@ if ($has_vanilla_db_access) {
<?php if ($vanilla_db_error !== null): ?>
<p class="vanilla-db-error"><?php echo htmlspecialchars($vanilla_db_error, ENT_QUOTES, 'UTF-8'); ?></p>
<?php else: ?>
<form class="vanilla-db-filter-bar" method="get" action="index.php">
<form class="vanilla-db-filter-bar" method="get" action="index-en.php">
<input type="hidden" name="open_modal" value="vanilla-db" />
<input type="hidden" name="vanilla_page" value="1" />
<div class="vanilla-db-search-wrap">
@ -1995,6 +2272,7 @@ if ($has_vanilla_db_access) {
<?php if ($vanilla_db_row['subtype'] !== '—'): ?><span class="vanilla-db-card-subtype"><?php echo htmlspecialchars($vanilla_db_row['subtype'], ENT_QUOTES, 'UTF-8'); ?></span><?php endif; ?>
</p>
<h4 class="item-custom-card-name vanilla-db-item-name <?php echo htmlspecialchars($vanilla_db_row['rarity_class'], ENT_QUOTES, 'UTF-8'); ?>" title="<?php echo htmlspecialchars($vanilla_db_row['rarity_label'], ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($vanilla_db_row['name'], ENT_QUOTES, 'UTF-8'); ?></h4>
<p class="vanilla-db-card-meta"><span class="vanilla-db-card-estimate" data-uex-estimate data-uex-query="<?php echo htmlspecialchars($vanilla_db_row['name'], ENT_QUOTES, 'UTF-8'); ?>"><strong>UEX estimate:</strong> <span class="vanilla-db-card-estimate-value is-loading" data-uex-estimate-value>Loading...</span></span></p>
<p class="vanilla-db-card-meta"><?php if ($vanilla_db_row['uuid'] !== '—'): ?><span class="vanilla-db-card-uuid">UUID : <?php echo htmlspecialchars($vanilla_db_row['uuid'], ENT_QUOTES, 'UTF-8'); ?></span><?php else: ?><span class="vanilla-db-muted vanilla-db-card-uuid">UUID : —</span><?php endif; ?></p>
</div>
<div class="vanilla-db-card-details">
@ -2022,14 +2300,14 @@ if ($has_vanilla_db_access) {
<div class="vanilla-db-pagination-pages">
<?php if ($vanilla_db_current_page > 1): ?>
<?php $vanilla_db_page_query = $vanilla_db_base_query; $vanilla_db_page_query['vanilla_page'] = 1; ?>
<a class="vanilla-db-page-link" href="index.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>">«</a>
<a class="vanilla-db-page-link" href="index-en.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>">«</a>
<?php $vanilla_db_page_query = $vanilla_db_base_query; $vanilla_db_page_query['vanilla_page'] = $vanilla_db_current_page - 1; ?>
<a class="vanilla-db-page-link" href="index.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"></a>
<a class="vanilla-db-page-link" href="index-en.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"></a>
<?php endif; ?>
<?php if ($vanilla_db_page_window_start > 1): ?>
<?php $vanilla_db_page_query = $vanilla_db_base_query; $vanilla_db_page_query['vanilla_page'] = 1; ?>
<a class="vanilla-db-page-link" href="index.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>">1</a>
<a class="vanilla-db-page-link" href="index-en.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>">1</a>
<?php if ($vanilla_db_page_window_start > 2): ?>
<span class="vanilla-db-page-ellipsis"></span>
<?php endif; ?>
@ -2040,7 +2318,7 @@ if ($has_vanilla_db_access) {
<span class="vanilla-db-page-current"><?php echo $vanilla_db_page_number; ?></span>
<?php else: ?>
<?php $vanilla_db_page_query = $vanilla_db_base_query; $vanilla_db_page_query['vanilla_page'] = $vanilla_db_page_number; ?>
<a class="vanilla-db-page-link" href="index.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"><?php echo $vanilla_db_page_number; ?></a>
<a class="vanilla-db-page-link" href="index-en.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"><?php echo $vanilla_db_page_number; ?></a>
<?php endif; ?>
<?php endfor; ?>
@ -2049,14 +2327,14 @@ if ($has_vanilla_db_access) {
<span class="vanilla-db-page-ellipsis"></span>
<?php endif; ?>
<?php $vanilla_db_page_query = $vanilla_db_base_query; $vanilla_db_page_query['vanilla_page'] = $vanilla_db_total_pages; ?>
<a class="vanilla-db-page-link" href="index.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"><?php echo $vanilla_db_total_pages; ?></a>
<a class="vanilla-db-page-link" href="index-en.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"><?php echo $vanilla_db_total_pages; ?></a>
<?php endif; ?>
<?php if ($vanilla_db_current_page < $vanilla_db_total_pages): ?>
<?php $vanilla_db_page_query = $vanilla_db_base_query; $vanilla_db_page_query['vanilla_page'] = $vanilla_db_current_page + 1; ?>
<a class="vanilla-db-page-link" href="index.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"></a>
<a class="vanilla-db-page-link" href="index-en.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>"></a>
<?php $vanilla_db_page_query = $vanilla_db_base_query; $vanilla_db_page_query['vanilla_page'] = $vanilla_db_total_pages; ?>
<a class="vanilla-db-page-link" href="index.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>">»</a>
<a class="vanilla-db-page-link" href="index-en.php?<?php echo htmlspecialchars(http_build_query($vanilla_db_page_query), ENT_QUOTES, 'UTF-8'); ?>">»</a>
<?php endif; ?>
</div>
</nav>
@ -2422,6 +2700,117 @@ if ($has_vanilla_db_access) {
})();
</script>
<script>
(function initVanillaDbPriceEstimates() {
const modal = document.getElementById('modal-VanillaDb');
if (!modal) {
return;
}
const estimateNodes = Array.from(modal.querySelectorAll('[data-uex-estimate]'));
if (estimateNodes.length === 0) {
return;
}
let hasLoaded = false;
let isLoading = false;
function applyEstimate(node, estimate) {
const valueNode = node.querySelector('[data-uex-estimate-value]');
if (!valueNode) {
return;
}
valueNode.classList.remove('is-loading', 'is-unavailable');
if (!estimate || !estimate.has_estimate) {
valueNode.textContent = estimate && estimate.error ? 'Unavailable' : 'No WTS listing';
valueNode.classList.add('is-unavailable');
if (estimate && estimate.sample_count) {
valueNode.title = estimate.sample_count + ' listing(s) analyzed';
}
return;
}
valueNode.textContent = estimate.formatted || '—';
valueNode.title = 'Average calculated from ' + (estimate.sample_count || 0) + ' WTS listing(s)';
}
function markGlobalFailure() {
estimateNodes.forEach(function (node) {
applyEstimate(node, { has_estimate: false, error: true, sample_count: 0 });
});
}
function loadEstimates() {
if (hasLoaded || isLoading) {
return;
}
const names = estimateNodes
.map(function (node) {
return node.dataset.uexQuery || '';
})
.filter(function (name, index, array) {
return name !== '' && array.indexOf(name) === index;
});
if (names.length === 0) {
hasLoaded = true;
return;
}
isLoading = true;
const params = new URLSearchParams();
params.set('ajax', 'vanilla-price-estimates');
names.forEach(function (name) {
params.append('names[]', name);
});
fetch('index-en.php?' + params.toString(), {
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
})
.then(function (response) {
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
return response.json();
})
.then(function (payload) {
const estimates = payload && payload.estimates ? payload.estimates : {};
estimateNodes.forEach(function (node) {
applyEstimate(node, estimates[node.dataset.uexQuery || ''] || null);
});
hasLoaded = true;
})
.catch(function () {
markGlobalFailure();
})
.finally(function () {
isLoading = false;
});
}
if (modal.classList.contains('md-show')) {
loadEstimates();
}
const observer = new MutationObserver(function () {
if (modal.classList.contains('md-show')) {
loadEstimates();
}
});
observer.observe(modal, {
attributes: true,
attributeFilter: ['class']
});
})();
</script>
<script>
(function initItemCustomSearch() {
const searchInput = document.getElementById('itemCustomSearch');

392
index.php
View File

@ -69,6 +69,234 @@ function index_vanilla_description_html(?string $description): string
return nl2br(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'));
}
function index_vanilla_uex_normalize_whitespace(string $value): string
{
return trim((string) preg_replace('/\s+/u', ' ', html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8')));
}
function index_vanilla_uex_normalize_search_text(string $value): string
{
$value = function_exists('mb_strtolower')
? mb_strtolower($value, 'UTF-8')
: strtolower($value);
$value = preg_replace('/[^[:alnum:]]+/u', ' ', $value);
return trim((string) preg_replace('/\s+/u', ' ', $value));
}
function index_vanilla_uex_title_matches_query(string $title, string $queryName): bool
{
$normalizedTitle = index_vanilla_uex_normalize_search_text($title);
$normalizedQuery = index_vanilla_uex_normalize_search_text($queryName);
if ($normalizedTitle === '' || $normalizedQuery === '') {
return false;
}
if (strpos($normalizedTitle, $normalizedQuery) !== false) {
return true;
}
$queryTokens = array_values(array_filter(explode(' ', $normalizedQuery), static function (string $token): bool {
return preg_match('/\d/', $token) || strlen($token) >= 3;
}));
if ($queryTokens === []) {
return false;
}
foreach ($queryTokens as $token) {
if (strpos($normalizedTitle, $token) === false) {
return false;
}
}
return true;
}
function index_vanilla_uex_extract_price_value(string $rawPrice): ?int
{
$rawPrice = trim($rawPrice);
if ($rawPrice === '') {
return null;
}
if (!preg_match('/([0-9][0-9\s,\.]*)((?:\s*[KMB])?)\s*(?:A?UEC)\b/i', $rawPrice, $matches)) {
return null;
}
$numberPart = preg_replace('/\s+/', '', (string) ($matches[1] ?? ''));
$suffix = strtoupper(trim((string) ($matches[2] ?? '')));
if ($numberPart === '') {
return null;
}
if ($suffix !== '') {
if (substr_count($numberPart, ',') === 1 && strpos($numberPart, '.') === false) {
$numberPart = str_replace(',', '.', $numberPart);
} else {
$numberPart = str_replace(',', '', $numberPart);
}
} else {
if (strpos($numberPart, ',') !== false && strpos($numberPart, '.') !== false) {
if (strrpos($numberPart, ',') > strrpos($numberPart, '.')) {
$numberPart = str_replace('.', '', $numberPart);
$numberPart = str_replace(',', '.', $numberPart);
} else {
$numberPart = str_replace(',', '', $numberPart);
}
} else {
$numberPart = str_replace(',', '', $numberPart);
}
}
if (!is_numeric($numberPart)) {
return null;
}
$value = (float) $numberPart;
$multiplier = match ($suffix) {
'K' => 1000,
'M' => 1000000,
'B' => 1000000000,
default => 1,
};
return (int) round($value * $multiplier);
}
function index_vanilla_uex_parse_estimate_from_html(string $html, string $queryName, int $sampleLimit = 10): array
{
$values = [];
$chunks = preg_split('/<div\s+class="search-row\b[^>]*>/i', $html) ?: [];
foreach ($chunks as $chunk) {
if (count($values) >= $sampleLimit) {
break;
}
if (!preg_match('/<a\b[^>]*class="text-bold"[^>]*>(.*?)<\/a>/is', $chunk, $titleMatches)) {
continue;
}
$title = index_vanilla_uex_normalize_whitespace((string) ($titleMatches[1] ?? ''));
if ($title === '' || !preg_match('/^WTS\b/i', $title) || !index_vanilla_uex_title_matches_query($title, $queryName)) {
continue;
}
if (!preg_match('/<h4\b[^>]*>(.*?)<\/h4>/is', $chunk, $priceMatches)) {
continue;
}
$priceValue = index_vanilla_uex_extract_price_value(index_vanilla_uex_normalize_whitespace((string) ($priceMatches[1] ?? '')));
if ($priceValue === null) {
continue;
}
$values[] = $priceValue;
}
if ($values === []) {
return [
'has_estimate' => false,
'average' => null,
'formatted' => '—',
'sample_count' => 0,
];
}
$average = (int) round(array_sum($values) / count($values));
return [
'has_estimate' => true,
'average' => $average,
'formatted' => '~' . number_format($average, 0, ',', ' ') . ' UEC',
'sample_count' => count($values),
];
}
function index_vanilla_uex_fetch_estimates(array $names, int $sampleLimit = 10): array
{
$results = [];
$uniqueNames = [];
foreach ($names as $name) {
$name = trim((string) $name);
if ($name === '' || isset($uniqueNames[$name])) {
continue;
}
$uniqueNames[$name] = $name;
}
if ($uniqueNames === []) {
return $results;
}
$multiHandle = curl_multi_init();
$handles = [];
$userAgent = 'Mozilla/5.0 (compatible; FlatLogicVanillaDb/1.0; +https://uexcorp.space/)';
foreach ($uniqueNames as $name) {
$url = 'https://uexcorp.space/search?q=' . rawurlencode($name);
$handle = curl_init();
curl_setopt_array($handle, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 4,
CURLOPT_TIMEOUT => 8,
CURLOPT_USERAGENT => $userAgent,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_HTTPHEADER => [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: fr-FR,fr;q=0.9,en;q=0.8',
'Cache-Control: no-cache',
],
]);
curl_multi_add_handle($multiHandle, $handle);
$handles[$name] = $handle;
}
$running = null;
do {
$status = curl_multi_exec($multiHandle, $running);
if ($running) {
curl_multi_select($multiHandle, 1.0);
}
} while ($running && $status === CURLM_OK);
foreach ($handles as $name => $handle) {
$error = curl_error($handle);
$httpCode = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
$body = (string) curl_multi_getcontent($handle);
if ($error !== '' || $httpCode < 200 || $httpCode >= 300 || trim($body) === '') {
$results[$name] = [
'has_estimate' => false,
'average' => null,
'formatted' => 'Indisponible',
'sample_count' => 0,
'error' => true,
];
} else {
$results[$name] = index_vanilla_uex_parse_estimate_from_html($body, $name, $sampleLimit);
$results[$name]['error'] = false;
}
curl_multi_remove_handle($multiHandle, $handle);
curl_close($handle);
}
curl_multi_close($multiHandle);
return $results;
}
auth_start_session();
auth_bootstrap();
@ -78,6 +306,34 @@ $is_authenticated = $session_cl_auth_user !== '';
$has_member_access = $is_authenticated && in_array($session_cl_auth_right, ['member', 'admin'], true);
$has_vanilla_db_access = $is_authenticated && in_array($session_cl_auth_right, ['member', 'moderator', 'admin'], true);
if (isset($_GET['ajax']) && (string) $_GET['ajax'] === 'vanilla-price-estimates') {
header('Content-Type: application/json; charset=UTF-8');
if (!$has_vanilla_db_access) {
http_response_code(403);
echo json_encode([
'ok' => false,
'message' => 'Accès refusé.',
'estimates' => new stdClass(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$requestedNames = $_GET['names'] ?? [];
if (!is_array($requestedNames)) {
$requestedNames = [$requestedNames];
}
$requestedNames = array_slice(array_values(array_filter(array_map('strval', $requestedNames), static fn ($name): bool => trim($name) !== '')), 0, 10);
$estimates = index_vanilla_uex_fetch_estimates($requestedNames, 10);
echo json_encode([
'ok' => true,
'estimates' => $estimates,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$scan_reference_rows = [];
$scan_reference_max_occurrence = 0;
$scan_reference_error = null;
@ -1427,6 +1683,30 @@ if ($has_vanilla_db_access) {
color: rgba(255, 255, 255, 0.58);
letter-spacing: 0.02em;
}
.vanilla-db-card-estimate {
display: inline-flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
font-size: 0.88em;
color: #ffe6a6;
}
.vanilla-db-card-estimate strong {
font-size: 0.92em;
letter-spacing: 0.04em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.7);
}
.vanilla-db-card-estimate-value.is-loading {
color: rgba(255, 255, 255, 0.62);
}
.vanilla-db-card-estimate-value.is-unavailable {
color: rgba(255, 255, 255, 0.52);
}
.vanilla-db-card-details {
display: flex;
align-items: flex-start;
@ -1995,6 +2275,7 @@ if ($has_vanilla_db_access) {
<?php if ($vanilla_db_row['subtype'] !== '—'): ?><span class="vanilla-db-card-subtype"><?php echo htmlspecialchars($vanilla_db_row['subtype'], ENT_QUOTES, 'UTF-8'); ?></span><?php endif; ?>
</p>
<h4 class="item-custom-card-name vanilla-db-item-name <?php echo htmlspecialchars($vanilla_db_row['rarity_class'], ENT_QUOTES, 'UTF-8'); ?>" title="<?php echo htmlspecialchars($vanilla_db_row['rarity_label'], ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($vanilla_db_row['name'], ENT_QUOTES, 'UTF-8'); ?></h4>
<p class="vanilla-db-card-meta"><span class="vanilla-db-card-estimate" data-uex-estimate data-uex-query="<?php echo htmlspecialchars($vanilla_db_row['name'], ENT_QUOTES, 'UTF-8'); ?>"><strong>Estimation UEX :</strong> <span class="vanilla-db-card-estimate-value is-loading" data-uex-estimate-value>Chargement...</span></span></p>
<p class="vanilla-db-card-meta"><?php if ($vanilla_db_row['uuid'] !== '—'): ?><span class="vanilla-db-card-uuid">UUID : <?php echo htmlspecialchars($vanilla_db_row['uuid'], ENT_QUOTES, 'UTF-8'); ?></span><?php else: ?><span class="vanilla-db-muted vanilla-db-card-uuid">UUID : —</span><?php endif; ?></p>
</div>
<div class="vanilla-db-card-details">
@ -2422,6 +2703,117 @@ if ($has_vanilla_db_access) {
})();
</script>
<script>
(function initVanillaDbPriceEstimates() {
const modal = document.getElementById('modal-VanillaDb');
if (!modal) {
return;
}
const estimateNodes = Array.from(modal.querySelectorAll('[data-uex-estimate]'));
if (estimateNodes.length === 0) {
return;
}
let hasLoaded = false;
let isLoading = false;
function applyEstimate(node, estimate) {
const valueNode = node.querySelector('[data-uex-estimate-value]');
if (!valueNode) {
return;
}
valueNode.classList.remove('is-loading', 'is-unavailable');
if (!estimate || !estimate.has_estimate) {
valueNode.textContent = estimate && estimate.error ? 'Indisponible' : 'Aucune annonce WTS';
valueNode.classList.add('is-unavailable');
if (estimate && estimate.sample_count) {
valueNode.title = estimate.sample_count + ' annonce(s) analysée(s)';
}
return;
}
valueNode.textContent = estimate.formatted || '—';
valueNode.title = 'Moyenne calculée sur ' + (estimate.sample_count || 0) + ' annonce(s) WTS';
}
function markGlobalFailure() {
estimateNodes.forEach(function (node) {
applyEstimate(node, { has_estimate: false, error: true, sample_count: 0 });
});
}
function loadEstimates() {
if (hasLoaded || isLoading) {
return;
}
const names = estimateNodes
.map(function (node) {
return node.dataset.uexQuery || '';
})
.filter(function (name, index, array) {
return name !== '' && array.indexOf(name) === index;
});
if (names.length === 0) {
hasLoaded = true;
return;
}
isLoading = true;
const params = new URLSearchParams();
params.set('ajax', 'vanilla-price-estimates');
names.forEach(function (name) {
params.append('names[]', name);
});
fetch('index.php?' + params.toString(), {
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
})
.then(function (response) {
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
return response.json();
})
.then(function (payload) {
const estimates = payload && payload.estimates ? payload.estimates : {};
estimateNodes.forEach(function (node) {
applyEstimate(node, estimates[node.dataset.uexQuery || ''] || null);
});
hasLoaded = true;
})
.catch(function () {
markGlobalFailure();
})
.finally(function () {
isLoading = false;
});
}
if (modal.classList.contains('md-show')) {
loadEstimates();
}
const observer = new MutationObserver(function () {
if (modal.classList.contains('md-show')) {
loadEstimates();
}
});
observer.observe(modal, {
attributes: true,
attributeFilter: ['class']
});
})();
</script>
<script>
(function initItemCustomSearch() {
const searchInput = document.getElementById('itemCustomSearch');