<?php
/**
 * Misc Functions
 *
 * @package     Give
 * @subpackage  Functions
 * @copyright   Copyright (c) 2016, GiveWP
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
 * @since       1.0
 */

use Give\License\PremiumAddonsListManager;
use Give\License\Repositories\LicenseRepository;
use Give\License\ValueObjects\LicenseOptionKeys;

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

/**
 * Is Test Mode Enabled.
 *
 * @return bool $ret True if return mode is enabled, false otherwise
 * @since 1.0
 */
function give_is_test_mode() {

	$ret = give_is_setting_enabled( give_get_option( 'test_mode' ) );

	return (bool) apply_filters( 'give_is_test_mode', $ret );

}

/**
 * Get the current page URL.
 *
 * @return string $current_url Current page URL.
 * @since 1.0
 */
function give_get_current_page_url() {

	global $wp;

	if ( get_option( 'permalink_structure' ) ) {
		$base = trailingslashit( home_url( $wp->request ) );
	} else {
		$base = add_query_arg( $wp->query_string, '', trailingslashit( home_url( $wp->request ) ) );
		$base = remove_query_arg( [ 'post_type', 'name' ], $base );
	}

	$scheme      = is_ssl() ? 'https' : 'http';
	$current_uri = set_url_scheme( $base, $scheme );

	if ( is_front_page() ) {
		$current_uri = home_url( '/' );
	}

	/**
	 * Filter the current page url
	 *
	 * @param string $current_uri
	 *
	 * @since 1.0
	 */
	return esc_url_raw( apply_filters( 'give_get_current_page_url', $current_uri ) );

}


/**
 * Verify credit card numbers live?
 *
 * @return bool $ret True is verify credit cards is live
 * @since 1.0
 */
function give_is_cc_verify_enabled() {

	$ret = true;

	/**
	 * Enable if use a single gateway other than PayPal or Manual. We have to assume it accepts credit cards.
	 * Enable if using more than one gateway if they are not both PayPal and manual, again assuming credit card usage.
	 */
	$gateways = give_get_enabled_payment_gateways();

	if ( count( $gateways ) == 1 && ! isset( $gateways['paypal'] ) && ! isset( $gateways['manual'] ) ) {
		$ret = true;
	} elseif ( count( $gateways ) == 1 ) {
		$ret = false;
	} elseif ( count( $gateways ) == 2 && isset( $gateways['paypal'] ) && isset( $gateways['manual'] ) ) {
		$ret = false;
	}

	/**
	 * Fire the filter
	 *
	 * @param bool $ret
	 *
	 * @since 1.0
	 */
	return (bool) apply_filters( 'give_is_cc_verify_enabled', $ret );
}

/**
 * Retrieve timezone.
 *
 * @return string $timezone The timezone ID.
 * @since 1.0
 */
function give_get_timezone_id() {

	// if site timezone string exists, return it.
	if ( $timezone = get_option( 'timezone_string' ) ) {
		return $timezone;
	}

	// get UTC offset, if it isn't set return UTC.
	if ( ! ( $utc_offset = 3600 * get_option( 'gmt_offset', 0 ) ) ) {
		return 'UTC';
	}

	// attempt to guess the timezone string from the UTC offset.
	$timezone = timezone_name_from_abbr( '', $utc_offset );

	// last try, guess timezone string manually.
	if ( $timezone === false ) {

		$is_dst = date( 'I' );

		foreach ( timezone_abbreviations_list() as $abbr ) {
			foreach ( $abbr as $city ) {
				if ( $city['dst'] == $is_dst && $city['offset'] == $utc_offset ) {
					return $city['timezone_id'];
				}
			}
		}
	}

	// Fallback.
	return 'UTC';
}


/**
 * Get User IP
 *
 * Returns the IP address of the current visitor
 *
 * @since 2.33.5  Add $single param.
 * @since       1.0
 *
 * @return string $ip User's IP address
 */
function give_get_ip($single = true)
{
    $ip_addresses = '127.0.0.1';
    $header_type = '';

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

	/**
	 * Filter the IP
	 *
     * @since 2.33.5 Add $single and $header_type params.
     * @since      1.0
	 */
    $ip_addresses = apply_filters('give_get_ip', $ip_addresses, $single, $header_type);

	// Filter empty values.
    if (false !== strpos($ip_addresses, ',')) {
        $ip_addresses = give_clean(explode(',', $ip_addresses));
        $ip_addresses = array_filter($ip_addresses);
        $ip_addresses = implode(',', $ip_addresses);
	} else {
        $ip_addresses = give_clean($ip_addresses);
	}

    if ($single && false !== strpos($ip_addresses, ',')) {
        return explode(',', $ip_addresses)[0];
    }

    return $ip_addresses;
}


/**
 * Store Donation Data in Sessions
 *
 * Used for storing info about donation
 *
 * @param $purchase_data
 *
 * @since 1.0
 *
 * @uses  Give()->session->set()
 */
function give_set_purchase_session( $purchase_data = [] ) {
	Give()->session->set( 'give_purchase', $purchase_data );
	Give()->session->set( 'give_email', $purchase_data['user_email'] );
}

/**
 * Retrieve Donation Data from Session
 *
 * Used for retrieving info about donation
 * after completing a donation
 *
 * @return mixed array | false
 * @uses  Give()->session->get()
 * @since 1.0
 */
function give_get_purchase_session() {
	return Give()->session->get( 'give_purchase' );
}

/**
 * Retrieve Payment Key of the Receipt Access Session.
 *
 * @return array|string
 * @since 1.8.17
 */
function give_get_receipt_session() {
	return Give()->session->get( 'receipt_access' );
}

/**
 * Retrieve Payment Key of the History Access Session.
 *
 * @return array|string
 * @since 1.8.17
 */
function give_get_history_session() {
	return (bool) Give()->session->get( 'history_access' );
}

/**
 * Generate Item Title for Payment Gateway.
 *
 * @since 1.8.14
 * @since 2.9.6  Function will return form title with selected form level if price id set to zero. Added second param to return result with requested character length.
 *
 * @param  array  $payment_data  Payment Data.
 *
 * @param  string|null  $length
 *
 * @return string By default, the name of the form. Then the price level text if any is found.
 */
function give_payment_gateway_item_title( $payment_data, $length = null ) {

	$form_id   = intval( $payment_data['post_data']['give-form-id'] );
	$item_name = isset( $payment_data['post_data']['give-form-title'] ) ? $payment_data['post_data']['give-form-title'] : '';
	$price_id  = isset( $payment_data['post_data']['give-price-id'] ) ? $payment_data['post_data']['give-price-id'] : '';

	// Verify has variable prices.
	if ( give_has_variable_prices( $form_id ) ) {

		$item_price_level_text = give_get_price_option_name( $form_id, $price_id, 0, false );

		/**
		 * Output donation level text if:
		 *
		 * 1. It's not a custom amount
		 * 2. The level field has actual text and isn't the amount (which is already displayed on the receipt).
		 */
		if ( 'custom' !== $price_id && ! empty( $item_price_level_text ) ) {
			// Matches a donation level - append level text.
			$item_name .= ' - ' . $item_price_level_text;
		}
	}

	/**
	 * Filter the Item Title of Payment Gateway.
	 *
	 * @param string $item_name    Item Title of Payment Gateway.
	 * @param int    $form_id      Donation Form ID.
	 * @param array  $payment_data Payment Data.
	 *
	 * @return string
	 * @since 1.8.14
	 */
	$item_name = apply_filters( 'give_payment_gateway_item_title', $item_name, $form_id, $payment_data );

	// Cut the length
	if ( $length ) {
		$item_name = substr( $item_name, 0, $length );
	}

	return $item_name;
}

/**
 * Get Donation Summary
 *
 * Creates a donation summary for payment gateways from the donation data before the payment is created in the database.
 *
 * @param array $donation_data
 * @param bool  $name_and_email
 * @param int   $length
 *
 * @return string
 * @since       1.8.12
 */
function give_payment_gateway_donation_summary( $donation_data, $name_and_email = true, $length = 255 ) {

	$form_id  = isset( $donation_data['post_data']['give-form-id'] ) ? $donation_data['post_data']['give-form-id'] : '';
	$price_id = isset( $donation_data['post_data']['give-price-id'] ) ? $donation_data['post_data']['give-price-id'] : '';

	// Form title.
	$summary = ( ! empty( $donation_data['post_data']['give-form-title'] ) ? $donation_data['post_data']['give-form-title'] : ( ! empty( $form_id ) ? wp_sprintf( __( 'Donation Form ID: %d', 'give' ), $form_id ) : __( 'Untitled donation form', 'give' ) ) );

	// Form multilevel if applicable.
	if ( ! empty( $price_id ) && 'custom' !== $price_id ) {
		$summary .= ': ' . give_get_price_option_name( $form_id, $donation_data['post_data']['give-price-id'] );
	}

	// Add Donor's name + email if requested.
	if ( $name_and_email ) {

		// First name
		if ( isset( $donation_data['user_info']['first_name'] ) && ! empty( $donation_data['user_info']['first_name'] ) ) {
			$summary .= ' - ' . $donation_data['user_info']['first_name'];
		}

		if ( isset( $donation_data['user_info']['last_name'] ) && ! empty( $donation_data['user_info']['last_name'] ) ) {
			$summary .= ' ' . $donation_data['user_info']['last_name'];
		}

		$summary .= ' (' . $donation_data['user_email'] . ')';
	}

	// Cut the length
	$summary = substr( $summary, 0, $length );

	return apply_filters( 'give_payment_gateway_donation_summary', $summary );
}


/**
 * Get user host
 *
 * Returns the webhost this site is using if possible
 *
 * @return string $host if detected, false otherwise
 * @since 1.0
 */
