PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260805
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260805
260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 All 189 releases
s2member / src / includes / classes / utils-users.inc.php

utils-users.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions 260805, at src/includes/classes/utils-users.inc.php

779 lines 41.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * User utilities.
5 *
6 * Copyright: © 2009-2011
7 * {@link http://websharks-inc.com/ WebSharks, Inc.}
8 * (coded in the USA)
9 *
10 * Released under the terms of the GNU General Public License.
11 * You should have received a copy of the GNU General Public License,
12 * along with this software. In the main directory, see: /licensing/
13 * If not, see: {@link http://www.gnu.org/licenses/}.
14 *
15 * @package s2Member\Utilities
16 * @since 3.5
17 */
18 if(!defined('WPINC')) // MUST have WordPress.
19 exit ('Do not access this file directly.');
20
21 if(!class_exists('c_ws_plugin__s2member_utils_users'))
22 {
23 /**
24 * User utilities.
25 *
26 * @package s2Member\Utilities
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_utils_users
30 {
31 /**
32 * Determines the total Users/Members in the database.
33 *
34 * @package s2Member\Utilities
35 * @since 3.5
36 *
37 * @return int Number of Users in the database, total.
38 */
39 public static function users_in_database()
40 {
41 global $wpdb;
42 /** @var wpdb $wpdb */
43
44 $wpdb->query("SELECT SQL_CALC_FOUND_ROWS `".$wpdb->users."`.`ID` FROM `".$wpdb->users."`, `".$wpdb->usermeta."` WHERE `".$wpdb->users."`.`ID` = `".$wpdb->usermeta."`.`user_id` AND `".$wpdb->usermeta."`.`meta_key` = '".esc_sql($wpdb->prefix."capabilities")."' LIMIT 1");
45 $users = (int)$wpdb->get_var("SELECT FOUND_ROWS()");
46
47 return $users;
48 }
49
50 /**
51 * Obtains Custom String for an existing Member, referenced by a Subscr. or Transaction ID.
52 *
53 * A second lookup parameter can be provided as well *(optional)*.
54 *
55 * @package s2Member\Utilities
56 * @since 3.5
57 *
58 * @param string $subscr_txn_baid_cid_id Either a Paid Subscr. ID, or a Paid Transaction ID.
59 * @param string $os0 Optional. A second lookup parameter, usually the `os0` value for PayPal integrations.
60 *
61 * @return string|bool The Custom String value on success, else false on failure.
62 */
63 public static function get_user_custom_with($subscr_txn_baid_cid_id = '', $os0 = '')
64 {
65 global $wpdb;
66 /** @var wpdb $wpdb */
67
68 if($subscr_txn_baid_cid_id && $os0) // This case includes some additional routines that can use the ``$os0`` value.
69 {
70 if(($q = $wpdb->get_row("SELECT `user_id` FROM `".$wpdb->usermeta."` WHERE (`meta_key` = '".$wpdb->prefix."s2member_subscr_id' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_baid' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_cid' OR `meta_key` = '".$wpdb->prefix."s2member_first_payment_txn_id') AND (`meta_value` = '".esc_sql($subscr_txn_baid_cid_id)."' OR `meta_value` = '".esc_sql($os0)."') LIMIT 1"))
71 || ($q = $wpdb->get_row("SELECT `ID` AS `user_id` FROM `".$wpdb->users."` WHERE `ID` = '".esc_sql($os0)."' LIMIT 1"))
72 ) if(($custom = get_user_option('s2member_custom', $q->user_id)))
73 return $custom;
74 }
75 else if($subscr_txn_baid_cid_id) // Otherwise, if all we have is a Subscr./Txn. ID value.
76 {
77 if(($q = $wpdb->get_row("SELECT `user_id` FROM `".$wpdb->usermeta."` WHERE (`meta_key` = '".$wpdb->prefix."s2member_subscr_id' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_baid' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_cid' OR `meta_key` = '".$wpdb->prefix."s2member_first_payment_txn_id') AND `meta_value` = '".esc_sql($subscr_txn_baid_cid_id)."' LIMIT 1")))
78 if(($custom = get_user_option('s2member_custom', $q->user_id)))
79 return $custom;
80 }
81 return FALSE; // Otherwise, return false.
82 }
83
84 /**
85 * Obtains the User ID for an existing Member, referenced by a Subscr. or Transaction ID.
86 *
87 * A second lookup parameter can be provided as well *(optional)*.
88 *
89 * @package s2Member\Utilities
90 * @since 3.5
91 *
92 * @param string $subscr_txn_baid_cid_id Either a Paid Subscr. ID, or a Paid Transaction ID.
93 * @param string $os0 Optional. A second lookup parameter, usually the `os0` value for PayPal integrations.
94 *
95 * @return integer|bool A WordPress User ID on success, else false on failure.
96 */
97 public static function get_user_id_with($subscr_txn_baid_cid_id = '', $os0 = '')
98 {
99 global $wpdb;
100 /** @var wpdb $wpdb */
101
102 if($subscr_txn_baid_cid_id && $os0) // This case includes some additional routines that can use the ``$os0`` value.
103 {
104 if(($q = $wpdb->get_row("SELECT `user_id` FROM `".$wpdb->usermeta."` WHERE (`meta_key` = '".$wpdb->prefix."s2member_subscr_id' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_baid' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_cid' OR `meta_key` = '".$wpdb->prefix."s2member_first_payment_txn_id') AND (`meta_value` = '".esc_sql($subscr_txn_baid_cid_id)."' OR `meta_value` = '".esc_sql($os0)."') LIMIT 1"))
105 || ($q = $wpdb->get_row("SELECT `ID` AS `user_id` FROM `".$wpdb->users."` WHERE `ID` = '".esc_sql($os0)."' LIMIT 1"))
106 ) return $q->user_id;
107 }
108 else if($subscr_txn_baid_cid_id) // Otherwise, if all we have is a Subscr./Txn. ID value.
109 {
110 if(($q = $wpdb->get_row("SELECT `user_id` FROM `".$wpdb->usermeta."` WHERE (`meta_key` = '".$wpdb->prefix."s2member_subscr_id' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_baid' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_cid' OR `meta_key` = '".$wpdb->prefix."s2member_first_payment_txn_id') AND `meta_value` = '".esc_sql($subscr_txn_baid_cid_id)."' LIMIT 1")))
111 return $q->user_id;
112 }
113 return FALSE; // Otherwise, return false.
114 }
115
116 /**
117 * Obtains the Email Address for an existing Member, referenced by a Subscr. or Transaction ID.
118 *
119 * A second lookup parameter can be provided as well *(optional)*.
120 *
121 * @package s2Member\Utilities
122 * @since 3.5
123 *
124 * @param string $subscr_txn_baid_cid_id Either a Paid Subscr. ID, or a Paid Transaction ID.
125 * @param string $os0 Optional. A second lookup parameter, usually the `os0` value for PayPal integrations.
126 *
127 * @return int|bool A User's Email Address on success, else false on failure.
128 */
129 public static function get_user_email_with($subscr_txn_baid_cid_id = '', $os0 = '')
130 {
131 global $wpdb;
132 /** @var wpdb $wpdb */
133
134 if($subscr_txn_baid_cid_id && $os0) // This case includes some additional routines that can use the ``$os0`` value.
135 {
136 if(($q = $wpdb->get_row("SELECT `user_id` FROM `".$wpdb->usermeta."` WHERE (`meta_key` = '".$wpdb->prefix."s2member_subscr_id' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_baid' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_cid' OR `meta_key` = '".$wpdb->prefix."s2member_first_payment_txn_id') AND (`meta_value` = '".esc_sql($subscr_txn_baid_cid_id)."' OR `meta_value` = '".esc_sql($os0)."') LIMIT 1"))
137 || ($q = $wpdb->get_row("SELECT `ID` AS `user_id` FROM `".$wpdb->users."` WHERE `ID` = '".esc_sql($os0)."' LIMIT 1"))
138 ) if(is_object($user = new WP_User ($q->user_id)) && !empty($user->ID) && ($email = $user->user_email))
139 return $email;
140 }
141 else if($subscr_txn_baid_cid_id) // Otherwise, if all we have is a Subscr./Txn. ID value.
142 {
143 if(($q = $wpdb->get_row("SELECT `user_id` FROM `".$wpdb->usermeta."` WHERE (`meta_key` = '".$wpdb->prefix."s2member_subscr_id' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_baid' OR `meta_key` = '".$wpdb->prefix."s2member_subscr_cid' OR `meta_key` = '".$wpdb->prefix."s2member_first_payment_txn_id') AND `meta_value` = '".esc_sql($subscr_txn_baid_cid_id)."' LIMIT 1")))
144 if(is_object($user = new WP_User ($q->user_id)) && !empty($user->ID) && ($email = $user->user_email))
145 return $email;
146 }
147 return FALSE; // Otherwise, return false.
148 }
149
150 /**
151 * Retrieves IPN Signup Vars & validates their Subscription ID.
152 *
153 * @package s2Member\Utilities
154 * @since 3.5
155 *
156 * @param integer|string $user_id Optional. A numeric WordPress User ID.
157 *
158 * @param string $subscr_txn_baid_cid_id Optional. Can be used instead of passing in a ``$user_id``.
159 * If ``$subscr_txn_baid_cid_id`` is passed in, it has to match the one found inside the resulting IPN Signup Vars collected by this routine.
160 * If neither of these parameters are passed in, the current User is assumed instead, obtained through ``wp_get_current_user()``.
161 *
162 * @return array|bool A User's IPN Signup Vars on success, else false on failure.
163 * If no IPN Signup Vars is found for this user, a makeshift array may be returned if fallback behavior is enabled.
164 */
165 public static function get_user_ipn_signup_vars($user_id = 0, $subscr_txn_baid_cid_id = '')
166 {
167 if($user_id || ($subscr_txn_baid_cid_id && ($user_id = c_ws_plugin__s2member_utils_users::get_user_id_with($subscr_txn_baid_cid_id)))
168 || (!$user_id && !$subscr_txn_baid_cid_id && is_object($user = wp_get_current_user()) && !empty($user->ID) && ($user_id = $user->ID))
169 )
170 {
171 $_subscr_baid = get_user_option('s2member_subscr_baid', $user_id);
172 $_subscr_cid = get_user_option('s2member_subscr_cid', $user_id);
173 $_subscr_id = get_user_option('s2member_subscr_id', $user_id);
174
175 if ($_subscr_id) {
176 $ipn_signup_vars = get_user_option('s2member_ipn_signup_vars', $user_id);
177 if (is_array($ipn_signup_vars)) {
178 // If we have ipn_signup_vars, subscr_id should match.
179 if (!empty($ipn_signup_vars['subscr_id']) && $ipn_signup_vars['subscr_id'] == $_subscr_id) {
180 return $ipn_signup_vars;
181 } else {
182 return FALSE;
183 }
184 }
185 //250426 Fall back to a makeshift ipn_signup_vars array.
186 //!!! TO-DO: Gateway enrichment for missing term details (period1/period3) when signup vars are missing.
187 // PayPal Checkout subs: use REST Subscriptions API.
188 // Legacy PayPal recurring profiles: use legacy NVP/Payflow APIs.
189 // Do not guess term values in fallback vars (risk of incorrect EOT decisions).
190 if (!empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['ipn_signup_vars_fallback'])) {
191 $userdata = get_userdata((int)$user_id);
192 $ipn_signup_vars = array(
193 'subscr_cid' => !empty($_subscr_cid) ? $_subscr_cid : '',
194 'subscr_id' => $_subscr_id,
195 'custom' => esc_html($_SERVER["HTTP_HOST"]),
196 'period1' => '',
197 'period3' => '',
198 'payer_email' => !empty($userdata->user_email) ? $userdata->user_email : 'USER EMAIL',
199 'first_name' => !empty($userdata->first_name) ? $userdata->first_name : 'FIRST NAME',
200 'last_name' => !empty($userdata->last_name) ? $userdata->last_name : 'LAST NAME',
201 'option_name1' => '',
202 'option_selection1' => '',
203 'option_name2' => '',
204 'option_selection2' => '',
205 'item_name' => 'FALLBACK IPN SIGNUP VARS',
206 'item_number' => (string) c_ws_plugin__s2member_user_access::user_access_level($userdata), //250916
207 );
208 return $ipn_signup_vars;
209 }
210 }
211 }
212 return FALSE; // Otherwise, return false.
213 }
214
215 /**
216 * Retrieves IPN Signup Var & validates their Subscription ID.
217 *
218 * The ``$user_id`` can be passed in directly; or a lookup can be performed with ``$subscr_id``.
219 *
220 * @package s2Member\Utilities
221 * @since 110912
222 *
223 * @param string $var Required. The requested Signup Var.
224 * @param int|string $user_id Optional. A numeric WordPress User ID.
225 * @param string $subscr_txn_baid_cid_id Optional. Can be used instead of passing in a ``$user_id``.
226 * If ``$subscr_id`` is passed in, it has to match the one found inside the resulting IPN Signup Vars collected by this routine.
227 * If neither of these parameters are passed in, the current User is assumed instead, obtained through ``wp_get_current_user()``.
228 *
229 * @return mixed|bool A User's IPN Signup Var on success, else false on failure.
230 */
231 public static function get_user_ipn_signup_var($var = '', $user_id = 0, $subscr_txn_baid_cid_id = '')
232 {
233 if(!empty($var) && is_array($user_ipn_signup_vars = c_ws_plugin__s2member_utils_users::get_user_ipn_signup_vars($user_id, $subscr_txn_baid_cid_id)))
234 {
235 if(isset($user_ipn_signup_vars[$var]))
236 return $user_ipn_signup_vars[$var];
237 }
238 return FALSE; // Otherwise, return false.
239 }
240
241 /**
242 * Obtains a User's Paid Subscr. ID *(if available)*; otherwise their WP User ID.
243 *
244 * If ``$user`` IS passed in, this function will return data from a specific ``$user``, or fail if not possible.
245 * If ``$user`` is NOT passed in, check the current User/Member.
246 *
247 * @package s2Member\Utilities
248 * @since 3.5
249 *
250 * @param object $user Optional. A `WP_User` object.
251 * In order to check the current User, you must call this function with no arguments/parameters.
252 *
253 * @return int|string|bool If possible, the User's Paid Subscr. ID, else their WordPress User ID, else false.
254 */
255 public static function get_user_subscr_or_wp_id($user = NULL)
256 {
257 if((func_num_args() && (!is_object($user) || empty($user->ID)))
258 || (!func_num_args() && (!is_object($user = (is_user_logged_in()) ? wp_get_current_user() : FALSE) || empty($user->ID)))
259 ) return FALSE; // The ``$user`` was passed in but is NOT an object; or nobody is logged in.
260
261 return ($subscr_id = get_user_option('s2member_subscr_id', $user->ID)) ? $subscr_id : $user->ID;
262 }
263
264 /**
265 * Determines whether or not a Username/Email is already in the database.
266 *
267 * Returns the WordPress User ID if they exist.
268 *
269 * @package s2Member\Utilities
270 * @since 3.5
271 *
272 * @param string $user_login A User's Username.
273 * @param string $user_email A User's Email Address.
274 *
275 * @return int|bool If exists, a WordPress User ID, else false.
276 */
277 public static function user_login_email_exists($user_login = '', $user_email = '')
278 {
279 global $wpdb;
280 /** @var wpdb $wpdb */
281
282 if($user_login && $user_email) // Only if we have both of these.
283 if(($user_id = $wpdb->get_var("SELECT `ID` FROM `".$wpdb->users."` WHERE `user_login` LIKE '".esc_sql(c_ws_plugin__s2member_utils_strings::like_escape($user_login))."' AND `user_email` LIKE '".esc_sql(c_ws_plugin__s2member_utils_strings::like_escape($user_email))."' LIMIT 1")))
284 return $user_id; // Return the associated WordPress ID.
285
286 return FALSE; // Otherwise, return false.
287 }
288
289 /**
290 * Determines whether or not a Username/Email is already in the database for this Blog.
291 *
292 * Returns the WordPress User ID if they exist.
293 *
294 * @package s2Member\Utilities
295 * @since 3.5
296 *
297 * @param string $user_login A User's Username.
298 * @param string $user_email A User's Email Address.
299 * @param int|string $blog_id A numeric WordPress Blog ID.
300 *
301 * @return int|bool If exists *(but not on Blog)*, a WordPress User ID, else false.
302 */
303 public static function ms_user_login_email_exists_but_not_on_blog($user_login = '', $user_email = '', $blog_id = 0)
304 {
305 if($user_login && $user_email) // Only if we have both of these.
306 if(is_multisite() && ($user_id = c_ws_plugin__s2member_utils_users::user_login_email_exists($user_login, $user_email)) && !is_user_member_of_blog($user_id, $blog_id))
307 return $user_id;
308
309 return FALSE; // Otherwise, return false.
310 }
311
312 /**
313 * Determines whether or not a Username/Email is already in the database for this Blog.
314 *
315 * This is an alias for: `c_ws_plugin__s2member_utils_users::ms_user_login_email_exists_but_not_on_blog()`.
316 *
317 * Returns the WordPress User ID if they exist.
318 *
319 * @package s2Member\Utilities
320 * @since 3.5
321 *
322 * @param string $user_login A User's Username.
323 * @param string $user_email A User's Email Address.
324 * @param int|string $blog_id A numeric WordPress Blog ID.
325 *
326 * @return int|bool If exists *(but not on Blog)*, a WordPress User ID, else false.
327 */
328 public static function ms_user_login_email_can_join_blog($user_login = '', $user_email = '', $blog_id = 0)
329 {
330 return c_ws_plugin__s2member_utils_users::ms_user_login_email_exists_but_not_on_blog($user_login, $user_email, $blog_id);
331 }
332
333 /**
334 * Retrieves a field value. Also supports Custom Fields.
335 *
336 * @package s2Member\Utilities
337 * @since 3.5
338 *
339 * @param string $field_id Required. A unique Custom Registration/Profile Field ID, that you configured with s2Member.
340 * Or, this could be set to any property that exists on the WP_User object for a particular User;
341 * ( i.e., `id`, `ID`, `user_login`, `user_email`, `first_name`, `last_name`, `display_name`, `ip`, `IP`,
342 * `s2member_registration_ip`, `s2member_custom`, `s2member_subscr_id`, `s2member_subscr_or_wp_id`,
343 * `s2member_subscr_gateway`, `s2member_custom_fields`, `s2member_file_download_access_[log|arc]`,
344 * `s2member_auto_eot_time`, `s2member_last_payment_time`, `s2member_paid_registration_times`,
345 * `s2member_access_role`, `s2member_access_level`, `s2member_access_label`,
346 * `s2member_access_ccaps`, etc, etc. ).
347 * @param int|string $user_id Optional. Defaults to the current User's ID.
348 * @param array $args Any additional behavioral args. Since v150717.
349 *
350 * @return mixed The value of the requested field, or false if the field does not exist.
351 */
352 public static function get_user_field($field_id = '', $user_id = 0, $args = array())
353 {
354 global $wpdb; /** @var wpdb $wpdb Reference for IDEs. */
355
356 $current_user = wp_get_current_user(); // Current user.
357
358 if(is_object($user = $user_id ? new WP_User($user_id) : $current_user)
359 && !empty($user->ID) && ($user_id = $user->ID))
360 {
361 $args = (array)$args; // Force array.
362
363 if(isset($user->{$field_id}))
364 return $user->{$field_id};
365
366 else if(isset($user->data->{$field_id}))
367 return $user->data->{$field_id};
368
369 else if(isset($user->{$wpdb->prefix.$field_id}))
370 return $user->{$wpdb->prefix.$field_id};
371
372 else if(isset($user->data->{$wpdb->prefix.$field_id}))
373 return $user->data->{$wpdb->prefix.$field_id};
374
375 else if(strcasecmp($field_id, 'full_name') === 0)
376 return trim($user->first_name.' '.$user->last_name);
377
378 else if(preg_match('/^(?:email|user_email)$/i', $field_id))
379 return $user->user_email;
380
381 else if(preg_match('/^(?:login|user_login)$/i', $field_id))
382 return $user->user_login;
383
384 else if(preg_match('/^(?:s2member_)?registration_time$/i', $field_id))
385 return $user->user_registered;
386
387 else if(strcasecmp($field_id, 's2member_access_role') === 0)
388 return c_ws_plugin__s2member_user_access::user_access_role($user);
389
390 else if(strcasecmp($field_id, 's2member_access_level') === 0)
391 return c_ws_plugin__s2member_user_access::user_access_level($user);
392
393 else if(strcasecmp($field_id, 's2member_access_label') === 0)
394 return c_ws_plugin__s2member_user_access::user_access_label($user);
395
396 else if(strcasecmp($field_id, 's2member_access_ccaps') === 0)
397 return c_ws_plugin__s2member_user_access::user_access_ccaps($user);
398
399 else if(strcasecmp($field_id, 'ip') === 0 && !empty($current_user->ID) && $current_user->ID === $user_id)
400 return c_ws_plugin__s2member_utils_ip::current(); // Current IP address.
401
402 else if(strcasecmp($field_id, 's2member_registration_ip') === 0 || strcasecmp($field_id, 'reg_ip') === 0 || strcasecmp($field_id, 'ip') === 0)
403 return get_user_option('s2member_registration_ip', $user_id);
404
405 else if(strcasecmp($field_id, 's2member_subscr_or_wp_id') === 0)
406 return ($subscr_id = get_user_option('s2member_subscr_id', $user_id)) ? $subscr_id : $user_id;
407
408 else if(strcasecmp($field_id, 'avatar') === 0) // Avatar with a specific size?
409 return get_avatar($user_id, !empty($args['size']) ? $args['size'] : 96);
410
411 else if(is_array($fields = get_user_option('s2member_custom_fields', $user_id)))
412 {
413 $field_var = preg_replace('/[^a-z0-9]/i', '_', strtolower($field_id));
414 if(isset($fields[$field_var])) return $fields[$field_var];
415 }
416 }
417 return FALSE; // Otherwise, return false.
418 }
419
420 /**
421 * Auto EOT time, else NPR (next payment time).
422 *
423 * @package s2Member\Utilities
424 * @since 150713
425 *
426 * @param int|string $user_id Optional. Defaults to the current User's ID.
427 * @param bool $check_gateway Defaults to a true value. If this is false, it is only possible to return a fixed EOT time.
428 * In other words, if this is false and there is no EOT time, empty values will be returned. Be careful with this, because not checking
429 * the payment gateway can result in an inaccurate return value. Only set to false if you want to limit the check to a fixed hard-coded EOT time.
430 * @param string $favor Defaults to a value of `fixed`; i.e., if a fixed EOT time is available, that is returned in favor of a next payment time.
431 * You can set this to `next` if you'd like to favor a next payment time (when applicable) instead of returning a fixed EOT time.
432 *
433 * @return array An associative array of EOT details; with the following elements.
434 *
435 * - `type` One of `fixed` (a fixed EOT time), `next` (next payment time; i.e., an ongoing recurring subscription); or an empty string if there is no EOT for the user.
436 * - `time` The timestamp (UTC time) that represents the EOT (End Of Term); else `0` if there is no EOT time.
437 * - `tense` If time is now (or earlier) this will be `past`. If time is in the future, this will be `future`. If there is no time, this is an empty string.
438 * - `debug` A string of details that explain to a developer what was returned. For debugging only.
439 */
440 public static function get_user_eot($user_id = 0, $check_gateway = TRUE, $favor = 'fixed')
441 {
442 if(!($user_id = (integer)$user_id)) // Empty user ID in this call?
443 $user_id = get_current_user_id(); // Assume current user.
444
445 if(!$favor || !in_array($favor, array('fixed', 'next'), TRUE))
446 $favor = 'fixed'; // Default behavior.
447
448 $now = time(); // Current timestamp.
449 $grace_time = (integer)$GLOBALS['WS_PLUGIN__']['s2member']['o']['eot_grace_time'];
450 $grace_time = (integer)apply_filters('ws_plugin__s2member_eot_grace_time', $grace_time);
451 $demotion_role = c_ws_plugin__s2member_option_forces::force_demotion_role('subscriber');
452 $empty_response = array('type' => '', 'time' => 0, 'tense' => '', 'debug' => '');
453
454 if(!$user_id || !($user = new WP_User($user_id)) || !$user->ID)
455 return array_merge($empty_response, array('debug' => 'Invalid user ID.'));
456
457 $ipn_signup_vars = self::get_user_ipn_signup_vars($user->ID);
458 $subscr_gateway = (string)get_user_option('s2member_subscr_gateway', $user->ID);
459 $subscr_id = (string)get_user_option('s2member_subscr_id', $user->ID);
460 $subscr_cid = (string)get_user_option('s2member_subscr_cid', $user->ID);
461 $last_auto_eot_time = (integer)get_user_option('s2member_last_auto_eot_time', $user->ID);
462 $auto_eot_time = (integer)get_user_option('s2member_auto_eot_time', $user->ID);
463
464 if($auto_eot_time) // They have a hard-coded EOT time at present?
465 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
466 'debug' => 'This is a fixed EOT time recorded by s2Member. It can be altered in the WordPress Dashboard for this user.');
467
468 if(!$subscr_gateway && !$subscr_id && !$subscr_cid && $last_auto_eot_time // EOTd?
469 && (!user_can($user->ID, 'access_s2member_level1') || c_ws_plugin__s2member_user_access::user_access_role($user) === $demotion_role)
470 && !c_ws_plugin__s2member_user_access::user_access_ccaps($user) // And no CCAPs either?
471 ) return array('type' => 'fixed', 'time' => $last_auto_eot_time, 'tense' => $last_auto_eot_time <= $now ? 'past' : 'future',
472 'debug' => 'This is an archived/fixed EOT time recorded by s2Member; i.e., the date this customer\'s access expired.');
473
474 if(!$subscr_gateway || !$subscr_id || !is_array($ipn_signup_vars) || !$ipn_signup_vars)
475 return array_merge($empty_response, array('debug' => 'This user has no subscription; i.e., missing `subscr_id`, `subscr_gateway` or `ipn_signup_vars`.'));
476
477 if(empty($ipn_signup_vars['txn_type']) || $ipn_signup_vars['txn_type'] !== 'subscr_signup')
478 return array_merge($empty_response, array('debug' => 'This user has no subscription; i.e., `txn_type` != `subscr_signup`.'));
479
480 $auto_eot_time // Update this now; i.e., build a new EOT time based on IPN signup vars.
481 = c_ws_plugin__s2member_utils_time::auto_eot_time($user->ID, $ipn_signup_vars['period1'], $ipn_signup_vars['period3']);
482
483 if($check_gateway) switch($subscr_gateway) // A bit different for each payment gateway.
484 {
485 case 'paypal': // PayPal (legacy Pro NVP/Payflow + PayPal Checkout REST).
486
487 //260213 PayPal Checkout subscriptions: use REST Subscriptions API for reconciliation (NVP GetRecurringPaymentsProfileDetails returns 11592).
488 if(!empty($ipn_signup_vars['s2member_paypal_proxy_use']) && $ipn_signup_vars['s2member_paypal_proxy_use'] === 'paypal_checkout')
489 {
490 $ppco_enabled = !empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_checkout_enable']) && (string)$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_checkout_enable'] !== '0';
491
492 $ppco_creds = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_creds();
493
494 if(!$ppco_enabled || empty($ppco_creds['client_id']) || empty($ppco_creds['secret']))
495 return array_merge($empty_response, array(
496 'debug' => 'PayPal Checkout subscription reconciliation skipped (disabled or missing REST API credentials).',
497 ));
498
499 $subscription_r = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_api_request('GET', '/v1/billing/subscriptions/'.rawurlencode($subscr_id));
500
501 $subscription_code = !empty($subscription_r['code']) ? (int)$subscription_r['code'] : 0;
502 $subscription_body = !empty($subscription_r['body']) ? (string)$subscription_r['body'] : '';
503
504 $subscription = array();
505 if($subscription_body)
506 $subscription = json_decode($subscription_body, true);
507
508 if(!is_array($subscription))
509 $subscription = array();
510
511 if(!($subscription_code >= 200 && $subscription_code <= 299 && !empty($subscription['id'])))
512 return array_merge($empty_response, array(
513 'debug' => 'PayPal Checkout subscription reconciliation failed (REST subscription lookup unsuccessful); skipping legacy PayPal status checks.',
514 ));
515
516 $status = !empty($subscription['status']) ? strtoupper((string)$subscription['status']) : '';
517 $next = !empty($subscription['billing_info']['next_billing_time']) ? (string)$subscription['billing_info']['next_billing_time'] : '';
518
519 if($status && $status !== 'ACTIVE')
520 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
521 'debug' => 'This is the estimated EOT time. PayPal Checkout says this subscription is no longer active, and thus, access should be terminated at this time.');
522
523 if($next && ($time = strtotime($next)) > $now)
524 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
525 'debug' => 'PayPal Checkout says this is the next payment time.');
526
527 return array_merge($empty_response, array(
528 'debug' => 'PayPal Checkout says this subscription is active; no next billing time was returned.',
529 ));
530 }
531
532 if(!c_ws_plugin__s2member_utils_conds::pro_is_installed()
533 || !class_exists('c_ws_plugin__s2member_pro_paypal_utilities')
534 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_username']
535 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_password']
536 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_signature']
537 ) return array_merge($empty_response, array('debug' => 'PayPal Pro API credentials missing in your s2Member configuration.'));
538
539 if($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_payflow_api_username'])
540 {
541 if(!($api_response = c_ws_plugin__s2member_pro_paypal_utilities::payflow_get_profile($subscr_id)) || !empty($api_response['__error']))
542 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the PayPal Pro API says there is no subscription for this user.'));
543
544 if(preg_match('/^(?:Pending|PendingProfile)$/i', $api_response['STATUS']))
545 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the PayPal Pro API says the subscription for this user is currently pending changes. Unable to determine at this moment. Please try again in 15 minutes.'));
546
547 if(!preg_match('/^(?:Active|ActiveProfile)$/i', $api_response['STATUS']))
548 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
549 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription is no longer active, and thus, access should be terminated at this time.');
550
551 if($api_response['TERM'] > 0 && $api_response['PAYMENTSLEFT'] <= 0)
552 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
553 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription has reached its last payment, and thus, access should be terminated at this time.');
554
555 if($api_response['TERM'] <= 0 || $api_response['PAYMENTSLEFT'] > 0)
556 if($api_response['NEXTPAYMENT'] && strlen($api_response['NEXTPAYMENT']) === 8) // MMDDYYYY format is not `strtotime()` compatible.
557 if(($time = strtotime(substr($api_response['NEXTPAYMENT'], -4).'-'.substr($api_response['NEXTPAYMENT'], 0, 2).'-'.substr($api_response['NEXTPAYMENT'], 2, 2))) > $now)
558 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
559 'debug' => 'The PayPal Pro API says this is the next payment time.');
560 }
561 else // Use PayPal Pro API (old flavor).
562 {
563 $api_args = array(
564 'METHOD' => 'GetRecurringPaymentsProfileDetails',
565 'PROFILEID' => $subscr_id,
566 );
567 if(!($api_response = c_ws_plugin__s2member_paypal_utilities::paypal_api_response($api_args)) || !empty($api_response['__error']))
568 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the PayPal Pro API says there is no subscription for this user.'));
569
570 if(preg_match('/^(?:Pending|PendingProfile)$/i', $api_response['STATUS']))
571 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the PayPal Pro API says the subscription for this user is currently pending changes. Unable to determine at this moment. Please try again in 15 minutes.'));
572
573 if(!preg_match('/^(?:Active|ActiveProfile)$/i', $api_response['STATUS']))
574 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
575 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription is no longer active, and thus, access should be terminated at this time.');
576
577 if($api_response['TOTALBILLINGCYCLES'] > 0 && $api_response['NUMCYCLESREMAINING'] <= 0)
578 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
579 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription has reached its last payment, and thus, access should be terminated at this time.');
580
581 if($api_response['TOTALBILLINGCYCLES'] <= 0 || $api_response['NUMCYCLESREMAINING'] > 0)
582 if($api_response['NEXTBILLINGDATE'] && ($time = strtotime($api_response['NEXTBILLINGDATE'])) > $now)
583 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
584 'debug' => 'The PayPal Pro API says this is the next payment time.');
585 }
586 return array_merge($empty_response, array('debug' => 'No fixed EOT, and there are no more payments needed from this user.'));
587
588 break; // Break switch.
589
590 case 'authnet': // Authorize.Net (EOT only; w/ limited functionality).
591
592 if(!c_ws_plugin__s2member_utils_conds::pro_is_installed()
593 || !class_exists('c_ws_plugin__s2member_pro_authnet_utilities')
594 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_authnet_api_login_id']
595 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_authnet_api_trans_key']
596 ) return array_merge($empty_response, array('debug' => 'Authorize.Net API credentials missing in your s2Member configuration.'));
597
598 $api_args = array(
599 'x_method' => 'status',
600 'x_subscription_id' => $subscr_id,
601 );
602 if(!($api_response = c_ws_plugin__s2member_pro_authnet_utilities::authnet_arb_response($api_args)) || !empty($api_response['__error']))
603 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the Authorize.Net API says there is no subscription for this user.'));
604
605 if(!preg_match('/^(?:active)$/i', $api_response['subscription_status']))
606 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
607 'debug' => 'This is the estimated EOT time. The Authorize.Net API says this subscription is no longer active, and thus, access should be terminated at this time.');
608
609 // Next payment time not possible with Authorize.Net at this time.
610 // Fixed recurring intervals not possible to query with Authorize.Net at this time.
611 return array_merge($empty_response, array('debug' => 'Partially-supported payment gateway; unable to determine.'));
612
613 break; // Break switch.
614
615 case 'stripe': // Stripe payment gateway (best).
616
617 if(!c_ws_plugin__s2member_utils_conds::pro_is_installed()
618 || !class_exists('c_ws_plugin__s2member_pro_stripe_utilities')
619 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_stripe_api_publishable_key']
620 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_stripe_api_secret_key']
621 ) return array_merge($empty_response, array('debug' => 'Stripe API credentials missing in your s2Member configuration.'));
622
623 if(!$subscr_cid) return array_merge($empty_response, array('debug' => 'No fixed EOT, and no `subscr_cid` on file. Unable to determine.'));
624
625 if(!is_object($stripe_subscription = c_ws_plugin__s2member_pro_stripe_utilities::get_customer_subscription($subscr_cid, $subscr_id)) || empty($stripe_subscription->id))
626 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the Stripe API says there is no subscription for this user.'));
627
628 if((integer)$stripe_subscription->ended_at > 0) // Done?
629 {
630 $time = $stripe_subscription->ended_at + $grace_time;
631 return array('type' => 'fixed', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
632 'debug' => 'The Stripe API says this subscription reached an expiration on this date + grace time.');
633 }
634 if(in_array($stripe_subscription->status, array('canceled', 'unpaid'), TRUE) || $stripe_subscription->cancel_at_period_end)
635 {
636 $time = $stripe_subscription->current_period_end + $grace_time;
637 return array('type' => 'fixed', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
638 'debug' => 'The Stripe API says this subscription was cancelled, and thus, should EOT on this date + grace time.');
639 }
640 if(isset($stripe_subscription->plan->metadata->recurring, $stripe_subscription->plan->metadata->recurring_times)
641 && !$stripe_subscription->plan->metadata->recurring) // Non-recurring subscription?
642 {
643 $time = (integer)$stripe_subscription->start;
644 $time += $stripe_subscription->plan->trial_period_days * DAY_IN_SECONDS;
645
646 switch($stripe_subscription->plan->interval)
647 {
648 case 'day': // Every X days in this case.
649 $time += (DAY_IN_SECONDS * $stripe_subscription->plan->interval_count) * 1;
650 break; // Break switch now.
651
652 case 'week': // Every X weeks in this case.
653 $time += (WEEK_IN_SECONDS * $stripe_subscription->plan->interval_count) * 1;
654 break; // Break switch now.
655
656 case 'month': // Every X months in this case.
657 $time += ((WEEK_IN_SECONDS * 4) * $stripe_subscription->plan->interval_count) * 1;
658 break; // Break switch now.
659
660 case 'year': // Every X years in this case.
661 $time += (YEAR_IN_SECONDS * $stripe_subscription->plan->interval_count) * 1;
662 break; // Break switch now.
663 }
664 if($favor === 'next' && $stripe_subscription->current_period_end + 1 < $time)
665 {
666 if($stripe_subscription->current_period_end + 1 > $now)
667 {
668 $time = $stripe_subscription->current_period_end + 1;
669 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
670 'debug' => 'The Stripe API says this is the next payment time.');
671 }
672 return array_merge($empty_response, array('debug' => 'Stripe says no more payments needed from this user.'));
673 }
674 $time += $grace_time; // Now add grace to the final EOT time.
675 return array('type' => 'fixed', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
676 'debug' => 'The Stripe API says this subscription will be completely over on this date + grace time.');
677 }
678 if(isset($stripe_subscription->plan->metadata->recurring, $stripe_subscription->plan->metadata->recurring_times)
679 && $stripe_subscription->plan->metadata->recurring && $stripe_subscription->plan->metadata->recurring_times <= 0)
680 {
681 if($stripe_subscription->current_period_end + 1 > $now)
682 {
683 $time = $stripe_subscription->current_period_end + 1;
684 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
685 'debug' => 'The Stripe API says this is the next payment time.');
686 }
687 return array_merge($empty_response, array('debug' => 'Stripe says no more payments needed from this user.'));
688 }
689 if(isset($stripe_subscription->plan->metadata->recurring, $stripe_subscription->plan->metadata->recurring_times)
690 && $stripe_subscription->plan->metadata->recurring && $stripe_subscription->plan->metadata->recurring_times > 0)
691 {
692 $time = (integer)$stripe_subscription->start;
693 $time += $stripe_subscription->plan->trial_period_days * DAY_IN_SECONDS;
694
695 switch($stripe_subscription->plan->interval)
696 {
697 case 'day': // Every X days in this case.
698 $time += (DAY_IN_SECONDS * $stripe_subscription->plan->interval_count)
699 * $stripe_subscription->plan->metadata->recurring_times;
700 break; // Break switch now.
701
702 case 'week': // Every X weeks in this case.
703 $time += (WEEK_IN_SECONDS * $stripe_subscription->plan->interval_count)
704 * $stripe_subscription->plan->metadata->recurring_times;
705 break; // Break switch now.
706
707 case 'month': // Every X months in this case.
708 $time += ((WEEK_IN_SECONDS * 4) * $stripe_subscription->plan->interval_count)
709 * $stripe_subscription->plan->metadata->recurring_times;
710 break; // Break switch now.
711
712 case 'year': // Every X years in this case.
713 $time += (YEAR_IN_SECONDS * $stripe_subscription->plan->interval_count)
714 * $stripe_subscription->plan->metadata->recurring_times;
715 break; // Break switch now.
716 }
717 if($favor === 'next' && $stripe_subscription->current_period_end + 1 < $time)
718 {
719 if($stripe_subscription->current_period_end + 1 > $now)
720 {
721 $time = $stripe_subscription->current_period_end + 1;
722 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
723 'debug' => 'The Stripe API says this is the next payment time.');
724 }
725 return array_merge($empty_response, array('debug' => 'Stripe says no more payments needed from this user.'));
726 }
727 $time += $grace_time; // Now add grace to the final EOT time.
728 return array('type' => 'fixed', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
729 'debug' => 'The Stripe API says this subscription will be completely over on this date + grace time.');
730 }
731 if($stripe_subscription->current_period_end + 1 > $now)
732 {
733 $time = $stripe_subscription->current_period_end + 1;
734 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
735 'debug' => 'The Stripe API says this is the next payment time.');
736 }
737 return array_merge($empty_response, array('debug' => 'No fixed EOT, and Stripe says there are no more payments needed from this user.'));
738
739 break; // Break switch.
740
741 case 'clickbank': // ClickBank (limited functionality).
742
743 if(!c_ws_plugin__s2member_utils_conds::pro_is_installed()
744 || !class_exists('c_ws_plugin__s2member_pro_clickbank_utilities')
745 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_clickbank_username']
746 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_clickbank_clerk_key']
747 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_clickbank_developer_key']
748 || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['pro_clickbank_secret_key']
749 ) return array_merge($empty_response, array('debug' => 'ClickBank API credentials missing in your s2Member configuration.'));
750
751 if(empty($ipn_signup_vars['txn_id'])) // ClickBank receipt number.
752 return array_merge($empty_response, array('debug' => 'No fixed EOT, and no `txn_id` on file. Unable to determine.'));
753
754 if(!($api_response = c_ws_plugin__s2member_pro_clickbank_utilities::clickbank_api_order($ipn_signup_vars['txn_id'])))
755 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the ClickBank API says there is no subscription for this user.'));
756
757 if(!preg_match('/^(?:TEST_)?SALE$/i', $api_response['txnType']) || !$api_response['recurring'])
758 return array_merge($empty_response, array('debug' => 'No fixed EOT, and the ClickBank API says there is no recurring subscription for this user.'));
759
760 if(strcasecmp($api_response['status'], 'active') !== 0 || $api_response['futurePayments'] <= 0)
761 return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future',
762 'debug' => 'This is the estimated EOT time. The ClickBank API says this subscription no longer active, or it has reached its last payment, and thus, access should be terminated at this time.');
763
764 if($api_response['nextPaymentDate'] && ($time = strtotime($api_response['nextPaymentDate'])) > $now)
765 return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future',
766 'debug' => 'The ClickBank API says this is the next payment time.');
767
768 return array_merge($empty_response, array('debug' => 'No fixed EOT, and there are no more payments needed from this user.'));
769
770 break; // Break switch.
771
772 default: // Default case handler.
773 return array_merge($empty_response, array('debug' => 'Partially-supported payment gateway; unable to determine.'));
774 }
775 return array_merge($empty_response, array('debug' => 'Payment gateway check disabled; unable to determine.'));
776 }
777 }
778 }
779