MS_Model_Relationship

Subscription model (former "Membership Relationship").

Defined (1)

The class is defined in the following location(s).

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