<?php
/****************************************************************

	IMPORTANT. PLEASE READ.

	DO NOT EDIT THIS FILE or any other file in the /wp-content/plugins/paid-memberships-pro/ directory.
	Doing so could break the PMPro plugin and/or keep you from upgrading this plugin in the future.
	We regularly release updates to the plugin, including important security fixes and new features.
	You want to be able to upgrade.

	If you were asked to insert code into "your functions.php file", it was meant that you edit the functions.php
	in the root folder of your active theme. e.g. /wp-content/themes/twentytwelve/functions.php
	You can also create a custom plugin to place customization code into. Instructions are here:
	http://www.paidmembershipspro.com/2012/08/create-a-plugin-for-pmpro-customizations/

	Further documentation for customizing Paid Memberships Pro can be found here:
	http://www.paidmembershipspro.com/documentation/

****************************************************************/
if(!function_exists("sornot"))
{
	function sornot($t, $n)
	{
		if($n == 1)
			return $t;
		else
			return $t . "s";
	}
}

//set up wpdb for the tables we need
function pmpro_setDBTables()
{
	global $wpdb;
	$wpdb->hide_errors();
	$wpdb->pmpro_membership_levels = $wpdb->prefix . 'pmpro_membership_levels';
	$wpdb->pmpro_memberships_users = $wpdb->prefix . 'pmpro_memberships_users';
	$wpdb->pmpro_memberships_categories = $wpdb->prefix . 'pmpro_memberships_categories';
	$wpdb->pmpro_memberships_pages = $wpdb->prefix . 'pmpro_memberships_pages';
	$wpdb->pmpro_membership_orders = $wpdb->prefix . 'pmpro_membership_orders';
	$wpdb->pmpro_discount_codes = $wpdb->prefix . 'pmpro_discount_codes';
	$wpdb->pmpro_discount_codes_levels = $wpdb->prefix . 'pmpro_discount_codes_levels';
	$wpdb->pmpro_discount_codes_uses = $wpdb->prefix . 'pmpro_discount_codes_uses';
}
pmpro_setDBTables();

//from: http://stackoverflow.com/questions/5266945/wordpress-how-detect-if-current-page-is-the-login-page/5892694#5892694
function pmpro_is_login_page() {
	return (in_array($GLOBALS['pagenow'], array('wp-login.php', 'wp-register.php')) || is_page("login"));
}

//thanks: http://wordpress.org/support/topic/is_plugin_active
function pmpro_is_plugin_active( $plugin ) {
	return in_array( $plugin, (array) get_option( 'active_plugins', array() ) );
}

//scraping - override n if you have more than 1 group of matches and don't want the first group
function pmpro_getMatches($p, $s, $firstvalue = FALSE, $n = 1)
{
	$ok = preg_match_all($p, $s, $matches);

	if(!$ok)
		return false;
	else
	{
		if($firstvalue)
			return $matches[$n][0];
		else
			return $matches[$n];
	}
}

function pmpro_br2nl($text, $tags = "br")
{
	if(!is_array($tags))
		$tags = explode(" ", $tags);

	foreach($tags as $tag)
	{
		$text = eregi_replace("<" . $tag . "[^>]*>", "\n", $text);
		$text = eregi_replace("</" . $tag . "[^>]*>", "\n", $text);
	}

	return($text);
}

function pmpro_getOption($s, $force = false)
{
	if(get_option("pmpro_" . $s))
		return get_option("pmpro_" . $s);
	else
		return "";
}

function pmpro_setOption($s, $v = NULL)
{
	//no value is given, set v to the p var
	if($v === NULL && isset($_POST[$s]))
		$v = $_POST[$s];

	if(is_array($v))
		$v = implode(",", $v);
	else
		$v = trim($v);

	return update_option("pmpro_" . $s, $v);
}

function pmpro_get_slug($post_id)
{
	global $pmpro_slugs, $wpdb;
	
	//make sure post id is int for security
	$post_id = intval($post_id);
	
	if(!$pmpro_slugs[$post_id])
		$pmpro_slugs[$post_id] = $wpdb->get_var("SELECT post_name FROM $wpdb->posts WHERE ID = '" . $post_id . "' LIMIT 1");

	return $pmpro_slugs[$post_id];
}

function pmpro_url($page = NULL, $querystring = "", $scheme = NULL)
{
	global $besecure;
	$besecure = apply_filters("besecure", $besecure);

	if(!$scheme && $besecure)
		$scheme = "https";
	elseif(!$scheme)
		$scheme = "http";

	if(!$page)
		$page = "levels";

	global $pmpro_pages;

	//start with the permalink
	$url = get_permalink($pmpro_pages[$page]);

	//WPML/etc support
	if(function_exists("icl_object_id") && defined("ICL_LANGUAGE_CODE"))
	{
		$trans_id = icl_object_id($pmpro_pages[$page], "page", false, ICL_LANGUAGE_CODE);
		if(!empty($trans_id))
		{
			$url = get_permalink($trans_id);
		}
	}

	//figure out querystring
	if(strpos($url, "?"))
		$querystring = str_replace("?", "&", $querystring);
	$url .= $querystring;

	//figure out scheme
	if(is_ssl())
		$url = str_replace("http:", "https:", $url);

	return $url;
}

function pmpro_isLevelFree(&$level)
{
	if(!empty($level) && $level->initial_payment <= 0 && $level->billing_amount <= 0 && $level->trial_amount <= 0)
		return true;
	else
		return false;
}

function pmpro_isLevelRecurring(&$level)
{
	if(!empty($level) && ($level->billing_amount > 0 || $level->trial_amount > 0))
		return true;
	else
		return false;
}

function pmpro_isLevelTrial(&$level)
{
	if($level->trial_limit > 0)
	{
		return true;
	}
	else
		return false;
}

function pmpro_isLevelExpiring(&$level)
{
	if($level->expiration_number > 0)
		return true;
	else
		return false;
}

function pmpro_getLevelCost(&$level, $tags = true, $short = false)
{
	//initial payment
	if(!$short)
		$r = sprintf(__('The price for membership is <strong>%s</strong> now', 'pmpro'), pmpro_formatPrice($level->initial_payment));
	else
		$r = sprintf(__('<strong>%s</strong> now', 'pmpro'), pmpro_formatPrice($level->initial_payment));

	//recurring part
	if($level->billing_amount != '0.00')
	{
		if($level->billing_limit > 1)
		{
			if($level->cycle_number == '1')
			{
				$r .= sprintf(__(' and then <strong>%s per %s for %d more %s</strong>.', 'pmpro'), pmpro_formatPrice($level->billing_amount), pmpro_translate_billing_period($level->cycle_period), $level->billing_limit, pmpro_translate_billing_period($level->cycle_period, $level->billing_limit));
			}
			else
			{
				$r .= sprintf(__(' and then <strong>%s every %d %s for %d more %s</strong>.', 'pmpro'), pmpro_formatPrice($level->billing_amount), $level->cycle_number, pmpro_translate_billing_period($level->cycle_period, $level->cycle_number), $level->billing_limit, pmpro_translate_billing_period($level->cycle_period, $level->billing_limit));
			}
		}
		elseif($level->billing_limit == 1)
		{
			$r .= sprintf(__(' and then <strong>%s after %d %s</strong>.', 'pmpro'), pmpro_formatPrice($level->billing_amount), $level->cycle_number, pmpro_translate_billing_period($level->cycle_period, $level->cycle_number));
		}
		else
		{
			if( $level->billing_amount === $level->initial_payment ) {
				if($level->cycle_number == '1')
				{
					if(!$short)
						$r = sprintf(__('The price for membership is <strong>%s per %s</strong>.', 'pmpro'), pmpro_formatPrice($level->initial_payment), pmpro_translate_billing_period($level->cycle_period) );
					else
						$r = sprintf(__('<strong>%s per %s</strong>.', 'pmpro'), pmpro_formatPrice($level->initial_payment), pmpro_translate_billing_period($level->cycle_period) );
				}
				else
				{
					if(!$short)
						$r = sprintf(__('The price for membership is <strong>%s every %d %s</strong>.', 'pmpro'), pmpro_formatPrice($level->initial_payment), $level->cycle_number, pmpro_translate_billing_period($level->cycle_period, $level->cycle_number) );
					else
						$r = sprintf(__('<strong>%s every %d %s</strong>.', 'pmpro'), pmpro_formatPrice($level->initial_payment), $level->cycle_number, pmpro_translate_billing_period($level->cycle_period, $level->cycle_number) );
				}
			} else {
				if($level->cycle_number == '1')
				{
					$r .= sprintf(__(' and then <strong>%s per %s</strong>.', 'pmpro'), pmpro_formatPrice($level->billing_amount), pmpro_translate_billing_period($level->cycle_period));
				}
				else
				{
					$r .= sprintf(__(' and then <strong>%s every %d %s</strong>.', 'pmpro'), pmpro_formatPrice($level->billing_amount), $level->cycle_number, pmpro_translate_billing_period($level->cycle_period, $level->cycle_number));
				}
			}
		}
	}
	else
		$r .= '.';

	//add a space
	$r .= ' ';

	//trial part
	if($level->trial_limit)
	{
		if($level->trial_amount == '0.00')
		{
			if($level->trial_limit == '1')
			{
				$r .= ' ' . __('After your initial payment, your first payment is Free.', 'pmpro');
			}
			else
			{
				$r .= ' ' . sprintf(__('After your initial payment, your first %d payments are Free.', 'pmpro'), $level->trial_limit);
			}
		}
		else
		{
			if($level->trial_limit == '1')
			{
				$r .= ' ' . sprintf(__('After your initial payment, your first payment will cost %s.', 'pmpro'), pmpro_formatPrice($level->trial_amount));
			}
			else
			{
				$r .= ' ' . sprintf(__('After your initial payment, your first %d payments will cost %s.', 'pmpro'), $level->trial_limit, pmpro_formatPrice($level->trial_amount));
			}
		}
	}

	//taxes part
	$tax_state = pmpro_getOption("tax_state");
	$tax_rate = pmpro_getOption("tax_rate");

	if($tax_state && $tax_rate && !pmpro_isLevelFree($level))
	{
		$r .= sprintf(__('Customers in %s will be charged %s%% tax.', 'pmpro'), $tax_state, round($tax_rate * 100, 2));
	}

	if(!$tags)
		$r = strip_tags($r);

	$r = apply_filters("pmpro_level_cost_text", $r, $level, $tags, $short);	//passing $tags and $short since v1.8
	return $r;
}

