<?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";
	}
}

//setup wpdb for the tables we need
function pmpro_setDBTables()
{
	global $table_prefix, $wpdb;
	$wpdb->hide_errors();
	$wpdb->pmpro_membership_levels = $table_prefix . 'pmpro_membership_levels';
	$wpdb->pmpro_memberships_users = $table_prefix . 'pmpro_memberships_users';
	$wpdb->pmpro_memberships_categories = $table_prefix . 'pmpro_memberships_categories';
	$wpdb->pmpro_memberships_pages = $table_prefix . 'pmpro_memberships_pages';
	$wpdb->pmpro_membership_orders = $table_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(isset($_REQUEST[$s]) && !$force)
	{
		if(!is_array($_REQUEST[$s]))
			return trim($_REQUEST[$s]);
		else
			return $_REQUEST[$s];
	}
	elseif(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 request var
	if($v === NULL && isset($_REQUEST[$s]))
		$v = $_REQUEST[$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;
	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')
				{
					$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(__('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 {
				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);		
	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")
{
	global $wpdb, $current_user;
	if(!$user_id)
		$user_id = $current_user->ID;
		
	if(!$user_id)
		return false;
	
	//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 . ")");				
		
		return $nextdate;
	}
	else
	{	
		//no order or level found, or level was not recurring
		return false;	
	}
}

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 "";	
		}
	}
}

if(!function_exists("cleanPhone"))
{
	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;
	}
}

if(!function_exists("formatPhone"))
{
	function formatPhone($phone)
	{
		$phone = cleanPhone($phone);
		
		if(strlen($phone) == 11)
			return substr($phone, 0, 1) . " (" . substr($phone, 1, 3) . ") " . substr($phone, 4, 3) . "-" . substr($phone, 7, 4);
		elseif(strlen($phone) == 10)
			return "(" . substr($phone, 0, 3) . ") " . substr($phone, 3, 3) . "-" . substr($phone, 6, 4);
		elseif(strlen($phone) == 7)
			return substr($phone, 0, 3) . "-" . substr($phone, 3, 4);
		else
			return $phone;
	}
}

function pmpro_showRequiresMembershipMessage()
{
	//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);
	}	
}

/* 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.
 */
function pmpro_hasMembershipLevel($levels = NULL, $user_id = NULL)
{
	global $current_user, $all_membership_levels, $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;
	}
	else //get membership levels for given user
	{
		$membership_levels = pmpro_getMembershipLevelsForUser($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 was sent in one of the levels
			if(in_array(0, $levels) || in_array("0", $levels))
				$return = true;					
		}
		else
		{
			foreach($levels as $level)
			{				
				$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) and intval($level) < 0 and !$found_level) //checking for the absence of this level
				{
					$return = true;
				}
				else if($found_level) //checking for the presence of this level
				{
					$return = true;
				}
			}
		}
	}
	
	$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)
{
	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;
	}

	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
		}
	}

	$old_levels = pmpro_getMembershipLevelsForUser($user_id);
				
	$pmpro_cancel_previous_subscriptions = apply_filters("pmpro_cancel_previous_subscriptions", true);
	if($pmpro_cancel_previous_subscriptions)
	{		
		//deactivate old memberships (updates pmpro_memberships_users table)
		if(!empty($old_levels))
		{			
			foreach($old_levels as $old_level) {
				$sql = "UPDATE $wpdb->pmpro_memberships_users SET `status`='inactive', `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;
				}										
			}
		}
		
		//cancel any other subscriptions they have (updates pmpro_membership_orders table)
		$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;
			}
		}
	}

	//get level id
	if(is_array($level))
		$level_id = $level['membership_id'];	//custom level
	else
		$level_id = $level;	//just id
	
	//remove cached level
	global $all_membership_levels;
	unset($all_membership_levels[$user_id]);
	
	//update user data and call action
	pmpro_set_current_user();
	do_action("pmpro_after_change_membership_level", $level_id, $user_id);	//$level is the $level_id here
	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)
{
	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)
	{
		$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 '%$s%' OR u.user_email LIKE '%$s%' OR um.meta_value LIKE '%$s%') ";
		if($l)
			$user_ids_query .= "AND mu.membership_id = '$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 '%$s%' OR u.user_email LIKE '%$s%' OR um.meta_value LIKE '%$s%') ";
		if($l)
			$user_ids_query .= "AND mu.membership_id = '$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 = '" . $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($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 = '" . $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 = '$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;
	
	//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 ='" . $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;
	}

	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");
		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;
	}

	global $wpdb;
	return $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_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 = '" . $level . "' LIMIT 1");
		$level_id = $level_obj->id;
		$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;
		}

		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;
	}
}

//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;
		}
		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)
		return true;
	else
		return false;
}

/**
 * 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]))
	{
		//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;
			
		//commas or periods?
		if(!empty($pmpro_currencies[$pmpro_currency]['separator']) && $pmpro_currencies[$pmpro_currency]['separator'])
			$formatted = str_replace(array(".",","), $pmpro_currencies[$pmpro_currency]['separator'], $formatted);
	}
	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";
}