<?php
/*
 * Software Name: Eorisis Setup
 * Software URI: https://eorisis.com/shop/software/joomla/setup
 * Author: Eorisis
 * Author URI: https://eorisis.com
 * Copyright: Copyright (C) 2011-2025 EORISIS. All Rights Reserved.
 * License: GNU General Public License version 2; See https://eorisis.com/gpl
 * Terms of Service: https://eorisis.com/tos
 */
namespace eorisis_setup\framework\install\lib\core\model;
\defined('_JEXEC') or die;

class curl
{
	private $options = [];

	// --------------------------------------------------

	public function __construct() {}

	// ----------------------------------------------------------

	public function request(array $args = []) : array
	{
		$args = \array_replace([
			'validate_ssl' => true,
			'ssl_options' => [],
			'as_array' => false,
			'include_headers' => false,
			'full_info' => false,
			'error_code_check' => false,
			'retry_attempts' => 3
		], $args);

		if ($args['ssl_options'])
		{
			$defaults = $args['ssl_options'];
		}
		else
		{
			$defaults = $this->ssl_validation_options($args['validate_ssl']);
		}

		$this->options = \array_replace($this->options, $defaults);

		// Set Timeout if not set
		if (!isset($this->options[CURLOPT_TIMEOUT]))
		{
			$this->options[CURLOPT_TIMEOUT] = 5;
		}

		// Without retry attempts
		if (!$args['retry_attempts'])
		{
			return $this->execute($args);
		}

		$response = [];

		// With retry attempts
		for ($i = 0; $i < $args['retry_attempts']; $i++)
		{
			$response = $this->execute($args);

			if ($response['status'] == 200)
			{
				return $response;
			}

			if ($i >= $args['retry_attempts'] - 1)
			{
				return $response;
			}

			$timeout = !$i ? 10 : 30;
			$this->options[CURLOPT_TIMEOUT] = $timeout;
			\sleep(1);
		}

		return $response;
	}

	// --------------------------------------------------

	public function set(string $key, $value)
	{
		switch ($key)
		{
			case 'uri':
				$this->options[CURLOPT_URL] = $value;
				break;

			case 'get':
				$this->options[CURLOPT_HTTPGET] = $value;
				break;

			case 'post':
				$this->options[CURLOPT_POST] = $value;
				break;

			case 'post_data':
				$this->options[CURLOPT_POSTFIELDS] = $value;
				break;

			case 'method':
				$this->options[CURLOPT_CUSTOMREQUEST] = $value;
				break;

			case 'verbose':
				$this->options[CURLOPT_VERBOSE] = $value;
				break;

			case 'useragent':
				$this->options[CURLOPT_USERAGENT] = $value;
				break;

			case 'http_headers':
				$this->options[CURLOPT_HTTPHEADER] = $value;
				break;

			case 'header':
				$this->options[CURLOPT_HEADER] = $value;
				break;

			case 'ip_resolve_version':
				switch ($value)
				{
					case 4:
						$this->options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
						break 2;

					case 6:
						$this->options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
						break 2;

					default:
						$this->options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_WHATEVER;
				}
				break;

			case 'return_transfer':
				$this->options[CURLOPT_RETURNTRANSFER] = $value;
				break;

			case 'nobody':
				$this->options[CURLOPT_NOBODY] = $value;
				break;

			case 'follow_location':
				$this->options[CURLOPT_FOLLOWLOCATION] = $value;
				break;

			case 'max_redirects':
				$this->options[CURLOPT_MAXREDIRS] = $value;
				break;

			case 'fail_on_error':
				$this->options[CURLOPT_FAILONERROR] = $value;
				break;

			case 'connect_timeout':
				$this->options[CURLOPT_CONNECTTIMEOUT] = $value;
				break;

			case 'timeout':
				$this->options[CURLOPT_TIMEOUT] = $value;
				break;

			case 'fresh_connect':
				$this->options[CURLOPT_FRESH_CONNECT] = $value;
				break;

			case 'dns_use_global_cache':
				$this->options[CURLOPT_DNS_USE_GLOBAL_CACHE] = $value;
				break;
			
			case 'dns_cache_timeout':
				$this->options[CURLOPT_DNS_CACHE_TIMEOUT] = $value;
				break;

			case 'http_version':
				switch ($value)
				{
					// HTTP/1.0
					case '1.0':
						$this->options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
						break 2;

					// HTTP/1.1
					case '1.1':
						$this->options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
						break 2;

					// HTTP 2
					case '2.0':
						$this->options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
						break 2;

					// HTTP 2 over TLS (HTTPS) only
					case '2TLS':
						$this->options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
						break 2;

					default:
						$this->options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_NONE;
				}
				break;
		}
	}

	// --------------------------------------------------

	public function reset_options()
	{
		$this->options = [];
	}

	// --------------------------------------------------

	private function execute(array $args) : array
	{
		$handler = \curl_init();

		// Set Options
		\curl_setopt_array($handler, $this->options);

		$response_headers = [];

		if ($args['include_headers'])
		{
			curl_setopt($handler, CURLOPT_HEADERFUNCTION, function($handler, $header) use (&$response_headers)
			{
				$len = \strlen((string)$header);
				$header = \explode(':', $header, 2);

				// ignore invalid headers
				if (\count($header) < 2)
				{
					return $len;
				}

				$key = \strtolower(\trim($header[0]));
				$response_headers[$key][] = \trim($header[1]);

				return $len;
			});
		}

		// Verbose
		if (!empty($this->options[CURLOPT_VERBOSE]))
		{
			$ms = \str_replace('.', '', \microtime(true));
			curl_setopt($handler, CURLOPT_STDERR, \fopen(JPATH_SITE.'/curl_error_log_'.$ms, 'w'));
		}

		$response['result'] = \curl_exec($handler); // Must be first
		$response['headers'] = $response_headers;
		$response['status'] = (int)\curl_getinfo($handler, CURLINFO_RESPONSE_CODE);
		$response['error_message'] = \curl_error($handler); // string
		$response['error_code'] = \curl_errno($handler); // int

		if ($args['full_info'])
		{
			$response['info'] = \curl_getinfo($handler);
		}

		if ($args['error_code_check'])
		{
			$response['ok'] = $response['error_code'] == CURLE_OK;
		}

		\curl_close($handler);

		if ($args['as_array'])
		{
			$response['result'] = (array)\json_decode((string)$response['result'], true);
		}

		return $response;
	}

	// --------------------------------------------------

	private function ssl_validation_options(bool $validate_ssl) : array
	{
		if ($validate_ssl)
		{
			$arr[CURLOPT_SSL_VERIFYPEER] = true;
			$arr[CURLOPT_SSL_VERIFYHOST] = 2;
		}
		else
		{
			$arr[CURLOPT_SSL_VERIFYPEER] = false;
			$arr[CURLOPT_SSL_VERIFYHOST] = 0;
		}

		return $arr;
	}
}
