V1.4.1
This commit is contained in:
parent
9063e6830c
commit
3565a88085
409
index-en.php
409
index-en.php
@ -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
392
index.php
@ -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');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user