<?php

/**
 * All helper functions used with Leaky Paywall
 *
 * @package Leaky Paywall
 * @since 1.0.0
 */

if (!function_exists('get_leaky_paywall_settings')) {

	/**
	 * Helper function to get Leaky Paywall settings for current site
	 *
	 * @since 1.0.0
	 *
	 * @return mixed Value set for the Leaky paywall settings.
	 */
	function get_leaky_paywall_settings()
	{
		$settings = new Leaky_Paywall_Settings();
		return $settings->get_settings();
	}
}

if (!function_exists('update_leaky_paywall_settings')) {

	/**
	 * Helper function to save zeen101's Leaky Paywall settings for current site
	 *
	 * @since 1.0.0
	 *
	 * @param array $settings The settings array.
	 * @return mixed Value set for the issuem options.
	 */
	function update_leaky_paywall_settings($settings)
	{

		$lp_settings = new Leaky_Paywall_Settings();
		$lp_settings->update_settings($settings);
	}
}

if (!function_exists('is_multisite_premium')) {
	/**
	 * Check if multisite
	 */
	function is_multisite_premium()
	{
		if (is_multisite()) {
			return true;
		}
		return false;
	}
}



if (!function_exists('is_level_deleted')) {
	/**
	 * Check if a level is deleted
	 *
	 * @param integer $level_id The level id.
	 * @return bool
	 */
	function is_level_deleted($level_id)
	{

		$level = get_leaky_paywall_subscription_level($level_id);

		if (isset($level['deleted']) && $level['deleted'] > 0) {
			return true;
		}

		return false;
	}
}

function is_level_hidden($level)
{
	if (isset($level['hide_registration_form']) && $level['hide_registration_form'] == 'on') {
		return true;
	}
	return false;
}

if (!function_exists('get_leaky_paywall_subscribers_site_id_by_subscriber_id')) {
	/**
	 * Get subscriber's site id by their subscriber id
	 *
	 * @param string $subscriber_id The subscriber's subscriber id.
	 * @param string $mode The payment mode.
	 * @return string The site id
	 */
	function get_leaky_paywall_subscribers_site_id_by_subscriber_id($subscriber_id, $mode = false)
	{
		$site_id = '';
		if (empty($mode)) {
			$settings = get_leaky_paywall_settings();
			$mode     = leaky_paywall_get_current_mode();
		}

		if (is_multisite_premium()) {
			global $wpdb;
			$results = $wpdb->get_col(
				$wpdb->prepare(
					"
					SELECT      $wpdb->usermeta.meta_key
					FROM        $wpdb->usermeta
					WHERE       $wpdb->usermeta.meta_key LIKE %s
					            AND $wpdb->usermeta.meta_value = %s
					",
					'_issuem_leaky_paywall_' . $mode . '_subscriber_id%',
					$subscriber_id
				)
			);
			if (!empty($results)) {
				foreach ($results as $result) {
					if (preg_match('/_issuem_leaky_paywall_' . $mode . '_subscriber_id(_(.+))/', $result, $matches)) {
						return $matches[2]; // should be the site ID that matches for this subscriber_id.
					}
				}
			}
		}

		return $site_id;
	}
}

if (!function_exists('get_leaky_paywall_subscribers_site_id_by_subscriber_email')) {
	/**
	 * Get subscriber's site id by their email
	 *
	 * @param string $subscriber_email The subscriber's email.
	 * @param string $mode The payment mode.
	 * @return string The site id
	 */
	function get_leaky_paywall_subscribers_site_id_by_subscriber_email($subscriber_email, $mode = false)
	{
		$site_id = '';
		if (empty($mode)) {
			$settings = get_leaky_paywall_settings();
			$mode     = 'off' === $settings['test_mode'] ? 'live' : 'test';
		}

		if (is_multisite_premium()) {
			global $wpdb;
			$results = $wpdb->get_col(
				$wpdb->prepare(
					"
					SELECT      $wpdb->usermeta.meta_key
					FROM        $wpdb->usermeta
					WHERE       $wpdb->usermeta.meta_key LIKE %s
					            AND $wpdb->usermeta.meta_value = %s
					",
					'_issuem_leaky_paywall_' . $mode . '_subscriber_email%',
					$subscriber_email
				)
			);
			if (!empty($results)) {
				foreach ($results as $result) {
					if (preg_match('/_issuem_leaky_paywall_' . $mode . '_subscriber_email(_(.+))/', $result, $matches)) {
						return $matches[2]; // should be the site ID that matches for this subscriber_id.
					}
				}
			}
		}

		return $site_id;
	}
}

if (!function_exists('get_leaky_paywall_subscriber_by_subscriber_id')) {

	/**
	 * Get a subscriber by subscriber id
	 *
	 * @param string  $subscriber_id The subscriber id.
	 * @param string  $mode The payment mode.
	 * @param integer $blog_id The blog id.
	 * @return object The subscriber
	 */
	function get_leaky_paywall_subscriber_by_subscriber_id($subscriber_id, $mode = false, $blog_id = false)
	{
		$site = '';

		if (empty($mode)) {
			$settings = get_leaky_paywall_settings();
			$mode     = 'off' === $settings['test_mode'] ? 'live' : 'test';
		}

		if (is_multisite_premium()) {
			if (empty($blog_id)) {
				$blog_id = get_leaky_paywall_subscribers_site_id_by_subscriber_id($subscriber_id);
				if ($blog_id) {
					$site = '_' . $blog_id;
				}
			} else {
				$site = '_' . $blog_id;
			}
		}

		$args  = array(
			'meta_key'   => '_issuem_leaky_paywall_' . $mode . '_subscriber_id' . $site,
			'meta_value' => $subscriber_id,
		);
		$users = get_users($args);

		if (!empty($users)) {
			foreach ($users as $user) {
				return $user;
			}
		}

		return false;
	}
}

if (!function_exists('get_leaky_paywall_subscriber_by_subscriber_email')) {

	function get_leaky_paywall_subscriber_by_subscriber_email($subscriber_email, $mode = false, $blog_id = false)
	{
		$site = '';

		if (is_email($subscriber_email)) {
			if (empty($mode)) {
				$settings = get_leaky_paywall_settings();
				$mode     = 'off' === $settings['test_mode'] ? 'live' : 'test';
			}

			if (is_multisite_premium()) {
				if (empty($blog_id)) {
					$blog_id = get_leaky_paywall_subscribers_site_id_by_subscriber_email($subscriber_email);
					if ($blog_id) {
						$site = '_' . $blog_id;
					}
				} else {
					$site = '_' . $blog_id;
				}
			}

			$args  = array(
				'meta_key'   => '_issuem_leaky_paywall_' . $mode . '_subscriber_email' . $site,
				'meta_value' => $subscriber_email,
			);
			$users = get_users($args);

			if (!empty($users)) {
				foreach ($users as $user) {
					return $user;
				}
			}
		}

		return false;
	}
}

if (!function_exists('add_leaky_paywall_login_hash')) {

	/**
	 * Adds unique hash to login table for user's login link
	 *
	 * @since 1.0.0
	 *
	 * @param string $email address of user "logging" in.
	 * @param string $hash of user "logging" in.
	 * @return mixed $wpdb insert ID or false
	 */
	function add_leaky_paywall_login_hash($email, $hash)
	{

		$expiration = apply_filters('leaky_paywall_login_link_expiration', 60 * 60); // 1 hour.
		set_transient('_lpl_' . $hash, $email, $expiration);
	}
}

if (!function_exists('is_leaky_paywall_login_hash_unique')) {

	/**
	 * Verifies hash is valid for login link
	 *
	 * @since 1.0.0
	 *
	 * @param string $hash of user "logging" in.
	 * @return mixed $wpdb var or false
	 */
	function is_leaky_paywall_login_hash_unique($hash)
	{

		if (preg_match('#^[0-9a-f]{32}$#i', $hash)) { // verify we get a valid 32 character md5 hash.

			return !(false !== get_transient('_lpl_' . $hash));
		}

		return false;
	}
}

if (!function_exists('verify_leaky_paywall_login_hash')) {

	/**
	 * Verifies hash is valid length and hasn't expired
	 *
	 * @since 1.0.0
	 *
	 * @param string $hash of user "logging" in.
	 * @return mixed $wpdb var or false
	 */
	function verify_leaky_paywall_login_hash($hash)
	{

		if (preg_match('#^[0-9a-f]{32}$#i', $hash)) { // verify we get a valid 32 character md5 hash.

			return (bool) get_transient('_lpl_' . $hash);
		}

		return false;
	}
}

if (!function_exists('get_leaky_paywall_email_from_login_hash')) {

	/**
	 * Gets logging in user's email address from login link's hash
	 *
	 * @since 1.0.0
	 *
	 * @param string $hash of user "logging" in.
	 * @return string email from $wpdb or false if invalid hash or expired link
	 */
	function get_leaky_paywall_email_from_login_hash($hash)
	{

		if (preg_match('#^[0-9a-f]{32}$#i', $hash)) { // verify we get a valid 32 character md5 hash.
			return get_transient('_lpl_' . $hash);
		}

		return false;
	}
}


/**
 * Returns the list of payment statuses that grant content access.
 *
 * @since 4.23.0
 * @return array
 */
function leaky_paywall_access_statuses() {
	return apply_filters( 'leaky_paywall_access_statuses', array( 'active', 'pending_cancel', 'trial', 'past_due' ) );
}

/**
 * Set a subscriber's payment status and fire transition hooks.
 *
 * All status changes should go through this function so that
 * plugins and integrations can react via the action hooks.
 *
 * Fires:
 *  - leaky_paywall_status_transition( $new_status, $old_status, $user_id )           — on every change
 *  - leaky_paywall_status_{$old_status}_to_{$new_status}( $user_id )                 — specific transition
 *  - leaky_paywall_status_gained_access( $new_status, $user_id )                     — when moving to an access status
 *  - leaky_paywall_status_lost_access( $new_status, $old_status, $user_id )          — when losing access
 *
 * @since 4.23.0
 *
 * @param int    $user_id    WordPress user ID.
 * @param string $new_status The new payment status.
 * @param string $source     Optional. What triggered the change (e.g. 'stripe_webhook', 'admin', 'cron').
 * @return bool  True if status was changed, false if it was already the same.
 */
function leaky_paywall_set_subscriber_status( $user_id, $new_status, $source = '' ) {

	$mode = leaky_paywall_get_current_mode();
	$site = leaky_paywall_get_current_site();
	$meta_key = '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site;

	$old_status = get_user_meta( $user_id, $meta_key, true );

	/**
	 * Filter the target status before it is written.
	 *
	 * Lets extensions veto or remap a transition before it happens — e.g. the
	 * Double Opt In extension forces access-granting transitions to "pending"
	 * until the user has verified their email. Returning the same value as
	 * $old_status will result in a no-op.
	 *
	 * @since 5.0.x
	 *
	 * @param string $new_status The status the caller is trying to set.
	 * @param string $old_status The current status on the user.
	 * @param int    $user_id    WordPress user ID.
	 * @param string $source     What triggered the change (e.g. 'stripe_webhook').
	 */
	$new_status = apply_filters( 'leaky_paywall_target_subscriber_status', $new_status, $old_status, $user_id, $source );

	// No change — skip hooks and DB write.
	if ( $old_status === $new_status ) {
		return false;
	}

	update_user_meta( $user_id, $meta_key, $new_status );

	/**
	 * Fires on every status change.
	 *
	 * @param string $new_status The new payment status.
	 * @param string $old_status The previous payment status.
	 * @param int    $user_id    WordPress user ID.
	 * @param string $source     What triggered the change.
	 */
	do_action( 'leaky_paywall_status_transition', $new_status, $old_status, $user_id, $source );

	/**
	 * Fires for a specific transition, e.g. leaky_paywall_status_active_to_expired.
	 *
	 * @param int    $user_id WordPress user ID.
	 * @param string $source  What triggered the change.
	 */
	if ( $old_status ) {
		do_action( 'leaky_paywall_status_' . $old_status . '_to_' . $new_status, $user_id, $source );
	}

	$access_statuses = leaky_paywall_access_statuses();
	$had_access      = in_array( $old_status, $access_statuses, true );
	$has_access      = in_array( $new_status, $access_statuses, true );

	if ( ! $had_access && $has_access ) {
		/**
		 * Fires when a subscriber gains access.
		 *
		 * @param string $new_status The new payment status.
		 * @param int    $user_id    WordPress user ID.
		 * @param string $source     What triggered the change.
		 */
		do_action( 'leaky_paywall_status_gained_access', $new_status, $user_id, $source );
	}

	if ( $had_access && ! $has_access ) {
		/**
		 * Fires when a subscriber loses access.
		 *
		 * @param string $new_status The new payment status.
		 * @param string $old_status The previous payment status.
		 * @param int    $user_id    WordPress user ID.
		 * @param string $source     What triggered the change.
		 */
		do_action( 'leaky_paywall_status_lost_access', $new_status, $old_status, $user_id, $source );
	}

	return true;
}

/**
 * Set a subscriber's level ID through a central function that fires transition hooks.
 *
 * @since 4.23.0
 *
 * @param int    $user_id      WordPress user ID.
 * @param string $new_level_id The new level ID.
 * @param string $source       Optional. What triggered the change (e.g. 'stripe_webhook', 'admin', 'registration').
 * @return bool  True if level was changed, false if it was already the same.
 */
function leaky_paywall_set_subscriber_level( $user_id, $new_level_id, $source = '' ) {

	$mode     = leaky_paywall_get_current_mode();
	$site     = leaky_paywall_get_current_site();
	$meta_key = '_issuem_leaky_paywall_' . $mode . '_level_id' . $site;

	$old_level_id = get_user_meta( $user_id, $meta_key, true );

	// No change — skip hooks and DB write.
	if ( (string) $old_level_id === (string) $new_level_id ) {
		return false;
	}

	update_user_meta( $user_id, $meta_key, $new_level_id );

	/**
	 * Fires on every level change.
	 *
	 * @since 4.23.0
	 *
	 * @param string $new_level_id The new level ID.
	 * @param string $old_level_id The previous level ID (empty string for new subscribers).
	 * @param int    $user_id      WordPress user ID.
	 * @param string $source       What triggered the change.
	 */
	do_action( 'leaky_paywall_level_transition', $new_level_id, $old_level_id, $user_id, $source );

	/**
	 * Fires for a specific level transition, e.g. leaky_paywall_level_0_to_1.
	 *
	 * @since 4.23.0
	 *
	 * @param int    $user_id WordPress user ID.
	 * @param string $source  What triggered the change.
	 */
	if ( '' !== $old_level_id ) {
		do_action( 'leaky_paywall_level_' . $old_level_id . '_to_' . $new_level_id, $user_id, $source );
	}

	return true;
}

if (!function_exists('leaky_paywall_user_has_access')) {

	/**
	 * Determine if a user has access based on their payment status.
	 *
	 * Status is the single source of truth for access. The daily expiration
	 * cron and gateway webhooks are responsible for transitioning statuses
	 * (e.g. active → expired) when a subscription period ends.
	 *
	 * @since 4.9.3
	 *
	 * @param object $user object from WordPress database.
	 * @return bool true if the user has access based on their payment status.
	 */
	function leaky_paywall_user_has_access($user = null)
	{
		$explicit_user = null !== $user;

		if (null === $user) {
			$user = wp_get_current_user();
		}

		if (!is_object($user)) {
			return false;
		}

		$mode           = leaky_paywall_get_current_mode();
		$site           = leaky_paywall_get_current_site();
		$payment_status = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site, true);

		$has_access = in_array( $payment_status, leaky_paywall_access_statuses(), true );

		// Only apply the logged-in check when checking the current visitor.
		// When a user object is explicitly provided (e.g. REST API lookup),
		// skip this — the caller already identified the user.
		if ( ! $explicit_user && ! is_user_logged_in() ) {
			$has_access = false;
		}

		if (leaky_paywall_user_can_bypass_paywall_by_role($user)) {
			$has_access = true;
		}

		return apply_filters('leaky_paywall_user_has_access', $has_access, $user);
	}
}

/**
 * Determine if a user already has an active subscription at the given level.
 *
 * Used to prevent duplicate subscription creation when a logged-in subscriber
 * tries to check out for a level they already have an active subscription for.
 *
 * @since 5.0.7
 *
 * @param WP_User|int|null $user     User object or ID. Defaults to current user.
 * @param int              $level_id The level ID to check against.
 * @return bool True if the user has an active subscription at this level.
 */
function leaky_paywall_user_has_active_subscription_at_level( $user, $level_id ) {
	if ( null === $user ) {
		$user = wp_get_current_user();
	} elseif ( is_numeric( $user ) ) {
		$user = get_userdata( $user );
	}

	if ( ! ( $user instanceof WP_User ) || ! $user->ID ) {
		return false;
	}

	$level_id = intval( $level_id );

	if ( $level_id < 0 ) {
		return false;
	}

	$mode = leaky_paywall_get_current_mode();
	$site = leaky_paywall_get_current_site();

	$current_level_id = get_user_meta( $user->ID, '_issuem_leaky_paywall_' . $mode . '_level_id' . $site, true );

	if ( '' === $current_level_id || intval( $current_level_id ) !== $level_id ) {
		return false;
	}

	// Non-recurring levels require manual re-registration to renew, so an active
	// subscription at this level is not a duplicate — it just means they are
	// within their current paid term and may want to extend it.
	$level = get_leaky_paywall_subscription_level( $level_id );

	if ( empty( $level['recurring'] ) || 'on' !== $level['recurring'] ) {
		return false;
	}

	$payment_status = get_user_meta( $user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site, true );

	$has_active = in_array( $payment_status, leaky_paywall_access_statuses(), true );

	/**
	 * Filter whether a user has an active subscription at a given level.
	 *
	 * Allows publishers to override the duplicate-subscription check, e.g. to
	 * allow certain users to create multiple subscriptions at the same level.
	 *
	 * @since 5.0.7
	 *
	 * @param bool    $has_active Whether the user has an active subscription at this level.
	 * @param WP_User $user       The user being checked.
	 * @param int     $level_id   The level ID being checked.
	 */
	return apply_filters( 'leaky_paywall_user_has_active_subscription_at_level', $has_active, $user, $level_id );
}

/**
 * Determine if a user has access based on their user role.
 *
 * @since 4.14.5
 *
 * @param object $user User object from WordPress database.
 * @return bool true if the user has access based on their role or false they do not
 */
function leaky_paywall_user_can_bypass_paywall_by_role($user)
{

	$settings   = get_leaky_paywall_settings();
	$roles      = (array) $user->roles;
	$can_bypass = false;

	foreach ($roles as $role) {

		if (in_array($role, $settings['bypass_paywall_restrictions'], true)) {
			$can_bypass = true;
		}
	}

	return $can_bypass;
}

/**
 * Get the current Leaky Paywall mode setting.  Lives in the Payments tab
 *
 * @return string live or test
 */
function leaky_paywall_get_current_mode()
{
	$settings = get_leaky_paywall_settings();
	$mode     = 'off' === $settings['test_mode'] ? 'live' : 'test';

	return apply_filters('leaky_paywall_current_mode', $mode);
}

/**
 * Get the current Leaky Paywall site id, if multisite
 *
 * @return string the id of the site
 */
function leaky_paywall_get_current_site()
{
	if (is_multisite_premium() && !is_main_site()) {
		$site = '_' . get_current_blog_id();
	} else {
		$site = '';
	}

	return apply_filters('leaky_paywall_current_site', $site);
}


if (!function_exists('leaky_paywall_get_currency')) {

	/**
	 * Get the currency value set in the Leaky Paywall settings
	 *
	 * @since 4.9.3
	 *
	 * @return string Currency code (i.e USD)
	 */
	function leaky_paywall_get_currency()
	{
		$settings = get_leaky_paywall_settings();
		$currency = $settings['leaky_paywall_currency'];

		return apply_filters('leaky_paywall_currency', $currency);
	}
}


/**
 * Verified if user has paid through Stripe
 *
 * @since 1.0.0
 *
 * @param string  $email address of user "logged" in.
 * @param integer $blog_id The blog id.
 * @return mixed Expiration date or subscriptions status or false if not paid
 */