function give_get_host() {
	$find_host = gethostname();

	if ( strpos( $find_host, 'sgvps.net' ) ) {
		$host = 'Siteground';
	} elseif ( defined( 'WPE_APIKEY' ) ) {
		$host = 'WP Engine';
	} elseif ( defined( 'PAGELYBIN' ) || strpos( $find_host, 'pagelyhosting.com' ) ) {
		$host = 'Pagely';
	} elseif ( strpos( $find_host, 'secureserver.net' ) ) {
		$host = 'GoDaddy/Media Temple';
	} elseif ( DB_HOST == 'localhost:/tmp/mysql5.sock' ) {
		$host = 'ICDSoft';
	} elseif ( DB_HOST == 'mysqlv5' ) {
		$host = 'NetworkSolutions';
	} elseif ( strpos( DB_HOST, 'ipagemysql.com' ) !== false ) {
		$host = 'iPage';
	} elseif ( strpos( DB_HOST, 'ipowermysql.com' ) !== false ) {
		$host = 'IPower';
	} elseif ( strpos( DB_HOST, '.gridserver.com' ) !== false ) {
		$host = 'MediaTemple Grid';
	} elseif ( strpos( DB_HOST, '.pair.com' ) !== false ) {
		$host = 'pair Networks';
	} elseif ( strpos( DB_HOST, '.stabletransit.com' ) !== false ) {
		$host = 'Rackspace Cloud';
	} elseif ( strpos( DB_HOST, '.sysfix.eu' ) !== false ) {
		$host = 'SysFix.eu Power Hosting';
	} elseif ( strpos( $_SERVER['SERVER_NAME'], 'Flywheel' ) !== false || strpos( $find_host, 'fw' ) ) {
		$host = 'Flywheel';
	} else {
		// Adding a general fallback for data gathering
		$host = 'DBH: ' . DB_HOST . ', SRV: ' . $_SERVER['SERVER_NAME'];
	}

	return $host;
}

/**
 * Marks a function as deprecated and informs when it has been used.
 *
 * There is a hook give_deprecated_function_run that will be called that can be used
 * to get the backtrace up to what file and function called the deprecated
 * function.
 *
 * The current behavior is to trigger a user error if WP_DEBUG is true.
 *
 * This function is to be used in every function that is deprecated.
 *
 * @param string $function    The function that was called.
 * @param string $version     The plugin version that deprecated the function.
 * @param string $replacement Optional. The function that should have been called.
 * @param array  $backtrace   Optional. Contains stack backtrace of deprecated function.
 *
 * @uses do_action() Calls 'give_deprecated_function_run' and passes the function name, what to use instead,
 *       and the version the function was deprecated in.
 * @uses apply_filters() Calls 'give_deprecated_function_trigger_error' and expects boolean value of true to do
 *       trigger or false to not trigger error.
 */
function _give_deprecated_function( $function, $version, $replacement = null, $backtrace = null ) {

	/**
	 * Fires while give deprecated function call occurs.
	 *
	 * Allow you to hook to deprecated function call.
	 *
	 * @param string $function    The function that was called.
	 * @param string $replacement Optional. The function that should have been called.
	 * @param string $version     The plugin version that deprecated the function.
	 *
	 * @since 1.0
	 */
	do_action( 'give_deprecated_function_run', $function, $replacement, $version );

	$show_errors = current_user_can( 'manage_options' );

	// Allow plugin to filter the output error trigger.
	if ( WP_DEBUG && apply_filters( 'give_deprecated_function_trigger_error', $show_errors ) ) {
		if ( ! is_null( $replacement ) ) {
			trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since GiveWP version %2$s! Use %3$s instead.', 'give' ), $function, $version, $replacement ) );
			trigger_error( print_r( $backtrace, 1 ) ); // Limited to previous 1028 characters, but since we only need to move back 1 in stack that should be fine.
			// Alternatively we could dump this to a file.
		} else {
			trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since GiveWP version %2$s with no alternative available.', 'give' ), $function, $version ) );
			trigger_error( print_r( $backtrace, 1 ) );// Limited to previous 1028 characters, but since we only need to move back 1 in stack that should be fine.
			// Alternatively we could dump this to a file.
		}
	}
}

/**
 * Give Get Admin ID
 *
 * Helper function to return the ID of the post for admin usage
 *
 * @return string $post_id
 */
function give_get_admin_post_id() {
	$post_id = isset( $_REQUEST['post'] ) ? absint( $_REQUEST['post'] ) : null;

	$post_id = ! empty( $post_id ) ? $post_id : ( isset( $_REQUEST['post_id'] ) ? absint( $_REQUEST['post_id'] ) : null );

	$post_id = ! empty( $post_id ) ? $post_id : ( isset( $_REQUEST['post_ID'] ) ? absint( $_REQUEST['post_ID'] ) : null );

	return $post_id;
}

/**
 * Get PHP Arg Separator Output
 *
 * @return string Arg separator output
 * @since 1.0
 */
function give_get_php_arg_separator_output() {
	return ini_get( 'arg_separator.output' );
}


/**
 * Month Num To Name
 *
 * Takes a month number and returns the name three letter name of it.
 *
 * @param int $n
 *
 * @return string Short month name
 * @since 1.0
 */
function give_month_num_to_name( $n ) {
	$timestamp = mktime( 0, 0, 0, $n, 1, 2005 );

	return date_i18n( 'M', $timestamp );
}

/**
 * Checks whether function is disabled.
 *
 * @param string $function Name of the function.
 *
 * @return bool Whether or not function is disabled.
 * @since 1.0
 */
function give_is_func_disabled( $function ) {
	$disabled = explode( ',', ini_get( 'disable_functions' ) );

	return in_array( $function, $disabled );
}


/**
 * Create SVG library function
 *
 * @param string $icon
 *
 * @return string
 */
function give_svg_icons( $icon ) {

	// Store your SVGs in an associative array
	$svgs = [
		'microphone'    => 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjY0cHgiIGhlaWdodD0iMTAwcHgiIHZpZXdCb3g9IjAgLTIwIDY0IDEyMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNjQgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8Zz4NCgkJPHBhdGggZD0iTTYyLDM2LjIxNWgtM2MtMS4xLDAtMiwwLjktMiwyVjUyYzAsNi42ODYtNS4yNjYsMTgtMjUsMThTNyw1OC42ODYsNyw1MlYzOC4yMTVjMC0xLjEtMC45LTItMi0ySDJjLTEuMSwwLTIsMC45LTIsMlY1Mg0KCQkJYzAsMTEuMTg0LDguMjE1LDIzLjE1MiwyNywyNC44MDFWOTBIMTRjLTEuMSwwLTIsMC44OTgtMiwydjZjMCwxLjEsMC45LDIsMiwyaDM2YzEuMSwwLDItMC45LDItMnYtNmMwLTEuMTAyLTAuOS0yLTItMkgzN1Y3Ni44MDENCgkJCUM1NS43ODUsNzUuMTUyLDY0LDYzLjE4NCw2NCw1MlYzOC4yMTVDNjQsMzcuMTE1LDYzLjEsMzYuMjE1LDYyLDM2LjIxNXoiLz4NCgkJPHBhdGggZD0iTTMyLDYwYzExLjczMiwwLDE1LTQuODE4LDE1LThWMzYuMjE1SDE3VjUyQzE3LDU1LjE4MiwyMC4yNjYsNjAsMzIsNjB6Ii8+DQoJCTxwYXRoIGQ9Ik00Nyw4YzAtMy4xODQtMy4yNjgtOC0xNS04QzIwLjI2NiwwLDE3LDQuODE2LDE3LDh2MjEuMjE1aDMwVjh6Ii8+DQoJPC9nPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPC9zdmc+DQo=',
		'alert'         => 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjI4LjkzOHB4IiBoZWlnaHQ9IjI1LjAwNXB4IiB2aWV3Qm94PSIwIDAgMjguOTM4IDI1LjAwNSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjguOTM4IDI1LjAwNTsiDQoJIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDsiIGQ9Ik0yOC44NTksMjQuMTU4TDE0Ljk1NywwLjI3OUMxNC44NTYsMC4xMDYsMTQuNjcsMCwxNC40NjgsMGMtMC4xOTgsMC0wLjM4MywwLjEwNi0wLjQ4MSwwLjI3OQ0KCUwwLjA3OSwyNC4xNThjLTAuMTAyLDAuMTc1LTAuMTA2LDAuMzg5LTAuMDA2LDAuNTY1YzAuMTAzLDAuMTc0LDAuMjg3LDAuMjgyLDAuNDg4LDAuMjgyaDI3LjgxNGMwLjIwMSwwLDAuMzg5LTAuMTA4LDAuNDg4LTAuMjgyDQoJYzAuMDQ3LTAuMDg4LDAuMDc0LTAuMTg2LDAuMDc0LTAuMjgxQzI4LjkzOCwyNC4zNDMsMjguOTExLDI0LjI0NSwyOC44NTksMjQuMTU4eiBNMTYuMzY5LDguNDc1bC0wLjQ2Miw5LjQ5M2gtMi4zODlsLTAuNDYxLTkuNDkzDQoJSDE2LjM2OXogTTE0LjcxMSwyMi44MjhoLTAuMDQyYy0xLjA4OSwwLTEuODQzLTAuODE3LTEuODQzLTEuOTA3YzAtMS4xMzEsMC43NzQtMS45MDcsMS44ODUtMS45MDdzMS44NDYsMC43NzUsMS44NjcsMS45MDcNCglDMTYuNTc5LDIyLjAxMSwxNS44NDQsMjIuODI4LDE0LjcxMSwyMi44Mjh6Ii8+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8L3N2Zz4NCg==',
		'placemark'     => 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMTAwcHgiIGhlaWdodD0iMTAwcHgiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMDAgMTAwIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwYXRoIGQ9Ik01MC40MzQsMjAuMjcxYy0xMi40OTksMC0yMi42NjgsMTAuMTY5LTIyLjY2OCwyMi42NjhjMCwxMS44MTQsMTguODE1LDMyLjE1NSwyMC45NiwzNC40MzdsMS43MDgsMS44MTZsMS43MDgtMS44MTYNCgkJYzIuMTQ1LTIuMjgxLDIwLjk2LTIyLjYyMywyMC45Ni0zNC40MzdDNzMuMTAzLDMwLjQ0LDYyLjkzNCwyMC4yNzEsNTAuNDM0LDIwLjI3MXogTTUwLjQzNCw1Mi4zMmMtNS4xNzIsMC05LjM4LTQuMjA4LTkuMzgtOS4zOA0KCQlzNC4yMDgtOS4zOCw5LjM4LTkuMzhjNS4xNzMsMCw5LjM4LDQuMjA4LDkuMzgsOS4zOFM1NS42MDcsNTIuMzIsNTAuNDM0LDUyLjMyeiIvPg0KPC9nPg0KPC9zdmc+DQo=',
		'give_grey'     => 'data:image/svg+xml;base64, ',
		'give_cpt_icon' => 'data:image/svg+xml;base64, ',
	];

	// Return the chosen icon's SVG string
	return $svgs[ $icon ];
}

