HEX
Server: Apache
System: Linux vpshost11508.publiccloud.com.br 5.15.179-grsec-vpshost-10.lc.el8.x86_64 #1 SMP Mon Apr 7 12:04:45 -03 2025 x86_64
User: wicomm2 (10002)
PHP: 8.3.0
Disabled: apache_child_terminate,dl,escapeshellarg,escapeshellcmd,exec,link,mail,openlog,passthru,pcntl_alarm,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_wait,pcntl_waitpid,pcntl_wexitstatus,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,php_check_syntax,php_strip_whitespace,popen,proc_close,proc_open,shell_exec,symlink,system
Upload Files
File: /home/storage/5/78/dd/wicomm2/public_html/clientes/performance-healthcheck/config.php
<?php
declare(strict_types=1);

@set_time_limit(0);
ini_set('max_execution_time', '0');
ini_set('default_socket_timeout', '60');
ignore_user_abort(true);

while (ob_get_level() > 0) { ob_end_flush(); }
ob_implicit_flush(true);

function app_config(): array
{
  return [
    'gemini' => [
      'api_key' => getenv('GEMINI_API_KEY') ?: 'AIzaSyCsYoQR76n32MCinh8eI53CwDiPaDZOMHk',
      // Modelo padrão atualizado (o alias gemini-1.5-flash pode retornar 404 no v1beta).
      'model' => getenv('GEMINI_MODEL') ?: 'gemini-2.5-flash',
      'base_url' => 'https://generativelanguage.googleapis.com/v1beta',
    ],
    'lighthouse' => [
      'api_key' => getenv('LIGHTHOUSE_API_KEY') ?: 'AIzaSyB_kZFiOw7jqqatZe7yv1SWfoEvx6QWvCE',
      'base_url' => 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed',
    ],
    'vtex' => [
      'account' => getenv('VTEX_ACCOUNT') ?: 'wicommpartnerbr',
      'app_key' => getenv('VTEX_APP_KEY') ?: 'vtexappkey-wicommpartnerbr-VMNRIQ',
      'app_token' => getenv('VTEX_APP_TOKEN') ?: 'EOIOUIUHNYKOAHIWYPKBPZRHIDVMWAFSJHEZSVEKXWCGYVAXJWOUQWQZNKIFQPJFGODHPDBAFLKTCETJSZTXJEBHEHYZOGGKLTSGBSVPXELLPGUWUQKVUHZMIEWFRWAE',
      'data_entity' => getenv('VTEX_DATA_ENTITY') ?: 'AP',
    ],
    'app' => [
      'timezone' => getenv('APP_TIMEZONE') ?: 'America/Sao_Paulo',
    ],
  ];
}

// ===============
// Helpers
// ===============

function now_iso(string $tz = 'America/Sao_Paulo'): string
{
  return (new DateTimeImmutable('now', new DateTimeZone($tz)))->format(DateTimeInterface::ATOM);
}

function json_out(int $code, array $payload): void
{
  http_response_code($code);
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode($payload, JSON_UNESCAPED_UNICODE);
  exit;
}

function http_request(string $method, string $url, array $headers = [], ?string $body = null, int $timeout = 180): array
{
  $ch = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 25,
  ]);
  if ($body !== null)
    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);

  $raw = curl_exec($ch);
  $err = curl_error($ch);
  $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($raw === false) {
    return ['ok' => false, 'code' => $code ?: 0, 'error' => $err ?: 'Falha', 'data' => null, 'raw' => null];
  }

  $data = json_decode($raw, true);
  $parsed = is_array($data) ? $data : null;

  $errVal = null;
  if (is_array($parsed)) {
    if (isset($parsed['message'])) {
      $errVal = $parsed['message'];
    } elseif (isset($parsed['error'])) {
      $errVal = $parsed['error'];
    }
  }

  if (is_array($errVal)) {
    $errVal = json_encode($errVal, JSON_UNESCAPED_UNICODE);
  }

  if ($errVal === null && !($code >= 200 && $code < 300)) {
    $errVal = 'Erro';
  }

  return [
    'ok' => $code >= 200 && $code < 300,
    'code' => $code,
    'error' => $errVal,
    'data' => $parsed,
    'raw' => $raw,
  ];
}

// ===============
// VTEX Master Data
// ===============

function vtex_base(array $config): string
{
  $acc = (string) ($config['vtex']['account'] ?? '');
  if ($acc === '')
    throw new RuntimeException('VTEX_ACCOUNT ausente');
  return 'https://' . $acc . '.myvtex.com';
}

