<?php
/**
 * Class for functions shared by the Brute force protection feature and its related json-endpoints
 *
 * @package automattic/jetpack-waf
 */

namespace Automattic\Jetpack\Waf\Brute_Force_Protection;

use Automattic\Jetpack\IP\Utils as IP_Utils;
use Automattic\Jetpack\Waf\Waf_Rules_Manager;
use WP_Error;

/**
 * Shared Functions class.
 */
class Brute_Force_Protection_Shared_Functions {
	/**
	 * Returns an array of IP objects that will never be blocked by the Brute force protection feature
	 *
	 * @deprecated 0.11.0 Use format_allow_list()
	 */
	public static function format_whitelist() {
		_deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::format_allow_list' );
		return self::format_allow_list();
	}

	/**
	 * Returns an array of IP objects that will never be blocked by the Brute force protection feature
	 *
	 * The array is segmented into a local allow list which applies only to the current site
	 * and a global allow list which, for multisite installs, applies to the entire networko
	 *
	 * @return array
	 */
	public static function format_allow_list() {
		$local_allow_list = self::get_local_allow_list();
		$formatted        = array(
			'local' => array(),
		);
		foreach ( $local_allow_list as $item ) {
			if ( $item->range ) {
				$formatted['local'][] = $item->range_low . ' - ' . $item->range_high;
			} else {
				$formatted['local'][] = $item->ip_address;
			}
		}
		if ( is_multisite() && current_user_can( 'manage_network' ) ) {
			$formatted['global'] = array();
			$global_allow_list   = self::get_global_allow_list();
			if ( false === $global_allow_list ) {
				// If the global allow list has never been set, check for a legacy option set prior to 3.6.
				$global_allow_list = get_site_option( 'jetpack_protect_whitelist', array() );
			}
			foreach ( $global_allow_list as $item ) {
				if ( $item->range ) {
					$formatted['global'][] = $item->range_low . ' - ' . $item->range_high;
				} else {
					$formatted['global'][] = $item->ip_address;
				}
			}
		}
		return $formatted;
	}

	/**
	 * Gets the local Brute force protection allow list.
	 *
	 * @deprecated 0.11.0 Use get_local_allow_list()
	 */
	public static function get_local_whitelist() {
		_deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::get_local_allow_list' );
		return self::get_local_allow_list();
	}