/**
 * Modify Admin Nav Menu Label
 *
 * @param object $post_type The current object to add a menu items meta box for.
 *
 * @return mixed
 * @since 1.3
 */
function modify_nav_menu_meta_box_object( $post_type ) {
	if ( isset( $post_type->name ) && $post_type->name == 'give_forms' ) {
		$post_type->labels->name = esc_html__( 'Donation Forms', 'give' );
	}

	return $post_type;
}

add_filter( 'nav_menu_meta_box_object', 'modify_nav_menu_meta_box_object' );

/**
 * Show Donation Forms Post Type in Appearance > Menus by default on fresh install.
 *
 * @return bool
 * @todo  Remove this, when WordPress Core ticket is resolved (https://core.trac.wordpress.org/ticket/16828).
 *
 * @since 1.8.14
 */
function give_donation_metabox_menu() {

	// Get Current Screen.
	$screen = get_current_screen();

	// Proceed, if current screen is navigation menus.
	if ( 'nav-menus' === $screen->id && give_is_setting_enabled( give_get_option( 'forms_singular' ) ) && ! get_user_option( 'give_is_donation_forms_menu_updated' ) ) {

		// Return false, if it fails to retrieve hidden meta box list and is not admin.
		if ( ! is_admin() || ( ! $hidden_meta_boxes = get_user_option( 'metaboxhidden_nav-menus' ) ) ) {
			return false;
		}

		// Return false, In case, we don't find 'Donation Form' in hidden meta box list.
		if ( ! in_array( 'add-post-type-give_forms', $hidden_meta_boxes, true ) ) {
			return false;
		}

		// Exclude 'Donation Form' value from hidden meta box's list.
		$hidden_meta_boxes = array_diff( $hidden_meta_boxes, [ 'add-post-type-give_forms' ] );

		// Get current user ID.
		$user = wp_get_current_user();

		update_user_option( $user->ID, 'metaboxhidden_nav-menus', $hidden_meta_boxes, true );
		update_user_option( $user->ID, 'give_is_donation_forms_menu_updated', true, true );
	}
}

add_action( 'current_screen', 'give_donation_metabox_menu' );

/**
 * Array_column backup usage
 *
 * This file is part of the array_column library.
 *
 * @since      : 1.3.0.1
 *
 * @copyright  Copyright (c) Ben Ramsey (http://benramsey.com)
 * @license    https://opensource.org/licenses/MIT MIT
 */

if ( ! function_exists( 'array_column' ) ) {
	/**
	 * Returns the values from a single column of the input array, identified by
	 * the $columnKey.
	 *
	 * Optionally, you may provide an $indexKey to index the values in the returned
	 * array by the values from the $indexKey column in the input array.
	 *
	 * @param array      $input     A multi-dimensional array (record set) from which to pull
	 *                              a column of values.
	 * @param int|string $columnKey The column of values to return. This value may be the
	 *                              integer key of the column you wish to retrieve, or it
	 *                              may be the string key name for an associative array.
	 * @param mixed      $indexKey  (Optional.) The column to use as the index/keys for
	 *                              the returned array. This value may be the integer key
	 *                              of the column, or it may be the string key name.
	 *
	 * @return array|boolean|null
	 */
	function array_column( $input = null, $columnKey = null, $indexKey = null ) {
		// Using func_get_args() in order to check for proper number of
		// parameters and trigger errors exactly as the built-in array_column()
		// does in PHP 5.5.
		$argc   = func_num_args();
		$params = func_get_args();

		if ( $argc < 2 ) {
			trigger_error( sprintf( 'array_column() expects at least 2 parameters, %s given.', $argc ), E_USER_WARNING );

			return null;
		}

		if ( ! is_array( $params[0] ) ) {
			trigger_error( sprintf( 'array_column() expects parameter 1 to be array, %s given.', gettype( $params[0] ) ), E_USER_WARNING );

			return null;
		}

		if ( ! is_int( $params[1] ) && ! is_float( $params[1] ) && ! is_string( $params[1] ) && $params[1] !== null && ! ( is_object( $params[1] ) && method_exists( $params[1], '__toString' ) ) ) {
			trigger_error( 'array_column(): The column key should be either a string or an integer.', E_USER_WARNING );

			return false;
		}

		if ( isset( $params[2] ) && ! is_int( $params[2] ) && ! is_float( $params[2] ) && ! is_string( $params[2] ) && ! ( is_object( $params[2] ) && method_exists( $params[2], '__toString' ) ) ) {
			trigger_error( 'array_column(): The index key should be either a string or an integer.', E_USER_WARNING );

			return false;
		}

		$paramsInput     = $params[0];
		$paramsColumnKey = ( $params[1] !== null ) ? (string) $params[1] : null;

		$paramsIndexKey = null;
		if ( isset( $params[2] ) ) {
			if ( is_float( $params[2] ) || is_int( $params[2] ) ) {
				$paramsIndexKey = (int) $params[2];
			} else {
				$paramsIndexKey = (string) $params[2];
			}
		}

		$resultArray = [];

		foreach ( $paramsInput as $row ) {
			$key    = $value = null;
			$keySet = $valueSet = false;

			if ( $paramsIndexKey !== null && array_key_exists( $paramsIndexKey, $row ) ) {
				$keySet = true;
				$key    = (string) $row[ $paramsIndexKey ];
			}

			if ( $paramsColumnKey === null ) {
				$valueSet = true;
				$value    = $row;
			} elseif ( is_array( $row ) && array_key_exists( $paramsColumnKey, $row ) ) {
				$valueSet = true;
				$value    = $row[ $paramsColumnKey ];
			}

			if ( $valueSet ) {
				if ( $keySet ) {
					$resultArray[ $key ] = $value;
				} else {
					$resultArray[] = $value;
				}
			}
		}

		return $resultArray;
	}
}// End if().

/**
 * Determines the receipt visibility status.
 *
 * @param int $donation_id Donation ID.
 *
 * @return bool Whether the receipt is visible or not.
 * @since 1.3.2
 */
function give_can_view_receipt( $donation_id ) {

	global $give_receipt_args;

	$donor            = false;
	$can_view_receipt = false;

	// Bail out, if donation id doesn't exist.
	if ( empty( $donation_id ) ) {
		return $can_view_receipt;
	}

	$give_receipt_args['id'] = $donation_id;

	// Add backward compatibility.
	if ( ! is_numeric( $donation_id ) ) {
		$give_receipt_args['id'] = give_get_donation_id_by_key( $donation_id );
	}

	// Return to download receipts from admin panel.
	if ( current_user_can( 'export_give_reports' ) ) {

		/**
		 * This filter will be used to modify can view receipt response when accessed from admin.
		 *
		 * @since 2.3.1
		 */
		return apply_filters( 'give_can_admin_view_receipt', true );
	}

	if ( is_user_logged_in() || current_user_can( 'view_give_sensitive_data' ) ) {

		// Proceed only, if user is logged in or can view sensitive Give data.
		$donor = Give()->donors->get_donor_by( 'user_id', get_current_user_id() );

	} elseif ( ! is_user_logged_in() ) {

		// Check whether it is purchase session?
		// This condition is to show receipt to donor after donation.
		$purchase_session = give_get_purchase_session();

		if (
			! empty( $purchase_session )
			&& absint( $purchase_session['donation_id'] ) === absint( $donation_id )
		) {
			$donor = Give()->donors->get_donor_by( 'email', $purchase_session['user_email'] );
		}

		// Check whether it is receipt access session?
		$receipt_session    = give_get_receipt_session();
		$email_access_token = ! empty( $_COOKIE['give_nl'] ) ? give_clean( $_COOKIE['give_nl'] ) : false;

		if (
			! empty( $receipt_session ) ||
			(
				give_is_setting_enabled( give_get_option( 'email_access' ) ) &&
				! empty( $email_access_token )
			)
		) {
			$donor = ! empty( $email_access_token )
				? Give()->donors->get_donor_by_token( $email_access_token )
				: false;
		}
	}

	// If donor object exists, compare the donation ids of donor with the donation receipt donor tries to access.
	if ( is_object( $donor ) ) {
		$is_donor_donated = in_array( (int) $donation_id, array_map( 'absint', explode( ',', $donor->payment_ids ) ), true );
		$can_view_receipt = $is_donor_donated ? true : $can_view_receipt;

		if ( ! $is_donor_donated ) {
			Give()->session->set( 'donor_donation_mismatch', true );
		}
	}

	return (bool) apply_filters( 'give_can_view_receipt', $can_view_receipt, $donation_id );

}

/**
 * Fallback for cal_days_in_month
 *
 * Fallback in case the calendar extension is not loaded in PHP; Only supports Gregorian calendar
 */
if ( ! function_exists( 'cal_days_in_month' ) ) {
	/**
	 * cal_days_in_month
	 *
	 * @param int $calendar
	 * @param int $month
	 * @param int $year
	 *
	 * @return bool|string
	 */
	function cal_days_in_month( $calendar, $month, $year ) {
		return date( 't', mktime( 0, 0, 0, $month, 1, $year ) );
	}
}

/**
 * Get plugin info including status, type, and license validation.
 *
 * @return array Plugin info plus status, type, and license validation if
 *               available.
 * @since 1.8.0
 *
 * @todo  update this function to query give addon and additional
 *
 * This is an enhanced version of get_plugins() that returns the status
 * (`active` or `inactive`) of all plugins, type of plugin (`add-on` or `other`
 * and license validation for Give add-ons (`true` or `false`). Does not include
 * MU plugins.
 */