function leaky_paywall_has_user_paid($email = false, $blog_id = null)
{

	$settings = get_leaky_paywall_settings();
	$paid     = false;
	$canceled = false;
	$expired  = false;
	$sites    = array(''); // Empty String for non-Multisite, so we cycle through "sites" one time with no $site set.
	$mode     = leaky_paywall_get_current_mode();
	if (empty($email)) {
		$user = wp_get_current_user();
		if (0 === $user->ID) { // no user.
			return false;
		}
	} else {
		if (is_email($email)) {
			$user = get_user_by('email', $email);

			if (!$user) { // no user found with that email address.
				return false;
			}
		} else {
			return false;
		}
	}

	if (is_multisite_premium()) {
		if (is_null($blog_id)) {
			global $blog_id;
			if (!is_main_site($blog_id)) {
				$sites = array('_all', '_' . $blog_id);
			} else {
				$sites = array('_all', '_' . $blog_id, '');
			}
		} elseif (is_int($blog_id)) {
			$sites = array('_' . $blog_id);
		} elseif (empty($blog_id)) {
			$sites = array('');
		} else {
			$sites = array($blog_id);
		}
	}

	foreach ($sites as $site) {

		$subscriber_id   = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_subscriber_id' . $site, true);
		$expires         = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_expires' . $site, true);
		$payment_gateway = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_gateway' . $site, true);
		$payment_status  = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site, true);
		$plan            = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_plan' . $site, true);

		if ('stripe' !== $payment_gateway) {

			if ('paypal_standard' === $payment_gateway || 'paypal-standard' === $payment_gateway) {
				if (!empty($plan) && in_array($payment_status, array('active', 'pending_cancel'), true)) {
					return 'subscription';
				}
			}

			switch ($payment_status) {

				case 'Active':
				case 'active':
				case 'pending_cancel':
				case 'trial':
					$expires = apply_filters('leaky_paywall_has_user_paid_expires', $expires, $payment_gateway, $payment_status, $subscriber_id, $plan, $expires, $user, $mode, $site);
					if (empty($expires) || '0000-00-00 00:00:00' === $expires) {
						return 'unlimited';
					}
					$paid = true;
					break;
				case 'refunded':
				case 'refund':
				case 'cancelled':
				case 'canceled':
				case 'reversed':
				case 'buyer_complaint':
				case 'denied':
				case 'expired':
				case 'failed':
				case 'voided':
				case 'deactivated':
				case 'suspended':
					break;
			}
		} else {

			// check with Stripe to make sure the user has an active subscription.

			$stripe = leaky_paywall_initialize_stripe_api();

			try {
				if (empty($subscriber_id)) {
					switch ($payment_status) {
						case 'Active':
						case 'active':
						case 'pending_cancel':
						case 'trial':
							if (empty($expires) || '0000-00-00 00:00:00' === $expires) {
								return 'unlimited';
							}
							$paid = true;
							break;
						case 'refunded':
						case 'refund':
						case 'cancelled':
						case 'canceled':
						case 'reversed':
						case 'buyer_complaint':
						case 'denied':
						case 'expired':
						case 'failed':
						case 'voided':
						case 'deactivated':
						case 'suspended':
							break;
					}
				} else {
					$cu = $stripe->customers->retrieve($subscriber_id, [], leaky_paywall_get_stripe_connect_params());

					if (!empty($cu)) {
						if (!empty($cu->deleted) && true === $cu->deleted) {
							$canceled = true;
						}
					}

					if (!empty($plan)) {
						if (isset($cu->subscriptions)) {
							$subscriptions = $cu->subscriptions->all(array('limit' => '1'));
							foreach ($subscriptions->data as $subscription) {
								if (leaky_paywall_is_valid_stripe_subscription($subscription)) {
									return 'subscription';
								}
							}
						}
					}

					$ch = $stripe->charges->all(
						array(
							'count'    => 1,
							'customer' => $subscriber_id,
						)
					);

					if (empty($expires) || '0000-00-00 00:00:00' === $expires) {
						return 'unlimited';
					} else {
						if (strtotime($expires) < time()) {
							if (true === $ch->data[0]->paid && false === $ch->data[0]->refunded) {
								$expired = $expires;
							}
						} else {
							$paid = true;
						}
					}
				}
			} catch (\Throwable $th) {
				/* Translators: %s - error message */
				$results = '<h1>' . sprintf(__('Error processing request: %s', 'leaky-paywall'), $th->getMessage()) . '</h1>';
			}
		}
	} // end foreach

	if (is_bool($canceled) && $canceled) {
		$paid = false;
	}

	if (is_bool($expired) && $expired) {
		$paid = false;
	}

	return apply_filters('leaky_paywall_has_user_paid', $paid, $payment_gateway, $payment_status, $subscriber_id, $plan, $expires, $user, $mode, $site);
}


if (!function_exists('leaky_paywall_set_expiration_date')) {

	/**
	 * Set a user's expiration data
	 *
	 * @param  int   $user_id the user id.
	 * @param  array $data    information about the subscription.
	 */
	function leaky_paywall_set_expiration_date($user_id, $data)
	{

		if (empty($user_id)) {
			return;
		}

		$expires  = '0000-00-00 00:00:00'; // default to never expire.
		$settings = get_leaky_paywall_settings();
		$mode     = leaky_paywall_get_current_mode();
		$site     = leaky_paywall_get_current_site();

		if (isset($data['expires']) && $data['expires']) {
			$expires = $data['expires'];
		} elseif (!empty($data['interval']) && isset($data['interval_count']) && 1 <= $data['interval_count']) {
			$expires = date_i18n('Y-m-d 23:59:59', strtotime('+' . $data['interval_count'] . ' ' . $data['interval'])); // we're generous, give them the whole day!
		}

		if ('on' === $settings['add_expiration_dates']) {

			$current_expires = get_user_meta($user_id, '_issuem_leaky_paywall_' . $mode . '_expires' . $site, true);

			if ($current_expires) {
				// if they already have an expiration date and aren't expired, add on to their current expiration date.
				if (strtotime($current_expires) > time()) {
					$expires = date_i18n('Y-m-d 23:59:59', strtotime($current_expires . ' +' . $data['interval_count'] . ' ' . $data['interval']));
				}
			}
		}

		update_user_meta($user_id, '_issuem_leaky_paywall_' . $mode . '_expires' . $site, apply_filters('leaky_paywall_set_expiration_date', $expires, $data, $user_id));
	}
}

if (!function_exists('leaky_paywall_new_subscriber')) {

	/**
	 * Adds new subscriber to subscriber table
	 *
	 * @since 1.0.0
	 *
	 * @param deprecated $hash No longer used.
	 * @param string     $email address of user "logged" in.
	 * @param int        $customer_id The customer id.
	 * @param array      $meta_args Arguments passed from type of subscriber.
	 * @param string     $login optional login name to use instead of email address.
	 * @return mixed $wpdb insert ID or false
	 */
	function leaky_paywall_new_subscriber(string $hash = null, $email, $customer_id, $meta_args, $login = '')
	{

		if (!is_email($email)) {
			return false;
		}

		if (apply_filters('leaky_paywall_new_subscriber_abort', false, $email, $meta_args)) {
			return false;
		}

		$settings = get_leaky_paywall_settings();
		$mode     = leaky_paywall_get_current_mode();
		$site     = leaky_paywall_get_current_site();
		$user     = get_user_by('email', $email);

		if ($user) {
			// the user already exists.
			// grab the ID for later.
			$user_id  = $user->ID;
			$userdata = get_user_by('id', $user_id);

			$user_data = array(
				'user_login'      => $userdata->user_login,
				'user_email'      => $userdata->user_email,
				'first_name'      => $userdata->first_name,
				'last_name'       => $userdata->last_name,
				'display_name'    => $userdata->display_name,
				'user_registered' => $userdata->user_registered,
			);

		} else {

			// the user doesn't already exist.

			// if they submitted a custom login name, use that.
			if (isset($meta_args['login'])) {
				$login = $meta_args['login'];
			}

			// create a new user with their email address as their username.
			// grab the ID for later.
			if (empty($login)) {
				$parts = explode('@', $email);
				$login = $parts[0];
			}

			// Avoid collisions.
			$user = get_user_by('login', $login);
			while ($user) {
				$login = $user->user_login . '_' . substr(uniqid(), 5);
				$user = get_user_by('login', $login);
			}

			if (isset($meta_args['password'])) {
				$password = $meta_args['password'];
			} else {
				$password = wp_generate_password();
			}

			$user_data = array(
				'user_login'      => $login,
				'user_email'      => $email,
				'user_pass'       => $password,
				'first_name'      => isset($meta_args['first_name']) ? $meta_args['first_name'] : '',
				'last_name'       => isset($meta_args['last_name']) ? $meta_args['last_name'] : '',
				'display_name'    => isset($meta_args['first_name']) ? $meta_args['first_name'] . ' ' . $meta_args['last_name'] : '',
				'user_registered' => date_i18n('Y-m-d H:i:s'),
			);

			$user_data = apply_filters('leaky_paywall_userdata_before_user_create', $user_data);
			$user_id  = wp_insert_user($user_data);
		}

		if (empty($user_id)) {
			leaky_paywall_log($meta_args, 'could not create user');
			return false;
		} else {
			$logged_userdata = $user_data;

			if (is_array($logged_userdata)) {
				unset($logged_userdata['user_pass']);
			}

			leaky_paywall_log($logged_userdata, 'leaky paywall - new subscriber created');
		}

		leaky_paywall_set_expiration_date($user_id, $meta_args);
		unset($meta_args['site']);

		if (isset($meta_args['created']) && $meta_args['created']) {
			$created_date         = strtotime($meta_args['created']);
			$meta_args['created'] = gmdate('Y-m-d H:i:s', $created_date);
		} else {
			$meta_args['created'] = gmdate('Y-m-d H:i:s');
		}

		// set free level subscribers to active.
		if ('0' === $meta_args['price']) {
			$meta_args['payment_status'] = 'active';
		}

		$meta = apply_filters('leaky_paywall_new_subscriber_meta', $meta_args, $email, $customer_id, $meta_args);

		// remove any extra underscores from site variable.
		$site = str_replace('__', '_', $site);

		foreach ($meta as $key => $value) {

			// do not want to store their password as plain text.
			if ('confirm_password' === $key || 'password' === $key) {
				continue;
			}

			// Level ID is handled separately via leaky_paywall_set_subscriber_level().
			if ( 'level_id' === $key ) {
				continue;
			}

			update_user_meta($user_id, '_issuem_leaky_paywall_' . $mode . '_' . $key . $site, $value);
		}

		if ( isset( $meta['level_id'] ) ) {
			leaky_paywall_set_subscriber_level( $user_id, $meta['level_id'], 'registration' );
		}

		do_action('leaky_paywall_new_subscriber', $user_id, $email, $meta, $customer_id, $meta_args, $user_data);

		return $user_id;
	}
}

if (!function_exists('leaky_paywall_update_subscriber')) {

	/**
	 * Updates an existing subscriber to subscriber table
	 *
	 * @since 1.0.0
	 *
	 * @param deprecated $hash No longer used.
	 * @param string     $email address of user "logged" in.
	 * @param int        $customer_id Customer ID.
	 * @param array      $meta_args Arguments passed from type of subscriber.
	 * @return mixed $wpdb insert ID or false
	 */
	function leaky_paywall_update_subscriber(string $hash = null, $email, $customer_id, $meta_args)
	{

		if (!is_email($email)) {
			return false;
		}

		$settings = get_leaky_paywall_settings();
		$mode     = leaky_paywall_get_current_mode();
		$site     = leaky_paywall_get_current_site();

		$expires = '0000-00-00 00:00:00';
		$user    = get_user_by('email', $email);

		if (is_user_logged_in() && !is_admin()) {
			// Update the existing user.
			$user_id = get_current_user_id();
		} elseif ($user) {
			// the user already exists.
			// grab the ID for later.
			$user_id = $user->ID;
		} else {
			return false; // User does not exist, cannot update.
		}

		$level = get_leaky_paywall_subscription_level($meta_args['level_id']);

		// do not update levels if it is a pay per post purchase.
		if (isset($level['pay_per_post'])) {
			return $user_id;
		}

		$current_level_id = get_user_meta($user_id, '_issuem_leaky_paywall_' . $mode . '_level_id' . $site, true);

		leaky_paywall_set_expiration_date($user_id, $meta_args);
		unset($meta_args['site']);

		$meta = array(
			'level_id'        => $meta_args['level_id'],
			'subscriber_id'   => $customer_id,
			'price'           => $meta_args['price'],
			'description'     => $meta_args['description'],
			'plan'            => $meta_args['plan'],
			'payment_gateway' => $meta_args['payment_gateway'],
			'payment_status'  => $meta_args['payment_status'],
		);

		$meta = apply_filters('leaky_paywall_update_subscriber_meta', $meta, $email, $customer_id, $meta_args);

		do_action('leaky_paywall_before_update_subscriber', $user_id, $current_level_id, $meta);

		foreach ($meta as $key => $value) {

			// Level ID is handled separately via leaky_paywall_set_subscriber_level().
			if ( 'level_id' === $key ) {
				continue;
			}

			update_user_meta($user_id, '_issuem_leaky_paywall_' . $mode . '_' . $key . $site, $value);
		}

		if ( isset( $meta['level_id'] ) ) {
			leaky_paywall_set_subscriber_level( $user_id, $meta['level_id'], 'subscriber_update' );
		}

		$user_id = wp_update_user(
			array(
				'ID'         => $user_id,
				'user_email' => $email,
				'first_name' => $meta_args['first_name'],
				'last_name' => $meta_args['last_name']
			)
		);

		do_action('leaky_paywall_update_subscriber', $user_id, $email, $meta, $customer_id, $meta_args);

		return $user_id;
	}
}


/**
 * Check if any Leaky Paywall add-on has a valid, active license.
 *
 * @since 5.0
 *
 * @return bool
 */
function leaky_paywall_is_pro() {

	if ( ! class_exists( 'Leaky_Paywall_License_Key' ) ) {
		return false;
	}

	foreach ( Leaky_Paywall_License_Key::get_registered_slugs() as $slug ) {
		$settings = get_option( $slug );

		if ( ! empty( $settings['license_status'] ) && 'valid' === $settings['license_status'] ) {
			return true;
		}
	}

	return false;
}

/**
 * Check if the current visitor is a search engine bot
 *
 * @since 5.0
 *
 * @return bool
 */
function leaky_paywall_is_search_engine_bot() {
	$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) ) : '';

	if ( empty( $user_agent ) ) {
		return false;
	}

	$bots = apply_filters( 'leaky_paywall_search_engine_bots', array(
		'googlebot',
		'bingbot',
		'slurp',
		'duckduckbot',
		'baiduspider',
		'yandexbot',
	) );

	foreach ( $bots as $bot ) {
		if ( strpos( $user_agent, $bot ) !== false ) {
			return true;
		}
	}

	return false;
}

/**
 * Get all valid and active Leaky Paywall levels
 *
 * @since 4.9.0
 *
 * @return array List of active levels
 */
function leaky_paywall_get_levels()
{
	$settings = get_leaky_paywall_settings();
	$blog_id  = get_current_blog_id();

	$level_list = array();

	foreach ($settings['levels'] as $key => $level) {

		if (!empty($level['deleted'])) {
			continue;
		}

		// if (is_multisite_premium() && !empty($level['site']) && 'all' !== $level['site'] && $blog_id !== $level['site']) {
		// 	continue;
		// }

		if (!is_numeric($key)) {
			continue;
		}

		$level_list[$key] = $level;
		$level_list[$key]['id'] = $key;
	}

	return $level_list;
}


if (!function_exists('leaky_paywall_translate_payment_gateway_slug_to_name')) {

	/**
	 * Translate a payment gateway slug to a name
	 *
	 * @param string $slug The slug.
	 * @return string The name of the gateway
	 */
	function leaky_paywall_translate_payment_gateway_slug_to_name($slug)
	{

		$return = 'Unknown';

		switch ($slug) {

			case 'stripe':
				$return = 'Stripe';
				break;

			case 'paypal_standard':
			case 'paypal-standard':
				$return = 'PayPal';
				break;

			case 'free_registration':
				$return = __('Free Registration', 'leaky-paywall');
				break;

			case 'manual':
				$return = __('Manually Added', 'leaky-paywall');
				break;
			default:
				$return = $slug;
				break;
		}

		return apply_filters('leaky_paywall_translate_payment_gateway_slug_to_name', $return, $slug);
	}
}

if (!function_exists('create_leaky_paywall_login_hash')) {

	/**
	 * Creates a 32-character hash string
	 *
	 * Generally used to create a unique hash for each subscriber, stored in the database
	 * and used for campaign links.
	 *
	 * @since 1.0.0
	 *
	 * @param string $str String you want to hash.
	 */
	function create_leaky_paywall_login_hash($str)
	{

		if (defined('SECURE_AUTH_SALT')) {
			$salt[] = SECURE_AUTH_SALT;
		}

		if (defined('AUTH_SALT')) {
			$salt[] = AUTH_SALT;
		}

		$salt[] = get_bloginfo('name');
		$salt[] = time();

		$hash = md5(md5(implode($salt)) . md5($str));

		while (!is_leaky_paywall_login_hash_unique($hash)) {
			$hash = create_leaky_paywall_login_hash($hash); // I did this on purpose...
		}

		return $hash; // doesn't have to be too secure, just want a pretty random and very unique string.

	}
}

if (!function_exists('leaky_paywall_attempt_login')) {
	/**
	 * Attempt a login
	 *
	 * @param string $login_hash The login hash.
	 */
	function leaky_paywall_attempt_login($login_hash)
	{
		$email = get_leaky_paywall_email_from_login_hash($login_hash);
		if (false !== $email) {
			$user = get_user_by('email', $email);

			if ($user) {
				delete_transient('_lpl_' . $login_hash); // one time use.
				wp_set_current_user($user->ID);
				wp_set_auth_cookie($user->ID, true);
			}
		}
	}
}

if (!function_exists('leaky_paywall_subscriber_restrictions')) {

	/**
	 * Returns current user's subscription restrictions
	 *
	 * @since 2.0.0
	 *
	 * @return array subscriber's subscription restrictions
	 */
	function leaky_paywall_subscriber_restrictions()
	{
		$settings = get_leaky_paywall_settings();

		if (isset($settings['restrictions']['post_types'])) {
			$restrictions = $settings['restrictions']['post_types']; // defaults.
		} else {
			$restrictions = '';
		}

		if (is_multisite_premium()) {
			$restriction_levels = leaky_paywall_subscriber_current_level_ids();
			if (!empty($restriction_levels)) {

				$restrictions        = array();
				$merged_restrictions = array();
				foreach ($restriction_levels as $restriction_level) {
					if (!empty($settings['levels'][$restriction_level]['post_types'])) {
						$restrictions = array_merge($restrictions, $settings['levels'][$restriction_level]['post_types']);
					}
				}
				$merged_restrictions = array();
				foreach ($restrictions as $key => $restriction) {
					if (empty($merged_restrictions)) {
						$merged_restrictions[$key] = $restriction;
						continue;
					} else {
						$post_type_found = false;
						foreach ($merged_restrictions as $tmp_key => $tmp_restriction) {
							if ($restriction['post_type'] === $tmp_restriction['post_type']) {
								$post_type_found     = true;
								$post_type_found_key = $tmp_key;
								break;
							}
						}
						if (!$post_type_found) {
							$merged_restrictions[$key] = $restriction;
						} else {
							if (-1 === $restriction['allowed_value'] || 'unlimited' === $restriction['allowed']) { // unlimited, just use it.
								$merged_restrictions[$post_type_found_key] = $restriction;
							} elseif ($merged_restrictions[$post_type_found_key]['allowed_value'] < $restriction['allowed_value']) {
								$merged_restrictions[$post_type_found_key] = $restriction;
							}
						}
					}
				}
				$restrictions = $merged_restrictions;
			}
		} else {
			$restriction_level = leaky_paywall_subscriber_current_level_id();
			if (false !== $restriction_level) {

				if (!empty($settings['levels'][$restriction_level]['post_types'])) {
					$restrictions = $settings['levels'][$restriction_level]['post_types'];
				}
			}
		}
		return apply_filters('leaky_paywall_subscriber_restrictions', $restrictions);
	}
}

if (!function_exists('leaky_paywall_subscriber_current_level_id')) {

	/**
	 * Returns current user's subscription restrictions
	 *
	 * @since 2.0.0
	 *
	 * @return array subscriber's subscription restrictions
	 */
	function leaky_paywall_subscriber_current_level_id($user = null)
	{

		if (null === $user) {
			$user = wp_get_current_user();
		}

		if (leaky_paywall_user_has_access($user)) {

			$sites = array('');
			if (is_multisite_premium()) {
				global $blog_id;
				if (!is_main_site($blog_id)) {
					$sites = array('_all', '_' . $blog_id);
				} else {
					$sites = array('_all', '_' . $blog_id, '');
				}
			}

			$mode = leaky_paywall_get_current_mode();

			foreach ($sites as $site) {
				$level_id = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_level_id' . $site, true);
				$level_id = apply_filters('get_leaky_paywall_users_level_id', $level_id, $user, $mode, $site);
				$level_id = apply_filters('get_leaky_paywall_subscription_level_level_id', $level_id);
				if (is_numeric($level_id)) {
					return $level_id;
				}
			}
		}

		return false;
	}
}

if (!function_exists('leaky_paywall_subscriber_current_level_ids')) {

	/**
	 * Returns current user's subscription restrictions
	 *
	 * @since 3.0.0
	 *
	 * @return array subscriber's subscription restrictions
	 */
	function leaky_paywall_subscriber_current_level_ids()
	{
		$level_ids = array();
		$settings  = get_leaky_paywall_settings();

		$sites = array('');
		if (is_multisite_premium()) {
			global $blog_id;
			if (!is_main_site($blog_id)) {
				$sites = array('_all', '_' . $blog_id);
			} else {
				$sites = array('_all', '_' . $blog_id, '');
			}
		}

		$user = wp_get_current_user();
		$mode = leaky_paywall_get_current_mode();

		foreach ($sites as $site) {
			$level_id = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_level_id' . $site, true);
			$level_id = apply_filters('get_leaky_paywall_users_level_id', $level_id, $user, $mode, $site);
			$level_id = apply_filters('get_leaky_paywall_subscription_level_level_id', $level_id);
			$status   = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site, true);

			if ( is_numeric( $level_id ) && in_array( $status, leaky_paywall_access_statuses(), true ) ) {
				$level_ids[] = $level_id;
			}
		}

		return apply_filters('leaky_paywall_subscriber_current_level_ids', $level_ids);
	}
}

