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

  1. <?php 
  2. /** 
  3. * Subscription model (former "Membership Relationship"). 
  4. * 
  5. * The Subscription defines which user has access to which membership and for 
  6. * how long. Do not confuse this with an invoice - a single subscription can 
  7. * have multiple invoices! 
  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_Relationship extends MS_Model_CustomPostType { 
  19.  
  20. /** 
  21. * Model custom post type. 
  22. * 
  23. * @since 1.0.0 
  24. * @internal Use self::get_post_type() instead! 
  25. * @var string $POST_TYPE 
  26. */ 
  27. protected static $POST_TYPE = 'ms_relationship'; 
  28.  
  29. /** 
  30. * Membership Relationship Status constants. 
  31. * Pending is the first status that means the member did not confirm his 
  32. * intention to complete his payment/registration. 
  33. * 
  34. * NO ACCESS. 
  35. * 
  36. * @since 1.0.0 
  37. * @see $status $status property. 
  38. */ 
  39. const STATUS_PENDING = 'pending'; 
  40.  
  41. /** 
  42. * Membership Relationship Status constants. 
  43. * This status has a much higher value than PENDING, because it means that 
  44. * the member already made a payment, but the subscription is not yet 
  45. * activated because the start date was not reached. 
  46. * 
  47. * NO ACCESS. 
  48. * 
  49. * @since 1.0.0 
  50. * @see $status $status property. 
  51. */ 
  52. const STATUS_WAITING = 'waiting'; 
  53.  
  54. /** 
  55. * Membership Relationship Status constants. 
  56. * 
  57. * FULL ACCESS TO MEMBERSHIP CONTENTS. 
  58. * 
  59. * @since 1.0.0 
  60. * @see $status $status property. 
  61. */ 
  62. const STATUS_ACTIVE = 'active'; 
  63.  
  64. /** 
  65. * Membership Relationship Status constants. 
  66. * 
  67. * FULL ACCESS TO MEMBERSHIP CONTENTS. 
  68. * 
  69. * @since 1.0.0 
  70. * @see $status $status property. 
  71. */ 
  72. const STATUS_TRIAL = 'trial'; 
  73.  
  74. /** 
  75. * Membership Relationship Status constants. 
  76. * User cancelled his subscription but the end date of the current payment 
  77. * period is not reached yet. The user has full access to the membership 
  78. * contents until the end date is reached. 
  79. * 
  80. * FULL ACCESS TO MEMBERSHIP CONTENTS. 
  81. * 
  82. * @since 1.0.0 
  83. * @see $status $status property. 
  84. */ 
  85. const STATUS_CANCELED = 'canceled'; 
  86.  
  87. /** 
  88. * Membership Relationship Status constants. 
  89. * 
  90. * NO ACCESS. 
  91. * 
  92. * @since 1.0.0 
  93. * @see $status $status property. 
  94. */ 
  95. const STATUS_TRIAL_EXPIRED = 'trial_expired'; 
  96.  
  97. /** 
  98. * Membership Relationship Status constants. 
  99. * End-Date reached. The subscription is available for renewal for a few 
  100. * more days. 
  101. * 
  102. * NO ACCESS. 
  103. * 
  104. * @since 1.0.0 
  105. * @see $status $status property. 
  106. */ 
  107. const STATUS_EXPIRED = 'expired'; 
  108.  
  109. /** 
  110. * Membership Relationship Status constants. 
  111. * Deactivated means, that we're completely done with this subscription. 
  112. * It's not displayed for renewal and the member can be set to inactive now. 
  113. * 
  114. * NO ACCESS. 
  115. * 
  116. * @since 1.0.0 
  117. * @see $status $status property. 
  118. */ 
  119. const STATUS_DEACTIVATED = 'deactivated'; 
  120.  
  121. /** 
  122. * The Membership ID. 
  123. * 
  124. * @since 1.0.0 
  125. * @var string $membership_id 
  126. */ 
  127. protected $membership_id; 
  128.  
  129. /** 
  130. * The Payment Gateway ID. 
  131. * 
  132. * @since 1.0.0 
  133. * @var string $gateway_id 
  134. */ 
  135. protected $gateway_id; 
  136.  
  137. /** 
  138. * The start date of the membership relationship. 
  139. * 
  140. * @since 1.0.0 
  141. * @var string $start_date 
  142. */ 
  143. protected $start_date; 
  144.  
  145. /** 
  146. * The expire date of the membership relationship. 
  147. * 
  148. * @since 1.0.0 
  149. * @var string $expire_date 
  150. */ 
  151. protected $expire_date; 
  152.  
  153. /** 
  154. * The trial expire date of the membership relationship. 
  155. * 
  156. * @since 1.0.0 
  157. * @var string $trial_expire_date 
  158. */ 
  159. protected $trial_expire_date; 
  160.  
  161. /** 
  162. * Trial period completed flag. 
  163. * 
  164. * Indicates if already used a trial period and can't have another trial period. 
  165. * 
  166. * @since 1.0.0 
  167. * @var string $trial_period_completed 
  168. */ 
  169. protected $trial_period_completed; 
  170.  
  171. /** 
  172. * The status of the membership relationship. 
  173. * 
  174. * @since 1.0.0 
  175. * @var string $status 
  176. */ 
  177. protected $status; 
  178.  
  179. /** 
  180. * Current invoice count. 
  181. * This is NOT the public invoice-number but an counter to determine how 
  182. * many invoices were generated for this subscription already. 
  183. * 
  184. * @since 1.0.0 
  185. * @var $current_invoice_number 
  186. */ 
  187. protected $current_invoice_number = 1; 
  188.  
  189. /** 
  190. * The moving/change/downgrade/upgrade from membership ID. 
  191. * 
  192. * This can be a single ID or a comma separated list of IDs. 
  193. * 
  194. * The value is set by function self::create_ms_relationship() 
  195. * It is used in MS_Model_Invoice: 
  196. * 1. When creating the invoice the move_from_id define the Pro Rating. 
  197. * 2. When an invoice is paid the move_from_id memberships are cancelled. 
  198. * 
  199. * @since 1.0.0 
  200. * @internal 
  201. * @var string $move_from_id 
  202. */ 
  203. protected $move_from_id = ''; 
  204.  
  205. /** 
  206. * After the memberships specified by $move_from_id were cancelled their 
  207. * IDs are stored in this property for logging purposes. 
  208. * 
  209. * @since 1.0.1.0 
  210. * @internal 
  211. * @var string $cancelled_memberships 
  212. */ 
  213. protected $cancelled_memberships = ''; 
  214.  
  215. /** 
  216. * Where the data came from. Can only be changed by data import tool 
  217. * 
  218. * @since 1.0.0 
  219. * @internal 
  220. * @var string 
  221. */ 
  222. protected $source = ''; 
  223.  
  224. /** 
  225. * Relevant for imported items. This is the ID that was used by the import 
  226. * source. 
  227. * 
  228. * @since 1.0.1.0 
  229. * @internal 
  230. * @var string 
  231. */ 
  232. protected $source_id = ''; 
  233.  
  234. /** 
  235. * The number of successful payments that were made for this subscription. 
  236. * 
  237. * We use this value to determine the end of a recurring payment plan. 
  238. * Also this information is displayed in the member-info popup (only for 
  239. * admins; see MS_View_Member_Dialog) 
  240. * 
  241. * @since 1.0.0 
  242. * @var array { 
  243. * A list of all payments that were made [since 1.1.0] 
  244. * 
  245. * string $date Payment date/time. 
  246. * number $amount Payment-Amount. 
  247. * string $gateway Gateway that confirmed payment. 
  248. * } 
  249. */ 
  250. protected $payments = array(); 
  251.  
  252. /** 
  253. * Flag that keeps track, if this subscription is a simulated or a real one. 
  254. * 
  255. * @since 1.0.0 
  256. * @internal 
  257. * @var bool 
  258. */ 
  259. protected $is_simulated = false; 
  260.  
  261. /** 
  262. * The payment type that this subscription was created with. 
  263. * Since the user can change the payment_type of the membership any time 
  264. * we might end up with a subscription with an invalid expire date. 
  265. * 
  266. * This flag allows us to detect changes in the parent membership payment 
  267. * options so we can update this membership accordingly. 
  268. * 
  269. * @since 1.0.0 
  270. * @var string 
  271. */ 
  272. protected $payment_type = ''; 
  273.  
  274. /** 
  275. * The related membership model object. 
  276. * 
  277. * @since 1.0.0 
  278. * @var MS_Model_Membership $membership 
  279. */ 
  280. private $membership; 
  281.  
  282. // 
  283. // 
  284. // 
  285. // -------------------------------------------------------------- COLLECTION 
  286.  
  287. /** 
  288. * Returns the post-type of the current object. 
  289. * 
  290. * @since 1.0.0 
  291. * @api 
  292. * 
  293. * @return string The post-type name. 
  294. */ 
  295. public static function get_post_type() { 
  296. return parent::_post_type( self::$POST_TYPE ); 
  297.  
  298. /** 
  299. * Get custom register post type args for this model. 
  300. * 
  301. * @since 1.0.0 
  302. * @internal 
  303. */ 
  304. public static function get_register_post_type_args() { 
  305. $args = array( 
  306. 'label' => __( 'Membership2 Subscriptions', 'membership2' ),  
  307. ); 
  308.  
  309. return apply_filters( 
  310. 'ms_customposttype_register_args',  
  311. $args,  
  312. self::get_post_type() 
  313. ); 
  314.  
  315. /** 
  316. * Don't persist this fields. 
  317. * 
  318. * @since 1.0.0 
  319. * @internal 
  320. * 
  321. * @var string[] The fields to ignore when persisting. 
  322. */ 
  323. static public $ignore_fields = array( 
  324. 'membership',  
  325. 'post_type',  
  326. ); 
  327.  
  328. /** 
  329. * Return existing status types and names. 
  330. * 
  331. * @since 1.0.0 
  332. * @internal 
  333. * 
  334. * @return array{ 
  335. * Return array of ( $type => name ); 
  336. * @type string $type The status type. 
  337. * @type string $name The status name. 
  338. * } 
  339. */ 
  340. public static function get_status_types() { 
  341. $status_types = array( 
  342. self::STATUS_PENDING => __( 'Pending', 'membership2' ),  
  343. self::STATUS_ACTIVE => __( 'Active', 'membership2' ),  
  344. self::STATUS_TRIAL => __( 'Trial', 'membership2' ),  
  345. self::STATUS_TRIAL_EXPIRED => __( 'Trial Expired', 'membership2' ),  
  346. self::STATUS_EXPIRED => __( 'Expired', 'membership2' ),  
  347. self::STATUS_DEACTIVATED => __( 'Deactivated', 'membership2' ),  
  348. self::STATUS_CANCELED => __( 'Canceled', 'membership2' ),  
  349. self::STATUS_WAITING => __( 'Not yet active', 'membership2' ),  
  350. ); 
  351.  
  352. return apply_filters( 
  353. 'ms_model_relationship_get_status_types',  
  354. $status_types 
  355. ); 
  356.  
  357. /** 
  358. * Create a new membership relationship. 
  359. * 
  360. * Search for existing relationship (unique object), creating if not exists. 
  361. * Set initial status. 
  362. * 
  363. * @since 1.0.0 
  364. * @internal 
  365. * 
  366. * @param int $membership_id The Membership to subscribe to. 
  367. * @param int $user_id The user who subscribes to the membership. 
  368. * @param string $gateway_id The gateway which is used for payment. 
  369. * @param int|string $move_from_id A list of membership IDs to cancel on 
  370. * payment. This property is handled by the MS_Model_Invoice class. 
  371. * @return MS_Model_Relationship The created relationship. 
  372. */ 
  373. public static function create_ms_relationship( 
  374. $membership_id = 0,  
  375. $user_id = 0,  
  376. $gateway_id = 'admin',  
  377. $move_from_id = 0 
  378. ) { 
  379. do_action( 
  380. 'ms_model_relationship_create_ms_relationship_before',  
  381. $membership_id,  
  382. $user_id,  
  383. $gateway_id,  
  384. $move_from_id 
  385. ); 
  386.  
  387. if ( MS_Model_Membership::is_valid_membership( $membership_id ) ) { 
  388. $subscription = self::_create_ms_relationship( 
  389. $membership_id,  
  390. $user_id,  
  391. $gateway_id,  
  392. $move_from_id 
  393. ); 
  394. } else { 
  395. $subscription = null; 
  396. MS_Helper_Debug::log( 
  397. 'Invalid membership_id: ' . 
  398. "$membership_id, ms_relationship not created for $user_id, $gateway_id, $move_from_id" 
  399. ); 
  400. MS_Helper_Debug::debug_trace(); 
  401.  
  402. return apply_filters( 
  403. 'ms_model_relationship_create_ms_relationship',  
  404. $subscription,  
  405. $membership_id,  
  406. $user_id,  
  407. $gateway_id,  
  408. $move_from_id 
  409. ); 
  410.  
  411. /** 
  412. * Helper function called by create_ms_relationship() 
  413. * 
  414. * @since 1.0.0 
  415. * @internal 
  416. * 
  417. * @return MS_Model_Relationship The created relationship. 
  418. */ 
  419. private static function _create_ms_relationship( $membership_id, $user_id, $gateway_id, $move_from_id ) { 
  420. $is_simulated = false; 
  421.  
  422. // Try to reuse existing db record to keep history. 
  423. $subscription = self::get_subscription( $user_id, $membership_id ); 
  424.  
  425. if ( 'simulation' == $gateway_id ) { 
  426. $is_simulated = true; 
  427. $gateway_id = 'admin'; 
  428. $subscription = false; 
  429.  
  430. // Not found, create a new one. 
  431. if ( empty( $subscription ) ) { 
  432. $subscription = MS_Factory::create( 'MS_Model_Relationship' ); 
  433. $subscription->status = self::STATUS_PENDING; 
  434. $subscription->is_simulated = $is_simulated; 
  435.  
  436. if ( $is_simulated ) { 
  437. $subscription->id = -1; 
  438.  
  439. // Always update these fields. 
  440. $subscription->membership_id = $membership_id; 
  441. $subscription->user_id = $user_id; 
  442. $subscription->move_from_id = $move_from_id; 
  443. $subscription->gateway_id = $gateway_id; 
  444. $subscription->expire_date = ''; 
  445.  
  446. // Set initial state. 
  447. switch ( $subscription->status ) { 
  448. case self::STATUS_DEACTIVATED: 
  449. $subscription->status = self::STATUS_PENDING; 
  450. break; 
  451.  
  452. case self::STATUS_TRIAL: 
  453. case self::STATUS_TRIAL_EXPIRED: 
  454. case self::STATUS_ACTIVE: 
  455. case self::STATUS_EXPIRED: 
  456. case self::STATUS_CANCELED: 
  457. /** Once a member or have tried the membership, not 
  458. * eligible to another trial period, unless the relationship 
  459. * is permanetly deleted. 
  460. */ 
  461. $subscription->trial_period_completed = true; 
  462. break; 
  463.  
  464. case self::STATUS_PENDING: 
  465. default: 
  466. // Initial status 
  467. $subscription->name = "user_id: $user_id, membership_id: $membership_id"; 
  468. $subscription->description = $subscription->name; 
  469. $subscription->trial_period_completed = false; 
  470. break; 
  471.  
  472. $subscription->config_period(); 
  473. $membership = $subscription->get_membership(); 
  474. $subscription->payment_type = $membership->payment_type; 
  475.  
  476. if ( 'admin' == $gateway_id ) { 
  477. $subscription->trial_period_completed = true; 
  478. $subscription->status = self::STATUS_ACTIVE; 
  479.  
  480. // Set the start/expire dates. Do this *after* the set_status() call! 
  481. $subscription->config_period(); 
  482.  
  483. if ( ! $subscription->is_system() && ! $is_simulated ) { 
  484. // Create event. 
  485. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_SIGNED_UP, $subscription ); 
  486.  
  487. $subscription->save(); 
  488.  
  489. return $subscription; 
  490.  
  491. /** 
  492. * Returns a list of subscription IDs that match the specified attributes. 
  493. * 
  494. * @since 1.0.1.0 
  495. * @internal 
  496. * 
  497. * @param $args The query post args. 
  498. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  499. * @return array A list of subscription IDs. 
  500. */ 
  501. public static function get_subscription_ids( $args = null ) { 
  502. static $Subscription_IDs = array(); 
  503. $args = self::get_query_args( $args ); 
  504. $key = md5( json_encode( $args ) ); 
  505.  
  506. if ( ! isset( $Subscription_IDs[$key] ) ) { 
  507. $Subscription_IDs[$key] = array(); 
  508.  
  509. MS_Factory::select_blog(); 
  510. $query = new WP_Query( $args ); 
  511. $items = $query->posts; 
  512. MS_Factory::revert_blog(); 
  513. $subscriptions = array(); 
  514.  
  515. /** 
  516. * We only cache the IDs to avoid re-querying the database. 
  517. * The positive side effect is, that the memory used by the 
  518. * membership list will be freed again after the calling function 
  519. * is done with it. 
  520. * 
  521. * If we cache the whole list here, it would not occupy memory for 
  522. * the whole request duration which can cause memory_limit errors. 
  523. * 
  524. * @see MS_Model_Membership::get_memberships() 
  525. */ 
  526. foreach ( $items as $post_id ) { 
  527. $Subscription_IDs[$key][] = $post_id; 
  528.  
  529. return $Subscription_IDs[$key]; 
  530.  
  531. /** 
  532. * Retrieve membership relationships. 
  533. * 
  534. * By default returns a list of relationships that are not "pending" or 
  535. * "deactivated". To get a list of all relationships use this: 
  536. * $args = array( 'status' => 'all' ) 
  537. * 
  538. * @since 1.0.0 
  539. * @internal 
  540. * 
  541. * @param array $args The query post args 
  542. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  543. * @param bool $include_system Whether to include the base/guest memberships. 
  544. * @return MS_Model_Relationship[] The array of membership relationships. 
  545. */ 
  546. public static function get_subscriptions( $args = null, $include_system = false, $ordered = false ) { 
  547. $ids = self::get_subscription_ids( $args ); 
  548. $subscriptions = array(); 
  549.  
  550. foreach ( $ids as $id ) { 
  551. $subscription = MS_Factory::load( 
  552. 'MS_Model_Relationship',  
  553. $id 
  554. ); 
  555.  
  556. // Remove System-Memberships 
  557. if ( $subscription->is_system() && ! $include_system ) { 
  558. continue; 
  559.  
  560. if ( ! empty( $args['author'] ) ) { 
  561. $subscriptions[ $subscription->membership_id ] = $subscription; 
  562. } else { 
  563. $subscriptions[ $id ] = $subscription; 
  564.  
  565. $subscriptions = apply_filters( 
  566. 'ms_model_relationship_get_subscriptions',  
  567. $subscriptions,  
  568. $args 
  569. ); 
  570.  
  571. // Sort the subscription list. 
  572. usort( 
  573. $subscriptions,  
  574. array( __CLASS__, 'sort_by_priority' ) 
  575. ); 
  576.  
  577. return $subscriptions; 
  578.  
  579. /** 
  580. * Sort function used as second param by `uasort()` to sort a subscription 
  581. * list by membership priority. 
  582. * Memberships with equal priority are sorted alphabeically. 
  583. * 
  584. * @since 1.0.1.0 
  585. * @param MS_Model_Relationship $a 
  586. * @param MS_Model_Relationship $b 
  587. * @return int -1: a < b | 0: a = b | +1: a > b 
  588. */ 
  589. static public function sort_by_priority( $a, $b ) { 
  590. $m1 = $a->get_membership(); 
  591. $m2 = $b->get_membership(); 
  592.  
  593. if ( $m1->priority == $m2->priority ) { 
  594. return $m1->name < $m2->name ? -1 : 1; 
  595. } else { 
  596. return $m1->priority - $m2->priority; 
  597.  
  598. /** 
  599. * Retrieve membership relationship count. 
  600. * 
  601. * @since 1.0.0 
  602. * @internal 
  603. * 
  604. * @param $args The query post args 
  605. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  606. * @return int The membership relationship count. 
  607. */ 
  608. public static function get_subscription_count( $args = null ) { 
  609. $ids = self::get_subscription_ids( $args ); 
  610. $count = count( $ids ); 
  611.  
  612. return apply_filters( 
  613. 'ms_model_relationship_get_subscription_count',  
  614. $count,  
  615. $args 
  616. ); 
  617.  
  618. /** 
  619. * Retrieve membership relationship. 
  620. * 
  621. * @since 1.0.0 
  622. * @internal 
  623. * 
  624. * @param int $user_id The user id 
  625. * @return int $membership_id The membership id. 
  626. */ 
  627. public static function get_subscription( $user_id, $membership_id ) { 
  628. $args = apply_filters( 
  629. 'ms_model_relationship_get_subscription_args',  
  630. self::get_query_args( 
  631. array( 
  632. 'user_id' => $user_id,  
  633. 'membership_id' => $membership_id,  
  634. 'status' => 'all',  
  635. ); 
  636.  
  637. MS_Factory::select_blog(); 
  638. $query = new WP_Query( $args ); 
  639. $post = $query->posts; 
  640. MS_Factory::revert_blog(); 
  641.  
  642. $subscription = null; 
  643.  
  644. if ( ! empty( $post[0] ) ) { 
  645. $subscription = MS_Factory::load( 
  646. 'MS_Model_Relationship',  
  647. $post[0] 
  648. ); 
  649.  
  650. return apply_filters( 
  651. 'ms_model_relationship_get_subscription',  
  652. $subscription,  
  653. $args 
  654. ); 
  655.  
  656. /** 
  657. * Create default args to search posts. 
  658. * 
  659. * Merge received args to default ones. 
  660. * 
  661. * @since 1.0.0 
  662. * @internal 
  663. * 
  664. * @param $args The query post args 
  665. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  666. * @return array The args. 
  667. */ 
  668. public static function get_query_args( $args = null ) { 
  669. $defaults = apply_filters( 
  670. 'ms_model_relationship_get_query_args_defaults',  
  671. array( 
  672. 'post_type' => self::get_post_type(),  
  673. 'post_status' => 'any',  
  674. 'fields' => 'ids',  
  675. 'nopaging' => true,  
  676. ); 
  677.  
  678. $args = wp_parse_args( $args, $defaults ); 
  679.  
  680. // Set filter arguments 
  681. if ( ! empty( $args['user_id'] ) ) { 
  682. $args['author'] = $args['user_id']; 
  683. unset( $args['user_id'] ); 
  684.  
  685. if ( ! empty( $args['membership_id'] ) ) { 
  686. $args['meta_query']['membership_id'] = array( 
  687. 'key' => 'membership_id',  
  688. 'value' => $args['membership_id'],  
  689. ); 
  690. unset( $args['membership_id'] ); 
  691.  
  692. if ( ! empty( $args['gateway_id'] ) ) { 
  693. $args['meta_query']['gateway_id'] = array( 
  694. 'key' => 'gateway_id',  
  695. 'value' => $args['gateway_id'],  
  696. ); 
  697. unset( $args['gateway_id'] ); 
  698.  
  699. if ( ! empty( $args['status'] ) ) { 
  700. // Allowed status filters: 
  701. // 'valid' .. all status values except Deactivated 
  702. // <any other value except 'all'> 
  703. switch ( $args['status'] ) { 
  704. case 'valid': 
  705. $args['meta_query']['status'] = array( 
  706. 'key' => 'status',  
  707. 'value' => self::STATUS_DEACTIVATED,  
  708. 'compare' => 'NOT LIKE',  
  709. ); 
  710. break; 
  711.  
  712. case 'exp': 
  713. $args['meta_query']['status'] = array( 
  714. 'key' => 'status',  
  715. 'value' => array( self::STATUS_TRIAL_EXPIRED, self::STATUS_EXPIRED ),  
  716. 'compare' => 'IN',  
  717. ); 
  718. break; 
  719.  
  720. case 'all': 
  721. // No params for this. We want all items! 
  722. break; 
  723.  
  724. default: 
  725. $args['meta_query']['status'] = array( 
  726. 'key' => 'status',  
  727. 'value' => $args['status'],  
  728. 'compare' => '=',  
  729. ); 
  730. break; 
  731.  
  732. // This is only reached when status === 'all' 
  733. unset( $args['status'] ); 
  734. } else { 
  735. $args['meta_query']['status'] = array( 
  736. 'key' => 'status',  
  737. 'value' => array( self::STATUS_DEACTIVATED, self::STATUS_PENDING ),  
  738. 'compare' => 'NOT IN',  
  739. ); 
  740.  
  741. return apply_filters( 
  742. 'ms_model_relationship_get_query_args',  
  743. $args,  
  744. $defaults 
  745. ); 
  746.  
  747.  
  748. // 
  749. // 
  750. // 
  751. // ------------------------------------------------------------- SINGLE ITEM 
  752.  
  753.  
  754. /** 
  755. * Cancel membership. 
  756. * 
  757. * @since 1.0.0 
  758. * @api 
  759. * 
  760. * @param bool $generate_event Optional. Defines if cancel events are generated. 
  761. */ 
  762. public function cancel_membership( $generate_event = true ) { 
  763. do_action( 
  764. 'ms_model_relationship_cancel_membership_before',  
  765. $this,  
  766. $generate_event 
  767. ); 
  768.  
  769. if ( self::STATUS_CANCELED == $this->status ) { return; } 
  770. if ( self::STATUS_DEACTIVATED == $this->status ) { return; } 
  771.  
  772. try { 
  773. // Canceling in trial period -> change the expired date. 
  774. if ( self::STATUS_TRIAL == $this->status ) { 
  775. $this->expire_date = $this->trial_expire_date; 
  776.  
  777. $this->status = $this->calculate_status( self::STATUS_CANCELED ); 
  778. $this->save(); 
  779.  
  780. // Cancel subscription in the gateway. 
  781. if ( $gateway = $this->get_gateway() ) { 
  782. $gateway->cancel_membership( $this ); 
  783.  
  784. // Remove any unpaid invoices. 
  785. $this->remove_unpaid_invoices(); 
  786.  
  787. if ( $generate_event ) { 
  788. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_CANCELED, $this ); 
  789. catch ( Exception $e ) { 
  790. MS_Helper_Debug::log( '[Error canceling membership]: '. $e->getMessage() ); 
  791.  
  792. do_action( 
  793. 'ms_model_relationship_cancel_membership_after',  
  794. $this,  
  795. $generate_event 
  796. ); 
  797.  
  798. /** 
  799. * Deactivate membership. 
  800. * 
  801. * Cancel membership and move to deactivated state. 
  802. * 
  803. * @since 1.0.0 
  804. * @api 
  805. */ 
  806. public function deactivate_membership() { 
  807. do_action( 
  808. 'ms_model_relationship_deactivate_membership_before',  
  809. $this 
  810. ); 
  811.  
  812. /** 
  813. * Documented in check_membership_status() 
  814. * 
  815. * @since 1.0.0 
  816. */ 
  817. if ( MS_Plugin::get_modifier( 'MS_LOCK_SUBSCRIPTIONS' ) ) { 
  818. return false; 
  819.  
  820. if ( self::STATUS_DEACTIVATED == $this->status ) { return; } 
  821.  
  822. try { 
  823. $this->cancel_membership( false ); 
  824. $this->status = self::STATUS_DEACTIVATED; 
  825. $this->save(); 
  826.  
  827. MS_Model_Event::save_event( 
  828. MS_Model_Event::TYPE_MS_DEACTIVATED,  
  829. $this 
  830. ); 
  831. catch( Exception $e ) { 
  832. MS_Helper_Debug::log( 
  833. '[Error deactivating membership]: '. $e->getMessage() 
  834. ); 
  835.  
  836. do_action( 
  837. 'ms_model_relationship_deactivate_membership_after',  
  838. $this 
  839. ); 
  840.  
  841. /** 
  842. * Save model. 
  843. * 
  844. * Only saves if is not admin user and not a visitor. 
  845. * Don't save automatically assigned visitor/system memberships. 
  846. * 
  847. * @since 1.0.0 
  848. * @api 
  849. */ 
  850. public function save() { 
  851. do_action( 'ms_model_relationship_save_before', $this ); 
  852.  
  853. if ( ! empty( $this->user_id ) 
  854. && ! MS_Model_Member::is_admin_user( $this->user_id ) 
  855. ) { 
  856. if ( ! $this->is_system() ) { 
  857. parent::save(); 
  858. parent::store_singleton(); 
  859.  
  860. do_action( 'ms_model_relationship_after', $this ); 
  861.  
  862. /** 
  863. * Removes any unpaid invoice that belongs to this subscription. 
  864. * 
  865. * @since 1.0.0 
  866. * @internal 
  867. */ 
  868. public function remove_unpaid_invoices() { 
  869. $invoices = $this->get_invoices(); 
  870.  
  871. foreach ( $invoices as $invoice ) { 
  872. if ( 'paid' != $invoice->status ) { 
  873. $invoice->delete(); 
  874.  
  875. /** 
  876. * Verify if the member can use the trial period. 
  877. * 
  878. * It returns FALSE if 
  879. * .. Trial Add-on is disabled 
  880. * .. The membership does not allow a trial period 
  881. * .. The current user already consumed trial period or is in trial period 
  882. * 
  883. * @since 1.0.0 
  884. * @api 
  885. * 
  886. * @return bool True if trial eligible. 
  887. */ 
  888. public function is_trial_eligible() { 
  889. $membership = $this->get_membership(); 
  890.  
  891. $trial_eligible_status = apply_filters( 
  892. 'ms_model_relationship_trial_eligible_status',  
  893. array( 
  894. self::STATUS_PENDING,  
  895. self::STATUS_DEACTIVATED,  
  896. ); 
  897.  
  898. $eligible = false; 
  899.  
  900. if ( ! $membership->has_trial() ) { 
  901. // Trial Membership is globally disabled. 
  902. $eligible = false; 
  903. } elseif ( self::STATUS_TRIAL == $this->status ) { 
  904. // Subscription IS already in trial, so it's save to assume true. 
  905. $eligible = true; 
  906. } elseif ( ! in_array( $this->status, $trial_eligible_status ) ) { 
  907. // Current Subscription is not allowed for a trial membership anymore. 
  908. $eligible = false; 
  909. } elseif ( $this->trial_period_completed ) { 
  910. // Trial membership already consumed. 
  911. $eligible = false; 
  912. } else { 
  913. // All other cases: User can sign up for trial! 
  914. $eligible = true; 
  915.  
  916. return apply_filters( 
  917. 'ms_model_relationship_is_trial_eligible',  
  918. $eligible,  
  919. $this 
  920. ); 
  921.  
  922. /** 
  923. * Returns true if the current subscription is expired. 
  924. * 
  925. * @since 1.0.1.0 
  926. * @return bool 
  927. */ 
  928. public function is_expired() { 
  929. $result = false; 
  930.  
  931. if ( self::STATUS_EXPIRED == $this->status ) { 
  932. $result = true; 
  933. } elseif ( self::STATUS_TRIAL_EXPIRED == $this->status ) { 
  934. $result = true; 
  935.  
  936. return apply_filters( 
  937. 'ms_model_relationship_is_expired',  
  938. $result,  
  939. $this 
  940. ); 
  941.  
  942. /** 
  943. * Checks if the current subscription consumes a trial period. 
  944. * 
  945. * When the subscription either is currently in trial or was in trial before 
  946. * then this function returns true. 
  947. * If the subscription never was in trial status it returns false. 
  948. * 
  949. * @since 1.0.0 
  950. * @api 
  951. * 
  952. * @return bool 
  953. */ 
  954. public function has_trial() { 
  955. $result = false; 
  956.  
  957. if ( ! MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_TRIAL ) ) { 
  958. $result = false; 
  959. } elseif ( ! $this->trial_expire_date ) { 
  960. $result = false; 
  961. } elseif ( $this->trial_expire_date == $this->start_date ) { 
  962. $result = false; 
  963. } else { 
  964. $result = true; 
  965.  
  966. return $result; 
  967.  
  968. /** 
  969. * Set Membership Relationship start date. 
  970. * 
  971. * @since 1.0.0 
  972. * @api 
  973. * 
  974. * @param string $start_date Optional. The start date to set. 
  975. * Default will be calculated/current date. 
  976. */ 
  977. public function set_start_date( $start_date = null ) { 
  978. $membership = $this->get_membership(); 
  979.  
  980. if ( empty( $start_date ) ) { 
  981. if ( MS_Model_Membership::PAYMENT_TYPE_DATE_RANGE == $membership->payment_type ) { 
  982. $start_date = $membership->period_date_start; 
  983. } else { 
  984. /** 
  985. * Note that we pass TRUE as second param to current_date 
  986. * This is needed so that we 100% use the current date, which 
  987. * is required to successfully do simulation. 
  988. */ 
  989. $start_date = MS_Helper_Period::current_date( null, true ); 
  990.  
  991. $this->start_date = apply_filters( 
  992. 'ms_model_relationship_set_start_date',  
  993. $start_date,  
  994. $this 
  995. ); 
  996.  
  997. /** 
  998. * Set trial expire date. 
  999. * 
  1000. * Validate to a date greater than start date. 
  1001. * 
  1002. * @since 1.0.0 
  1003. * @api 
  1004. * 
  1005. * @param string $trial_expire_date Optional. The trial expire date to set. 
  1006. * Default will be calculated based on start_date. 
  1007. */ 
  1008. public function set_trial_expire_date( $trial_expire_date = null ) { 
  1009. if ( $this->is_trial_eligible() ) { 
  1010. $valid_date = MS_Helper_Period::is_after( 
  1011. $trial_expire_date,  
  1012. $this->start_date 
  1013. ); 
  1014.  
  1015. if ( ! $valid_date ) { 
  1016. $trial_expire_date = $this->calc_trial_expire_date( $this->start_date ); 
  1017.  
  1018. /** 
  1019. * When payment-type is DATE-RANGE make sure that the trial period 
  1020. * is not longer than the specified end-date 
  1021. */ 
  1022. $membership = $this->get_membership(); 
  1023. if ( MS_Model_Membership::PAYMENT_TYPE_DATE_RANGE == $membership->payment_type ) { 
  1024. if ( $membership->period_date_end < $trial_expire_date ) { 
  1025. $trial_expire_date = $membership->period_date_end; 
  1026. } else { 
  1027. // Do NOT set any trial-expire-date when trial period is not available! 
  1028. $trial_expire_date = ''; 
  1029.  
  1030. $this->trial_expire_date = apply_filters( 
  1031. 'ms_model_relationship_set_trial_start_date',  
  1032. $trial_expire_date,  
  1033. $this 
  1034. ); 
  1035.  
  1036. // Subscriptions with this status have no valid expire-date. 
  1037. $no_expire_date = array( 
  1038. self::STATUS_DEACTIVATED,  
  1039. self::STATUS_PENDING,  
  1040. self::STATUS_TRIAL,  
  1041. self::STATUS_TRIAL_EXPIRED,  
  1042. ); 
  1043.  
  1044. if ( $this->trial_expire_date && in_array( $this->status, $no_expire_date ) ) { 
  1045. // Set the expire date to trial-expire date 
  1046. $this->expire_date = $this->trial_expire_date; 
  1047.  
  1048. /** 
  1049. * Set trial expire date. 
  1050. * 
  1051. * Validate to a date greater than start date and trial expire date. 
  1052. * 
  1053. * @since 1.0.0 
  1054. * @api 
  1055. * 
  1056. * @param string $expire_date Optional. The expire date to set. 
  1057. * Default will be calculated based on start_date. 
  1058. */ 
  1059. public function set_expire_date( $expire_date = null ) { 
  1060. $no_expire_date = array( 
  1061. self::STATUS_DEACTIVATED,  
  1062. self::STATUS_PENDING,  
  1063. ); 
  1064.  
  1065. if ( ! in_array( $this->status, $no_expire_date ) ) { 
  1066. $valid_date = MS_Helper_Period::is_after( 
  1067. $expire_date,  
  1068. $this->start_date,  
  1069. $this->trial_expire_date 
  1070. ); 
  1071. if ( ! $valid_date ) { 
  1072. $expire_date = $this->calc_expire_date( $this->start_date ); 
  1073. } else { 
  1074. // Do NOT set any expire-date when subscription is not active! 
  1075. $expire_date = ''; 
  1076.  
  1077. $this->expire_date = apply_filters( 
  1078. 'ms_model_relationship_set_expire_date',  
  1079. $expire_date,  
  1080. $this 
  1081. ); 
  1082.  
  1083. /** 
  1084. * Calculate trial expire date. 
  1085. * 
  1086. * Based in the membership definition. 
  1087. * 
  1088. * @since 1.0.0 
  1089. * @internal 
  1090. * 
  1091. * @param string $start_date Optional. The start date to calculate date from. 
  1092. * @return string The calculated trial expire date. 
  1093. */ 
  1094. public function calc_trial_expire_date( $start_date = null ) { 
  1095. $membership = $this->get_membership(); 
  1096. $trial_expire_date = null; 
  1097.  
  1098. if ( empty( $start_date ) ) { 
  1099. $start_date = $this->start_date; 
  1100. if ( empty( $start_date ) ) { 
  1101. $start_date = MS_Helper_Period::current_date(); 
  1102.  
  1103. if ( $this->is_trial_eligible() ) { 
  1104. // Trial period was not consumed yet, calculate the expiration date. 
  1105.  
  1106. if ( MS_Model_Membership::PAYMENT_TYPE_DATE_RANGE == $membership->payment_type ) { 
  1107. $from_date = $membership->period_date_start; 
  1108. } else { 
  1109. $from_date = $start_date; 
  1110.  
  1111. $period_unit = MS_Helper_Period::get_period_value( 
  1112. $membership->trial_period,  
  1113. 'period_unit' 
  1114. ); 
  1115. $period_type = MS_Helper_Period::get_period_value( 
  1116. $membership->trial_period,  
  1117. 'period_type' 
  1118. ); 
  1119.  
  1120. $trial_expire_date = MS_Helper_Period::add_interval( 
  1121. $period_unit,  
  1122. $period_type,  
  1123. $from_date 
  1124. ); 
  1125. } else { 
  1126. // Subscription not entitled for trial anymore. Trial expires instantly. 
  1127. $trial_expire_date = $start_date; 
  1128.  
  1129. return apply_filters( 
  1130. 'ms_model_relationship_calc_trial_expire_date',  
  1131. $trial_expire_date,  
  1132. $start_date,  
  1133. $this 
  1134. ); 
  1135.  
  1136. /** 
  1137. * Calculate expire date. 
  1138. * 
  1139. * Based in the membership definition 
  1140. * 
  1141. * @since 1.0.0 
  1142. * @internal 
  1143. * 
  1144. * @param string $start_date Optional. The start date to calculate date from. 
  1145. * @param bool $paid If the user made a payment to extend the expire date. 
  1146. * @return string The calculated expire date. 
  1147. */ 
  1148. public function calc_expire_date( $start_date = null, $paid = false ) { 
  1149. $membership = $this->get_membership(); 
  1150. $gateway = $this->get_gateway(); 
  1151.  
  1152. $start_date = $this->calc_trial_expire_date( $start_date ); 
  1153. $expire_date = null; 
  1154.  
  1155. /** 
  1156. * When in trial period and gateway does not send automatic recurring 
  1157. * payment notifications, the expire date is equal to trial expire date. 
  1158. */ 
  1159. if ( $this->is_trial_eligible() ) { 
  1160. $expire_date = $start_date; 
  1161. } else { 
  1162. if ( $paid ) { 
  1163. /** 
  1164. * Always extend the membership from current date or later, even if 
  1165. * the specified start-date is in the past. 
  1166. * 
  1167. * Example: User does not pay for 3 days (subscription set "pending") 
  1168. * Then he pays: The 3 days without access are for free; 
  1169. * his subscriptions is extended from current date! 
  1170. */ 
  1171. $today = MS_Helper_Period::current_date(); 
  1172. if ( MS_Helper_Period::is_after( $today, $start_date ) ) { 
  1173. $start_date = $today; 
  1174.  
  1175. /** 
  1176. * The gatway calls the payment handler URL automatically: 
  1177. * This means that the user does not need to re-authorize each 
  1178. * payment. 
  1179. */ 
  1180. switch ( $membership->payment_type ) { 
  1181. case MS_Model_Membership::PAYMENT_TYPE_PERMANENT: 
  1182. $expire_date = false; 
  1183. break; 
  1184.  
  1185. case MS_Model_Membership::PAYMENT_TYPE_FINITE: 
  1186. $period_unit = MS_Helper_Period::get_period_value( 
  1187. $membership->period,  
  1188. 'period_unit' 
  1189. ); 
  1190. $period_type = MS_Helper_Period::get_period_value( 
  1191. $membership->period,  
  1192. 'period_type' 
  1193. ); 
  1194. $expire_date = MS_Helper_Period::add_interval( 
  1195. $period_unit,  
  1196. $period_type,  
  1197. $start_date 
  1198. ); 
  1199. break; 
  1200.  
  1201. case MS_Model_Membership::PAYMENT_TYPE_DATE_RANGE: 
  1202. $expire_date = $membership->period_date_end; 
  1203. break; 
  1204.  
  1205. case MS_Model_Membership::PAYMENT_TYPE_RECURRING: 
  1206. $period_unit = MS_Helper_Period::get_period_value( 
  1207. $membership->pay_cycle_period,  
  1208. 'period_unit' 
  1209. ); 
  1210. $period_type = MS_Helper_Period::get_period_value( 
  1211. $membership->pay_cycle_period,  
  1212. 'period_type' 
  1213. ); 
  1214. $expire_date = MS_Helper_Period::add_interval( 
  1215. $period_unit,  
  1216. $period_type,  
  1217. $start_date 
  1218. ); 
  1219. break; 
  1220.  
  1221. return apply_filters( 
  1222. 'ms_model_relationship_calc_expire_date',  
  1223. $expire_date,  
  1224. $this 
  1225. ); 
  1226.  
  1227. /** 
  1228. * Configure the membership period dates based on the current subscription 
  1229. * status. 
  1230. * 
  1231. * Set initial membership period or renew periods. 
  1232. * 
  1233. * @since 1.0.0 
  1234. * @internal 
  1235. */ 
  1236. public function config_period() { // Needed because of status change. 
  1237. do_action( 
  1238. 'ms_model_relationship_config_period_before',  
  1239. $this 
  1240. ); 
  1241.  
  1242. switch ( $this->status ) { 
  1243. case self::STATUS_DEACTIVATED: 
  1244. case self::STATUS_PENDING: 
  1245. // Set initial start, trial and expire date. 
  1246. $this->set_start_date(); 
  1247. $this->set_trial_expire_date(); 
  1248. $this->set_expire_date(); 
  1249. break; 
  1250.  
  1251. case self::STATUS_EXPIRED: 
  1252. case self::STATUS_CANCELED: 
  1253. case self::STATUS_ACTIVE: 
  1254. /** 
  1255. * If no expire date is set yet then add it now. 
  1256. * This case happens when the subscription was added via the 
  1257. * Members admin page (by an admin and not by the member) 
  1258. * 
  1259. * Usually nothing is done here as the expire date is only 
  1260. * changed by add_payment(). 
  1261. */ 
  1262. if ( empty( $this->expire_date ) ) { 
  1263. $membership = $this->get_membership(); 
  1264. if ( MS_Model_Membership::PAYMENT_TYPE_PERMANENT != $membership->payment_type ) { 
  1265. $this->set_expire_date(); 
  1266. break; 
  1267.  
  1268. case self::STATUS_TRIAL: 
  1269. case self::STATUS_TRIAL_EXPIRED: 
  1270. $this->set_trial_expire_date(); 
  1271. break; 
  1272.  
  1273. default: 
  1274. do_action( 
  1275. 'ms_model_relationship_config_period_for_status_' . $this->status,  
  1276. $this 
  1277. ); 
  1278. break; 
  1279.  
  1280. do_action( 
  1281. 'ms_model_relationship_config_period_after',  
  1282. $this 
  1283. ); 
  1284.  
  1285. /** 
  1286. * Returns the number of days since the subscription started. 
  1287. * 
  1288. * Example: If the start_date is 14 days ago it will return the value 14. 
  1289. * 
  1290. * @since 1.0.0 
  1291. * @api 
  1292. * 
  1293. * @return int Remaining days. 
  1294. */ 
  1295. public function get_current_period() { 
  1296. $period_days = MS_Helper_Period::subtract_dates( 
  1297. MS_Helper_Period::current_date(),  
  1298. $this->start_date 
  1299. ); 
  1300.  
  1301. return apply_filters( 
  1302. 'ms_model_relationship_get_current_period',  
  1303. $period_days,  
  1304. $this 
  1305. ); 
  1306.  
  1307. /** 
  1308. * Returns the number of days until trial period ends. 
  1309. * 
  1310. * @since 1.0.0 
  1311. * @api 
  1312. * 
  1313. * @return int Remaining days. 
  1314. */ 
  1315. public function get_remaining_trial_period() { 
  1316. $period_days = MS_Helper_Period::subtract_dates( 
  1317. $this->trial_expire_date,  
  1318. MS_Helper_Period::current_date() 
  1319. ); 
  1320.  
  1321. return apply_filters( 
  1322. 'ms_model_relationship_get_remaining_trial_period',  
  1323. $period_days,  
  1324. $this 
  1325. ); 
  1326.  
  1327. /** 
  1328. * Get number of days until this membership expires. 
  1329. * 
  1330. * @since 1.0.0 
  1331. * @api 
  1332. * 
  1333. * @return int Remaining days. 
  1334. */ 
  1335. public function get_remaining_period() { 
  1336. $period_days = MS_Helper_Period::subtract_dates( 
  1337. $this->expire_date,  
  1338. MS_Helper_Period::current_date() 
  1339. ); 
  1340.  
  1341. return apply_filters( 
  1342. 'ms_model_relationship_get_remaining_period',  
  1343. $period_days,  
  1344. $this 
  1345. ); 
  1346.  
  1347. /** 
  1348. * Get Member model of the subscription owner. 
  1349. * 
  1350. * @since 1.0.0 
  1351. * @api 
  1352. * 
  1353. * @return MS_Model_Member The member object. 
  1354. */ 
  1355. public function get_member() { 
  1356. $member = null; 
  1357.  
  1358. if ( ! empty( $this->user_id ) ) { 
  1359. $member = MS_Factory::load( 'MS_Model_Member', $this->user_id ); 
  1360.  
  1361. return apply_filters( 
  1362. 'ms_model_relationship_get_member',  
  1363. $member 
  1364. ); 
  1365.  
  1366. /** 
  1367. * Convenience function to access current invoice for this subscription. 
  1368. * 
  1369. * @since 1.0.0 
  1370. * @api 
  1371. * 
  1372. * @return MS_Model_Invoice 
  1373. */ 
  1374. public function get_current_invoice( $create_missing = true ) { 
  1375. return MS_Model_Invoice::get_current_invoice( $this, $create_missing ); 
  1376.  
  1377. /** 
  1378. * Convenience function to access next invoice for this subscription. 
  1379. * 
  1380. * @since 1.0.0 
  1381. * @api 
  1382. * 
  1383. * @return MS_Model_Invoice 
  1384. */ 
  1385. public function get_next_invoice( $create_missing = true ) { 
  1386. return MS_Model_Invoice::get_next_invoice( $this, $create_missing ); 
  1387.  
  1388. /** 
  1389. * Convenience function to access previous invoice for this subscription. 
  1390. * 
  1391. * @since 1.0.0 
  1392. * @api 
  1393. * 
  1394. * @return MS_Model_Invoice 
  1395. */ 
  1396. public function get_previous_invoice( $status = null ) { 
  1397. return MS_Model_Invoice::get_previous_invoice( $this, $status ); 
  1398.  
  1399. /** 
  1400. * Get a list of all invoices linked to this relationship 
  1401. * 
  1402. * @since 1.0.0 
  1403. * @api 
  1404. * 
  1405. * @return MS_Model_Invoice[] List of invoices. 
  1406. */ 
  1407. public function get_invoices() { 
  1408. $invoices = MS_Model_Invoice::get_invoices( 
  1409. array( 
  1410. 'nopaging' => true,  
  1411. 'meta_query' => array( 
  1412. array( 
  1413. 'key' => 'ms_relationship_id',  
  1414. 'value' => $this->id,  
  1415. ),  
  1416. ),  
  1417. ); 
  1418.  
  1419. return apply_filters( 
  1420. 'ms_model_relationship_get_invoices',  
  1421. $invoices 
  1422. ); 
  1423.  
  1424. /** 
  1425. * Finds the first unpaid invoice of the current subscription and returns 
  1426. * the invoice_id. 
  1427. * 
  1428. * If the subscription has no unpaid invoices then a new invoice is created! 
  1429. * 
  1430. * @since 1.0.2.0 
  1431. * @return int The first invoice that is not paid yet. 
  1432. */ 
  1433. public function first_unpaid_invoice() { 
  1434. $invoice_id = 0; 
  1435.  
  1436. // Try to find the first unpaid invoice for the subscription. 
  1437. $invoices = $this->get_invoices(); 
  1438. foreach ( $invoices as $invoice ) { 
  1439. if ( ! $invoice->is_paid() ) { 
  1440. $invoice_id = $invoice->id; 
  1441. break; 
  1442.  
  1443. // If no unpaid invoice was found: Create one. 
  1444. if ( ! $invoice_id ) { 
  1445. $invoice = $this->get_next_invoice(); 
  1446. $invoice_id = $invoice->id; 
  1447.  
  1448. return $invoice_id; 
  1449.  
  1450. /** 
  1451. * Get related Membership model. 
  1452. * 
  1453. * @since 1.0.0 
  1454. * @api 
  1455. * 
  1456. * @return MS_Model_Membership The membership model. 
  1457. */ 
  1458. public function get_membership() { 
  1459. if ( empty( $this->membership->id ) ) { 
  1460. $this->membership = MS_Factory::load( 
  1461. 'MS_Model_Membership',  
  1462. $this->membership_id,  
  1463. $this->id 
  1464. ); 
  1465.  
  1466. // Set the context of the membership to current subscription. 
  1467. $this->membership->subscription_id = $this->id; 
  1468.  
  1469. return apply_filters( 
  1470. 'ms_model_relationship_get_membership',  
  1471. $this->membership 
  1472. ); 
  1473.  
  1474. /** 
  1475. * Returns true if the related membership is the base-membership. 
  1476. * 
  1477. * @since 1.0.0 
  1478. * @api 
  1479. * 
  1480. * @return bool 
  1481. */ 
  1482. public function is_base() { 
  1483. return $this->get_membership()->is_base(); 
  1484.  
  1485. /** 
  1486. * Returns true if the related membership is the guest-membership. 
  1487. * 
  1488. * @since 1.0.0 
  1489. * @api 
  1490. * 
  1491. * @return bool 
  1492. */ 
  1493. public function is_guest() { 
  1494. return $this->get_membership()->is_guest(); 
  1495.  
  1496. /** 
  1497. * Returns true if the related membership is the user-membership. 
  1498. * 
  1499. * @since 1.0.0 
  1500. * @api 
  1501. * 
  1502. * @return bool 
  1503. */ 
  1504. public function is_user() { 
  1505. return $this->get_membership()->is_user(); 
  1506.  
  1507. /** 
  1508. * Returns true if the related membership is a system membership. 
  1509. * 
  1510. * @since 1.0.0 
  1511. * @api 
  1512. * 
  1513. * @return bool 
  1514. */ 
  1515. public function is_system() { 
  1516. return $this->get_membership()->is_system(); 
  1517.  
  1518. /** 
  1519. * Get related gateway model. 
  1520. * 
  1521. * @since 1.0.0 
  1522. * @api 
  1523. * 
  1524. * @return MS_Model_Gateway 
  1525. */ 
  1526. public function get_gateway() { 
  1527. $gateway = MS_Model_Gateway::factory( $this->gateway_id ); 
  1528.  
  1529. return apply_filters( 
  1530. 'ms_model_relationship_get_gateway',  
  1531. $gateway 
  1532. ); 
  1533.  
  1534. /** 
  1535. * Either creates or updates the value of a custom data field. 
  1536. * 
  1537. * Note: Remember to prefix the $key with a unique string to prevent 
  1538. * conflicts with other plugins that also use this function. 
  1539. * 
  1540. * @since 1.0.0 
  1541. * @api 
  1542. * 
  1543. * @param string $key The field-key. 
  1544. * @param mixed $value The new value to assign to the field. 
  1545. */ 
  1546. public function set_custom_data( $key, $value ) { 
  1547. // Wrapper function, so this function shows up in API docs. 
  1548. parent::set_custom_data( $key, $value ); 
  1549.  
  1550. /** 
  1551. * Removes a custom data field from this object. 
  1552. * 
  1553. * @since 1.0.0 
  1554. * @api 
  1555. * 
  1556. * @param string $key The field-key. 
  1557. */ 
  1558. public function delete_custom_data( $key ) { 
  1559. // Wrapper function, so this function shows up in API docs. 
  1560. parent::delete_custom_data( $key ); 
  1561.  
  1562. /** 
  1563. * Returns the value of a custom data field. 
  1564. * 
  1565. * @since 1.0.0 
  1566. * @api 
  1567. * 
  1568. * @param string $key The field-key. 
  1569. * @return mixed The value that was previously assigned to the custom field 
  1570. * or false if no value was set for the field. 
  1571. */ 
  1572. public function get_custom_data( $key ) { 
  1573. // Wrapper function, so this function shows up in API docs. 
  1574. return parent::get_custom_data( $key ); 
  1575.  
  1576. /** 
  1577. * Get textual payment information description. 
  1578. * 
  1579. * @since 1.0.0 
  1580. * @api 
  1581. * 
  1582. * @param MS_Model_Invoice $invoice Optional. Specific invoice that defines 
  1583. * the price. Default is the price defined in the membership. 
  1584. * @param bool $short Optional. Default is false. If set to true then a 
  1585. * hort sumary is returned 
  1586. * @return string The description. 
  1587. */ 
  1588. public function get_payment_description( $invoice = null, $short = false ) { 
  1589. $currency = MS_Plugin::instance()->settings->currency; 
  1590. $membership = $this->get_membership(); 
  1591. $desc = ''; 
  1592.  
  1593. if ( null !== $invoice ) { 
  1594. $total_price = $invoice->total; // Includes Tax 
  1595. $trial_price = $invoice->trial_price; // Includes Tax 
  1596. } else { 
  1597. $total_price = $membership->total_price; // Excludes Tax 
  1598. $trial_price = $membership->trial_price; // Excludes Tax 
  1599.  
  1600. $total_price = MS_Helper_Billing::format_price( $total_price ); 
  1601. $trial_price = MS_Helper_Billing::format_price( $trial_price ); 
  1602.  
  1603. $payment_type = $this->payment_type; 
  1604. if ( ! $payment_type ) { 
  1605. $payment_type = $membership->payment_type; 
  1606.  
  1607. switch ( $payment_type ) { 
  1608. case MS_Model_Membership::PAYMENT_TYPE_PERMANENT: 
  1609. if ( 0 == $total_price ) { 
  1610. if ( $short ) { 
  1611. $lbl = __( 'Nothing (for ever)', 'membership2' ); 
  1612. } else { 
  1613. $lbl = __( 'You will pay nothing for permanent access.', 'membership2' ); 
  1614. } else { 
  1615. if ( $short ) { 
  1616. $lbl = __( '<span class="price">%1$s %2$s</span> (for ever)', 'membership2' ); 
  1617. } else { 
  1618. $lbl = __( 'You will pay <span class="price">%1$s %2$s</span> for permanent access.', 'membership2' ); 
  1619.  
  1620. $desc = sprintf( 
  1621. $lbl,  
  1622. $currency,  
  1623. $total_price 
  1624. ); 
  1625. break; 
  1626.  
  1627. case MS_Model_Membership::PAYMENT_TYPE_FINITE: 
  1628. if ( 0 == $total_price ) { 
  1629. if ( $short ) { 
  1630. $lbl = __( 'Nothing (until %4$s)', 'membership2' ); 
  1631. } else { 
  1632. $lbl = __( 'You will pay nothing for access until %3$s.', 'membership2' ); 
  1633. } else { 
  1634. if ( $short ) { 
  1635. $lbl = __( '<span class="price">%1$s %2$s</span> (until %4$s)', 'membership2' ); 
  1636. } else { 
  1637. $lbl = __( 'You will pay <span class="price">%1$s %2$s</span> for access until %3$s.', 'membership2' ); 
  1638.  
  1639. $desc .= sprintf( 
  1640. $lbl,  
  1641. $currency,  
  1642. $total_price,  
  1643. MS_Helper_Period::format_date( $this->calc_expire_date( $this->expire_date ) ),  
  1644. $this->calc_expire_date( $this->expire_date ) 
  1645. ); 
  1646. break; 
  1647.  
  1648. case MS_Model_Membership::PAYMENT_TYPE_DATE_RANGE: 
  1649. if ( 0 == $total_price ) { 
  1650. if ( $short ) { 
  1651. $lbl = __( 'Nothing (%5$s - %6$s)', 'membership2' ); 
  1652. } else { 
  1653. $lbl = __( 'You will pay nothing for access from %3$s until %4$s.', 'membership2' ); 
  1654. } else { 
  1655. if ( $short ) { 
  1656. $lbl = __( '<span class="price">%1$s %2$s</span> (%5$s - %6$s)', 'membership2' ); 
  1657. } else { 
  1658. $lbl = __( 'You will pay <span class="price">%1$s %2$s</span> to access from %3$s until %4$s.', 'membership2' ); 
  1659.  
  1660. $desc .= sprintf( 
  1661. $lbl,  
  1662. $currency,  
  1663. $total_price,  
  1664. MS_Helper_Period::format_date( $membership->period_date_start ),  
  1665. MS_Helper_Period::format_date( $membership->period_date_end ),  
  1666. $membership->period_date_start,  
  1667. $membership->period_date_end 
  1668. ); 
  1669. break; 
  1670.  
  1671. case MS_Model_Membership::PAYMENT_TYPE_RECURRING: 
  1672. if ( 1 == $membership->pay_cycle_repetitions ) { 
  1673. // Exactly 1 payment. Actually same as the "finite" type. 
  1674. if ( $short ) { 
  1675. $lbl = __( '<span class="price">%1$s %2$s</span> (once)', 'membership2' ); 
  1676. } else { 
  1677. $lbl = __( 'You will pay <span class="price">%1$s %2$s</span> once.', 'membership2' ); 
  1678. } else { 
  1679. if ( $membership->pay_cycle_repetitions > 1 ) { 
  1680. // Fixed number of payments (more than 1) 
  1681. if ( $short ) { 
  1682. $lbl = __( '%4$s times <span class="price">%1$s %2$s</span> (each %3$s)', 'membership2' ); 
  1683. } else { 
  1684. $lbl = __( 'You will make %4$s payments of <span class="price">%1$s %2$s</span>, one each %3$s.', 'membership2' ); 
  1685. } else { 
  1686. // Indefinite number of payments 
  1687. if ( $short ) { 
  1688. $lbl = __( '<span class="price">%1$s %2$s</span> (each %3$s)', 'membership2' ); 
  1689. } else { 
  1690. $lbl = __( 'You will pay <span class="price">%1$s %2$s</span> each %3$s.', 'membership2' ); 
  1691.  
  1692. $desc .= sprintf( 
  1693. $lbl,  
  1694. $currency,  
  1695. $total_price,  
  1696. MS_Helper_Period::get_period_desc( $membership->pay_cycle_period ),  
  1697. $membership->pay_cycle_repetitions 
  1698. ); 
  1699. break; 
  1700.  
  1701. if ( $this->is_trial_eligible() && 0 != $total_price ) { 
  1702. if ( 0 == absint( $trial_price ) ) { 
  1703. if ( $short ) { 
  1704. if ( MS_Model_Membership::PAYMENT_TYPE_RECURRING == $payment_type ) { 
  1705. $lbl = __( 'after %4$s', 'membership2' ); 
  1706. } else { 
  1707. $lbl = __( 'on %4$s', 'membership2' ); 
  1708. } else { 
  1709. $trial_price = __( 'nothing', 'membership2' ); 
  1710. $lbl = __( 'The trial period of %1$s is for free.', 'membership2' ); 
  1711. } else { 
  1712. $trial_price = MS_Helper_Billing::format_price( $trial_price ); 
  1713. $lbl = __( 'For the trial period of %1$s you only pay <span class="price">%2$s %3$s</span>.', 'membership2' ); 
  1714.  
  1715. $desc .= sprintf( 
  1716. ' <br />' . $lbl,  
  1717. MS_Helper_Period::get_period_desc( $membership->trial_period, true ),  
  1718. $currency,  
  1719. $trial_price,  
  1720. MS_Helper_Period::format_date( $invoice->due_date, __( 'M j', 'membership2' ) ) 
  1721. ); 
  1722.  
  1723. return apply_filters( 
  1724. 'ms_model_relationship_get_payment_description',  
  1725. $desc,  
  1726. $membership,  
  1727. $payment_type 
  1728. ); 
  1729.  
  1730. /** 
  1731. * Saves information on a payment that was made. 
  1732. * This function also extends the expire_date for one period if the 
  1733. * membership payment-type is recurring or limited 
  1734. * 
  1735. * @since 1.0.0 
  1736. * @api 
  1737. * 
  1738. * @param float $amount The payment amount. Set to 0 for free subscriptions. 
  1739. * @param string $gateway The payment gateway-ID. 
  1740. * @param string $external_id A string that can identify the payment. 
  1741. * @return bool True if the subscription has ACTIVE status after payment. 
  1742. * If the amount was 0 and membership uses a trial period the status 
  1743. * could also be TRIAL, in which case the returnv alue is false. 
  1744. */ 
  1745. public function add_payment( $amount, $gateway, $external_id = '' ) { 
  1746. $this->payments = lib3()->array->get( $this->payments ); 
  1747.  
  1748. // Update the payment-gateway. 
  1749. if ( ! $this->gateway_id ) { 
  1750. $this->gateway_id = $gateway; 
  1751. } elseif ( MS_Gateway_Free::ID != $gateway ) { 
  1752. // Don't change an existing gateway to "Free". 
  1753. $this->gateway_id = $gateway; 
  1754.  
  1755. if ( $amount > 0 ) { 
  1756. $this->payments[] = array( 
  1757. 'date' => MS_Helper_Period::current_date( MS_Helper_Period::DATE_TIME_FORMAT ),  
  1758. 'amount' => $amount,  
  1759. 'gateway' => $gateway,  
  1760. 'external_id' => $external_id,  
  1761. ); 
  1762.  
  1763. // Upon first payment set the start date to current date. 
  1764. if ( 1 == count( $this->payments ) && ! $this->trial_expire_date ) { 
  1765. $this->set_start_date(); 
  1766.  
  1767. // Updates the subscription status. 
  1768. if ( MS_Gateway_Free::ID == $gateway && $this->is_trial_eligible() ) { 
  1769. // Calculate the final trial expire date. 
  1770.  
  1771. /** 
  1772. * Important: 
  1773. * FIRST set the TRIAL EXPIRE DATE, otherwise status is set to 
  1774. * active instead of trial! 
  1775. */ 
  1776. $this->set_trial_expire_date(); 
  1777.  
  1778. $this->set_status( self::STATUS_TRIAL ); 
  1779. } else { 
  1780. /** 
  1781. * Important: 
  1782. * FIRST set the SUBSCRIPTION STATUS, otherwise the expire date is 
  1783. * based on start_date instead of trial_expire_date! 
  1784. */ 
  1785. $this->set_status( self::STATUS_ACTIVE ); 
  1786.  
  1787. /** 
  1788. * Renew period. Every time this function is called, the expire 
  1789. * date is extended for 1 period 
  1790. */ 
  1791. $this->expire_date = $this->calc_expire_date( 
  1792. $this->expire_date, // Extend past the current expire date. 
  1793. true // Grant the user a full payment interval. 
  1794. ); 
  1795.  
  1796. $this->save(); 
  1797.  
  1798. // Thanks for paying or for starting your trial period! 
  1799. // You're officially active :) 
  1800. $member = $this->get_member(); 
  1801. $member->is_member = true; 
  1802.  
  1803. if ( self::STATUS_ACTIVE == $this->status ) { 
  1804. /** 
  1805. * Make sure the new subscription is instantly available in the 
  1806. * member object. 
  1807. * 
  1808. * Before version 1.0.1.2 the new subscription was available in the 
  1809. * member object after the next page refresh. 
  1810. * 
  1811. * @since 1.0.1.2 
  1812. */ 
  1813. $found = false; 
  1814. $subscriptions = $member->subscriptions; 
  1815. foreach ( $subscriptions as $sub ) { 
  1816. if ( $sub->membership_id == $this->membership_id ) { 
  1817. $found = true; 
  1818. break; 
  1819. if ( ! $found ) { 
  1820. $subscriptions[] = $this; 
  1821. $member->subscriptions = $subscriptions; 
  1822.  
  1823. $member->save(); 
  1824.  
  1825. // Return true if the subscription is active. 
  1826. $paid_status = array( 
  1827. self::STATUS_ACTIVE,  
  1828. self::STATUS_WAITING,  
  1829. ); 
  1830. $is_active = in_array( $this->status, $paid_status ); 
  1831. return $is_active; 
  1832.  
  1833. /** 
  1834. * Returns a sanitized list of all payments. 
  1835. * 
  1836. * @since 1.0.2.0 
  1837. * @return array 
  1838. */ 
  1839. public function get_payments() { 
  1840. $res = lib3()->array->get( $this->payments ); 
  1841.  
  1842. foreach ( $res as $key => $info ) { 
  1843. if ( ! isset( $info['amount'] ) ) { 
  1844. unset( $res[$key] ); 
  1845. continue; 
  1846.  
  1847. if ( ! isset( $info['date'] ) ) { $res[$key]['date'] = ''; } 
  1848. if ( ! isset( $info['gateway'] ) ) { $res[$key]['gateway'] = ''; } 
  1849. if ( ! isset( $info['external_id'] ) ) { $res[$key]['external_id'] = ''; } 
  1850.  
  1851. return $res; 
  1852.  
  1853. /** 
  1854. * Set membership relationship status. 
  1855. * 
  1856. * Validates every time. 
  1857. * Check for status that need membership verification for trial, active and expired. 
  1858. * 
  1859. * @since 1.0.0 
  1860. * @internal Use $this->status instead! 
  1861. * 
  1862. * @param string $status The status to set. 
  1863. */ 
  1864. public function set_status( $status ) { 
  1865. // These status are not validated, and promptly assigned 
  1866. $ignored_status = apply_filters( 
  1867. 'ms_model_relationship_unvalidated_status',  
  1868. array( 
  1869. self::STATUS_DEACTIVATED,  
  1870. self::STATUS_TRIAL_EXPIRED,  
  1871. ),  
  1872. 'set' 
  1873. ); 
  1874. $membership = $this->get_membership(); 
  1875.  
  1876. if ( in_array( $status, $ignored_status ) ) { 
  1877. // No validation for this status. 
  1878. $this->status = $status; 
  1879. } else { 
  1880. // Check if this status is still valid. 
  1881. $calc_status = $this->calculate_status( $status ); 
  1882. $this->handle_status_change( $calc_status ); 
  1883.  
  1884. $this->status = apply_filters( 
  1885. 'ms_model_relationship_set_status',  
  1886. $this->status,  
  1887. $this 
  1888. ); 
  1889.  
  1890. /** 
  1891. * Trigger an action to allow other plugins to oberse a change in an 
  1892. * subscription status. 
  1893. * 
  1894. * @since 1.0.0 
  1895. * @var MS_Model_Relationship The subscription model. 
  1896. * @var MS_Model_Member The member who is affected. 
  1897. */ 
  1898. do_action( 
  1899. 'ms_subscription_status-' . $this->status,  
  1900. $this,  
  1901. $membership,  
  1902. $this->get_member() 
  1903. ); 
  1904.  
  1905. /** 
  1906. * Get membership relationship status. 
  1907. * 
  1908. * Validates every time. 
  1909. * 
  1910. * Verifies start and end date of a membership and updates status if expired. 
  1911. * 
  1912. * @since 1.0.0 
  1913. * @internal Use $this->status instead! 
  1914. * 
  1915. * @return string The current status. 
  1916. */ 
  1917. public function get_status() { 
  1918. return apply_filters( 
  1919. 'membership_model_relationship_get_status',  
  1920. $this->status,  
  1921. $this 
  1922. ); 
  1923.  
  1924. /** 
  1925. * Returns an i18n translated version of the subscription status. 
  1926. * 
  1927. * @since 1.0.0 
  1928. * @api 
  1929. * 
  1930. * @return string 
  1931. */ 
  1932. public function status_text() { 
  1933. static $Status = null; 
  1934.  
  1935. if ( null === $Status ) { 
  1936. $Status = self::get_status_types(); 
  1937.  
  1938. $result = $this->status; 
  1939.  
  1940. if ( isset( $Status[$this->status] ) ) { 
  1941. $result = $Status[$this->status]; 
  1942.  
  1943. return apply_filters( 
  1944. 'ms_subscription_status_text',  
  1945. $result,  
  1946. $this->status 
  1947. ); 
  1948.  
  1949. /** 
  1950. * Calculate the membership status. 
  1951. * 
  1952. * Calculate status for the membership verifying the start date,  
  1953. * trial exire date and expire date. 
  1954. * 
  1955. * @since 1.0.0 
  1956. * @internal 
  1957. * 
  1958. * @param string $set_status The set status to compare. 
  1959. * @return string The calculated status. 
  1960. */ 
  1961. protected function calculate_status( $set_status = null, $debug = false ) { 
  1962. /** 
  1963. * Documented in check_membership_status() 
  1964. * 
  1965. * @since 1.0.0 
  1966. */ 
  1967. if ( MS_Plugin::get_modifier( 'MS_LOCK_SUBSCRIPTIONS' ) ) { 
  1968. return $set_status; 
  1969.  
  1970. $membership = $this->get_membership(); 
  1971. $calc_status = null; 
  1972. $debug_msg = array(); 
  1973. $check_trial = $this->is_trial_eligible(); 
  1974.  
  1975. if ( ! empty( $this->payments ) ) { 
  1976. /** 
  1977. * The user already paid for this membership, so don't check for 
  1978. * trial status anymore 
  1979. */ 
  1980. $check_trial = false; 
  1981.  
  1982. // If the start-date is not reached yet, then set membership to Pending. 
  1983. if ( ! $calc_status 
  1984. && ! empty( $this->start_date ) 
  1985. && strtotime( $this->start_date ) > strtotime( MS_Helper_Period::current_date() ) 
  1986. ) { 
  1987. $calc_status = self::STATUS_WAITING; 
  1988. $debug_msg[] = '[WAITING: Start-date not reached]'; 
  1989. } elseif ( ! $calc_status && $debug ) { 
  1990. $debug_msg[] = '[Not WAITING: No start-date or start-date reached]'; 
  1991.  
  1992. if ( $check_trial ) { 
  1993. if ( ! $calc_status 
  1994. && strtotime( $this->trial_expire_date ) >= strtotime( MS_Helper_Period::current_date() ) 
  1995. ) { 
  1996. $calc_status = self::STATUS_TRIAL; 
  1997. $debug_msg[] = '[TRIAL: Trial-Expire date not reached]'; 
  1998. } elseif ( ! $calc_status && $debug ) { 
  1999. $debug_msg[] = '[Not TRIAL: Trial-Expire date reached]'; 
  2000.  
  2001. if ( ! $calc_status 
  2002. && strtotime( $this->trial_expire_date ) < strtotime( MS_Helper_Period::current_date() ) 
  2003. ) { 
  2004. $calc_status = self::STATUS_TRIAL_EXPIRED; 
  2005. $debug_msg[] = '[TRIAL-EXPIRED: Trial-Expire date reached]'; 
  2006. } elseif ( ! $calc_status && $debug ) { 
  2007. $debug_msg[] = '[Not TRIAL-EXPIRED: Trial-Expire date not reached]'; 
  2008. } elseif ( ! $calc_status && $debug ) { 
  2009. $debug_msg[] = '[Skipped TRIAL status]'; 
  2010.  
  2011. // Status an only become active when added by admin or invoice is paid. 
  2012. $can_activate = false; 
  2013. if ( 'admin' == $this->gateway_id ) { 
  2014. $can_activate = true; 
  2015. $debug_msg[] = '[Can activate: Admin gateway]'; 
  2016. } elseif ( $membership->is_free() ) { 
  2017. $can_activate = true; 
  2018. $debug_msg[] = '[Can activate: Free membership]'; 
  2019. } else { 
  2020. $valid_payment = false; 
  2021. // Check if there is *any* payment, no matter what height. 
  2022. foreach ( $this->get_payments() as $payment ) { 
  2023. if ( $payment['amount'] > 0 ) { 
  2024. $valid_payment = true; 
  2025. $debug_msg[] = '[Can activate: Payment found]'; 
  2026. break; 
  2027. if ( ! $valid_payment ) { 
  2028. // Check if any invoice was paid already. 
  2029. for ( $ind = $this->current_invoice_number; $ind > 0; $ind -= 1 ) { 
  2030. $invoice = MS_Model_Invoice::get_invoice( $this->id, $ind ); 
  2031. if ( ! $invoice ) { continue; } 
  2032. if ( $invoice->uses_trial ) { continue; } 
  2033. if ( $invoice->is_paid() ) { 
  2034. $valid_payment = true; 
  2035. $debug_msg[] = '[Can activate: Paid invoice found]'; 
  2036. break; 
  2037. if ( ! $valid_payment ) { 
  2038. // Check if the current invoice is free. 
  2039. $invoice = $this->get_current_invoice(); 
  2040. if ( 0 == $invoice->total ) { 
  2041. $valid_payment = true; 
  2042.  
  2043. if ( $valid_payment ) { 
  2044. $can_activate = true; 
  2045.  
  2046. if ( ! $can_activate && $debug ) { 
  2047. $debug_msg[] = sprintf( 
  2048. '[Can not activate: Gateway: %s; Price: %s; Invoice: %s]',  
  2049. $this->gateway_id,  
  2050. $membership->price,  
  2051. $invoice->total 
  2052. ); 
  2053.  
  2054. if ( $can_activate ) { 
  2055. // Permanent memberships grant instant access, no matter what. 
  2056. if ( ! $calc_status 
  2057. && MS_Model_Membership::PAYMENT_TYPE_PERMANENT == $membership->payment_type 
  2058. ) { 
  2059. $calc_status = self::STATUS_ACTIVE; 
  2060. $debug_msg[] = '[ACTIVE(1): Payment-type is permanent]'; 
  2061. } elseif ( ! $calc_status && $debug ) { 
  2062. $debug_msg[] = '[Not ACTIVE(1): Payment-type is not permanent]'; 
  2063.  
  2064. // If expire date is empty and Active-state is requests then use active. 
  2065. if ( ! $calc_status 
  2066. && empty( $this->expire_date ) 
  2067. && self::STATUS_ACTIVE == $set_status 
  2068. ) { 
  2069. $calc_status = self::STATUS_ACTIVE; 
  2070. $debug_msg[] = '[ACTIVE(2): Expire date empty and active requested]'; 
  2071. } elseif ( ! $calc_status && $debug ) { 
  2072. $debug_msg[] = '[Not ACTIVE(2): Expire date set or wrong status-request]'; 
  2073.  
  2074. // If expire date is not reached then membership obviously is active. 
  2075. if ( ! $calc_status 
  2076. && ! empty( $this->expire_date ) 
  2077. && strtotime( $this->expire_date ) >= strtotime( MS_Helper_Period::current_date() ) 
  2078. ) { 
  2079. $calc_status = self::STATUS_ACTIVE; 
  2080. $debug_msg[] = '[ACTIVE(3): Expire date set and not reached]'; 
  2081. } elseif ( ! $calc_status && $debug ) { 
  2082. $debug_msg[] = '[Not ACTIVE(3): Expire date set and reached]'; 
  2083. } elseif ( ! $calc_status && self::STATUS_PENDING == $this->status ) { 
  2084. // Invoice is not paid yet. 
  2085. $calc_status = self::STATUS_PENDING; 
  2086. $debug_msg[] = '[PENDING: Cannot activate pending subscription]'; 
  2087. } elseif ( ! $calc_status && $debug ) { 
  2088. $debug_msg[] = '[Not ACTIVE/PENDING: Cannot activate subscription]'; 
  2089.  
  2090. // If no other condition was true then the expire date was reached. 
  2091. if ( ! $calc_status ) { 
  2092. $calc_status = self::STATUS_EXPIRED; 
  2093. $debug_msg[] = '[EXPIRED: Default status]'; 
  2094.  
  2095. // Did the user cancel the membership? 
  2096. $cancel_it = self::STATUS_CANCELED == $set_status 
  2097. || ( 
  2098. self::STATUS_CANCELED == $this->status 
  2099. && self::STATUS_ACTIVE != $set_status 
  2100. && self::STATUS_TRIAL != $set_status 
  2101. ); 
  2102. if ( $cancel_it ) { 
  2103. /** 
  2104. * When a membership is cancelled then it will stay "Cancelled" 
  2105. * until the expiration date is reached. A user has access to the 
  2106. * contents of a cancelled membership until it expired. 
  2107. */ 
  2108.  
  2109. if ( self::STATUS_EXPIRED == $calc_status ) { 
  2110. // Membership has expired. Finally deactivate it! 
  2111. // (possibly it was cancelled a few days earlier) 
  2112. $calc_status = self::STATUS_DEACTIVATED; 
  2113. } elseif ( self::STATUS_TRIAL_EXPIRED == $calc_status ) { 
  2114. // Trial period has expired. Finally deactivate it! 
  2115. $calc_status = self::STATUS_DEACTIVATED; 
  2116. } elseif ( self::STATUS_TRIAL == $calc_status ) { 
  2117. // User can keep access until trial period finishes... 
  2118. $calc_status = self::STATUS_CANCELED; 
  2119. } elseif ( MS_Model_Membership::PAYMENT_TYPE_PERMANENT == $membership->payment_type ) { 
  2120. // This membership has no expiration-time. Deactivate it! 
  2121. $calc_status = self::STATUS_DEACTIVATED; 
  2122. } elseif ( self::STATUS_WAITING == $calc_status ) { 
  2123. // The membership did not yet start. Deactivate it! 
  2124. $calc_status = self::STATUS_DEACTIVATED; 
  2125. } elseif ( ! $this->expire_date ) { 
  2126. // Membership without expire date cannot be cancelled. Deactivate it! 
  2127. $calc_status = self::STATUS_DEACTIVATED; 
  2128. } else { 
  2129. // Wait until the expiration date is reached... 
  2130. $calc_status = self::STATUS_CANCELED; 
  2131.  
  2132. if ( $debug ) { 
  2133. // Intended debug output, leave it here. 
  2134. lib3()->debug->dump( $debug_msg ); 
  2135.  
  2136. return apply_filters( 
  2137. 'membership_model_relationship_calculate_status',  
  2138. $calc_status,  
  2139. $this 
  2140. ); 
  2141.  
  2142. /** 
  2143. * Handle status change. 
  2144. * 
  2145. * Save news when status change. 
  2146. * 
  2147. * @since 1.0.0 
  2148. * @internal 
  2149. * 
  2150. * @param string $new_status The status to change to. 
  2151. */ 
  2152. public function handle_status_change( $new_status ) { 
  2153. do_action( 
  2154. 'ms_model_relationship_handle_status_change_before',  
  2155. $new_status,  
  2156. $this 
  2157. ); 
  2158.  
  2159. if ( empty( $new_status ) ) { return false; } 
  2160. if ( $new_status == $this->status ) { return false; } 
  2161. if ( ! array_key_exists( $new_status, self::get_status_types() ) ) { return false; } 
  2162.  
  2163. if ( $this->is_simulated ) { 
  2164. // Do not trigger any events for simulated relationships. 
  2165. } elseif ( self::STATUS_DEACTIVATED == $new_status ) { 
  2166. /** 
  2167. * Deactivated manually or automatically after a limited 
  2168. * expiration-period or trial period ended. 
  2169. */ 
  2170. MS_Model_Event::save_event( 
  2171. MS_Model_Event::TYPE_MS_DEACTIVATED,  
  2172. $this 
  2173. ); 
  2174. } else { 
  2175. // Current status to change from. 
  2176. switch ( $this->status ) { 
  2177. case self::STATUS_PENDING: 
  2178. // signup 
  2179. if ( in_array( $new_status, array( self::STATUS_TRIAL, self::STATUS_ACTIVE ) ) ) { 
  2180. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_SIGNED_UP, $this ); 
  2181.  
  2182. // When changing from Pending -> Trial set trial_period_completed to true. 
  2183. $this->trial_period_completed = true; 
  2184. break; 
  2185.  
  2186. case self::STATUS_TRIAL: 
  2187. // Trial finished 
  2188. if ( self::STATUS_TRIAL_EXPIRED == $new_status ) { 
  2189. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_TRIAL_FINISHED, $this ); 
  2190. } elseif ( self::STATUS_ACTIVE == $new_status ) { 
  2191. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_RENEWED, $this ); 
  2192. } elseif ( self::STATUS_CANCELED == $new_status ) { 
  2193. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_CANCELED, $this ); 
  2194. break; 
  2195.  
  2196. case self::STATUS_TRIAL_EXPIRED: 
  2197. if ( self::STATUS_ACTIVE == $new_status ) { 
  2198. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_RENEWED, $this ); 
  2199. break; 
  2200.  
  2201. case self::STATUS_ACTIVE: 
  2202. if ( self::STATUS_CANCELED == $new_status ) { 
  2203. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_CANCELED, $this ); 
  2204. } elseif ( self::STATUS_EXPIRED == $new_status ) { 
  2205. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_EXPIRED, $this ); 
  2206. break; 
  2207.  
  2208. case self::STATUS_EXPIRED: 
  2209. case self::STATUS_CANCELED: 
  2210. if ( self::STATUS_ACTIVE == $new_status ) { 
  2211. MS_Model_Event::save_event( MS_Model_Event::TYPE_MS_RENEWED, $this ); 
  2212. break; 
  2213.  
  2214. case self::STATUS_DEACTIVATED: 
  2215. break; 
  2216.  
  2217. case self::STATUS_WAITING: 
  2218. // Start date is not reached yet, so don't do anything. 
  2219. break; 
  2220.  
  2221. $this->status = apply_filters( 
  2222. 'ms_model_relationship_set_status',  
  2223. $new_status 
  2224. ); 
  2225. $this->save(); 
  2226.  
  2227. do_action( 
  2228. 'ms_model_relationship_handle_status_change_after',  
  2229. $new_status,  
  2230. $this 
  2231. ); 
  2232.  
  2233. /** 
  2234. * Get a detailled status description. 
  2235. * 
  2236. * @since 1.0.0 
  2237. * @api 
  2238. * 
  2239. * @return string The status description. 
  2240. */ 
  2241. public function get_status_description() { 
  2242. $desc = ''; 
  2243.  
  2244. switch ( $this->status ) { 
  2245. case self::STATUS_PENDING: 
  2246. $desc = __( 'Pending payment.', 'membership2' ); 
  2247. break; 
  2248.  
  2249. case self::STATUS_TRIAL: 
  2250. $desc = sprintf( 
  2251. '%s <span class="ms-date">%s</span>',  
  2252. __( 'Membership Trial expires on ', 'membership2' ),  
  2253. MS_Helper_Period::format_date( $this->trial_expire_date ) 
  2254. ); 
  2255. break; 
  2256.  
  2257. case self::STATUS_ACTIVE: 
  2258. if ( ! empty( $this->expire_date ) ) { 
  2259. $desc = sprintf( 
  2260. '%s <span class="ms-date">%s</span>',  
  2261. __( 'Membership expires on ', 'membership2' ),  
  2262. MS_Helper_Period::format_date( $this->expire_date ) 
  2263. ); 
  2264. else { 
  2265. $desc = __( 'Permanent access.', 'membership2' ); 
  2266. break; 
  2267.  
  2268. case self::STATUS_TRIAL_EXPIRED: 
  2269. case self::STATUS_EXPIRED: 
  2270. $desc = sprintf( 
  2271. '%s <span class="ms-date">%s</span>',  
  2272. __( 'Membership expired since ', 'membership2' ),  
  2273. MS_Helper_Period::format_date( $this->expire_date ) 
  2274. ); 
  2275. break; 
  2276.  
  2277. case self::STATUS_CANCELED: 
  2278. $desc = sprintf( 
  2279. '%s <span class="ms-date">%s</span>',  
  2280. __( 'Membership canceled, valid until it expires on ', 'membership2' ),  
  2281. MS_Helper_Period::format_date( $this->expire_date ) 
  2282. ); 
  2283. break; 
  2284.  
  2285. case self::STATUS_DEACTIVATED: 
  2286. $desc = __( 'Membership deactivated.', 'membership2' ); 
  2287. break; 
  2288.  
  2289. return apply_filters( 
  2290. 'ms_model_relationship_get_status_description',  
  2291. $desc 
  2292. ); 
  2293.  
  2294. /** 
  2295. * Check membership status. 
  2296. * 
  2297. * Execute actions when time/period condition are met. 
  2298. * E.g. change membership status, add communication to queue, create invoices. 
  2299. * 
  2300. * This check is called via a cron job. 
  2301. * 
  2302. * @since 1.0.0 
  2303. * @internal Used by Cron 
  2304. * @see MS_Model_Plugin::check_membership_status() 
  2305. */ 
  2306. public function check_membership_status() { 
  2307. do_action( 
  2308. 'ms_model_relationship_check_membership_status_before',  
  2309. $this 
  2310. ); 
  2311.  
  2312. /** 
  2313. * Use `define( 'MS_LOCK_SUBSCRIPTIONS', true );` in wp-config.php to prevent 
  2314. * Membership2 from sending *any* emails to users. 
  2315. * Also any currently enqueued message is removed from the queue 
  2316. * 
  2317. * @since 1.0.0 
  2318. */ 
  2319. if ( MS_Plugin::get_modifier( 'MS_LOCK_SUBSCRIPTIONS' ) ) { 
  2320. return false; 
  2321.  
  2322. $membership = $this->get_membership(); 
  2323. $remaining_days = $this->get_remaining_period(); 
  2324. $remaining_trial_days = $this->get_remaining_trial_period(); 
  2325.  
  2326. $comms = MS_Model_Communication::get_communications( $membership ); 
  2327. $invoice_before_days = 5;//@todo create a setting to configure this period. 
  2328. $deactivate_expired_after_days = 30; //@todo create a setting to configure this period. 
  2329. $deactivate_pending_after_days = 30; //@todo create a setting to configure this period. 
  2330. $deactivate_trial_expired_after_days = 5; //@todo create a setting to configure this period. 
  2331.  
  2332. //@todo: Add a flag to subscriptions with sent communications. Then improve the conditions below to prevent multiple emails. 
  2333.  
  2334. do_action( 
  2335. 'ms_check_membership_status-' . $this->status,  
  2336. $this,  
  2337. $remaining_days,  
  2338. $remaining_trial_days 
  2339. ); 
  2340.  
  2341. // Update the Subscription status. 
  2342. $next_status = $this->calculate_status( null ); 
  2343.  
  2344. switch ( $next_status ) { 
  2345. case self::STATUS_TRIAL: 
  2346. if ( MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_TRIAL ) 
  2347. && MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_AUTO_MSGS_PLUS ) 
  2348. ) { 
  2349. // Send trial end communication. 
  2350. $comm = $comms[ MS_Model_Communication::COMM_TYPE_BEFORE_TRIAL_FINISHES ]; 
  2351.  
  2352. if ( $comm->enabled ) { 
  2353. $days = MS_Helper_Period::get_period_in_days( 
  2354. $comm->period['period_unit'],  
  2355. $comm->period['period_type'] 
  2356. ); 
  2357. //@todo: This will send out the reminder multiple times on the reminder-day (4 times or more often) 
  2358. if ( $days == $remaining_trial_days ) { 
  2359. $comm->add_to_queue( $this->id ); 
  2360. MS_Model_Event::save_event( 
  2361. MS_Model_Event::TYPE_MS_BEFORE_TRIAL_FINISHES,  
  2362. $this 
  2363. ); 
  2364.  
  2365. // Check for card expiration 
  2366. $gateway = $this->get_gateway(); 
  2367. $gateway->check_card_expiration( $this ); 
  2368. break; 
  2369.  
  2370. case self::STATUS_TRIAL_EXPIRED: 
  2371. if ( MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_TRIAL ) ) { 
  2372. // Mark the trial period as completed. $this->save() is below. 
  2373. $this->trial_period_completed = true; 
  2374.  
  2375. // Request payment to the gateway (for gateways that allows it). 
  2376. $gateway = $this->get_gateway(); 
  2377.  
  2378. /** 
  2379. * The subscription will be either automatically activated 
  2380. * or set to pending. 
  2381. * 
  2382. * Important: Set trial_period_completed=true before calling 
  2383. * request_payment()! 
  2384. */ 
  2385. if ( $gateway->request_payment( $this ) ) { 
  2386. $next_status = self::STATUS_ACTIVE; 
  2387. $this->status = $next_status; 
  2388. $this->config_period(); // Needed because of status change. 
  2389.  
  2390. // Check for card expiration 
  2391. $gateway->check_card_expiration( $this ); 
  2392.  
  2393. // Deactivate expired memberships after a period of time. 
  2394. if ( $deactivate_trial_expired_after_days < - $remaining_trial_days ) { 
  2395. $this->deactivate_membership(); 
  2396. break; 
  2397.  
  2398. case self::STATUS_ACTIVE: 
  2399. case self::STATUS_EXPIRED: 
  2400. case self::STATUS_CANCELED: 
  2401. /** 
  2402. * Make sure the expire date has a correct value, in case the user 
  2403. * changed the payment_type of the parent membership after this 
  2404. * subscription was created. 
  2405. */ 
  2406. if ( $this->payment_type != $membership->payment_type ) { 
  2407. $this->payment_type = $membership->payment_type; 
  2408.  
  2409. switch ( $this->payment_type ) { 
  2410. case MS_Model_Membership::PAYMENT_TYPE_PERMANENT: 
  2411. $this->expire_date = false; 
  2412. break; 
  2413.  
  2414. default: 
  2415. // Either keep the current expire date (if valid) or 
  2416. // calculate a new expire date, based on current date. 
  2417. if ( ! $this->expire_date ) { 
  2418. $this->expire_date = $this->calc_expire_date( 
  2419. MS_Helper_Period::current_date() 
  2420. ); 
  2421.  
  2422. break; 
  2423.  
  2424. // Recalculate the days until the subscription expires. 
  2425. $remaining_days = $this->get_remaining_period(); 
  2426.  
  2427. // Recalculate the new Subscription status. 
  2428. $next_status = $this->calculate_status(); 
  2429.  
  2430. /** 
  2431. * Only "Recurring" memberships will ever try to automatically 
  2432. * renew the subscription. All other types will expire when the 
  2433. * end date is reached. 
  2434. */ 
  2435. $auto_renew = ($membership->payment_type == MS_Model_Membership::PAYMENT_TYPE_RECURRING); 
  2436. $deactivate = false; 
  2437. $invoice = null; 
  2438.  
  2439. if ( $auto_renew && $membership->pay_cycle_repetitions > 0 ) { 
  2440. /** 
  2441. * The membership has a payment-repetition limit. 
  2442. * When this limit is reached then we do not auto-renew the 
  2443. * subscription but expire it. 
  2444. */ 
  2445. $payments = $this->get_payments(); 
  2446. if ( count( $payments ) >= $membership->pay_cycle_repetitions ) { 
  2447. $auto_renew = false; 
  2448.  
  2449. if ( $auto_renew ) { 
  2450. if ( $remaining_days < $invoice_before_days ) { 
  2451. // Create a new invoice. 
  2452. $invoice = $this->get_next_invoice(); 
  2453. } else { 
  2454. $invoice = $this->get_current_invoice(); 
  2455. } else { 
  2456. $invoice = $this->get_current_invoice(); 
  2457.  
  2458. // Advanced communications Add-on. 
  2459. if ( MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_AUTO_MSGS_PLUS ) ) { 
  2460. // Before finishes communication. 
  2461. $comm = $comms[ MS_Model_Communication::COMM_TYPE_BEFORE_FINISHES ]; 
  2462. $days = MS_Helper_Period::get_period_in_days( 
  2463. $comm->period['period_unit'],  
  2464. $comm->period['period_type'] 
  2465. ); 
  2466. if ( $days == $remaining_days ) { 
  2467. $comm->add_to_queue( $this->id ); 
  2468. MS_Model_Event::save_event( 
  2469. MS_Model_Event::TYPE_MS_BEFORE_FINISHES,  
  2470. $this 
  2471. ); 
  2472.  
  2473. // After finishes communication. 
  2474. $comm = $comms[ MS_Model_Communication::COMM_TYPE_AFTER_FINISHES ]; 
  2475. $days = MS_Helper_Period::get_period_in_days( 
  2476. $comm->period['period_unit'],  
  2477. $comm->period['period_type'] 
  2478. ); 
  2479.  
  2480. if ( $remaining_days < 0 && $days == abs( $remaining_days ) ) { 
  2481. $comm->add_to_queue( $this->id ); 
  2482. MS_Model_Event::save_event( 
  2483. MS_Model_Event::TYPE_MS_AFTER_FINISHES,  
  2484. $this 
  2485. ); 
  2486.  
  2487. // Before payment due. 
  2488. $comm = $comms[ MS_Model_Communication::COMM_TYPE_BEFORE_PAYMENT_DUE ]; 
  2489. $days = MS_Helper_Period::get_period_in_days( 
  2490. $comm->period['period_unit'],  
  2491. $comm->period['period_type'] 
  2492. ); 
  2493. $invoice_days = MS_Helper_Period::subtract_dates( 
  2494. $invoice->due_date,  
  2495. MS_Helper_Period::current_date() 
  2496. ); 
  2497.  
  2498. if ( MS_Model_Invoice::STATUS_BILLED == $invoice->status 
  2499. && $days == $invoice_days 
  2500. ) { 
  2501. $comm->add_to_queue( $this->id ); 
  2502. MS_Model_Event::save_event( MS_Model_Event::TYPE_PAYMENT_BEFORE_DUE, $this ); 
  2503.  
  2504. // After payment due event 
  2505. $comm = $comms[ MS_Model_Communication::COMM_TYPE_AFTER_PAYMENT_DUE ]; 
  2506. $days = MS_Helper_Period::get_period_in_days( 
  2507. $comm->period['period_unit'],  
  2508. $comm->period['period_type'] 
  2509. ); 
  2510. $invoice_days = MS_Helper_Period::subtract_dates( 
  2511. $invoice->due_date,  
  2512. MS_Helper_Period::current_date() 
  2513. ); 
  2514.  
  2515. if ( MS_Model_Invoice::STATUS_BILLED == $invoice->status 
  2516. && $days == $invoice_days 
  2517. ) { 
  2518. $comm->add_to_queue( $this->id ); 
  2519. MS_Model_Event::save_event( MS_Model_Event::TYPE_PAYMENT_AFTER_DUE, $this ); 
  2520. } // -- End of advanced communications Add-on 
  2521.  
  2522. // Subscription ended. See if we can renew it. 
  2523. if ( $remaining_days <= 0 ) { 
  2524. if ( $auto_renew ) { 
  2525. /** 
  2526. * The membership can be renewed. Try to renew it 
  2527. * automatically by requesting the next payment from the 
  2528. * payment gateway (only works if gateway supports this) 
  2529. */ 
  2530. $gateway = $this->get_gateway(); 
  2531. $gateway->check_card_expiration( $this ); 
  2532. $gateway->request_payment( $this ); 
  2533.  
  2534. // Check if the payment was successful. 
  2535. $remaining_days = $this->get_remaining_period(); 
  2536.  
  2537. /** 
  2538. * User did not renew the membership. Give him some time 
  2539. * to react before restricting his access. 
  2540. */ 
  2541. if ( $deactivate_expired_after_days < - $remaining_days ) { 
  2542. $deactivate = true; 
  2543. } else { 
  2544. $deactivate = true; 
  2545.  
  2546. if ( $deactivate ) { 
  2547. $this->deactivate_membership(); 
  2548.  
  2549. // Move membership to configured membership. 
  2550. $membership = $this->get_membership(); 
  2551.  
  2552. $new_membership = MS_Factory::load( 
  2553. 'MS_Model_Membership',  
  2554. $membership->on_end_membership_id 
  2555. ); 
  2556.  
  2557. if ( $new_membership->is_valid() ) { 
  2558. $member = MS_Factory::load( 'MS_Model_Member', $this->user_id ); 
  2559. $new_subscription = $member->add_membership( 
  2560. $membership->on_end_membership_id,  
  2561. $this->gateway_id 
  2562. ); 
  2563.  
  2564. MS_Model_Event::save_event( 
  2565. MS_Model_Event::TYPE_MS_MOVED,  
  2566. $new_subscription 
  2567. ); 
  2568.  
  2569. /** 
  2570. * If the new membership is paid we want that the user 
  2571. * confirms the payment in his account. So we set it 
  2572. * to "Pending" first. 
  2573. */ 
  2574. if ( ! $new_membership->is_free() ) { 
  2575. $new_subscription->status = self::STATUS_PENDING; 
  2576. break; 
  2577.  
  2578. case self::STATUS_DEACTIVATED: 
  2579. /** 
  2580. * A subscription was finally deactivated. 
  2581. * Lets check if the member has any other active subscriptions,  
  2582. * or (if not) his account should be deactivated. 
  2583. * 
  2584. * First get a list of all subscriptions that do not have status 
  2585. * Pending / Deactivated. 
  2586. */ 
  2587. $subscriptions = self::get_subscriptions( 
  2588. array( 'user_id' => $this->user_id ) 
  2589. ); 
  2590.  
  2591. // Check if there is a subscription that keeps the user active. 
  2592. $deactivate = true; 
  2593. foreach ( $subscriptions as $item ) { 
  2594. if ( $item->id == $this->id ) { continue; } 
  2595. $deactivate = false; 
  2596.  
  2597. if ( $deactivate ) { 
  2598. $member = $this->get_member(); 
  2599. $member->is_member = false; 
  2600. $member->save(); 
  2601. break; 
  2602.  
  2603. case self::STATUS_PENDING: 
  2604. default: 
  2605. // Do nothing. 
  2606. break; 
  2607.  
  2608. // Save the new status. 
  2609. $this->status = $next_status; 
  2610. $this->save(); 
  2611.  
  2612. // Save the changed email queue. 
  2613. foreach ( $comms as $comm ) { 
  2614. $comm->save(); 
  2615.  
  2616. do_action( 
  2617. 'ms_model_relationship_check_membership_status_after',  
  2618. $this 
  2619. ); 
  2620.  
  2621. /** 
  2622. * Returns property. 
  2623. * 
  2624. * @since 1.0.0 
  2625. * @internal 
  2626. * 
  2627. * @param string $property The name of a property. 
  2628. * @return mixed Returns mixed value of a property or NULL if a property doesn't exist. 
  2629. */ 
  2630. public function __get( $property ) { 
  2631. $value = null; 
  2632.  
  2633. switch ( $property ) { 
  2634. case 'status': 
  2635. $value = $this->get_status(); 
  2636. break; 
  2637.  
  2638. default: 
  2639. if ( ! property_exists( $this, $property ) ) { 
  2640. MS_Helper_Debug::log( 'Property does not exist: ' . $property ); 
  2641. } else { 
  2642. $value = $this->$property; 
  2643. break; 
  2644.  
  2645. return apply_filters( 
  2646. 'ms_model_relationship__get',  
  2647. $value,  
  2648. $property,  
  2649. $this 
  2650. ); 
  2651.  
  2652. /** 
  2653. * Set specific property. 
  2654. * 
  2655. * @since 1.0.0 
  2656. * @internal 
  2657. * 
  2658. * @param string $property The name of a property to associate. 
  2659. * @param mixed $value The value of a property. 
  2660. */ 
  2661. public function __set( $property, $value ) { 
  2662. switch ( $property ) { 
  2663. case 'start_date': 
  2664. $this->set_start_date( $value ); 
  2665. break; 
  2666.  
  2667. case 'trial_expire_date': 
  2668. $this->set_trial_expire_date( $value ); 
  2669. break; 
  2670.  
  2671. case 'expire_date': 
  2672. $this->set_expire_date( $value ); 
  2673. break; 
  2674.  
  2675. case 'status': 
  2676. $this->set_status( $value ); 
  2677. break; 
  2678.  
  2679. default: 
  2680. if ( property_exists( $this, $property ) ) { 
  2681. $this->$property = $value; 
  2682. break; 
  2683.  
  2684. do_action( 
  2685. 'ms_model_relationship__set_after',  
  2686. $property,  
  2687. $value,  
  2688. $this 
  2689. ); 
  2690.  
.