function pmpro_getLevelExpiration(&$level)
{
	if($level->expiration_number)
	{
		$expiration_text = sprintf(__("Membership expires after %d %s.", "pmpro"), $level->expiration_number, pmpro_translate_billing_period($level->expiration_period, $level->expiration_number));
	}
	else
		$expiration_text = "";

	$expiration_text = apply_filters("pmpro_level_expiration_text", $expiration_text, $level);
	return $expiration_text;
}

function pmpro_hideAds()
{
	global $pmpro_display_ads;
	return !$pmpro_display_ads;
}

function pmpro_displayAds()
{
	global $pmpro_display_ads;
	return $pmpro_display_ads;
}

function pmpro_next_payment($user_id = NULL, $order_status = "success", $format = "timestamp")
{
	global $wpdb, $current_user;
	if(!$user_id)
		$user_id = $current_user->ID;

	if(!$user_id)
		$r = false;
	else
	{
		//get last order
		$order = new MemberOrder();
		$order->getLastMemberOrder($user_id, $order_status);

		//get current membership level
		$level = pmpro_getMembershipLevelForUser($user_id);

		if(!empty($order) && !empty($order->id) && !empty($level) && !empty($level->id) && !empty($level->cycle_number))
		{
			//last payment date
			$lastdate = date("Y-m-d", $order->timestamp);

			//next payment date
			$nextdate = $wpdb->get_var("SELECT UNIX_TIMESTAMP('" . $lastdate . "' + INTERVAL " . $level->cycle_number . " " . $level->cycle_period . ")");

			$r = $nextdate;
		}
		else
		{
			//no order or level found, or level was not recurring
			$r = false;
		}
	}
	
	/**
	 * Filter the next payment date.
	 *
	 * @since 1.8.5
	 *
	 * @param mixed $r false or the next payment date timestamp
	 * @param int $user_id The user id to get the next payment date for
	 * @param string $order_status Status or array of statuses to find the last order based on.
	 */
	$r = apply_filters('pmpro_next_payment', $r, $user_id, $order_status);
	
	//return in desired format
	if($r === false)
		return false;				//always return false when no date found
	elseif($format == "timestamp")
		return $r;
	elseif($format == "date_format")
		return date(get_option('date_format'), $r);
	else
		return date($format, $r);	//assume a PHP date format	
}

if(!function_exists("last4"))
{
	function last4($t)
	{
		return substr($t, strlen($t) - 4, 4);
	}
}

if(!function_exists("hideCardNumber"))
{
	function hideCardNumber($c, $dashes = true)
	{
		if($c)
		{
			if($dashes)
				return "XXXX-XXXX-XXXX-" . substr($c, strlen($c) - 4, 4);
			else
				return "XXXXXXXXXXXX" . substr($c, strlen($c) - 4, 4);
		}
		else
		{
			return "";
		}
	}
}

//check for existing functions since we didn't use a prefix for this function
if(!function_exists("cleanPhone"))
{
	/**
	 * Function to remove special characters from a phone number.
	 * NOTE: Could probably replace with preg_replace("[^0-9]", "", $phone)
	 *
	 * @since 1.0
	 *	 
	 * @param string $phone The phone number to clean.
	 */
	function cleanPhone($phone)
	{
		//if a + is passed, just pass it along
		if(strpos($phone, "+") !== false)
			return $phone;
		//clean the phone
		$phone = str_replace("-", "", $phone);
		$phone = str_replace(".", "", $phone);
		$phone = str_replace("(", "", $phone);
		$phone = str_replace(")", "", $phone);
		$phone = str_replace(" ", "", $phone);
		return $phone;
	}
}

//check for existing functions since we didn't use a prefix for this function
if(!function_exists("formatPhone"))
{
	/**
	 * Function to format a phone number.
	 *
	 * @since 1.0
	 *	 
	 * @param string $phone The phone number to format.
	 */
	function formatPhone($phone)
	{
		$r = cleanPhone($phone);

		if(strlen($r) == 11)
			$r = substr($r, 0, 1) . " (" . substr($r, 1, 3) . ") " . substr($r, 4, 3) . "-" . substr($r, 7, 4);
		elseif(strlen($r) == 10)
			$r = "(" . substr($r, 0, 3) . ") " . substr($r, 3, 3) . "-" . substr($r, 6, 4);
		elseif(strlen($r) == 7)
			$r = substr($r, 0, 3) . "-" . substr($r, 3, 4);		
		
		/**
		 * Filter to do more or less cleaning of phone numbers.
		 *
		 * @since 1.8.4.4
		 *
		 * @param string $r The formatted phone number.
		 * @param string $phone The original phone number.
		 */
		return apply_filters('pmpro_format_phone', $r, $phone);
	}
}

function pmpro_showRequiresMembershipMessage()
{
	//TODO $current_user $post_membership_levels_names are undefined variables
	//get the correct message
	if(is_feed())
	{
		$content = pmpro_getOption("rsstext");
		$content = str_replace("!!levels!!", implode(", ", $post_membership_levels_names), $content);
	}
	elseif($current_user->ID)
	{
		//not a member
		$content = pmpro_getOption("nonmembertext");
		$content = str_replace("!!levels!!", implode(", ", $post_membership_levels_names), $content);
	}
	else
	{
		//not logged in!
		$content = pmpro_getOption("notloggedintext");
		$content = str_replace("!!levels!!", implode(", ", $post_membership_levels_names), $content);
	}
}

/**
 * Function to check if a user has specified membership levels.
 *
 * pmpro_hasMembershipLevel() checks if the passed user is a member of the passed level
 * $level may either be the ID or name of the desired membership_level. (or an array of such)
 * If $user_id is omitted, the value will be retrieved from $current_user.
 *
 *  Return values:
 *	 * Success returns boolean true.
 *	 * Failure returns a string containing the error message.
 *
 * @since 1.8.5 Added 'e' option for expired members.
 * @since 1.0.0
 *
 * @param mixed $levels The levels to check.
 * @param int $user_id The user ID to check.
 *
 * @return bool Result of membership query.
 */
