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

  1. <?php 
  2. /** 
  3. * Membership model. 
  4. * 
  5. * A membership defines payment options, access rules and similar details. 
  6. * It is not related to a specific user! 
  7. * 
  8. * Note that all properties are declared protected but they can be access 
  9. * directly (e.g. `$membership->type` to get the type value). 
  10. * There are magic methods \_\_get() and \_\_set() that do some validation before 
  11. * accessing the properties. 
  12. * 
  13. * @since 1.0.0 
  14. * @package Membership2 
  15. * @subpackage Model 
  16. */ 
  17. class MS_Model_Membership extends MS_Model_CustomPostType { 
  18.  
  19. /** 
  20. * Model custom post type. 
  21. * 
  22. * @since 1.0.0 
  23. * @internal self::get_post_type() to get this value! 
  24. * 
  25. * @var string $POST_TYPE 
  26. */ 
  27. protected static $POST_TYPE = 'ms_membership'; 
  28.  
  29. /** 
  30. * Membership type constant. 
  31. * 
  32. * @since 1.0.0 
  33. * @see $type $type property. 
  34. */ 
  35. const TYPE_STANDARD = 'simple'; 
  36.  
  37. /** 
  38. * Membership type constant. 
  39. * 
  40. * @since 1.0.0 
  41. * @see $type $type property. 
  42. */ 
  43. const TYPE_DRIPPED = 'dripped'; 
  44.  
  45. /** 
  46. * Membership type constant. 
  47. * System membership, hidden, created automatically. 
  48. * 
  49. * @since 1.1.0 
  50. * @see $type $type property. 
  51. */ 
  52. const TYPE_BASE = 'base'; 
  53.  
  54. /** 
  55. * Membership type constant. 
  56. * Guest membership, only one membership possible. 
  57. * 
  58. * @since 1.1.0 
  59. * @see $type $type property. 
  60. */ 
  61. const TYPE_GUEST = 'guest'; 
  62.  
  63. /** 
  64. * Membership type constant. 
  65. * User membership, only one membership possible. 
  66. * 
  67. * @since 1.1.0 
  68. * @see $type $type property. 
  69. */ 
  70. const TYPE_USER = 'user'; 
  71.  
  72. /** 
  73. * Membership payment type constants. 
  74. * 
  75. * @since 1.0.0 
  76. * @see $payment_type $payment_type property. 
  77. */ 
  78. const PAYMENT_TYPE_PERMANENT = 'permanent'; 
  79.  
  80. /** 
  81. * Membership payment type constants. 
  82. * 
  83. * @since 1.0.0 
  84. * @see $payment_type $payment_type property. 
  85. */ 
  86. const PAYMENT_TYPE_FINITE = 'finite'; 
  87.  
  88. /** 
  89. * Membership payment type constants. 
  90. * 
  91. * @since 1.0.0 
  92. * @see $payment_type $payment_type property. 
  93. */ 
  94. const PAYMENT_TYPE_DATE_RANGE = 'date-range'; 
  95.  
  96. /** 
  97. * Membership payment type constants. 
  98. * The only type that auto-renews without asking the user! 
  99. * 
  100. * @since 1.0.0 
  101. * @see $payment_type $payment_type property. 
  102. */ 
  103. const PAYMENT_TYPE_RECURRING = 'recurring'; 
  104.  
  105. /** 
  106. * Membership type. 
  107. * Default is TYPE_STANDARD. 
  108. * 
  109. * @since 1.0.0 
  110. * @var string $type 
  111. */ 
  112. protected $type = self::TYPE_STANDARD; 
  113.  
  114. /** 
  115. * Membership payment type. 
  116. * Default is PAYMENT_TYPE_PERMANENT. 
  117. * 
  118. * @since 1.0.0 
  119. * @var string $payment_type 
  120. */ 
  121. protected $payment_type = self::PAYMENT_TYPE_PERMANENT; 
  122.  
  123. /** 
  124. * Membership active status. 
  125. * By default a new membership is active. 
  126. * 
  127. * @since 1.0.0 
  128. * @var bool $active 
  129. */ 
  130. protected $active = true; 
  131.  
  132. /** 
  133. * Membership private status. 
  134. * Private means that the membership will not be displayed on the default 
  135. * registration page and membership list. 
  136. * Users can still stubscribe to the membership via the shortcode 
  137. * [ms-membership-buy] or by otherwise reaching the subscription URL. 
  138. * 
  139. * @since 1.0.0 
  140. * @var bool $private 
  141. */ 
  142. protected $private = false; 
  143.  
  144. /** 
  145. * Membership free status. 
  146. * 
  147. * @since 1.0.0 
  148. * @var bool $free. 
  149. */ 
  150. protected $is_free = false; 
  151.  
  152. /** 
  153. * Membership price. 
  154. * 
  155. * @since 1.0.0 
  156. * @var float $price. 
  157. */ 
  158. protected $price = 0; 
  159.  
  160. /** 
  161. * A list of disabled gateways. 
  162. * 
  163. * @since 2.0.0 
  164. * @var array $disabled_gateways. 
  165. */ 
  166. protected $disabled_gateways = array(); 
  167.  
  168. /** 
  169. * Membership period for finite access. 
  170. * Used for payment_type PAYMENT_TYPE_FINITE. 
  171. * 
  172. * @since 1.0.0 
  173. * @see $payment_type $payment_type property. 
  174. * @var array $period { 
  175. * @type int $period_unit The period of time quantity. 
  176. * @type string $period_type The period type (days, weeks, months, years). 
  177. * } 
  178. */ 
  179. protected $period = array( 'period_unit' => 1, 'period_type' => 'days' ); 
  180.  
  181. /** 
  182. * Membership payment recurring period cycle. 
  183. * Used for the payment_type PAYMENT_TYPE_RECURRING. 
  184. * 
  185. * @since 1.0.0 
  186. * @see $payment_type $payment_type property. 
  187. * @var array $pay_cycle_period {@see $period $period property}. 
  188. */ 
  189. protected $pay_cycle_period = array( 'period_unit' => 1, 'period_type' => 'days' ); 
  190.  
  191. /** 
  192. * Defines how many payments are made before the membership ends. 
  193. * Used for the payment_type PAYMENT_TYPE_RECURRING. 
  194. * 
  195. * @since 1.1.0 
  196. * @see $payment_type $payment_type property. 
  197. * @var int 
  198. */ 
  199. protected $pay_cycle_repetitions = 0; 
  200.  
  201. /** 
  202. * Membership start date for date range payment type. 
  203. * Used for the payment_type PAYMENT_TYPE_DATE_RANGE. 
  204. * 
  205. * @since 1.0.0 
  206. * @see $payment_type $payment_type property. 
  207. * @var string The membership start date. 
  208. */ 
  209. protected $period_date_start = ''; 
  210.  
  211. /** 
  212. * Membership end date for date range payment type. 
  213. * Used for the payment_type PAYMENT_TYPE_DATE_RANGE. 
  214. * 
  215. * @since 1.0.0 
  216. * @see $payment_type $payment_type property. 
  217. * @var string The membership end date. 
  218. */ 
  219. protected $period_date_end = ''; 
  220.  
  221. /** 
  222. * Membership trial period enabled indicator. 
  223. * Requires the Trial Period Add-on to work. 
  224. * 
  225. * @since 1.0.0 
  226. * @var bool $trial_period_enabled. 
  227. */ 
  228. protected $trial_period_enabled = false; 
  229.  
  230. /** 
  231. * Membership trial price value. 
  232. * Requires the Trial Period Add-on to work. 
  233. * 
  234. * @since 1.0.0 
  235. * @internal This property has no effect yet. 
  236. * @var float $trial_price. 
  237. */ 
  238. protected $trial_price = 0; 
  239.  
  240. /** 
  241. * Membership trial period. 
  242. * 
  243. * @since 1.0.0 
  244. * @var array $trial_period {@see $period $period property}. 
  245. */ 
  246. protected $trial_period = array( 'period_unit' => 1, 'period_type' => 'days' ); 
  247.  
  248. /** 
  249. * Move to Membership when the current one expires. 
  250. * 
  251. * After current membership expire move to the indicated membership_id. 
  252. * This membership is assigned when the current membership expires. 
  253. * 
  254. * @see MS_Model_Relationship::check_membership_status() 
  255. * 
  256. * @since 1.0.0 
  257. * @var int $on_end_membership_id. 
  258. */ 
  259. protected $on_end_membership_id = 0; 
  260.  
  261. /** 
  262. * Membership setup completed flag. 
  263. * 
  264. * We need this to determine if payment options of the membership are edited 
  265. * the first time during the setup assistant, or later via the membership 
  266. * list. 
  267. * 
  268. * @since 1.0.0 
  269. * @internal 
  270. * @var bool $is_setup_completed. 
  271. */ 
  272. protected $is_setup_completed = false; 
  273.  
  274. /** 
  275. * Where the data came from. Can only be changed by data import tool. 
  276. * 
  277. * @since 1.1.0 
  278. * @internal 
  279. * @var string 
  280. */ 
  281. protected $source = ''; 
  282.  
  283. /** 
  284. * Membership composite Rules. 
  285. * 
  286. * These are protection rules for this membership only. 
  287. * 
  288. * Network-wide mode: The rules stored in here are the rules that apply 
  289. * to the currently selected site in the network! 
  290. * 
  291. * Example: 
  292. * When the network has 10 sites then $rule_values will have 10 "page" rules 
  293. * which are stored as "1:page", "2:page", ... 
  294. * However, the $_rules property will only have ONE "page" rule, and that's 
  295. * the one for the currently visible site! 
  296. * 
  297. * @since 1.0.0 
  298. * @internal 
  299. * @var array MS_Rule[]. 
  300. */ 
  301. protected $_rules = array(); 
  302.  
  303. /** 
  304. * Only used for serialization of the membership. 
  305. * @see __sleep() 
  306. * 
  307. * @since 1.1.0 
  308. * @internal 
  309. * @var array 
  310. */ 
  311. protected $rule_values = array(); 
  312.  
  313. /** 
  314. * Used in simulation mode explaining why a page is allowed or denied. 
  315. * 
  316. * @since 1.0.1 
  317. * @internal 
  318. * @var array 
  319. */ 
  320. public $_access_reason = array(); 
  321.  
  322. /** 
  323. * Similar to $_access_reason, but only contains the rules that denied page 
  324. * access. 
  325. * 
  326. * @since 1.1.0 
  327. * @internal 
  328. * @var array 
  329. */ 
  330. public $_deny_rule = array(); 
  331.  
  332. /** 
  333. * Similar to $_access_reason, but only contains the rules that allowed page 
  334. * access. 
  335. * 
  336. * @since 1.1.0 
  337. * @internal 
  338. * @var array 
  339. */ 
  340. public $_allow_rule = array(); 
  341.  
  342. /** 
  343. * Stores the subscription-ID of the parent object. 
  344. * This value will only have a value when the Membership is loaded within 
  345. * the context of a subscription. 
  346. * 
  347. * @since 1.1.0.7 
  348. * @var int 
  349. */ 
  350. protected $subscription_id = 0; 
  351.  
  352.  
  353. // 
  354. // 
  355. // 
  356. // -------------------------------------------------------------- COLLECTION 
  357.  
  358.  
  359. /** 
  360. * Returns the post-type of the current object. 
  361. * 
  362. * @since 2.0.0 
  363. * @api 
  364. * 
  365. * @return string The post-type name. 
  366. */ 
  367. public static function get_post_type() { 
  368. return parent::_post_type( self::$POST_TYPE ); 
  369.  
  370. /** 
  371. * Get custom register post type args for this model. 
  372. * 
  373. * @since 1.0.0 
  374. * @internal 
  375. */ 
  376. public static function get_register_post_type_args() { 
  377. $args = array( 
  378. 'label' => __( 'Membership2 Memberships', MS_TEXT_DOMAIN ),  
  379. 'description' => __( 'Memberships user can join to.', MS_TEXT_DOMAIN ),  
  380. 'show_ui' => false,  
  381. 'show_in_menu' => false,  
  382. 'menu_position' => 70, // below Users 
  383. 'menu_icon' => 'dashicons-lock',  
  384. 'public' => true,  
  385. 'has_archive' => false,  
  386. 'publicly_queryable' => false,  
  387. 'supports' => false,  
  388. 'hierarchical' => false,  
  389. ); 
  390.  
  391. return apply_filters( 
  392. 'ms_customposttype_register_args',  
  393. $args,  
  394. self::get_post_type() 
  395. ); 
  396.  
  397. /** 
  398. * Get membership types. 
  399. * 
  400. * @since 1.0.0 
  401. * @internal 
  402. * 
  403. * @return array { 
  404. * Returns array of $type => $title. 
  405. * 
  406. * @type string $type The membership type 
  407. * @type string $title The membership type title 
  408. * } 
  409. */ 
  410. static public function get_types() { 
  411. $types = array( 
  412. self::TYPE_STANDARD => __( 'Standard Membership', MS_TEXT_DOMAIN ),  
  413. self::TYPE_DRIPPED => __( 'Dripped Content Membership', MS_TEXT_DOMAIN ),  
  414. self::TYPE_GUEST => __( 'Guest Membership', MS_TEXT_DOMAIN ),  
  415. self::TYPE_USER => __( 'Default Membership', MS_TEXT_DOMAIN ),  
  416. self::TYPE_BASE => __( 'System Membership', MS_TEXT_DOMAIN ),  
  417. ); 
  418.  
  419. return apply_filters( 'ms_model_membership_get_types', $types ); 
  420.  
  421. /** 
  422. * Get membership payment types. 
  423. * 
  424. * @since 1.0.0 
  425. * @internal 
  426. * 
  427. * @return array { 
  428. * Returns array of $type => $title. 
  429. * 
  430. * @type string $type The membership payment type 
  431. * @type string $title The membership payment type title 
  432. * } 
  433. */ 
  434. public static function get_payment_types( $type = 'paid' ) { 
  435. if ( 'free' == $type ) { 
  436. $payment_types = array( 
  437. self::PAYMENT_TYPE_PERMANENT => __( 'Permanent access', MS_TEXT_DOMAIN ),  
  438. self::PAYMENT_TYPE_FINITE => __( 'Finite access', MS_TEXT_DOMAIN ),  
  439. self::PAYMENT_TYPE_DATE_RANGE => __( 'Date range access', MS_TEXT_DOMAIN ),  
  440. ); 
  441. } else { 
  442. $payment_types = array( 
  443. self::PAYMENT_TYPE_PERMANENT => __( 'One payment for permanent access', MS_TEXT_DOMAIN ),  
  444. self::PAYMENT_TYPE_FINITE => __( 'One payment for finite access', MS_TEXT_DOMAIN ),  
  445. self::PAYMENT_TYPE_DATE_RANGE => __( 'One payment for date range access', MS_TEXT_DOMAIN ),  
  446. self::PAYMENT_TYPE_RECURRING => __( 'Recurring payments', MS_TEXT_DOMAIN ),  
  447. ); 
  448.  
  449. return apply_filters( 
  450. 'ms_model_membership_get_payment_types',  
  451. $payment_types,  
  452. $type 
  453. ); 
  454.  
  455. /** 
  456. * Get available Memberships count. 
  457. * 
  458. * @since 1.0.0 
  459. * @internal 
  460. * 
  461. * @param $args The query post args 
  462. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  463. * @return int The membership count. 
  464. */ 
  465. public static function get_membership_count( $args = null ) { 
  466. MS_Factory::select_blog(); 
  467. $args = self::get_query_args( $args ); 
  468. $query = new WP_Query( $args ); 
  469. MS_Factory::revert_blog(); 
  470.  
  471. $count = $query->found_posts; 
  472.  
  473. return apply_filters( 
  474. 'ms_model_membership_get_membership_count',  
  475. $count,  
  476. $args 
  477. ); 
  478.  
  479. /** 
  480. * Find out if the installation has at least one paid membership 
  481. * 
  482. * @since 1.1.0 
  483. * @internal 
  484. * 
  485. * @return bool 
  486. */ 
  487. public static function have_paid_membership() { 
  488. static $Have_Paid = null; 
  489.  
  490. if ( null === $Have_Paid ) { 
  491. global $wpdb; 
  492. // Using a custom WPDB query because building the meta-query is more 
  493. // complex than really required here... 
  494. $sql = " 
  495. SELECT COUNT( 1 ) 
  496. FROM {$wpdb->posts} p 
  497. INNER JOIN {$wpdb->postmeta} priv ON priv.post_id = p.ID AND priv.meta_key = %s 
  498. INNER JOIN {$wpdb->postmeta} free ON free.post_id = p.ID AND free.meta_key = %s 
  499. INNER JOIN {$wpdb->postmeta} pric ON pric.post_id = p.ID AND pric.meta_key = %s 
  500. WHERE 
  501. p.post_type = %s 
  502. AND priv.meta_value != '1' 
  503. AND NOT ( 
  504. free.meta_value = '1' 
  505. OR pric.meta_value = '0' 
  506. "; 
  507.  
  508. $sql = $wpdb->prepare( 
  509. $sql,  
  510. 'private', // INNER JOIN 
  511. 'is_free', // INNER JOIN 
  512. 'price', // INNER JOIN 
  513. self::get_post_type() // WHERE condition 
  514. ); 
  515.  
  516. $res = $wpdb->get_var( $sql ); 
  517.  
  518. $Have_Paid = apply_filters( 
  519. 'ms_model_membership_have_paid_membership',  
  520. intval( $res ) > 0 
  521. ); 
  522.  
  523. return $Have_Paid; 
  524.  
  525. /** 
  526. * Get WP_Query object arguments. 
  527. * 
  528. * Default search arguments for this custom post_type. 
  529. * 
  530. * @since 1.0.0 
  531. * @internal 
  532. * 
  533. * @param $args The query post args 
  534. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  535. * @return array $args The parsed args. 
  536. */ 
  537. public static function get_query_args( $args = null ) { 
  538. $defaults = apply_filters( 
  539. 'ms_model_membership_get_query_args_defaults',  
  540. array( 
  541. 'post_type' => self::get_post_type(),  
  542. 'order' => 'DESC',  
  543. 'orderby' => 'name',  
  544. 'post_status' => 'any',  
  545. 'post_per_page' => -1,  
  546. 'nopaging' => true,  
  547. 'include_base' => false,  
  548. 'include_guest' => true,  
  549. ); 
  550.  
  551. $args = wp_parse_args( $args, $defaults ); 
  552.  
  553. if ( ! lib2()->is_true( $args['include_base'] ) ) { 
  554. $args['meta_query']['base'] = array( 
  555. 'key' => 'type',  
  556. 'value' => self::TYPE_BASE,  
  557. 'compare' => '!=',  
  558. ); 
  559.  
  560. if ( ! lib2()->is_true( $args['include_guest'] ) ) { 
  561. $args['meta_query']['guest'] = array( 
  562. 'key' => 'type',  
  563. 'value' => self::TYPE_GUEST,  
  564. 'compare' => '!=',  
  565. ); 
  566. $args['meta_query']['user'] = array( 
  567. 'key' => 'type',  
  568. 'value' => self::TYPE_USER,  
  569. 'compare' => '!=',  
  570. ); 
  571.  
  572. return apply_filters( 
  573. 'ms_model_membership_get_query_args',  
  574. $args,  
  575. $defaults 
  576. ); 
  577.  
  578. /** 
  579. * Get Memberships models. 
  580. * 
  581. * When no $args are specified then all memberships except the base 
  582. * membership will be returned. 
  583. * 
  584. * To include the base membership use: 
  585. * $args = array( 'include_base' => 1 ) 
  586. * 
  587. * To exclude the guest membership use: 
  588. * $args = array( 'include_guest' => 0 ) 
  589. * 
  590. * @since 1.0.0 
  591. * @internal 
  592. * 
  593. * @param $args The query post args 
  594. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  595. * @return MS_Model_Membership[] The selected memberships. 
  596. */ 
  597. static public function get_memberships( $args = null ) { 
  598. $args = self::get_query_args( $args ); 
  599. $args['order'] = 'ASC'; 
  600.  
  601. MS_Factory::select_blog(); 
  602. $query = new WP_Query( $args ); 
  603. $items = $query->get_posts(); 
  604. MS_Factory::revert_blog(); 
  605.  
  606. $memberships = array(); 
  607. foreach ( $items as $item ) { 
  608. $memberships[] = MS_Factory::load( 
  609. 'MS_Model_Membership',  
  610. $item->ID 
  611. ); 
  612.  
  613. return apply_filters( 
  614. 'ms_model_membership_get_memberships',  
  615. $memberships,  
  616. $args 
  617. ); 
  618.  
  619. /** 
  620. * Returns a list of the dripped memberships. 
  621. * 
  622. * @since 1.1.0 
  623. * @internal 
  624. * 
  625. * @param $args The query post args 
  626. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  627. * @return MS_Model_Membership[] The selected memberships. 
  628. */ 
  629. static public function get_dripped_memberships( $args = null ) { 
  630. $drip_args = array( 
  631. 'meta_query' => array( 
  632. array( 
  633. 'key' => 'type',  
  634. 'value' => self::TYPE_DRIPPED,  
  635. ),  
  636. ),  
  637. ); 
  638.  
  639. $drip_args = wp_parse_args( $drip_args, $args ); 
  640. $memberships = self::get_memberships( $drip_args ); 
  641.  
  642. return apply_filters( 
  643. 'ms_model_membership_get_dripped_memberships',  
  644. $memberships,  
  645. $args 
  646. ); 
  647.  
  648. /** 
  649. * Get membership names. 
  650. * 
  651. * @since 1.0.0 
  652. * @internal 
  653. * 
  654. * @param $args The query post args 
  655. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  656. * @param bool $include_base_membership Include base membership from the list. 
  657. * @return array { 
  658. * Returns array of $membership_id => $name 
  659. * @type int $membership_id The membership Id. 
  660. * @type string $name The membership name; 
  661. * } 
  662. */ 
  663. public static function get_membership_names( $args = null ) { 
  664. $items = self::get_memberships( $args ); 
  665.  
  666. $memberships = array(); 
  667. foreach ( $items as $item ) { 
  668. $memberships[ $item->id ] = $item->name; 
  669.  
  670. return apply_filters( 
  671. 'ms_model_membership_get_membership_names',  
  672. $memberships,  
  673. $args 
  674. ); 
  675.  
  676. /** 
  677. * Get membership eligible to signup. 
  678. * 
  679. * @since 1.0.0 
  680. * @internal 
  681. * 
  682. * @param $args The query post args 
  683. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  684. * @param int[] $exclude_ids Optional. The membership ids to exclude. 
  685. * @param bool $only_names Optional. Return only array { 
  686. * @type int $membership_id The membership ID. 
  687. * @type string $membership_name The membership name. 
  688. * } 
  689. * @param bool $include_private If private memberships should be listed 
  690. * This param is only recognized in the admin section so admins can 
  691. * manually assign a private membership to a user. 
  692. * @return array { 
  693. * Returns array of $membership_id => $membership 
  694. * @type int $membership_id The membership Id. 
  695. * @type MS_Model_Membership The membership model object. 
  696. * } 
  697. */ 
  698. public static function get_signup_membership_list( 
  699. $args = null,  
  700. $exclude_ids = null,  
  701. $only_names = false,  
  702. $include_private = false 
  703. ) { 
  704. $not_in = array(); 
  705. if ( is_array( $exclude_ids ) ) { 
  706. $not_in = $exclude_ids; 
  707. $not_in[] = MS_Model_Membership::get_base()->id; 
  708. $not_in[] = MS_Model_Membership::get_guest()->id; 
  709. $not_in[] = MS_Model_Membership::get_user()->id; 
  710. $args['post__not_in'] = array_unique( $not_in ); 
  711.  
  712. if ( ! is_admin() ) { 
  713. $include_private = false; 
  714. // List of private memberships (they are grouped in own array). 
  715. $private = array(); 
  716.  
  717. // Retrieve memberships user is not part of, using selected args 
  718. $memberships = self::get_memberships( $args ); 
  719.  
  720. foreach ( $memberships as $key => $membership ) { 
  721. // Remove if not active. 
  722. if ( ! $membership->active ) { 
  723. unset( $memberships[ $key ] ); 
  724.  
  725. if ( $membership->private ) { 
  726. if ( $include_private ) { 
  727. // Move the private memberships to a option-group. 
  728. $private[ $key ] = $memberships[ $key ]; 
  729. unset( $memberships[ $key ] ); 
  730.  
  731. if ( $only_names ) { 
  732. $ms_names = array(); 
  733. foreach ( $memberships as $ms ) { 
  734. $ms_names[ $ms->id ] = $ms->name; 
  735. if ( ! empty( $private ) ) { 
  736. $priv_key = __( 'Private Memberships', MS_TEXT_DOMAIN ); 
  737. $ms_names[ $priv_key ] = array(); 
  738. foreach ( $private as $ms ) { 
  739. $ms_names[ $priv_key ][ $ms->id ] = $ms->name; 
  740. $memberships = $ms_names; 
  741. } else { 
  742. $memberships = array_merge( $memberships, $private ); 
  743.  
  744. return apply_filters( 
  745. 'ms_model_membership_get_signup_membership_list',  
  746. $memberships,  
  747. $exclude_ids,  
  748. $only_names 
  749. ); 
  750.  
  751. /** 
  752. * Verify if membership is valid. 
  753. * 
  754. * Verify if membership was not deleted, trying to load from DB. 
  755. * 
  756. * @since 1.0.0 
  757. * @api 
  758. * 
  759. * @param int $membership_id The membership id to verify. 
  760. * @return bool True if is valid. 
  761. */ 
  762. public static function is_valid_membership( $membership_id ) { 
  763. $membership = MS_Factory::load( 'MS_Model_Membership', $membership_id, '_is_valid_' ); 
  764. $valid = ( $membership->id > 0 ); 
  765.  
  766. return apply_filters( 
  767. 'ms_model_membership_is_valid_membership',  
  768. $valid,  
  769. $membership_id 
  770. ); 
  771.  
  772. /** 
  773. * Get Membership2 membership. 
  774. * 
  775. * Create a new membership if membership does not exist. 
  776. * 
  777. * @since 1.1.0 
  778. * @internal 
  779. * 
  780. * @param string $type The membership to load [protected_content|role] 
  781. * @param book $create_missing If set to false then missing special 
  782. * memberships are not created. 
  783. * @return MS_Model_Membership The Membership2. 
  784. */ 
  785. private static function _get_system_membership( $type, $create_missing = true ) { 
  786. static $Special_Membership = array(); 
  787. $comp_key = $type; 
  788. $membership = false; 
  789.  
  790. if ( ! isset( $Special_Membership[$comp_key] ) ) { 
  791. $membership = false; 
  792. global $wpdb; 
  793.  
  794. MS_Factory::select_blog(); 
  795. /** 
  796. * We are using a normal SQL query instead of using the WP_Query object 
  797. * here, because the WP_Query object does some strange things sometimes: 
  798. * In some cases new Membership2 memberships were created when a 
  799. * guest accessed the page. 
  800. * 
  801. * By using a manual query we are very certain that only one 
  802. * base-membership exists on the database. 
  803. */ 
  804. $sql = " 
  805. SELECT ID 
  806. FROM {$wpdb->posts} p 
  807. INNER JOIN {$wpdb->postmeta} m_type ON m_type.post_id = p.ID 
  808. WHERE 
  809. p.post_type = %s 
  810. AND m_type.meta_key = %s 
  811. AND m_type.meta_value = %s 
  812. "; 
  813. $values = array( 
  814. self::get_post_type(),  
  815. 'type',  
  816. $type,  
  817. ); 
  818.  
  819. $sql = $wpdb->prepare( $sql, $values ); 
  820. $item = $wpdb->get_results( $sql ); 
  821. $base = array_shift( $item ); // Remove the base membership from the results 
  822. MS_Factory::revert_blog(); 
  823.  
  824. if ( ! empty( $base ) ) { 
  825. $membership = MS_Factory::load( 'MS_Model_Membership', $base->ID ); 
  826. } else if ( $create_missing ) { 
  827. $names = self::get_types(); 
  828.  
  829. $description = __( 'Membership2 Core Membership', MS_TEXT_DOMAIN ); 
  830. $membership = MS_Factory::create( 'MS_Model_Membership' ); 
  831. $membership->name = $names[$type]; 
  832. $membership->title = $names[$type]; 
  833. $membership->description = $description; 
  834. $membership->type = $type; 
  835. $membership->save(); 
  836.  
  837. $Special_Membership[$comp_key] = apply_filters( 
  838. 'ms_model_membership_get_system_membership',  
  839. $membership,  
  840. $type 
  841. ); 
  842.  
  843. return $Special_Membership[$comp_key]; 
  844.  
  845. /** 
  846. * Get Membership2 base membership. 
  847. * 
  848. * Create a new membership if membership does not exist. 
  849. * This is an internal membership which is never displayed anywhere. 
  850. * 
  851. * @since 1.0.0 
  852. * @api 
  853. * 
  854. * @return MS_Model_Membership The base membership. 
  855. */ 
  856. public static function get_base() { 
  857. static $Base_Membership = null; 
  858.  
  859. if ( null === $Base_Membership ) { 
  860. $Base_Membership = self::_get_system_membership( 
  861. self::TYPE_BASE 
  862. ); 
  863.  
  864. foreach ( $Base_Membership->_rules as $key => $rule ) { 
  865. $Base_Membership->_rules[$key]->is_base_rule = true; 
  866.  
  867. $Base_Membership = apply_filters( 
  868. 'ms_model_membership_get_base',  
  869. $Base_Membership 
  870. ); 
  871.  
  872. return $Base_Membership; 
  873.  
  874. /** 
  875. * Get special membership that is assigned to all guests. 
  876. * 
  877. * Create a new membership if membership does not exist. 
  878. * 
  879. * @since 1.1.0 
  880. * @api 
  881. * 
  882. * @param string $role A WordPress user-role. 
  883. * @return MS_Model_Membership The guest membership. 
  884. */ 
  885. public static function get_guest() { 
  886. static $Guest_Membership = null; 
  887.  
  888. if ( null === $Guest_Membership ) { 
  889. $Guest_Membership = self::_get_system_membership( 
  890. self::TYPE_GUEST,  
  891. false // Don't create this membership automatically 
  892. ); 
  893.  
  894. $Guest_Membership = apply_filters( 
  895. 'ms_model_membership_get_guest',  
  896. $Guest_Membership 
  897. ); 
  898.  
  899. if ( ! $Guest_Membership ) { 
  900. $Guest_Membership = MS_Factory::create( 'MS_Model_Membership' ); 
  901.  
  902. return $Guest_Membership; 
  903.  
  904. /** 
  905. * Get default membership for all logged-in users that did not yet subscribe 
  906. * to any membership. 
  907. * 
  908. * Create a new membership if membership does not exist. 
  909. * 
  910. * @since 1.1.0 
  911. * @api 
  912. * 
  913. * @param string $role A WordPress user-role. 
  914. * @return MS_Model_Membership The guest membership. 
  915. */ 
  916. public static function get_user() { 
  917. static $User_Membership = null; 
  918.  
  919. if ( null === $User_Membership ) { 
  920. $User_Membership = self::_get_system_membership( 
  921. self::TYPE_USER,  
  922. false // Don't create this membership automatically 
  923. ); 
  924.  
  925. $User_Membership = apply_filters( 
  926. 'ms_model_membership_get_user',  
  927. $User_Membership 
  928. ); 
  929.  
  930. if ( ! $User_Membership ) { 
  931. $User_Membership = MS_Factory::create( 'MS_Model_Membership' ); 
  932.  
  933. return $User_Membership; 
  934.  
  935.  
  936. // 
  937. // 
  938. // 
  939. // ------------------------------------------------------------- SINGLE ITEM 
  940.  
  941.  
  942. /** 
  943. * Returns a list of variables that should be included in serialization,  
  944. * i.e. these values are the only ones that are stored in DB 
  945. * 
  946. * @since 1.1.0 
  947. * @internal 
  948. * @return array 
  949. */ 
  950. public function __sleep() { 
  951. /** 
  952. * Rule values are pre-processd before saving... 
  953. * Note: $this->_rules only contains rules for the *current* site, so 
  954. * all rules that are serialized here get the current-site prefix. 
  955. * Rules for the other sites are already in the $this->rule_values 
  956. * array and were not de-serialized on page load. 
  957. */ 
  958. $this->rule_values = lib2()->array->get( $this->rule_values ); 
  959. foreach ( $this->_rules as $rule_type => $rule ) { 
  960. $key = MS_Rule::rule_key( $rule_type ); 
  961.  
  962. $this->rule_values[$key] = $rule->serialize(); 
  963. if ( empty( $this->rule_values[$key] ) ) { 
  964. unset( $this->rule_values[$key] ); 
  965.  
  966. return array( 
  967. 'id',  
  968. 'name',  
  969. 'title',  
  970. 'description',  
  971. 'rule_values',  
  972. 'type',  
  973. 'payment_type',  
  974. 'active',  
  975. 'private',  
  976. 'is_free',  
  977. 'disabled_gateways',  
  978. 'price',  
  979. 'period',  
  980. 'pay_cycle_period',  
  981. 'pay_cycle_repetitions',  
  982. 'period_date_start',  
  983. 'period_date_end',  
  984. 'trial_period_enabled',  
  985. 'trial_price',  
  986. 'trial_period',  
  987. 'on_end_membership_id',  
  988. 'is_setup_completed',  
  989. 'custom_data',  
  990. ); 
  991.  
  992. /** 
  993. * Set rules membership_id before saving. 
  994. * 
  995. * @since 1.0.0 
  996. * @internal 
  997. */ 
  998. public function before_save() { 
  999. parent::before_save(); 
  1000.  
  1001. foreach ( $this->_rules as $rule ) { 
  1002. $rule->membership_id = $this->id; 
  1003.  
  1004. /** 
  1005. * Save model and move the object to the singleton cache if required. 
  1006. * 
  1007. * @since 2.0.0 
  1008. */ 
  1009. public function save() { 
  1010. parent::save(); 
  1011. parent::store_singleton(); 
  1012.  
  1013. /** 
  1014. * After the membership was saved to DB we make sure that it is published. 
  1015. * 
  1016. * Network-wide mode: We are still in the switched blog (main site) so 
  1017. * there is no need to call MS_Factory::select_blog() in this function. 
  1018. * 
  1019. * @since 2.0.0 
  1020. * @internal 
  1021. */ 
  1022. public function after_save() { 
  1023. // It is important! The Membership2 membership must be public 
  1024. // so that the membership options are available for guest users. 
  1025. wp_publish_post( $this->id ); 
  1026.  
  1027. /** 
  1028. * Permanently delete the membership. 
  1029. * 
  1030. * @since 1.0.0 
  1031. * @api 
  1032. * 
  1033. * @return bool 
  1034. */ 
  1035. public function delete() { 
  1036. do_action( 'ms_model_membership_before_delete', $this ); 
  1037. $res = false; 
  1038.  
  1039. if ( $this->is_base() ) { 
  1040. throw new Exception( 
  1041. 'Can not delete the system membership.' 
  1042. ); 
  1043.  
  1044. if ( ! empty( $this->id ) ) { 
  1045. if ( $this->get_members_count() > 0 ) { 
  1046. $ms_relationships = MS_Model_Relationship::get_subscriptions( 
  1047. array( 'membership_id' => $this->id ),  
  1048. true 
  1049. ); 
  1050.  
  1051. foreach ( $ms_relationships as $ms_relationship ) { 
  1052. $ms_relationship->delete(); 
  1053.  
  1054. $res = ( false !== wp_delete_post( $this->id, true ) ); 
  1055.  
  1056. do_action( 'ms_model_membership_after_delete', $this, $res ); 
  1057. return $res; 
  1058.  
  1059. /** 
  1060. * Merge current rules to Membership2. 
  1061. * 
  1062. * Assure the membership rules get updated whenever Membership2 is changed. 
  1063. * 
  1064. * @since 1.0.0 
  1065. * @internal 
  1066. */ 
  1067. public function prepare_obj() { 
  1068. parent::prepare_obj(); 
  1069.  
  1070. if ( false !== strpos( $this->_factory_id, '_is_valid_' ) ) { 
  1071. // This object only checks if the item ID is valid. 
  1072. // No need to load any rules yet... 
  1073. return; 
  1074.  
  1075. foreach ( $this->rule_values as $key => $values ) { 
  1076. // Skip rules without any values. 
  1077. if ( empty( $values ) ) { continue; } 
  1078.  
  1079. // Network-wide: Only instanciate rules for the *current* site! 
  1080. if ( ! MS_Rule::is_current_site( $key ) ) { continue; } 
  1081.  
  1082. // Key could be "type" of "site:type" format. 
  1083. $rule_type = MS_Rule::rule_type( $key ); 
  1084.  
  1085. // At this point we have an empty rule-instance 
  1086. $rule = $this->get_rule( $rule_type ); 
  1087.  
  1088. // Now we populate that rule-instance with site-specific settings. 
  1089. $rule->populate( $values ); 
  1090.  
  1091. // validate rules using Membership2 rules 
  1092. if ( ! $this->is_base() && $this->is_valid() ) { 
  1093. $this->merge_protection_rules(); 
  1094.  
  1095. /** 
  1096. * Get current payment type description. 
  1097. * 
  1098. * Description to show in the admin list table. 
  1099. * 
  1100. * @since 1.0.0 
  1101. * @api 
  1102. * 
  1103. * @return string The current payment type description. 
  1104. */ 
  1105. public function get_payment_type_desc() { 
  1106. $desc = __( 'N/A', MS_TEXT_DOMAIN ); 
  1107. $has_payment = ! $this->is_free(); 
  1108.  
  1109. switch ( $this->payment_type ) { 
  1110. case self::PAYMENT_TYPE_FINITE: 
  1111. $desc = sprintf( 
  1112. __( 'For %1$s', MS_TEXT_DOMAIN ),  
  1113. MS_Helper_Period::get_period_desc( $this->period ) 
  1114. ); 
  1115. break; 
  1116.  
  1117. case self::PAYMENT_TYPE_DATE_RANGE: 
  1118. $desc = sprintf( 
  1119. __( 'From %1$s to %2$s', MS_TEXT_DOMAIN ),  
  1120. $this->period_date_start,  
  1121. $this->period_date_end 
  1122. ); 
  1123. break; 
  1124.  
  1125. case self::PAYMENT_TYPE_RECURRING: 
  1126. $desc = __( 'Each %1$s', MS_TEXT_DOMAIN ); 
  1127.  
  1128. if ( $has_payment ) { 
  1129. if ( 1 == $this->pay_cycle_repetitions ) { 
  1130. $desc = __( 'Single payment', MS_TEXT_DOMAIN ); 
  1131. } elseif ( $this->pay_cycle_repetitions > 1 ) { 
  1132. $desc .= ', ' . __( '%2$s payments', MS_TEXT_DOMAIN ); 
  1133.  
  1134. $desc = sprintf( 
  1135. $desc,  
  1136. MS_Helper_Period::get_period_desc( $this->pay_cycle_period ),  
  1137. $this->pay_cycle_repetitions 
  1138. ); 
  1139. break; 
  1140.  
  1141. case self::PAYMENT_TYPE_PERMANENT: 
  1142. default: 
  1143. if ( $has_payment ) { 
  1144. $desc = __( 'Single payment', MS_TEXT_DOMAIN ); 
  1145. } else { 
  1146. $desc = __( 'Permanent access', MS_TEXT_DOMAIN ); 
  1147. break; 
  1148.  
  1149. return apply_filters( 
  1150. 'ms_model_membership_get_payment_type_desc',  
  1151. $desc,  
  1152. $this 
  1153. ); 
  1154.  
  1155. /** 
  1156. * Returns true if the current membership is free. 
  1157. * 
  1158. * A membership is free when... 
  1159. * ... it is explicitely marked as "free" 
  1160. * ... the price is 0.00 
  1161. * ... it is a parent membership that cannot be signed up for 
  1162. * 
  1163. * @since 1.0.4.7 
  1164. * @api 
  1165. * 
  1166. * @return bool 
  1167. */ 
  1168. public function is_free() { 
  1169. $result = false; 
  1170.  
  1171. if ( $this->is_free ) { $result = true; } 
  1172. elseif ( empty( $this->price ) ) { $result = true; } 
  1173.  
  1174. $result = apply_filters( 
  1175. 'ms_model_membership_is_free',  
  1176. $result,  
  1177. $this 
  1178. ); 
  1179.  
  1180. if ( $result && $this->is_free ) { 
  1181. $this->is_free = $result; 
  1182.  
  1183. return $result; 
  1184.  
  1185. /** 
  1186. * Checks if a specific payment gateway is allowed for the current 
  1187. * membership. 
  1188. * 
  1189. * @since 2.0.0 
  1190. * @param string $gateway_id The payment gateway ID. 
  1191. * @return bool 
  1192. */ 
  1193. public function can_use_gateway( $gateway_id ) { 
  1194. $result = true; 
  1195.  
  1196. $this->disabled_gateways = lib2()->array->get( $this->disabled_gateways ); 
  1197. if ( isset( $this->disabled_gateways[$gateway_id] ) ) { 
  1198. $state = $this->disabled_gateways[$gateway_id]; 
  1199. $result = ! lib2()->is_true( $state ); 
  1200.  
  1201. if ( $result ) { 
  1202. $gateway = MS_Model_Gateway::factory( $gateway_id ); 
  1203. $result = $gateway->payment_type_supported( $this ); 
  1204.  
  1205. $result = apply_filters( 
  1206. 'ms_model_membership_can_use_gateway',  
  1207. $result,  
  1208. $gateway_id,  
  1209. $this 
  1210. ); 
  1211. return $result; 
  1212.  
  1213. /** 
  1214. * Get protection Rule Model. 
  1215. * 
  1216. * Note for network-wide mode: 
  1217. * In DB the rules for each site are stored in different objects. 
  1218. * When loading a membership we will always load 1 instance of each 
  1219. * rule_type, and this is the instance that belongs to the current site! 
  1220. * Instances for other sites are not accessible. 
  1221. * -> This is why we do not use/need a site_id or similar in this function. 
  1222. * 
  1223. * @since 1.0.0 
  1224. * @api 
  1225. * 
  1226. * @param string $rule_type The rule model type @see MS_Rule 
  1227. * @return MS_Rule The requested rule model. 
  1228. */ 
  1229. public function get_rule( $rule_type ) { 
  1230. if ( 'attachment' === $rule_type ) { 
  1231. $rule_type = MS_Rule_Media::RULE_ID; 
  1232.  
  1233. if ( ! isset( $this->_rules[ $rule_type ] ) 
  1234. || ! is_object( $this->_rules[ $rule_type ] ) // During plugin update. 
  1235. ) { 
  1236. // Create a new rule model object. 
  1237. $rule = MS_Rule::rule_factory( 
  1238. $rule_type,  
  1239. $this->id,  
  1240. $this->subscription_id 
  1241. ); 
  1242.  
  1243. $rule = apply_filters( 
  1244. 'ms_model_membership_get_rule',  
  1245. $rule,  
  1246. $rule_type,  
  1247. $this 
  1248. ); 
  1249.  
  1250. $this->_rules[ $rule_type ] = $rule; 
  1251. if ( ! is_array( $rule->rule_value ) ) { 
  1252. $rule->rule_value = array(); 
  1253.  
  1254. return $this->_rules[ $rule_type ]; 
  1255.  
  1256. /** 
  1257. * Set protection Rule Model. 
  1258. * 
  1259. * Note for network-wide mode: 
  1260. * In DB the rules for each site are stored in different objects. 
  1261. * When loading a membership we will always load 1 instance of each 
  1262. * rule_type, and this is the instance that belongs to the current site! 
  1263. * Instances for other sites are not accessible. 
  1264. * -> This is why we do not use/need a site_id or similar in this function. 
  1265. * 
  1266. * @since 1.0.0 
  1267. * @api 
  1268. * 
  1269. * @param string The rule model type @see MS_Rule 
  1270. * @param MS_Rule $rule The protection rule to set. 
  1271. */ 
  1272. public function set_rule( $rule_type, $rule ) { 
  1273. $this->_rules[ $rule_type ] = apply_filters( 
  1274. 'ms_model_membership_set_rule',  
  1275. $rule,  
  1276. $rule_type,  
  1277. $this 
  1278. ); 
  1279.  
  1280. /** 
  1281. * Returns the unique HEX color for this membership. 
  1282. * The color is calculated from the membership-ID and therefore will never 
  1283. * change. 
  1284. * 
  1285. * @since 1.1.0 
  1286. * @api 
  1287. * 
  1288. * @return string Hex color, e.g. '#FFFFFF' 
  1289. */ 
  1290. public function get_color() { 
  1291. return MS_Helper_Utility::color_index( $this->type . $this->id ); 
  1292.  
  1293. /** 
  1294. * Returns a HTML tag that shows the membership name with the internal 
  1295. * membership color. 
  1296. * 
  1297. * @since 2.0.0 
  1298. * @api 
  1299. */ 
  1300. public function get_name_tag() { 
  1301. $code = sprintf( 
  1302. '<span class="ms-membership" style="background:%2$s">%1$s</span>',  
  1303. esc_html( $this->name ),  
  1304. $this->get_color() 
  1305. ); 
  1306.  
  1307. return $code; 
  1308.  
  1309. /** 
  1310. * Echo a HTML tag that shows the membership name with the internal 
  1311. * membership color. 
  1312. * 
  1313. * @since 2.0.0 
  1314. * @api 
  1315. */ 
  1316. public function name_tag() { 
  1317. echo $this->get_name_tag(); 
  1318.  
  1319. /** 
  1320. * Get current membership type description. 
  1321. * 
  1322. * @since 1.0.0 
  1323. * @api 
  1324. * 
  1325. * @return string The membership type description. 
  1326. */ 
  1327. public function get_type_description() { 
  1328. $types = self::get_types(); 
  1329. $desc = $types[ $this->type ]; 
  1330.  
  1331. return apply_filters( 
  1332. 'ms_model_membership_get_type_description',  
  1333. $desc,  
  1334. $this 
  1335. ); 
  1336.  
  1337. /** 
  1338. * Either creates or updates the value of a custom data field. 
  1339. * 
  1340. * Note: Remember to prefix the $key with a unique string to prevent 
  1341. * conflicts with other plugins that also use this function. 
  1342. * 
  1343. * @since 2.0.0 
  1344. * @api 
  1345. * 
  1346. * @param string $key The field-key. 
  1347. * @param mixed $value The new value to assign to the field. 
  1348. */ 
  1349. public function set_custom_data( $key, $value ) { 
  1350. // Wrapper function, so this function shows up in API docs. 
  1351. parent::set_custom_data( $key, $value ); 
  1352.  
  1353. /** 
  1354. * Removes a custom data field from this object. 
  1355. * 
  1356. * @since 2.0.0 
  1357. * @api 
  1358. * 
  1359. * @param string $key The field-key. 
  1360. */ 
  1361. public function delete_custom_data( $key ) { 
  1362. // Wrapper function, so this function shows up in API docs. 
  1363. parent::delete_custom_data( $key ); 
  1364.  
  1365. /** 
  1366. * Returns the value of a custom data field. 
  1367. * 
  1368. * @since 2.0.0 
  1369. * @api 
  1370. * 
  1371. * @param string $key The field-key. 
  1372. * @return mixed The value that was previously assigned to the custom field 
  1373. * or false if no value was set for the field. 
  1374. */ 
  1375. public function get_custom_data( $key ) { 
  1376. // Wrapper function, so this function shows up in API docs. 
  1377. return parent::get_custom_data( $key ); 
  1378.  
  1379. /** 
  1380. * Merge Membership2 rules. 
  1381. * 
  1382. * Merge every rule model with Membership2/visitor membership rules. 
  1383. * This ensure rules are consistent with Membership2 rules. 
  1384. * 
  1385. * @since 1.0.0 
  1386. * @internal 
  1387. */ 
  1388. public function merge_protection_rules() { 
  1389. if ( $this->is_base() ) { 
  1390. // This is the visitor membership, no need to merge anything. 
  1391. return; 
  1392.  
  1393. $base_rules = self::get_base()->_rules; 
  1394.  
  1395. foreach ( $base_rules as $key => $base_rule ) { 
  1396. try { 
  1397. // Key could be "type" of "site:type" format. 
  1398. $rule_type = MS_Rule::rule_type( $key ); 
  1399.  
  1400. $rule = $this->get_rule( $rule_type ); 
  1401. $rule->protect_undefined_items( $base_rule, true ); 
  1402. $this->set_rule( $rule_type, $rule ); 
  1403. catch( Exception $e ) { 
  1404. MS_Helper_Debug::log( $e ); 
  1405.  
  1406. $this->_rules = apply_filters( 
  1407. 'ms_model_membership_merge_protection_rules',  
  1408. $this->_rules,  
  1409. $this 
  1410. ); 
  1411.  
  1412. /** 
  1413. * Get after membership expired options. 
  1414. * 
  1415. * Memberships can be downgraded to the guest level protection. 
  1416. * 
  1417. * @since 1.0.0 
  1418. * @api 
  1419. * 
  1420. * @return array { 
  1421. * Returns array of $membership_id => $description. 
  1422. * @type int $membership_id The membership Id. 
  1423. * @type string $description The expired option description. 
  1424. * } 
  1425. */ 
  1426. public function get_after_ms_ends_options() { 
  1427. $options = array( 
  1428. 0 => __( 'Restrict access to Visitor-Level', MS_TEXT_DOMAIN ),  
  1429. ); 
  1430.  
  1431. $args = array( 
  1432. 'include_guest' => false,  
  1433. ); 
  1434. $options += $this->get_membership_names( $args ); 
  1435. unset( $options[$this->id] ); 
  1436.  
  1437. $label = __( 'Change to: %s', MS_TEXT_DOMAIN ); 
  1438. foreach ( $options as $id => $option ) { 
  1439. if ( $id > 0 ) { 
  1440. $options[$id] = sprintf( $label, $option ); 
  1441.  
  1442. return apply_filters( 
  1443. 'ms_model_membership_get_membership_names',  
  1444. $options,  
  1445. $this 
  1446. ); 
  1447.  
  1448. /** 
  1449. * Get members count of this membership. 
  1450. * 
  1451. * @since 1.0.0 
  1452. * @api 
  1453. * 
  1454. * @return int The members count. 
  1455. */ 
  1456. public function get_members_count() { 
  1457. $count = MS_Model_Relationship::get_subscription_count( 
  1458. array( 'membership_id' => $this->id ) 
  1459. ); 
  1460.  
  1461. return apply_filters( 
  1462. 'ms_model_membership_get_members_count',  
  1463. $count 
  1464. ); 
  1465.  
  1466. /** 
  1467. * Return membership has dripped content. 
  1468. * 
  1469. * Verify post and page rules if there is a dripped content. 
  1470. * 
  1471. * @since 1.0.0 
  1472. * @api 
  1473. * 
  1474. * @return boolean 
  1475. */ 
  1476. public function has_dripped_content() { 
  1477. $has_dripped = false; 
  1478. $dripped = array( 'post', 'page' ); 
  1479.  
  1480. foreach ( $dripped as $rule_type ) { 
  1481. // using count() as !empty() never returned true 
  1482. if ( 0 < count( $this->get_rule( $rule_type )->dripped ) ) { 
  1483. $has_dripped = true; 
  1484.  
  1485. return apply_filters( 
  1486. 'ms_model_membership_has_dripped_content',  
  1487. $has_dripped,  
  1488. $this 
  1489. ); 
  1490.  
  1491. /** 
  1492. * Get protection rules sorted. 
  1493. * 
  1494. * First one has priority over the last one. 
  1495. * These rules are used to determine access. 
  1496. * 
  1497. * @since 1.0.0 
  1498. * @internal 
  1499. */ 
  1500. private function get_rules_hierarchy() { 
  1501. $rule_types = MS_Model_Rule::get_rule_types(); 
  1502. $rules = array(); 
  1503. $subscription = MS_Factory::load( 'MS_Model_Relationship', $this->subscription_id ); 
  1504.  
  1505. foreach ( $rule_types as $rule_type ) { 
  1506. $rule = $this->get_rule( $rule_type ); 
  1507.  
  1508. if ( $rule->rule_type != $rule_type ) { 
  1509. // This means that the $rule_type was not found... 
  1510. continue; 
  1511.  
  1512. // Sometimes the $subscription->id can be 0, which is intentional: 
  1513. // This is the case when the membership was auto-assigned to guest 
  1514. // or default membership. 
  1515. $rule->_subscription_id = $subscription->id; 
  1516.  
  1517. $rule->membership_id = $this->id; 
  1518. $rules[ $rule_type ] = $rule; 
  1519.  
  1520. return apply_filters( 
  1521. 'ms_model_membership_get_rules_hierarchy',  
  1522. $rules,  
  1523. $this 
  1524. ); 
  1525.  
  1526. /** 
  1527. * Mark membership setup as completed. 
  1528. * 
  1529. * Only purpose of this flag is to display the correct update message to the 
  1530. * user: If setup_completed() returns true, then "Membership added" is 
  1531. * displayed, otherwise "Membership updated" 
  1532. * 
  1533. * @since 1.0.0 
  1534. * @internal 
  1535. * 
  1536. * @return bool $marked True in the first time setup is finished. 
  1537. */ 
  1538. public function setup_completed() { 
  1539. $marked = false; 
  1540.  
  1541. if ( ! $this->is_setup_completed ) { 
  1542. $this->is_setup_completed = true; 
  1543. $marked = true; 
  1544.  
  1545. return apply_filters( 
  1546. 'ms_model_membership_setup_completed',  
  1547. $marked,  
  1548. $this 
  1549. ); 
  1550.  
  1551. /** 
  1552. * Returns true if the membership the base membership. 
  1553. * 
  1554. * @since 1.1.0 
  1555. * @see description of MS_Model_Membership::get_base() 
  1556. * @api 
  1557. * 
  1558. * @return bool 
  1559. */ 
  1560. public function is_base() { 
  1561. $res = $this->type == self::TYPE_BASE; 
  1562.  
  1563. return apply_filters( 
  1564. 'ms_model_membership_is_base',  
  1565. $res,  
  1566. $this 
  1567. ); 
  1568.  
  1569. /** 
  1570. * Returns true if the membership the guest membership. 
  1571. * 
  1572. * @since 1.1.0 
  1573. * @see description of MS_Model_Membership::get_guest() 
  1574. * @api 
  1575. * 
  1576. * @return bool 
  1577. */ 
  1578. public function is_guest() { 
  1579. $res = $this->type == self::TYPE_GUEST; 
  1580.  
  1581. return apply_filters( 
  1582. 'ms_model_membership_is_guest',  
  1583. $res,  
  1584. $this 
  1585. ); 
  1586.  
  1587. /** 
  1588. * Returns true if the membership the user membership. 
  1589. * 
  1590. * @since 1.1.0 
  1591. * @see description of MS_Model_Membership::get_user() 
  1592. * @api 
  1593. * 
  1594. * @return bool 
  1595. */ 
  1596. public function is_user() { 
  1597. $res = $this->type == self::TYPE_USER; 
  1598.  
  1599. return apply_filters( 
  1600. 'ms_model_membership_is_user',  
  1601. $res,  
  1602. $this 
  1603. ); 
  1604.  
  1605. /** 
  1606. * Returns true if the membership a dripped membership. 
  1607. * 
  1608. * @since 1.1.0 
  1609. * @api 
  1610. * 
  1611. * @return bool 
  1612. */ 
  1613. public function is_dripped() { 
  1614. $res = $this->type == self::TYPE_DRIPPED; 
  1615.  
  1616. return apply_filters( 
  1617. 'ms_model_membership_is_dripped',  
  1618. $res,  
  1619. $this 
  1620. ); 
  1621.  
  1622. /** 
  1623. * Returns true if the membership the base or guest/user membership. 
  1624. * 
  1625. * @since 1.1.0 
  1626. * @api 
  1627. * 
  1628. * @return bool 
  1629. */ 
  1630. public function is_system() { 
  1631. $res = $this->is_base() || $this->is_guest() || $this->is_user(); 
  1632.  
  1633. return apply_filters( 
  1634. 'ms_model_membership_is_system',  
  1635. $res,  
  1636. $this 
  1637. ); 
  1638.  
  1639. /** 
  1640. * Can be used to validate if the current membership is actually loaded 
  1641. * from database. If this function returns false, then the specified 
  1642. * membership-ID does not exist in DB. 
  1643. * 
  1644. * @since 1.1.0 
  1645. * @api 
  1646. * 
  1647. * @return bool 
  1648. */ 
  1649. public function is_valid() { 
  1650. $res = ! empty( $this->id ); 
  1651.  
  1652. return apply_filters( 
  1653. 'ms_model_membership_is_valid',  
  1654. $res,  
  1655. $this 
  1656. ); 
  1657.  
  1658. /** 
  1659. * Verify access to current page. 
  1660. * 
  1661. * Verify membership rules hierarchy for content accessed directly. 
  1662. * If 'has access' is found, it does have access. 
  1663. * Only for active memberships. 
  1664. * 
  1665. * @since 1.0.0 
  1666. * @api 
  1667. * 
  1668. * @param int $post_id 
  1669. * @return bool|null True if has access to current page. Default is false. 
  1670. * Null means: Rule not relevant for current page. 
  1671. */ 
  1672. public function has_access_to_current_page( $post_id = null ) { 
  1673. $has_access = null; 
  1674. $this->_access_reason = array(); 
  1675. $this->_deny_rule = array(); 
  1676. $this->_allow_rule = array(); 
  1677.  
  1678. // Only verify access if membership is Active. 
  1679. if ( $this->active ) { 
  1680.  
  1681. // If 'has access' is found in the hierarchy, it does have access. 
  1682. $rules = $this->get_rules_hierarchy(); 
  1683. foreach ( $rules as $rule ) { 
  1684. $rule_access = $rule->has_access( $post_id ); 
  1685.  
  1686. if ( null === $rule_access ) { 
  1687. $this->_access_reason[] = sprintf( 
  1688. __( 'Ignored: Rule "%s"', MS_TEXT_DOMAIN ),  
  1689. $rule->rule_type 
  1690. ); 
  1691. continue; 
  1692.  
  1693. $this->_access_reason[] = sprintf( 
  1694. __( '%s: Rule "%s"', MS_TEXT_DOMAIN ),  
  1695. $rule_access ? __( 'Allow', MS_TEXT_DOMAIN ) : __( 'Deny', MS_TEXT_DOMAIN ),  
  1696. $rule->rule_type 
  1697. ); 
  1698.  
  1699. if ( ! $rule_access ) { 
  1700. $this->_deny_rule[] = $rule->rule_type; 
  1701. } else { 
  1702. $this->_allow_rule[] = $rule->rule_type; 
  1703.  
  1704. // URL groups have final decission. 
  1705. if ( MS_Rule_Url::RULE_ID === $rule->rule_type ) { 
  1706. $has_access = $rule_access; 
  1707. break; 
  1708.  
  1709. // Special pages have final decission after URL groups. 
  1710. if ( MS_Rule_Special::RULE_ID === $rule->rule_type ) { 
  1711. $has_access = $rule_access; 
  1712. $this->_access_reason[] = $rule->matched_type; 
  1713. break; 
  1714.  
  1715. $has_access = ( $has_access || $rule_access ); 
  1716.  
  1717. if ( true === $has_access ) { 
  1718. break; 
  1719.  
  1720. return apply_filters( 
  1721. 'ms_model_membership_has_access_to_current_page',  
  1722. $has_access,  
  1723. $post_id,  
  1724. $this 
  1725. ); 
  1726.  
  1727. /** 
  1728. * Verify access to post. 
  1729. * 
  1730. * Verify membership rules hierarchy for specific post or CPT. 
  1731. * 
  1732. * @since 1.0.0 
  1733. * @api 
  1734. * 
  1735. * @param int $post_id ID of specific post 
  1736. * @return boolean True if has access to current page. Default is false. 
  1737. */ 
  1738. public function has_access_to_post( $post_id ) { 
  1739. $has_access = null; 
  1740.  
  1741. if ( MS_Model_Member::is_normal_admin() ) { 
  1742. return true; 
  1743.  
  1744. if ( ! empty( $post_id ) ) { 
  1745. $post = get_post( $post_id ); 
  1746. if ( 'attachment' === $post->post_type ) { 
  1747. $post_id = get_post_field( 'post_parent', $post_id ); 
  1748.  
  1749. // If 'has access' is found in the hierarchy, it does have access. 
  1750. $rules = $this->get_rules_hierarchy(); 
  1751. foreach ( $rules as $rule ) { 
  1752. $rule->prepare_rule( $subscription ); 
  1753.  
  1754. // url groups have final decision 
  1755. if ( MS_Rule_Url::RULE_ID == $rule->rule_type 
  1756. && $rule->has_rule_for_post( $post_id ) 
  1757. ) { 
  1758. $has_access = $rule->has_access( $post_id ); 
  1759. break; 
  1760. } else { 
  1761. $rule_access = $rule->has_access( $post_id ); 
  1762. if ( null !== $rule_access ) { 
  1763. $has_access = $rule_access; 
  1764.  
  1765. if ( $has_access ) { 
  1766. break; 
  1767.  
  1768. if ( null === $has_access ) { 
  1769. // The post is not denied by any rule, so allow access. 
  1770. $has_access = true; 
  1771.  
  1772. return apply_filters( 
  1773. 'ms_model_membership_has_access_to_post',  
  1774. $has_access,  
  1775. $this 
  1776. ); 
  1777.  
  1778. /** 
  1779. * Set up the membership. This is always done, regardless if the user is 
  1780. * a normal user or an Admin user. 
  1781. * 
  1782. * @since 1.0.0 
  1783. * @internal 
  1784. * 
  1785. * @param MS_Model_Relationship $subscription The membership relationship. 
  1786. */ 
  1787. public function initialize( $subscription ) { 
  1788. do_action( 
  1789. 'ms_model_membership_initialize_before',  
  1790. $subscription,  
  1791. $this 
  1792. ); 
  1793.  
  1794. $this->subscription_id = $subscription->id; 
  1795. $rules = $this->get_rules_hierarchy(); 
  1796.  
  1797. // Apply protection settings of all rules (replace/hide contents, ...) 
  1798. foreach ( $rules as $rule ) { 
  1799. $rule->prepare_rule( $subscription ); 
  1800.  
  1801. do_action( 
  1802. 'ms_model_membership_initialize_after',  
  1803. $subscription,  
  1804. $this 
  1805. ); 
  1806.  
  1807. /** 
  1808. * Set initial protection for front-end. 
  1809. * This function is only executed when the current user is no Admin user. 
  1810. * 
  1811. * Hide restricted content for this membership. 
  1812. * 
  1813. * @since 1.0.0 
  1814. * @internal 
  1815. */ 
  1816. public function protect_content() { 
  1817. do_action( 
  1818. 'ms_model_membership_protect_content_before',  
  1819. $this 
  1820. ); 
  1821.  
  1822. $rules = $this->get_rules_hierarchy(); 
  1823.  
  1824. // Apply protection settings of all rules (replace/hide contents, ...) 
  1825. foreach ( $rules as $rule ) { 
  1826. $rule->protect_content(); 
  1827.  
  1828. do_action( 
  1829. 'ms_model_membership_protect_content_after',  
  1830. $this 
  1831. ); 
  1832.  
  1833. /** 
  1834. * Set initial protection for admin side. 
  1835. * 
  1836. * Hide restricted content for this membership. 
  1837. * 
  1838. * @since 1.1 
  1839. * @internal 
  1840. */ 
  1841. public function protect_admin_content() { 
  1842. do_action( 
  1843. 'ms_model_membership_protect_content_before',  
  1844. $this 
  1845. ); 
  1846.  
  1847. $rules = $this->get_rules_hierarchy(); 
  1848.  
  1849. foreach ( $rules as $rule ) { 
  1850. $rule->protect_admin_content(); 
  1851.  
  1852. do_action( 
  1853. 'ms_model_membership_protect_content_after',  
  1854. $this 
  1855. ); 
  1856.  
  1857. /** 
  1858. * Checks if the user is allowed to change the payment details for the 
  1859. * current membership. 
  1860. * 
  1861. * Payment details can only be changed when 
  1862. * (A) no payment details were saved yet - OR - 
  1863. * (B) no members signed up for the memberhips 
  1864. * 
  1865. * @since 1.0.4.5 
  1866. * @api 
  1867. * 
  1868. * @return bool 
  1869. */ 
  1870. public function can_change_payment() { 
  1871. // Allow if Membership is new/unsaved. 
  1872. if ( empty( $this->id ) ) { return true; } 
  1873.  
  1874. // Allow if no payment detail was entered yet (incomplete setup). 
  1875. if ( empty( $this->payment_type ) ) { return true; } 
  1876.  
  1877. // Allow if no members signed up yet. 
  1878. $members = MS_Model_Relationship::get_subscription_count( 
  1879. array( 'membership_id' => $this->id ) 
  1880. ); 
  1881. if ( empty( $members ) ) { return true; } 
  1882.  
  1883. // Otherwise payment details cannot be changed anymore. 
  1884. return false; 
  1885.  
  1886. /** 
  1887. * Returns property associated with the render. 
  1888. * 
  1889. * @since 1.0.0 
  1890. * @internal 
  1891. * 
  1892. * @param string $property The name of a property. 
  1893. * @return mixed Returns mixed value of a property or NULL if a property doesn't exist. 
  1894. */ 
  1895. public function __get( $property ) { 
  1896. $value = null; 
  1897.  
  1898. switch ( $property ) { 
  1899. case 'type': 
  1900. switch ( $this->type ) { 
  1901. case self::TYPE_BASE: 
  1902. case self::TYPE_GUEST: 
  1903. case self::TYPE_USER: 
  1904. case self::TYPE_DRIPPED: 
  1905. break; 
  1906.  
  1907. default: 
  1908. $this->type = self::TYPE_STANDARD; 
  1909. break; 
  1910.  
  1911. $value = $this->type; 
  1912. break; 
  1913.  
  1914. case 'payment_type': 
  1915. $types = self::get_payment_types(); 
  1916. if ( ! array_key_exists( $this->payment_type, $types ) ) { 
  1917. $this->payment_type = self::PAYMENT_TYPE_PERMANENT; 
  1918. $value = $this->payment_type; 
  1919. break; 
  1920.  
  1921. case 'trial_period_enabled': 
  1922. case 'active': 
  1923. case 'private': 
  1924. case 'is_free': 
  1925. $value = lib2()->is_true( $this->$property ); 
  1926. break; 
  1927.  
  1928. case 'type_description': 
  1929. $value = $this->get_type_description(); 
  1930. break; 
  1931.  
  1932. case 'period_unit': 
  1933. $value = MS_Helper_Period::get_period_value( $this->period, 'period_unit' ); 
  1934. break; 
  1935.  
  1936. case 'period_type': 
  1937. $value = MS_Helper_Period::get_period_value( $this->period, 'period_type' ); 
  1938. break; 
  1939.  
  1940. case 'pay_cycle_period_unit': 
  1941. $value = MS_Helper_Period::get_period_value( $this->pay_cycle_period, 'period_unit' ); 
  1942. break; 
  1943.  
  1944. case 'pay_cycle_period_type': 
  1945. $value = MS_Helper_Period::get_period_value( $this->pay_cycle_period, 'period_type' ); 
  1946. break; 
  1947.  
  1948. case 'trial_period_unit': 
  1949. $value = MS_Helper_Period::get_period_value( $this->trial_period, 'period_unit' ); 
  1950. break; 
  1951.  
  1952. case 'trial_period_type': 
  1953. $value = MS_Helper_Period::get_period_value( $this->trial_period, 'period_type' ); 
  1954. break; 
  1955.  
  1956. case 'price': 
  1957. if ( $this->is_free() ) { 
  1958. $value = 0; 
  1959. } else { 
  1960. $value = $this->price; 
  1961. break; 
  1962.  
  1963. case 'total_price': 
  1964. if ( $this->is_free() ) { 
  1965. $value = 0; 
  1966. } else { 
  1967. $value = $this->price; 
  1968.  
  1969. $value = apply_filters( 
  1970. 'ms_apply_taxes',  
  1971. $value,  
  1972. $this 
  1973. ); 
  1974. break; 
  1975.  
  1976. case 'pay_cycle_repetitions': 
  1977. $value = absint( $this->pay_cycle_repetitions ); 
  1978. break; 
  1979.  
  1980. case 'disabled_gateways': 
  1981. $value = lib2()->array->get( $this->disabled_gateways ); 
  1982. break; 
  1983.  
  1984. default: 
  1985. if ( property_exists( $this, $property ) ) { 
  1986. $value = $this->$property; 
  1987. break; 
  1988.  
  1989. return apply_filters( 
  1990. 'ms_model_membership__get',  
  1991. $value,  
  1992. $property,  
  1993. $this 
  1994. ); 
  1995.  
  1996. /** 
  1997. * Validate specific property before set. 
  1998. * 
  1999. * @since 1.0.0 
  2000. * @internal 
  2001. * 
  2002. * @param string $property The name of a property to associate. 
  2003. * @param mixed $value The value of a property. 
  2004. */ 
  2005. public function __set( $property, $value ) { 
  2006. if ( property_exists( $this, $property ) ) { 
  2007. switch ( $property ) { 
  2008. case 'name': 
  2009. case 'title': 
  2010. $this->$property = sanitize_text_field( $value ); 
  2011. break; 
  2012.  
  2013. case 'description': 
  2014. $this->$property = wp_kses( $value, 'post' ); 
  2015. break; 
  2016.  
  2017. case 'type': 
  2018. switch ( $value ) { 
  2019. case self::TYPE_BASE: 
  2020. case self::TYPE_GUEST: 
  2021. case self::TYPE_USER: 
  2022. // Only one instance of these types can exist. 
  2023. $existing = $this->_get_system_membership( $value, false ); 
  2024.  
  2025. if ( $existing && $existing->id != $this->id ) { 
  2026. $value = self::TYPE_STANDARD; 
  2027. } else { 
  2028. $this->active = true; 
  2029. $this->private = true; 
  2030. $this->is_free = true; 
  2031. $this->price = 0; 
  2032. $this->post_name = sanitize_html_class( $this->title ); 
  2033. $this->payment_type = self::PAYMENT_TYPE_PERMANENT; 
  2034. $this->post_author = get_current_user_id(); 
  2035. break; 
  2036.  
  2037. case self::TYPE_DRIPPED: 
  2038. break; 
  2039.  
  2040. default: 
  2041. $value = self::TYPE_STANDARD; 
  2042. break; 
  2043.  
  2044. $this->type = $value; 
  2045. break; 
  2046.  
  2047. case 'payment_type': 
  2048. $types = self::get_payment_types(); 
  2049. if ( array_key_exists( $value, $types ) ) { 
  2050. if ( $this->can_change_payment() ) { 
  2051. $this->payment_type = $value; 
  2052. } elseif ( $this->payment_type != $value ) { 
  2053. $error = 'Payment type cannot be changed after members have signed up.'; 
  2054. MS_Helper_Debug::log( $error ); 
  2055. throw new Exception( $error ); 
  2056. } else { 
  2057. throw new Exception( 'Invalid membership type.' ); 
  2058. break; 
  2059.  
  2060. case 'trial_period_enabled': 
  2061. case 'active': 
  2062. case 'private': 
  2063. case 'is_free': 
  2064. $this->$property = lib2()->is_true( $value ); 
  2065. break; 
  2066.  
  2067. case 'price': 
  2068. case 'trial_price': 
  2069. $this->$property = floatval( $value ); 
  2070. break; 
  2071.  
  2072. case 'pay_cycle_repetitions': 
  2073. $this->$property = absint( $value ); 
  2074. break; 
  2075.  
  2076. case 'period': 
  2077. case 'pay_cycle_period': 
  2078. case 'trial_period': 
  2079. $this->$property = $this->validate_period( $value ); 
  2080. break; 
  2081.  
  2082. case 'period_date_start': 
  2083. case 'period_date_end': 
  2084. $this->$property = $this->validate_date( $value ); 
  2085. break; 
  2086.  
  2087. case 'on_end_membership_id': 
  2088. if ( 0 == $value ) { 
  2089. $this->$property = 0; 
  2090. } else if ( 0 < MS_Factory::load( 'MS_Model_Membership', $value )->id ) { 
  2091. $this->$property = $value; 
  2092. break; 
  2093.  
  2094. default: 
  2095. $this->$property = $value; 
  2096. break; 
  2097. } else { 
  2098. switch ( $property ) { 
  2099. case 'period_unit': 
  2100. $this->period['period_unit'] = $this->validate_period_unit( $value ); 
  2101. break; 
  2102.  
  2103. case 'period_type': 
  2104. $this->period['period_type'] = $this->validate_period_type( $value ); 
  2105. break; 
  2106.  
  2107. case 'pay_cycle_period_unit': 
  2108. $this->pay_cycle_period['period_unit'] = $this->validate_period_unit( $value ); 
  2109. break; 
  2110.  
  2111. case 'pay_cycle_period_type': 
  2112. $this->pay_cycle_period['period_type'] = $this->validate_period_type( $value ); 
  2113. break; 
  2114.  
  2115. case 'trial_period_unit': 
  2116. $this->trial_period['period_unit'] = $this->validate_period_unit( $value ); 
  2117. break; 
  2118.  
  2119. case 'trial_period_type': 
  2120. $this->trial_period['period_type'] = $this->validate_period_type( $value ); 
  2121. break; 
  2122.  
  2123. do_action( 
  2124. 'ms_model_membership__set_after',  
  2125. $property,  
  2126. $value,  
  2127. $this 
  2128. ); 
  2129.  
.