<?php if (!defined('PmWiki')) exit();
/*  Copyright 2026 Petko Yotov www.pmwiki.org/petko
    This file is part of PmWiki; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published
    by the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.  See pmwiki.php for full details.

    These helper functions use cURL to make remote requests. 
    Inspired by the JavaScript function PmLib.afetch() by Petko Yotov.
    The script is not included by default, enable it with such a line:
    
    include_once("$FarmD/scripts/request.php");
*/

SDVA($PmREQ, [
  'cachedir' => "$WorkDir/.cache/PmREQ",
  'EnableStopWatch' => 0,
]);

function PmREQB($data) { # only body|json, replace file_get_contents($url)
  $resp = PmREQ($data);
  return $resp? ($resp['json']??$resp['body']??'') : $resp;
}
function PmREQ($data) {
  global $Now, $PmREQ;
  if (is_string($data)) $data = [ 'url'=>$data ];
  $url = $data['url'] ?? $data[0];
  
  if (!filter_var($url, FILTER_VALIDATE_URL)) {
    if (file_exists($url)) return file_get_contents($url);
    return false;
  }
  $ch = curl_init();
  $method = $data['method'] ?? 'GET';
  $qheaders = $data['headers'] ?? [];
  $ref = $data['ref'] ?? false;
  if ($ref) $qheaders['Referer'] = $ref;
  $cachedelay = $data['cache'] ?? false;
  $sw = $PmREQ['EnableStopWatch'] ?? 0;

  if (isset($data['get']) && is_array($data['get'])) {
    $get = http_build_query($data['get']);
    $sep = (strpos($url, '?')>0) ? '&':'?';
    $url .= "$sep$get";
  }
  $post = '';
  if (@$data['post']) {
    $post = http_build_query($data['post']);
    $qheaders['Content-Type'] = 'application/x-www-form-urlencoded';
  }
  elseif (@$data['postjson']) {
    $post = json_encode($data['postjson']);
    $qheaders['Content-Type'] = 'application/json';
  }
  elseif ($method == 'HEAD') {
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');
  }
  elseif ($method != 'GET') { # OPTIONS
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
  }
  if ($post) $qheaders['Content-Length'] = strlen($post);
  if (@$data['ua']=='ff') {
    $ver = PmFFVersion();
    $data['ua'] = "Mozilla/5.0 (X11; Linux x86_64; rv:$ver.0) Gecko/20100101 Firefox/$ver.0";
  }
  if (@$data['ua']) {
    $qheaders['User-Agent'] = $data['ua'];
  }
  
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  if ($post) {
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
  }
  if ($qheaders) {
    $h = [];
    foreach($qheaders as $k=>$v) $h[] = "$k: $v";
    curl_setopt($ch, CURLOPT_HTTPHEADER, $h);
  }
  
  if (isset($data['curlopt'])) {
    foreach($data['curlopt'] as $k=>$v) curl_setopt($ch, $k, $v);
  }
  
  curl_setopt($ch, CURLOPT_HEADER, true);
  
  $cached = false;
  if ($cachedelay) {
    $cached = PmREQCache($cachedelay, $url, $post, $qheaders);
  }
  if ($cached) {
    if ($sw) StopWatch("PmREQ: Retrieved $url from CACHE");
    list($rheaders, $body) = $cached;
  }
  else {
    $response = curl_exec($ch);
    if (curl_errno($ch)) {
      $err = "cURL Error: " . curl_error($ch);
      error_log($err);
      if ($sw) StopWatch("PmREQ ($url): $err");
      return false;
    }
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($sw) StopWatch("PmREQ: Retrieved $url code=$code");
    $hsize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $rheaders = substr($response, 0, $hsize);
    $body = substr($response, $hsize);
    if ($code == 200 && $cachedelay) {
      PmREQCache($cachedelay, $url, $post, $qheaders, [$rheaders, $body]);
    }
  }
  $rheaders = PmREQParseHeaders($rheaders);
  $out = ['code'=>$code??200, 'headers'=>$rheaders, 'body'=>$body];
  if (@$data['jd'] || stripos($rheaders['content-type']??'', 'application/json') === 0) {
    $out['json'] = json_decode($body, true);
  }
  return $out;
}

function PmREQParseHeaders($rheaders) {
  $headers = [];
  $lines = explode("\r\n", trim($rheaders));
  foreach ($lines as $line) {
    if (empty($line)) continue;
    if (stripos($line, 'HTTP/') === 0) { # HTTP/1.1 200 OK
      $headers['__status_line'] = $line;
      continue;
    }
    $parts = explode(':', $line, 2);
    if (count($parts) === 2) {
      $key = strtolower(trim($parts[0]));
      $value = trim($parts[1]);
      # Duplicate headers into arrays
      if (isset($headers[$key])) {
        if (is_array($headers[$key])) $headers[$key][] = $value;
        else $headers[$key] = [$headers[$key], $value];
      }
      else $headers[$key] = $value;
    }
  }
  return $headers;
}

function PmREQCache($delay, $url, $post, $qheaders, $rheaders_body = null) {
  global $Now, $PmREQ;
  $hash = md5(serialize([$url, $post, $qheaders]));
  $host = parse_url($url,  PHP_URL_HOST);
  $dir = $PmREQ['cachedir'];
  $fname = "$dir/$host-$hash,cache";
  $cleanup = "$dir/cleanup,cache";
  
  if (!is_null($rheaders_body)) { # needs cache
    list($rheaders, $body) = $rheaders_body;
    $rheaders = preg_replace("/(\r\n){2,}/", '$1', trim($rheaders));
    $mtime = $Now + PPTD($delay);
    $jtime = PSFT('%Y-%m-%dT%H:%M:%SZ', $mtime, null, 'GMT');
    $jnow  = PSFT('%Y-%m-%dT%H:%M:%SZ', $Now,   null, 'GMT');
    $content = "X-PmREQ-expires: $jtime\r\nX-PmREQ-cached: $jnow\r\n"
      . "X-PmREQ-filepath: $fname\r\nX-PmREQ-url: $url\r\n"
      . "$rheaders\r\n\r\n$body";
    mkdirpdeny($dir);
    file_put_contents($fname, $content);
    fixperms($fname);
    touch($fname, $mtime);
    return true;
  }
  if (is_dir($dir) && (!file_exists($cleanup)||filemtime($cleanup)<$Now-3600)) {
    touch($cleanup);
    fixperms($cleanup);
    $files = glob("$dir/*-*,cache");
    foreach($files as $file) {
      if (filemtime($file)<$Now) unlink($file);
    }
  }
  if (file_exists($fname) && filemtime($fname)>$Now) {
    $raw = file_get_contents($fname);
    list($rheaders, $rbody) = explode("\r\n\r\n", $raw);
    return [$rheaders, $rbody];
  }
  return false;
}
# This estimates the current Firefox version (for browser requests)
function PmFFVersion($stamp = null) {
  global $PmFFVersionData;
  SDVA($PmFFVersionData, array(
    # version on that date
    'basever' => 153,
    'basedate' => '2026-07-21',
    'interval' => 28,
  ));
  $baseday = $PmFFVersionData['baseday'] 
    ?? unixtojd(strtotime($PmFFVersionData['basedate']));
  if (is_null($stamp)) $stamp = time();
  $currday = unixtojd($stamp);
  $diffday = $currday-$baseday;
  $diffver = floor($diffday/$PmFFVersionData['interval']);
  return $PmFFVersionData['basever']+$diffver-1;# previous, avoid overtaking
}