if (!function_exists('leaky_paywall_server_pdf_download')) {
	/**
	 * Download PDF
	 *
	 * @param integer $download_id The download id of the pdf.
	 */
	function leaky_paywall_server_pdf_download($download_id)
	{
		// Grab the download info.
		$url = wp_get_attachment_url($download_id);

		wp_safe_redirect($url);
		die();
	}
}

function build_leaky_paywall_subscription_levels_row_summary($level, $row_key)
{
	$nonce = wp_create_nonce('leaky-paywall-level-row-nonce');
	$settings = get_leaky_paywall_settings();
	$duration = $level['subscription_length_type'] == 'unlimited' ? 'Forever' : $level['interval_count'] . ' ' . $level['interval'];
	$delete_link = admin_url() . 'admin.php?page=leaky-paywall-settings&tab=subscriptions&delete_level_id=' . $row_key . '&_wpnonce=' . esc_attr($nonce);
	$page_for_register = $settings['page_for_register'] > 0 ? esc_url(get_page_link($settings['page_for_register']) . '?level_id=' . esc_attr($row_key)) : '';

?>
	<tr>
		<td><?php echo esc_html($row_key); ?></td>
		<td><?php echo esc_html($level['label']); ?><br>
			<div class="row-actions"><a href="<?php echo esc_url(admin_url()); ?>admin.php?page=leaky-paywall-settings&tab=subscriptions&level_id=<?php echo absint($row_key); ?>">Edit</a> | <span class="delete"><a class="leaky-paywall-level-delete" data-level-id="<?php echo esc_attr($row_key); ?>" href="<?php echo esc_url($delete_link); ?>">Delete</a></span></div>
		</td>
		<td><?php echo esc_html($level['price']); ?></td>
		<td><?php echo esc_html($duration); ?></td>
		<td><?php echo ! empty( $level['recurring'] ) && 'on' === $level['recurring'] ? 'recurring' : 'one time'; ?></td>
		<td><?php echo $page_for_register; ?></td>
	</tr>

	<?php
}