function give_get_plugins( $args = [] ) {
	$plugins             = get_plugins();
	$active_plugin_paths = (array) get_option( 'active_plugins', [] );

	if ( is_multisite() ) {
		$network_activated_plugin_paths = array_keys( get_site_option( 'active_sitewide_plugins', [] ) );
		$active_plugin_paths            = array_merge( $active_plugin_paths, $network_activated_plugin_paths );
	}

	foreach ( $plugins as $plugin_path => $plugin_data ) {
		// Is plugin active?
		if ( in_array( $plugin_path, $active_plugin_paths ) ) {
			$plugins[ $plugin_path ]['Status'] = 'active';
		} else {
			$plugins[ $plugin_path ]['Status'] = 'inactive';
		}

		$dirname                         = strtolower( dirname( $plugin_path ) );
		$plugins[ $plugin_path ]['Dir']  = $dirname;
		$plugins[ $plugin_path ]['Path'] = $plugin_path;

		// A third party add-on may contain more then one author like sofort, so it is better to compare array.
		$author = false !== strpos( $plugin_data['Author'], ',' )
			? array_map( 'trim', explode( ',', $plugin_data['Author'] ) )
			: [ $plugin_data['Author'] ];

		// Is the plugin a Give add-on?
		if (
			false !== strpos( $dirname, 'give-' )
			&& (
				false !== strpos( $plugin_data['PluginURI'], 'givewp.com' )
				|| array_intersect( $author, [ 'WordImpress', 'GiveWP' ] )
			)
		) {
			// Plugin is a Give-addon.
			$plugins[ $plugin_path ]['Type'] = 'add-on';

			$license_active = Give_License::get_license_by_plugin_dirname( $dirname );

			// Does a valid license exist?
			$plugins[ $plugin_path ]['License'] = $license_active && 'valid' === $license_active['license'];

		} else {
			// Plugin is not a Give add-on.
			$plugins[ $plugin_path ]['Type'] = 'other';
		}
	}

	if ( ! empty( $args['only_add_on'] ) ) {
		$plugins = array_filter(
			$plugins,
			static function( $plugin ) {
				return 'add-on' === $plugin['Type'];
			}
		);
	}

	if ( ! empty( $args['only_premium_add_ons'] ) ) {
		$premiumAddonsListManger = give( PremiumAddonsListManager::class );

		foreach ( $plugins as $key => $plugin ) {
			if ( 'add-on' !== $plugin['Type'] ) {
				unset( $plugins[ $key ] );
			}

			if ( ! $premiumAddonsListManger->isPremiumAddons( $plugin['PluginURI'] ) ) {
				unset( $plugins[ $key ] );
			}
		}
	}

	return $plugins;
}

/**
 * Check if terms enabled or not for form.
 *
 * @param $form_id
 *
 * @return bool
 * @since 1.8
 */
function give_is_terms_enabled( $form_id ) {
	$form_option = give_get_meta( $form_id, '_give_terms_option', true );

	if ( give_is_setting_enabled( $form_option, 'global' ) && give_is_setting_enabled( give_get_option( 'terms' ) ) ) {
		return true;

	} elseif ( give_is_setting_enabled( $form_option ) ) {
		return true;

	} else {
		return false;
	}
}

/**
 * Delete donation stats cache.
 *
 * @param string|array $date_range Date for stats.
 *                                 Date value should be in today, yesterday, this_week, last_week, this_month,
 *                                 last_month, this_quarter, last_quarter, this_year, last_year. For date value other,
 *                                 all cache will be removed.
 *
 * @param array        $args
 *
 * @return WP_Error|bool
 * @since 1.8.7
 *
 * @todo  Resolve stats cache key naming issue. Currently it is difficult to regenerate cache key.
 */
function give_delete_donation_stats( $date_range = '', $args = [] ) {

	// Delete all cache.
	$status = Give_Cache::delete( Give_Cache::get_options_like( 'give_stats' ) );

	/**
	 * Fire the action when donation stats delete.
	 *
	 * @param string|array $date_range
	 * @param array        $args
	 *
	 * @since 1.8.7
	 */
	do_action( 'give_delete_donation_stats', $status, $date_range, $args );

	return $status;
}

/**
 * Check if admin creating new donation form or not.
 *
 * @return bool
 * @since 2.0
 */
function give_is_add_new_form_page() {
	$status = false;

	if ( false !== strpos( $_SERVER['REQUEST_URI'], '/wp-admin/post-new.php?post_type=give_forms' ) ) {
		$status = true;
	}

	return $status;
}

/**
 * Get Form/Payment meta.
 *
 * Note: This function will help you to get meta for payment and form.
 *       If you want to get meta for donors then use get_meta of Give_Donor and
 *       If you want to get meta for logs then use get_meta of Give_Logging->logmeta_db.
 *
 * @param int    $id
 * @param string $meta_key
 * @param bool   $single
 * @param bool   $default
 * @param string $meta_type
 *
 * @return mixed
 * @since 1.8.8
 */
function give_get_meta( $id, $meta_key = '', $single = false, $default = false, $meta_type = '' ) {
	switch ( $meta_type ) {
		case 'donation':
			$meta_value = Give()->payment_meta->get_meta( $id, $meta_key, $single );
			break;

		case 'form':
			$meta_value = Give()->form_meta->get_meta( $id, $meta_key, $single );
			break;

		case 'donor':
			$meta_value = Give()->donor_meta->get_meta( $id, $meta_key, $single );
			break;

		default:
			$meta_value = get_post_meta( $id, $meta_key, $single );
	}

	/**
	 * Filter the meta value
	 *
	 * @since 1.8.8
	 */
	$meta_value = apply_filters( 'give_get_meta', $meta_value, $id, $meta_key, $default, $meta_type );

	if ( ( empty( $meta_key ) || empty( $meta_value ) ) && $default ) {
		$meta_value = $default;
	}

	return $meta_value;
}

/**
 * Update Form/Payment meta.
 *
 * @param int    $id
 * @param string $meta_key
 * @param mixed  $meta_value
 * @param mixed  $prev_value
 * @param string $meta_type
 *
 * @return mixed
 * @since 1.8.8
 */
function give_update_meta( $id, $meta_key, $meta_value, $prev_value = '', $meta_type = '' ) {
	switch ( $meta_type ) {
		case 'donation':
			$status = Give()->payment_meta->update_meta( $id, $meta_key, $meta_value, $prev_value );
			break;

		case 'form':
			$status = Give()->form_meta->update_meta( $id, $meta_key, $meta_value, $prev_value );
			break;

		case 'donor':
			$status = Give()->donor_meta->update_meta( $id, $meta_key, $meta_value, $prev_value );
			break;

		default:
			$status = update_post_meta( $id, $meta_key, $meta_value, $prev_value );
	}

	/**
	 * Filter the meta value update status
	 *
	 * @since 1.8.8
	 */
	return apply_filters( 'give_update_meta', $status, $id, $meta_key, $meta_value, $meta_type );
}

/**
 * Delete Form/Payment meta.
 *
 * @param int    $id
 * @param string $meta_key
 * @param string $meta_value
 * @param string $meta_type
 *
 * @return mixed
 * @since 1.8.8
 */
function give_delete_meta( $id, $meta_key, $meta_value = '', $meta_type = '' ) {
	switch ( $meta_type ) {
		case 'donation':
			$status = Give()->payment_meta->delete_meta( $id, $meta_key, $meta_value );
			break;

		case 'form':
			$status = Give()->form_meta->delete_meta( $id, $meta_key, $meta_value );
			break;

		case 'donor':
			$status = Give()->donor_meta->delete_meta( $id, $meta_key, $meta_value );
			break;

		default:
			$status = delete_post_meta( $id, $meta_key, $meta_value );
	}

	/**
	 * Filter the meta value delete status
	 *
	 * @since 1.8.8
	 */
	return apply_filters( 'give_delete_meta', $status, $id, $meta_key, $meta_value, $meta_type );
}

/**
 * Check if the upgrade routine has been run for a specific action
 *
 * @param string $upgrade_action The upgrade action to check completion for
 *
 * @return bool                   If the action has been added to the completed actions array
 * @since  1.0
 */
function give_has_upgrade_completed( $upgrade_action = '' ) {
	// Bailout.
	if ( empty( $upgrade_action ) ) {
		return false;
	}

	// Fresh install?
	// If fresh install then all upgrades will be consider as completed.
	$is_fresh_install = ! Give_Cache_Setting::get_option( 'give_version' );
	if ( $is_fresh_install ) {
		return true;
	}

	$completed_upgrades = give_get_completed_upgrades();

	return in_array( $upgrade_action, $completed_upgrades );

}

/**
 * For use when doing 'stepped' upgrade routines, to see if we need to start somewhere in the middle
 *
 * @return mixed   When nothing to resume returns false, otherwise starts the upgrade where it left off
 * @since 1.8
 */
function give_maybe_resume_upgrade() {
	$doing_upgrade = get_option( 'give_doing_upgrade', false );
	if ( empty( $doing_upgrade ) ) {
		return false;
	}

	return $doing_upgrade;
}

/**
 * Adds an upgrade action to the completed upgrades array
 *
 * @param string $upgrade_action The action to add to the completed upgrades array
 *
 * @return bool                   If the function was successfully added
 * @since  1.0
 */
function give_set_upgrade_complete( $upgrade_action = '' ) {

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

	$completed_upgrades   = give_get_completed_upgrades();
	$completed_upgrades[] = $upgrade_action;

	// Remove any blanks, and only show uniques.
	$completed_upgrades = array_unique( array_values( $completed_upgrades ) );

	/**
	 * Fire the action when any upgrade set to complete.
	 *
	 * @since 1.8.12
	 */
	do_action( 'give_set_upgrade_completed', $upgrade_action, $completed_upgrades );

	return update_option( 'give_completed_upgrades', $completed_upgrades, false );
}

/**
 * Get's the array of completed upgrade actions
 *
 * @return array The array of completed upgrades
 * @since  1.0
 */
function give_get_completed_upgrades() {
	return (array) Give_Cache_Setting::get_option( 'give_completed_upgrades' );
}

/**
 * In 2.0 we updated table for log, payment and form.
 *
 * Note: internal purpose only.
 *
 * @param string $type Context for table
 *
 * @return null|array
 *
 * @since 4.9.0 rename function - PHP 8 compatibility
 * @since 2.0
 * @global wpdb  $wpdb
 */
function give_v20_bc_table_details( $type ) {
	global $wpdb;
	$table = [];

	// Bailout.
	if ( empty( $type ) ) {
		return null;
	}

	switch ( $type ) {
		case 'form':
			$table['name']         = $wpdb->formmeta;
			$table['column']['id'] = 'form_id';

			break;

		case 'payment':
			$table['name']         = $wpdb->donationmeta;
			$table['column']['id'] = Give()->payment_meta->get_meta_type() . '_id';
	}

	// Backward compatibility.
	if ( ! give_has_upgrade_completed( 'v20_move_metadata_into_new_table' ) ) {
		$table['name']         = $wpdb->postmeta;
		$table['column']['id'] = 'post_id';
	}

	return $table;
}