	/**
	 * Gets the local Brute force protection allow list.
	 *
	 * The 'local' part of the allow list only really applies to multisite installs,
	 * which can have a network wide allow list, as well as a local list that applies
	 * only to the current site. On single site installs, there will only be a local
	 * allow list.
	 *
	 * @return array A list of IP Address objects or an empty array
	 */
	public static function get_local_allow_list() {
		$allow_list = get_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME );
		if ( false === $allow_list ) {
			// The local allow list has never been set.
			if ( is_multisite() ) {
				// On a multisite, we can check for a legacy site_option that existed prior to v 3.6, or default to an empty array.
				$allow_list = get_site_option( 'jetpack_protect_whitelist', array() );
			} else {
				// On a single site, we can just use an empty array.
				$allow_list = array();
			}
		} else {
			$allow_list = IP_Utils::get_ip_addresses_from_string( $allow_list );
			$allow_list = array_map(
				function ( $ip_address ) {
					return self::create_ip_object( $ip_address );
				},
				$allow_list
			);
		}
		return $allow_list;
	}

	/**
	 * Get the global, network-wide allow list.
	 *
	 * @deprecated 0.11.0 Use get_global_allow_list()
	 */
	public static function get_global_whitelist() {
		_deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::get_global_allow_list' );
		return self::get_global_allow_list();
	}

	/**
	 * Get the global, network-wide allow list.
	 *
	 * It will revert to the legacy site_option if jetpack_protect_global_whitelist has never been set.
	 *
	 * @return array
	 */
	public static function get_global_allow_list() {
		$allow_list = get_site_option( 'jetpack_protect_global_whitelist' );
		if ( false === $allow_list ) {
			// The global allow list has never been set. Check for legacy site_option, or default to an empty array.
			$allow_list = get_site_option( 'jetpack_protect_whitelist', array() );
		}
		return $allow_list;
	}

	/**
	 * Convert a string into an IP Address object.
	 *
	 * @param string $ip_address The IP Address to convert.
	 * @return object An IP Address object.
	 */
	private static function create_ip_object( $ip_address ) {
		// Hyphenated range notation.
		if ( strpos( $ip_address, '-' ) ) {
			$ip_range_parts = explode( '-', $ip_address );
			return (object) array(
				'range'      => true,
				'range_low'  => trim( $ip_range_parts[0] ),
				'range_high' => trim( $ip_range_parts[1] ),
			);
		}

		// CIDR notation.
		if ( strpos( $ip_address, '/' ) !== false ) {
			return (object) array(
				'range'      => true,
				'range_low'  => $ip_address,
				'range_high' => null,
			);
		}

		// Single IP Address.
		return (object) array(
			'range'      => false,
			'ip_address' => $ip_address,
		);
	}

	/**
	 * Save IP allow list.
	 *
	 * @deprecated 0.11.0 Use save_allow_list()
	 *
	 * @param mixed $allow_list IP allow list.
	 * @param bool  $global (default: false) Global.
	 */
	public static function save_whitelist( $allow_list, $global = false ) {
		_deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::save_allow_list' );
		return self::save_allow_list( $allow_list, $global );
	}

	/**
	 * Save IP allow list.
	 *
	 * @access public
	 * @param mixed $allow_list IP allow list.
	 * @param bool  $global (default: false) Global.
	 * @return bool
	 */
	public static function save_allow_list( $allow_list, $global = false ) {
		$allow_list_error = false;
		$new_items        = array();
		if ( ! is_array( $allow_list ) ) {
			return new WP_Error( 'invalid_parameters', __( 'Expecting an array', 'jetpack-waf' ) );
		}
		if ( $global && ! is_multisite() ) {
			return new WP_Error( 'invalid_parameters', __( 'Cannot use global flag on non-multisites', 'jetpack-waf' ) );
		}
		if ( $global && ! current_user_can( 'manage_network' ) ) {
			return new WP_Error( 'permission_denied', __( 'Only super admins can edit the global allow list', 'jetpack-waf' ) );
		}
		// Validate each item.
		foreach ( $allow_list as $item ) {
			$item = trim( $item );
			if ( empty( $item ) ) {
				continue;
			}
			$new_item = self::create_ip_object( $item );
			if ( $new_item->range ) {
				if ( ! filter_var( $new_item->range_low, FILTER_VALIDATE_IP ) || ! filter_var( $new_item->range_high, FILTER_VALIDATE_IP ) ) {
					$allow_list_error = true;
					break;
				}
				if ( ! IP_Utils::convert_ip_address( $new_item->range_low ) || ! IP_Utils::convert_ip_address( $new_item->range_high ) ) {
					$allow_list_error = true;
					break;
				}
			} else {
				if ( ! filter_var( $new_item->ip_address, FILTER_VALIDATE_IP ) ) {
					$allow_list_error = true;
					break;
				}
				if ( ! IP_Utils::convert_ip_address( $new_item->ip_address ) ) {
					$allow_list_error = true;
					break;
				}
			}
			$new_items[] = $new_item;
		} // End item loop.
		if ( ! empty( $allow_list_error ) ) {
			return new WP_Error( 'invalid_ip', __( 'One of your IP addresses was not valid.', 'jetpack-waf' ) );
		}
		if ( $global ) {
			update_site_option( 'jetpack_protect_global_whitelist', $new_items );
			// Once a user has saved their global allow list, we can permanently remove the legacy option.
			delete_site_option( 'jetpack_protect_whitelist' );
		} else {
			$new_items = array_map(
				function ( $item ) {
					if ( $item->range ) {
							return $item->range_low . '-' . $item->range_high;
					}
					return $item->ip_address;
				},
				$new_items
			);
			update_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME, implode( ' ', $new_items ) );
		}
		return true;
	}
}