function pmpro_hasMembershipLevel($levels = NULL, $user_id = NULL)
{
	global $current_user, $wpdb;

	$return = false;

	if(empty($user_id)) //no user_id passed, check the current user
	{
		$user_id = $current_user->ID;
		$membership_levels = $current_user->membership_levels;
	}
	elseif(is_numeric($user_id)) //get membership levels for given user
	{
		$membership_levels = pmpro_getMembershipLevelsForUser($user_id);
	}
	else
		return false;	//invalid user_id

	if($levels === "0" || $levels === 0) //if 0 was passed, return true if they have no level and false if they have any
	{
		$return = empty($membership_levels);
	}
	elseif(empty($levels)) //if no level var was passed, we're just checking if they have any level
	{
		$return = !empty($membership_levels);
	}
	else
	{
		if(!is_array($levels)) //make an array out of a single element so we can use the same code
		{
			$levels = array($levels);
		}
				
		if(empty($membership_levels))
		{			
			//user has no levels just check if 0, L, -1, or e was sent in one of the levels
			if(in_array(0, $levels, true) || in_array("0", $levels))
				$return = true;
			elseif(in_array("L", $levels) || in_array("l", $levels))
				$return = (!empty($user_id) && $user_id == $current_user->ID);
			elseif(in_array("-L", $levels) || in_array("-l", $levels))
				$return = (empty($user_id) || $user_id != $current_user->ID);
			elseif(in_array("E", $levels) || in_array("e", $levels)) {
				$sql = "SELECT id FROM $wpdb->pmpro_memberships_users WHERE user_id=$user_id AND status='expired' LIMIT 1";
				$expired = $wpdb->get_var($sql);
				return !empty($expired);
			}
		}
		else
		{			
			foreach($levels as $level)
			{				
				if(strtoupper($level) == "L")
				{					
					//checking if user is logged in
					if(!empty($user_id) && $user_id == $current_user->ID)
						$return = true;
				}
				elseif(strtoupper($level) == "-L")
				{					
					//checking if user is logged out
					if(empty($user_id) || $user_id != $current_user->ID)
						$return = true;
				}
				elseif($level == "0" || strtoupper($level) == "E")
				{
					continue;	//user with levels so not a "non-member" or expired
				}
				else
				{
					//checking a level id
					$level_obj = pmpro_getLevel(is_numeric($level) ? abs(intval($level)) : $level); //make sure our level is in a proper format
					if(empty($level_obj)){continue;} //invalid level
					$found_level = false;
					foreach($membership_levels as $membership_level)
					{
						if($membership_level->id == $level_obj->id) //found a match
						{
							$found_level = true;
						}
					}
										
					if(is_numeric($level) && intval($level) < 0 && !$found_level) //checking for the absence of this level and they don't have it
					{
						$return = true;
					}					
					elseif(is_numeric($level) && intval($level) > 0 && $found_level) //checking for the presence of this level and they have it
					{					
						$return = true;
					}
					elseif(!is_numeric($level))	//if a level name was passed
						$return = $found_level;
				}
			}
		}
	}

	$return = apply_filters("pmpro_has_membership_level", $return, $user_id, $levels);
	return $return;
}

/* pmpro_changeMembershipLevel() creates or updates the membership level of the given user to the given level.
 *
 * $level may either be the ID or name of the desired membership_level.
 * If $user_id is omitted, the value will be retrieved from $current_user.
 *
 * Return values:
 *		Success returns boolean true.
 *		Failure returns boolean false.
 */
function pmpro_changeMembershipLevel($level, $user_id = NULL, $old_level_status = 'inactive')
{
	global $wpdb;
	global $current_user, $pmpro_error;

	if(empty($user_id))
	{
		$user_id = $current_user->ID;
	}

	if(empty($user_id))
	{
		$pmpro_error = __("User ID not found.", "pmpro");
		return false;
	}

	//make sure user id is int for security
	$user_id = intval($user_id);
	
	if(empty($level)) //cancelling membership
	{
		$level = 0;
	}
	else if(is_array($level))
	{
		//custom level
	}
	else
	{
		$level_obj = pmpro_getLevel($level);
		if(empty($level_obj))
		{
			$pmpro_error = __("Invalid level.", "pmpro");
            return false;
		}
		$level = $level_obj->id;
	}

	//if it's a custom level, they're changing
	if(!is_array($level))
	{
        //are they even changing?
		if(pmpro_hasMembershipLevel($level, $user_id)) {
            $pmpro_error = __("not changing?", "pmpro");
            return false; //not changing
		}
	}

    //get all active membershipships for this user
	$old_levels = pmpro_getMembershipLevelsForUser($user_id);

    //deactivate old memberships based on the old_level_status passed in (updates pmpro_memberships_users table)
    if($old_levels)
    {
        foreach($old_levels as $old_level) {

            $sql = "UPDATE $wpdb->pmpro_memberships_users SET `status`='$old_level_status', `enddate`='" . current_time('mysql') . "' WHERE `id`=".$old_level->subscription_id;

            if(!$wpdb->query($sql))
            {
                $pmpro_error = __("Error interacting with database", "pmpro") . ": ".(mysql_errno()?mysql_error():'unavailable');

                return false;
            }
        }
    }

    //get level id
	if(is_array($level))
		$level_id = $level['membership_id'];	//custom level
	else
		$level_id = $level;	//just id

	/**
	 * Action to run before the membership level changes.
	 *
	 * @param int $level_id ID of the level changed to.
	 * @param int $user_id ID of the user changed.
	 */
	do_action("pmpro_before_change_membership_level", $level_id, $user_id);

    //should we cancel their gateway subscriptions?
    $pmpro_cancel_previous_subscriptions = true;
    if(isset($_REQUEST['cancel_membership']) && $_REQUEST['cancel_membership'] == false)
        $pmpro_cancel_previous_subscriptions = false;
    $pmpro_cancel_previous_subscriptions = apply_filters("pmpro_cancel_previous_subscriptions", $pmpro_cancel_previous_subscriptions);

    //cancel any other subscriptions they have (updates pmpro_membership_orders table)
	if($pmpro_cancel_previous_subscriptions)
	{
        $other_order_ids = $wpdb->get_col("SELECT id FROM $wpdb->pmpro_membership_orders WHERE user_id = '" . $user_id . "' AND status = 'success' ORDER BY id DESC");

        foreach($other_order_ids as $order_id)
        {
            $c_order = new MemberOrder($order_id);
            $c_order->cancel();

            if(!empty($c_order->error))
                $pmpro_error = $c_order->error;
        }
	}

	//insert current membership
	if(!empty($level)) //are we getting a new one or just cancelling the old ones
	{
		if(is_array($level))
		{
			//make sure the dates are in good formats
			if($level['startdate'] != current_time('mysql') && $level['startdate'] != "NULL" && substr($level['startdate'], 0, 1) != "'")
				$level['startdate'] = "'" . $level['startdate'] . "'";

			if($level['enddate'] != current_time('mysql') && $level['enddate'] != "NULL" && substr($level['enddate'], 0, 1) != "'")
				$level['enddate'] = "'" . $level['enddate'] . "'";

		 //Better support mySQL Strict Mode by passing  a proper enum value for cycle_period
		 if ($level['cycle_period'] == '') $level['cycle_period'] = 0;

		 $sql = "INSERT INTO $wpdb->pmpro_memberships_users (user_id, membership_id, code_id, initial_payment, billing_amount, cycle_number, cycle_period, billing_limit, trial_amount, trial_limit, startdate, enddate)
					VALUES('" . $level['user_id'] . "',
					'" . $level['membership_id'] . "',
					'" . intval($level['code_id']) . "',
					'" . $level['initial_payment'] . "',
					'" . $level['billing_amount'] . "',
					'" . $level['cycle_number'] . "',
					'" . $level['cycle_period'] . "',
					'" . $level['billing_limit'] . "',
					'" . $level['trial_amount'] . "',
					'" . $level['trial_limit'] . "',
					" . $level['startdate'] . ",
					" . $level['enddate'] . ")";

			if(!$wpdb->query($sql))
			{
				$pmpro_error = __("Error interacting with database", "pmpro") . ": ".(mysql_errno()?mysql_error():'unavailable');
				return false;
			}
		}
		else
		{
			$sql = "INSERT INTO $wpdb->pmpro_memberships_users (user_id, membership_id, code_id, initial_payment, billing_amount, cycle_number, cycle_period, billing_limit, trial_amount, trial_limit, startdate, enddate)
			    VALUES (
			    '" . $user_id . "',
			    '" . $level . "',
			    '0',
			    '0',
			    '0',
			    '0',
			    '0',
			    '0',
			    '0',
			    '0',
			    '" . current_time('mysql') . "',
                	    '0000-00-00 00:00:00'
                	    )";

			if(!$wpdb->query($sql))
			{
				$pmpro_error = __("Error interacting with database", "pmpro") . ": ".(mysql_errno()?mysql_error():'unavailable');
				return false;
			}
		}
	}

	//remove cached level
	global $all_membership_levels;
	unset($all_membership_levels[$user_id]);

	//update user data and call action
	pmpro_set_current_user();

	/**
	 * Action to run after the membership level changes.
	 *
	 * @param int $level_id ID of the level changed to.
	 * @param int $user_id ID of the user changed.
	 */
	do_action("pmpro_after_change_membership_level", $level_id, $user_id);
	return true;
}

