<?php
namespace Pushengage\Utils;
use Pushengage\Utils\Options;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Helpers {
	/**
	 * Returns Jed-formatted localization data. Added for backwards-compatibility.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $domain Translation domain.
	 * @return array          The information of the locale.
	 */
	public static function get_jed_locale_data( $domain ) {
		$translations = get_translations_for_domain( $domain );
		$translations2 = get_translations_for_domain( 'default' );

		$locale = array(
			'' => array(
				'domain' => $domain,
				'lang'   => is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(),
			),
		);

		if ( ! empty( $translations->headers['Plural-Forms'] ) ) {
			$locale['']['plural_forms'] = $translations->headers['Plural-Forms'];
		}

		foreach ( $translations->entries as $msgid => $entry ) {
			$locale[ $msgid ] = $entry->translations;
		}

		// If any of the translated strings incorrectly contains HTML line breaks, we need to return or else the admin is no longer accessible.
		$json = wp_json_encode( $locale );
		if ( preg_match( '/<br[\s\/\\\\]*>/', $json ) ) {
			return array();
		}

		return $locale;
	}

	/**
	 * Checks if a plugin is active
	 *
	 * @since 4.0.0
	 *
	 * @param  string  $basename The plugin basename.
	 * @return boolean Whether or not the plugin is active.
	 */
	public static function is_plugin_active( $basename ) {
		$active_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins' ) );
		return in_array( $basename, (array) $active_plugins, true );
	}

	/**
	 * Get the active caching plugin details
	 *
	 * @since 4.0.0
	 *
	 * @return array|null   The active active caching plugin details returns null if no caching is active
	 */
	public static function get_active_caching_plugin() {
		// Array of popular caching plugin slugs
		$caching_plugins = array(
			array(
				'title' => 'WP Rocket',
				'basename' => 'wp-rocket/wp-rocket.php',
			),
			array(
				'title' => 'W3 Super Cache',
				'basename' => 'wp-super-cache/wp-cache.php',
			),
			array(
				'title' => 'W3 Total Cache',
				'basename' => 'w3-total-cache/w3-total-cache.php',
			),
			array(
				'title' => 'Comet Cache',
				'basename' => 'comet-cache/comet-cache.php',
			),
			array(
				'title' => 'WP fastest Cache',
				'basename' => 'wp-fastest-cache/wpFastestCache.php',
			),
			array(
				'title' => 'Cache Enabler',
				'basename' => 'cache-enabler/cache-enabler.php',
			),
			array(
				'title' => 'Hyper Cache',
				'basename' => 'hyper-cache/plugin.php',
			),
			array(
				'title' => 'SiteGround Optimizer',
				'basename' => 'sg-cachepress/sg-cachepress.php',
			),
		);

		// Loop through each plugin and check if it's active
		$active_caching_plugin = null;
		foreach ( $caching_plugins as $plugin ) {
			if ( self::is_plugin_active( $plugin['basename'] ) ) {
				$active_caching_plugin = $plugin;
				break;
			}
		}
		return $active_caching_plugin;
	}

	/**
	 * Checks if the website is using SSL or not.
	 *
	 * Defers to WordPress core `is_ssl()` which inspects `HTTPS` /
	 * `SERVER_PORT` set by the actual web-server / SAPI. Proxy headers
	 * (`HTTP_CF_VISITOR`, `HTTP_X_FORWARDED_PROTO`) are deliberately NOT
	 * trusted here: they are attacker-spoofable on any deployment without
	 * a reverse proxy that strips them, and trusting them would let a
	 * client convince downstream code that an insecure request is secure.
	 *
	 * Site owners who run behind a reverse proxy that terminates TLS
	 * should either:
	 *  - Configure WordPress to honor `X-Forwarded-Proto` in `wp-config.php`
	 *    (set `$_SERVER['HTTPS']='on'` when the trusted proxy header is
	 *    present — the canonical approach), or
	 *  - Use the `pushengage_is_ssl` filter below to override the result
	 *    based on their own (trusted-proxy-aware) detection logic.
	 *
	 * @since 4.0.4.1
	 *
	 * @return boolean
	 */
	public static function is_ssl() {
		$is_ssl = function_exists( 'is_ssl' ) ? is_ssl() : false;

		/**
		 * Filter the PushEngage SSL-detection result.
		 *
		 * Use this to override the default detection when running behind
		 * a reverse proxy / CDN that terminates TLS, after asserting the
		 * request actually came through the trusted proxy.
		 *
		 * @since 4.2.6
		 *
		 * @param bool $is_ssl Whether the current request is over HTTPS.
		 */
		return (bool) apply_filters( 'pushengage_is_ssl', $is_ssl );
	}

	/**
	 * Returns whether the block editor is loading on the current screen.
	 *
	 * @since 4.0.5.1
	 *
	 * @return boolean|null True if the block editor is being loaded, false if
	 *                      not loading and null if can not determine.
	 */
	public static function is_block_editor() {
		$curr_screen = get_current_screen();
		if ( ! empty( $curr_screen ) && method_exists( $curr_screen, 'is_block_editor' ) ) {
			return $curr_screen->is_block_editor();
		}
		return null;
	}

	/**
	 * Determines if given URL is a valid HTTP or HTTPS URLs and length is
	 * less than or equal to max_len.
	 *
	 * @since 4.0.5.1
	 *
	 * @param string $url URL to check.
	 * @param number $max_len Maximum allowed length of the URL.
	 *
	 * @return bool false on failure.
	 */
	public static function is_http_or_https_url( $url, $max_len = 0 ) {
		if ( empty( $url ) || ! is_string( $url ) ) {
			return false;
		}

		if ( $max_len && strlen( $url ) > $max_len ) {
			return false;
		}

		// Reject any value containing control characters / whitespace
		// (including CR/LF) that could enable header or payload smuggling
		// when the URL is later forwarded to outbound HTTP / push payloads.
		if ( preg_match( '/[\s\x00-\x1F\x7F]/', $url ) ) {
			return false;
		}

		// Use wp_parse_url + explicit scheme/host check rather than
		// `filter_var(..., FILTER_VALIDATE_URL)` so we don't reject valid
		// internationalized URLs (Unicode paths, IDN-decoded hosts) that
		// the older loose prefix check accepted. We still close the
		// original vulnerability — non-URL strings like "httpsfoo" or
		// "https.evil.com" lack a `scheme://host` structure and so are
		// rejected by the parse below.
		$parsed = wp_parse_url( $url );
		if ( ! is_array( $parsed ) || empty( $parsed['scheme'] ) || empty( $parsed['host'] ) ) {
			return false;
		}

		// Reject URLs with embedded credentials (`user:pass@host`). The
		// rendered host in some clients displays the user component as
		// the apparent destination, which is a well-known phishing
		// primitive when subscribers click through push-notification
		// URLs.
		if ( ! empty( $parsed['user'] ) || isset( $parsed['pass'] ) ) {
			return false;
		}

		$scheme = strtolower( $parsed['scheme'] );
		return 'http' === $scheme || 'https' === $scheme;
	}



	/**
	 * Decodes JSON string as associative array.
	 *
	 * @param string $data
	 * @return mixed|null
	 */
	public static function json_decode( $data ) {
		$flag = 0;
		if ( defined( 'JSON_INVALID_UTF8_IGNORE' ) ) {
			// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_invalid_utf8_ignoreFound
			$flag = JSON_INVALID_UTF8_IGNORE | $flag;
		}
		return json_decode( $data, true, 512, $flag );
	}

	/**
	 * Convert HTML entities to their corresponding characters
	 *
	 * @since 4.0.6
	 * @param string The input string
	 * @return string The decoded string
	 */
	public static function decode_entities( $string ) {
		$flag = ENT_QUOTES;
		if ( defined( 'ENT_HTML401' ) ) {
			$flag = ENT_HTML401 | $flag;
		}
		return html_entity_decode( str_replace( array( '&apos;', '&#x27;', '&#39;', '&quot;' ), '\'', $string ), $flag, 'UTF-8' );
	}

	/**
	 * Retrieves the pe_post_options post meta field for the given post ID.
	 *
	 * @since 4.0.6
	 * @param int $post_id Post ID
	 * @return mixed The push options post meta
	 */
	public static function get_push_options_post_meta( $post_id ) {
		$push_options = array();
		$post_meta = get_post_meta( $post_id, 'pe_push_options', true );
		if ( ! empty( $post_meta ) ) {
			// Prior to 4.0.6 pe_push_options was stored as json string which was causing issues
			// with special characters decoding. Now we store the serialized array in metadata.
			// So we need to check if the value is a string or array.
			if ( is_string( $post_meta ) ) {
				$push_options = Helpers::json_decode( $post_meta );
			} else {
				$push_options = $post_meta;
			}
		}

		return $push_options;
	}

	/**
	* Get image from post
	*
	* @since 4.0.8
	*
	* @param string|int[] $size
	* @param number $post_id
	*
	* @return string
	*/
	public static function get_post_image( $size, $post_id ) {
		$image_url = '';

		if ( has_post_thumbnail( $post_id ) ) {
			$raw_image = wp_get_attachment_image_src( get_post_thumbnail_id( $post_id ), $size );
			if ( ! empty( $raw_image ) ) {
				$image_url = ! empty( $raw_image[0] ) ? $raw_image[0] : '';
			}
		}

		return $image_url;
	}

	/**
	 * Check if WooCommerce integration settings enabled.
	 *
	 * @since 4.0.9
	 * @return boolean
	 */
	public static function is_woocommerce_integrated() {
		$settings = Options::get_site_settings();

		if ( empty( $settings['woo_integration'] ) ) {
			return false;
		}

		$integration_settings = $settings['woo_integration'];

		return (
			! empty( $integration_settings['cart_abandonment']['enable'] ) &&
			! empty( $integration_settings['cart_abandonment']['name'] )
		) || (
			! empty( $integration_settings['browse_abandonment']['enable'] ) &&
			! empty( $integration_settings['browse_abandonment']['name'] )
		);
	}

	/**
	 * Function to recursively flatten an array
	 *
	 * @since 4.1.1.1
	 *
	 * @param array $array The array to flatten
	 * @return array The flattened array
	 */
	public static function flatten_array( $array ) {
		$result = array();
		foreach ( $array as $element ) {
			if ( is_array( $element ) ) {
				$result = array_merge( $result, self::flatten_array( $element ) );
			} else {
				$result[] = $element;
			}
		}
		return $result;
	}

	/**
	 * Get the site's timezone string with backward compatibility for older WordPress versions.
	 *
	 * In WordPress 5.3.0 and later, wp_timezone_string() returns a valid
	 * timezone identifier (e.g., "Asia/Kathmandu") based on the site's settings.
	 *
	 * For older versions of WordPress ( < 5.3 ), this helper replicates the same behavior:
	 *   1. If the "timezone_string" option is set, it is returned as-is.
	 *   2. If only "gmt_offset" is set, it is converted into a valid timezone
	 *      string in the format "UTC+05:45" or "UTC-03:00".
	 *   3. If no timezone or offset is set, it defaults to "UTC".
	 *
	 * This ensures consistent timezone handling across all supported WordPress versions.
	 *
	 * @since 4.2.0
	 *
	 * @return string Timezone string compatible with PHP DateTimeZone.
	 */
	public static function get_wp_timezone_string() {
		if ( function_exists( 'wp_timezone_string' ) ) {
			return wp_timezone_string();
		}

		$timezone_string = get_option( 'timezone_string' );
		if ( ! empty( $timezone_string ) ) {
			return $timezone_string;
		}

		$offset = (float) get_option( 'gmt_offset' );

		if ( empty( $offset ) && '0' !== $offset ) {
			return 'UTC';
		}

		$hours   = (int) $offset;
		$minutes = ( $offset - $hours ) * 60;

		$sign        = ( 0 > $offset ) ? '-' : '+';
		$abs_hours   = abs( $hours );
		$abs_minutes = abs( (int) $minutes );

		return sprintf( '%s%02d:%02d', $sign, $abs_hours, $abs_minutes );
	}

	/**
	 * Check whether the given user (or current user) is allowed to see the
	 * PushEngage post-editor metabox and pre-publish checklist.
	 *
	 * Admins (`manage_options`) always pass. Other users must:
	 *   - have `edit_posts`, AND
	 *   - have at least one role listed in `auto_push_allowed_roles`.
	 *
	 * Send-time enforcement remains `publish_posts` (see Core::is_sending_instant_post).
	 *
	 * @since 4.2.5
	 *
	 * @param int|null $user_id Optional user ID. Defaults to current user.
	 * @return bool
	 */
	public static function user_can_access_post_editor_metabox( $user_id = null ) {
		$user_id = $user_id ? (int) $user_id : get_current_user_id();
		if ( ! $user_id ) {
			return false;
		}
		if ( user_can( $user_id, 'manage_options' ) ) {
			return true;
		}
		if ( ! user_can( $user_id, 'edit_posts' ) ) {
			return false;
		}
		$allowed = Options::get_auto_push_allowed_roles();
		if ( empty( $allowed ) ) {
			return false;
		}
		$user  = get_userdata( $user_id );
		$roles = $user ? (array) $user->roles : array();
		return (bool) array_intersect( $roles, $allowed );
	}
}