/**
 * Remove the Give transaction pages from WP search results.
 *
 * @param WP_Query $query
 *
 * @since 1.8.13
 */
function give_remove_pages_from_search( $query ) {

	if ( ! $query->is_admin && $query->is_search && $query->is_main_query() ) {

		$transaction_failed = give_get_option( 'failure_page', 0 );
		$success_page       = give_get_option( 'success_page', 0 );

		$args = apply_filters(
			'give_remove_pages_from_search',
			[
				$transaction_failed,
				$success_page,
			],
			$query
		);
		$query->set( 'post__not_in', $args );
	}
}

add_action( 'pre_get_posts', 'give_remove_pages_from_search', 10, 1 );

/**
 * Inserts a new key/value before a key in the array.
 *
 * @param string       $key       The key to insert before.
 * @param array        $array     An array to insert in to.
 * @param string       $new_key   The key to insert.
 * @param array|string $new_value An value to insert.
 *
 * @return array The new array if the key exists, the passed array otherwise.
 *
 * @since 1.8.13
 *
 * @see   array_insert_before()
 */
function give_array_insert_before( $key, array &$array, $new_key, $new_value ) {
	if ( array_key_exists( $key, $array ) ) {
		$new = [];
		foreach ( $array as $k => $value ) {
			if ( $k === $key ) {
				$new[ $new_key ] = $new_value;
			}
			$new[ $k ] = $value;
		}

		return $new;
	}

	return $array;
}

/**
 * Inserts a new key/value after a key in the array.
 *
 * @param string       $key       The key to insert after.
 * @param array        $array     An array to insert in to.
 * @param string       $new_key   The key to insert.
 * @param array|string $new_value An value to insert.
 *
 * @return array The new array if the key exists, the passed array otherwise.
 *
 * @since 1.8.13
 *
 * @see   array_insert_before()
 */
function give_array_insert_after( $key, array &$array, $new_key, $new_value ) {
	if ( array_key_exists( $key, $array ) ) {
		$new = [];
		foreach ( $array as $k => $value ) {
			$new[ $k ] = $value;
			if ( $k === $key ) {
				$new[ $new_key ] = $new_value;
			}
		}

		return $new;
	}

	return $array;
}

/**
 * Pluck a certain field out of each object in a list.
 *
 * This has the same functionality and prototype of
 * array_column() (PHP 5.5) but also supports objects.
 *
 * @param array      $list      List of objects or arrays
 * @param int|string $field     Field from the object to place instead of the entire object
 * @param int|string $index_key Optional. Field from the object to use as keys for the new array.
 *                              Default null.
 *
 * @return array Array of found values. If `$index_key` is set, an array of found values with keys
 *               corresponding to `$index_key`. If `$index_key` is null, array keys from the original
 *               `$list` will be preserved in the results.
 * @since 1.8.13
 */
function give_list_pluck( $list, $field, $index_key = null ) {

	if ( ! $index_key ) {
		/**
		 * This is simple. Could at some point wrap array_column()
		 * if we knew we had an array of arrays.
		 */
		foreach ( $list as $key => $value ) {
			if ( is_object( $value ) ) {
				if ( isset( $value->$field ) ) {
					$list[ $key ] = $value->$field;
				}
			} else {
				if ( isset( $value[ $field ] ) ) {
					$list[ $key ] = $value[ $field ];
				}
			}
		}

		return $list;
	}

	/*
	 * When index_key is not set for a particular item, push the value
	 * to the end of the stack. This is how array_column() behaves.
	 */
	$newlist = [];
	foreach ( $list as $value ) {
		if ( is_object( $value ) ) {
			if ( isset( $value->$index_key ) ) {
				$newlist[ $value->$index_key ] = $value->$field;
			} else {
				$newlist[] = $value->$field;
			}
		} else {
			if ( isset( $value[ $index_key ] ) ) {
				$newlist[ $value[ $index_key ] ] = $value[ $field ];
			} else {
				$newlist[] = $value[ $field ];
			}
		}
	}

	$list = $newlist;

	return $list;
}

/**
 * Add meta data field to a donor.
 *
 * @param int    $donor_id   Donor ID.
 * @param string $meta_key   Metadata name.
 * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
 * @param bool   $unique     Optional. Whether the same key should not be added.
 *                           Default false.
 *
 * @return int|false Meta ID on success, false on failure.
 * @since 1.8.13
 */
function add_donor_meta( $donor_id, $meta_key, $meta_value, $unique = false ) {
	return add_metadata( 'give_customer', $donor_id, $meta_key, $meta_value, $unique );
}

/**
 * Remove metadata matching criteria from a Donor meta.
 *
 * You can match based on the key, or key and value. Removing based on key and
 * value, will keep from removing duplicate metadata with the same key. It also
 * allows removing all metadata matching key, if needed.
 *
 * @param int    $donor_id   Donor ID
 * @param string $meta_key   Metadata name.
 * @param mixed  $meta_value Optional. Metadata value.
 *
 * @return bool True on success, false on failure.
 * @since 1.8.13
 */
function delete_donor_meta( $donor_id, $meta_key, $meta_value = '' ) {
	return delete_metadata( 'give_customer', $donor_id, $meta_key, $meta_value );
}

/**
 * Retrieve donor meta field for a donor meta table.
 *
 * @param int    $donor_id Donor ID.
 * @param string $key      Optional. The meta key to retrieve. By default, returns data for all keys.
 * @param bool   $single   Whether to return a single value.
 *
 * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
 *  is true.
 * @since 1.8.13
 */
function get_donor_meta( $donor_id, $key = '', $single = false ) {
	return get_metadata( 'give_customer', $donor_id, $key, $single );
}

/**
 * Update customer meta field based on Donor ID.
 *
 * If the meta field for the donor does not exist, it will be added.
 *
 * @param int    $donor_id   Donor ID.
 * @param string $meta_key   Metadata key.
 * @param mixed  $meta_value Metadata value.
 * @param mixed  $prev_value Optional. Previous value to check before removing.
 *
 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
 * @since 1.8.13
 */
function update_donor_meta( $donor_id, $meta_key, $meta_value, $prev_value = '' ) {
	return update_metadata( 'give_customer', $donor_id, $meta_key, $meta_value, $prev_value );
}


/**
 * Give recalculate income and donation of the donation from ID
 *
 * @param int $form_id Form id of which recalculation needs to be done.
 *
 * @return void
 * @since 1.8.13
 */
function give_recount_form_income_donation( $form_id = 0 ) {
	// Check if form id is not empty.
	if ( ! empty( $form_id ) ) {
		/**
		 * Filter to modify payment status.
		 *
		 * @since 1.8.13
		 */
		$accepted_statuses = apply_filters( 'give_recount_accepted_statuses', [ 'publish' ] );

		/**
		 * Filter to modify args of payment query before recalculating the form total
		 *
		 * @since 1.8.13
		 */
		$args = apply_filters(
			'give_recount_form_stats_args',
			[
				'give_forms' => $form_id,
				'status'     => $accepted_statuses,
				'number'     => - 1,
				'fields'     => 'ids',
			]
		);

		$totals = [
			'sales'    => 0,
			'earnings' => 0,
		];

		$payments = new Give_Payments_Query( $args );
		$payments = $payments->get_payments();

		if ( $payments ) {
			foreach ( $payments as $payment ) {
				// Ensure acceptable status only.
				if ( ! in_array( $payment->post_status, $accepted_statuses ) ) {
					continue;
				}

				// Ensure only payments for this form are counted.
				if ( $payment->form_id != $form_id ) {
					continue;
				}

				$totals['sales'] ++;
				$totals['earnings'] += $payment->total;

			}
		}
		give_update_meta( $form_id, '_give_form_sales', $totals['sales'] );
		give_update_meta( $form_id, '_give_form_earnings', give_sanitize_amount_for_db( $totals['earnings'] ) );
	}// End if().
}


/**
 * Get attribute string
 *
 * @param array $attributes
 * @param array $default_attributes
 *
 * @return string
 * @since 1.8.17
 */
function give_get_attribute_str( $attributes, $default_attributes = [] ) {
	$attribute_str = '';

	if ( isset( $attributes['attributes'] ) ) {
		$attributes = $attributes['attributes'];
	}

	if ( ! empty( $default_attributes ) ) {
		$attributes = wp_parse_args( $attributes, $default_attributes );
	}

	if ( empty( $attributes ) ) {
		return $attribute_str;
	}

	foreach ( $attributes as $tag => $value ) {
		if ( 'value' == $tag ) {
			$value = esc_attr( $value );
		}

		$attribute_str .= " {$tag}=\"{$value}\"";
	}

	return trim( $attribute_str );
}

/**
 * Get the upload dir path
 *
 * @return string $wp_upload_dir;
 * @since 1.8.17
 */
function give_get_wp_upload_dir() {
	$wp_upload_dir = wp_upload_dir();

	return ( ! empty( $wp_upload_dir['path'] ) ? $wp_upload_dir['path'] : false );
}

/**
 * Get the data from uploaded JSON file
 *
 * @param string $file_name filename of the json file that is being uploaded
 *
 * @return string|bool $file_contents File content
 * @since 1.8.17
 */
function give_get_core_settings_json( $file_name ) {
	$upload_dir = give_get_wp_upload_dir();
	$file_path  = $upload_dir . '/' . $file_name;

	if ( is_wp_error( $file_path ) || empty( $file_path ) ) {
		Give_Admin_Settings::add_error( 'give-import-csv', __( 'Please upload or provide a valid JSON file.', 'give' ) );
	}

	$file_contents = file_get_contents( $file_path );

	return $file_contents;
}

/**
 * Get number of donation to show when user is not login.
 *
 * @return int $country The two letter country code for the site's base country
 * @since 1.8.17
 */
function give_get_limit_display_donations() {
	return give_get_option( 'limit_display_donations', 1 );
}

/**
 * Add footer to the table when donor is view the donation history page with out login
 *
 * @since 1.8.17
 */