/* pmpro_toggleMembershipCategory() creates or deletes a linking entry between the membership level and post category tables.
 *
 * $level may either be the ID or name of the desired membership_level.
 * $category must be a valid post category ID.
 *
 * Return values:
 *		Success returns boolean true.
 *		Failure returns a string containing the error message.
 */
function pmpro_toggleMembershipCategory( $level, $category, $value )
{
	global $wpdb;
	$category = intval($category);

		if ( ($level = intval($level)) <= 0 )
		{
			$safe = addslashes($level);
			if ( ($level = intval($wpdb->get_var("SELECT id FROM {$wpdb->pmpro_membership_levels} WHERE name = '$safe' LIMIT 1"))) <= 0 )
			{
				return __("Membership level not found.", "pmpro");
			}
		}

	if ( $value )
	{
	  $sql = "REPLACE INTO {$wpdb->pmpro_memberships_categories} (`membership_id`,`category_id`) VALUES ('$level','$category')";
	  $wpdb->query($sql);
	  if(mysql_errno()) return mysql_error();
	}
	else
	{
	  $sql = "DELETE FROM {$wpdb->pmpro_memberships_categories} WHERE `membership_id` = '$level' AND `category_id` = '$category' LIMIT 1";
	  $wpdb->query($sql);
	  if(mysql_errno()) return mysql_error();
	}

	return true;
}

/* pmpro_updateMembershipCategories() ensures that all those and only those categories given
* are associated with the given membership level.
*
* $level is a valid membership level ID or name
* $categories is an array of post category IDs
*
* Return values:
*		Success returns boolean true.
*		Failure returns a string containing the error message.
*/
function pmpro_updateMembershipCategories($level, $categories)
{
	global $wpdb;

	if(!is_numeric($level))
	{
		$level = $wpdb->get_var("SELECT id FROM $wpdb->pmpro_membership_levels WHERE name = '" . esc_sql($level) . "' LIMIT 1");
		if(empty($level))
		{
			return __("Membership level not found.", "pmpro");
		}
	}

	// remove all existing links...
	$sqlQuery = "DELETE FROM $wpdb->pmpro_memberships_categories WHERE `membership_id` = '" . esc_sql($level) . "'";
	$wpdb->query($sqlQuery);
	if(mysql_errno()) return mysql_error();

	// add the given links [back?] in...
	foreach($categories as $cat)
	{
		if(is_string($r = pmpro_toggleMembershipCategory( $level, $cat, true)))
		{
			//uh oh, error
			return $r;
		}
	}

	//all good
	return true;
}

