<?php

/**
 * All-in-One WP Migration 
 * Copyright (C) 2014-2018 ServMask Inc.
 *
 * Modified Copyright (C) 2025 Lead Connector
 * 
 * 
 * This program is free software: 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
if ( ! defined( 'ABSPATH' ) ) {
	die( 'Well Sir You Cannot Jump Here...haha' );
}

class Lcm_URL_Curl {

	/**
	 * Base URL
	 *
	 * @var string $base_url Base URL
	 */
	protected $base_url = null;

	/**
	 * Base path
	 *
	 * @var string $path Base path
	 */
	protected $path = null;

	/**
	 * Base query
	 *
	 * @var array $query Base query
	 */
	protected $query = array();

	/**
	 * Effective URL
	 *
	 * @var string $effective_url Effective URL
	 */
	protected $effective_url = null;

	/**
	 * Content length
	 *
	 * @var integer $content_length Content length
	 */
	protected $content_length = 0;

	/**
	 * cURL SSL
	 *
	 * @var boolean $ssl SSL mode
	 */
	protected $ssl = true;

	/**
	 * cURL handler
	 *
	 * @var resource $handler cURL handler
	 */
	protected $handler = null;

	/**
	 * cURL options
	 *
	 * @var array $options cURL options
	 */
	protected $options = array();

	/**
	 * cURL headers
	 *
	 * @var array $headers cURL headers
	 */
	protected $headers = array( 'User-Agent' => 'All-in-One WP Migration' );

	/**
	 * cURL messages
	 *
	 * @var array $messages cURL messages
	 */
	protected $messages = array(
		// [Informational 1xx]
		100 => '100 Continue',
		101 => '101 Switching Protocols',

		// [Successful 2xx]
		200 => '200 OK',
		201 => '201 Created',
		202 => '202 Accepted',
		203 => '203 Non-Authoritative Information',
		204 => '204 No Content',
		205 => '205 Reset Content',
		206 => '206 Partial Content',

		// [Redirection 3xx]
		300 => '300 Multiple Choices',
		301 => '301 Moved Permanently',
		302 => '302 Found',
		303 => '303 See Other',
		304 => '304 Not Modified',
		305 => '305 Use Proxy',
		306 => '306 (Unused)',
		307 => '307 Temporary Redirect',

		// [Client Error 4xx]
		400 => '400 Bad Request',
		401 => '401 Unauthorized',
		402 => '402 Payment Required',
		403 => '403 Forbidden',
		404 => '404 Not Found',
		405 => '405 Method Not Allowed',
		406 => '406 Not Acceptable',
		407 => '407 Proxy Authentication Required',
		408 => '408 Request Timeout',
		409 => '409 Conflict',
		410 => '410 Gone',
		411 => '411 Length Required',
		412 => '412 Precondition Failed',
		413 => '413 Request Entity Too Large',
		414 => '414 Request-URI Too Long',
		415 => '415 Unsupported Media Type',
		416 => '416 Requested Range Not Satisfiable',
		417 => '417 Expectation Failed',

		// [Server Error 5xx]
		500 => '500 Internal Server Error',
		501 => '501 Not Implemented',
		502 => '502 Bad Gateway',
		503 => '503 Service Unavailable',
		504 => '504 Gateway Timeout',
		505 => '505 HTTP Version Not Supported',
	);

	public function __construct() {
		if ( ! extension_loaded( 'curl' ) ) {
			throw new Lcm_Error_Exception( __( 'URL Extension requires PHP cURL extension. ', LC_MIGRATOR_PLUGIN_NAME ) );
		}

		// Default configuration
		$this->set_option( CURLOPT_HEADER, false );	
		$this->set_option( CURLOPT_RETURNTRANSFER, true );
		$this->set_option( CURLOPT_FOLLOWLOCATION, true );
		$this->set_option( CURLOPT_SSL_VERIFYHOST, false );
		$this->set_option( CURLOPT_SSL_VERIFYPEER, false );
		$this->set_option( CURLOPT_CONNECTTIMEOUT, 120 );
		$this->set_option( CURLOPT_TIMEOUT, 0 );

		// Enable WP proxy
		$proxy = new WP_HTTP_Proxy();
		if ( $proxy->is_enabled() ) {
			$this->set_option( CURLOPT_PROXY, $proxy->host() );
			$this->set_option( CURLOPT_PROXYPORT, $proxy->port() );
			if ( $proxy->use_authentication() ) {
				$this->set_option( CURLOPT_PROXYUSERPWD, $proxy->authentication() );
			}
		}
	}

	/**
	 * Set SSL mode
	 *
	 * @param  boolean $value SSL mode
	 * @return Lcm_URL_Curl
	 */
	public function set_ssl( $value ) {
		$this->ssl = $value;
		return $this;
	}

	/**
	 * Get SSL mode
	 *
	 * @return boolean
	 */
	public function get_ssl() {
		return $this->ssl;
	}

	/**
	 * Set base URL
	 *
	 * @param  string $value Base URL
	 * @return object
	 */
	public function set_base_url( $value ) {
		$this->base_url = $value;
		return $this;
	}

	/**
	 * Get base URL
	 *
	 * @return string
	 */
	public function get_base_url() {
		return $this->base_url;
	}

	/**
	 * Set base path
	 *
	 * @param  string $value Base path
	 * @return object
	 */
	public function set_path( $value ) {
		$this->path = $value;
		return $this;
	}

	/**
	 * Get base path
	 *
	 * @return string
	 */
	public function get_path() {
		return $this->path;
	}

	/**
	 * Set base query
	 *
	 * @param  array  $value Base query
	 * @return object
	 */
	public function set_query( $value ) {
		$this->query = $value;
		return $this;
	}

	/**
	 * Get base query
	 *
	 * @return array
	 */
	public function get_query() {
		return $this->query;
	}

	/**
	 * Set effective URL
	 *
	 * @param  string $value Effective URL
	 * @return object
	 */
	public function set_effective_url( $value ) {
		$this->effective_url = $value;
		return $this;
	}

	/**
	 * Get effective URL
	 *
	 * @return string
	 */
	public function get_effective_url() {
		return $this->effective_url;
	}

	/**
	 * Set content length
	 *
	 * @param  integer $value Content length
	 * @return object
	 */
	public function set_content_length( $value ) {
		$this->content_length = $value;
		return $this;
	}

	/**
	 * Get content length
	 *
	 * @return integer
	 */
	public function get_content_length() {
		return $this->content_length;
	}

	/**
	 * Set cURL option
	 *
	 * @param  mixed  $name  cURL option name
	 * @param  mixed  $value cURL option value
	 * @return object
	 */
	public function set_option( $name, $value ) {
		$this->options[ $name ] = $value;
		return $this;
	}

	/**
	 * Get cURL option
	 *
	 * @param  mixed $name cURL option name
	 * @return mixed
	 */
	public function get_option( $name ) {
		return isset( $this->options[ $name ] ) ? $this->options[ $name ] : null;
	}

	/**
	 * Set cURL header
	 *
	 * @param  string $name  cURL header name
	 * @param  string $value cURL header value
	 * @return object
	 */
	public function set_header( $name, $value ) {
		$this->headers[ $name ] = $value;
		return $this;
	}

	/**
	 * Get cURL header
	 *
	 * @param  string $name cURL header name
	 * @return string
	 */
	public function get_header( $name ) {
		return isset( $this->headers[ $name ] ) ? $this->headers[ $name ] : null;
	}

	/**
	 * Make cURL request
	 *
	 * @param  boolean $parse_as_json JSON parse
	 * @return mixed
	 * @throws Lcm_Connect_Exception
	 * @throws Lcm_Rate_Limit_Exception
	 * @throws Lcm_Internal_Server_Error_Exception
	 * @throws Lcm_Error_Exception
	 */
	public function make_request( $parse_as_json = false ) {
		$this->handler = curl_init();

		// Set base URL
		if ( $this->get_query() ) {
			$this->set_option( CURLOPT_URL, $this->get_base_url() . $this->get_path() . '?' . build_query( $this->get_query() ) );
		} else {
			$this->set_option( CURLOPT_URL, $this->get_base_url() . $this->get_path() );
		}

		// Apply cURL headers
		$http_headers = array();
		foreach ( $this->headers as $name => $value ) {
			$http_headers[] = "$name: $value";
		}

		$this->set_option( CURLOPT_HTTPHEADER, $http_headers );

		// Apply cURL options
		foreach ( $this->options as $name => $value ) {
			curl_setopt( $this->handler, $name, $value );
		}

		// HTTP request
		$response = curl_exec( $this->handler );
		if ( $response === false ) {
			if ( ( $errno = curl_errno( $this->handler ) ) ) {
				throw new Lcm_Connect_Exception( sprintf( __( 'Unable to connect to URL address. Error code: %s. ', LC_MIGRATOR_PLUGIN_NAME ), $errno, $errno ) );
			}
		}

		// HTTP headers
		if ( $this->get_option( CURLOPT_HEADER ) ) {
			$headers  = substr( $response, 0, curl_getinfo( $this->handler, CURLINFO_HEADER_SIZE ) );
			$response = substr( $response, curl_getinfo( $this->handler, CURLINFO_HEADER_SIZE ) );
		}

		// HTTP errors
		$http_code = curl_getinfo( $this->handler, CURLINFO_HTTP_CODE );
		if ( $http_code === 429 ) {
			throw new Lcm_Rate_Limit_Exception( sprintf( __( 'Too many requests. Please try again later. Error code: %s ', LC_MIGRATOR_PLUGIN_NAME ), $http_code, $http_code ) );
		}

		if ( $http_code >= 500 ) {
			throw new Lcm_Internal_Server_Error_Exception( sprintf( __( 'Internal Server Error. Please try again later. Error code: %s ', LC_MIGRATOR_PLUGIN_NAME ), $http_code, $http_code ) );
		}

		if ( $http_code >= 400 ) {
			if ( isset( $this->messages[ $http_code ] ) ) {
				throw new Lcm_Error_Exception( sprintf( __( '%s. ', LC_MIGRATOR_PLUGIN_NAME ), $this->messages[ $http_code ], $http_code ) );
			} else {
				throw new Lcm_Error_Exception( sprintf( __( 'Error code: %s. ', LC_MIGRATOR_PLUGIN_NAME ), $http_code, $http_code ) );
			}
		}

		// Effective URL
		$this->set_effective_url( curl_getinfo( $this->handler, CURLINFO_EFFECTIVE_URL ) );

		// Content length
		$this->set_content_length( curl_getinfo( $this->handler, CURLINFO_CONTENT_LENGTH_DOWNLOAD ) );

		// HTTP headers
		if ( $this->get_option( CURLOPT_HEADER ) ) {
			return $this->http_parse_headers( $headers );
		}

		// JSON response
		if ( $parse_as_json ) {
			return json_decode( $response, true );
		}

		return $response;
	}

	/**
	 * Parse HTTP headers
	 *
	 * @param  string $headers HTTP headers
	 * @return array
	 * @throws Lcm_Error_Exception
	 */
	public function http_parse_headers( $headers ) {
		$headers = preg_split( '/(\r|\n)+/', $headers, -1, PREG_SPLIT_NO_EMPTY );

		$parse_headers = array();
		for ( $i = 1; $i < count( $headers ); $i++ ) {
			if ( strpos( $headers[ $i ], ':' ) !== false ) {
				list( $key, $raw_value ) = explode( ':', $headers[ $i ], 2 );

				$key   = strtolower( trim( $key ) );
				$value = trim( $raw_value );
				if ( array_key_exists( $key, $parse_headers ) ) {
					// See HTTP RFC Sec 4.2 Paragraph 5
					// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
					// If a header appears more than once, it must also be able to
					// be represented as a single header with a comma-separated
					// list of values.  We transform accordingly.
					$parse_headers[ $key ] .= ',' . $value;
				} else {
					$parse_headers[ $key ] = $value;
				}
			}
		}

		return $parse_headers;
	}

	/**
	 * Close cURL handler
	 *
	 * @return void
	 */
	public function close_connection() {
		if ( $this->handler !== null ) {
			curl_close( $this->handler );
		}

		$this->handler = null;
	}

	/**
	 * Destroy cURL handler
	 *
	 * @return void
	 */
	public function __destruct() {
		if ( $this->handler !== null ) {
			curl_close( $this->handler );
		}
	}
}