function give_donation_history_table_end() {
	$email = Give()->session->get( 'give_email' );
	?>
	<tfoot>
		<tr>
			<td colspan="9999">
				<div class="give-security-wrap">
					<div class="give-security-column give-security-description-wrap">
						<?php
						echo sprintf( __( 'For security reasons, please confirm your email address (%s) to view your complete donation history.', 'give' ), $email );
						?>
					</div>
					<div class="give-security-column give-security-button-wrap">
						<a href="#" data-email="<?php echo $email; ?>" id="give-confirm-email-btn"
						   class="give-confirm-email-btn give-btn">
							<?php _e( 'Confirm Email', 'give' ); ?>
						</a>
						<span><?php _e( 'Email Sent!', 'give' ); ?></span>
					</div>
				</div>
			</td>
		</tr>
	</tfoot>
	<?php
}


/**
 * Wrapper for _doing_it_wrong.
 *
 * @param string $function
 * @param string $message
 * @param string $version deprecated
 *
 * @return void
 * @since  1.8.18
 * @since  2.5.13 Refactor function
 */
function give_doing_it_wrong( $function, $message, $version = null ) {
	/**
	 * Fires while calling function incorrectly.
	 *
	 * Allow you to hook to incorrect function call.
	 *
	 * @param string $function    The function that was called.
	 * @param string $replacement Optional. The function that should have been called.
	 * @param string $version     The plugin version that deprecated the function.
	 *
	 * @since 2.5.13
	 */
	do_action( 'give_doing_it_wrong', $function, $message, $version );

	$show_errors = current_user_can( 'manage_options' );

	// Allow plugin to filter the output error trigger.
	if ( WP_DEBUG && apply_filters( 'give_doing_it_wrong_trigger_error', $show_errors ) ) {
		trigger_error( sprintf( __( '%1$s was called <strong>incorrectly</strong>. %2$s', 'give' ), $function, $message ) );
		trigger_error( print_r( wp_debug_backtrace_summary(), 1 ) );// Limited to previous 1028 characters, but since we only need to move back 1 in stack that should be fine.
	}
}


/**
 * Remove limit from running php script complete.
 *
 * @since 1.8.18
 */
function give_ignore_user_abort() {
	ignore_user_abort( true );

	if ( ! give_is_func_disabled( 'set_time_limit' )) {
		set_time_limit( 0 );
	}
}

/**
 * Get post type count.
 *
 * @param string $post_type
 * @param array  $args
 *
 * @return int
 * @since 2.0.2
 */
function give_get_total_post_type_count( $post_type = '', $args = [] ) {
	global $wpdb;
	$where = '';

	if ( ! $post_type ) {
		return 0;
	}

	// Bulit where query
	if ( ! empty( $post_type ) ) {
		$where .= ' WHERE';

		if ( is_array( $post_type ) ) {
			$where .= " post_type='" . implode( "' OR post_type='", $post_type ) . "'";
		} else {
			$where .= " post_type='{$post_type}'";
		}
	}

	$result = $wpdb->get_var( "SELECT count(ID) FROM {$wpdb->posts}{$where}" );

	return absint( $result );
}

/**
 * Define a constant if it is not already defined.
 *
 * @param string $name  Constant name.
 * @param string $value Value.
 *
 * @credit WooCommerce
 * @since  2.0.5
 */
function give_maybe_define_constant( $name, $value ) {
	if ( ! defined( $name ) ) {
		define( $name, $value );
	}
}

/**
 * Decode time short tag in string
 *
 * @param string $string
 * @param int    $timestamp
 *
 * @return string
 * @since 2.1.0
 */
function give_time_do_tags( $string, $timestamp = 0 ) {
	$current_time = ! empty( $timestamp ) ? $timestamp : current_time( 'timestamp' );

	$formatted_string = str_replace(
		[
			'{D}',
			'{DD}',
			'{M}',
			'{MM}',
			'{YY}',
			'{YYYY}',
			'{H}',
			'{HH}',
			'{N}',
			'{S}',
		],
		[
			date( 'j', $current_time ),
			date( 'd', $current_time ),
			date( 'n', $current_time ),
			date( 'm', $current_time ),
			date( 'Y', $current_time ),
			date( 'Y', $current_time ),
			date( 'G', $current_time ),
			date( 'H', $current_time ),
			date( 's', $current_time ),
		],
		$string
	);

	/**
	 * Filter the parsed string.
	 *
	 * @since 2.1.0
	 */
	return apply_filters( 'give_time_do_tags', $formatted_string, $string, $timestamp );
}


/**
 * Check if Company field enabled or not for form or globally.
 *
 * @param $form_id
 *
 * @return bool
 * @since 2.1
 */
function give_is_company_field_enabled( $form_id ) {
	$form_setting_val   = give_get_meta( $form_id, '_give_company_field', true );
	$global_setting_val = give_get_option( 'company_field' );

	if ( ! empty( $form_setting_val ) ) {
		if ( give_is_setting_enabled( $form_setting_val, [ 'required', 'optional' ] ) ) {
			return true;
		} elseif ( 'global' === $form_setting_val && give_is_setting_enabled(
			$global_setting_val,
			[
				'required',
				'optional',
			]
		) ) {
			return true;
		} else {
			return false;
		}
	} elseif ( give_is_setting_enabled( $global_setting_val, [ 'required', 'optional' ] ) ) {
		return true;

	} else {
		return false;
	}
}

/**
 * Check if Last Name field is required
 *
 * @param $form_id
 *
 * @return bool
 * @since 2.15.0
 */
function give_is_last_name_required( $form_id ) {
	$form_setting_val   = give_get_meta( $form_id, '_give_last_name_field_required', true );
	$global_setting_val = give_get_option( 'last_name_field_required' );

	if ( ! empty( $form_setting_val ) ) {
		if ( 'required' === $form_setting_val ) {
			return true;
		}

		return 'global' === $form_setting_val && 'required' === $global_setting_val;
	}

	return 'required' === $global_setting_val;
}

/**
 * Check if anonymous donation field enabled or not for form or globally.
 *
 * @param $form_id
 *
 * @return bool
 * @since 2.1
 */
function give_is_anonymous_donation_field_enabled( $form_id ) {
	$form_setting_val   = give_get_meta( $form_id, '_give_anonymous_donation', true, 'global' );
	$global_setting_val = give_get_option( 'anonymous_donation', 'disabled' );

	if ( ! empty( $form_setting_val ) ) {
		if ( give_is_setting_enabled( $form_setting_val ) ) {
			return true;
		} elseif ( 'global' === $form_setting_val && give_is_setting_enabled( $global_setting_val ) ) {
			return true;
		} else {
			return false;
		}
	} elseif ( give_is_setting_enabled( $global_setting_val ) ) {
		return true;
	}

	return false;
}

/**
 * Check if donor comment field enabled or not for form or globally.
 *
 * @param $form_id
 *
 * @return bool
 * @since 2.1
 */
function give_is_donor_comment_field_enabled( $form_id ) {
	$form_setting_val   = give_get_meta( $form_id, '_give_donor_comment', true, 'global' );
	$global_setting_val = give_get_option( 'donor_comment', 'disabled' );

	if ( ! empty( $form_setting_val ) ) {
		if ( give_is_setting_enabled( $form_setting_val ) ) {
			return true;
		} elseif ( 'global' === $form_setting_val && give_is_setting_enabled( $global_setting_val ) ) {
			return true;
		} else {
			return false;
		}
	} elseif ( give_is_setting_enabled( $global_setting_val ) ) {
		return true;
	}

	return false;

}

/**
 * Get add-on user meta value information
 * Note: only for internal use.
 *
 * @param string $banner_addon_name Give add-on name.
 *
 * @return array
 *
 * @since 4.9.0 rename function - PHP 8 compatibility
 * @since 2.1.0
 */
function give_get_active_by_user_meta( $banner_addon_name ) {
	global $wpdb;

	// Get the option key.
	$option_name = Give_Addon_Activation_Banner::get_banner_user_meta_key( $banner_addon_name );
	$data        = [];

	if ( empty( $GLOBALS['give_addon_activated_by_user'] ) ) {
		$GLOBALS['give_addon_activated_by_user'] = [];

		// Get the meta of activation banner by user.
		$activation_banners = $wpdb->get_results(
			"
					SELECT option_name, option_value
					FROM {$wpdb->options}
					WHERE option_name LIKE '%_active_by_user%'
					AND option_name LIKE '%give_addon%'
					",
			ARRAY_A
		);

		if ( ! empty( $activation_banners ) ) {
			$GLOBALS['give_addon_activated_by_user'] = array_combine(
				wp_list_pluck( $activation_banners, 'option_name' ),
				wp_list_pluck( $activation_banners, 'option_value' )
			);
		}
	}

	if ( array_key_exists( $option_name, $GLOBALS['give_addon_activated_by_user'] ) ) {
		$data = maybe_unserialize( $GLOBALS['give_addon_activated_by_user'][ $option_name ] );
	}

	return $data;
}

/**
 * Get time interval for which nonce is valid
 *
 * @return int
 * @since 2.1.3
 */
function give_get_nonce_life() {
	/**
	 * Filters the lifespan of nonces in seconds.
	 *
	 * @see wp-inlucdes/pluggable.php:wp_nonce_tick
	 */
	return (int) apply_filters( 'nonce_life', DAY_IN_SECONDS );
}

/**
 * Get nonce field without id
 *
 * @param string $action
 * @param string $name
 * @param bool   $referer
 *
 * @return string
 * @since 2.1.3
 */
function give_get_nonce_field( $action, $name, $referer = false ) {
	return str_replace(
		"id=\"{$name}\"",
		'',
		wp_nonce_field( $action, $name, $referer, false )
	);
}

/**
 * Display/Return a formatted goal for a donation form
 *
 * @since 3.16.0 Add form_id to the array return
 * @since 2.1
 *
 * @param int|Give_Donate_Form $form Form ID or Form Object.
 *
 * @return array
 */