/* pmpro_getMembershipCategories() returns the categories for a given level
*
* $level_id is a valid membership level ID
*
* Return values:
*		Success returns boolean true.
*		Failure returns boolean false.
*/
function pmpro_getMembershipCategories($level_id)
{
	$level_id = intval($level_id);
	
	global $wpdb;
	$categories = $wpdb->get_col("SELECT c.category_id
										FROM {$wpdb->pmpro_memberships_categories} AS c
										WHERE c.membership_id = '" . $level_id . "'");

	return $categories;
}


function pmpro_isAdmin($user_id = NULL)
{
	global $current_user, $wpdb;
	if(!$user_id)
		$user_id = $current_user->ID;

	if(!$user_id)
		return false;

	$admincap = user_can($user_id, "manage_options");
	if($admincap)
		return true;
	else
		return false;
}

function pmpro_replaceUserMeta($user_id, $meta_keys, $meta_values, $prev_values = NULL)
{
	//expects all arrays for last 3 params or all strings
	if(!is_array($meta_keys))
	{
		$meta_keys = array($meta_keys);
		$meta_values = array($meta_values);
		$prev_values = array($prev_values);
	}

	for($i = 0; $i < count($meta_values); $i++)
	{
		if($prev_values[$i])
		{
			update_user_meta($user_id, $meta_keys[$i], $meta_values[$i], $prev_values[$i]);
		}
		else
		{
			$old_value = get_user_meta($user_id, $meta_keys[$i], true);
			if($old_value)
			{
				update_user_meta($user_id, $meta_keys[$i], $meta_values[$i], $old_value);
			}
			else
			{
				update_user_meta($user_id, $meta_keys[$i], $meta_values[$i]);
			}
		}
	}

	return $i;
}

function pmpro_getMetavalues($query)
{
	global $wpdb;

	$results = $wpdb->get_results($query);
	$r = new stdClass();
	foreach($results as $result)
	{
        if(!empty($r))
		    $r->{$result->key} = $result->value;
	}

	return $r;
}

//function to return the pagination string
function pmpro_getPaginationString($page = 1, $totalitems, $limit = 15, $adjacents = 1, $targetpage = "/", $pagestring = "&pn=")
{
	//defaults
	if(!$adjacents) $adjacents = 1;
	if(!$limit) $limit = 15;
	if(!$page) $page = 1;
	if(!$targetpage) $targetpage = "/";

	//other vars
	$prev = $page - 1;									//previous page is page - 1
	$next = $page + 1;									//next page is page + 1
	$lastpage = ceil($totalitems / $limit);				//lastpage is = total items / items per page, rounded up.
	$lpm1 = $lastpage - 1;								//last page minus 1

	/*
		Now we apply our rules and draw the pagination object.
		We're actually saving the code to a variable in case we want to draw it more than once.
	*/
	$pagination = "";
	if($lastpage > 1)
	{
		$pagination .= "<div class=\"pmpro_pagination\"";
		if(!empty($margin) || !empty($padding))
		{
			$pagination .= " style=\"";
			if($margin)
				$pagination .= "margin: $margin;";
			if($padding)
				$pagination .= "padding: $padding;";
			$pagination .= "\"";
		}
		$pagination .= ">";

		//previous button
		if ($page > 1)
			$pagination .= "<a href=\"$targetpage$pagestring$prev\">&laquo; prev</a>";
		else
			$pagination .= "<span class=\"disabled\">&laquo; prev</span>";

		//pages
		if ($lastpage < 7 + ($adjacents * 2))	//not enough pages to bother breaking it up
		{
			for ($counter = 1; $counter <= $lastpage; $counter++)
			{
				if ($counter == $page)
					$pagination .= "<span class=\"current\">$counter</span>";
				else
					$pagination .= "<a href=\"" . $targetpage . $pagestring . $counter . "\">$counter</a>";
			}
		}
		elseif($lastpage >= 7 + ($adjacents * 2))	//enough pages to hide some
		{
			//close to beginning; only hide later pages
			if($page < 1 + ($adjacents * 3))
			{
				for ($counter = 1; $counter < 4 + ($adjacents * 2); $counter++)
				{
					if ($counter == $page)
						$pagination .= "<span class=\"current\">$counter</span>";
					else
						$pagination .= "<a href=\"" . $targetpage . $pagestring . $counter . "\">$counter</a>";
				}
				$pagination .= "...";
				$pagination .= "<a href=\"" . $targetpage . $pagestring . $lpm1 . "\">$lpm1</a>";
				$pagination .= "<a href=\"" . $targetpage . $pagestring . $lastpage . "\">$lastpage</a>";
			}
			//in middle; hide some front and some back
			elseif($lastpage - ($adjacents * 2) > $page && $page > ($adjacents * 2))
			{
				$pagination .= "<a href=\"" . $targetpage . $pagestring . "1\">1</a>";
				$pagination .= "<a href=\"" . $targetpage . $pagestring . "2\">2</a>";
				$pagination .= "...";
				for ($counter = $page - $adjacents; $counter <= $page + $adjacents; $counter++)
				{
					if ($counter == $page)
						$pagination .= "<span class=\"current\">$counter</span>";
					else
						$pagination .= "<a href=\"" . $targetpage . $pagestring . $counter . "\">$counter</a>";
				}
				$pagination .= "...";
				$pagination .= "<a href=\"" . $targetpage . $pagestring . $lpm1 . "\">$lpm1</a>";
				$pagination .= "<a href=\"" . $targetpage . $pagestring . $lastpage . "\">$lastpage</a>";
			}
			//close to end; only hide early pages
			else
			{
				$pagination .= "<a href=\"" . $targetpage . $pagestring . "1\">1</a>";
				$pagination .= "<a href=\"" . $targetpage . $pagestring . "2\">2</a>";
				$pagination .= "...";
				for ($counter = $lastpage - (1 + ($adjacents * 3)); $counter <= $lastpage; $counter++)
				{
					if ($counter == $page)
						$pagination .= "<span class=\"current\">$counter</span>";
					else
						$pagination .= "<a href=\"" . $targetpage . $pagestring . $counter . "\">$counter</a>";
				}
			}
		}

		//next button
		if ($page < $counter - 1)
			$pagination .= "<a href=\"" . $targetpage . $pagestring . $next . "\">next &raquo;</a>";
		else
			$pagination .= "<span class=\"disabled\">next &raquo;</span>";
		$pagination .= "</div>\n";
	}

	return $pagination;

}

function pmpro_calculateInitialPaymentRevenue($s = NULL, $l = NULL)
{
	global $wpdb;

	//if we're limiting users by search
	if($s || $l)
	{
		$user_ids_query = "SELECT u.ID FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um  ON u.ID = um.user_id LEFT JOIN $wpdb->pmpro_memberships_users mu ON u.ID = mu.user_id WHERE mu.status = 'active' ";
		if($s)
			$user_ids_query .= "AND (u.user_login LIKE '%" . esc_sql($s) . "%' OR u.user_email LIKE '%" . esc_sql($s) . "%' OR um.meta_value LIKE '%$" . esc_sql(s) . "%') ";
		if($l)
			$user_ids_query .= "AND mu.membership_id = '" . esc_sql($l) . "' ";
	}

	//query to sum initial payments
	$sqlQuery = "SELECT SUM(initial_payment) FROM $wpdb->pmpro_memberships_users WHERE `status` = 'active' ";
	if(!empty($user_ids_query))
		$sqlQuery .= "AND user_id IN(" . $user_ids_query . ") ";

	$total = $wpdb->get_var($sqlQuery);

	return (double)$total;
}

function pmpro_calculateRecurringRevenue($s, $l)
{
	global $wpdb;

	//if we're limiting users by search
	if($s || $l)
	{
		$user_ids_query = "AND user_id IN(SELECT u.ID FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um  ON u.ID = um.user_id LEFT JOIN $wpdb->pmpro_memberships_users mu ON u.ID = mu.user_id WHERE mu.status = 'active' ";
		if($s)
			$user_ids_query .= "AND (u.user_login LIKE '%" . esc_sql($s) . "%' OR u.user_email LIKE '%" . esc_sql($s) . "%' OR um.meta_value LIKE '%" . esc_sql($s) . "%') ";
		if($l)
			$user_ids_query .= "AND mu.membership_id = '" . esc_sql($l) . "' ";
		$user_ids_query .= ")";
	}
	else
		$user_ids_query = "";

	//4 queries to get annual earnings for each cycle period. currently ignoring trial periods and billing limits.
	$sqlQuery = "
		SELECT SUM((12/cycle_number)*billing_amount) FROM $wpdb->pmpro_memberships_users WHERE status = 'active' AND cycle_period = 'Month' AND cycle_number <> 12 $user_ids_query
			UNION
		SELECT SUM((365/cycle_number)*billing_amount) FROM $wpdb->pmpro_memberships_users WHERE status = 'active' AND cycle_period = 'Day' AND cycle_number <> 365 $user_ids_query
			UNION
		SELECT SUM((52/cycle_number)*billing_amount) FROM $wpdb->pmpro_memberships_users WHERE status = 'active' AND cycle_period = 'Week' AND cycle_number <> 52 $user_ids_query
			UNION
		SELECT SUM(billing_amount) FROM $wpdb->pmpro_memberships_users WHERE status = 'active' AND cycle_period = 'Year' $user_ids_query
	";

	$annual_revenues = $wpdb->get_col($sqlQuery);

	$total = 0;
	foreach($annual_revenues as $r)
	{
		$total += $r;
	}

	return $total;
}

function pmpro_generateUsername($firstname = "", $lastname = "", $email = "")
{
	global $wpdb;

	//try first initial + last name, firstname, lastname
	$firstname = preg_replace("/[^A-Za-z]/", "", $firstname);
	$lastname = preg_replace("/[^A-Za-z]/", "", $lastname);
	if($firstname && $lastname)
	{
		$username = substr($firstname, 0, 1) . $lastname;
	}
	elseif($firstname)
	{
		$username = $firstname;
	}
	elseif($lastname)
	{
		$username = $lastname;
	}

	//is it taken?
	$taken = $wpdb->get_var("SELECT user_login FROM $wpdb->users WHERE user_login = '" . esc_sql($username) . "' LIMIT 1");

	if(!$taken)
		return $username;

	//try the beginning of the email address
	$emailparts = explode("@", $email);
	if(is_array($emailparts))
		$email = preg_replace("/[^A-Za-z]/", "", $emailparts[0]);

	if(!empty($email))
	{
		$username = $email;
	}

	//is this taken? if not, add numbers until it works
	$taken = true;
	$count = 0;
	while($taken)
	{
		//add a # to the end
		if($count)
		{
			$username = preg_replace("/[0-9]/", "", $username) . $count;
		}

		//taken?
		$taken = $wpdb->get_var("SELECT user_login FROM $wpdb->users WHERE user_login = '" . esc_sql($username) . "' LIMIT 1");

		//increment the number
		$count++;
	}

	//must have a good username now
	return $username;
}

//get a new random code for discount codes
function pmpro_getDiscountCode($seed = NULL)
{
	global $wpdb;

	while(empty($code))
	{
		$scramble = md5(AUTH_KEY . current_time('timestamp') . $seed . SECURE_AUTH_KEY);
		$code = substr($scramble, 0, 10);
		$check = $wpdb->get_var("SELECT code FROM $wpdb->pmpro_discount_codes WHERE code = '" . esc_sql($code) . "' LIMIT 1");
		if($check || is_numeric($code))
			$code = NULL;
	}

	return strtoupper($code);
}

//is a discount code valid
function pmpro_checkDiscountCode($code, $level_id = NULL, $return_errors = false)
{
	global $wpdb;

	$error = false;

	//make sure level id is int for security
	$level_id = intval($level_id);
	
	//no code, no code
	if(empty($code))
		$error = __("No code was given to check.", "pmpro");

	//get code from db
	if(!$error)
	{
		$dbcode = $wpdb->get_row("SELECT *, UNIX_TIMESTAMP(starts) as starts, UNIX_TIMESTAMP(expires) as expires FROM $wpdb->pmpro_discount_codes WHERE code ='" . esc_sql($code) . "' LIMIT 1");

		//did we find it?
		if(empty($dbcode->id))
			$error = __("The discount code could not be found.", "pmpro");
	}

	//check if the code has started
	if(!$error)
	{
		//fix the date timestamps
		$dbcode->starts = strtotime(date("m/d/Y", $dbcode->starts));
		$dbcode->expires = strtotime(date("m/d/Y", $dbcode->expires));

		//today
		$today = strtotime(date("m/d/Y 00:00:00", current_time("timestamp")));

		//has this code started yet?
		if(!empty($dbcode->starts) && $dbcode->starts > $today)
			$error = sprintf(__("This discount code goes into effect on %s.", "pmpro"), date(get_option('date_format'), $dbcode->starts));
	}

	//check if the code is expired
	if(!$error)
	{
		if(!empty($dbcode->expires) && $dbcode->expires < $today)
			$error = sprintf(__("This discount code expired on %s.", "pmpro"), date(get_option('date_format'), $dbcode->expires));
	}

	//have we run out of uses?
	if(!$error)
	{
		if($dbcode->uses > 0)
		{
			$used = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->pmpro_discount_codes_uses WHERE code_id = '" . $dbcode->id . "'");
			if($used >= $dbcode->uses)
				$error = __("This discount code is no longer valid.", "pmpro");
		}
	}

	//if a level was passed check if this code applies
	if(!$error)
	{
		$pmpro_check_discount_code_levels = apply_filters("pmpro_check_discount_code_levels", true, $dbcode->id);
		if(!empty($level_id) && $pmpro_check_discount_code_levels)
		{
			$code_level = $wpdb->get_row("SELECT l.id, cl.*, l.name, l.description, l.allow_signups FROM $wpdb->pmpro_discount_codes_levels cl LEFT JOIN $wpdb->pmpro_membership_levels l ON cl.level_id = l.id WHERE cl.code_id = '" . $dbcode->id . "' AND cl.level_id = '" . $level_id . "' LIMIT 1");

			if(empty($code_level))
				$error = __("This discount code does not apply to this membership level.", "pmpro");
		}
	}

	//allow filter
	$pmpro_check_discount_code = apply_filters("pmpro_check_discount_code", !$error, $dbcode, $level_id, $code);
	if(is_string($pmpro_check_discount_code))
		$error = $pmpro_check_discount_code;	//string returned, this is an error
	elseif(!$pmpro_check_discount_code && !$error)
		$error = true;							//no error before, but filter returned error
	elseif($pmpro_check_discount_code)
		$error = false;							//filter is true, so error false

	//return
	if($error)
	{
		//there was an error
		if(!empty($return_errors))
			return array(false, $error);
		else
			return false;
	}
	else
	{
		//guess we're all good
		if(!empty($return_errors))
			return array(true, __("This discount code is okay.", "pmpro"));
		else
			return true;
	}
}

function pmpro_no_quotes($s, $quotes = array("'", '"'))
{
	return str_replace($quotes, "", $s);
}

//from: http://www.php.net/manual/en/function.implode.php#86845
function pmpro_implodeToEnglish($array)
{
	// sanity check
	if (!$array || !count ($array))
		return '';

	// get last element
	$last = array_pop ($array);

	// if it was the only element - return it
	if (!count ($array))
		return $last;

	return implode (', ', $array).' ' . __('and', 'pmpro') . ' '.$last;
}

//from yoast wordpress seo
function pmpro_text_limit( $text, $limit, $finish = '&hellip;')
{
	if( strlen( $text ) > $limit ) {
		$text = substr( $text, 0, $limit );
		$text = substr( $text, 0, - ( strlen( strrchr( $text,' ') ) ) );
		$text .= $finish;
	}
	return $text;
}

/* pmpro_getMembershipLevelForUser() returns the first active membership level for a user
 *
 * If $user_id is omitted, the value will be retrieved from $current_user.
 *
 * Return values:
 *		Success returns the level object.
 *		Failure returns false.
 */
function pmpro_getMembershipLevelForUser($user_id = NULL, $force = false)
{
	if(empty($user_id))
	{
		global $current_user;
		$user_id = $current_user->ID;
	}

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

	//make sure user id is int for security
	$user_id = intval($user_id);
	
	global $all_membership_levels;

	if(isset($all_membership_levels[$user_id]) && !$force)
	{
		return $all_membership_levels[$user_id];
	}
	else
	{
		global $wpdb;
		$all_membership_levels[$user_id] = $wpdb->get_row("SELECT
															l.id AS ID,
															l.id as id,
															mu.id as subscription_id,
															l.name AS name,
															l.description,
															l.expiration_number,
															l.expiration_period,
															mu.initial_payment,
															mu.billing_amount,
															mu.cycle_number,
															mu.cycle_period,
															mu.billing_limit,
															mu.trial_amount,
															mu.trial_limit,
															mu.code_id as code_id,
															UNIX_TIMESTAMP(startdate) as startdate,
															UNIX_TIMESTAMP(enddate) as enddate
														FROM {$wpdb->pmpro_membership_levels} AS l
														JOIN {$wpdb->pmpro_memberships_users} AS mu ON (l.id = mu.membership_id)
														WHERE mu.user_id = $user_id AND mu.status = 'active'
														LIMIT 1");

		/**
		 * pmpro_get_membership_level_for_user filter.
		 *
		 * Filters the returned level.
		 *
		 * @since 1.8.5.4
		 *
		 * @param object $level Level object.
		 */
		$all_membership_levels[$user_id] = apply_filters('pmpro_get_membership_level_for_user', $all_membership_levels[$user_id]);

		return $all_membership_levels[$user_id];
	}
}

/* pmpro_getMembershipLevelsForUser() returns the membership levels for a user
 *
 * If $user_id is omitted, the value will be retrieved from $current_user.
 * By default it only includes actvie memberships.
 *
 * Return values:
 *		Success returns an array of level objects.
 *		Failure returns false.
 */
function pmpro_getMembershipLevelsForUser($user_id = NULL, $include_inactive = false)
{
	if(empty($user_id))
	{
		global $current_user;
		$user_id = $current_user->ID;
	}

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

	//make sure user id is int for security
	$user_id = intval($user_id);
	
	global $wpdb;

	$levels = $wpdb->get_results("SELECT
								l.id AS ID,
								l.id as id,
								mu.id as subscription_id,
								l.name,
								l.description,
								l.expiration_number,
								l.expiration_period,
								mu.initial_payment,
								mu.billing_amount,
								mu.cycle_number,
								mu.cycle_period,
								mu.billing_limit,
								mu.trial_amount,
								mu.trial_limit,
								mu.code_id as code_id,
								UNIX_TIMESTAMP(startdate) as startdate,
								UNIX_TIMESTAMP(enddate) as enddate
							FROM {$wpdb->pmpro_membership_levels} AS l
							JOIN {$wpdb->pmpro_memberships_users} AS mu ON (l.id = mu.membership_id)
							WHERE mu.user_id = $user_id".($include_inactive?"":" AND mu.status = 'active'"));
	/**
	 * pmpro_get_membership_levels_for_user filter.
	 *
	 * Filters the returned levels.
	 *
	 * @since 1.8.5.4
	 *
	 * @param array $levels Array of level objects.
	 */
	$levels = apply_filters('pmpro_get_membership_levels_for_user', $levels);

	return $levels;
}

/* pmpro_getLevel() returns the level object for a level
 *
 * $level may be the level id or name
 *
 * Return values:
 *		Success returns the level object.
 *		Failure returns false.
 */
function pmpro_getLevel($level)
{
	global $pmpro_levels;

	if(is_object($level) && !empty($level->id))
		$level = $level->id;

	//was a name passed? (Todo: make sure level names have at least one non-numeric character.
	if(is_numeric($level))
	{
		$level_id = intval($level);
		if(isset($pmpro_levels[$level_id]))
		{
			return $pmpro_levels[$level_id];
		}
		else
		{
			global $wpdb;
			$pmpro_levels[$level_id] = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_membership_levels WHERE id = '" . $level_id . "' LIMIT 1");
			return $pmpro_levels[$level_id];
		}
	}
	else
	{		
		global $wpdb;
		$level_obj = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_membership_levels WHERE name = '" . esc_sql($level) . "' LIMIT 1");	
		
		if(!empty($level_obj))
			$level_id = $level_obj->id;
		else
			return false;
		
		$pmpro_levels[$level_id] = $level_obj;
		return $pmpro_levels[$level_id];
	}
}

/*
	Function to populate pmpro_levels with all levels. We query the DB every time just to be sure we have the latest.
	This should be called if you want to be sure you get all levels as $pmpro_levels may only have a subset of levels.
*/
function pmpro_getAllLevels($include_hidden = false, $force = false)
{
	global $pmpro_levels, $wpdb;

	//just use what's cached (doesn't take into account include_hidden setting)
	if(!empty($pmpro_levels) && !$force)
		return $pmpro_levels;

	//build query
	$sqlQuery = "SELECT * FROM $wpdb->pmpro_membership_levels ";
	if(!$include_hidden)
		$sqlQuery .= " WHERE allow_signups = 1 ORDER BY id";

	//get levels from the DB
	$raw_levels = $wpdb->get_results($sqlQuery);

	//lets put them into an array where the key is the id of the level
	$pmpro_levels = array();
	foreach($raw_levels as $raw_level)
	{
		$pmpro_levels[$raw_level->id] = $raw_level;
	}

	return $pmpro_levels;
}

function pmpro_getCheckoutButton($level_id, $button_text = NULL, $classes = NULL)
{
	if(empty($button_text))
		$button_text = __("Sign Up for !!name!! Now", "pmpro");

	if(empty($classes))
		$classes = "pmpro_btn";

	if(empty($level_id))
		$r = __("Please specify a level id.", "pmpro");
	else
	{
		//get level
		$level = pmpro_getLevel($level_id);

		//replace vars
		$replacements = array(
			"!!id!!" => $level->id,
			"!!name!!" => $level->name,
			"!!description!!" => $level->description,
			"!!confirmation!!" => $level->confirmation,
			"!!initial_payment!!" => $level->initial_payment,
			"!!billing_amount!!" => $level->billing_amount,
			"!!cycle_number!!" => $level->cycle_number,
			"!!cycle_period!!" => $level->cycle_period,
			"!!billing_limit!!" => $level->billing_limit,
			"!!trial_amount!!" => $level->trial_amount,
			"!!trial_limit!!" => $level->trial_limit,
			"!!expiration_number!!" => $level->expiration_number,
			"!!expiration_period!!" => $level->expiration_period
		);
		$button_text = str_replace(array_keys($replacements), $replacements, $button_text);

		//button text
		$r = "<a href=\"" . pmpro_url("checkout", "?level=" . $level_id) . "\" class=\"" . $classes . "\">" . $button_text . "</a>";
	}
	return $r;
}

/**
 * Get the "domain" from a URL. By domain, we mean the host name, minus any subdomains. So just the domain and TLD.
 *
 * @param string $url The URL to parse. (generally pass site_url() in WP)
 * @return string The domain.
 */
function pmpro_getDomainFromURL($url = NULL)
{
	$domainparts = parse_url($url);
	$domainparts = explode(".", $domainparts['host']);
	if(count($domainparts) > 1)
	{
		//check for ips
		$isip = true;
		foreach($domainparts as $part)
		{
			if(!is_numeric($part))
			{
				$isip = false;
				break;
			}
		}

		if($isip)
		{
			//ip, e.g. 127.1.1.1
			$domain = implode(".", $domainparts);
		}
		else
		{
			//www.something.com, etc.
			$domain = $domainparts[count($domainparts)-2] . "." . $domainparts[count($domainparts)-1];
		}
	}
	else
	{
		//localhost or another single word domain
		$domain = $domainparts[0];
	}

	return $domain;
}

/*
	Get a member's start date... either in general or for a specific level_id.
*/
if(!function_exists("pmpro_getMemberStartdate"))
{
	function pmpro_getMemberStartdate($user_id = NULL, $level_id = 0)
	{
		if(empty($user_id))
		{
			global $current_user;
			$user_id = $current_user->ID;
		}

		//make sure user and level id are int for security
		$user_id = intval($user_id);
		$level_id = intval($level_id);
		
		global $pmpro_startdates;	//for cache
		if(empty($pmpro_startdates[$user_id][$level_id]))
		{
			global $wpdb;

			if(!empty($level_id))
				$sqlQuery = "SELECT UNIX_TIMESTAMP(startdate) FROM $wpdb->pmpro_memberships_users WHERE status = 'active' AND membership_id IN(" . esc_sql($level_id) . ") AND user_id = '" . $user_id . "' ORDER BY id LIMIT 1";
			else
				$sqlQuery = "SELECT UNIX_TIMESTAMP(startdate) FROM $wpdb->pmpro_memberships_users WHERE status = 'active' AND user_id = '" . $user_id . "' ORDER BY id LIMIT 1";

			$startdate = apply_filters("pmpro_member_startdate", $wpdb->get_var($sqlQuery), $user_id, $level_id);

			$pmpro_startdates[$user_id][$level_id] = $startdate;
		}

		return $pmpro_startdates[$user_id][$level_id];
	}
}

/*
	How long has this member been a member
*/
if(!function_exists("pmpro_getMemberDays"))
{
	function pmpro_getMemberDays($user_id = NULL, $level_id = 0)
	{
		if(empty($user_id))
		{
			global $current_user;
			$user_id = $current_user->ID;
		}

		global $pmpro_member_days;
		if(empty($pmpro_member_days[$user_id][$level_id]))
		{
			$startdate = pmpro_getMemberStartdate($user_id, $level_id);

			//check that there was a startdate at all
			if(empty($startdate))
				$pmpro_member_days[$user_id][$level_id] = 0;
			else
			{
				$now = current_time('timestamp');
				$days = ($now - $startdate)/3600/24;

				$pmpro_member_days[$user_id][$level_id] = $days;
			}
		}

		return $pmpro_member_days[$user_id][$level_id];
	}
}

//the start of a message handling script
function pmpro_setMessage($message, $type, $force = false)
{
	global $pmpro_msg, $pmpro_msgt;

	//for now, we only show the first message generated
	if($force || empty($pmpro_msg))
	{
		$pmpro_msg = $message;
		$pmpro_msgt = $type;
	}
}

/**
 * Show a a PMPro message set via pmpro_setMessage
 *
 * @since 1.8.5
 */
function pmpro_showMessage()
{
	global $pmpro_msg, $pmpro_msgt;
	
	if(!empty($pmpro_msg))
	{
	?>
	<div class="<?php echo $pmpro_msgt;?>">
		<p><?php echo $pmpro_msg;?></p>
	</div>
	<?php
	}
}

//used in class definitions for input fields to see if there was an error
function pmpro_getClassForField($field)
{
	global $pmpro_error_fields, $pmpro_required_billing_fields, $pmpro_required_user_fields;
	$classes = array();

	//error on this field?
	if(!empty($pmpro_error_fields) && in_array($field, $pmpro_error_fields))
	{
		$classes[] = "pmpro_error";
	}

	if(is_array($pmpro_required_billing_fields) && is_array($pmpro_required_user_fields))
		$required_fields = array_merge(array_keys($pmpro_required_billing_fields), array_keys($pmpro_required_user_fields));
	elseif(is_array($pmpro_required_billing_fields))
		$required_fields = array_keys($pmpro_required_billing_fields);
	elseif(is_array($pmpro_required_user_fields))
		$required_fields = array_keys($pmpro_required_user_fields);
	else
		$required_fields = array();

	//required?
	if(in_array($field, $required_fields))
	{
		$classes[] = "pmpro_required";
	}

	$classes = apply_filters("pmpro_field_classes", $classes, $field);

	if(!empty($classes))
		return implode(" ", $classes);
	else
		return "";
}

//get a var from $_GET or $_POST
function pmpro_getParam($index, $method = "REQUEST", $default = "")
{
	if($method == "REQUEST")
	{
		if(!empty($_REQUEST[$index]))
			return $_REQUEST[$index];
	}
	elseif($method == "POST")
	{
		if(!empty($_POST[$index]))
			return $_POST[$index];
	}
	elseif($method == "GET")
	{
		if(!empty($_GET[$index]))
			return $_GET[$index];
	}

	return $default;
}

/*
	Format an address from address, city, state, zip, country, and phone
*/
function pmpro_formatAddress($name, $address1, $address2, $city, $state, $zip, $country, $phone, $nl2br = true)
{
	$address = "";

	if(!empty($name))
		$address .= $name . "\n";

	if(!empty($address1))
		$address .= $address1 . "\n";

	if(!empty($address2))
		$address .= $address2 . "\n";

	if(!empty($city) && !empty($state))
	{
		$address .= $city . ", " . $state;

		if(!empty($zip))
			$address .= " " . $zip;

		$address .= "\n";
	}

	if(!empty($country))
		$address .= $country . "\n";

	if(!empty($phone))
		$address .= formatPhone($phone);

	if($nl2br)
		$address = nl2br($address);

	return $address;
}

/*
	Checks if all required settings are set.
*/
function pmpro_is_ready()
{
	global $wpdb, $pmpro_pages, $pmpro_level_ready, $pmpro_gateway_ready, $pmpro_pages_ready;

	//check if there is at least one level
	$pmpro_level_ready = (bool)$wpdb->get_var("SELECT id FROM $wpdb->pmpro_membership_levels LIMIT 1");

	//check if the gateway settings are good. first check if it's needed (is there paid membership level)
	$paid_membership_level = $wpdb->get_var("SELECT id FROM $wpdb->pmpro_membership_levels WHERE allow_signups = 1 AND (initial_payment > 0 OR billing_amount > 0 OR trial_amount > 0) LIMIT 1");
	$paid_user_subscription = $wpdb->get_var("SELECT user_id FROM $wpdb->pmpro_memberships_users WHERE initial_payment > 0 OR billing_amount > 0 OR trial_amount > 0 LIMIT 1");

	if(empty($paid_membership_level) && empty($paid_user_subscription))
	{
		//no paid membership level now or attached to a user. we don't need the gateway setup
		$pmpro_gateway_ready = true;
	}
	else
	{
		$gateway = pmpro_getOption("gateway");
		if($gateway == "authorizenet")
		{
			if(pmpro_getOption("gateway_environment") && pmpro_getOption("loginname") && pmpro_getOption("transactionkey"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "paypal" || $gateway == "paypalexpress")
		{
			if(pmpro_getOption("gateway_environment") && pmpro_getOption("gateway_email") && pmpro_getOption("apiusername") && pmpro_getOption("apipassword") && pmpro_getOption("apisignature"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "paypalstandard")
		{
			if(pmpro_getOption("gateway_environment") && pmpro_getOption("gateway_email"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "payflowpro")
		{
			if(pmpro_getOption("payflow_partner") && pmpro_getOption("payflow_vendor") && pmpro_getOption("payflow_user") && pmpro_getOption("payflow_pwd"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "stripe")
		{
			if(pmpro_getOption("gateway_environment") && pmpro_getOption("stripe_secretkey") && pmpro_getOption("stripe_publishablekey"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "braintree")
		{
			if(pmpro_getOption("gateway_environment") && pmpro_getOption("braintree_merchantid") && pmpro_getOption("braintree_publickey") && pmpro_getOption("braintree_privatekey"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "twocheckout")
		{
			if(pmpro_getOption("gateway_environment") && pmpro_getOption("twocheckout_apiusername") && pmpro_getOption("twocheckout_apipassword"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "cybersource")
		{
			if(pmpro_getOption("gateway_environment") && pmpro_getOption("cybersource_merchantid") && pmpro_getOption("cybersource_securitykey"))
				$pmpro_gateway_ready = true;
			else
				$pmpro_gateway_ready = false;
		}
		elseif($gateway == "check")
		{
			$pmpro_gateway_ready = true;
		}
		else
		{
			$pmpro_gateway_ready = false;
		}
	}

	//check if we have all pages
	if($pmpro_pages["account"] &&
	   $pmpro_pages["billing"] &&
	   $pmpro_pages["cancel"] &&
	   $pmpro_pages["checkout"] &&
	   $pmpro_pages["confirmation"] &&
	   $pmpro_pages["invoice"] &&
	   $pmpro_pages["levels"])
		$pmpro_pages_ready = true;
	else
		$pmpro_pages_ready = false;

	//now check both
	if($pmpro_gateway_ready && $pmpro_pages_ready)
		$r = true;
	else
		$r = false;
	
	/**
	 * Filter to determine if PMPro setup is complete or
	 * if notices or warnings need to be shown in the PMPro settings.
	 *
	 * Note: The filter should return true or false and also set
	 * the $pmpro_level_ready, $pmpro_gateway_ready, $pmpro_pages_ready global variabls.
	 * 
	 * @since 1.8.4.5
	 *
	 * @param bool $r ready?	 
	 */	
	$r = apply_filters('pmpro_is_ready', $r);
	
	return $r;
}

/**
 * Format a price per the currency settings.
 *
 * @since  1.7.15
 */
function pmpro_formatPrice($price)
{
	global $pmpro_currency, $pmpro_currency_symbol, $pmpro_currencies;

	//start with the price formatted with two decimals
	$formatted = number_format($price, 2);

	//settings stored in array?
	if(!empty($pmpro_currencies[$pmpro_currency]) && is_array($pmpro_currencies[$pmpro_currency]))
	{
		//format number do decimals, with decimal_separator and thousands_separator
		$formatted = number_format($price,
			(isset($pmpro_currencies[$pmpro_currency]['decimals']) ? (int)$pmpro_currencies[$pmpro_currency]['decimals'] : 2),
			(isset($pmpro_currencies[$pmpro_currency]['decimal_separator']) ? $pmpro_currencies[$pmpro_currency]['decimal_separator'] : '.'),
			(isset($pmpro_currencies[$pmpro_currency]['thousands_separator']) ? $pmpro_currencies[$pmpro_currency]['thousands_separator'] : ',')
		);
		
		//which side is the symbol on?
		if(!empty($pmpro_currencies[$pmpro_currency]['position']) && $pmpro_currencies[$pmpro_currency]['position']== 'left')
			$formatted = $pmpro_currency_symbol . $formatted;
		else
			$formatted = $formatted . $pmpro_currency_symbol;
	}
	else
		$formatted = $pmpro_currency_symbol . $formatted;	//default to symbol on the left

	//filter
	return apply_filters('pmpro_format_price', $formatted, $price, $pmpro_currency, $pmpro_currency_symbol);
}

/**
 * Which side does the currency symbol go on?
 *
 * @since  1.7.15
 */
function pmpro_getCurrencyPosition()
{
	global $pmpro_currency, $pmpro_currencies;

	if(!empty($pmpro_currencies[$pmpro_currency]) && is_array($pmpro_currencies[$pmpro_currency]) && !empty($pmpro_currencies[$pmpro_currency]['position']))
		return $pmpro_currencies[$pmpro_currency]['position'];
	else
		return "left";
}

/*
 * What gateway should we be using?
 *
 * @since 1.8
 */
function pmpro_getGateway()
{
	//grab from param or options
	if (!empty($_REQUEST['gateway']))
		$gateway = $_REQUEST['gateway'];		//gateway passed as param
	elseif (!empty($_REQUEST['review']))
		$gateway = "paypalexpress";				//if review param assume paypalexpress
	else
		$gateway = pmpro_getOption("gateway");  //get from options

	//set valid gateways - the active gateway in the settings and any gateway added through the filter will be allowed
	if(pmpro_getOption("gateway", true) == "paypal")
		$valid_gateways = apply_filters("pmpro_valid_gateways", array("paypal", "paypalexpress"));
	else
		$valid_gateways = apply_filters("pmpro_valid_gateways", array(pmpro_getOption("gateway", true)));

	//make sure it's valid
	if(!in_array($gateway, $valid_gateways))
		$gateway = false;

	//filter for good measure
	$gateway = apply_filters('pmpro_get_gateway', $gateway, $valid_gateways);

	return $gateway;
}

/*
 * Does the date provided fall in this month.
 * Used in logins/visits/views report.
 *
 * @since 1.8.3
 */
function pmpro_isDateThisMonth($str)
{
	$now = current_time('timestamp');
	$this_month = intval(date("n", $now));
	$this_year = intval(date("Y", $now));

	$date = strtotime($str, $now);
	$date_month = intval(date("n", $date));
	$date_year = intval(date("Y", $date));

	if($date_month === $this_month && $date_year === $this_year)
		return true;
	else
		return false;
}

/**
 * Function to generate PMPro front end pages.
 *
 * @param array $pages {
 *     Formatted as array($name => $title) or array(array('title'=>'The Title', 'content'=>'The Content'))
 *
 *     @type string $name Page name. (Letters, numbers, and underscores only.)
 *     @type string $title Page title.
 * }
 * @return array $created_pages Created page IDs.
 * @since 1.8.5
 */
function pmpro_generatePages($pages) {

	global $pmpro_pages;

	$pages_created = array();

	if(!empty($pages)) {
		foreach($pages as $name => $page) {

			//does it already exist?
			if(!empty($pmpro_pages[$name]))
				continue;

			//no id set. create an array to store the page info
			if(is_array($page)) {
				$title = $page['title'];
				$content = $page['content'];
			} else {
				$title = $page;
				$content = '[pmpro_' . $name . ']';
			}
			
			$insert = array(
				'post_title' => $title,
				'post_status' => 'publish',
				'post_type' => 'page',
				'post_content' => $content,
				'comment_status' => 'closed',
				'ping_status' => 'closed'
			);

			//make non-account pages a subpage of account
			if ($name != "account") {
				$insert['post_parent'] = $pmpro_pages['account'];
			}

			//create the page
			$pmpro_pages[$name] = wp_insert_post($insert);

			//update the option too
			pmpro_setOption($name . "_page_id", $pmpro_pages[$name]);
			$pages_created[] = $pmpro_pages[$name];
		}
	}

	return $pages_created;
}