function vtex_headers(array $config): array
{
  $k = (string) ($config['vtex']['app_key'] ?? '');
  $t = (string) ($config['vtex']['app_token'] ?? '');
  if ($k === '' || $t === '')
    throw new RuntimeException('VTEX_APP_KEY ou VTEX_APP_TOKEN ausente');

  return [
    'Accept: application/json',
    'Content-Type: application/json',
    'X-VTEX-API-AppKey: ' . $k,
    'X-VTEX-API-AppToken: ' . $t,
  ];
}

function md_entity(array $config): string
{
  $e = (string) ($config['vtex']['data_entity'] ?? 'AP');
  return $e !== '' ? $e : 'AP';
}

function md_search_clients(array $config, int $size = 200): array
{
  $entity = md_entity($config);
  $fields = 'id,client_name,client_url,client_plataform,client_plataforma,performance_history';
  $url = vtex_base($config) . "/api/dataentities/{$entity}/search?_fields={$fields}&_size={$size}";

  $r = http_request('GET', $url, vtex_headers($config), null, 60);
  if (!$r['ok'])
    throw new RuntimeException('Master Data search falhou');

  $items = $r['data'];
  return is_array($items) ? $items : [];
}

function md_get_client(array $config, string $id): array
{
  $entity = md_entity($config);

  // Em algumas contas, o endpoint de documents retorna um doc “mínimo” sem `_fields`.
  $fields = 'id,client_name,client_url,client_plataform,client_plataforma,performance_history';
  $url = vtex_base($config) . "/api/dataentities/{$entity}/documents/{$id}?_fields={$fields}";

  $r = http_request('GET', $url, vtex_headers($config), null, 60);
  if (!$r['ok'] || !is_array($r['data']))
    throw new RuntimeException('Master Data get falhou');

  return $r['data'];
}

function md_patch_client(array $config, string $id, array $partial): array
{
  $entity = md_entity($config);
  $url = vtex_base($config) . "/api/dataentities/{$entity}/documents/{$id}";

  // PATCH primeiro, se não aceitar, faz PUT
  $r = http_request('PATCH', $url, vtex_headers($config), json_encode($partial, JSON_UNESCAPED_UNICODE), 60);
  if ($r['ok'] && is_array($r['data']))
    return $r['data'];

  $r2 = http_request('PUT', $url, vtex_headers($config), json_encode($partial, JSON_UNESCAPED_UNICODE), 60);
  if (!$r2['ok'] || !is_array($r2['data']))
    throw new RuntimeException('Master Data update falhou');

  return $r2['data'];
}

function md_history_as_array(array $client): array
{
  $h = $client['performance_history'] ?? [];
  if (is_string($h)) {
    $decoded = json_decode($h, true);
    return is_array($decoded) ? $decoded : [];
  }
  return is_array($h) ? $h : [];
}

// ===============
// Lighthouse (PageSpeed Insights v5)
// ===============

function run_lighthouse(array $config, string $targetUrl, string $strategy = 'mobile'): array
{
  $key = (string) ($config['lighthouse']['api_key'] ?? '');
  if ($key === '')
    throw new RuntimeException('LIGHTHOUSE_API_KEY ausente');

  $base = (string) ($config['lighthouse']['base_url'] ?? '');
  if ($base === '')
    throw new RuntimeException('Lighthouse base_url ausente');

  $q = [
    'url' => $targetUrl,
    'key' => $key,
    'strategy' => $strategy,
    'category' => ['performance', 'accessibility', 'best-practices', 'seo', 'pwa'],
  ];

  $url = $base . '?' . http_build_query($q);

  $r = http_request('GET', $url, ['Accept: application/json'], null, 180);
  if (!$r['ok'] || !is_array($r['data']))
    throw new RuntimeException('Lighthouse falhou');

  return $r['data'];
}