function give_goal_progress_stats( $form ) {

	if ( ! $form instanceof Give_Donate_Form ) {
		$form = new Give_Donate_Form( $form );
	}

	$goal_format = give_get_form_goal_format( $form->ID );

	/**
	 * Filter the form.
	 * @since 1.8.8
	 */
	$total_goal = apply_filters( 'give_goal_amount_target_output', round( give_maybe_sanitize_amount( $form->goal ), 2 ), $form->ID, $form );

	switch ( $goal_format ) {
		case 'donation':
			/**
			 * Filter the form donations.
			 *
			 * @since 2.1
			 */
			$actual = apply_filters( 'give_goal_donations_raised_output', $form->sales, $form->ID, $form );
			break;
		case 'donors':
			/**
			 * Filter to modify total number if donor for the donation form.
			 *
			 * @param int              $donors  Total number of donors that donated to the form.
			 * @param int              $form_id Donation Form ID.
			 * @param Give_Donate_Form $form    instances of Give_Donate_Form.
			 *
			 * @return int $donors Total number of donors that donated to the form.
			 * @since 2.1.3
			 */
			$actual = apply_filters( 'give_goal_donors_target_output', give_get_form_donor_count( $form->ID ), $form->ID, $form );
			break;
		default:
            /**
             * Filter the form income.
             *
             * @since 3.16.0 Revert changes implemented on the 3.14.0 version
             * @since 3.14.0 Replace "$form->earnings" with (new DonationQuery())->form($form->ID)->sumIntendedAmount()
             * @since 1.8.8
             */
            $actual = apply_filters( 'give_goal_amount_raised_output', $form->earnings, $form->ID, $form );
			break;
	}

	$progress = $total_goal ? round( ( $actual / $total_goal ) * 100, 2 ) : 0;

	$stats_array = [
		'raw_actual' => $actual,
		'raw_goal'   => $total_goal,
	];

	/**
	 * Filter the goal progress output
	 *
	 * @since 1.8.8
	 */
	$progress = apply_filters( 'give_goal_amount_funded_percentage_output', $progress, $form->ID, $form );

	// Define Actual Goal based on the goal format.
	switch ( $goal_format ) {
		case 'percentage':
			$actual     = "{$progress}%";
			$total_goal = '';
			break;

		case 'amount' === $goal_format:
			$actual     = give_currency_filter( give_format_amount( $actual ) );
			$total_goal = give_currency_filter( give_format_amount( $total_goal ) );
			break;

		default:
			$actual     = give_format_amount( $actual, [ 'decimal' => false ] );
			$total_goal = give_format_amount( $total_goal, [ 'decimal' => false ] );
			break;
	}

	$stats_array = array_merge(
		[
			'progress' => $progress,
			'actual'   => $actual,
			'goal'     => $total_goal,
			'format'   => $goal_format,
			'form_id' => $form->ID
		],
		$stats_array
	);

	/**
	 * Filter the goal stats
	 *
	 * @since 2.1
	 */
	return apply_filters( 'give_goal_progress_stats', $stats_array );
}

/**
 * Get the admin messages key to show the notices.
 *
 * @return array $message admin message key.
 * @since 2.1.4
 */
function give_get_admin_messages_key() {
	$messages = empty( $_GET['give-messages'] ) ? [] : give_clean( $_GET['give-messages'] );

	// backward compatibility.
	if ( ! empty( $_GET['give-message'] ) ) {
		$messages[] = give_clean( $_GET['give-message'] );
	}

	/**
	 * Filter to modify the admin messages key.
	 *
	 * @param array $message admin message key.
	 *
	 * @return array $message admin message key.
	 * @since 2.1.4
	 */
	return (array) apply_filters( 'give_get_admin_messages_key', $messages );
}

/**
 * Get User Agent String.
 *
 * @return array|string
 * @since 2.1.4
 */
function give_get_user_agent() {

	// Get User Agent.
	$user_agent = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? give_clean( $_SERVER['HTTP_USER_AGENT'] ) : ''; // WPCS: input var ok.

	return $user_agent;
}

/**
 * Set a cookie - wrapper for setcookie using WP constants.
 *
 * @param string  $name   Name of the cookie being set.
 * @param string  $value  Value of the cookie.
 * @param integer $expire Expiry of the cookie.
 * @param bool    $secure Whether the cookie should be served only over https.
 *
 * @since 2.2.0
 */
function give_setcookie( $name, $value, $expire = 0, $secure = false ) {
	if ( ! headers_sent() ) {
		setcookie(
			$name,
			$value,
			$expire,
			COOKIEPATH ? COOKIEPATH : '/',
			COOKIE_DOMAIN,
			$secure,
			apply_filters( 'give_cookie_httponly', false, $name, $value, $expire, $secure )
		);
	}
}

/**
 * Get formatted billing address.
 *
 * @param array $address
 *
 * @return string Formatted address.
 * @since 2.2.0
 */
function give_get_formatted_address( $address = [] ) {
	$formatted_address = '';

	/**
	 * Address format.
	 *
	 * @since 2.2.0
	 */
	$address_format = apply_filters( 'give_address_format_template', "{street_address}\n{city}, {state} {postal_code}\n{country}" );
	preg_match_all( '/{([A-z0-9\-\_\ ]+)}/s', $address_format, $matches );

	if ( ! empty( $matches ) && ! empty( $address ) ) {
		$address_values = [];

		foreach ( $matches[1] as $address_tag ) {
			$address_values[ $address_tag ] = '';

			if ( isset( $address[ $address_tag ] ) ) {
				$address_values[ $address_tag ] = $address[ $address_tag ];
			}
		}

		$formatted_address = str_ireplace( $matches[0], $address_values, $address_format );
	}

	/**
	 * Give get formatted address.
	 *
	 * @param string $formatted_address Formatted address.
	 * @param string $address_format    Format of the address.
	 *
	 * @since 2.2.0
	 */
	$formatted_address = apply_filters( 'give_get_formatted_address', $formatted_address, $address_format, $address );

	return $formatted_address;
}

/**
 * Get safe url for assets
 * Note: this function will return url without http protocol
 *
 * @param string $url URL
 *
 * @return string
 * @since 2.2.0
 */
function give_get_safe_asset_url( $url ) {

	// Bailout, if empty URL passed.
	if ( empty( $url ) ) {
		return $url;
	}

	$schema        = parse_url( $url, PHP_URL_SCHEME );
	$schema_length = strlen( $schema ) + 1;
	$url           = substr( $url, $schema_length );

	/**
	 * Fire the filter
	 *
	 * @since 2.2.0
	 */
	return apply_filters( 'give_get_safe_asset_url', $url );
}

/**
 * Give get formatted date.
 * Note: This function does not work well with localize translated  date strings
 *
 * @param string $date           Date.
 * @param string $format         Date Format.
 * @param string $current_format Current date Format.
 * @param bool   $localize
 *
 * @return string
 * @since 2.3.0
 */
function give_get_formatted_date( $date, $format = 'Y-m-d', $current_format = '', $localize = false ) {
	$current_format = empty( $current_format ) ? give_date_format() : $current_format;
	$date_obj       = DateTime::createFromFormat( $current_format, $date );
	$formatted_date = '';

	if ( $date_obj instanceof DateTime ) {
		$formatted_date = $localize ?
			date_i18n( $format, $date_obj->getTimestamp() ) :
			$date_obj->format( $format );
	}

	/**
	 * Give get formatted date.
	 *
	 * @param string $formatted_date Formatted date.
	 * @param array
	 *
	 * @since 2.3.0
	 */
	return apply_filters( 'give_get_formatted_date', $formatted_date, [ $date, $format, $current_format ] );
}

/**
 * This function will be used to fetch the donation receipt link.
 *
 * @param int $donation_id Donation ID.
 *
 * @return string
 * @since 2.3.1
 */
function give_get_receipt_link( $donation_id ) {

	return sprintf(
		'<a href="%1$s">%2$s</a>',
		esc_url( give_get_receipt_url( $donation_id ) ),
		esc_html__( 'View the receipt in your browser &raquo;', 'give' )
	);

}

/**
 * Get receipt_url
 *
 * @param int $donation_id Donation ID.
 *
 * @return string
 * @since 2.0
 */
function give_get_receipt_url( $donation_id ) {

	$receipt_url = esc_url_raw(
		add_query_arg(
			[
				'donation_id' => $donation_id,
			],
			give_get_history_page_uri()
		)
	);

	return $receipt_url;
}

/**
 * Get "View in browser" Receipt Link for email.
 *
 * @param int $donation_id Donation ID.
 *
 * @return string
 * @since 2.4.1
 */
function give_get_view_receipt_link( $donation_id ) {

	return sprintf(
		'<a href="%1$s">%2$s</a>',
		give_get_view_receipt_url( $donation_id ),
		esc_html__( 'View the receipt in your browser &raquo;', 'give' )
	);

}

/**
 * Get "View in browser" Receipt URL for email.
 *
 * @param int $donation_id Donation ID.
 *
 * @return string
 * @since 2.4.1
 */
function give_get_view_receipt_url( $donation_id ) {

	$receipt_url = esc_url_raw(
		add_query_arg(
			[
				'action'     => 'view_in_browser',
				'_give_hash' => give_get_payment_key( $donation_id ),
			],
			give_get_history_page_uri()
		)
	);

	return $receipt_url;
}

/**
 * This function is used to display donation receipt content based on the parameters.
 *
 * @param $args
 *
 * @return bool|mixed
 * @since 2.4.1
 */
