File: /home/u353189757/domains/adaptia.com.br/public_html/api/config.php
<?php
declare(strict_types=1);
const ADAPT_ADMIN_USERNAME = 'hzaccaro';
const ADAPT_ADMIN_PASSWORD_HASH = '$2y$12$6BQ8GaixugZqPs5.hzpHnuszBt01cCnvjhCL.huefn49MCul/AaPS';
const ADAPT_AUTH_RATE_SALT = 'b3c8df99c7252b6f1a63a399fb973c3c';
const ADAPT_AUTH_MAX_ATTEMPTS = 5;
const ADAPT_AUTH_LOCK_SECONDS = 900;
const ADAPT_HEATMAP_RETENTION_DAYS = 90;
const ADAPT_TRACKING_MAX_BODY_BYTES = 131072;
const ADAPT_TRACKING_MAX_EVENTS = 80;
function adapt_public_root(): string
{
return dirname(__DIR__);
}
function adapt_private_root(): string
{
$override = getenv('ADAPT_PRIVATE_DIR');
if (is_string($override) && trim($override) !== '') {
return rtrim($override, "\\/");
}
return dirname(adapt_public_root()) . DIRECTORY_SEPARATOR . 'adaptia-private';
}
function adapt_private_path(string $fileName): string
{
return adapt_private_root() . DIRECTORY_SEPARATOR . $fileName;
}
function adapt_send_noindex_headers(): void
{
header('X-Robots-Tag: noindex, nofollow', true);
}
function adapt_json_response(array $payload, int $statusCode = 200): void
{
http_response_code($statusCode);
adapt_send_noindex_headers();
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function adapt_ensure_private_storage(): void
{
$dir = adapt_private_root();
if (!is_dir($dir) && !mkdir($dir, 0700, true) && !is_dir($dir)) {
throw new RuntimeException('private_storage_unavailable');
}
if (!is_writable($dir)) {
throw new RuntimeException('private_storage_not_writable');
}
$sentinel = $dir . DIRECTORY_SEPARATOR . '.private';
if (!is_file($sentinel)) {
@file_put_contents($sentinel, "Adapt IA private storage\n", LOCK_EX);
}
}
function adapt_storage_status(): array
{
try {
adapt_ensure_private_storage();
return [
'ok' => true,
'path' => adapt_private_root(),
'error' => '',
];
} catch (Throwable $exception) {
return [
'ok' => false,
'path' => adapt_private_root(),
'error' => $exception->getMessage(),
];
}
}
function adapt_write_json_file_locked(string $path, array $data): void
{
$handle = fopen($path, 'c+');
if (!$handle) {
throw new RuntimeException('json_file_open_failed');
}
try {
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('json_file_lock_failed');
}
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) {
throw new RuntimeException('json_encode_failed');
}
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, $json . "\n");
fflush($handle);
flock($handle, LOCK_UN);
} finally {
fclose($handle);
}
}
function adapt_read_json_file(string $path, array $fallback): array
{
if (!is_file($path)) {
return $fallback;
}
$content = file_get_contents($path);
if (!is_string($content) || trim($content) === '') {
return $fallback;
}
$decoded = json_decode($content, true);
return is_array($decoded) ? $decoded : $fallback;
}
function adapt_append_submission(array $submission): void
{
adapt_ensure_private_storage();
$path = adapt_private_path('submissions.json');
$handle = fopen($path, 'c+');
if (!$handle) {
throw new RuntimeException('submissions_file_open_failed');
}
try {
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('submissions_file_lock_failed');
}
rewind($handle);
$content = stream_get_contents($handle);
$decoded = is_string($content) && trim($content) !== '' ? json_decode($content, true) : null;
$data = is_array($decoded) ? $decoded : ['version' => 1, 'items' => []];
$data['items'] = isset($data['items']) && is_array($data['items']) ? $data['items'] : [];
$data['items'][] = $submission;
$data['updated_at'] = gmdate('c');
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) {
throw new RuntimeException('submissions_json_encode_failed');
}
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, $json . "\n");
fflush($handle);
flock($handle, LOCK_UN);
} finally {
fclose($handle);
}
}
function adapt_load_submissions(): array
{
adapt_ensure_private_storage();
return adapt_read_json_file(adapt_private_path('submissions.json'), ['version' => 1, 'items' => []]);
}
function adapt_append_tracking_records(array $records): void
{
if (!$records) {
return;
}
adapt_ensure_private_storage();
adapt_maybe_prune_tracking_events();
$handle = fopen(adapt_private_path('tracking-events.jsonl'), 'ab');
if (!$handle) {
throw new RuntimeException('tracking_file_open_failed');
}
try {
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('tracking_file_lock_failed');
}
foreach ($records as $record) {
$line = json_encode($record, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (is_string($line)) {
fwrite($handle, $line . "\n");
}
}
fflush($handle);
flock($handle, LOCK_UN);
} finally {
fclose($handle);
}
}
function adapt_maybe_prune_tracking_events(): void
{
$today = gmdate('Y-m-d');
$metaPath = adapt_private_path('tracking-prune.json');
$meta = adapt_read_json_file($metaPath, []);
if (($meta['last_pruned'] ?? '') === $today) {
return;
}
$trackingPath = adapt_private_path('tracking-events.jsonl');
if (is_file($trackingPath)) {
$cutoff = time() - (ADAPT_HEATMAP_RETENTION_DAYS * 86400);
$handle = fopen($trackingPath, 'c+');
if (!$handle) {
throw new RuntimeException('tracking_prune_open_failed');
}
try {
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('tracking_prune_lock_failed');
}
rewind($handle);
$keptLines = [];
while (($line = fgets($handle)) !== false) {
$decoded = json_decode($line, true);
$serverTime = is_array($decoded) ? strtotime((string) ($decoded['server_time'] ?? '')) : false;
if ($serverTime !== false && $serverTime >= $cutoff) {
$keptLines[] = rtrim($line, "\r\n");
}
}
ftruncate($handle, 0);
rewind($handle);
if ($keptLines) {
fwrite($handle, implode("\n", $keptLines) . "\n");
}
fflush($handle);
flock($handle, LOCK_UN);
} finally {
fclose($handle);
}
}
adapt_write_json_file_locked($metaPath, ['last_pruned' => $today, 'retention_days' => ADAPT_HEATMAP_RETENTION_DAYS]);
}
function adapt_read_tracking_events(int $maxLines = 30000): array
{
adapt_ensure_private_storage();
$path = adapt_private_path('tracking-events.jsonl');
if (!is_file($path)) {
return [];
}
$handle = fopen($path, 'rb');
if (!$handle) {
return [];
}
$events = [];
try {
while (($line = fgets($handle)) !== false) {
$decoded = json_decode($line, true);
if (is_array($decoded)) {
$events[] = $decoded;
if (count($events) > $maxLines) {
array_shift($events);
}
}
}
} finally {
fclose($handle);
}
return $events;
}
function adapt_clean_text(mixed $value, int $maxLength = 160): string
{
$text = trim(strip_tags((string) $value));
$text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $text) ?? '';
$text = preg_replace('/\s+/u', ' ', $text) ?? $text;
$text = trim($text);
if (function_exists('mb_substr')) {
return mb_substr($text, 0, $maxLength, 'UTF-8');
}
return substr($text, 0, $maxLength);
}
function adapt_clean_multiline_text(mixed $value, int $maxLength = 2000): string
{
$text = trim(strip_tags((string) $value));
$text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $text) ?? '';
$text = preg_replace("/\r\n|\r/u", "\n", $text) ?? $text;
$text = preg_replace("/\n{3,}/u", "\n\n", $text) ?? $text;
$text = trim($text);
if (function_exists('mb_substr')) {
return mb_substr($text, 0, $maxLength, 'UTF-8');
}
return substr($text, 0, $maxLength);
}
function adapt_clean_url(mixed $value): string
{
$url = adapt_clean_text($value, 500);
if ($url === '') {
return '';
}
if (str_starts_with($url, '/')) {
return $url;
}
$parts = parse_url($url);
if (!is_array($parts) || !isset($parts['scheme'], $parts['host'])) {
return '';
}
$scheme = strtolower((string) $parts['scheme']);
if (!in_array($scheme, ['http', 'https'], true)) {
return '';
}
$path = isset($parts['path']) ? (string) $parts['path'] : '';
return $scheme . '://' . strtolower((string) $parts['host']) . $path;
}
function adapt_clean_token(mixed $value, int $maxLength = 80): string
{
$token = preg_replace('/[^a-zA-Z0-9._:-]/', '', (string) $value) ?? '';
if (function_exists('mb_substr')) {
return mb_substr($token, 0, $maxLength, 'UTF-8');
}
return substr($token, 0, $maxLength);
}
function adapt_int_between(mixed $value, int $min, int $max): int
{
$number = is_numeric($value) ? (int) $value : 0;
return max($min, min($max, $number));
}
function adapt_sanitize_viewport(mixed $viewport): array
{
if (!is_array($viewport)) {
return ['width' => 0, 'height' => 0, 'dpr' => 1];
}
return [
'width' => adapt_int_between($viewport['width'] ?? 0, 0, 10000),
'height' => adapt_int_between($viewport['height'] ?? 0, 0, 10000),
'dpr' => max(1, min(5, (float) ($viewport['dpr'] ?? 1))),
];
}
function adapt_sanitize_section_list(mixed $sections): array
{
if (!is_array($sections)) {
return [];
}
$cleanSections = [];
foreach (array_slice($sections, 0, 24) as $section) {
if (!is_array($section)) {
continue;
}
$id = adapt_clean_token($section['id'] ?? '', 80);
if ($id === '') {
continue;
}
$cleanSections[] = [
'id' => $id,
'title' => adapt_clean_text($section['title'] ?? $id, 120),
'duration_ms' => adapt_int_between($section['duration_ms'] ?? 0, 0, 86400000),
];
}
return $cleanSections;
}
function adapt_sanitize_tracking_target(mixed $target): array
{
if (!is_array($target)) {
return [];
}
$tag = strtoupper(adapt_clean_text($target['tag'] ?? '', 20));
$cleanTarget = [
'tag' => $tag,
'id' => adapt_clean_token($target['id'] ?? '', 80),
'classes' => adapt_clean_text($target['classes'] ?? '', 120),
'role' => adapt_clean_text($target['role'] ?? '', 60),
'name' => adapt_clean_text($target['name'] ?? '', 80),
'type' => adapt_clean_text($target['type'] ?? '', 40),
'label' => adapt_clean_text($target['label'] ?? '', 120),
'href' => adapt_clean_text($target['href'] ?? '', 160),
'link_host' => adapt_clean_text($target['link_host'] ?? '', 120),
];
if (in_array($tag, ['INPUT', 'TEXTAREA', 'SELECT'], true)) {
$cleanTarget['label'] = '';
$cleanTarget['href'] = '';
}
return array_filter($cleanTarget, static fn ($value) => $value !== '');
}
function adapt_sanitize_tracking_event(mixed $event): ?array
{
if (!is_array($event)) {
return null;
}
$type = adapt_clean_token($event['type'] ?? '', 40);
$allowedTypes = ['session_start', 'section_enter', 'section_leave', 'click', 'scroll_depth', 'session_ping', 'session_end'];
if (!in_array($type, $allowedTypes, true)) {
return null;
}
$clean = [
'type' => $type,
't' => adapt_int_between($event['t'] ?? 0, 0, 86400000),
];
if (isset($event['section_id'])) {
$clean['section_id'] = adapt_clean_token($event['section_id'], 80);
}
if (isset($event['section_title'])) {
$clean['section_title'] = adapt_clean_text($event['section_title'], 120);
}
if (isset($event['duration_ms'])) {
$clean['duration_ms'] = adapt_int_between($event['duration_ms'], 0, 86400000);
}
if (isset($event['max_scroll_depth'])) {
$clean['max_scroll_depth'] = adapt_int_between($event['max_scroll_depth'], 0, 100);
}
if (isset($event['click_count'])) {
$clean['click_count'] = adapt_int_between($event['click_count'], 0, 10000);
}
if (isset($event['x'])) {
$clean['x'] = adapt_int_between($event['x'], 0, 10000);
}
if (isset($event['y'])) {
$clean['y'] = adapt_int_between($event['y'], 0, 10000);
}
if (isset($event['target'])) {
$clean['target'] = adapt_sanitize_tracking_target($event['target']);
}
if (isset($event['sections'])) {
$clean['sections'] = adapt_sanitize_section_list($event['sections']);
}
if (isset($event['viewport'])) {
$clean['viewport'] = adapt_sanitize_viewport($event['viewport']);
}
return $clean;
}
function adapt_start_admin_session(): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
return;
}
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
session_name('adapt_admin');
session_set_cookie_params([
'lifetime' => 0,
'path' => '/admin',
'secure' => $isHttps,
'httponly' => true,
'samesite' => 'Strict',
]);
session_start();
}
function adapt_client_rate_key(): string
{
$ip = (string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
$agent = (string) ($_SERVER['HTTP_USER_AGENT'] ?? 'unknown');
return hash('sha256', $ip . '|' . $agent . '|' . ADAPT_AUTH_RATE_SALT);
}
function adapt_auth_rate_data(): array
{
adapt_ensure_private_storage();
$data = adapt_read_json_file(adapt_private_path('auth-rate.json'), ['items' => []]);
$data['items'] = isset($data['items']) && is_array($data['items']) ? $data['items'] : [];
return $data;
}
function adapt_auth_locked_until(string $key): int
{
$data = adapt_auth_rate_data();
$entry = $data['items'][$key] ?? [];
return is_array($entry) ? (int) ($entry['locked_until'] ?? 0) : 0;
}
function adapt_auth_record_failure(string $key): void
{
$data = adapt_auth_rate_data();
$entry = $data['items'][$key] ?? ['attempts' => 0, 'locked_until' => 0];
$attempts = ((int) ($entry['attempts'] ?? 0)) + 1;
$lockedUntil = $attempts >= ADAPT_AUTH_MAX_ATTEMPTS ? time() + ADAPT_AUTH_LOCK_SECONDS : 0;
$data['items'][$key] = [
'attempts' => $attempts,
'locked_until' => $lockedUntil,
'updated_at' => gmdate('c'),
];
adapt_write_json_file_locked(adapt_private_path('auth-rate.json'), $data);
}
function adapt_auth_clear_rate(string $key): void
{
$data = adapt_auth_rate_data();
if (isset($data['items'][$key])) {
unset($data['items'][$key]);
adapt_write_json_file_locked(adapt_private_path('auth-rate.json'), $data);
}
}