function extract_lighthouse_summary(array $psi): array
{
  $lr = $psi['lighthouseResult'] ?? [];
  $cats = $lr['categories'] ?? [];
  $audits = $lr['audits'] ?? [];

  $score = static function (string $key) use ($cats): ?int {
    $v = $cats[$key]['score'] ?? null;
    if ($v === null)
      return null;
    return (int) round(((float) $v) * 100);
  };

  $metrics = [
    'fcp' => $audits['first-contentful-paint']['displayValue'] ?? null,
    'lcp' => $audits['largest-contentful-paint']['displayValue'] ?? null,
    'cls' => $audits['cumulative-layout-shift']['displayValue'] ?? null,
    'tbt' => $audits['total-blocking-time']['displayValue'] ?? null,
    'tti' => $audits['interactive']['displayValue'] ?? null,
    'speed_index' => $audits['speed-index']['displayValue'] ?? null,
  ];

  return [
    'scores' => [
      'performance' => $score('performance'),
      'accessibility' => $score('accessibility'),
      'best-practices' => $score('best-practices'),
      'seo' => $score('seo'),
      'pwa' => $score('pwa'),
    ],
    'metrics' => $metrics,
    'lighthouse_raw' => $lr,
  ];
}

// ===============
// Gemini
// ===============

function gemini_report(array $config, array $client, array $summary, string $strategy = 'mobile'): array
{
  $key = (string) ($config['gemini']['api_key'] ?? '');
  if ($key === '')
    throw new RuntimeException('GEMINI_API_KEY ausente');

  $model = (string) ($config['gemini']['model'] ?? 'gemini-2.5-flash');
  $base = (string) ($config['gemini']['base_url'] ?? 'https://generativelanguage.googleapis.com/v1beta');
  $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent";

  $schemaHint = [
    'summary' => 'string',
    'priority_actions' => [
      ['title' => 'string', 'why' => 'string', 'how' => 'string', 'expected_impact' => 'string', 'scope' => 'performance|accessibility|best-practices|seo|pwa']
    ],
    'risks' => [
      ['title' => 'string', 'context' => 'string']
    ],
    'notes' => ['string']
  ];

  $clientName = (string) ($client['client_name'] ?? '');
  $clientUrl = (string) ($client['client_url'] ?? '');
  $platformRaw = $client['client_plataform'] ?? ($client['client_plataforma'] ?? '');
  $platform = is_array($platformRaw) ? json_encode($platformRaw, JSON_UNESCAPED_UNICODE) : (string) $platformRaw;

  $system = 'Você é um analista técnico de performance web. Você recebe o JSON do Lighthouse e devolve um relatório em JSON estrito, sem markdown, sem texto fora do JSON, seguindo o schema sugerido. Você não inventa métricas, você usa apenas o que existe no JSON e quando faltar dado você explicita a ausência. A escrita deve ser específica e orientada a implementação, evitando generalidades.';

  $user = [
    'context' => [
      'client_name' => $clientName,
      'client_url' => $clientUrl,
      'client_plataform' => $platform,
      'strategy' => $strategy,
      'scores' => $summary['scores'] ?? [],
      'metrics' => $summary['metrics'] ?? [],
      'schema' => $schemaHint,
    ],
    'lighthouse' => $summary['lighthouse_raw'] ?? new stdClass(),
  ];

  $payload = [
    'systemInstruction' => ['parts' => [['text' => $system]]],
    'contents' => [
      [
        'role' => 'user',
        'parts' => [['text' => json_encode($user, JSON_UNESCAPED_UNICODE)]],
      ]
    ],
    'generationConfig' => [
      'temperature' => 0.2,
    ],
  ];

  $r = http_request('POST', $url, ['Content-Type: application/json', 'Accept: application/json'], json_encode($payload, JSON_UNESCAPED_UNICODE), 180);
  if (!$r['ok'] || !is_array($r['data'])) {
    $code = (int) ($r['code'] ?? 0);
    $raw = (string) ($r['raw'] ?? '');
    $raw = mb_substr($raw, 0, 1400);
    $msgRaw = $r['error'] ?? '';
    $msg = is_array($msgRaw) ? json_encode($msgRaw, JSON_UNESCAPED_UNICODE) : (string) $msgRaw;
    throw new RuntimeException('Gemini falhou (HTTP ' . $code . ')' . ($msg !== '' ? ' | ' . $msg : '') . ($raw !== '' ? "\n\nResposta:\n" . $raw : ''));
  }

  $text = $r['data']['candidates'][0]['content']['parts'][0]['text'] ?? '';
  $json = json_decode((string) $text, true);

  if (!is_array($json)) {
    return [
      'summary' => 'Falha ao interpretar JSON do Gemini, o conteúdo foi salvo como texto.',
      'priority_actions' => [],
      'risks' => [],
      'notes' => [mb_substr((string) $text, 0, 800)],
      '_raw_text' => (string) $text,
    ];
  }

  return $json;
}