function give_display_donation_receipt( $args ) {

	global $give_receipt_args;

	$give_receipt_args = $args;

	ob_start();

	$get_data     = give_clean( filter_input_array( INPUT_GET ) );
	$donation_id  = ! empty( $get_data['donation_id'] ) ? $get_data['donation_id'] : false;
	$receipt_type = ! empty( $get_data['receipt_type'] ) ? $get_data['receipt_type'] : false;

	$give_receipt_args['id'] = $donation_id;

	if ( 'view_in_browser' !== $receipt_type ) {

		$email_access    = give_get_option( 'email_access' );
		$is_email_access = give_is_setting_enabled( $email_access ) && ! Give()->email_access->token_exists;

		// No donation id found & Email Access is Turned on.
		if ( ! $donation_id ) {

			if ( $is_email_access ) {
				give_get_template_part( 'email-login-form' );
			} else {
				echo Give_Notices::print_frontend_notice( $args['error'], false, 'error' );
			}

			return ob_get_clean();
		}

		// Donation id provided, but user is logged out. Offer them the ability to login and view the receipt.
		if ( ! ( $user_can_view = give_can_view_receipt( $donation_id ) ) ) {

			if ( true === Give()->session->get( 'donor_donation_mismatch' ) ) {

				/**
				 * This filter will be used to modify the donor mismatch text for front end error notice.
				 *
				 * @since 2.3.1
				 */
				$donor_mismatch_text = apply_filters( 'give_receipt_donor_mismatch_notice_text', __( 'You are trying to access invalid donation receipt. Please try again.', 'give' ) );

				echo Give_Notices::print_frontend_notice(
					$donor_mismatch_text,
					false,
					'error'
				);

			} elseif ( $is_email_access ) {

				give_get_template_part( 'email-login-form' );

			} else {

				global $give_login_redirect;

				$give_login_redirect = give_get_current_page_url();

				Give_Notices::print_frontend_notice(
					apply_filters(
						'give_must_be_logged_in_error_message',
						__( 'You must be logged in to view this donation receipt.', 'give' )
					)
				);

				give_get_template_part( 'shortcode', 'login' );
			}

			return ob_get_clean();
		}

		/**
		 * Check if the user has permission to view the receipt.
		 *
		 * If user is logged in, user ID is compared to user ID of ID stored in payment meta
		 * or if user is logged out and donation was made as a guest, the donation session is checked for
		 * or if user is logged in and the user can view sensitive shop data.
		 */
		if ( ! apply_filters( 'give_user_can_view_receipt', $user_can_view, $args ) ) {
			return Give_Notices::print_frontend_notice( $args['error'], false, 'error' );
		}
	} else {
		$donation_id             = give_get_donation_id_by_key( $get_data['donation_id'] );
		$give_receipt_args['id'] = $donation_id;
	}

	give_get_template_part( 'shortcode', 'receipt' );

	return ob_get_clean();
}


/**
 * Get plugin add-on readme.txt path
 * Note: only for internal use
 *
 * @param      $plugin_slug
 * @param bool        $by_plugin_name
 *
 * @return mixed|void
 * @since 2.5.0
 */
function give_get_addon_readme_url( $plugin_slug, $by_plugin_name = false ) {

	if ( $by_plugin_name ) {
		$plugin_slug = Give_License::get_short_name( $plugin_slug );
	}

	$website_url = trailingslashit( Give_License::get_website_url() );

	/**
	 * Filter the addon readme.txt url
	 *
	 * @since 2.1.4
	 */
	$url = apply_filters(
		'give_addon_readme_file_url',
		"{$website_url}downloads/plugins/{$plugin_slug}/readme.txt",
		$plugin_slug,
		$by_plugin_name
	);

	return $url;
}

/**
 * Refresh all givewp license.
 *
 * @since 4.15.0 add support for Harbor unified licenses
 * @since 4.8.0 Update active license date after license refresh when active license is found
 * @since 4.3.0 updated to store platform fee percentage
 * @since 2.27.0 delete update_plugins transient instead of invalidate it
 * @since  2.5.0
 *
 * @param  bool  $wp_check_updates
 *
 * @access public
 * @return array|WP_Error
 */
function give_refresh_licenses( $wp_check_updates = true ) {
	$give_licenses = get_option( 'give_licenses', [] );
	$give_addons   = give_get_plugins( [ 'only_premium_add_ons' => true ] );

	if ( ! $give_licenses && ! $give_addons ) {
		return [];
	}

	$license_keys = $give_licenses ? implode( ',', array_keys( $give_licenses ) ) : '';

	// Exclude addons already licensed via Harbor from the unlicensed list sent to the legacy API.
	$legacy_unlicensed_addons = array_filter( $give_addons, static function ( $plugin ) {
		return ! $plugin['License'];
	} );

	$unlicensed_give_addon = $legacy_unlicensed_addons
		? array_values(
			array_diff(
				array_map(
					function ( $plugin_name ) {
						return trim( str_replace( 'Give - ', '', $plugin_name ) );
					},
					wp_list_pluck( $legacy_unlicensed_addons, 'Name', true )
				),
				wp_list_pluck( $give_licenses, 'item_name', true )
			)
		)
		: [];

	// Nothing for the legacy API to check.
	if ( empty( $give_licenses ) && empty( $unlicensed_give_addon ) ) {
		return [];
	}

	$tmp = Give_License::request_license_api(
		[
			'edd_action' => 'check_licenses',
			'licenses'   => $license_keys,
			'unlicensed' => implode( ',', $unlicensed_give_addon ),
		],
		true
	);

	if ( ! $tmp || is_wp_error( $tmp ) ) {
		return [];
	}

	// Prevent fatal error on WP 4.9.10
	// Because wp_list_pluck accept only array or array of array in that version.
	// @see https://github.com/impress-org/give/issues/4176
	$tmp = json_decode( json_encode( $tmp ), true );

	// Remove unlicensed add-on from response.
	$tmp_unlicensed = [];
	foreach ( $tmp as $key => $data ) {
		if ( empty( $data ) ) {
			unset( $tmp[ "{$key}" ] );
			continue;
		}

		if ( empty( $data['check_license'] ) ) {
			$tmp_unlicensed[ $key ] = $data;
			unset( $tmp[ "{$key}" ] );
		}
	}

	$check_licenses = wp_list_pluck( $tmp, 'check_license' );

	/* @var stdClass $data */
	foreach ( $check_licenses as $key => $data ) {
		if ( is_wp_error( $data ) ) {
			continue;
		}

		if ( ! $data['success'] ) {
			unset( $give_licenses[ $key ] );
			continue;
		}

		$give_licenses[ $key ] = $data;
	}

	$tmp_update_plugins = array_merge(
		array_filter( wp_list_pluck( $tmp, 'get_version' ) ),
		array_filter( wp_list_pluck( $tmp, 'get_versions' ) )
	);

	if ( $tmp_unlicensed ) {
		$tmp_update_plugins = array_merge( $tmp_update_plugins, $tmp_unlicensed );
	}

	update_option( 'give_licenses', $give_licenses, 'no' );
	update_option( 'give_get_versions', $tmp_update_plugins, 'no' );

	$refresh         = Give_License::refresh_license_status();
	$refresh['time'] = time();

	update_option( 'give_licenses_refreshed_last_checked', $refresh, 'no' );

    $platform_fee_percentage = get_platform_fee_from_licenses();

    if (!is_null($platform_fee_percentage)) {
        update_option( LicenseOptionKeys::PLATFORM_FEE_PERCENTAGE, $platform_fee_percentage, 'no' );
    }

    // Update active license date after license refresh when active license is found
    $active_license_date = get_active_license_date();

	if(!is_null($active_license_date)) {
        update_option(LicenseOptionKeys::LAST_ACTIVE_LICENSE_DATE, $active_license_date, 'no');
    }

	// Tell WordPress to look for updates.
	if ( $wp_check_updates ) {
		delete_site_transient('update_plugins');
	}

	return [
		'give_licenses'     => $give_licenses,
		'give_get_versions' => $tmp_update_plugins,
	];
}

/**
 * Get platform fee from stored licenses
 */
function get_platform_fee_from_licenses(): ?float
{
    /** @var LicenseRepository $repository */
    $repository = give(LicenseRepository::class);

    return $repository->findLowestPlatformFeePercentageFromActiveLicenses();
}

/**
 * Set Active License Date
 *
 * @since 4.8.0
 */
function get_active_license_date(): ?int
{
    /** @var LicenseRepository $repository */
    $repository = give(LicenseRepository::class);

    return $repository->getCurrentActiveLicenseDate();
}

/**
 * Check add-ons updates
 * Note: only for internal use
 *
 * @param stdClass $_transient_data Plugin updates information
 *
 * @return stdClass
 * @since 2.5.0
 */
function give_check_addon_updates( $_transient_data ) {
	if ( ! is_object( $_transient_data ) ) {
		$_transient_data = new stdClass();
	}

	$update_plugins = get_option( 'give_get_versions', [] );
	$check_licenses = get_option( 'give_licenses', [] );

	// Skip legacy update checks when Harbor is managing licensing and no legacy licenses exist.
	if ( empty( $check_licenses ) && lw_harbor_is_product_license_active( 'give' ) ) {
		return $_transient_data;
	}

	if ( ! $update_plugins ) {
		$data = give_refresh_licenses( false );

		if (
			empty( $data['give_get_versions'] )
			|| is_wp_error( $data )
		) {
			return $_transient_data;
		}

		$update_plugins = $data['give_get_versions'];
	}

	foreach ( $update_plugins as $key => $data ) {
		$plugins = ! empty( $check_licenses[ $key ] )
			? ( ! empty( $check_licenses[ $key ]['is_all_access_pass'] ) ? $data : [ $data ] )
			: [ $data ];

		foreach ( $plugins as $plugin ) {
			// This value will be empty if any error occurred when verifying version of add-on.
			if ( empty( $plugin['new_version'] ) ) {
				continue;
			}

			$plugin     = array_map( 'maybe_unserialize', $plugin );
			$tmp_plugin = Give_License::get_plugin_by_slug( $plugin['slug'] );

			if ( ! $tmp_plugin ) {
				continue;
			}

			$plugin['plugin'] = $tmp_plugin['Path'];

			if ( - 1 !== version_compare( $tmp_plugin['Version'], $plugin['new_version'] ) ) {
				$_transient_data->no_update[ $tmp_plugin['Path'] ] = (object) $plugin;
			} else {
				$_transient_data->response[ $tmp_plugin['Path'] ] = (object) $plugin;
			}

			$_transient_data->checked[ $tmp_plugin['Path'] ] = $tmp_plugin['Version'];
		}
	}

	$_transient_data->last_checked = time();

	return $_transient_data;
}

/**
 * Get page by title
 *
 * @since 2.26.0
 *
 * @param string $page_title
 * @param string $output
 * @param string $post_type
 *
 * @return null|WP_Post
 */
function give_get_page_by_title(string $page_title, string $output = OBJECT, string $post_type = 'page')
{
    $args = [
        'title' => $page_title,
        'post_type' => $post_type,
        'post_status' => get_post_stati(),
        'posts_per_page' => 1,
        'update_post_term_cache' => false,
        'update_post_meta_cache' => false,
        'no_found_rows' => true,
        'orderby' => 'post_date ID',
        'order' => 'ASC',
    ];
    $query = new WP_Query($args);
    $pages = $query->posts;

    if (empty($pages)) {
        return null;
    }

    return get_post($pages[0], $output);
}
