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