<?php
/**
 * Please see wp-cookie-allow.php for more details.
 *
 * @author $Vincent Weber <weepie-plugins@outlook.com>$
 */

use GeoIp2\Database\Reader;
use WpieFw\Helpers\WpieMiscHelper;
use WpieFw\Helpers\WpieAjaxHelper;
use WpieFw\Exceptions\WpieRuntimeException;
use WpieFw\Exceptions\WpieExceptionLogger;
use WpieFw\Exceptions\WpieExceptionInterface;
use WpieFw\Exceptions\WpieInvalidArgumentException;

if( ! defined( 'ABSPATH' ) ) exit;

if( !class_exists( 'WpcaModuleGeo' ) ) {
	/**
	 * WpcaGeo Class
	 *
	 * Module to handle the GEO functionality
	 *
	 * @author $Vincent Weber <weepie-plugins@outlook.com>$
	 *
	 * @see https://github.com/maxmind/GeoIP2-php
	 *
	 * @since 3.2.6
	 */
	class WpcaModuleGeo extends WpieFw\Modules\Iterator\WpieModule
	{
	    /**
	     * The file name template for the mmdb files
	     *
	     * @var string
	     *
	     * @since 3.2.11
	     */
		const TEMPL_MMDB_FILE_NAME = 'GeoLite2-%s.mmdb';

	    /**
	     * The file name for the database archive file
	     *
	     * @var string
	     *
	     * @since 3.2.13
	     */
	    const TEMPL_MMDB_ARCHIVE_FILE_NAME = 'databases.zip';

	    /**
	     * Allowed datbase keys
	     *
	     * @var array
	     *
	     * @since 3.2.11
	     */
	    const ALLOWED_MMDB = array( 'country', 'city' );

		/**
		 * Module priority
		 *
		 * Must be lower then WpcaCore (5)
		 *
		 * @var integer
		 *
		 * @since 3.2.6
		 */
		protected $priority = 2;

		/**
		 * The client IP address
		 *
		 * @var string$style_box_btn_align = $this->settingsProcessor->get('style')->get( 'style_box_btn_align' );
		 *
		 * @since 3.2.6
		 */
		private $clientIP = '0.0.0.0';

		/**
		 * The GeoIP db archive file path
		 *
		 * @var string
		 *
		 * @since 3.2.13
		 */
		private $geoIPDBarchiveFile = '';

		/**
		 * Flag if GeoIP db archive file is present
		 *
		 * @var boolean
		 *
		 * @since 3.2.13
		 */
		private $hasGeoIPDBarchive = false;

		/**
		 * Flag if is a local request
		 *
		 * @var string
		 *
		 * @since 3.2.11
		 */
		private $isLocalRequest = false;

		/**
		 * Container for the current mmdb reader instances
		 *
		 * @var array
		 *
		 * @since 3.2.11
		 */
		public static $reader = array();

		/**
		 * Container for the current mmdb record instances
		 *
		 * @var array
		 *
		 * @since 3.2.11
		 */
		public static $record = array();


		/**
		 * Constructor
		 *
		 * @since 3.2.6
		 */
		public function __construct() {}

		/**
		 * {@inheritDoc}
		 * @see \WpieFw\Modules\Iterator\WpieModule::start()
		 */
		public function start()
		{
			try {
				$this->geoIPDBarchiveFile = self::getDatabaseDir() . DIRECTORY_SEPARATOR . self::TEMPL_MMDB_ARCHIVE_FILE_NAME;
				$this->hasGeoIPDBarchive = file_exists( $this->geoIPDBarchiveFile );

				if( !WpieMiscHelper::isActivating() ) {
					$this->maybeExtractDatabase();
				}

				$this->clientIP = WpieMiscHelper::getClientIp();

				foreach ( self::ALLOWED_MMDB as $database ) {
				    if( !$this->hasDatabaseFiles( $database ) ) {
						$path = self::getDatabasePath( $database );

						throw new WpieRuntimeException( sprintf( __( 'the GEO database file (%s) could not be located. Please verify the existence at: %s or try to extract the %s file manually.', 'wpca' ),
							$database,
							self::getDatabaseDir(),
							self::TEMPL_MMDB_ARCHIVE_FILE_NAME ) );
				    } else {
				        // declare default state null for records
				        self::$reader[$database] = null;
				        self::$record[$database] = null;
				    }
				}

				if( !class_exists( 'GeoIp2\Database\Reader' ) ) {
					throw new WpieRuntimeException( __( 'the GeoIp2\Database\Reader class does not exist.', 'wpca' ) );
				}

				// Flag if the current request is from a local host or network
				$this->isLocalRequest = ( WpieMiscHelper::isLocalhost() || WpieMiscHelper::isLan() );

			} catch( WpieExceptionInterface $e ) {
				$this->notice( $e->getMessage(), 'error' );
				WpieExceptionLogger::log( $e );
			} catch( \Throwable $e ) {
				$this->notice( $e->getMessage(), 'error' );
				WpieExceptionLogger::log( $e );
			}
		}

		/**
		 * Get a Reader instance for given database key
		 *
		 * @param string $database
		 *
		 * @throws WpieInvalidArgumentException when the requested database is not allowed
		 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database is corrupt or invalid
		 *
		 * @return \GeoIp2\Database\Reader
		 */
		public static function getReader( $database = 'country' )
		{
		    if( !in_array( $database, self::ALLOWED_MMDB ) ) {
		        throw new WpieInvalidArgumentException( sprintf( __( 'the (%s) GEO database is not allowed. The following databases are allowed: %s.', 'wpca' ),
		            $database,
		            join( ',', self::ALLOWED_MMDB ) ) );
		    }

		    if ( empty( self::$reader ) || null === self::$reader[$database] ) {
		        // Instantiate the Reader for the given database
		        self::$reader[$database] = new Reader( self::getDatabasePath( $database ) );
		    }

		    return self::$reader[$database];
		}

		/**
		 * Singleton implementation to retrieve a mmdb record
		 *
		 * @param string the IP address
		 * @param string $database the database key
		 *
		 * @throws \MaxMind\Db\Reader\InvalidDatabaseException
		 * @throws \GeoIp2\Exception\AddressNotFoundException
		 *
		 * @uses GeoIp2\Database\Reader::country()
		 * @uses GeoIp2\Database\Reader::city()
		 *
		 * @since 3.2.11
		 *
		 * @return \GeoIp2\Model\country|\GeoIp2\Model\city
		 */
		public static function getRecordForAddress( $address = '', $database = 'country' )
		{
		    if ( empty( self::$record ) || null === self::$record[$database] ) {
		        // Query the Country database for clients IP-address
		        self::$record[$database] = self::getReader( $database )->{$database}( $address );
		    }

		    return self::$record[$database];
		}

		/**
		 * Determine if geo logic is possible
		 *
		 * @uses is_admin()
		 * @uses WpcaModuleGeo::isLocalRequest
		 * @uses apply_filters() to let others modify the return value with the wpca_geoip_do filter
		 *
		 * @since 3.2.11
		 *
		 * @return boolean
		 */
		public function isGeoLogicPossible()
		{
		    static $is = null;

		    if( null !== $is ) {
		        return $is;
		    }

			/** @var WpcaModuleCore $moduleCore */
			$moduleCore = $this->getModule( 'core' );

			// Rest api calls are not admin, there for the check on
			// WpcaModuleCore::doingRenderLayoutAjax()
		    $is = (
				( !is_admin() || $moduleCore->doingRenderLayoutAjax ) &&
				!$this->isLocalRequest
			);

		    /**
		     * Let others decide to or not to do the geo logic
		     *
		     * @var bool $is
		     *
		     * @since 1.2.7
		     * @since 3.2.11 replaced with arg with $is
		     */
		    $is = apply_filters( 'wpca_geoip_do', $is );

		    return $is;
		}

		/**
		 * Determine if IP address is from the EU
		 *
		 * For this purpose, the {@see \GeoIp2\Model\country::$isInEuropeanUnion} property is checked
		 *
		 * @uses self::getRecordForAddress()
		 *
		 * @since 3.2.6
		 *
		 * @return boolean
		 */
		public function isIpAddressFromEU()
		{
		    return self::getRecordForAddress( $this->clientIP )->country->isInEuropeanUnion;
		}

		/**
		 * Fetch the data for Country or City
		 *
		 * This method will only continue, if the request is not from an admin page
		 * and the geo logic is possible
		 *
		 * @param string $database choosing wich database to use. Default to country
		 *
		 * @uses self::getRecordForAddress()
		 *
		 * @since 3.2.6
		 * @since 3.2.11 changed scope to public
		 *
		 * @return \GeoIp2\Model\country|\GeoIp2\Model\city|boolean false when fetching a recored is not needed or wanted
		 */
		public function fetch( $database = 'country' )
		{
		    // only do this logic when possible
		    if( !$this->isGeoLogicPossible() ) {
		        return false;
		    }

		    return self::getRecordForAddress( $this->clientIP, $database );
		}

		/**
		 * Callback for the wpca_activate_plugin hook
		 *
		 * @access public
		 *
		 * @param bool $upgrading flag is plugin is being updated to a newer version
		 *
		 * @since 3.2.13
		 */
		public function activatePlugin( $upgrading )
		{
			$this->maybeExtractDatabase( true );
		}

		/**
		 * {@inheritDoc}
		 * @see \WpieFw\Modules\Iterator\WpieModuleInterface::init()
		 */
		public function init()
		{
			if( is_admin() && !WpieAjaxHelper::doingAjax() ) {
				add_action( 'wpca_activate_plugin', array( $this, 'activatePlugin' ) );
			}
		}

		/**
		 * The GeoIP databases directory
		 *
		 * @since 3.2.13
		 *
		 * @return string
		 */
		private static function getDatabaseDir()
		{
		    return dirname( __FILE__ ) . '/db';
		}

		/**
		 * Get a database file path for given database key
		 *
		 * @param string $database
		 *
		 * @since 3.2.11
		 *
		 * @return string
		 */
		private static function getDatabasePath( $database = '' )
		{
		    $filename = sprintf( self::TEMPL_MMDB_FILE_NAME, ucfirst( $database ) );

		    return self::getDatabaseDir() . DIRECTORY_SEPARATOR . $filename;
		}

		private function hasDatabaseFiles( $database = '' )
		{
			if( '' !== $database ) {
				return file_exists( self::getDatabasePath( $database ) );
			} else {
				$has = [];
				foreach ( self::ALLOWED_MMDB as $database ) {
					$has[$database] =  file_exists( self::getDatabasePath( $database ) );
				}

				return !in_array( false, $has, true );
			}
		}

		private function maybeExtractDatabase( $activating = false )
		{
			if( $this->hasGeoIPDBarchive) {
				if( class_exists( 'ZipArchive', false ) ) {
					// try to raise the memory limit
					wp_raise_memory_limit( 'admin' );

					$zip = new ZipArchive();
					$extracted = $zip->open( $this->geoIPDBarchiveFile );
					if (true === $extracted) {
						$zip->extractTo( self::getDatabaseDir() );
						$zip->close();
						// clean up
						wp_delete_file( $this->geoIPDBarchiveFile );
						$this->hasGeoIPDBarchive = false;
					  } else {
						throw new WpieRuntimeException(
							sprintf( __( 'an error occured extracting the geo database archive file : %s', 'wpca' ), $extracted )
						);
					  }
				} else {
					throw new WpieRuntimeException(
						sprintf( __( 'could not extract the geo databases archive. Please try to extract this file manually: %s', 'wpca' ),
						$this->geoIPDBarchiveFile )
					);
				}
			 }

			 if( $activating && !$this->hasGeoIPDBarchive && !$this->hasDatabaseFiles() ) {
				throw new WpieRuntimeException(	__( 'geo database archive file not found', 'wpca' ) );
			 }
		}
	}

	if( !function_exists( 'wpca_is_ip_from_eu' ) )
	{
		/**
		 * Determine if IP address is from the EU
		 *
		 * @uses WpcaGeo::isIpAddressFromEU()
		 *
		 * @since 3.2.6
		 *
		 * @return boolean
		 */
		function wpca_is_ip_from_eu()
		{
			if( false !== ( $module = \WpieFw\modules\WpieModuleProcessor::getModule( 'wpca', 'geo' ) ) )
			{
				$is = $module->isIpAddressFromEU();
				unset( $module );

				return $is;
			}
		}
	}
}