diff --git a/adminpages/discountcodes.php b/adminpages/discountcodes.php index ed07e78..dbfc848 100644 --- a/adminpages/discountcodes.php +++ b/adminpages/discountcodes.php @@ -428,7 +428,18 @@ - initial_payment))?>" /> + + + initial_payment))?>" /> + + + @@ -439,7 +450,16 @@ style="display: none;"> - billing_amount))?>" /> per + + billing_amount))?>" /> + + per cycle_number))?>" /> trial_amount))?>" /> + + trial_amount))?>" /> + trial_limit))?>" /> . diff --git a/adminpages/functions.php b/adminpages/functions.php index 1479faf..364602b 100644 --- a/adminpages/functions.php +++ b/adminpages/functions.php @@ -39,12 +39,10 @@ function pmpro_checkLevelForStripeCompatibility($level = NULL) { /* Stripe currently does not support: - * Trial Amounts > 0. - * Daily billing periods. + * Trial Amounts > 0. * Billing Limits. */ - if($level->trial_amount > 0 || - ($level->cycle_number > 0 && $level->cycle_period == "Day") || + if($level->trial_amount > 0 || $level->billing_limit > 0) { return false; @@ -97,8 +95,7 @@ function pmpro_checkLevelForPayflowCompatibility($level = NULL) */ if($level->trial_amount > 0 || - $level->cycle_number > 1 || - ($level->cycle_number == 1 && $level->cycle_period == "Day")) + $level->cycle_number > 1) { return false; } diff --git a/adminpages/membershiplevels.php b/adminpages/membershiplevels.php index 9887ee2..b4535a5 100755 --- a/adminpages/membershiplevels.php +++ b/adminpages/membershiplevels.php @@ -337,7 +337,17 @@ - initial_payment))?>" /> + + + initial_payment))?>" /> + + @@ -348,7 +358,16 @@ style="display: none;"> - billing_amount))?>" /> + + billing_amount))?>" /> + + cycle_number))?>" /> trial_amount))?>" /> + + trial_amount))?>" /> + trial_limit))?>" /> . @@ -541,14 +568,14 @@ - initial_payment?> + initial_payment);?> -- - billing_amount?> cycle_number.' '.pmpro_translate_billing_period($level->cycle_period,$level->cycle_number)?> + billing_amount);?> cycle_number.' '.pmpro_translate_billing_period($level->cycle_period,$level->cycle_number)?> billing_limit) { ?>( billing_limit?> cycle_period,$level->billing_limit)?>) @@ -558,7 +585,7 @@ -- - trial_amount?> trial_limit?> trial_limit)?> + trial_amount);?> trial_limit?> trial_limit)?> diff --git a/adminpages/memberslist.php b/adminpages/memberslist.php index 83a23eb..f809730 100755 --- a/adminpages/memberslist.php +++ b/adminpages/memberslist.php @@ -6,7 +6,7 @@ } //vars - global $wpdb, $pmpro_currency_symbol; + global $wpdb; if(isset($_REQUEST['s'])) $s = $_REQUEST['s']; else @@ -193,11 +193,11 @@ membership?> initial_payment > 0) { ?> - initial_payment?> + initial_payment);?> initial_payment > 0 && (float)$auser->billing_amount > 0) { ?>+
billing_amount > 0) { ?> - billing_amount?>/cycle_period?> + billing_amount);?>/cycle_number > 1) { echo $auser->cycle_number . " " . $auser->cycle_period . "s"; } else { echo $auser->cycle_period; } ?> initial_payment <= 0 && (float)$auser->billing_amount <= 0) { ?> - diff --git a/adminpages/orders.php b/adminpages/orders.php index fb06c45..b607bfd 100644 --- a/adminpages/orders.php +++ b/adminpages/orders.php @@ -6,7 +6,7 @@ } //vars - global $wpdb, $pmpro_currency_symbol; + global $wpdb; if(isset($_REQUEST['s'])) $s = $_REQUEST['s']; else @@ -425,7 +425,7 @@ 0) { echo $order->status; } else { ?> get_col("SELECT DISTINCT(status) FROM $wpdb->pmpro_membership_orders"); $statuses = array_unique(array_merge($default_statuses, $used_statuses)); asort($statuses); @@ -613,7 +613,7 @@ @@ -625,7 +625,7 @@ @@ -935,7 +935,7 @@ function pmpro_ShowMonthOrYear() membership_id;?> - total;?> + total);?> payment_type)) echo $order->payment_type . "
";?> accountnumber)) { ?> diff --git a/adminpages/paymentsettings.php b/adminpages/paymentsettings.php index 9ebc6dd..03d39e2 100644 --- a/adminpages/paymentsettings.php +++ b/adminpages/paymentsettings.php @@ -391,6 +391,8 @@ function pmpro_changeGateway(gateway) global $pmpro_currencies; foreach($pmpro_currencies as $ccode => $cdescription) { + if(is_array($cdescription)) + $cdescription = $cdescription['name']; ?> :
- +
@@ -94,7 +94,7 @@ function pmpro_report_memberships_widget() {
- +
@@ -189,7 +189,7 @@ function pmpro_report_memberships_page() $cols = array(); if($period == "daily") { - $lastday = date("t", $startdate); + $lastday = date("t", strtotime($startdate, current_time("timestamp"))); for($i = 1; $i <= $lastday; $i++) { @@ -304,7 +304,7 @@ function pmpro_report_memberships_page() - + "); //insert fields for other card fields - form$.append(""); + if(jQuery('#CardType').length) + jQuery('#CardType').val(response['card']['type']); + else + form$.append(""); form$.append(""); form$.append(""); form$.append(""); @@ -808,6 +813,11 @@ function pmpro_braintree_dont_require_CVV($fields) $creds['user_password'] = $password; $creds['remember'] = true; $user = wp_signon($creds, false); + + //setting some cookies + wp_set_current_user($user_id, $username); + + wp_set_auth_cookie($user_id, true, (force_ssl_login() || force_ssl_admin())); } } else $user_id = $current_user->ID; diff --git a/readme.txt b/readme.txt index a46cf18..f2c608a 100755 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: strangerstudios Tags: memberships, membership, authorize.net, ecommerce, paypal, stripe, braintree, restrict access, restrict content, directory site, payflow Requires at least: 3.5 Tested up to: 4.0 -Stable tag: 1.7.14.2 +Stable tag: 1.7.15 The easiest way to GET PAID with your WordPress site. Flexible content control by Membership Level, Reports, Affiliates and Discounts @@ -102,6 +102,28 @@ Not sure? You can find out by doing a bit a research. 4. Offer Membership Discounts with specific price rules (restricted by level, unique pricing for each level, # of uses, expiration date.) == Changelog == += 1.7.15 = +* SECURITY FIX: The /services/getfile.php script has been disabled by default. You must set the PMPRO_GETFILE_ENABLED constant to true or 1 to allow the script to run. Additionally, the script will strip ../ and /. type strings out of the URI when looking for files to get and will not read any files using the extensions set via the pmpro_getfile_extension_blacklist filter. By default inc, php, php3, php4, php5, phps, and phtml file types are not allowed. (Thanks, Kacper Szurek) +* BUG: Fixed issue with Stripe integration where existing members checking out for new recurring subscriptions would receive extra charges. Now deleting the old Stripe subscription and any related open invoices and creating a new subscription instead of just updating the old subscription. (Thanks, Antonv and Thomas Sjolshagen) +* BUG: Fixed issue with Braintree integration where the billing address associated with a credit card was not being updated via the update billing page. (Thanks, Keith Abramo) +* BUG: Fixed issue where pmpro_next_payment() would return a 0 timestamp instead of false when there is no previous order. (Thanks, Thomas Sjolshagen) +* ENHANCEMENT: Added pmpro_formatPrice() and pmpro_getCurrencyPosition() functions. Now using them to render prices with formatting. You can use the pmpro_format_price filter or pmpro_currecies filter to adjust the formatting of prices to support currency symbols after the price or to use commas instead of periods for separators. +* ENAHNCEMENT: Added getSubscriptionStatus() to Authorize.net gateway class. Also fixed up some of the logic around checking the gateway environment. +* BUG: Now urlencoding the API Username and Password sent through the PayPal APIs in case your values have + or other special characters in them. (Thanks, mrschmiddy) +* BUG: Now showing cycle number in the Fee column of the members list. E.g. a level that is $10 every 3 months will now show up as $10.00 + $10.00/3 Months. +* BUG: Fixed bug where user first_name and last_name were being overwritten by PayPal values when using PayPal Standard. +* ENHANCEMENT: Added PMPRO_CRON_LIMIT constant, which can be used to limit the number of records processed by each scheduled cron job. This can for example, keep your server from going over PHP time limits or email limits. Use define('PMPRO_CRON_LIMIT', 100); to set the limit to 100. +* BUG: Discount code AJAX calls now going through admin-ajax.php, fixing issues where the Themed Profiles module of Theme My Login would block those calls. (Thanks, Tony) +* ENHANCEMENT: Removed the "CardType" field at checkout and now using the jquery.creditCardValidator script to determine the card type on form submit. +* BUG: No longer setting $order->subtotal and invoice total to the billing amount (vs the initial price) for recurring payments with Cybersource, PayPal Standard, PayPal Express or Twocheckout. (Thanks, Joce Nunes) +* ENHANCEMENT: The search filter will no longer filter out a post that is in a category blocked by one membership level if the user also has access to that content through another category. +* BUG/ENHANCEMENT: Running email body through wpautop if it doesn't look like HTML. +* ENHANCEMENT: Added pmpro_getfile_before_error hook in getfile.php. +* ENHANCEMENT: Added pmpro_ipn_check_receiver_email filter if you want to change how the email is checked in the IPN log. +* BUG: Fixed bug where reports would show duplicate month labels on the last day of the month. +* BUG: Fixed some issues with logging in at checkout, especially when using FORCE_SSL_ADMIN. (Thanks, Wimans) +* ENHANCEMENT: Added "pending" as a default status for orders available on the edit order page in the dashboard. + = 1.7.14.2 = * BUG: Removed the debug call to d($...) that was left in preheaders/checkout.php and would show up when checkout forms were submitted with empty fields. (Thanks, Nicolas) diff --git a/scheduled/crons.php b/scheduled/crons.php index f2f536d..611b505 100644 --- a/scheduled/crons.php +++ b/scheduled/crons.php @@ -2,17 +2,20 @@ /* Expiring Memberships */ - add_action("pmpro_cron_expire_memberships", "pmpro_cron_expire_memberships"); + add_action("pmpro_cron_expire_memberships", "pmpro_cron_expire_memberships"); function pmpro_cron_expire_memberships() { global $wpdb; //make sure we only run once a day - $today = date("Y-m-d"); + $today = date("Y-m-d", current_time("timestamp")); //look for memberships that expired before today $sqlQuery = "SELECT mu.user_id, mu.membership_id, mu.startdate, mu.enddate FROM $wpdb->pmpro_memberships_users mu WHERE mu.status = 'active' AND mu.enddate IS NOT NULL AND mu.enddate <> '' AND mu.enddate <> '0000-00-00 00:00:00' AND DATE(mu.enddate) <= '" . $today . "' ORDER BY mu.enddate"; - + + if(defined('PMPRO_CRON_LIMIT')) + $sqlQuery .= " LIMIT " . PMPRO_CRON_LIMIT; + $expired = $wpdb->get_results($sqlQuery); foreach($expired as $e) @@ -42,7 +45,7 @@ function pmpro_cron_expiration_warnings() global $wpdb; //make sure we only run once a day - $today = date("Y-m-d 00:00:00"); + $today = date("Y-m-d 00:00:00", current_time("timestamp")); $pmpro_email_days_before_expiration = apply_filters("pmpro_email_days_before_expiration", 7); @@ -59,6 +62,9 @@ function pmpro_cron_expiration_warnings() AND (um.meta_value IS NULL OR DATE_ADD(um.meta_value, INTERVAL " . $pmpro_email_days_before_expiration . " Day) <= '" . $today . "') ORDER BY mu.enddate"; + if(defined('PMPRO_CRON_LIMIT')) + $sqlQuery .= " LIMIT " . PMPRO_CRON_LIMIT; + $expiring_soon = $wpdb->get_results($sqlQuery); foreach($expiring_soon as $e) @@ -102,7 +108,10 @@ function pmpro_cron_credit_card_expiring_warnings() AND CONCAT(um2.meta_value, '-', um1.meta_value, '-01') < '" . $next_month_date . "' AND (um3.meta_value IS NULL OR CONCAT(um2.meta_value, '-', um1.meta_value, '-01') <> um3.meta_value) "; - + + if(defined('PMPRO_CRON_LIMIT')) + $sqlQuery .= " LIMIT " . PMPRO_CRON_LIMIT; + $cc_expiring_user_ids = $wpdb->get_col($sqlQuery); if(!empty($cc_expiring_user_ids)) @@ -161,7 +170,7 @@ function pmpro_cron_trial_ending_warnings() global $wpdb; //make sure we only run once a day - $today = date("Y-m-d 00:00:00"); + $today = date("Y-m-d 00:00:00", current_time("timestamp")); $pmpro_email_days_before_trial_end = apply_filters("pmpro_email_days_before_trial_end", 7); @@ -180,7 +189,10 @@ function pmpro_cron_trial_ending_warnings() AND (um.meta_value IS NULL OR um.meta_value = '' OR DATE_ADD(um.meta_value, INTERVAL " . $pmpro_email_days_before_trial_end . " Day) <= '" . $today . "') ORDER BY mu.startdate"; - + + if(defined('PMPRO_CRON_LIMIT')) + $sqlQuery .= " LIMIT " . PMPRO_CRON_LIMIT; + $trial_ending_soon = $wpdb->get_results($sqlQuery); foreach($trial_ending_soon as $e) diff --git a/services/getfile.php b/services/getfile.php index fc789f2..cd83aa5 100755 --- a/services/getfile.php +++ b/services/getfile.php @@ -1,12 +1,23 @@ 1) @@ -52,6 +70,8 @@ { if(!pmpro_has_membership_access($file_post_parent)) { + do_action("pmpro_getfile_before_error", $filename, $file_post_parent); + //nope header('HTTP/1.1 503 Service Unavailable', true, 503); echo "HTTP/1.1 503 Service Unavailable"; @@ -67,8 +87,40 @@ //in case we want to do something else with the file do_action("pmpro_getfile_before_readfile", $filename, $file_mimetype); - //show the file + //if file is not found, die + if(!file_exists($filename)) + { + status_header( 404 ); + nocache_headers(); + die("File not found."); + } + + //if blacklistsed file type, redirect to it instead + $basename = basename($filename); + $parts = explode('.', $basename); + $ext = strtolower($parts[count($parts)-1]); + + //build blacklist and allow for filtering + $blacklist = array("inc", "php", "php3", "php4", "php5", "phps", "phtml"); + $blacklist = apply_filters("pmpro_getfile_extension_blacklist", $blacklist); + + //check + if(in_array($ext, $blacklist)) + { + //add a noloop param to avoid infinite loops + $uri = add_query_arg("noloop", 1, $uri); + + //guess scheme and add host back to uri + if(is_ssl()) + $uri = "https://" . $_SERVER['HTTP_HOST'] . "/" . $uri; + else + $uri = "http://" . $_SERVER['HTTP_HOST'] . "/" . $uri; + + wp_redirect($uri); + exit; + } + + //okay show the file header("Content-type: " . $file_mimetype); readfile($filename); - exit; -?> \ No newline at end of file + exit; \ No newline at end of file diff --git a/services/ipnhandler.php b/services/ipnhandler.php index 28cb91c..a03a237 100755 --- a/services/ipnhandler.php +++ b/services/ipnhandler.php @@ -413,13 +413,32 @@ function pmpro_ipnCheckReceiverEmail($email) if(!in_array(strtolower(pmpro_getOption('gateway_email')), $email)) { - //not yours - ipnlog("ERROR: receiver_email (" . $_POST['receiver_email'] . ") did not match (" . pmpro_getOption('gateway_email') . ")"); - //email them - return false; + $r = false; } else + $r = true; + + $r = apply_filters('pmpro_ipn_check_receiver_email', $r, $email); + + if($r) return true; + else + { + if(!empty($_POST['receiver_email'])) + $receiver_email = $_POST['receiver_email']; + else + $receiver_email = "N/A"; + + if(!empty($_POST['business'])) + $business = $_POST['business']; + else + $business = "N/A"; + + //not yours + ipnlog("ERROR: receiver_email (" . $receiver_email . ") and business email (" . $business . ") did not match (" . pmpro_getOption('gateway_email') . ")"); + return false; + } + } /* @@ -498,13 +517,13 @@ function pmpro_ipnChangeMembershipLevel($txn_id, &$morder) if(!empty($_POST['first_name'])) { $old_firstname = get_user_meta($morder->user_id, "first_name", true); - if(!empty($old_firstname)) + if(empty($old_firstname)) update_user_meta($morder->user_id, "first_name", $_POST['first_name']); } if(!empty($_POST['last_name'])) { $old_lastname = get_user_meta($morder->user_id, "last_name", true); - if(!empty($old_lastname)) + if(empty($old_lastname)) update_user_meta($morder->user_id, "last_name", $_POST['last_name']); } diff --git a/services/stripe-webhook.php b/services/stripe-webhook.php index 9087268..47dcbde 100644 --- a/services/stripe-webhook.php +++ b/services/stripe-webhook.php @@ -185,7 +185,7 @@ elseif($event->type == "customer.subscription.deleted") { //for one of our users? if they still have a membership, notify the admin - $user = getUserFromCustomerEvent($event, "success"); + $user = getUserFromCustomerEvent($event, "success", true); if(!empty($user->ID)) { do_action("pmpro_stripe_subscription_deleted", $user->ID); @@ -225,7 +225,7 @@ function getUserFromInvoiceEvent($event) $customer_id = $event->data->object->customer; //look up the order - $user_id = $wpdb->get_var("SELECT user_id FROM $wpdb->pmpro_membership_orders WHERE subscription_transaction_id = '" . $customer_id . "' LIMIT 1"); + $user_id = $wpdb->get_var("SELECT user_id FROM $wpdb->pmpro_membership_orders WHERE subscription_transaction_id = '" . esc_sql($customer_id) . "' LIMIT 1"); if(!empty($user_id)) return get_userdata($user_id); @@ -233,7 +233,7 @@ function getUserFromInvoiceEvent($event) return false; } - function getUserFromCustomerEvent($event, $status = false) + function getUserFromCustomerEvent($event, $status = false, $checkplan = true) { //pause here to give PMPro a chance to finish checkout sleep(PMPRO_STRIPE_WEBHOOK_DELAY); @@ -241,13 +241,16 @@ function getUserFromCustomerEvent($event, $status = false) global $wpdb; $customer_id = $event->data->object->customer; - + $plan_id = $event->data->object->plan->id; + //look up the order - $sqlQuery = "SELECT user_id FROM $wpdb->pmpro_membership_orders WHERE subscription_transaction_id = '" . $customer_id . "' "; + $sqlQuery = "SELECT user_id FROM $wpdb->pmpro_membership_orders WHERE subscription_transaction_id = '" . esc_sql($customer_id) . "' "; if($status) - $sqlQuery .= " AND status='" . $status . "' "; + $sqlQuery .= " AND status='" . esc_sql($status) . "' "; + if($checkplan) + $sqlQuery .= " AND code='" . esc_sql($plan_id) . "' "; $sqlQuery .= " LIMIT 1"; - + $user_id = $wpdb->get_var($sqlQuery); if(!empty($user_id))