/app/model/class-ms-model-member.php

  1. <?php 
  2. /** 
  3. * Member model. 
  4. * 
  5. * Defines several details about a WordPress user. 
  6. * The Member object allows us to quickly check if the user did subscribe to a 
  7. * certain membership, and other useful stuff. 
  8. * 
  9. * Note that all properties are declared protected but they can be access 
  10. * directly (e.g. `$membership->type` to get the type value). 
  11. * There are magic methods \_\_get() and \_\_set() that do some validation before 
  12. * accessing the properties. 
  13. * 
  14. * @since 1.0.0 
  15. * @package Membership2 
  16. * @subpackage Model 
  17. */ 
  18. class MS_Model_Member extends MS_Model { 
  19.  
  20. /** 
  21. * Members search constants. 
  22. * 
  23. * @since 1.0.0 
  24. * @internal 
  25. */ 
  26. const SEARCH_ONLY_MEMBERS = 'only members'; //TODO: replace space with _ !!!! 
  27.  
  28. /** 
  29. * Members search constants. 
  30. * 
  31. * @since 1.0.0 
  32. * @internal 
  33. */ 
  34. const SEARCH_ALL_USERS = 'all_users'; 
  35.  
  36. /** 
  37. * Cache for function is_admin_user() 
  38. * 
  39. * @since 1.0.0 
  40. * @internal 
  41. * @var bool[] 
  42. */ 
  43. static protected $_is_admin_user = array(); 
  44.  
  45. /** 
  46. * Cache for function is_normal_admin() 
  47. * 
  48. * @since 1.0.0 
  49. * @internal 
  50. * @var bool[] 
  51. */ 
  52. static protected $_is_normal_admin = array(); 
  53.  
  54. /** 
  55. * Cache for function is_simulated_user() 
  56. * 
  57. * @since 1.0.0 
  58. * @internal 
  59. * @var bool[] 
  60. */ 
  61. static protected $_is_simulated_user = array(); 
  62.  
  63. /** 
  64. * Cache for function is_normal_user() 
  65. * 
  66. * @since 1.0.0 
  67. * @internal 
  68. * @var bool[] 
  69. */ 
  70. static protected $_is_normal_user = array(); 
  71.  
  72. /** 
  73. * Member's active subscriptions. 
  74. * 
  75. * Note: This field is populated by MS_Factory when the Member instance is 
  76. * created. 
  77. * 
  78. * @since 1.0.0 
  79. * @var array { 
  80. * @type int $membership_id The membership ID. 
  81. * @type MS_Model_Relationship The membership relationship model object. 
  82. * } 
  83. */ 
  84. protected $subscriptions = array(); 
  85.  
  86. /** 
  87. * Indicator if the user is an active M2 Member. 
  88. * 
  89. * This is a convenience/redudant flag to speed up SQL queries. 
  90. * Actually everyone that has an active or trial status membership is 
  91. * considered an active member. 
  92. * 
  93. * This flag is set when: 
  94. * - In MS_Model_Relationship, when a payment is recorded 
  95. * via add_payment() 
  96. * 
  97. * This flag is reset when: 
  98. * - In MS_Model_Relationship, when a subscription is deactivated 
  99. * via check_membership_status() 
  100. * 
  101. * @since 1.0.0 
  102. * @var boolean 
  103. */ 
  104. protected $is_member = false; 
  105.  
  106. /** 
  107. * Member's username. 
  108. * 
  109. * Mapped from wordpress $wp_user object. 
  110. * @see WP_User $user_login. 
  111. * 
  112. * @since 1.0.0 
  113. * @var string 
  114. */ 
  115. protected $username = ''; 
  116.  
  117. /** 
  118. * Member's email. 
  119. * 
  120. * Mapped from wordpress $wp_user object. 
  121. * @see WP_User $user_email. 
  122. * 
  123. * @since 1.0.0 
  124. * @var string 
  125. */ 
  126. protected $email = ''; 
  127.  
  128. /** 
  129. * Member's name. 
  130. * 
  131. * Mapped from wordpress $wp_user object. 
  132. * @see WP_User $user_nicename. 
  133. * 
  134. * @since 1.0.0 
  135. * @var string 
  136. */ 
  137. protected $name = ''; 
  138.  
  139. /** 
  140. * Member's first name. 
  141. * 
  142. * Mapped from wordpress $wp_user object. 
  143. * @see WP_User $first_name 
  144. * 
  145. * @since 1.0.0 
  146. * @var string 
  147. */ 
  148. protected $first_name = ''; 
  149.  
  150. /** 
  151. * Member's last name. 
  152. * 
  153. * Mapped from wordpress $wp_user object. 
  154. * @see WP_User $last_name. 
  155. * 
  156. * @since 1.0.0 
  157. * @var string 
  158. */ 
  159. protected $last_name = ''; 
  160.  
  161. /** 
  162. * Member's password. 
  163. * 
  164. * Used when registering. 
  165. * 
  166. * @since 1.0.0 
  167. * @internal 
  168. * @var string 
  169. */ 
  170. protected $password = ''; 
  171.  
  172. /** 
  173. * Member's password confirmation. 
  174. * 
  175. * Used when registering. 
  176. * 
  177. * @since 1.0.0 
  178. * @internal 
  179. * @var string 
  180. */ 
  181. protected $password2 = ''; 
  182.  
  183. /** 
  184. * Member's gateway profiles info. 
  185. * 
  186. * Save gateway IDs. 
  187. * 
  188. * @since 1.0.0 
  189. * @var array { 
  190. * Return structure: $gateway[ $field ] => $value; 
  191. * 
  192. * @type string $gateway_id The gateway id. 
  193. * @type string $field The field to store. 
  194. * @type mixed $value The field value to store. 
  195. * } 
  196. */ 
  197. protected $gateway_profiles = array(); 
  198.  
  199. /** 
  200. * The associated WP_User object 
  201. * 
  202. * @since 1.0.0 
  203. * @internal 
  204. * @var WP_User 
  205. */ 
  206. protected $wp_user = null; 
  207.  
  208. /** 
  209. * Custom data can be used by other plugins via the set_custom_data() and 
  210. * get_custom_data() functions. 
  211. * 
  212. * This can be used to store additional information on user-level, e.g. 
  213. * settings needed by some Add-ons or even by other plugins. 
  214. * 
  215. * @since 1.0.0 
  216. * 
  217. * @var array 
  218. */ 
  219. protected $custom_data = array(); 
  220.  
  221.  
  222. // 
  223. // 
  224. // 
  225. // -------------------------------------------------------------- COLLECTION 
  226.  
  227. /** 
  228. * Get current member. 
  229. * 
  230. * @since 1.0.0 
  231. * @internal 
  232. * 
  233. * @return MS_Model_Member The current member. 
  234. */ 
  235. static public function get_current_member() { 
  236. return MS_Factory::load( 'MS_Model_Member', get_current_user_id() ); 
  237.  
  238. /** 
  239. * Checks if user-signup is enabled for this site or not. 
  240. * 
  241. * @since 1.0.0 
  242. * @api 
  243. * 
  244. * @return bool 
  245. */ 
  246. static public function can_register() { 
  247. static $Signup_Allowed = null; 
  248.  
  249. if ( null === $Signup_Allowed ) { 
  250. $Signup_Allowed = false; 
  251.  
  252. if ( is_multisite() ) { 
  253. $reg_option = get_site_option( 'registration', 'none' ); 
  254. if ( in_array( $reg_option, array( 'all', 'user' ) ) ) { 
  255. $Signup_Allowed = true; 
  256. } else { 
  257. if ( get_option( 'users_can_register' ) ) { 
  258. $Signup_Allowed = true; 
  259.  
  260. return apply_filters( 
  261. 'ms_member_can_register',  
  262. $Signup_Allowed 
  263. ); 
  264.  
  265. /** 
  266. * Allows users to register for this site. 
  267. * 
  268. * @since 1.0.0 
  269. * @api 
  270. * 
  271. * @return bool 
  272. */ 
  273. static public function allow_registration() { 
  274. if ( self::can_register() ) { return; } 
  275.  
  276. if ( is_multisite() ) { 
  277. $reg_option = get_site_option( 'registration', 'none' ); 
  278. if ( 'blog' == $reg_option ) { 
  279. // Creation of new blogs is allowd. Add User-Registration. 
  280. update_site_option( 'registration', 'all' ); 
  281. } else { 
  282. // Only enable user registration and keep blogs disabled. 
  283. update_site_option( 'registration', 'user' ); 
  284. } else { 
  285. // Simply enable registration on single sites. 
  286. update_option( 'users_can_register', true ); 
  287.  
  288. /** 
  289. * Get members total count. 
  290. * 
  291. * @since 1.0.0 
  292. * @internal 
  293. * 
  294. * @param $args The query user args 
  295. * @see @link http://codex.wordpress.org/Class_Reference/WP_User_Query 
  296. * @return int The count. 
  297. */ 
  298. public static function get_members_count( $args = null ) { 
  299. $args = self::get_query_args( $args, self::SEARCH_ALL_USERS ); 
  300. $args['number'] = 0; 
  301. $args['count_total'] = true; 
  302. $wp_user_search = new WP_User_Query( $args ); 
  303.  
  304. return apply_filters( 
  305. 'ms_model_member_get_members_count',  
  306. $wp_user_search->get_total() 
  307. ); 
  308.  
  309. /** 
  310. * Get members IDs. 
  311. * The IDs are cached and only fetched once for each set of $args. 
  312. * 
  313. * @since 1.0.0 
  314. * @internal 
  315. * 
  316. * @param $args The query user args 
  317. * @see @link http://codex.wordpress.org/Class_Reference/WP_User_Query 
  318. * @return array List of member IDs 
  319. */ 
  320. public static function get_member_ids( $args = null, $search_option = self::SEARCH_ALL_USERS ) { 
  321. static $Members = array(); 
  322. if ( ! isset( $args['number'] ) ) { 
  323. $args['number'] = 0; 
  324.  
  325. $key = json_encode( $args ); 
  326.  
  327. if ( ! isset( $Members[$key] ) ) { 
  328. $args = self::get_query_args( $args, $search_option ); 
  329. $wp_user_search = new WP_User_Query( $args ); 
  330. $users = $wp_user_search->get_results(); 
  331. $members = array(); 
  332.  
  333. foreach ( $users as $user_id ) { 
  334. $members[] = $user_id; 
  335.  
  336. $Members[$key] = apply_filters( 
  337. 'ms_model_member_get_member_ids',  
  338. $members,  
  339. $args 
  340. ); 
  341.  
  342. return $Members[$key]; 
  343.  
  344. /** 
  345. * Get members. 
  346. * 
  347. * @since 1.0.0 
  348. * @internal 
  349. * 
  350. * @param $args The query user args 
  351. * @see @link http://codex.wordpress.org/Class_Reference/WP_User_Query 
  352. * @return MS_Model_Member[] The selected members. 
  353. */ 
  354. public static function get_members( $args = null, $search_option = self::SEARCH_ALL_USERS ) { 
  355. $members = array(); 
  356. $ids = self::get_member_ids( $args, $search_option ); 
  357.  
  358. foreach ( $ids as $user_id ) { 
  359. $members[] = MS_Factory::load( 'MS_Model_Member', $user_id ); 
  360.  
  361. return apply_filters( 
  362. 'ms_model_member_get_members',  
  363. $members,  
  364. $ids,  
  365. $args 
  366. ); 
  367.  
  368. /** 
  369. * Get usernames. 
  370. * 
  371. * @since 1.0.0 
  372. * @internal 
  373. * 
  374. * @param $args The query user args 
  375. * @see @link http://codex.wordpress.org/Class_Reference/WP_User_Query 
  376. * @param string $search_option The search options (only members, not members, all users). 
  377. * @return array { 
  378. * @type int $id The user_id. 
  379. * @type string $username The username. 
  380. * } 
  381. */ 
  382. public static function get_usernames( $args = null, $search_option = self::SEARCH_ONLY_MEMBERS, $return_array = true ) { 
  383. $members = array(); 
  384.  
  385. if ( $return_array ) { 
  386. $members[0] = __( 'Select a user', 'membership2' ); 
  387.  
  388. $args['fields'] = array( 'ID', 'user_login' ); 
  389. $args['number'] = 0; 
  390. $args = self::get_query_args( $args, $search_option ); 
  391. $wp_user_search = new WP_User_Query( $args ); 
  392. $users = $wp_user_search->get_results(); 
  393.  
  394. foreach ( $users as $user ) { 
  395. if ( ! self::is_admin_user( $user->ID ) ) { 
  396. if ( $return_array ) { 
  397. $members[ $user->ID ] = $user->user_login; 
  398. } else { 
  399. $members[] = array( 
  400. 'id' => $user->ID,  
  401. 'text' => $user->user_login,  
  402. ); 
  403.  
  404. return apply_filters( 
  405. 'ms_model_member_get_members_usernames',  
  406. $members,  
  407. $return_array 
  408. ); 
  409.  
  410. /** 
  411. * Get WP_Query object arguments. 
  412. * 
  413. * Default search arguments for this model. 
  414. * 
  415. * @since 1.0.0 
  416. * @internal 
  417. * 
  418. * @param $args The query user args 
  419. * @see @link http://codex.wordpress.org/Class_Reference/WP_User_Query 
  420. * @param string $search_option The search options (only members, not members, all users). 
  421. * @return array $args The parsed args. 
  422. */ 
  423. public static function get_query_args( $args = null, $search_option = self::SEARCH_ONLY_MEMBERS ) { 
  424. global $wpdb; 
  425.  
  426. $defaults = apply_filters( 
  427. 'ms_model_member_get_query_args_defaults',  
  428. array( 
  429. 'order' => 'DESC',  
  430. 'orderby' => 'ID',  
  431. 'number' => 20,  
  432. 'offset' => 0,  
  433. 'fields' => 'ID',  
  434. ); 
  435.  
  436. $args = lib3()->array->get( $args ); 
  437. lib3()->array->equip( $args, 'meta_query', 'membership_id', 'subscription_status' ); 
  438.  
  439. if ( 'none' !== $args['meta_query'] ) { 
  440. $args['meta_query'] = lib3()->array->get( $args['meta_query'] ); 
  441.  
  442. switch ( $search_option ) { 
  443. case self::SEARCH_ONLY_MEMBERS: 
  444. $args['meta_query'] = array( 
  445. array( 
  446. 'key' => 'ms_is_member',  
  447. 'value' => true,  
  448. ),  
  449. ); 
  450. break; 
  451.  
  452. case self::SEARCH_ALL_USERS: 
  453. default: 
  454. break; 
  455. } else { 
  456. unset( $args['meta_query'] ); 
  457.  
  458. // For performance reasons we execute a custom SQL to get relevant user_ids. 
  459. if ( ! empty( $args['membership_id'] ) 
  460. || ! empty( $args['subscription_status'] ) 
  461. && 'all' != $args['subscription_status'] 
  462. ) { 
  463. $membership_id = intval( $args['membership_id'] ); 
  464. $status = $args['subscription_status']; 
  465.  
  466. switch ( $status ) { 
  467. case 'expired': 
  468. $status_val = implode( 
  469. ', ',  
  470. array( 
  471. "'" . MS_Model_Relationship::STATUS_TRIAL_EXPIRED . "'",  
  472. "'" . MS_Model_Relationship::STATUS_EXPIRED . "'",  
  473. ); 
  474. break; 
  475.  
  476. default: 
  477. $status_val = $wpdb->prepare( " '%s' ", $status ); 
  478. break; 
  479.  
  480. $sql = " 
  481. SELECT DISTINCT usr.meta_value 
  482. FROM {$wpdb->posts} p 
  483. INNER JOIN {$wpdb->postmeta} mem ON mem.post_id=p.ID AND mem.meta_key='membership_id' 
  484. INNER JOIN {$wpdb->postmeta} sta ON sta.post_id=p.ID AND sta.meta_key='status' 
  485. INNER JOIN {$wpdb->postmeta} usr ON usr.post_id=p.ID AND usr.meta_key='user_id' 
  486. WHERE 
  487. p.post_type = %s 
  488. AND ('0' = %s OR mem.meta_value = %s) 
  489. AND ('' = %s OR sta.meta_value IN ({$status_val})) 
  490. "; 
  491. $sql = $wpdb->prepare( 
  492. $sql,  
  493. MS_Model_Relationship::get_post_type(),  
  494. $membership_id,  
  495. $membership_id,  
  496. $status 
  497. ); 
  498. $ids = $wpdb->get_col( $sql ); 
  499. if ( empty( $ids ) || ! is_array( $ids ) ) { $ids = array( 0 ); } 
  500. $args['include'] = $ids; 
  501.  
  502. if ( MS_Plugin::is_network_wide() ) { 
  503. $defaults['blog_id'] = false; 
  504.  
  505. $args = wp_parse_args( $args, $defaults ); 
  506.  
  507. return apply_filters( 
  508. 'ms_model_member_get_query_args',  
  509. $args,  
  510. $defaults 
  511. ); 
  512.  
  513. /** 
  514. * Returns the current user ID. 
  515. * This function can be called before the init action hook. 
  516. * 
  517. * Much of this logic is taken from wp-includes/pluggable.php 
  518. * 
  519. * @since 1.0.0 
  520. * @internal 
  521. * @return int|false 
  522. */ 
  523. public static function get_user_id() { 
  524. static $User_id = false; 
  525.  
  526. if ( $User_id ) { 
  527. // We already found the user-id, no need to do it again. 
  528. return $User_id; 
  529.  
  530. if ( defined( 'DOING_CRON' ) && DOING_CRON ) { 
  531. // A cron request has no user credentials... 
  532. return 0; 
  533.  
  534. $cookie = wp_parse_auth_cookie(); 
  535.  
  536. if ( ! $cookie ) { 
  537. // Missing, expired or corrupt cookie. 
  538. return 0; 
  539.  
  540. $scheme = $cookie['scheme']; 
  541. $username = $cookie['username']; 
  542. $hmac = $cookie['hmac']; 
  543. $token = $cookie['token']; 
  544. $expiration = $cookie['expiration']; 
  545.  
  546. $user = get_user_by( 'login', $username ); 
  547.  
  548. if ( ! $user ) { 
  549. // Invalid username. 
  550. return 0; 
  551.  
  552. $pass_frag = substr( $user->user_pass, 8, 4 ); 
  553. $key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme ); 
  554. $algo = function_exists( 'hash' ) ? 'sha256' : 'sha1'; 
  555. $hash = hash_hmac( $algo, $username . '|' . $expiration . '|' . $token, $key ); 
  556.  
  557. if ( ! hash_equals( $hash, $hmac ) ) { 
  558. // Forged/expired cookie value. 
  559. return 0; 
  560.  
  561. // Remember the user-ID so we don't have to validate everything again. 
  562. $User_id = $user->ID; 
  563.  
  564. return $User_id; 
  565.  
  566. /** 
  567. * Verify is user is logged in. 
  568. * 
  569. * @since 1.0.0 
  570. * @internal 
  571. * 
  572. * @return boolean True if user is logged in. 
  573. */ 
  574. public static function is_logged_in() { 
  575. $logged = is_user_logged_in(); 
  576.  
  577. return apply_filters( 'ms_member_is_logged_in', $logged ); 
  578.  
  579. /** 
  580. * Verify is user is Admin user. 
  581. * 
  582. * @since 1.0.0 
  583. * @api 
  584. * 
  585. * @param int|false $user_id Optional. The user ID. Default to current user. 
  586. * @param bool $deprecated Do not use. 
  587. * @return boolean True if user is admin. 
  588. */ 
  589. static public function is_admin_user( $user_id = false ) { 
  590. $cache_result = true; 
  591.  
  592. if ( ! isset( self::$_is_admin_user[ $user_id ] ) ) { 
  593. $is_admin = false; 
  594. $default_user_id = null; 
  595.  
  596. if ( empty( $user_id ) ) { 
  597. $default_user_id = $user_id; 
  598. $user_id = self::get_user_id(); 
  599.  
  600. if ( is_super_admin( $user_id ) ) { 
  601. // Superadmin always is considered admin user, no discussion... 
  602. $is_admin = true; 
  603. } else { 
  604. /** 
  605. * Use the capability defined by the main plugin controller. 
  606. * 
  607. * This capability defines which user is considered admin user. 
  608. * An Admin user has full permissions to edit M2 settings. 
  609. * 
  610. * To modify the capability: 
  611. * Use filter `ms_admin_user_capability` or 
  612. * define( 'MS_ADMIN_CAPABILITY', '...' ) 
  613. * 
  614. * @var string|bool A WordPress capability or boolean false. 
  615. */ 
  616. $controller = MS_Plugin::instance()->controller; 
  617. if ( $controller ) { 
  618. $capability = $controller->capability; 
  619. } else { 
  620. // This is used in case the function is called too early. 
  621. $capability = 'manage_options'; 
  622. $cache_result = false; 
  623.  
  624. if ( ! empty( $capability ) ) { 
  625. if ( empty( $user_id ) ) { 
  626. $is_admin = current_user_can( $capability ); 
  627. } else { 
  628. $is_admin = user_can( $user_id, $capability ); 
  629.  
  630. $is_admin = apply_filters( 
  631. 'ms_model_member_is_admin_user',  
  632. $is_admin,  
  633. $user_id,  
  634. $capability 
  635. ); 
  636.  
  637. if ( $cache_result ) { 
  638. self::$_is_admin_user[ $user_id ] = $is_admin; 
  639.  
  640. if ( null !== $default_user_id ) { 
  641. self::$_is_admin_user[ $default_user_id ] = $is_admin; 
  642. } else { 
  643. $is_admin = self::$_is_admin_user[ $user_id ]; 
  644.  
  645. return $is_admin; 
  646.  
  647. /** 
  648. * Verify is user is Admin user and simulation mode is deactivated. 
  649. * 
  650. * @since 1.0.0 
  651. * @api 
  652. * 
  653. * @param int|false $user_id Optional. The user ID. Default to current user. 
  654. * @return boolean 
  655. */ 
  656. static public function is_normal_admin( $user_id = false ) { 
  657. if ( ! isset( self::$_is_normal_admin[$user_id] ) ) { 
  658. $res = self::is_admin_user( $user_id ) 
  659. && ! MS_Factory::load( 'MS_Model_Simulate' )->is_simulating(); 
  660. self::$_is_normal_admin[$user_id] = $res; 
  661.  
  662. if ( empty( $user_id ) ) { 
  663. self::$_is_normal_admin[ get_current_user_id() ] = $res; 
  664.  
  665. return self::$_is_normal_admin[$user_id]; 
  666.  
  667. /** 
  668. * Verify is user is Admin user and simulation mode is active. 
  669. * 
  670. * @since 1.0.0 
  671. * @api 
  672. * 
  673. * @param int|false $user_id Optional. The user ID. Default to current user. 
  674. * @return boolean 
  675. */ 
  676. static public function is_simulated_user( $user_id = false ) { 
  677. if ( ! isset( self::$_is_simulated_user[$user_id] ) ) { 
  678. $res = self::is_admin_user( $user_id ) 
  679. && MS_Factory::load( 'MS_Model_Simulate' )->is_simulating(); 
  680. self::$_is_simulated_user[$user_id] = $res; 
  681.  
  682. if ( empty( $user_id ) ) { 
  683. self::$_is_simulated_user[ get_current_user_id() ] = $res; 
  684.  
  685. return self::$_is_simulated_user[$user_id]; 
  686.  
  687. /** 
  688. * Verify is user is not Admin user and simulation mode is deactivated. 
  689. * 
  690. * @since 1.0.0 
  691. * @api 
  692. * 
  693. * @param int|false $user_id Optional. The user ID. Default to current user. 
  694. * @return boolean 
  695. */ 
  696. static public function is_normal_user( $user_id = false ) { 
  697. if ( ! isset( self::$_is_normal_user[$user_id] ) ) { 
  698. // Simlation is only activated when the current user is an Admin. 
  699. $res = ! self::is_admin_user( $user_id ); 
  700. self::$_is_normal_user[$user_id] = $res; 
  701.  
  702. if ( empty( $user_id ) ) { 
  703. self::$_is_normal_user[ get_current_user_id() ] = $res; 
  704.  
  705. return self::$_is_normal_user[$user_id]; 
  706.  
  707. /** 
  708. * Get email addresses of all admin users. 
  709. * 
  710. * @since 1.0.0 
  711. * @internal 
  712. * 
  713. * @return string[] The admin emails. 
  714. */ 
  715. public static function get_admin_user_emails() { 
  716. $admins = array(); 
  717.  
  718. $args = array( 
  719. 'role' => 'administrator',  
  720. 'fields' => array( 'ID', 'user_email' ),  
  721. ); 
  722.  
  723. $wp_user_search = new WP_User_Query( $args ); 
  724. $users = $wp_user_search->get_results(); 
  725.  
  726. if ( ! empty ($users ) ) { 
  727. foreach ( $users as $user ) { 
  728. $admins[ $user->user_email ] = $user->user_email; 
  729.  
  730. return apply_filters( 
  731. 'ms_model_member_get_admin_user_emails',  
  732. $admins 
  733. ); 
  734.  
  735. /** 
  736. * Get username from user_id. 
  737. * 
  738. * @since 1.0.0 
  739. * @api 
  740. * 
  741. * @param int $user_id The user ID to get username. 
  742. * @return string The username. 
  743. */ 
  744. public static function get_username( $user_id ) { 
  745. $member = MS_Factory::load( 'MS_Model_Member', $user_id ); 
  746.  
  747. return apply_filters( 
  748. 'ms_model_member_get_username',  
  749. $member->username,  
  750. $user_id 
  751. ); 
  752.  
  753. /** 
  754. * Search for orphaned relationships and remove them. 
  755. * 
  756. * We write a custom SQL query for this, as solving it with a meta-query 
  757. * structure is very performance intense and requires at least two queries 
  758. * and a loop... 
  759. * 
  760. * For additional performance we will only do this check once every hour. 
  761. * 
  762. * Note: We cannot use the hook 'delete_user' to do this, because in 
  763. * Multisite users are deleted via the Main network admin; however, there 
  764. * we do not have access to the site data; especially if Plugin is not 
  765. * network enabled... 
  766. * 
  767. * @todo Change this to use WP-Cron instead of own implementation... 
  768. * 
  769. * @since 1.0.0 
  770. * @internal 
  771. */ 
  772. static public function clean_db() { 
  773. $timestamp = absint( MS_Factory::get_transient( 'ms_member_clean_db' ) ); 
  774. $elapsed = time() - $timestamp; 
  775.  
  776. if ( $elapsed > 3600 ) { 
  777. // Last check is longer than 1 hour ago. Check again. 
  778. MS_Factory::set_transient( 'ms_member_clean_db', time(), 3600 ); 
  779. } else { 
  780. // Last check was within past hour. Do nothing yet... 
  781. return; 
  782.  
  783. global $wpdb; 
  784.  
  785. // Find all Relationships that have no post-author. 
  786. $sql = " 
  787. SELECT p.ID 
  788. FROM {$wpdb->posts} p 
  789. WHERE p.post_type=%s 
  790. AND NOT EXISTS ( 
  791. SELECT 1 
  792. FROM {$wpdb->users} u 
  793. WHERE u.ID = p.post_author 
  794. ); 
  795. "; 
  796.  
  797. $sql = $wpdb->prepare( 
  798. $sql,  
  799. MS_Model_Relationship::get_post_type() 
  800. ); 
  801.  
  802. // Delete these Relationships! 
  803. $items = $wpdb->get_results( $sql ); 
  804. foreach ( $items as $item ) { 
  805. $junk = MS_Factory::load( 'MS_Model_Relationship', $item->ID ); 
  806. $junk->delete(); 
  807.  
  808. // 
  809. // 
  810. // 
  811. // ------------------------------------------------------------- SINGLE ITEM 
  812.  
  813.  
  814. /** 
  815. * Returns a list of variables that should be included in serialization,  
  816. * i.e. these values are the only ones that are stored in DB 
  817. * 
  818. * @since 1.0.0 
  819. * @internal 
  820. * @return array 
  821. */ 
  822. public function __sleep() { 
  823. return array( 
  824. 'id',  
  825. 'username',  
  826. 'email',  
  827. 'name',  
  828. 'first_name',  
  829. 'last_name',  
  830. 'subscriptions',  
  831. 'is_member',  
  832. 'gateway_profiles',  
  833. 'custom_data',  
  834. ); 
  835.  
  836. /** 
  837. * Validates the object right after it was loaded/initialized. 
  838. * 
  839. * We ensure that the custom_data field is an array. 
  840. * 
  841. * @since 1.0.0 
  842. */ 
  843. public function prepare_obj() { 
  844. parent::prepare_obj(); 
  845.  
  846. if ( ! is_array( $this->custom_data ) ) { 
  847. $this->custom_data = array(); 
  848.  
  849. /** 
  850. * Save member. 
  851. * 
  852. * Create a new user is id is empty. 
  853. * Save member fields to wp_user and wp_usermeta tables. 
  854. * Set cache for further use in MS_Factory::load. 
  855. * The usermeta are prefixed with 'ms_'. 
  856. * 
  857. * @since 1.0.0 
  858. * @api 
  859. * 
  860. * @return MS_Model_Member The saved member object. 
  861. */ 
  862. public function save() { 
  863. $class = get_class( $this ); 
  864.  
  865. /** 
  866. * Tell WordPress core that we do NOT want to trigger the 
  867. * Password-Reset email while updating the user now. 
  868. * New since WordPress 4.3.0 
  869. * 
  870. * @since 1.0.2.2 
  871. */ 
  872. add_filter( 'send_password_change_email', '__return_false' ); 
  873.  
  874. if ( empty( $this->id ) ) { 
  875. $this->create_new_user(); 
  876.  
  877. if ( isset( $this->username ) ) { 
  878. $wp_user = new stdClass(); 
  879. $wp_user->ID = $this->id; 
  880. $wp_user->nickname = $this->username; 
  881. $wp_user->user_nicename = $this->username; 
  882. $wp_user->first_name = $this->first_name; 
  883. $wp_user->last_name = $this->last_name; 
  884.  
  885. if ( ! empty( $this->password ) 
  886. && $this->password == $this->password2 
  887. ) { 
  888. $wp_user->user_pass = $this->password; 
  889. wp_update_user( get_object_vars( $wp_user ) ); 
  890.  
  891. // Serialize our plugin meta data 
  892. $data = MS_Factory::serialize_model( $this ); 
  893.  
  894. // Then update all meta fields that are inside the collection 
  895. foreach ( $data as $field => $val ) { 
  896. update_user_meta( $this->id, 'ms_' . $field, $val ); 
  897.  
  898. wp_cache_set( $this->id, $this, $class ); 
  899.  
  900. // Remove our "mute-password-reset-email" trigger again. 
  901. remove_filter( 'send_password_change_email', '__return_false' ); 
  902.  
  903. return apply_filters( 'ms_model_member_save', $this ); 
  904.  
  905. /** 
  906. * Create new WP user. 
  907. * 
  908. * @since 1.0.0 
  909. * @internal 
  910. * @throws Exception 
  911. */ 
  912. private function create_new_user() { 
  913. // Check if the WordPress settings allow user registration. 
  914. if ( ! MS_Model_Member::can_register() ) { 
  915. throw new Exception( __( 'Registration is currently not allowed.', 'membership2' ), 1 ); 
  916. return; 
  917.  
  918. if ( is_user_logged_in() ) { 
  919. throw new Exception( __( 'You cannot register a new account, because you are already logged in.', 'membership2' ), 1 ); 
  920. return; 
  921.  
  922. $validation_errors = new WP_Error(); 
  923.  
  924. $required = array( 
  925. 'username' => __( 'Username', 'membership2' ),  
  926. 'email' => __( 'Email address', 'membership2' ),  
  927. 'password' => __( 'Password', 'membership2' ),  
  928. 'password2' => __( 'Password confirmation', 'membership2' ),  
  929. ); 
  930.  
  931. /** 
  932. * Filter the required field list to customize the fields that are 
  933. * mandatory. 
  934. * 
  935. * @since 1.0.1.0 
  936. * @var array 
  937. */ 
  938. $required = apply_filters( 
  939. 'ms_model_member_create_user_required_fields',  
  940. $required 
  941. ); 
  942.  
  943. foreach ( $required as $field => $message ) { 
  944. if ( empty( $this->$field ) && empty( $_POST[$field] ) ) { 
  945. $validation_errors->add( 
  946. $field,  
  947. sprintf( 
  948. __( 'Please ensure that the <span class="ms-bold">%s</span> information is completed.', 'membership2' ),  
  949. $message 
  950. ); 
  951.  
  952. if ( $this->password != $this->password2 ) { 
  953. $validation_errors->add( 
  954. 'passmatch',  
  955. __( 'Please ensure the passwords match.', 'membership2' ) 
  956. ); 
  957.  
  958. if ( ! validate_username( $this->username ) ) { 
  959. $validation_errors->add( 
  960. 'usernamenotvalid',  
  961. __( 'The username is not valid, sorry.', 'membership2' ) 
  962. ); 
  963.  
  964. if ( username_exists( $this->username ) ) { 
  965. $validation_errors->add( 
  966. 'usernameexists',  
  967. __( 'That username is already taken, sorry.', 'membership2' ) 
  968. ); 
  969.  
  970. if ( ! is_email( $this->email ) ) { 
  971. $validation_errors->add( 
  972. 'emailnotvalid',  
  973. __( 'The email address is not valid, sorry.', 'membership2' ) 
  974. ); 
  975.  
  976. if ( email_exists( $this->email ) ) { 
  977. $validation_errors->add( 
  978. 'emailexists',  
  979. __( 'That email address is already taken, sorry.', 'membership2' ) 
  980. ); 
  981.  
  982. // Check the multisite Email-Domain limitation for new registrations. 
  983. if ( is_multisite() ) { 
  984. $illegal_names = get_site_option( 'illegal_names' ); 
  985. $limited_domains = get_site_option( 'limited_email_domains' ); 
  986. $banned_domains = get_site_option( 'banned_email_domains' ); 
  987. $email_domain = substr( strrchr( $this->email, '@' ), 1 ); 
  988.  
  989. if ( $illegal_names && is_array( $illegal_names ) ) { 
  990. if ( in_array( $this->username, $illegal_names ) ) { 
  991. $validation_errors->add( 
  992. 'illegalname',  
  993. __( 'The username is not valid, sorry.', 'membership2' ) 
  994. ); 
  995.  
  996. if ( $limited_domains && is_array( $limited_domains ) ) { 
  997. if ( ! in_array( $email_domain, $limited_domains ) ) { 
  998. $validation_errors->add( 
  999. 'emaildomain',  
  1000. __( 'That email domain is not allowed for registration, sorry.', 'membership2' ) 
  1001. ); 
  1002.  
  1003. if ( $banned_domains && is_array( $banned_domains ) ) { 
  1004. if ( in_array( $email_domain, $banned_domains ) ) { 
  1005. $validation_errors->add( 
  1006. 'emaildomain',  
  1007. __( 'That email domain is not allowed for registration, sorry.', 'membership2' ) 
  1008. ); 
  1009.  
  1010. $validation_errors = apply_filters( 
  1011. 'ms_model_membership_create_new_user_validation_errors',  
  1012. $validation_errors 
  1013. ); 
  1014.  
  1015. // Compatibility with WangGuard 
  1016. $_POST['user_email'] = $this->email; 
  1017.  
  1018. $user_data = array( 
  1019. 'user_name' => $this->username,  
  1020. 'orig_username' => $this->username,  
  1021. 'user_email' => $this->email,  
  1022. 'errors' => $validation_errors,  
  1023. ); 
  1024.  
  1025. $user_data = apply_filters( 
  1026. 'wpmu_validate_user_signup',  
  1027. $user_data 
  1028. ); 
  1029.  
  1030. if ( is_wp_error( $user_data ) ) { 
  1031. /** 
  1032. * Some plugins incorrectly return a WP_Error object as result of 
  1033. * the wpmu_validate_user_signup filter. 
  1034. */ 
  1035. $validation_errors = $user_data; 
  1036. } else { 
  1037. $validation_errors = $user_data['errors']; 
  1038.  
  1039. $errors = $validation_errors->get_error_messages(); 
  1040.  
  1041. if ( ! empty( $errors ) ) { 
  1042. throw new Exception( implode( '<br/>', $errors ) ); 
  1043. } else { 
  1044. if ( ! $this->password ) { 
  1045. /** 
  1046. * For some reason the user did not provide a password in the 
  1047. * registration form. We help out here by creating a password 
  1048. * for the little bugger and send him a password-reset email. 
  1049. * 
  1050. * So: Generate a STRONG password for the new user. 
  1051. * 
  1052. * Important: This password should be sent to the user via the 
  1053. * Email template "User Account Created" 
  1054. */ 
  1055. $this->password = wp_generate_password( 24 ); 
  1056. $this->password2 = $this->password; 
  1057.  
  1058. $user_id = wp_create_user( 
  1059. $this->username,  
  1060. $this->password,  
  1061. $this->email 
  1062. ); 
  1063.  
  1064. if ( is_wp_error( $user_id ) ) { 
  1065. $validation_errors->add( 
  1066. 'userid',  
  1067. $user_id->get_error_message() 
  1068. ); 
  1069.  
  1070. throw new Exception( 
  1071. implode( 
  1072. '<br/>',  
  1073. $validation_errors->get_error_messages() 
  1074. ); 
  1075.  
  1076. $this->id = $user_id; 
  1077.  
  1078. do_action( 'ms_model_member_create_new_user', $this ); 
  1079.  
  1080. /** 
  1081. * Marks the current user as "confirmed" 
  1082. * 
  1083. * @since 1.0.0 
  1084. * @internal 
  1085. */ 
  1086. public function confirm() { 
  1087. global $wpdb; 
  1088.  
  1089. $auto_confirm = apply_filters( 
  1090. 'ms_model_member_auto_confirm',  
  1091. true,  
  1092. $this 
  1093. ); 
  1094.  
  1095. if ( ! $auto_confirm ) { return; } 
  1096.  
  1097. $sql = "UPDATE $wpdb->users SET user_status = 0 WHERE ID = %d"; 
  1098. $sql = $wpdb->prepare( $sql, $this->id ); 
  1099. $wpdb->query( $sql ); 
  1100.  
  1101. /** 
  1102. * Sign on user. 
  1103. * 
  1104. * @since 1.0.0 
  1105. * @api 
  1106. */ 
  1107. public function signon_user() { 
  1108. $user = new WP_User( $this->id ); 
  1109.  
  1110. if ( ! headers_sent() ) { 
  1111. $user = @wp_signon( 
  1112. array( 
  1113. 'user_login' => $this->username,  
  1114. 'user_password' => $this->password,  
  1115. 'remember' => true,  
  1116. ); 
  1117.  
  1118. // Stop here in case the login failed. 
  1119. if ( is_wp_error( $user ) ) { 
  1120. return $user; 
  1121.  
  1122. // Also used in class-ms-controller-dialog.php (Ajax login) 
  1123. wp_set_current_user( $this->id ); 
  1124. wp_set_auth_cookie( $this->id ); 
  1125. do_action( 'wp_login', $this->username, $user ); 
  1126. do_action( 'ms_model_member_signon_user', $user, $this ); 
  1127.  
  1128. /** 
  1129. * Either creates or updates the value of a custom data field. 
  1130. * 
  1131. * Note: Remember to prefix the $key with a unique string to prevent 
  1132. * conflicts with other plugins that also use this function. 
  1133. * 
  1134. * @since 1.0.0 
  1135. * @api 
  1136. * 
  1137. * @param string $key The field-key. 
  1138. * @param mixed $value The new value to assign to the field. 
  1139. */ 
  1140. public function set_custom_data( $key, $value ) { 
  1141. $this->custom_data[ $key ] = $value; 
  1142.  
  1143. /** 
  1144. * Removes a custom data field from this object. 
  1145. * 
  1146. * @since 1.0.0 
  1147. * @api 
  1148. * 
  1149. * @param string $key The field-key. 
  1150. */ 
  1151. public function delete_custom_data( $key ) { 
  1152. unset( $this->custom_data[ $key ] ); 
  1153.  
  1154. /** 
  1155. * Returns the value of a custom data field. 
  1156. * 
  1157. * @since 1.0.0 
  1158. * @api 
  1159. * 
  1160. * @param string $key The field-key. 
  1161. * @return mixed The value that was previously assigned to the custom field 
  1162. * or false if no value was set for the field. 
  1163. */ 
  1164. public function get_custom_data( $key ) { 
  1165. $res = false; 
  1166. if ( isset( $this->custom_data[ $key ] ) ) { 
  1167. $res = $this->custom_data[ $key ]; 
  1168. return $res; 
  1169.  
  1170. /** 
  1171. * Returns a list of all membership IDs of the current user. 
  1172. * 
  1173. * @since 1.0.0 
  1174. * @api 
  1175. * 
  1176. * @return array 
  1177. */ 
  1178. public function get_membership_ids() { 
  1179. $result = array(); 
  1180.  
  1181. foreach ( $this->subscriptions as $subscription ) { 
  1182. $result[] = $subscription->membership_id; 
  1183.  
  1184. return $result; 
  1185.  
  1186. /** 
  1187. * Add a new membership. 
  1188. * 
  1189. * If multiple membership is disabled, may move existing membership. 
  1190. * 
  1191. * Only add a membership if a user is not already a member. 
  1192. * 
  1193. * @since 1.0.0 
  1194. * @api 
  1195. * 
  1196. * @param int $membership_id The membership id to add to. 
  1197. * @param string $gateway_id Optional. The gateway used to add the membership. 
  1198. * @param int|string $move_from_id Optional. The membership id(s) to cancel. 
  1199. * 
  1200. * @return object|null $subscription 
  1201. */ 
  1202. public function add_membership( $membership_id, $gateway_id = 'admin', $move_from_id = 0 ) { 
  1203. $subscription = null; 
  1204.  
  1205. if ( MS_Model_Membership::is_valid_membership( $membership_id ) ) { 
  1206. if ( ! $this->get_subscription( $membership_id ) ) { 
  1207. $subscription = MS_Model_Relationship::create_ms_relationship( 
  1208. $membership_id,  
  1209. $this->id,  
  1210. $gateway_id,  
  1211. $move_from_id 
  1212. ); 
  1213.  
  1214. if ( 'admin' != $gateway_id ) { 
  1215. $subscription->get_current_invoice(); 
  1216.  
  1217. if ( MS_Model_Relationship::STATUS_PENDING !== $subscription->status ) { 
  1218. $this->subscriptions[] = $subscription; 
  1219.  
  1220. usort( 
  1221. $this->subscriptions,  
  1222. array( 'MS_Model_Relationship', 'sort_by_priority' ) 
  1223. ); 
  1224. } else { 
  1225. $subscription = $this->get_subscription( $membership_id ); 
  1226.  
  1227. // Reset the status and start/expire dates when added by admin. 
  1228. if ( 'admin' == $gateway_id ) { 
  1229. $subscription->start_date = null; // Will calculate correct date. 
  1230. $subscription->trial_expire_date = null; 
  1231. $subscription->expire_date = null; 
  1232. $subscription->status = MS_Model_Relationship::STATUS_ACTIVE; 
  1233. $subscription->save(); 
  1234.  
  1235. $this->is_member = true; 
  1236.  
  1237. return apply_filters( 
  1238. 'ms_model_member_add_membership',  
  1239. $subscription,  
  1240. $membership_id,  
  1241. $gateway_id,  
  1242. $move_from_id,  
  1243. $this 
  1244. ); 
  1245.  
  1246. /** 
  1247. * Drop a membership. 
  1248. * 
  1249. * Only update the status to deactivated. 
  1250. * 
  1251. * @since 1.0.0 
  1252. * @api 
  1253. * 
  1254. * @param int $membership_id The membership id to drop. 
  1255. */ 
  1256. public function drop_membership( $membership_id ) { 
  1257. $subscription = $this->get_subscription( $membership_id, $key ); 
  1258. if ( $subscription ) { 
  1259. do_action( 
  1260. 'ms_model_membership_drop_membership',  
  1261. $subscription,  
  1262. $this 
  1263. ); 
  1264.  
  1265. $subscription->deactivate_membership(); 
  1266. unset( $this->subscriptions[$key] ); 
  1267.  
  1268. $is_member = false; 
  1269. foreach ( $this->subscriptions as $subscription ) { 
  1270. if ( ! $subscription->is_system() ) { 
  1271. $is_member = true; 
  1272. break; 
  1273. $this->is_member = $is_member; 
  1274.  
  1275. do_action( 
  1276. 'ms_model_membership_drop_membership',  
  1277. $membership_id,  
  1278. $this 
  1279. ); 
  1280.  
  1281. /** 
  1282. * Cancel a membership. 
  1283. * 
  1284. * The membership remains valid until expiration date. 
  1285. * 
  1286. * @since 1.0.0 
  1287. * @api 
  1288. * 
  1289. * @param int $membership_id The membership id to drop. 
  1290. */ 
  1291. public function cancel_membership( $membership_id ) { 
  1292. $subscription = $this->get_subscription( $membership_id ); 
  1293. if ( $subscription ) { 
  1294. do_action( 
  1295. 'ms_model_membership_cancel_membership',  
  1296. $subscription,  
  1297. $this 
  1298. ); 
  1299.  
  1300. $subscription->cancel_membership(); 
  1301. } else { 
  1302. // The membership might be on status "PENDING" which is not included 
  1303. // in $this->subscriptions. 
  1304. $subscription = MS_Model_Relationship::get_subscription( 
  1305. $this->id,  
  1306. $membership_id 
  1307. ); 
  1308.  
  1309. if ( $subscription->user_id == $this->id ) { 
  1310. $subscription->cancel_membership(); 
  1311.  
  1312. do_action( 
  1313. 'ms_model_membership_cancel_membership',  
  1314. $membership_id,  
  1315. $this 
  1316. ); 
  1317.  
  1318. /** 
  1319. * Move a membership. 
  1320. * 
  1321. * @since 1.0.0 
  1322. * @api 
  1323. * 
  1324. * @param int $old_membership_id The membership id to move from. 
  1325. * @param int $mew_membership_id The membership id to move to. 
  1326. */ 
  1327. public function move_membership( $old_membership_id, $mew_membership_id ) { 
  1328. $old_subscription = $this->get_subscription( $old_membership_id ); 
  1329. if ( $old_subscription ) { 
  1330. $new_subscription = MS_Model_Relationship::create_ms_relationship( 
  1331. $mew_membership_id,  
  1332. $this->id,  
  1333. $old_subscription->gateway_id,  
  1334. $old_membership_id 
  1335. ); 
  1336.  
  1337. $this->cancel_membership( $old_membership_id ); 
  1338. $this->subscriptions[] = $new_subscription; 
  1339.  
  1340. MS_Model_Event::save_event( 
  1341. MS_Model_Event::TYPE_MS_MOVED,  
  1342. $new_subscription 
  1343. ); 
  1344.  
  1345. do_action( 
  1346. 'ms_model_membership_move_membership',  
  1347. $old_membership_id,  
  1348. $mew_membership_id,  
  1349. $this 
  1350. ); 
  1351.  
  1352. /** 
  1353. * Check membership relationship status. 
  1354. * 
  1355. * Canceled status is allowed until it expires. 
  1356. * 
  1357. * @since 1.0.0 
  1358. * @api 
  1359. * 
  1360. * @param int $membership_id Optional. The specific membership to verify. 
  1361. * If empty, verify against all memberships. 
  1362. * @return bool True if has a valid membership. 
  1363. */ 
  1364. public function has_membership( $membership_id = 0 ) { 
  1365. $has_membership = false; 
  1366.  
  1367. // Allowed membership status to have access 
  1368. $allowed_status = apply_filters( 
  1369. 'ms_model_member_allowed_status',  
  1370. array( 
  1371. MS_Model_Relationship::STATUS_ACTIVE,  
  1372. MS_Model_Relationship::STATUS_TRIAL,  
  1373. MS_Model_Relationship::STATUS_CANCELED,  
  1374. ); 
  1375.  
  1376. if ( self::is_normal_admin( $this->id ) ) { 
  1377. $has_membership = true; 
  1378.  
  1379. if ( ! empty( $membership_id ) ) { 
  1380. $subscription = $this->get_subscription( $membership_id ); 
  1381. // Membership-ID specified: Check if user has this membership 
  1382. if ( $subscription 
  1383. && in_array( $subscription->get_status(), $allowed_status ) 
  1384. ) { 
  1385. $has_membership = true; 
  1386. } elseif ( ! empty ( $this->subscriptions ) ) { 
  1387. // No membership-ID: Check if user has *any* membership 
  1388. foreach ( $this->subscriptions as $subscription ) { 
  1389. if ( $subscription->is_system() ) { continue; } 
  1390. if ( in_array( $subscription->get_status(), $allowed_status ) ) { 
  1391. $has_membership = true; 
  1392. break; 
  1393.  
  1394. return apply_filters( 
  1395. 'ms_model_member_has_membership',  
  1396. $has_membership,  
  1397. $membership_id,  
  1398. $this 
  1399. ); 
  1400.  
  1401. /** 
  1402. * Return the subscription object for the specified membership. 
  1403. * 
  1404. * @since 1.0.0 
  1405. * @api 
  1406. * 
  1407. * @param int|string $membership_id The specific membership to return. 
  1408. * Value 'priority' will return the subcription with lowest priority. 
  1409. * @return MS_Model_Relationship The subscription object. 
  1410. */ 
  1411. public function get_subscription( $membership_id, &$key = -1 ) { 
  1412. $subscription = null; 
  1413. $key = -1; 
  1414.  
  1415. if ( 'priority' == $membership_id ) { 
  1416. // Find subscription with the lowest priority. 
  1417. $cur_priority = -1; 
  1418. foreach ( $this->subscriptions as $ind => $item ) { 
  1419. $membership = $item->get_membership(); 
  1420. if ( ! $membership->active ) { continue; } 
  1421. if ( $cur_priority < 0 || $membership->priority < $cur_priority ) { 
  1422. $subscription = $item; 
  1423. $cur_priority = $membership->priority; 
  1424. $key = $ind; 
  1425. } elseif ( ! empty( $membership_id ) ) { 
  1426. // Membership-ID specified: Check if user has this membership 
  1427. foreach ( $this->subscriptions as $ind => $item ) { 
  1428. if ( $item->membership_id == $membership_id ) { 
  1429. $subscription = $item; 
  1430. $key = $ind; 
  1431. break; 
  1432.  
  1433. return apply_filters( 
  1434. 'ms_model_member_get_subscription',  
  1435. $subscription,  
  1436. $membership_id,  
  1437. $this 
  1438. ); 
  1439.  
  1440. /** 
  1441. * Returns a list of memberships for all active subscriptions of the member. 
  1442. * 
  1443. * @since 1.0.1.0 
  1444. * @return array 
  1445. */ 
  1446. protected function get_active_memberships() { 
  1447. $active_memberships = array(); 
  1448.  
  1449. $active_status = array( 
  1450. MS_Model_Relationship::STATUS_ACTIVE,  
  1451. MS_Model_Relationship::STATUS_TRIAL,  
  1452. MS_Model_Relationship::STATUS_CANCELED,  
  1453. ); 
  1454.  
  1455. foreach ( $this->subscriptions as $sub ) { 
  1456. if ( $sub->is_base() ) { continue; } 
  1457. if ( ! in_array( $sub->status, $active_status ) ) { continue; } 
  1458.  
  1459. $membership = $sub->get_membership(); 
  1460. $active_memberships[$membership->id] = $membership; 
  1461.  
  1462. return $active_memberships; 
  1463.  
  1464. /** 
  1465. * Checks if the current user is allowed to subscribe to the specified 
  1466. * membership. 
  1467. * 
  1468. * @since 1.0.1.0 
  1469. * @api 
  1470. * @param int $membership_id A membership_id. 
  1471. * @return bool Whether subscription is allowed or not. 
  1472. */ 
  1473. public function can_subscribe_to( $membership_id ) { 
  1474. static $Access_Flags = null; 
  1475.  
  1476. if ( null === $Access_Flags ) { 
  1477. $Access_Flags = array(); 
  1478. $active_memberships = $this->get_active_memberships(); 
  1479. $all_memberships = MS_Model_Membership::get_memberships(); 
  1480.  
  1481. /** 
  1482. * Controls how to handle conflicts in upgrade path settings when a 
  1483. * member has multiple memberships. 
  1484. * 
  1485. * Default is true: 
  1486. * If one membership forbids the upgrade, then that's it. 
  1487. * 
  1488. * Custom set to false: 
  1489. * If one membership allows the upgrade, then allow it. 
  1490. * 
  1491. * @since 1.0.1.0 
  1492. * @var bool 
  1493. */ 
  1494. $prefer_forbidden = apply_filters( 
  1495. 'ms_model_member_can_subscribe_to_prefer_forbidden',  
  1496. true 
  1497. ); 
  1498.  
  1499. foreach ( $active_memberships as $membership ) { 
  1500. $base_id = $membership->id; 
  1501. if ( $membership->is_guest() || $membership->is_user() ) { 
  1502. $base_id = 'guest'; 
  1503.  
  1504. foreach ( $all_memberships as $ms ) { 
  1505. if ( isset( $active_memberships[$ms->id] ) ) { continue; } 
  1506.  
  1507. $is_allowed = $ms->update_allowed( $base_id ); 
  1508.  
  1509. if ( ! isset( $Access_Flags[$ms->id] ) ) { 
  1510. $Access_Flags[$ms->id] = $is_allowed; 
  1511. } else { 
  1512. if ( $prefer_forbidden && ! $is_allowed ) { 
  1513. $Access_Flags[$ms->id] = $is_allowed; 
  1514. } elseif ( ! $prefer_forbidden && $is_allowed ) { 
  1515. $Access_Flags[$ms->id] = $is_allowed; 
  1516.  
  1517. $result = true; 
  1518. if ( isset( $Access_Flags[$membership_id] ) ) { 
  1519. $result = $Access_Flags[$membership_id]; 
  1520.  
  1521. return apply_filters( 
  1522. 'ms_model_member_can_subscribe_to',  
  1523. $result,  
  1524. $membership_id 
  1525. ); 
  1526.  
  1527. /** 
  1528. * Returns an array of existing subscriptions that should be cancelled when 
  1529. * the user signs up to the specified membership. 
  1530. * 
  1531. * @since 1.0.1.0 
  1532. * @param int $membership_id A membership ID. 
  1533. * @return array Might be an empty array or a list of membership IDs. 
  1534. */ 
  1535. public function cancel_ids_on_subscription( $membership_id ) { 
  1536. $result = array(); 
  1537.  
  1538. $membership = MS_Factory::load( 'MS_Model_Membership', $membership_id ); 
  1539. $active_memberships = $this->get_active_memberships(); 
  1540.  
  1541. foreach ( $active_memberships as $ms ) { 
  1542. if ( $membership->update_replaces( $ms->id ) ) { 
  1543. $result[] = $ms->id; 
  1544.  
  1545. return $result; 
  1546.  
  1547. /** 
  1548. * Delete member usermeta. 
  1549. * 
  1550. * Delete all plugin related usermeta. 
  1551. * 
  1552. * @since 1.0.0 
  1553. * @internal 
  1554. */ 
  1555. public function delete_all_membership_usermeta() { 
  1556. $this->subscriptions = array(); 
  1557. $this->gateway_profiles = array(); 
  1558. $this->is_member = false; 
  1559.  
  1560. do_action( 
  1561. 'ms_model_membership_delete_all_membership_usermeta',  
  1562. $this 
  1563. ); 
  1564.  
  1565. /** 
  1566. * Returns the WP_User object that is linked to the current member 
  1567. * 
  1568. * @since 1.0.0 
  1569. * @api 
  1570. * 
  1571. * @return WP_User 
  1572. */ 
  1573. public function get_user() { 
  1574. return $this->wp_user; 
  1575.  
  1576. /** 
  1577. * Returns a value from the user-meta table. 
  1578. * 
  1579. * @since 1.0.1.0 
  1580. * @api 
  1581. * @param string $key The meta-key. 
  1582. * @return mixed The meta-value. 
  1583. */ 
  1584. public function get_meta( $key ) { 
  1585. return get_user_meta( $this->id, $key, true ); 
  1586.  
  1587. /** 
  1588. * Updates a value in the user-meta table. 
  1589. * 
  1590. * @since 1.0.1.0 
  1591. * @api 
  1592. * @param string $key The meta-key. 
  1593. * @param mixed $value The new meta-value. 
  1594. */ 
  1595. public function set_meta( $key, $value ) { 
  1596. update_user_meta( $this->id, $key, $value ); 
  1597.  
  1598. /** 
  1599. * Verify if current object is valid. 
  1600. * 
  1601. * @since 1.0.0 
  1602. * @api 
  1603. * 
  1604. * @return boolean True if is valid. 
  1605. */ 
  1606. public function is_valid() { 
  1607. $valid = ( $this->id > 0 ); 
  1608.  
  1609. return apply_filters( 'ms_model_member_is_valid', $valid, $this ); 
  1610.  
  1611. /** 
  1612. * Get gateway profile. 
  1613. * 
  1614. * @since 1.0.0 
  1615. * @api 
  1616. * 
  1617. * @param string $gateway The gateway ID. 
  1618. * @param string $field Optional. The field to retrive. Default to null,  
  1619. * returning all profile info. 
  1620. * 
  1621. * @return mixed The gateway profile info. 
  1622. */ 
  1623. public function get_gateway_profile( $gateway, $field = null ) { 
  1624. $profile = null; 
  1625.  
  1626. if ( ! isset( $this->gateway_profiles[ $gateway ] ) ) { 
  1627. $this->gateway_profiles[ $gateway ] = array(); 
  1628.  
  1629. if ( empty( $field ) ) { 
  1630. $profile = $this->gateway_profiles[ $gateway ]; 
  1631. } else { 
  1632. if ( ! isset( $this->gateway_profiles[ $gateway ][ $field ] ) ) { 
  1633. $this->gateway_profiles[ $gateway ][ $field ] = ''; 
  1634. $profile = $this->gateway_profiles[ $gateway ][ $field ]; 
  1635.  
  1636. return apply_filters( 'ms_model_member_get_gateway_profile', $profile ); 
  1637.  
  1638. /** 
  1639. * Set gateway profile. 
  1640. * 
  1641. * @since 1.0.0 
  1642. * @api 
  1643. * 
  1644. * @param string $gateway The gateway ID. 
  1645. * @param string $field The field name to save. 
  1646. * @param mixed $value The field value to save. 
  1647. */ 
  1648. public function set_gateway_profile( $gateway, $field, $value ) { 
  1649. $this->gateway_profiles[ $gateway ][ $field ] = $value; 
  1650.  
  1651. do_action( 
  1652. 'ms_model_member_set_gateway_profile',  
  1653. $gateway,  
  1654. $field,  
  1655. $value,  
  1656. $this 
  1657. ); 
  1658.  
  1659. /** 
  1660. * Validate member info. 
  1661. * 
  1662. * @since 1.0.0 
  1663. * @internal 
  1664. * 
  1665. * @return boolean True if validated. 
  1666. * @throws Exception if not validated. 
  1667. */ 
  1668. public function validate_member_info() { 
  1669. $validation_errors = new WP_Error(); 
  1670.  
  1671. if ( ! is_email( $this->email ) ) { 
  1672. $validation_errors->add( 
  1673. 'emailnotvalid',  
  1674. __( 'The email address is not valid, sorry.', 'membership2' ) 
  1675. ); 
  1676.  
  1677. if ( $this->password != $this->password2 ) { 
  1678. $validation_errors->add( 
  1679. 'passmatch',  
  1680. __( 'Please ensure the passwords match.', 'membership2' ) 
  1681. ); 
  1682.  
  1683. $errors = apply_filters( 
  1684. 'ms_model_member_validate_member_info_errors',  
  1685. $validation_errors->get_error_messages() 
  1686. ); 
  1687.  
  1688. if ( ! empty( $errors ) ) { 
  1689. throw new Exception( implode( '<br/>', $errors ) ); 
  1690. } else { 
  1691. return true; 
  1692.  
  1693. /** 
  1694. * Creates a new password-reset key and stores it in the DB. 
  1695. * 
  1696. * @since 1.0.2.3 
  1697. * @return object { 
  1698. * The reset-key that can be sent to the user. 
  1699. * 
  1700. * @var key The reset key 
  1701. * @var url The full reset URL 
  1702. * } 
  1703. */ 
  1704. public function new_password_reset_key() { 
  1705. global $wpdb, $wp_hasher; 
  1706.  
  1707. // Generate something random for a password reset key. 
  1708. $key = wp_generate_password( 20, false ); 
  1709.  
  1710. do_action( 'retrieve_password_key', $this->username, $key ); 
  1711.  
  1712. // Now insert a hashed version of the key into the DB. 
  1713. // Important: The has needs to include the time() value! 
  1714. if ( empty( $wp_hasher ) ) { 
  1715. require_once ABSPATH . WPINC . '/class-phpass.php'; 
  1716. $wp_hasher = new PasswordHash( 8, true ); 
  1717. $hashed = time() . ':' . $wp_hasher->HashPassword( $key ); 
  1718. $wpdb->update( 
  1719. $wpdb->users,  
  1720. array( 'user_activation_key' => $hashed ),  
  1721. array( 'user_login' => $this->username ) 
  1722. ); 
  1723.  
  1724. MS_Model_Pages::create_missing_pages(); 
  1725. $reset_url = MS_Model_Pages::get_page_url( MS_Model_Pages::MS_PAGE_ACCOUNT ); 
  1726. $reset_url = esc_url_raw( 
  1727. add_query_arg( 
  1728. array( 
  1729. 'action' => MS_Controller_Frontend::ACTION_VIEW_RESETPASS,  
  1730. 'key' => $key,  
  1731. 'login' => rawurlencode( $this->username ),  
  1732. ),  
  1733. $reset_url 
  1734. ); 
  1735.  
  1736. return (object) array( 
  1737. 'key' => $key,  
  1738. 'url' => $reset_url,  
  1739. ); 
  1740.  
  1741. /** 
  1742. * Get specific property. 
  1743. * 
  1744. * @since 1.0.0 
  1745. * @internal 
  1746. * 
  1747. * @param string $name The name of a property to associate. 
  1748. * @return mixed The value of a property. 
  1749. */ 
  1750. public function __get( $property ) { 
  1751. $value = null; 
  1752.  
  1753. if ( property_exists( $this, $property ) ) { 
  1754. $value = $this->$property; 
  1755. } else { 
  1756. switch ( $property ) { 
  1757. case 'full_name': 
  1758. if ( ! empty( $this->first_name ) || ! empty( $this->last_name ) ) { 
  1759. $value = trim( $this->first_name . ' ' . $this->last_name ); 
  1760. } elseif ( ! empty( $this->name ) ) { 
  1761. $value = trim( $this->name ); 
  1762. } else { 
  1763. $value = trim( $this->username ); 
  1764. break; 
  1765.  
  1766. return $value; 
  1767.  
  1768. /** 
  1769. * Set specific property. 
  1770. * 
  1771. * @since 1.0.0 
  1772. * @internal 
  1773. * 
  1774. * @param string $name The name of a property to associate. 
  1775. * @param mixed $value The value of a property. 
  1776. */ 
  1777. public function __set( $property, $value ) { 
  1778. if ( property_exists( $this, $property ) ) { 
  1779. switch ( $property ) { 
  1780. case 'email': 
  1781. if ( is_email( $value ) ) { 
  1782. $this->$property = $value; 
  1783. break; 
  1784.  
  1785. case 'username': 
  1786. $this->$property = sanitize_user( $value ); 
  1787. break; 
  1788.  
  1789. case 'name': 
  1790. case 'first_name': 
  1791. case 'last_name': 
  1792. $this->$property = sanitize_text_field( $value ); 
  1793. break; 
  1794.  
  1795. default: 
  1796. $this->$property = $value; 
  1797. break; 
.