if (!function_exists('build_leaky_paywall_subscription_levels_row')) {

	/**
	 * Build subscription level row
	 *
	 * @since 1.0.0
	 *
	 * @param array   $level The level.
	 * @param integer $row_key The row key.
	 * @return string The HTML for the level row
	 */
	function build_leaky_paywall_subscription_levels_row($level = array(), $row_key = '')
	{

		global $leaky_paywall;
		$settings = get_leaky_paywall_settings();

		$default = array(
			'label'                         => '',
			'description'                   => '',
			'registration_form_description' => '',
			'price'                         => '',
			'subscription_length_type'      => 'limited',
			'interval_count'                => 1,
			'interval'                      => 'month',
			'recurring'                     => 'off',
			'hide_subscribe_card'           => 'off',
			'hide_registration_form'           => 'off',
			'plan_id'                       => array(),
			'post_types'                    => array(
				array(
					'post_type'     => ACTIVE_ISSUEM ? 'article' : 'post',
					'allowed'       => 'unlimited',
					'allowed_value' => 0,
				),
			),
			'deleted'                       => 0,
			'site'                          => 'all',
		);
		$level   = wp_parse_args($level, $default);

		if (empty($level['recurring'])) {
			$level['recurring'] = 'off';
		}

		if (!empty($level['deleted'])) {
			$deleted = 'hidden';
		} else {
			$deleted = '';
		}

		ob_start();
	?>

		<table class="issuem-leaky-paywall-subscription-level-row-table leaky-paywall-table <?php echo esc_attr($deleted); ?>">
			<?php
			if (isset($settings['page_for_register']) && $settings['page_for_register']) {
			?>
				<tr>
					<th>
						<label for="level-name-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Direct Sign Up Link', 'leaky-paywall'); ?></label>
					</th>
					<td>
						<p><?php echo esc_url(get_page_link($settings['page_for_register'])) . '?level_id=' . esc_attr($row_key); ?></p>
					</td>
				</tr>
			<?php
			} ?>


			<tr>
				<th>
					<label for="level-name-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Subscription Level Name', 'leaky-paywall'); ?></label>
				</th>
				<td>
					<input id="level-name-<?php echo esc_attr($row_key); ?>" type="text" class="regular-text" name="levels[<?php echo esc_attr($row_key); ?>][label]" value="<?php echo esc_attr($level['label']); ?>" />
				</td>
			</tr>

			<tr>
				<th>
					<label for="level-description-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Subscribe Card Description', 'leaky-paywall'); ?></label>
				</th>
				<td>
					<textarea id="level-description-<?php echo esc_attr($row_key); ?>" name="levels[<?php echo esc_attr($row_key); ?>][description]" class="large-text"><?php echo wp_kses_post(stripslashes($level['description'] ?? '')); ?></textarea>
					<p class="description"><?php esc_html_e('If entered, this will replace the auto-generated access description on the subscribe cards. HTML allowed.', 'leaky-paywall'); ?></p>
				</td>
			</tr>

			<tr>
				<th>
					<label for="level-registration-form-description-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Registration Form Description', 'leaky-paywall'); ?></label>
				</th>
				<td>
					<textarea id="level-registration-form-description-<?php echo esc_attr($row_key); ?>" name="levels[<?php echo esc_attr($row_key); ?>][registration_form_description]" class="large-text"><?php echo wp_kses_post(stripslashes($level['registration_form_description'] ?? '')); ?></textarea>
					<p class="description"><?php esc_html_e('If entered, this will replace the auto-generated content access description on the registration form. HTML allowed.', 'leaky-paywall'); ?></p>
				</td>
			</tr>

			<?php
			if (is_plugin_active('leaky-paywall-recurring-payments/leaky-paywall-recurring-payments.php')) {
			?>
				<tr>
					<th>
						<label for="level-recurring-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Recurring', 'leaky-paywall'); ?></label>
					</th>
					<td>
						<input id="level-recurring-<?php echo esc_attr($row_key); ?>" class="stripe-recurring" type="checkbox" name="levels[<?php echo esc_attr($row_key); ?>][recurring]" value="on" <?php echo checked('on', $level['recurring'] ?? '', false); ?> /> Enable recurring payments<br>
						<span style="color: #999; font-size: 11px;" class="recurring-help <?php echo checked('on', $level['recurring'] ?? '', false) ? '' : 'hidden'; ?>">Webhooks must be setup in your payment gateway account for recurring payments to work properly. <a target="_blank" href="https://docs.leakypaywall.com/article/120-leaky-paywall-recurring-payments">See documentation here.</a></span>

						<?php

						if (is_array($level['plan_id'])) {
							foreach ($level['plan_id'] as $plan_id) {
								if (!$plan_id) {
									continue;
								}
						?>
								<input type="hidden" class="level-plan_id-<?php echo esc_attr($row_key); ?>" name="levels[<?php echo esc_attr($row_key); ?>][plan_id][]" value="<?php echo esc_attr($plan_id); ?>">
							<?php
							}
						} else {
							?>
							<input type="hidden" id="level-plan_id-<?php echo esc_attr($row_key); ?>" name="levels[<?php echo esc_attr($row_key); ?>][plan_id]" value="<?php echo esc_attr($level['plan_id']); ?>">
						<?php
						}

						?>

					</td>
				</tr>
			<?php
			} else {
			?>
				<tr>
					<th>
						<label><?php esc_html_e('Recurring', 'leaky-paywall'); ?></label>
					</th>
					<td>
						<label class="lp-pro-feature-toggle">
							<input type="checkbox" disabled />
							<?php esc_html_e( 'Enable recurring payments', 'leaky-paywall' ); ?>
							<span class="lp-pro-badge"><?php esc_html_e( 'Pro', 'leaky-paywall' ); ?></span>
						</label>
						<p class="description">
							<a href="#" class="lp-pro-feature-link"><?php esc_html_e( 'Upgrade to Pro to enable recurring subscription payments.', 'leaky-paywall' ); ?></a>
						</p>
					</td>
				</tr>
			<?php
			}
			?>

			<tr>
				<th>
					<label for="level-price-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Subscription Price', 'leaky-paywall'); ?></label>
				</th>
				<td>
					<input id="level-price-<?php echo esc_attr($row_key); ?>" type="text" style="width: 100px;" name="levels[<?php echo esc_attr($row_key); ?>][price]" value="<?php echo esc_attr($level['price']); ?>" />
					<p class="description"><?php esc_html_e('0 for Free Subscriptions', 'leaky-paywall'); ?></p>
				</td>
			</tr>

			<tr>
				<th>
					<label for="level-interval-count-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Subscription Length', 'leaky-paywall'); ?></label>
				</th>
				<td>
					<select class="subscription_length_type" name="levels[<?php echo esc_attr($row_key); ?>][subscription_length_type]">
						<option value="unlimited" <?php echo selected('unlimited', $level['subscription_length_type'], false); ?>><?php esc_html_e('Forever', 'leaky-paywall'); ?></option>
						<option value="limited" <?php echo selected('limited', $level['subscription_length_type'], false); ?>> <?php esc_html_e('Limited for...', 'leaky-paywall'); ?></option>
					</select>

					<?php
					if ('unlimited' === $level['subscription_length_type']) {
						$subscription_length_input_style = 'display: none;';
					} else {
						$subscription_length_input_style = '';
					}
					?>

					<div class="interval_div" style="<?php echo esc_attr($subscription_length_input_style); ?>">
						<input id="level-interval-count-<?php echo esc_attr($row_key); ?>" type="text" class="interval_count small-text" name="levels[<?php echo esc_attr($row_key); ?>][interval_count]" value="<?php echo esc_attr($level['interval_count']); ?>" />
						<select id="interval" name="levels[<?php echo esc_attr($row_key); ?>][interval]">
							<option value="day" <?php echo selected('day' === $level['interval'], true, false); ?>><?php esc_html_e('Day(s)', 'leaky-paywall'); ?></option>
							<option value="week" <?php echo selected('week' === $level['interval'], true, false); ?>><?php esc_html_e('Week(s)', 'leaky-paywall'); ?></option>
							<option value="month" <?php echo selected('month' === $level['interval'], true, false); ?>><?php esc_html_e('Month(s)', 'leaky-paywall'); ?></option>
							<option value="year" <?php echo selected('year' === $level['interval'], true, false); ?>><?php esc_html_e('Year(s)', 'leaky-paywall'); ?></option>
						</select>
					</div>
				</td>
			</tr>

			<tr>
				<th><?php esc_html_e('Access Options', 'leaky-paywall'); ?></th>
				<td id="issuem-leaky-paywall-subsciption-row-<?php echo esc_attr($row_key); ?>-post-types">

					<table class="leaky-paywall-interal-setting-table">
						<tr>
							<th>Number Allowed</th>
							<th>Post Type</th>
							<th>Taxonomy <span style="font-weight: normal; font-size: 11px; color: #999;"> Category,tag,etc.</span></th>
							<th class="narrow-cell">&nbsp;</th>
						</tr>

						<?php
						$last_key = -1;
						if (!empty($level['post_types'])) {
							foreach ($level['post_types'] as $select_post_key => $select_post_type) {

								build_leaky_paywall_subscription_row_post_type($select_post_type, $select_post_key, $row_key);

								$last_key = $select_post_key;
							}
						}
						?>
					</table>
				</td>
			</tr>

			<tr>
				<th>&nbsp;</th>
				<td>
					<script>
						var leaky_paywall_subscription_row_<?php echo esc_attr($row_key); ?>_last_post_type_key = <?php echo absint($last_key); ?>;
					</script>
					<p><input data-row-key="<?php echo esc_attr($row_key); ?>" class="button-secondary" id="add-subscription-row-post-type" class="add-new-issuem-leaky-paywall-row-post-type" type="submit" name="add_leaky_paywall_subscription_row_post_type" value="<?php esc_attr_e('+ Add Access Option', 'leaky-paywall'); ?>" /></p>
					<?php
					if ($leaky_paywall->is_site_wide_enabled()) {
						echo '<p class="description">';
						esc_attr_e('Post Types that are not native the to the site currently being viewed are marked with an asterisk.', 'leaky-paywall');
						echo '</p>';
					}
					?>

					<p class="description"><?php esc_html_e('Access processed from top to bottom. Set limit to 0 to block access.', 'leaky-paywall'); ?></p>
				</td>
			</tr>

			<tr>
				<th>
					<label for="level-hide-subscribe-card-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Hide Subscribe Card', 'leaky-paywall'); ?></label>
				</th>
				<td>
					<input id="level-hide-subscribe-card-<?php echo esc_attr($row_key); ?>" class="hide-subscribe- card" type="checkbox" name="levels[<?php echo esc_attr($row_key); ?>][hide_subscribe_card]" value="on" <?php echo checked('on', $level['hide_subscribe_card'], false); ?> /> <?php esc_html_e('Do not display subscribe card on subscribe page', 'leaky-paywall'); ?>
				</td>
			</tr>

			<tr>
				<th>
					<label for="level-hide-registration-form-<?php echo esc_attr($row_key); ?>"><?php esc_html_e('Hide Registration Form', 'leaky-paywall'); ?></label>
				</th>
				<td>
					<input id="level-hide-registration-form-<?php echo esc_attr($row_key); ?>" class="hide-registration-form" type="checkbox" name="levels[<?php echo esc_attr($row_key); ?>][hide_registration_form]" value="on" <?php echo checked('on', $level['hide_registration_form'], false); ?> /> <?php esc_html_e('Disable the direct sign up link for this level, but still allow manual assignment.', 'leaky-paywall'); ?>
				</td>
			</tr>

			<?php
			if (is_multisite_premium()) {
			?>
				<tr>
					<th><?php esc_html_e('Site', 'leaky-paywall'); ?></th>
					<td id="issuem-leaky-paywall-subsciption-row-<?php echo esc_attr($row_key); ?>-site">
						<select id="site" name="levels[<?php echo esc_attr($row_key); ?>][site]">
							<?php
							if (is_super_admin()) {
							?>
								<option value="all" <?php echo selected('all', $level['site'], false); ?>><?php esc_html_e('All Sites', 'leaky-paywall'); ?></option>
								<?php
								$sites = get_sites();
								foreach ($sites as $site) {
									$site_details = get_blog_details($site->id);
								?>
									<option value="<?php echo esc_attr($site->id); ?>" <?php echo selected($site->id, $level['site'], false); ?>><?php echo esc_html($site_details->blogname); ?></option>
								<?php
								}
							} else {
								$site_details = get_blog_details(get_current_blog_id());
								?>
								<option value="<?php echo get_current_blog_id(); ?>" <?php echo selected(get_current_blog_id(), $level['site'], false); ?>><?php echo esc_html($site_details->blogname); ?></option>
							<?php
							}
							?>


						</select>
					</td>
				</tr>

		<?php
			}

			// leaving for backwards compatibility, but it will deprecated.
			echo wp_kses_post(apply_filters('build_leaky_paywall_subscription_levels_row_addon_filter', '', $level, $row_key));

			do_action('leaky_paywall_after_subscription_levels_row', $level, $row_key);

			echo '</table>';

			$content = ob_get_contents();
			ob_end_clean();

			return $content;
		}
	}

	if (!function_exists('build_leaky_paywall_subscription_row_ajax')) {

		/**
		 * AJAX Wrapper
		 *
		 * @since 1.0.0
		 */
		function build_leaky_paywall_subscription_row_ajax()
		{
			if ( ! current_user_can( 'manage_options' ) ) {
				die();
			}

			check_ajax_referer( 'leaky-paywall-js-nonce', 'nonce' );

			if (isset($_REQUEST['row-key'])) {
				// phpcs:ignore
				die(build_leaky_paywall_subscription_levels_row(array(), sanitize_text_field(wp_unslash($_REQUEST['row-key']))));
			} else {
				die();
			}
		}
		add_action('wp_ajax_issuem-leaky-paywall-add-new-subscription-row', 'build_leaky_paywall_subscription_row_ajax');
	}

	if (!function_exists('build_leaky_paywall_subscription_row_post_type')) {

		/**
		 * Build Leaky Paywall subscription row
		 *
		 * @since 1.0.0
		 *
		 * @param array   $select_post_type Data for post type.
		 * @param integer $select_post_key The post key.
		 * @param integer $row_key The row key.
		 * @return mixed Value set for the issuem options.
		 */
		function build_leaky_paywall_subscription_row_post_type($select_post_type = array(), $select_post_key = '', $row_key = '')
		{

			$default_select_post_type = array(
				'post_type'     => ACTIVE_ISSUEM ? 'article' : 'post',
				'allowed'       => 'unlimited',
				'allowed_value' => 0,
				'site'          => 0,
				'taxonomy'      => '',
			);
			$select_post_type         = wp_parse_args($select_post_type, $default_select_post_type);

			echo '<tr class="issuem-leaky-paywall-row-post-type">';
			echo '<td><select class="allowed_type" name="levels[' . esc_attr($row_key) . '][post_types][' . esc_attr($select_post_key) . '][allowed]">';
			echo '<option value="unlimited" ' . selected('unlimited', $select_post_type['allowed'], false) . '>' . esc_html__('Unlimited', 'leaky-paywall') . '</option>';
			echo '<option value="limited" ' . selected('limited', $select_post_type['allowed'], false) . '>' . esc_html__('Limit to...', 'leaky-paywall') . '</option>';
			echo '</select>';

			if ('unlimited' === $select_post_type['allowed']) {
				$allowed_value_input_style = 'display: none;';
			} else {
				$allowed_value_input_style = '';
			}

			echo '<div class="allowed_value_div" style="' . esc_attr($allowed_value_input_style) . '">';
			$allowed_value_output = max( 0, intval( $select_post_type['allowed_value'] ) );
			echo '<input type="number" min="0" class="allowed_value small-text" name="levels[' . esc_attr($row_key) . '][post_types][' . esc_attr($select_post_key) . '][allowed_value]" value="' . esc_attr($allowed_value_output) . '" placeholder="' . esc_attr__('#', 'leaky-paywall') . '" />';
			echo '</div></td>';

			echo '<td><select class="select_level_post_type" name="levels[' . esc_attr($row_key) . '][post_types][' . esc_attr($select_post_key) . '][post_type]">';
			$post_types        = get_post_types(array('public' => true), 'objects');
			$post_types_names  = get_post_types(array(), 'names');
			$hidden_post_types = array('attachment', 'revision', 'nav_menu_item');
			if (in_array($select_post_type['post_type'], $post_types_names, true)) {
				foreach ($post_types as $post_type) {
					if (in_array($post_type->name, $hidden_post_types, true)) {
						continue;
					}
					echo '<option value="' . esc_attr($post_type->name) . '" ' . selected($post_type->name, $select_post_type['post_type'], false) . '>' . esc_html($post_type->labels->name) . '</option>';
				}
			} else {
				echo '<option value="' . esc_attr($select_post_type['post_type']) . '">' . esc_html($select_post_type['post_type']) . ' &#42;</option>';
			}
			echo '</select></td>';

			// get taxonomies for this post type.
			echo '<td><select style="width: 100%;" name="levels[' . esc_attr($row_key) . '][post_types][' . esc_attr($select_post_key) . '][taxonomy]">';
			$tax_post_type = $select_post_type['post_type'] ? $select_post_type['post_type'] : 'post';
			$taxes         = get_object_taxonomies($tax_post_type, 'objects');
			$hidden_taxes  = apply_filters('leaky_paywall_settings_hidden_taxonomies', array('post_format'));

			echo '<option value="all" ' . selected('all', $select_post_type['taxonomy'], false) . '>All</option>';

			foreach ($taxes as $tax) {

				if (in_array($tax->name, $hidden_taxes, true)) {
					continue;
				}

				// create option group for this taxonomy.
				echo '<optgroup label="' . esc_attr($tax->label) . '">';

				// create options for this taxonomy.
				$terms = get_terms(
					array(
						'taxonomy'   => $tax->name,
						'hide_empty' => false,
					)
				);

				foreach ($terms as $term) {
					echo '<option value="' . esc_attr($term->term_id) . '" ' . selected($term->term_id, $select_post_type['taxonomy'], false) . '>' . esc_html($term->name) . '</option>';
				}

				echo '</optgroup>';
			}
			echo '</select></td>';

			echo '<td class="narrow-cell"><span class="delete-x delete-post-type-row">&times;</span></td>';

			echo '</tr>';
		}
	}

	if (!function_exists('build_leaky_paywall_subscription_row_post_type_ajax')) {

		/**
		 * AJAX Wrapper
		 *
		 * @since 1.0.0
		 */
		function build_leaky_paywall_subscription_row_post_type_ajax()
		{
			if ( ! current_user_can( 'manage_options' ) ) {
				die();
			}

			check_ajax_referer( 'leaky-paywall-js-nonce', 'nonce' );

			if (isset($_REQUEST['select-post-key']) && isset($_REQUEST['row-key'])) {
				$settings = get_leaky_paywall_settings();

				if (is_multisite_premium() && isset($_SERVER['HTTP_REFERER']) && preg_match('#^' . network_admin_url() . '#i', sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])))) {
					if (!defined('WP_NETWORK_ADMIN')) {
						define('WP_NETWORK_ADMIN', true);
					}
				}

				// phpcs:ignore
				die(build_leaky_paywall_subscription_row_post_type(array(), sanitize_text_field(wp_unslash($_REQUEST['select-post-key'])), sanitize_text_field(wp_unslash($_REQUEST['row-key']))));
			}
			die();
		}
		add_action('wp_ajax_issuem-leaky-paywall-add-new-subscription-row-post-type', 'build_leaky_paywall_subscription_row_post_type_ajax');
	}


	/**
	 * Build a default restriction row
	 *
	 * @since 1.0.0
	 *
	 * @param array   $restriction The restriction.
	 * @param integer $row_key The row key.
	 * @return mixed Value set for the issuem options.
	 */
	function build_leaky_paywall_default_restriction_row($restriction = array(), $row_key = '')
	{

		$settings = get_leaky_paywall_settings();

		if (empty($restriction)) {
			$restriction = array(
				'post_type'     => '',
				'taxonomy'      => '',
				'allowed_value' => '0',
			);
		}

		if (!isset($restriction['taxonomy'])) {
			$restriction['taxonomy'] = 'all';
		}

		echo '<tr class="issuem-leaky-paywall-restriction-row">';
		$hidden_post_types = array('attachment', 'revision', 'nav_menu_item', 'lp_transaction', 'custom_css');
		$post_types        = get_post_types(array('public' => true), 'objects');

		echo '<td><select class="leaky-paywall-restriction-post-type" id="restriction-post-type-' . esc_attr($row_key) . '" name="restrictions[post_types][' . esc_attr($row_key) . '][post_type]">';
		foreach ($post_types as $post_type) {

			if (in_array($post_type->name, $hidden_post_types, true)) {
				continue;
			}

			echo '<option value="' . esc_attr($post_type->name) . '" ' . selected($post_type->name, $restriction['post_type'], false) . '>' . esc_html($post_type->labels->name) . '</option>';
		}

		echo '</select></td>';

		// get taxonomies for this post type.
		echo '<td><select style="width: 100%;" name="restrictions[post_types][' . esc_attr($row_key) . '][taxonomy]">';
		$tax_post_type = $restriction['post_type'] ? $restriction['post_type'] : 'post';
		$taxes         = get_object_taxonomies($tax_post_type, 'objects');
		$hidden_taxes  = apply_filters('leaky_paywall_settings_hidden_taxonomies', array('post_format', 'yst_prominent_words'));

		echo '<option value="all" ' . selected('all', $restriction['taxonomy'], false) . '>All</option>';

		foreach ($taxes as $tax) {

			if (in_array($tax->name, $hidden_taxes, true)) {
				continue;
			}

			// create option group for this taxonomy.
			echo '<optgroup label="' . esc_attr($tax->label) . '">';

			// create options for this taxonomy.
			$terms = get_terms(
				array(
					'taxonomy'   => $tax->name,
					'hide_empty' => false,
				)
			);

			foreach ($terms as $term) {
				echo '<option value="' . esc_attr($term->term_id) . '" ' . selected($term->term_id, $restriction['taxonomy'], false) . '>' . esc_html($term->name) . '</option>';
			}

			echo '</optgroup>';
		}
		echo '</select></td>';

		echo '<td>';

		$restriction_allowed_output = max( 0, intval( $restriction['allowed_value'] ) );

		if ('on' === $settings['enable_combined_restrictions']) {
			echo '<p class="allowed-number-helper-text" style="color: #555; font-size: 12px;">Using combined restrictions.</p>';
			echo '<input style="display: none;" id="restriction-allowed-' . esc_attr($row_key) . '" type="number" min="0" class="small-text restriction-allowed-number-setting" name="restrictions[post_types][' . esc_attr($row_key) . '][allowed_value]" value="' . esc_attr($restriction_allowed_output) . '" />';
		} else {
			echo '<p class="allowed-number-helper-text" style="color: #555; font-size: 12px; display: none;">Using combined restrictions.</p>';
			echo '<input id="restriction-allowed-' . esc_attr($row_key) . '" type="number" min="0" class="small-text restriction-allowed-number-setting" name="restrictions[post_types][' . esc_attr($row_key) . '][allowed_value]" value="' . esc_attr($restriction_allowed_output) . '" />';
		}

		echo '</td>';

		echo '<td class="narrow-cell"><span class="delete-x delete-restriction-row">&times;</span></td>';

		echo '</tr>';
	}

	/**
	 * Get the taxonomies for the selected post type in a restriction setting row
	 *
	 * @since 4.7.5
	 */
	function leaky_paywall_get_restriction_row_post_type_taxonomies()
	{

		if (!wp_verify_nonce(sanitize_key($_POST['nonce']), 'leaky-paywall-js-nonce')) {
			die(esc_html__('Failed Security Check', 'leaky-paywall'));
		}

		$post_type    = isset($_REQUEST['post_type']) ? sanitize_text_field(wp_unslash($_REQUEST['post_type'])) : '';
		$taxes        = get_object_taxonomies($post_type, 'objects');
		$hidden_taxes = apply_filters('leaky_paywall_settings_hidden_taxonomies', array('post_format', 'yst_prominent_words'));

		ob_start();
		?>

		<select style="width: 100%;">
			<option value="all">All</option>

			<?php
			foreach ($taxes as $tax) {

				if (in_array($tax->name, $hidden_taxes, true)) {
					continue;
				}

				// create option group for this taxonomy.
				echo '<optgroup label="' . esc_attr($tax->label) . '">';

				// create options for this taxonomy.
				$terms = get_terms(
					array(
						'taxonomy'   => $tax->name,
						'hide_empty' => false,
					)
				);

				foreach ($terms as $term) {
					echo '<option value="' . esc_attr($term->term_id) . '">' . esc_html($term->name) . '</option>';
				}

				echo '</optgroup>';
			}

			?>

		</select>

	<?php
		$content = ob_get_contents();
		ob_end_clean();

		wp_send_json($content);
	}
	add_action('wp_ajax_leaky-paywall-get-restriction-row-post-type-taxonomies', 'leaky_paywall_get_restriction_row_post_type_taxonomies');


	if (!function_exists('build_leaky_paywall_default_restriction_row_ajax')) {

		/**
		 * AJAX Wrapper
		 *
		 * @since 1.0.0
		 */
		function build_leaky_paywall_default_restriction_row_ajax()
		{

			if (!wp_verify_nonce(sanitize_key($_POST['nonce']), 'leaky-paywall-js-nonce')) {
				die(esc_html__('Failed Security Check', 'leaky-paywall'));
			}

			if (isset($_REQUEST['row-key'])) {
				// phpcs:ignore
				die(build_leaky_paywall_default_restriction_row(array(), sanitize_text_field(wp_unslash($_REQUEST['row-key']))));
			} else {
				die();
			}
		}
		add_action('wp_ajax_issuem-leaky-paywall-add-new-restriction-row', 'build_leaky_paywall_default_restriction_row_ajax');
	}

	/**
	 * AJAX handler to toggle an email's enabled/disabled state.
	 */
	function leaky_paywall_ajax_toggle_email() {
		check_ajax_referer( 'leaky-paywall-js-nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'Unauthorized', 403 );
		}

		$email_id = isset( $_POST['email_id'] ) ? sanitize_text_field( wp_unslash( $_POST['email_id'] ) ) : '';
		$email    = LP_Emails::instance()->get_email( $email_id );

		if ( ! $email ) {
			wp_send_json_error( 'Email not found', 404 );
		}

		$option_key          = 'leaky_paywall_email_' . $email->id . '_settings';
		$settings            = get_option( $option_key, array() );
		$currently_enabled   = isset( $settings['enabled'] ) && 'yes' === $settings['enabled'];
		$settings['enabled'] = $currently_enabled ? 'no' : 'yes';

		update_option( $option_key, $settings );

		wp_send_json_success( array( 'enabled' => $settings['enabled'] ) );
	}
	add_action( 'wp_ajax_lp_toggle_email', 'leaky_paywall_ajax_toggle_email' );

	if (!function_exists('wp_print_r')) {

		/**
		 * Helper function used for printing out debug information
		 *
		 * HT: Glenn Ansley @ iThemes.com
		 *
		 * @since 1.0.0
		 *
		 * @param int  $args Arguments to pass to print_r.
		 * @param bool $die TRUE to die else FALSE (default TRUE).
		 */
		function wp_print_r($args, $die = true)
		{

			$echo = '<pre>' . print_r($args, true) . '</pre>';

			if ($die) {
				die(esc_attr($echo));
			} else {
				echo esc_attr($echo);
			}
		}
	}

	if (!function_exists('get_leaky_paywall_subscription_level')) {
		/**
		 * Get the Leaky Paywall level
		 *
		 * @param int $level_id The level id.
		 */
		function get_leaky_paywall_subscription_level($level_id)
		{

			$settings = get_leaky_paywall_settings();

			$level_id = apply_filters('get_leaky_paywall_subscription_level_level_id', $level_id);
			if (isset($settings['levels'][$level_id])) {
				$level       = $settings['levels'][$level_id];
				$level['id'] = $level_id;
			} else {
				$level = false;
			}

			return apply_filters('get_leaky_paywall_subscription_level', $level, $level_id);
		}
	}

	if (!function_exists('leaky_paywall_subscription_options')) {

		/**
		 * Display the subscription card options
		 */
		function leaky_paywall_subscription_options()
		{

			global $blog_id;

			$settings          = get_leaky_paywall_settings();
			$mode              = leaky_paywall_get_current_mode();
			$site              = leaky_paywall_get_current_site();
			$current_level_ids = leaky_paywall_subscriber_current_level_ids();

			$results = apply_filters('leaky_paywall_subscription_options', '');
			// If someone wants to completely override this, they can with the above filter.
			if (empty($results)) {

				$has_allowed_value = false;
				$results          .= '<h2 class="subscription-options-title">' . __('Subscription Options', 'leaky-paywall') . '</h2>';

				$results .= apply_filters('leaky_paywall_subscription_options_header', '');

				if (!empty($settings['levels'])) {

					$results .= apply_filters('leaky_paywall_before_subscription_options', '');

					$results .= '<div class="leaky_paywall_subscription_options">';
					foreach (apply_filters('leaky_paywall_subscription_levels', $settings['levels']) as $level_id => $level) {

						if (!empty($level['deleted'])) {
							continue;
						}

						if (isset($level['hide_subscribe_card']) && 'on' === $level['hide_subscribe_card']) {
							continue;
						}

						if (is_multisite_premium() && !empty($level['site']) && 'all' !== $level['site'] && $blog_id !== $level['site']) {
							continue;
						}

						$level = apply_filters('leaky_paywall_subscription_options_level', $level, $level_id);

						if (isset($level['recurring']) && 'on' === $level['recurring']) {
							$is_recurring = true;
						} else {
							$is_recurring = false;
						}

						$payment_options = '';
						$allowed_content = '';

						if (in_array($level_id, $current_level_ids)) {
							$current_level = 'current-level';
						} else {
							$current_level = '';
						}

						$results .= '<div id="option-' . $level_id . '" class="leaky_paywall_subscription_option ' . $current_level . '">';
						if ($current_level) {
							$results .= '<p class="leaky-paywall-subscription-current-level">' .  __('Your Current Level', 'leaky-paywall') . '</p>';
						}
						$results .= '<h3 class="leaky_paywall_subscription_option_title">' . apply_filters('leaky_paywall_subscription_option_title', stripslashes($level['label'])) . '</h3>';

						$results .= '<div class="leaky_paywall_subscription_allowed_content">';

						if (!empty($level['post_types']) && empty($level['description'])) {
							foreach ($level['post_types'] as $post_type) {

								if (isset($post_type['taxonomy'])) {

									$term = get_term_by('term_taxonomy_id', $post_type['taxonomy']);

									if (is_object($term)) {
										$name = $term->name;
									} else {
										$name = '';
									}

									$post_type_obj = get_post_type_object($post_type['post_type']);
									if (!empty($post_type_obj)) {
										if (0 <= $post_type['allowed_value']) {
											$has_allowed_value = true;

											if (1 === $post_type['allowed_value']) {
												$plural = '';
											} else {
												$plural = 's';
											}

											/* Translators: %1$s - allowed value, %2$s - name, %3$s - post type name */
											$allowed_content .= '<p>' . sprintf(__('Access %1$s %2$s %3$s*', 'leaky-paywall'), $post_type['allowed_value'], $name, $post_type_obj->labels->singular_name . $plural) . '</p>';
										} else {
											/* Translators: %1$s - name, %2$s - post type name */
											$allowed_content .= '<p>' . sprintf(__('Unlimited %1$s %2$s', 'leaky-paywall'), $name, $post_type_obj->labels->name) . '</p>';
										}
									}
								} else {

									/* @todo: We may need to change the site ID during this process, some sites may have different post types enabled */
									$post_type_obj = get_post_type_object($post_type['post_type']);
									if (!empty($post_type_obj)) {
										if (0 <= $post_type['allowed_value']) {
											$has_allowed_value = true;

											if (1 === $post_type['allowed_value']) {
												$plural = '';
											} else {
												$plural = 's';
											}
											/* Translators: %1$s - allowed value, %2$s - post type name */
											$allowed_content .= '<p>' . sprintf(__('Access %1$s %2$s*', 'leaky-paywall'), $post_type['allowed_value'], $post_type_obj->labels->singular_name . $plural) . '</p>';
										} else {
											/* Translators: %s - type of post object */
											$allowed_content .= '<p>' . sprintf(__('Unlimited %s', 'leaky-paywall'), $post_type_obj->labels->name) . '</p>';
										}
									}
								}
							}
						} else {
							$allowed_content = stripslashes($level['description']);
						}
						$results .= apply_filters('leaky_paywall_subscription_options_allowed_content', $allowed_content, $level_id, $level);
						$results .= '</div>';

						$subscription_price = '';

						$subscription_price .= '<div class="leaky_paywall_subscription_price">';
						$subscription_price .= '<p>';
						if (!empty($level['price'])) {
							if (!empty($level['recurring']) && 'on' === $level['recurring'] && apply_filters('leaky_paywall_subscription_options_price_recurring_on', true, $current_level)) {
								$subscription_price .= '<strong>' . leaky_paywall_get_level_display_price($level) . ' ' . leaky_paywall_human_readable_interval($level['interval_count'], $level['interval']) . ' ' . __('(recurring)', 'leaky-paywall') . '</strong>';
								$subscription_price .= apply_filters('leaky_paywall_before_subscription_options_recurring_price', '');
							} else {
								/* Translators: %1$s - display price, %2$s - interval */
								$subscription_price .= '<strong>' . sprintf(__('%1$s %2$s', 'leaky-paywall'), leaky_paywall_get_level_display_price($level), leaky_paywall_human_readable_interval($level['interval_count'], $level['interval'])) . '</strong>';
								$subscription_price .= apply_filters('leaky_paywall_before_subscription_options_non_recurring_price', '');
							}

							if (!empty($level['trial_period'])) {
								/* Translators: %s - trial period days */
								$subscription_price .= '<span class="leaky-paywall-trial-period">' . sprintf(__('Free for the first %s day(s)', 'leaky-paywall'), $level['trial_period']) . '</span>';
							}
						} else {
							$subscription_price .= '<strong>' . __('Free', 'leaky-paywall') . '</strong>';
						}

						$subscription_price .= '</p>';
						$subscription_price .= '</div>';

						$results .= apply_filters('leaky_paywall_subscription_options_subscription_price', $subscription_price, $level_id, $level);

						$subscription_action  = '';
						$subscription_action .= '<div class="leaky_paywall_subscription_payment_options">';

						// Don't show payment options if the users is currently subscribed to this level and it is a recurring level.
						if (in_array($level_id, $current_level_ids, true)) {

							$subscription_action .= '<div class="leaky_paywall_subscription_current_level"><span>';
							$subscription_action .= __('Your Current Subscription', 'leaky-paywall');
							$subscription_action .= '</span></div>';
						}

						if (in_array($level_id, $current_level_ids, true) && leaky_paywall_user_has_access() && $is_recurring) {
							$subscription_action .= ''; // they already have an active recurring subscription to this level.
						} else {

							$subscription_action .= apply_filters('leaky_paywall_subscription_options_payment_options', $payment_options, $level, $level_id);
						}

						$subscription_action .= '</div>';

						$results .= apply_filters('leaky_paywall_subscription_options_subscription_action', $subscription_action, $level_id, $current_level_ids, $payment_options);

						$results .= '</div>';
					}

					$results .= apply_filters('leaky_paywall_subscription_options_after_last_subscription_option', '');

					$results .= '</div>';

					$results .= apply_filters('leaky_paywall_subscription_options_after_subscription_options', '');

					if ($has_allowed_value) {

						$results .= '<div class="leaky_paywall_subscription_limit_details">';
						$results .= '*' . ucfirst(leaky_paywall_human_readable_interval($settings['cookie_expiration'], $settings['cookie_expiration_interval']));
						$results .= '</div>';
					}
				}

				$results .= apply_filters('leaky_paywall_subscription_options_footer', '');
			}

			return $results;
		}
	}


	/**
	 * Pass a PHP date format string to this function to return its jQuery datepicker equivalent
	 *
	 * @since 1.1.0
	 * @param string $date_format PHP Date Format.
	 * @return string jQuery datePicker Format.
	 */
	function leaky_paywall_jquery_datepicker_format($date_format)
	{

		// http://us2.php.net/manual/en/function.date.php .
		// http://api.jqueryui.com/datepicker/#utility-formatDate .
		$php_format = array(
			// day.
			'/d/', // Day of the month, 2 digits with leading zeros.
			'/D/', // A textual representation of a day, three letters.
			'/j/', // Day of the month without leading zeros.
			'/l/', // A full textual representation of the day of the week.
			// '/N/', //ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0).
			// '/S/', //English ordinal suffix for the day of the month, 2 characters.
			// '/w/', //Numeric representation of the day of the week.
			'/z/', // The day of the year (starting from 0).

			// week.
			// '/W/', //ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0).

			// month.
			'/F/', // A full textual representation of a month, such as January or March.
			'/m/', // Numeric representation of a month, with leading zeros.
			'/M/', // A short textual representation of a month, three letters.
			'/n/', // numeric month no leading zeros.
			// 't/', //Number of days in the given month.

			// year.
			// '/L/', //Whether it's a leap year.
			// '/o/', //ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0).
			'/Y/', // A full numeric representation of a year, 4 digits.
			'/y/', // A two digit representation of a year.
		);

		$datepicker_format = array(
			// day.
			'dd', // day of month (two digit).
			'D',  // day name short.
			'd',  // day of month (no leading zero).
			'DD', // day name long.
			// '',   //N - Equivalent does not exist in datePicker.
			// '',   //S - Equivalent does not exist in datePicker.
			// '',   //w - Equivalent does not exist in datePicker.
			'z' => 'o',  // The day of the year (starting from 0).

			// week.
			// '',   //W - Equivalent does not exist in datePicker.

			// month.
			'MM', // month name long.
			'mm', // month of year (two digit).
			'M',  // month name short.
			'm',  // month of year (no leading zero).
			// '',   //t - Equivalent does not exist in datePicker.

			// year.
			// '',   //L - Equivalent does not exist in datePicker.
			// '',   //o - Equivalent does not exist in datePicker.
			'yy', // year (four digit).
			'y',  // month name long.
		);

		return preg_replace($php_format, $datepicker_format, preg_quote($date_format, '/'));
	}


	/**
	 * Add lost password link to login form
	 */
	function leaky_paywall_add_lost_password_link()
	{
		return '<a id="leaky-paywall-lost-password-link" href="' . wp_lostpassword_url() . '">' . __('Lost Password?', 'leaky-paywall') . '</a>';
	}


	/**
	 * Get the payment gateways
	 *
	 * @return array $gateways The gateways.
	 */
	function leaky_paywall_payment_gateways()
	{
		$gateways = array(
			'manual'            => __('Manual', 'leaky-paywall'),
			'stripe'            => __('Stripe', 'leaky-paywall'),
			'paypal_standard'   => __('PayPal Standard', 'leaky-paywall'),
			'free_registration' => __('Free Registration', 'leaky-paywall'),
		);
		return apply_filters('leaky_paywall_subscriber_payment_gateways', $gateways);
	}


	/**
	 * Create a human readable interval
	 *
	 * @param string $interval_count The interval count.
	 * @param string $interval The interval.
	 */
	function leaky_paywall_human_readable_interval($interval_count, $interval)
	{

		if (0 >= $interval_count) {
			return __('for life', 'leaky-paywall');
		}

		if (1 < $interval_count) {
			$interval .= 's';
		}

		switch ($interval) {
			case 'day':
				$interval_str = __('day', 'leaky-paywall');
				break;
			case 'days':
				$interval_str = __('days', 'leaky-paywall');
				break;
			case 'week':
				$interval_str = __('week', 'leaky-paywall');
				break;
			case 'weeks':
				$interval_str = __('weeks', 'leaky-paywall');
				break;
			case 'month':
				$interval_str = __('month', 'leaky-paywall');
				break;
			case 'months':
				$interval_str = __('months', 'leaky-paywall');
				break;
			case 'year':
				$interval_str = __('year', 'leaky-paywall');
				break;
			case 'years':
				$interval_str = __('years', 'leaky-paywall');
				break;
			default:
				$interval_str = $interval;
				break;
		}

		if (1 === $interval_count) {
			return __('every', 'leaky-paywall') . ' ' . $interval_str;
		} else {
			return __('every', 'leaky-paywall') . ' ' . $interval_count . ' ' . $interval_str;
		}
	}


	/**
	 * Send email based on subscription status
	 *
	 * @param integer $user_id The user id.
	 * @param string  $status The status of the notification.
	 * @param array   $args The details of the subscriber.
	 */
	function leaky_paywall_email_subscription_status( $user_id, $status = 'new', $args = '' ) {

		if ( empty( $user_id ) ) {
			return;
		}

		$password = '';
		if ( ! empty( $args ) && is_array( $args ) ) {
			$password = isset( $args['password'] ) ? $args['password'] : '';
		}

		do_action( 'leaky_paywall_before_email_status', $user_id, $status );

		$emails     = LP_Emails::instance();
		$email_args = array( 'password' => $password, 'status' => $status );

		switch ( $status ) {
			case 'new':
			case 'update':
				$new_sub = $emails->get_email( 'new_subscriber' );
				if ( $new_sub ) {
					$new_sub->trigger( $user_id, $email_args );
				}

				$admin = $emails->get_email( 'admin_new_subscriber' );
				if ( $admin ) {
					$admin->trigger( $user_id, $email_args );
				}
				break;

			case 'renewal_reminder':
				$renewal = $emails->get_email( 'renewal_reminder' );
				if ( $renewal ) {
					$renewal->trigger( $user_id, $email_args );
				}
				break;
		}
	}


	/**
	 * Register cron job on plugin activation.
	 */
	function leaky_paywall_process_renewal_reminder_schedule()
	{

		if (!wp_next_scheduled('leaky_paywall_process_renewal_reminder')) {
			wp_schedule_event(time(), 'daily', 'leaky_paywall_process_renewal_reminder');
		}
	}
	add_action('admin_init', 'leaky_paywall_process_renewal_reminder_schedule');

	/**
	 * Remove our renewal reminder scheduled event if Leaky Paywall is deactivated
	 */
	function leaky_paywall_process_renewal_reminder_deactivation()
	{
		wp_clear_scheduled_hook('leaky_paywall_process_renewal_reminder');
	}
	register_deactivation_hook(__FILE__, 'leaky_paywall_process_renewal_reminder_deactivation');


	/**
	 * Process renewal reminder email for each Leaky Paywall subscriber
	 *
	 * @since 4.9.3
	 */
	function leaky_paywall_maybe_send_renewal_reminder()
	{

		$renewal_email = LP_Emails::instance()->get_email( 'renewal_reminder' );

		if ( ! $renewal_email || ! $renewal_email->is_enabled() ) {
			return;
		}

		if ( empty( $renewal_email->body ) ) {
			return;
		}

		$mode = leaky_paywall_get_current_mode();
		$site = leaky_paywall_get_current_site();

		leaky_paywall_log(current_time('Y-m-d'), 'process renewal reminder');

		$start_date = time(); // now
		$end_date = strtotime('+' . absint( $renewal_email->days_before ) . ' day'); // x days in the future

		$args = array(
			'number'     => 99,
			'meta_query' => array(
				'relation' => 'AND',
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_level_id' . $site,
					'compare' => 'EXISTS',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => array(gmdate('Y-m-d', $start_date), gmdate('Y-m-d', $end_date)),
					'compare' => 'BETWEEN',
					'type'    => 'DATE',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => '0000-00-00 00:00:00',
					'compare' => '!=',
				),

			),
		);

		$users = get_users($args);

		if (empty($users)) {
			return;
		}

		foreach ($users as $user) {

			$user_id    = $user->ID;
			$expiration = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_expires' . $site, true);
			$plan       = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_plan' . $site, true);

			// do not send renewal reminders to users with recurring plans.
			if (!empty($plan)) {
				continue;
			}

			// user does not have an expiration date sent, so we can't do the calculations needed.
			if (empty($expiration) || '0000-00-00 00:00:00' === $expiration) {
				continue;
			}

			// if expiration is the past, continue.
			if (strtotime($expiration) < current_time('timestamp')) {
				continue;
			}

			$already_emailed = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_renewal_emailed' . $site, true);

			if ($already_emailed) {
				continue;
			}

			leaky_paywall_email_subscription_status($user_id, 'renewal_reminder');
			update_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_renewal_emailed' . $site, current_time('timestamp'));
		}
	}
	add_action('leaky_paywall_process_renewal_reminder', 'leaky_paywall_maybe_send_renewal_reminder');


	/**
	 * Register the expiration check cron job on admin_init.
	 *
	 * @since 4.23.0
	 */
	function leaky_paywall_expiration_check_schedule() {
		if ( ! wp_next_scheduled( 'leaky_paywall_process_expiration_check' ) ) {
			wp_schedule_event( time(), 'daily', 'leaky_paywall_process_expiration_check' );
		}
	}
	add_action( 'admin_init', 'leaky_paywall_expiration_check_schedule' );

	/**
	 * Clear the expiration check cron on plugin deactivation.
	 *
	 * @since 4.23.0
	 */
	function leaky_paywall_expiration_check_deactivation() {
		wp_clear_scheduled_hook( 'leaky_paywall_process_expiration_check' );
	}
	register_deactivation_hook( __FILE__, 'leaky_paywall_expiration_check_deactivation' );

	/**
	 * Safety net: transition subscribers with access-granting statuses
	 * whose expiration dates have passed to 'expired'.
	 *
	 * Processes in batches to avoid memory issues on large sites.
	 *
	 * @since 4.23.0
	 */
	function leaky_paywall_process_expiration_check() {
		$mode = leaky_paywall_get_current_mode();
		$site = leaky_paywall_get_current_site();

		// Exclude pending_cancel — the subscriber keeps access until period end;
		// the gateway's subscription.deleted webhook handles the transition to expired.
		$cron_statuses = array_diff( leaky_paywall_access_statuses(), array( 'pending_cancel' ) );
		$cron_statuses = array_values( $cron_statuses );
		$expires_key   = '_issuem_leaky_paywall_' . $mode . '_expires' . $site;

		// Grace period: don't expire until 24 hours after expiration.
		// This gives payment gateways time to process renewals and send
		// webhooks before the cron steps in (prevents the end-of-February
		// race condition where Stripe invoices are pending but not yet paid).
		$grace_cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-24 hours' ) );

		$args = array(
			'number'     => 200,
			'meta_query' => array(
				'relation' => 'AND',
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site,
					'value'   => $cron_statuses,
					'compare' => 'IN',
				),
				array(
					'key'     => $expires_key,
					'value'   => $grace_cutoff,
					'compare' => '<',
					'type'    => 'DATETIME',
				),
				array(
					'key'     => $expires_key,
					'value'   => '0000-00-00 00:00:00',
					'compare' => '!=',
				),
				array(
					'key'     => $expires_key,
					'value'   => '0',
					'compare' => '!=',
				),
				array(
					'key'     => $expires_key,
					'value'   => '',
					'compare' => '!=',
				),
			),
		);

		$users = get_users( $args );

		if ( empty( $users ) ) {
			return;
		}

		foreach ( $users as $user ) {
			$status = get_user_meta( $user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site, true );

			// For past_due subscribers, verify with Stripe before expiring.
			if ( 'past_due' === $status ) {
				leaky_paywall_sync_past_due_subscriber( $user );
				continue;
			}

			leaky_paywall_set_subscriber_status( $user->ID, 'expired', 'cron' );
			do_action( 'leaky_paywall_cron_expired_subscriber', $user );
		}
	}
	add_action( 'leaky_paywall_process_expiration_check', 'leaky_paywall_process_expiration_check' );

	/**
	 * Sync a past_due subscriber with Stripe to determine if their subscription
	 * has been canceled. If no active subscription is found, expire them.
	 *
	 * @since 5.0.6
	 *
	 * @param WP_User $user The subscriber.
	 */
	function leaky_paywall_sync_past_due_subscriber( $user ) {
		$mode          = leaky_paywall_get_current_mode();
		$site          = leaky_paywall_get_current_site();
		$subscriber_id = lp_get_subscriber_meta( 'subscriber_id', $user );

		if ( ! $subscriber_id ) {
			leaky_paywall_set_subscriber_status( $user->ID, 'expired', 'cron' );
			do_action( 'leaky_paywall_cron_expired_subscriber', $user );
			return;
		}

		$gateway = get_user_meta( $user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_gateway' . $site, true );

		if ( 'stripe' !== $gateway && 'stripe_checkout' !== $gateway ) {
			leaky_paywall_set_subscriber_status( $user->ID, 'expired', 'cron' );
			do_action( 'leaky_paywall_cron_expired_subscriber', $user );
			return;
		}

		try {
			$stripe        = leaky_paywall_initialize_stripe_api();
			$subscriptions = $stripe->subscriptions->all( array(
				'customer' => $subscriber_id,
				'status'   => 'past_due',
				'limit'    => 1,
			), leaky_paywall_get_stripe_connect_params() );

			if ( ! empty( $subscriptions->data ) ) {
				// Stripe still says past_due — leave them alone, retries may still be in progress.
				return;
			}

			// No past_due subscription found — check if there's an active one.
			$active_subs = $stripe->subscriptions->all( array(
				'customer' => $subscriber_id,
				'status'   => 'active',
				'limit'    => 1,
			), leaky_paywall_get_stripe_connect_params() );

			if ( ! empty( $active_subs->data ) ) {
				leaky_paywall_set_subscriber_status( $user->ID, 'active', 'cron' );
				return;
			}

			// No active or past_due subscription — expire them.
			leaky_paywall_set_subscriber_status( $user->ID, 'expired', 'cron' );
			do_action( 'leaky_paywall_cron_expired_subscriber', $user );

		} catch ( \Throwable $th ) {
			leaky_paywall_log( $th->getMessage(), 'leaky paywall - past_due sync error for user ' . $user->ID );
		}
	}


	/**
	 * One-time migration to move email settings from the monolithic LP settings
	 * array to per-email wp_option keys.
	 *
	 * Converts the inverted checkbox logic ('on' = disabled) to standard
	 * 'yes'/'no' values.
	 *
	 * @since 4.23.0
	 */
	function lp_maybe_migrate_email_settings() {

		if ( get_option( 'lp_email_settings_migrated' ) ) {
			return;
		}

		$settings = get_option( 'issuem-leaky-paywall' );

		if ( ! $settings ) {
			update_option( 'lp_email_settings_migrated', '1' );
			return;
		}

		// New Subscriber Email.
		// Old inverted logic: 'on' = disabled, 'off' = enabled.
		$new_sub = array(
			'enabled' => ( isset( $settings['new_subscriber_email'] ) && 'on' === $settings['new_subscriber_email'] ) ? 'no' : 'yes',
			'subject' => isset( $settings['new_email_subject'] ) ? $settings['new_email_subject'] : '',
			'body'    => isset( $settings['new_email_body'] ) ? $settings['new_email_body'] : '',
		);
		update_option( 'leaky_paywall_email_new_subscriber_settings', $new_sub );

		// Admin New Subscriber Email.
		$admin_new = array(
			'enabled'    => ( isset( $settings['new_subscriber_admin_email'] ) && 'on' === $settings['new_subscriber_admin_email'] ) ? 'no' : 'yes',
			'subject'    => isset( $settings['admin_new_subscriber_email_subject'] ) ? $settings['admin_new_subscriber_email_subject'] : '',
			'body'       => '',
			'recipients' => isset( $settings['admin_new_subscriber_email_recipients'] ) ? $settings['admin_new_subscriber_email_recipients'] : get_option( 'admin_email' ),
		);
		update_option( 'leaky_paywall_email_admin_new_subscriber_settings', $admin_new );

		// Renewal Reminder Email.
		$renewal = array(
			'enabled'     => ( isset( $settings['renewal_reminder_email'] ) && 'on' === $settings['renewal_reminder_email'] ) ? 'no' : 'yes',
			'subject'     => isset( $settings['renewal_reminder_email_subject'] ) ? $settings['renewal_reminder_email_subject'] : '',
			'body'        => isset( $settings['renewal_reminder_email_body'] ) ? $settings['renewal_reminder_email_body'] : '',
			'days_before' => isset( $settings['renewal_reminder_days_before'] ) ? $settings['renewal_reminder_days_before'] : '7',
		);
		update_option( 'leaky_paywall_email_renewal_reminder_settings', $renewal );

		update_option( 'lp_email_settings_migrated', '1' );

		leaky_paywall_log( 'Email settings migrated to per-email options', 'migration' );
	}
	add_action( 'admin_init', 'lp_maybe_migrate_email_settings', 5 );


	/**
	 * Schedule the status migration to run in the background via Action Scheduler.
	 *
	 * @since 4.23.0
	 * @since 4.24.0 Moved from admin_init to Action Scheduler for performance.
	 */
	function leaky_paywall_maybe_schedule_status_migration() {
		if ( get_option( 'leaky_paywall_status_migration_v2' ) ) {
			return;
		}

		if ( as_has_scheduled_action( 'leaky_paywall_run_status_migration_batch' ) ) {
			return;
		}

		as_enqueue_async_action( 'leaky_paywall_run_status_migration_batch' );
	}
	add_action( 'admin_init', 'leaky_paywall_maybe_schedule_status_migration' );

	/**
	 * One-time migration to align existing subscriber statuses with the new
	 * status-as-source-of-truth system. Runs in the background via Action Scheduler.
	 *
	 * 1. canceled Stripe subscribers → sync with Stripe for authoritative status
	 * 2. canceled non-Stripe subscribers + future expiration → pending_cancel
	 * 3. active/trial + past expiration + no recurring plan → expired
	 * 4. remaining canceled non-Stripe subscribers + past/no expiration → expired
	 *
	 * @since 4.23.0
	 * @since 4.24.0 Runs as an async Action Scheduler batch instead of on admin_init.
	 * @since 4.25.0 Sync canceled Stripe subscribers with Stripe before migrating.
	 */
	function leaky_paywall_run_status_migration_batch() {

		$mode      = leaky_paywall_get_current_mode();
		$site      = leaky_paywall_get_current_site();
		$now       = gmdate( 'Y-m-d H:i:s' );
		$batch     = 100;
		$processed = 0;

		// 1. Canceled Stripe subscribers → sync with Stripe for authoritative status.
		// The LP expires meta may not reflect the actual Stripe subscription period end,
		// so we check Stripe directly instead of relying on the local expires date.
		$canceled_stripe_users = get_users( array(
			'number'     => $batch,
			'meta_query' => array(
				'relation' => 'AND',
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site,
					'value'   => 'canceled',
					'compare' => '=',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_subscriber_id' . $site,
					'value'   => 'cus_',
					'compare' => 'LIKE',
				),
			),
		) );

		foreach ( $canceled_stripe_users as $user ) {
			if ( function_exists( 'leaky_paywall_sync_stripe_subscription' ) ) {
				leaky_paywall_sync_stripe_subscription( $user );
			} else {
				leaky_paywall_set_subscriber_status( $user->ID, 'expired', 'migration' );
			}
			$processed++;
		}

		// 2. Canceled non-Stripe subscribers with future expiration → pending_cancel.
		$users_to_pending = get_users( array(
			'number'     => $batch,
			'meta_query' => array(
				'relation' => 'AND',
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site,
					'value'   => 'canceled',
					'compare' => '=',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => $now,
					'compare' => '>',
					'type'    => 'DATETIME',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => '0000-00-00 00:00:00',
					'compare' => '!=',
				),
			),
		) );

		foreach ( $users_to_pending as $user ) {
			leaky_paywall_set_subscriber_status( $user->ID, 'pending_cancel', 'migration' );
			$processed++;
		}

		// 3. Active/trial subscribers with past expiration + no recurring plan → expired.
		// Note: Active subscribers with a recurring plan are skipped — their subscription may still be valid.
		$users_to_expire = get_users( array(
			'number'     => $batch,
			'meta_query' => array(
				'relation' => 'AND',
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site,
					'value'   => array( 'active', 'trial' ),
					'compare' => 'IN',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => $now,
					'compare' => '<',
					'type'    => 'DATETIME',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => '0000-00-00 00:00:00',
					'compare' => '!=',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => '0',
					'compare' => '!=',
				),
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
					'value'   => '',
					'compare' => '!=',
				),
			),
		) );

		foreach ( $users_to_expire as $user ) {
			$old_status = get_user_meta( $user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site, true );
			$plan       = get_user_meta( $user->ID, '_issuem_leaky_paywall_' . $mode . '_plan' . $site, true );

			// Skip active recurring subscribers — their subscription may still be valid.
			if ( 'active' === $old_status && ! empty( $plan ) ) {
				continue;
			}

			leaky_paywall_set_subscriber_status( $user->ID, 'expired', 'migration' );
			$processed++;
		}

		// 4. Remaining canceled non-Stripe subscribers with past or no expiration → expired.
		$users_canceled_expired = get_users( array(
			'number'     => $batch,
			'meta_query' => array(
				'relation' => 'AND',
				array(
					'key'     => '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site,
					'value'   => 'canceled',
					'compare' => '=',
				),
				array(
					'relation' => 'OR',
					array(
						'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
						'value'   => $now,
						'compare' => '<',
						'type'    => 'DATETIME',
					),
					array(
						'key'     => '_issuem_leaky_paywall_' . $mode . '_expires' . $site,
						'value'   => '0000-00-00 00:00:00',
						'compare' => '=',
					),
				),
			),
		) );

		foreach ( $users_canceled_expired as $user ) {
			leaky_paywall_set_subscriber_status( $user->ID, 'expired', 'migration' );
			$processed++;
		}

		if ( 0 === $processed ) {
			update_option( 'leaky_paywall_status_migration_v2', true );
			leaky_paywall_log( 'Status migration v2 complete', 'migration' );
		} else {
			leaky_paywall_log( 'Migrated ' . $processed . ' subscribers, scheduling next batch', 'migration' );
			as_enqueue_async_action( 'leaky_paywall_run_status_migration_batch' );
		}
	}
	add_action( 'leaky_paywall_run_status_migration_batch', 'leaky_paywall_run_status_migration_batch' );


	/**
	 * Calculate the differce between two date values
	 *
	 * @since 4.9.3
	 * @param string $date_1 The first date.
	 * @param string $date_2 The second date.
	 * @param string $difference_format The difference format.
	 * @return string
	 */
	function leaky_paywall_date_difference($date_1, $date_2, $difference_format = '%a')
	{

		$datetime1 = date_create($date_1);
		$datetime2 = date_create($date_2);

		$interval = date_diff($datetime1, $datetime2);

		return $interval->format($difference_format);
	}

	/**
	 * Set email content type
	 *
	 * @param string $content_type The content type.
	 * @return string
	 */
	function leaky_paywall_set_email_content_type($content_type)
	{
		return 'text/html';
	}


	/**
	 * Filter email tags
	 *
	 * @param string $message The email message.
	 * @param int    $user_id The user id.
	 * @param string $display_name The display name of the user.
	 * @param string $password The password of the user.
	 * @return string
	 */
	function leaky_paywall_filter_email_tags($message, $user_id, $display_name, $password)
	{

		$user = get_userdata($user_id);

		if ( ! $user ) {
			return $message;
		}

		$site_name = stripslashes_deep(html_entity_decode(get_bloginfo('name'), ENT_COMPAT, 'UTF-8'));

		$values = array(
			'blogname'    => $site_name,
			'sitename'    => $site_name,
			'username'    => $user->user_login,
			'useremail'   => $user->user_email,
			'firstname'   => $user->user_firstname,
			'lastname'    => $user->user_lastname,
			'displayname' => $display_name,
			'password'    => $password,
		);

		// Match %token% or %token|fallback%. The fallback supplies a default when
		// the token resolves to an empty string (e.g. %firstname|there% renders
		// "there" for subscribers with no first name on file).
		$message = preg_replace_callback(
			'/%([a-z_][a-z0-9_]*)(?:\|([^%]*))?%/',
			function ( $m ) use ( $values ) {
				if ( ! array_key_exists( $m[1], $values ) ) {
					return $m[0];
				}

				$value = (string) $values[ $m[1] ];

				if ( '' !== $value ) {
					return $value;
				}

				return isset( $m[2] ) ? trim( $m[2] ) : '';
			},
			$message
		);

		return $message;
	}


	/**
	 * Get currencies supported by Leaky Paywall
	 *
	 * @return array
	 */
	function leaky_paywall_supported_currencies()
	{
		$currencies = array(
			'AED' => array(
				'symbol'  => '&#1583;.&#1573;',
				'label'   => __('UAE dirham', 'leaky-paywall'),
				'country' => __('UAE', 'leaky-paywall'),
			),
			'AFN' => array(
				'symbol'  => 'Afs',
				'label'   => __('Afghan afghani', 'leaky-paywall'),
				'country' => __('Afghanistan', 'leaky-paywall'),
			),
			'ALL' => array(
				'symbol'  => 'L',
				'label'   => __('Albanian lek', 'leaky-paywall'),
				'country' => __('Albania', 'leaky-paywall'),
			),
			'AMD' => array(
				'symbol'  => 'AMD',
				'label'   => __('Armenian dram', 'leaky-paywall'),
				'country' => __('Armenia', 'leaky-paywall'),
			),
			'ANG' => array(
				'symbol'  => 'NA&#402;',
				'label'   => __('Netherlands Antillean gulden', 'leaky-paywall'),
				'country' => __('Netherlands', 'leaky-paywall'),
			),
			'AOA' => array(
				'symbol'  => 'Kz',
				'label'   => __('Angolan kwanza', 'leaky-paywall'),
				'country' => __('Angolia', 'leaky-paywall'),
			),
			'ARS' => array(
				'symbol'  => '$',
				'label'   => __('Argentine peso', 'leaky-paywall'),
				'country' => __('Argentina', 'leaky-paywall'),
			),
			'AUD' => array(
				'symbol'  => '$',
				'label'   => __('Australian dollar', 'leaky-paywall'),
				'country' => __('Australia', 'leaky-paywall'),
			),
			'AWG' => array(
				'symbol'  => '&#402;',
				'label'   => __('Aruban florin', 'leaky-paywall'),
				'country' => __('Aruba', 'leaky-paywall'),
			),
			'AZN' => array(
				'symbol'  => 'AZN',
				'label'   => __('Azerbaijani manat', 'leaky-paywall'),
				'country' => __('Azerbaij', 'leaky-paywall'),
			),
			'BAM' => array(
				'symbol'  => 'KM',
				'label'   => __('Bosnia and Herzegovina konvertibilna marka', 'leaky-paywall'),
				'country' => __('Bosnia', 'leaky-paywall'),
			),
			'BBD' => array(
				'symbol'  => 'Bds$',
				'label'   => __('Barbadian dollar', 'leaky-paywall'),
				'country' => __('Barbadian', 'leaky-paywall'),
			),
			'BDT' => array(
				'symbol'  => '&#2547;',
				'label'   => __('Bangladeshi taka', 'leaky-paywall'),
				'country' => __('Bangladesh', 'leaky-paywall'),
			),
			'BGN' => array(
				'symbol'  => 'BGN',
				'label'   => __('Bulgarian lev', 'leaky-paywall'),
				'country' => __('Bulgaria', 'leaky-paywall'),
			),
			'BIF' => array(
				'symbol'  => 'FBu',
				'label'   => __('Burundi franc', 'leaky-paywall'),
				'country' => __('Burundi', 'leaky-paywall'),
			),
			'BMD' => array(
				'symbol'  => 'BD$',
				'label'   => __('Bermudian dollar', 'leaky-paywall'),
				'country' => __('Bermuda', 'leaky-paywall'),
			),
			'BND' => array(
				'symbol'  => 'B$',
				'label'   => __('Brunei dollar', 'leaky-paywall'),
				'country' => __('Brunei', 'leaky-paywall'),
			),
			'BOB' => array(
				'symbol'  => 'Bs.',
				'label'   => __('Bolivian boliviano', 'leaky-paywall'),
				'country' => __('Bolivia', 'leaky-paywall'),
			),
			'BRL' => array(
				'symbol'  => 'R$',
				'label'   => __('Brazilian real', 'leaky-paywall'),
				'country' => __('Brazil', 'leaky-paywall'),
			),
			'BSD' => array(
				'symbol'  => 'B$',
				'label'   => __('Bahamian dollar', 'leaky-paywall'),
				'country' => __('Bahamas', 'leaky-paywall'),
			),
			'BWP' => array(
				'symbol'  => 'P',
				'label'   => __('Botswana pula', 'leaky-paywall'),
				'country' => __('Botswana', 'leaky-paywall'),
			),
			'BZD' => array(
				'symbol'  => 'BZ$',
				'label'   => __('Belize dollar', 'leaky-paywall'),
				'country' => __('Belize', 'leaky-paywall'),
			),
			'CAD' => array(
				'symbol'  => '$',
				'label'   => __('Canadian dollar', 'leaky-paywall'),
				'country' => __('Canada', 'leaky-paywall'),
			),
			'CDF' => array(
				'symbol'  => 'F',
				'label'   => __('Congolese franc', 'leaky-paywall'),
				'country' => __('Congo', 'leaky-paywall'),
			),
			'CHF' => array(
				'symbol'  => 'CHF',
				'label'   => __('Swiss franc', 'leaky-paywall'),
				'country' => __('Switzerland', 'leaky-paywall'),
			),
			'CLP' => array(
				'symbol'  => '$',
				'label'   => __('Chilean peso', 'leaky-paywall'),
				'country' => __('Chili', 'leaky-paywall'),
			),
			'CNY' => array(
				'symbol'  => '&#165;',
				'label'   => __('Chinese Yuan Renminbi', 'leaky-paywall'),
				'country' => __('Chinese Yuan', 'leaky-paywall'),
			),
			'COP' => array(
				'symbol'  => 'Col$',
				'label'   => __('Colombian peso', 'leaky-paywall'),
				'country' => __('Colombia', 'leaky-paywall'),
			),
			'CRC' => array(
				'symbol'  => '&#8353;',
				'label'   => __('Costa Rican colon', 'leaky-paywall'),
				'country' => __('Costa Rica', 'leaky-paywall'),
			),
			'CVE' => array(
				'symbol'  => 'Esc',
				'label'   => __('Cape Verdean escudo', 'leaky-paywall'),
				'country' => __('Cape Verde', 'leaky-paywall'),
			),
			'CZK' => array(
				'symbol'  => 'K&#269;',
				'label'   => __('Czech koruna', 'leaky-paywall'),
				'country' => __('Czech', 'leaky-paywall'),
			),
			'DJF' => array(
				'symbol'  => 'Fdj',
				'label'   => __('Djiboutian franc', 'leaky-paywall'),
				'country' => __('Djibouti', 'leaky-paywall'),
			),
			'DKK' => array(
				'symbol'  => 'kr',
				'label'   => __('Danish krone', 'leaky-paywall'),
				'country' => __('Danish', 'leaky-paywall'),
			),
			'DOP' => array(
				'symbol'  => 'RD$',
				'label'   => __('Dominican peso', 'leaky-paywall'),
				'country' => __('Dominican Republic', 'leaky-paywall'),
			),
			'DZD' => array(
				'symbol'  => '&#1583;.&#1580;',
				'label'   => __('Algerian dinar', 'leaky-paywall'),
				'country' => __('Algeria', 'leaky-paywall'),
			),
			'EEK' => array(
				'symbol'  => 'KR',
				'label'   => __('Estonian kroon', 'leaky-paywall'),
				'country' => __('Estonia', 'leaky-paywall'),
			),
			'EGP' => array(
				'symbol'  => '&#163;',
				'label'   => __('Egyptian pound', 'leaky-paywall'),
				'country' => __('Egypt', 'leaky-paywall'),
			),
			'ETB' => array(
				'symbol'  => 'Br',
				'label'   => __('Ethiopian birr', 'leaky-paywall'),
				'country' => __('Ethiopia', 'leaky-paywall'),
			),
			'EUR' => array(
				'symbol'  => '&#8364;',
				'label'   => __('European Euro', 'leaky-paywall'),
				'country' => __('Euro', 'leaky-paywall'),
			),
			'FJD' => array(
				'symbol'  => 'FJ$',
				'label'   => __('Fijian dollar', 'leaky-paywall'),
				'country' => __('Fiji', 'leaky-paywall'),
			),
			'FKP' => array(
				'symbol'  => '&#163;',
				'label'   => __('Falkland Islands pound', 'leaky-paywall'),
				'country' => __('Falkland Islands', 'leaky-paywall'),
			),
			'GBP' => array(
				'symbol'  => '&#163;',
				'label'   => __('British pound', 'leaky-paywall'),
				'country' => __('Great Britian', 'leaky-paywall'),
			),
			'GEL' => array(
				'symbol'  => 'GEL',
				'label'   => __('Georgian lari', 'leaky-paywall'),
				'country' => __('Georgia', 'leaky-paywall'),
			),
			'GIP' => array(
				'symbol'  => '&#163;',
				'label'   => __('Gibraltar pound', 'leaky-paywall'),
				'country' => __('Gibraltar', 'leaky-paywall'),
			),
			'GMD' => array(
				'symbol'  => 'D',
				'label'   => __('Gambian dalasi', 'leaky-paywall'),
				'country' => __('Gambia', 'leaky-paywall'),
			),
			'GNF' => array(
				'symbol'  => 'FG',
				'label'   => __('Guinean franc', 'leaky-paywall'),
				'country' => __('Guinea', 'leaky-paywall'),
			),
			'GTQ' => array(
				'symbol'  => 'Q',
				'label'   => __('Guatemalan quetzal', 'leaky-paywall'),
				'country' => __('Guatemala', 'leaky-paywall'),
			),
			'GYD' => array(
				'symbol'  => 'GY$',
				'label'   => __('Guyanese dollar', 'leaky-paywall'),
				'country' => __('Guyanese', 'leaky-paywall'),
			),
			'HKD' => array(
				'symbol'  => 'HK$',
				'label'   => __('Hong Kong dollar', 'leaky-paywall'),
				'country' => __('Hong Kong', 'leaky-paywall'),
			),
			'HNL' => array(
				'symbol'  => 'L',
				'label'   => __('Honduran lempira', 'leaky-paywall'),
				'country' => __('Honduras', 'leaky-paywall'),
			),
			'HRK' => array(
				'symbol'  => 'kn',
				'label'   => __('Croatian kuna', 'leaky-paywall'),
				'country' => __('Croatia', 'leaky-paywall'),
			),
			'HTG' => array(
				'symbol'  => 'G',
				'label'   => __('Haitian gourde', 'leaky-paywall'),
				'country' => __('Haiti', 'leaky-paywall'),
			),
			'HUF' => array(
				'symbol'  => 'Ft',
				'label'   => __('Hungarian forint', 'leaky-paywall'),
				'country' => __('Hungary', 'leaky-paywall'),
			),
			'IDR' => array(
				'symbol'  => 'Rp',
				'label'   => __('Indonesian rupiah', 'leaky-paywall'),
				'country' => __('Idonesia', 'leaky-paywall'),
			),
			'ILS' => array(
				'symbol'  => '&#8362;',
				'label'   => __('Israeli new sheqel', 'leaky-paywall'),
				'country' => __('Israel', 'leaky-paywall'),
			),
			'INR' => array(
				'symbol'  => '&#8377;',
				'label'   => __('Indian rupee', 'leaky-paywall'),
				'country' => __('India', 'leaky-paywall'),
			),
			'ISK' => array(
				'symbol'  => 'kr',
				'label'   => __('Icelandic króna', 'leaky-paywall'),
				'country' => __('Iceland', 'leaky-paywall'),
			),
			'JMD' => array(
				'symbol'  => 'J$',
				'label'   => __('Jamaican dollar', 'leaky-paywall'),
				'country' => __('Jamaica', 'leaky-paywall'),
			),
			'JPY' => array(
				'symbol'  => '&#165;',
				'label'   => __('Japanese yen', 'leaky-paywall'),
				'country' => __('Japan', 'leaky-paywall'),
			),
			'KES' => array(
				'symbol'  => 'KSh',
				'label'   => __('Kenyan shilling', 'leaky-paywall'),
				'country' => __('Kenya', 'leaky-paywall'),
			),
			'KGS' => array(
				'symbol'  => '&#1089;&#1086;&#1084;',
				'label'   => __('Kyrgyzstani som', 'leaky-paywall'),
				'country' => __('Kyrgyzstan', 'leaky-paywall'),
			),
			'KHR' => array(
				'symbol'  => '&#6107;',
				'label'   => __('Cambodian riel', 'leaky-paywall'),
				'country' => __('Cambodia', 'leaky-paywall'),
			),
			'KMF' => array(
				'symbol'  => 'KMF',
				'label'   => __('Comorian franc', 'leaky-paywall'),
				'country' => __('Comorian', 'leaky-paywall'),
			),
			'KRW' => array(
				'symbol'  => 'W',
				'label'   => __('South Korean won', 'leaky-paywall'),
				'country' => __('South Korea', 'leaky-paywall'),
			),
			'KYD' => array(
				'symbol'  => 'KY$',
				'label'   => __('Cayman Islands dollar', 'leaky-paywall'),
				'country' => __('Cayman Islands', 'leaky-paywall'),
			),
			'KZT' => array(
				'symbol'  => 'T',
				'label'   => __('Kazakhstani tenge', 'leaky-paywall'),
				'country' => __('Kazakhstan', 'leaky-paywall'),
			),
			'LAK' => array(
				'symbol'  => 'KN',
				'label'   => __('Lao kip', 'leaky-paywall'),
				'country' => __('Loa', 'leaky-paywall'),
			),
			'LBP' => array(
				'symbol'  => '&#163;',
				'label'   => __('Lebanese lira', 'leaky-paywall'),
				'country' => __('Lebanese', 'leaky-paywall'),
			),
			'LKR' => array(
				'symbol'  => 'Rs',
				'label'   => __('Sri Lankan rupee', 'leaky-paywall'),
				'country' => __('Sri Lanka', 'leaky-paywall'),
			),
			'LRD' => array(
				'symbol'  => 'L$',
				'label'   => __('Liberian dollar', 'leaky-paywall'),
				'country' => __('Liberia', 'leaky-paywall'),
			),
			'LSL' => array(
				'symbol'  => 'M',
				'label'   => __('Lesotho loti', 'leaky-paywall'),
				'country' => __('Lesotho', 'leaky-paywall'),
			),
			'LTL' => array(
				'symbol'  => 'Lt',
				'label'   => __('Lithuanian litas', 'leaky-paywall'),
				'country' => __('Lithuania', 'leaky-paywall'),
			),
			'LVL' => array(
				'symbol'  => 'Ls',
				'label'   => __('Latvian lats', 'leaky-paywall'),
				'country' => __('Latvia', 'leaky-paywall'),
			),
			'MAD' => array(
				'symbol'  => 'MAD',
				'label'   => __('Moroccan dirham', 'leaky-paywall'),
				'country' => __('Morocco', 'leaky-paywall'),
			),
			'MDL' => array(
				'symbol'  => 'MDL',
				'label'   => __('Moldovan leu', 'leaky-paywall'),
				'country' => __('Moldova', 'leaky-paywall'),
			),
			'MGA' => array(
				'symbol'  => 'FMG',
				'label'   => __('Malagasy ariary', 'leaky-paywall'),
				'country' => __('Malagasy', 'leaky-paywall'),
			),
			'MKD' => array(
				'symbol'  => 'MKD',
				'label'   => __('Macedonian denar', 'leaky-paywall'),
				'country' => __('Macedonia', 'leaky-paywall'),
			),
			'MNT' => array(
				'symbol'  => '&#8366;',
				'label'   => __('Mongolian tugrik', 'leaky-paywall'),
				'country' => __('Mongolia', 'leaky-paywall'),
			),
			'MOP' => array(
				'symbol'  => 'P',
				'label'   => __('Macanese pataca', 'leaky-paywall'),
				'country' => __('Macanese', 'leaky-paywall'),
			),
			'MRO' => array(
				'symbol'  => 'UM',
				'label'   => __('Mauritanian ouguiya', 'leaky-paywall'),
				'country' => '',
			),
			'MUR' => array(
				'symbol'  => 'Rs',
				'label'   => __('Mauritian rupee', 'leaky-paywall'),
				'country' => '',
			),
			'MVR' => array(
				'symbol'  => 'Rf',
				'label'   => __('Maldivian rufiyaa', 'leaky-paywall'),
				'country' => '',
			),
			'MWK' => array(
				'symbol'  => 'MK',
				'label'   => __('Malawian kwacha', 'leaky-paywall'),
				'country' => '',
			),
			'MXN' => array(
				'symbol'  => '$',
				'label'   => __('Mexican peso', 'leaky-paywall'),
				'country' => '',
			),
			'MYR' => array(
				'symbol'  => 'RM',
				'label'   => __('Malaysian ringgit', 'leaky-paywall'),
				'country' => '',
			),
			'MZN' => array(
				'symbol'  => 'MT',
				'label'   => __('Mozambique Metical', 'leaky-paywall'),
				'country' => '',
			),
			'NAD' => array(
				'symbol'  => 'N$',
				'label'   => __('Namibian dollar', 'leaky-paywall'),
				'country' => '',
			),
			'NGN' => array(
				'symbol'  => '&#8358;',
				'label'   => __('Nigerian naira', 'leaky-paywall'),
				'country' => '',
			),
			'NIO' => array(
				'symbol'  => 'C$',
				'label'   => __('Nicaraguan Córdoba', 'leaky-paywall'),
				'country' => '',
			),
			'NOK' => array(
				'symbol'  => 'kr',
				'label'   => __('Norwegian krone', 'leaky-paywall'),
				'country' => '',
			),
			'NPR' => array(
				'symbol'  => 'NRs',
				'label'   => __('Nepalese rupee', 'leaky-paywall'),
				'country' => '',
			),
			'NZD' => array(
				'symbol'  => 'NZ$',
				'label'   => __('New Zealand dollar', 'leaky-paywall'),
				'country' => '',
			),
			'PAB' => array(
				'symbol'  => 'B./',
				'label'   => __('Panamanian balboa', 'leaky-paywall'),
				'country' => '',
			),
			'PEN' => array(
				'symbol'  => 'S/.',
				'label'   => __('Peruvian nuevo sol', 'leaky-paywall'),
				'country' => '',
			),
			'PGK' => array(
				'symbol'  => 'K',
				'label'   => __('Papua New Guinean kina', 'leaky-paywall'),
				'country' => '',
			),
			'PHP' => array(
				'symbol'  => '&#8369;',
				'label'   => __('Philippine peso', 'leaky-paywall'),
				'country' => '',
			),
			'PKR' => array(
				'symbol'  => 'Rs.',
				'label'   => __('Pakistani rupee', 'leaky-paywall'),
				'country' => '',
			),
			'PLN' => array(
				'symbol'  => 'z&#322;',
				'label'   => __('Polish zloty', 'leaky-paywall'),
				'country' => '',
			),
			'PYG' => array(
				'symbol'  => '&#8370;',
				'label'   => __('Paraguayan guarani', 'leaky-paywall'),
				'country' => '',
			),
			'QAR' => array(
				'symbol'  => 'QR',
				'label'   => __('Qatari riyal', 'leaky-paywall'),
				'country' => '',
			),
			'RON' => array(
				'symbol'  => 'L',
				'label'   => __('Romanian leu', 'leaky-paywall'),
				'country' => '',
			),
			'RSD' => array(
				'symbol'  => 'din.',
				'label'   => __('Serbian dinar', 'leaky-paywall'),
				'country' => '',
			),
			'RUB' => array(
				'symbol'  => 'R',
				'label'   => __('Russian ruble', 'leaky-paywall'),
				'country' => '',
			),
			'RWF' => array(
				'symbol'  => 'R&#8355;',
				'label'   => __('Rwandan Franc'),
				'country' => '',
			),
			'SAR' => array(
				'symbol' => 'SR',
				'label'  => __('Saudi riyal', 'leaky-paywall'),
			),
			'SBD' => array(
				'symbol'  => 'SI$',
				'label'   => __('Solomon Islands dollar', 'leaky-paywall'),
				'country' => '',
			),
			'SCR' => array(
				'symbol'  => 'SR',
				'label'   => __('Seychellois rupee', 'leaky-paywall'),
				'country' => '',
			),
			'SEK' => array(
				'symbol'  => 'kr',
				'label'   => __('Swedish krona', 'leaky-paywall'),
				'country' => '',
			),
			'SGD' => array(
				'symbol'  => 'S$',
				'label'   => __('Singapore dollar', 'leaky-paywall'),
				'country' => '',
			),
			'SHP' => array(
				'symbol'  => '&#163;',
				'label'   => __('Saint Helena pound', 'leaky-paywall'),
				'country' => '',
			),
			'SLL' => array(
				'symbol'  => 'Le',
				'label'   => __('Sierra Leonean leone', 'leaky-paywall'),
				'country' => '',
			),
			'SOS' => array(
				'symbol'  => 'Sh.',
				'label'   => __('Somali shilling', 'leaky-paywall'),
				'country' => '',
			),
			'SRD' => array(
				'symbol'  => '$',
				'label'   => __('Surinamese dollar', 'leaky-paywall'),
				'country' => '',
			),
			'STD' => array(
				'symbol'  => 'STD',
				'label'   => __('São Tomé and Príncipe Dobra', 'leaky-paywall'),
				'country' => '',
			),
			'SVC' => array(
				'symbol'  => '$',
				'label'   => __('El Salvador Colon', 'leaky-paywall'),
				'country' => '',
			),
			'SZL' => array(
				'symbol'  => 'E',
				'label'   => __('Swazi lilangeni', 'leaky-paywall'),
				'country' => '',
			),
			'THB' => array(
				'symbol'  => '&#3647;',
				'label'   => __('Thai baht', 'leaky-paywall'),
				'country' => '',
			),
			'TJS' => array(
				'symbol'  => 'TJS',
				'label'   => __('Tajikistani somoni', 'leaky-paywall'),
				'country' => '',
			),
			'TOP' => array(
				'symbol'  => 'T$',
				'label'   => __("Tonga Pa'anga", 'leaky-paywall'),
				'country' => '',
			),
			'TRY' => array(
				'symbol'  => 'TRY',
				'label'   => __('Turkish new lira', 'leaky-paywall'),
				'country' => '',
			),
			'TTD' => array(
				'symbol'  => 'TT$',
				'label'   => __('Trinidad and Tobago dollar', 'leaky-paywall'),
				'country' => '',
			),
			'TWD' => array(
				'symbol'  => 'NT$',
				'label'   => __('New Taiwan dollar', 'leaky-paywall'),
				'country' => '',
			),
			'TZS' => array(
				'symbol'  => 'TZS',
				'label'   => __('Tanzanian shilling', 'leaky-paywall'),
				'country' => '',
			),
			'UAH' => array(
				'symbol'  => 'UAH',
				'label'   => __('Ukrainian hryvnia', 'leaky-paywall'),
				'country' => '',
			),
			'UGX' => array(
				'symbol'  => 'USh',
				'label'   => __('Ugandan shilling', 'leaky-paywall'),
				'country' => '',
			),
			'USD' => array(
				'symbol'  => '$',
				'label'   => __('United States dollar', 'leaky-paywall'),
				'country' => __('United States', 'leaky-paywall'),
			),
			'UYU' => array(
				'symbol'  => '$U',
				'label'   => __('Uruguayan peso', 'leaky-paywall'),
				'country' => '',
			),
			'UZS' => array(
				'symbol'  => 'UZS',
				'label'   => __('Uzbekistani som', 'leaky-paywall'),
				'country' => '',
			),
			'VND' => array(
				'symbol'  => '&#8363;',
				'label'   => __('Vietnamese dong', 'leaky-paywall'),
				'country' => '',
			),
			'VUV' => array(
				'symbol'  => 'VT',
				'label'   => __('Vanuatu vatu', 'leaky-paywall'),
				'country' => '',
			),
			'WST' => array(
				'symbol'  => 'WS$',
				'label'   => __('Samoan tala', 'leaky-paywall'),
				'country' => '',
			),
			'XAF' => array(
				'symbol'  => 'CFA',
				'label'   => __('Central African CFA franc', 'leaky-paywall'),
				'country' => '',
			),
			'XCD' => array(
				'symbol'  => 'EC$',
				'label'   => __('East Caribbean dollar', 'leaky-paywall'),
				'country' => '',
			),
			'XOF' => array(
				'symbol'  => 'CFA',
				'label'   => __('West African CFA franc', 'leaky-paywall'),
				'country' => '',
			),
			'XPF' => array(
				'symbol'  => 'F',
				'label'   => __('CFP franc', 'leaky-paywall'),
				'country' => '',
			),
			'YER' => array(
				'symbol'  => 'YER',
				'label'   => __('Yemeni rial', 'leaky-paywall'),
				'country' => '',
			),
			'ZAR' => array(
				'symbol'  => 'R',
				'label'   => __('South African rand', 'leaky-paywall'),
				'country' => '',
			),
			'ZMW' => array(
				'symbol'  => 'ZK',
				'label'   => __('Zambian kwacha', 'leaky-paywall'),
				'country' => '',
			),
		);

		return apply_filters('leaky_paywall_supported_currencies', $currencies);
	}

	if (!function_exists('zeen101_dot_com_leaky_rss_feed_check')) {

		/**
		 * Check leakypaywall.com for new RSS items in the leaky blast feed, to update users of latest Leaky Paywall news
		 *
		 * @since 1.1.1
		 */
		function zeen101_dot_com_leaky_rss_feed_check()
		{

			include_once ABSPATH . WPINC . '/feed.php';

			$output  = '';
			$feedurl = 'https://leakypaywall.com/feed/?post_type=blast&target=leaky-paywall';

			$rss = fetch_feed($feedurl);

			if ($rss && !is_wp_error($rss)) {

				$rss_items = $rss->get_items(0, 1);

				foreach ($rss_items as $item) {

					$last_rss_item = get_option('last_zeen101_dot_com_leaky_rss_item');

					$latest_rss_item = $item->get_content();

					if ($last_rss_item !== $latest_rss_item) {

						$current_user = wp_get_current_user();

						update_option('last_zeen101_dot_com_leaky_rss_item', $latest_rss_item);

						update_user_meta($current_user->ID, 'leaky_paywall_rss_item_notice_link', 0);
					}
				}
			}
		}
		add_action('zeen101_dot_com_leaky_rss_feed_check', 'zeen101_dot_com_leaky_rss_feed_check');

		if (!wp_next_scheduled('zeen101_dot_com_leaky_rss_feed_check')) {
			wp_schedule_event(time(), 'daily', 'zeen101_dot_com_leaky_rss_feed_check');
		}
	}


	if (!function_exists('object_to_array')) {
		/**
		 * Helper function to convert object to array
		 *
		 * @since 3.7.0
		 * @param object $object An object.
		 * @return array
		 */
		function object_to_array($object)
		{

			if (!is_object($object) && !is_array($object)) {
				return $object;
			}

			return array_map('objectToArray', (array) $object);
		}
	}

	/**
	 * Allow csv files to be uploaded via the media uploader
	 *
	 * @param array $existing_mimes Existing mimes.
	 * @since  3.7.1
	 */
	function leaky_paywall_upload_mimes($existing_mimes = array())
	{
		$existing_mimes['csv'] = 'text/csv';
		return $existing_mimes;
	}
	add_filter('upload_mimes', 'leaky_paywall_upload_mimes');


	/**
	 * Convert csv file to array
	 *
	 * @param  string $filename  csv file name.
	 * @param  string $delimiter separator for data fields.
	 * @return array            array of data from csv
	 */
	function leaky_paywall_csv_to_array($filename = '', $delimiter = ',')
	{

		if (!file_exists($filename) || !is_readable($filename)) {
			return;
		}

		$header = null;
		$data   = array();
		$handle = fopen($filename, 'r');

		if (false !== $handle) {
			$row = fgetcsv($handle, 1000, $delimiter);

			while (false !== $row) {

				if (!$header) {
					$header = $row;
				} else {
					$data[] = array_combine($header, $row);
				}
			}
			fclose($handle);
		}
		return $data;
	}

	/**
	 * Get the old form input value
	 *
	 * @param string $input name of input.
	 * @param bool   $echo Whether to echo value.
	 * @return string
	 */
	function leaky_paywall_old_form_value($input, $echo = true)
	{

		$value = '';

		if (isset($_POST[$input]) && sanitize_text_field(wp_unslash($_POST[$input]))) {
			$value = sanitize_text_field(wp_unslash($_POST[$input]));
		}

		if ($echo) {
			echo esc_attr($value);
		} else {
			return $value;
		}
	}

	/**
	 * Get the current site's selected currency symbol
	 *
	 * @since 4.5.2
	 * @return string
	 */
	function leaky_paywall_get_current_currency_symbol()
	{

		$currency   = leaky_paywall_get_currency();
		$currencies = leaky_paywall_supported_currencies();

		return $currencies[$currency]['symbol'];
	}

	/**
	 * Check if the current registration has an amount equal to zero (and thus free)
	 *
	 * @since 4.7.1
	 * @param array $meta Registration data.
	 * @return bool
	 */
	function leaky_paywall_is_free_registration($meta)
	{

		if ($meta['price'] > 0) {
			$is_free = false;
		} else {
			$is_free = true;
		}

		return apply_filters('leaky_paywall_is_free_registration', $is_free, $meta);
	}

	/**
	 * Determine if the current subscriber can view the content
	 *
	 * @since 4.7.1
	 * @return bool
	 */
	function leaky_paywall_subscriber_can_view()
	{

		$restricted = new Leaky_Paywall_Restrictions();
		return $restricted->subscriber_can_view();
	}

	/**
	 * Log Leaky Paywall events and data to a file
	 *
	 * @param array|object $data the data to store.
	 * @param string       $event name of event.
	 *
	 * @since 4.7.1
	 * @return void
	 */
	function leaky_paywall_log($data, $event)
	{

		leaky_paywall_debug_log($event . ' | ' . wp_json_encode($data));
	}

	/**
	 * Show Leaky Paywall profile fields on user
	 *
	 * @param object $user The user object.
	 */
	function leaky_paywall_show_extra_profile_fields($user)
	{

		$mode     = leaky_paywall_get_current_mode();
		$site     = leaky_paywall_get_current_site();

		$level_id = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_level_id' . $site, true);

		if (!is_numeric($level_id)) {
			return;
		}

		$level = get_leaky_paywall_subscription_level($level_id);
		$description      = $level['label'];
		$gateway          = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_gateway' . $site, true);
		$status           = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_payment_status' . $site, true);
		$expires          = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_expires' . $site, true);
		$plan             = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_plan' . $site, true);
		$subscriber_id    = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_subscriber_id' . $site, true);
		$subscriber_notes = get_user_meta($user->ID, '_leaky_paywall_subscriber_notes', true);
		$renewal_emailed = get_user_meta($user->ID, '_issuem_leaky_paywall_' . $mode . '_renewal_emailed' . $site, true);

		leaky_paywall_sync_stripe_subscription($user);

	?>

		<h3>Leaky Paywall</h3>

		<table class="form-table">

			<tr>
				<th><label for="level_id">Level ID</label></th>

				<td>
					<?php echo esc_html($level_id); ?>

				</td>
			</tr>

			<tr>
				<th><label for="level_description">Level Description</label></th>

				<td>
					<?php echo esc_html($description); ?>

				</td>
			</tr>

			<tr>
				<th><label for="payment_gateway">Payment Gateway</label></th>

				<td>
					<?php echo esc_html($gateway); ?>

				</td>
			</tr>

			<tr>
				<th><label for="payment_status">Payment Status</label></th>

				<td>
					<?php echo esc_html($status); ?>

				</td>
			</tr>

			<tr>
				<th><label for="expires">Expires</label></th>

				<td>
					<?php echo esc_html($expires); ?>

				</td>
			</tr>

			<?php
			if ($plan) {
			?>
				<tr>
					<th><label for="plan">Plan</label></th>

					<td>
						<?php echo esc_html($plan); ?>

					</td>
				</tr>
			<?php
			}
			?>


			<?php
			if ($subscriber_id) {
			?>
				<tr>
					<th><label for="subscriber_id">Subscriber ID</label></th>

					<td>
						<?php echo esc_html($subscriber_id); ?>

					</td>
				</tr>
			<?php
			}
			?>

			<?php
			if ($subscriber_notes) {
			?>
				<tr>
					<th><label for="subscriber_notes">Subscriber Notes</label></th>

					<td>
						<?php echo esc_html($subscriber_notes); ?>

					</td>
				</tr>
			<?php
			}
			?>

			<?php
			if ($renewal_emailed) {
			?>
				<tr>
					<th><label for="subscriber_notes">Renewal Reminder Email Sent</label></th>

					<td>
						<?php echo esc_html($renewal_emailed); ?>

					</td>
				</tr>
			<?php
			} ?>

			<?php do_action('leaky_paywall_after_wp_user_profile_fields', $user); ?>

		</table>
	<?php
	}
	add_action('show_user_profile', 'leaky_paywall_show_extra_profile_fields');
	add_action('edit_user_profile', 'leaky_paywall_show_extra_profile_fields');

	/**
	 * Add settings link to plugin table for Leaky Paywall
	 *
	 * @since 4.10.4
	 * @param array $links default plugin links.
	 * @return array $links
	 */
	function leaky_paywall_plugin_add_settings_link($links)
	{
		$settings_link = '';

		if ( ! leaky_paywall_is_pro() ) {
			$settings_link .= '<span class="lp-pro-upgrade"><a target="_blank" href="https://leakypaywall.com/upgrade-to-leaky-paywall-pro/?utm_source=WordPress&utm_medium=all-plugins&utm_content=upgrade-to-pro&utm_campaign=lp">' . __('Upgrade to Pro') . '</a></span>  | ';
		}

		$settings_link .= '<a href="admin.php?page=leaky-paywall-settings">' . __('Settings') . '</a>';
		array_unshift($links, $settings_link);
		return $links;
	}
	add_filter('plugin_action_links_' . LEAKY_PAYWALL_BASENAME, 'leaky_paywall_plugin_add_settings_link');

	/**
	 * Plugin row meta links for add ons
	 *
	 * @since 4.10.4
	 * @param array  $input already defined meta links.
	 * @param string $file plugin file path and name being processed.
	 * @return array $input
	 */
	function leaky_paywall_plugin_row_meta($input, $file)
	{

		if ('leaky-paywall/leaky-paywall.php' !== $file) {
			return $input;
		}

		$lp_link = esc_url(
			add_query_arg(
				array(
					'utm_source'   => 'plugins-page',
					'utm_medium'   => 'plugin-row',
					'utm_campaign' => 'admin',
				),
				'https://leakypaywall.com/downloads/category/leaky-paywall-addons/'
			)
		);

		$links = array(
			'<a href="' . $lp_link . '">' . esc_html__('Extensions', 'leaky-paywall') . '</a>',
		);

		$input = array_merge($input, $links);

		return $input;
	}
	add_filter('plugin_row_meta', 'leaky_paywall_plugin_row_meta', 10, 2);

	/**
	 * Maybe delete user
	 */
	function leaky_paywall_maybe_delete_user()
	{

		if (!isset($_POST['leaky-paywall-delete-account-nonce'])) {
			return;
		}

		if (!wp_verify_nonce(sanitize_key(wp_unslash($_POST['leaky-paywall-delete-account-nonce'])), 'leaky-paywall-delete-account')) {
			return;
		}

		$settings = get_leaky_paywall_settings();

		require_once ABSPATH . 'wp-admin/includes/user.php';

		$user = wp_get_current_user();

		if (in_array('subscriber', $user->roles, true)) {

			$stripe_cancel_note = '';
			$mode               = leaky_paywall_get_current_mode();
			$site               = leaky_paywall_get_current_site();
			$subscriber_id      = get_user_meta( $user->ID, '_issuem_leaky_paywall_' . $mode . '_subscriber_id' . $site, true );

			if ( $subscriber_id && strpos( $subscriber_id, 'cus_' ) === 0 ) {
				$stripe = leaky_paywall_initialize_stripe_api();
				try {
					$subs = $stripe->subscriptions->all(
						array(
							'customer' => $subscriber_id,
							'status'   => 'all',
						),
						leaky_paywall_get_stripe_connect_params()
					);
					$cancelable = array( 'active', 'trialing', 'past_due', 'incomplete' );
					foreach ( $subs->data as $sub ) {
						if ( ! in_array( $sub->status, $cancelable, true ) ) {
							continue;
						}
						$stripe->subscriptions->cancel( $sub->id, array(), leaky_paywall_get_stripe_connect_params() );
						leaky_paywall_log( $user->user_email, 'Stripe subscription ' . $sub->id . ' cancelled on account deletion' );
					}
				} catch ( \Stripe\Exception\ApiErrorException $e ) {
					$stripe_cancel_note = '<p><strong>Note:</strong> An error occurred while cancelling the Stripe subscription: ' . esc_html( $e->getMessage() ) . '</p>';
					leaky_paywall_log( $user->user_email, 'Stripe cancellation error on account deletion: ' . $e->getMessage() );
				}
			}

			wp_delete_user($user->ID);

			do_action('leaky_paywall_after_user_deleted', $user);

			$admin_message = '';
			$headers       = array();

			$admin_emails = array();
			$admin_emails = get_option('admin_email');

			$site_name  = stripslashes_deep(html_entity_decode(get_bloginfo('name'), ENT_COMPAT, 'UTF-8'));
			$from_name  = isset($settings['from_name']) ? $settings['from_name'] : $site_name;
			$from_email = isset($settings['from_email']) ? $settings['from_email'] : get_option('admin_email');

			$headers[] = 'From: ' . stripslashes_deep(html_entity_decode($from_name, ENT_COMPAT, 'UTF-8')) . " <$from_email>";
			$headers[] = 'Reply-To: ' . $from_email;
			$headers[] = 'Content-Type: text/html; charset=UTF-8';

			$admin_message = '<p>The user ' . $user->user_email . ' has deleted their account.</p>' . $stripe_cancel_note;

			/* Translators: %s - site name */
			wp_mail($admin_emails, sprintf(esc_attr__('User Account Deleted on %s', 'leaky-paywall'), $site_name), $admin_message, $headers);

			wp_die(wp_kses_post('<p>Your account has been deleted. Your access and information has been removed.</p><p><a href="' . home_url() . '">Continue</a></p>'), 'Account Deleted');
		}

		wp_die(wp_kses_post('<p>Your user role cannot be deleted from the My Account page. Please contact a site administrator.</p><p><a href="' . home_url() . '">Continue</a></p>'), 'Account Deleted');
	}
	add_action('init', 'leaky_paywall_maybe_delete_user');


	/**
	 * Get level display price
	 *
	 * @param array $level Leaky Paywall level.
	 */
	function leaky_paywall_get_level_display_price($level)
	{

		$price = $level['price'];
		$display_price = leaky_paywall_format_display_price($price);

		return apply_filters('leaky_paywall_display_price', $display_price, $level);
	}

	function leaky_paywall_format_display_price($price)
	{

		$settings = get_leaky_paywall_settings();

		$currency_position  = $settings['leaky_paywall_currency_position'];
		$thousand_separator = $settings['leaky_paywall_thousand_separator'];
		$decimal_separator  = $settings['leaky_paywall_decimal_separator'];
		$decimal_number     = empty($settings['leaky_paywall_decimal_number']) ? '0' : $settings['leaky_paywall_decimal_number'];
		$currency_symbol    = leaky_paywall_get_current_currency_symbol();

		$broken_price = explode('.', $price);

		$before_decimal = $broken_price[0];
		$after_decimal  = substr(isset($broken_price[1]) ? $broken_price[1] : '', 0, $decimal_number);

		if (!$after_decimal && 2 === $decimal_number) {
			$after_decimal = '00';
		}

		if ($price > 0) {

			$decimal          = $after_decimal ? $decimal_separator : '';

			if ($before_decimal) {
				$formatted_number = number_format($before_decimal, 0, '', $thousand_separator) . $decimal . $after_decimal;
			} else {
				$formatted_number = '0' . $decimal . $after_decimal; // less than $1
			}

			switch ($currency_position) {
				case 'left':
					$display_price = $currency_symbol . $formatted_number;
					break;
				case 'right':
					$display_price = $formatted_number . $currency_symbol;
					break;
				case 'left_space':
					$display_price = $currency_symbol . ' ' . $formatted_number;
					break;
				case 'right_space':
					$display_price = $formatted_number . ' ' . $currency_symbol;
					break;
				default:
					$display_price = $currency_symbol . $formatted_number;
					break;
			}
		} else {
			$display_price = __('Free', 'leaky-paywall');
		}

		return $display_price;
	}


	/**
	 * Replace language-specific characters by ASCII-equivalents.
	 *
	 * @param string $s Initial string.
	 * @return string
	 */
	function leaky_paywall_normalize_chars($s)
	{
		$replace = array(
			'ъ' => '-',
			'Ь' => '-',
			'Ъ' => '-',
			'ь' => '-',
			'Ă' => 'A',
			'Ą' => 'A',
			'À' => 'A',
			'Ã' => 'A',
			'Á' => 'A',
			'Æ' => 'A',
			'Â' => 'A',
			'Å' => 'A',
			'Ä' => 'Ae',
			'Þ' => 'B',
			'Ć' => 'C',
			'ץ' => 'C',
			'Ç' => 'C',
			'È' => 'E',
			'Ę' => 'E',
			'É' => 'E',
			'Ë' => 'E',
			'Ê' => 'E',
			'Ğ' => 'G',
			'İ' => 'I',
			'Ï' => 'I',
			'Î' => 'I',
			'Í' => 'I',
			'Ì' => 'I',
			'Ł' => 'L',
			'Ñ' => 'N',
			'Ń' => 'N',
			'Ø' => 'O',
			'Ó' => 'O',
			'Ò' => 'O',
			'Ô' => 'O',
			'Õ' => 'O',
			'Ö' => 'Oe',
			'Ş' => 'S',
			'Ś' => 'S',
			'Ș' => 'S',
			'Š' => 'S',
			'Ț' => 'T',
			'Ù' => 'U',
			'Û' => 'U',
			'Ú' => 'U',
			'Ü' => 'Ue',
			'Ý' => 'Y',
			'Ź' => 'Z',
			'Ž' => 'Z',
			'Ż' => 'Z',
			'â' => 'a',
			'ǎ' => 'a',
			'ą' => 'a',
			'á' => 'a',
			'ă' => 'a',
			'ã' => 'a',
			'Ǎ' => 'a',
			'а' => 'a',
			'А' => 'a',
			'å' => 'a',
			'à' => 'a',
			'א' => 'a',
			'Ǻ' => 'a',
			'Ā' => 'a',
			'ǻ' => 'a',
			'ā' => 'a',
			'ä' => 'ae',
			'æ' => 'ae',
			'Ǽ' => 'ae',
			'ǽ' => 'ae',
			'б' => 'b',
			'ב' => 'b',
			'Б' => 'b',
			'þ' => 'b',
			'ĉ' => 'c',
			'Ĉ' => 'c',
			'Ċ' => 'c',
			'ć' => 'c',
			'ç' => 'c',
			'ц' => 'c',
			'צ' => 'c',
			'ċ' => 'c',
			'Ц' => 'c',
			'Č' => 'c',
			'č' => 'c',
			'Ч' => 'ch',
			'ч' => 'ch',
			'ד' => 'd',
			'ď' => 'd',
			'Đ' => 'd',
			'Ď' => 'd',
			'đ' => 'd',
			'д' => 'd',
			'Д' => 'D',
			'ð' => 'd',
			'є' => 'e',
			'ע' => 'e',
			'е' => 'e',
			'Е' => 'e',
			'Ə' => 'e',
			'ę' => 'e',
			'ĕ' => 'e',
			'ē' => 'e',
			'Ē' => 'e',
			'Ė' => 'e',
			'ė' => 'e',
			'ě' => 'e',
			'Ě' => 'e',
			'Є' => 'e',
			'Ĕ' => 'e',
			'ê' => 'e',
			'ə' => 'e',
			'è' => 'e',
			'ë' => 'e',
			'é' => 'e',
			'ф' => 'f',
			'ƒ' => 'f',
			'Ф' => 'f',
			'ġ' => 'g',
			'Ģ' => 'g',
			'Ġ' => 'g',
			'Ĝ' => 'g',
			'Г' => 'g',
			'г' => 'g',
			'ĝ' => 'g',
			'ğ' => 'g',
			'ג' => 'g',
			'Ґ' => 'g',
			'ґ' => 'g',
			'ģ' => 'g',
			'ח' => 'h',
			'ħ' => 'h',
			'Х' => 'h',
			'Ħ' => 'h',
			'Ĥ' => 'h',
			'ĥ' => 'h',
			'х' => 'h',
			'ה' => 'h',
			'î' => 'i',
			'ï' => 'i',
			'í' => 'i',
			'ì' => 'i',
			'į' => 'i',
			'ĭ' => 'i',
			'ı' => 'i',
			'Ĭ' => 'i',
			'И' => 'i',
			'ĩ' => 'i',
			'ǐ' => 'i',
			'Ĩ' => 'i',
			'Ǐ' => 'i',
			'и' => 'i',
			'Į' => 'i',
			'י' => 'i',
			'Ї' => 'i',
			'Ī' => 'i',
			'І' => 'i',
			'ї' => 'i',
			'і' => 'i',
			'ī' => 'i',
			'ĳ' => 'ij',
			'Ĳ' => 'ij',
			'й' => 'j',
			'Й' => 'j',
			'Ĵ' => 'j',
			'ĵ' => 'j',
			'я' => 'ja',
			'Я' => 'ja',
			'Э' => 'je',
			'э' => 'je',
			'ё' => 'jo',
			'Ё' => 'jo',
			'ю' => 'ju',
			'Ю' => 'ju',
			'ĸ' => 'k',
			'כ' => 'k',
			'Ķ' => 'k',
			'К' => 'k',
			'к' => 'k',
			'ķ' => 'k',
			'ך' => 'k',
			'Ŀ' => 'l',
			'ŀ' => 'l',
			'Л' => 'l',
			'ł' => 'l',
			'ļ' => 'l',
			'ĺ' => 'l',
			'Ĺ' => 'l',
			'Ļ' => 'l',
			'л' => 'l',
			'Ľ' => 'l',
			'ľ' => 'l',
			'ל' => 'l',
			'מ' => 'm',
			'М' => 'm',
			'ם' => 'm',
			'м' => 'm',
			'ñ' => 'n',
			'н' => 'n',
			'Ņ' => 'n',
			'ן' => 'n',
			'ŋ' => 'n',
			'נ' => 'n',
			'Н' => 'n',
			'ń' => 'n',
			'Ŋ' => 'n',
			'ņ' => 'n',
			'ŉ' => 'n',
			'Ň' => 'n',
			'ň' => 'n',
			'о' => 'o',
			'О' => 'o',
			'ő' => 'o',
			'õ' => 'o',
			'ô' => 'o',
			'Ő' => 'o',
			'ŏ' => 'o',
			'Ŏ' => 'o',
			'Ō' => 'o',
			'ō' => 'o',
			'ø' => 'o',
			'ǿ' => 'o',
			'ǒ' => 'o',
			'ò' => 'o',
			'Ǿ' => 'o',
			'Ǒ' => 'o',
			'ơ' => 'o',
			'ó' => 'o',
			'Ơ' => 'o',
			'œ' => 'oe',
			'Œ' => 'oe',
			'ö' => 'oe',
			'פ' => 'p',
			'ף' => 'p',
			'п' => 'p',
			'П' => 'p',
			'ק' => 'q',
			'ŕ' => 'r',
			'ř' => 'r',
			'Ř' => 'r',
			'ŗ' => 'r',
			'Ŗ' => 'r',
			'ר' => 'r',
			'Ŕ' => 'r',
			'Р' => 'r',
			'р' => 'r',
			'ș' => 's',
			'с' => 's',
			'Ŝ' => 's',
			'š' => 's',
			'ś' => 's',
			'ס' => 's',
			'ş' => 's',
			'С' => 's',
			'ŝ' => 's',
			'Щ' => 'sch',
			'щ' => 'sch',
			'ш' => 'sh',
			'Ш' => 'sh',
			'ß' => 'ss',
			'т' => 't',
			'ט' => 't',
			'ŧ' => 't',
			'ת' => 't',
			'ť' => 't',
			'ţ' => 't',
			'Ţ' => 't',
			'Т' => 't',
			'ț' => 't',
			'Ŧ' => 't',
			'Ť' => 't',
			'™' => 'tm',
			'ū' => 'u',
			'у' => 'u',
			'Ũ' => 'u',
			'ũ' => 'u',
			'Ư' => 'u',
			'ư' => 'u',
			'Ū' => 'u',
			'Ǔ' => 'u',
			'ų' => 'u',
			'Ų' => 'u',
			'ŭ' => 'u',
			'Ŭ' => 'u',
			'Ů' => 'u',
			'ů' => 'u',
			'ű' => 'u',
			'Ű' => 'u',
			'Ǖ' => 'u',
			'ǔ' => 'u',
			'Ǜ' => 'u',
			'ù' => 'u',
			'ú' => 'u',
			'û' => 'u',
			'У' => 'u',
			'ǚ' => 'u',
			'ǜ' => 'u',
			'Ǚ' => 'u',
			'Ǘ' => 'u',
			'ǖ' => 'u',
			'ǘ' => 'u',
			'ü' => 'ue',
			'в' => 'v',
			'ו' => 'v',
			'В' => 'v',
			'ש' => 'w',
			'ŵ' => 'w',
			'Ŵ' => 'w',
			'ы' => 'y',
			'ŷ' => 'y',
			'ý' => 'y',
			'ÿ' => 'y',
			'Ÿ' => 'y',
			'Ŷ' => 'y',
			'Ы' => 'y',
			'ž' => 'z',
			'З' => 'z',
			'з' => 'z',
			'ź' => 'z',
			'ז' => 'z',
			'ż' => 'z',
			'ſ' => 'z',
			'Ж' => 'zh',
			'ж' => 'zh',
		);
		return strtr($s, $replace);
	}

	/**
	 * Add leaky paywall links to admin toolbar
	 *
	 * @param object $admin_bar The admin bar object.
	 */
	function leaky_paywall_add_toolbar_items($admin_bar)
	{

		if (!current_user_can('edit_user')) {
			return;
		}

		$admin_bar->add_menu(
			array(
				'id'    => 'leaky-paywall-toolbar',
				'title' => 'Leaky Paywall',
				'href'  => admin_url() . 'admin.php?page=issuem-leaky-paywall',
				'meta'  => array(
					'title' => __('Leaky Paywall'),
				),
			)
		);
		$admin_bar->add_menu(
			array(
				'id'     => 'leaky-paywall-toolbar-settings',
				'parent' => 'leaky-paywall-toolbar',
				'title'  => 'Settings',
				'href'   => admin_url() . 'admin.php?page=leaky-paywall-settings',
				'meta'   => array(
					'title'  => __('Settings'),
					'target' => '',
					'class'  => 'my_menu_item_class',
				),
			)
		);
		$admin_bar->add_menu(
			array(
				'id'     => 'leaky-paywall-toolbar-subscribers',
				'parent' => 'leaky-paywall-toolbar',
				'title'  => 'Subscribers',
				'href'   => admin_url() . 'admin.php?page=leaky-paywall-subscribers',
				'meta'   => array(
					'title'  => __('Subscribers'),
					'target' => '',
					'class'  => 'my_menu_item_class',
				),
			)
		);

		$admin_bar->add_menu(
			array(
				'id'     => 'leaky-paywall-toolbar-transactions',
				'parent' => 'leaky-paywall-toolbar',
				'title'  => 'Transactions',
				'href'   => admin_url() . 'edit.php?post_type=lp_transaction',
				'meta'   => array(
					'title'  => __('Transactions'),
					'target' => '',
					'class'  => 'my_menu_item_class',
				),
			)
		);

		$admin_bar->add_menu(
			array(
				'id'     => 'leaky-paywall-toolbar-tools',
				'parent' => 'leaky-paywall-toolbar',
				'title'  => 'Tools',
				'href'   => admin_url( 'admin.php?page=leaky-paywall-tools' ),
				'meta'   => array(
					'title'  => __('Tools'),
					'target' => '',
					'class'  => 'my_menu_item_class',
				),
			)
		);

		}
	add_action('admin_bar_menu', 'leaky_paywall_add_toolbar_items', 100);

	/**
	 * Display Leaky Paywall rate us notice
	 */
	function leaky_paywall_display_rate_us_notice()
	{

		$notice_id = 'lp_rate_us_feedback';

		if (!current_user_can('manage_options')) {
			return;
		}

		$current_user_has_viewed = get_user_meta(get_current_user_id(), $notice_id, true);

		if ('dashboard' !== get_current_screen()->id || $current_user_has_viewed) {
			return;
		}

		$site = leaky_paywall_get_current_site();

		$args = array(
			'number'     => 11,
			'meta_query' => array(
				array(
					'key'     => '_issuem_leaky_paywall_live_level_id' . $site,
					'compare' => 'EXISTS',
				),
			),
		);

		$wp_user_search         = new WP_User_Query($args);
		$total_live_subscribers = count($wp_user_search->get_results());

		if (100 >= $total_live_subscribers) {
			return;
		}

		$dismiss_url = add_query_arg(
			array(
				'action'    => 'leaky_paywall_set_admin_notice_viewed',
				'nonce' 	=> wp_create_nonce('leaky-paywall-admin-notice-nonce'),
				'notice_id' => esc_attr($notice_id),
			),
			admin_url()
		);

	?>
		<div class="notice updated is-dismissible leaky-paywall-message leaky-paywall-message-dismissed" data-notice_id="<?php echo esc_attr($notice_id); ?>">
			<div class="leaky-paywall-message-inner">

				<div class="leaky-paywall-message-content">
					<p><strong><?php echo esc_html__('Congrats!', 'leaky-paywall'); ?></strong> 🥳<?php esc_html_e('You have more than 100 subscribers with Leaky Paywall. Please help us by leaving a review on WordPress.org. We read every review and use your feedback to make Leaky Paywall better for everyone!', 'leaky-paywall'); ?></p>
					<p class="leaky-paywall-message-actions">
						<a href="https://wordpress.org/support/plugin/leaky-paywall/reviews/?filter=5/#new-post" target="_blank" class="button button-primary"><?php esc_html_e('Leave a Review', 'leaky-paywall'); ?></a>
						<a href="<?php echo esc_url_raw($dismiss_url); ?>" class="button leaky-paywall-button-notice-dismiss"><?php esc_html_e('Hide', 'leaky-paywall'); ?></a>
					</p>
				</div>
				<div class="leaky-paywall-message-logo">
					<img src="<?php echo esc_url(LEAKY_PAYWALL_URL); ?>/images/zeen101-logo.png" alt="ZEEN101" width="100">
				</div>
			</div>
		</div>

		<style>
			.leaky-paywall-message-inner {
				overflow: hidden;
				width: 100%;
			}

			.leaky-paywall-message-inner .leaky-paywall-message-content {
				width: 60%;
				float: left;
			}

			.leaky-paywall-message-inner .leaky-paywall-message-logo {
				width: 20%;
				float: right;
				text-align: right;
				padding-top: 7px;
				padding-bottom: 7px;
			}
		</style>
	<?php
	}
	add_action('admin_notices', 'leaky_paywall_display_rate_us_notice', 20);

	/**
	 * Update admin notice viewed
	 */
	function leaky_paywall_update_admin_notice_viewed()
	{

		if (!isset($_GET['nonce'])) {
			return;
		}

		if (!wp_verify_nonce(sanitize_key($_GET['nonce']), 'leaky-paywall-admin-notice-nonce')) {
			return;
		}

		if (!isset($_GET['action'])) {
			return;
		}

		if ('leaky_paywall_set_admin_notice_viewed' !== $_GET['action']) {
			return;
		}

		if (!isset($_GET['notice_id'])) {
			return;
		}

		update_user_meta(get_current_user_id(), sanitize_text_field(wp_unslash($_GET['notice_id'])), true);
	}
	add_action('admin_init', 'leaky_paywall_update_admin_notice_viewed');

	/**
	 * Get a Transaction ID from an email address
	 *
	 * @param string $email The email address.
	 */
	function leaky_paywall_get_transaction_id_from_email($email)
	{

		$transaction_id = '';

		$args = array(
			'post_type'       => 'lp_transaction',
			'number_of_posts' => 1,
			'meta_query'      => array(
				array(
					'key'     => '_email',
					'value'   => $email,
					'compare' => '=',
				),
			),
		);

		$transactions = get_posts($args);

		if (!empty($transactions)) {
			$transaction    = $transactions[0];
			$transaction_id = $transaction->ID;
		}

		return $transaction_id;
	}

	/**
	 * Sets a Gateway Transaction ID in post meta for the given Transaction ID
	 *
	 * @since  4.14.5
	 * @param int    $transaction_id Transaction ID.
	 * @param string $gateway_transaction_id The transaction ID from the gateway.
	 */
	function leaky_paywall_set_payment_transaction_id($transaction_id, $gateway_transaction_id)
	{

		update_post_meta($transaction_id, '_gateway_txn_id', $gateway_transaction_id);
	}

	/**
	 * Find a transaction by its gateway transaction ID.
	 *
	 * @since 4.21.0
	 *
	 * @param string $gateway_id The gateway transaction ID (charge ID or payment intent ID).
	 * @return int Transaction post ID, or 0 if not found.
	 */
	function leaky_paywall_find_transaction_by_gateway_id( $gateway_id ) {
		$args = array(
			'post_type'      => 'lp_transaction',
			'posts_per_page' => 1,
			'post_status'    => 'publish',
			'meta_query'     => array(
				array(
					'key'     => '_gateway_txn_id',
					'value'   => $gateway_id,
					'compare' => '=',
				),
			),
			'fields' => 'ids',
		);

		$posts = get_posts( $args );

		return ! empty( $posts ) ? $posts[0] : 0;
	}

	/**
	 * Get the total amount already refunded for a given charge ID.
	 *
	 * Looks up existing refund transactions linked to the charge to prevent
	 * duplicate refund records when Stripe retries webhooks.
	 *
	 * @since 4.21.0
	 *
	 * @param string $charge_id The Stripe charge ID.
	 * @return float Total refunded amount (positive number).
	 */
	function leaky_paywall_get_existing_refund_total( $charge_id ) {
		global $wpdb;

		$total = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COALESCE( SUM( ABS( CAST( pm_price.meta_value AS DECIMAL(10,2) ) ) ), 0 )
				 FROM {$wpdb->posts} p
				 INNER JOIN {$wpdb->postmeta} pm_price
					 ON p.ID = pm_price.post_id AND pm_price.meta_key = '_price'
				 INNER JOIN {$wpdb->postmeta} pm_ref
					 ON p.ID = pm_ref.post_id AND pm_ref.meta_key = '_refund_charge_id'
				 WHERE p.post_type = 'lp_transaction'
				 AND pm_ref.meta_value = %s",
				$charge_id
			)
		);

		return (float) $total;
	}

	/**
	 * Create a refund transaction with a negative price.
	 *
	 * @since 4.21.0
	 *
	 * @param int    $original_txn_id The original transaction post ID (0 if not found).
	 * @param object $user            The WordPress user object.
	 * @param string $charge_id       The Stripe charge ID.
	 * @param float  $refund_amount   The refund amount (positive number).
	 * @return int The refund transaction post ID.
	 */
	function leaky_paywall_create_refund_transaction( $original_txn_id, $user, $charge_id, $refund_amount ) {
		$level_id = '';
		$currency = '';

		if ( $original_txn_id ) {
			$level_id = get_post_meta( $original_txn_id, '_level_id', true );
			$currency = get_post_meta( $original_txn_id, '_currency', true );
		}

		$transaction = array(
			'post_title'   => 'Refund for ' . $user->user_email,
			'post_content' => '',
			'post_status'  => 'publish',
			'post_author'  => 1,
			'post_type'    => 'lp_transaction',
		);

		$transaction_id = wp_insert_post( $transaction );

		update_post_meta( $transaction_id, '_email', $user->user_email );
		update_post_meta( $transaction_id, '_first_name', $user->first_name );
		update_post_meta( $transaction_id, '_last_name', $user->last_name );
		update_post_meta( $transaction_id, '_level_id', $level_id );
		update_post_meta( $transaction_id, '_gateway', 'stripe' );
		update_post_meta( $transaction_id, '_gateway_txn_id', $charge_id );
		update_post_meta( $transaction_id, '_price', '-' . number_format( $refund_amount, 2, '.', '' ) );
		update_post_meta( $transaction_id, '_currency', $currency );
		update_post_meta( $transaction_id, '_status', 'refund' );
		update_post_meta( $transaction_id, '_transaction_status', 'complete' );
		update_post_meta( $transaction_id, '_is_recurring', false );
		update_post_meta( $transaction_id, '_refund_charge_id', $charge_id );

		if ( $original_txn_id ) {
			update_post_meta( $transaction_id, '_refund_for', $original_txn_id );
		}

		do_action( 'leaky_paywall_after_create_refund_transaction', $transaction_id, $original_txn_id, $user );

		leaky_paywall_log( 'Refund transaction ' . $transaction_id . ' created for ' . $user->user_email . ' - amount: ' . $refund_amount, 'stripe refund' );

		return $transaction_id;
	}


	/**
	 * Check login fail
	 *
	 * @param string $username The username.
	 */
	function leaky_paywall_login_fail($username)
	{

		$settings       = get_leaky_paywall_settings();
		$referrer       = wp_get_referer();
		$clean_referrer = str_replace(array('http://', 'https://'), '', $referrer);
		$login_link     = str_replace(array('http://', 'https://'), '', get_page_link($settings['page_for_login']));
		$profile_link   = str_replace(array('http://', 'https://'), '', get_page_link($settings['page_for_profile']));

		// Only run this check if the user was on the leaky paywall login page or profile page. This keeps it from breaking other login plugins.
		if ($clean_referrer !== $login_link && $clean_referrer !== $profile_link) {
			return;
		}

		if (!empty($referrer) && !strstr($referrer, 'wp-login') && !strstr($referrer, 'wp-admin')) {
			wp_safe_redirect($referrer . '/?login=failed');
			exit();
		}
	}
	add_action('wp_login_failed', 'leaky_paywall_login_fail');

	/**
	 * Gets the IP address of the user.
	 *
	 * @return string The IP address of the user.
	 */
	function leaky_paywall_get_ip()
	{

		if (!empty($_SERVER['HTTP_CLIENT_IP']))   //check ip from share internet
		{
			$ip = sanitize_text_field($_SERVER['HTTP_CLIENT_IP']);
		} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))   //to check ip is pass from proxy
		{

			$ip_array = array_values(array_filter(explode(',', sanitize_text_field($_SERVER['HTTP_X_FORWARDED_FOR']))));

			if (is_array($ip_array)) {
				$ip = $ip_array[0];
			} else {
				$ip = sanitize_text_field($_SERVER['HTTP_X_FORWARDED_FOR']);
			}
		} else {
			$ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field($_SERVER['REMOTE_ADDR']) : '';
		}

		if (!filter_var($ip, FILTER_VALIDATE_IP)) {
			$ip = '';
		}

		return apply_filters('leaky_paywall_ip_address', $ip);
	}



	/**
	 * Load any CSS we need for the plugins list table.
	 */
	function leaky_paywall_plugin_list_styles()
	{
		echo '<style>span.lp-pro-upgrade a, span.lp-pro-upgrade a:hover{color: #759542; font-weight: 600;}</style>';
	}
	add_action('admin_print_styles-plugins.php', 'leaky_paywall_plugin_list_styles');

	/**
	 * Allow interval text to be translatable
	 */
	function leaky_paywall_get_interval_text($interval, $interval_count)
	{

		$interval_text = '';

		if ($interval == 'day') {
			if ($interval_count > 1) {
				$interval_text = esc_html__('days', 'leaky-paywall');
			} else {
				$interval_text = esc_html__('day', 'leaky-paywall');
			}
		}

		if ($interval == 'week') {
			if ($interval_count > 1) {
				$interval_text = esc_html__('weeks', 'leaky-paywall');
			} else {
				$interval_text = esc_html__('week', 'leaky-paywall');
			}
		}

		if ($interval == 'month') {
			if ($interval_count > 1) {
				$interval_text = esc_html__('months', 'leaky-paywall');
			} else {
				$interval_text = esc_html__('month', 'leaky-paywall');
			}
		}

		if ($interval == 'year') {
			if ($interval_count > 1) {
				$interval_text = esc_html__('years', 'leaky-paywall');
			} else {
				$interval_text = esc_html__('year', 'leaky-paywall');
			}
		}

		return $interval_text;
	}
