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