MS_Rule

Membership Rule Parent class.

Defined (1)

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

/app/class-ms-rule.php  
  1. class MS_Rule extends MS_Model { 
  2.  
  3. /** 
  4. * Membership ID. 
  5. * @since 1.0.0 
  6. * @var int $membership_id 
  7. */ 
  8. protected $membership_id = 0; 
  9.  
  10. /** 
  11. * Does this rule belong to the base membership? 
  12. * If yes, then we need to invert all access: "has access" in base rule 
  13. * means that the item is protected. 
  14. * @since 1.0.0 
  15. * @var bool 
  16. */ 
  17. protected $is_base_rule = false; 
  18.  
  19. /** 
  20. * Rule type. 
  21. * @since 1.0.0 
  22. * @var string $rule_type 
  23. */ 
  24. protected $rule_type; 
  25.  
  26. /** 
  27. * Rule value data. 
  28. * Each child rule may use it's own data structure, but 
  29. * need to override core methods that use parent data structure. 
  30. * @since 1.0.0 
  31. * @var array $rule_value { 
  32. * @type int $item_id The protected item ID. 
  33. * @type int $value The rule value. 0: no access; 1: has access. 
  34. * } 
  35. */ 
  36. protected $rule_value = array(); 
  37.  
  38. /** 
  39. * Dripped Rule data. 
  40. * Each child rule may use it's own data structure, but 
  41. * need to override core methods that use parent data structure. 
  42. * @since 1.0.0 
  43. * @var array { 
  44. * A hash that defines the drip options of each protected item. 
  45. * @type int The protected item ID. 
  46. * @type array { 
  47. * @type string $type Type of dripped protection 
  48. * @type string $date Only used for type 'specific_date' 
  49. * @type string $delay_unit Only used for type 'from_registration' 
  50. * @type string $delay_type Only used for type 'from_registration' 
  51. * } 
  52. * } 
  53. */ 
  54. protected $dripped = array(); 
  55.  
  56. /** 
  57. * The subscription-id which this rule belongs to. 
  58. * Object hierarchy is: 
  59. * Subscription -> Membership -> Rule 
  60. * When we know the Subscription-ID we also know the Membership-ID 
  61. * @since 1.0.0 
  62. * @var int 
  63. */ 
  64. protected $_subscription_id = 0; 
  65.  
  66. /** 
  67. * Class constructor. 
  68. * @since 1.0.0 
  69. * @param int $membership_id The membership that owns this rule object. 
  70. */ 
  71. public function __construct( $membership_id ) { 
  72. parent::__construct(); 
  73.  
  74. $this->membership_id = apply_filters( 
  75. 'ms_rule_constructor_membership_id',  
  76. $membership_id,  
  77. $this 
  78. ); 
  79.  
  80. $membership = MS_Factory::load( 'MS_Model_membership', $membership_id ); 
  81. $this->is_base_rule = $membership->is_base(); 
  82.  
  83. $this->initialize(); 
  84.  
  85. /** 
  86. * Called by the constructor. 
  87. * This function offers a save way for each rule to initialize itself if 
  88. * required. 
  89. * This function is executed in Admin and Front-End, so it should only 
  90. * initialize stuff that is really needed! 
  91. * @since 1.0.0 
  92. */ 
  93. protected function initialize() { 
  94. // Can be overwritten by child classes. 
  95.  
  96. /** 
  97. * Returns the active flag for a specific rule. 
  98. * Default state is "active" (return value TRUE) 
  99. * Rules that need to be activated via an add-on should overwrite this 
  100. * method to return the current rule-state 
  101. * @since 1.0.0 
  102. * @return bool 
  103. */ 
  104. static public function is_active() { 
  105. return true; 
  106.  
  107. /** 
  108. * Validate dripped type. 
  109. * @since 1.0.0 
  110. * @param string $type The rule type to validate. 
  111. * @return bool True if is a valid dripped type. 
  112. */ 
  113. public static function is_valid_dripped_type( $type ) { 
  114. $valid = array_key_exists( $type, MS_Model_Rule::get_dripped_types() ); 
  115.  
  116. return apply_filters( 'ms_rule_is_valid_dripped_type', $valid ); 
  117.  
  118. /** 
  119. * Create a rule model. 
  120. * @since 1.0.0 
  121. * @param string $rule_type The rule type to create. 
  122. * @param int $membership_id The Membership model this rule belongs to. 
  123. * @param int $subscription_id The Subscription ID 
  124. * @return MS_Rule The rule model. 
  125. * @throws Exception when rule type is not valid. 
  126. */ 
  127. public static function rule_factory( $rule_type, $membership_id, $subscription_id ) { 
  128. $rule_types = MS_Model_Rule::get_rule_type_classes(); 
  129.  
  130. if ( isset( $rule_types[ $rule_type ] ) ) { 
  131. $class = $rule_types[ $rule_type ]; 
  132.  
  133. $rule = MS_Factory::load( $class, $membership_id, $subscription_id ); 
  134. $rule->_subscription_id = $subscription_id; 
  135. } else { 
  136. $rule = MS_Factory::create( 'MS_Rule', $membership_id ); 
  137.  
  138. return apply_filters( 
  139. 'ms_rule_rule_factory',  
  140. $rule,  
  141. $rule_type,  
  142. $membership_id 
  143. ); 
  144.  
  145. /** 
  146. * Determines the rule-type from the specified rule-key. 
  147. * Rule key: 
  148. * - For site-wide protection $rule_type is same as the $key. 
  149. * - For network-wide protection the $key has format "blog_id:rule_rype". 
  150. * @since 1.0.0 
  151. * @param string $key The rule-key which may or may not include a site_id. 
  152. * @return string The rule_type value extracted from the rule-key. 
  153. */ 
  154. public static function rule_type( $key ) { 
  155. $type = strtolower( $key ); 
  156.  
  157. if ( MS_Plugin::is_network_wide() ) { 
  158. $parts = explode( ':', $key ); 
  159. if ( 2 == count( $parts ) ) { 
  160. list( $site_id, $type ) = $parts; 
  161.  
  162. return $type; 
  163.  
  164. /** 
  165. * Builds the rule-key based on the provided rule_type. This function uses 
  166. * the current blog_id to build the rule-key for network-wide mode. 
  167. * @since 1.0.0 
  168. * @param string $rule_type 
  169. * @return string The rule-key (includes the site_id in network-wide mode). 
  170. */ 
  171. public static function rule_key( $rule_type ) { 
  172. $key = $rule_type; 
  173.  
  174. if ( MS_Plugin::is_network_wide() ) { 
  175. $network_global_rules = array( 
  176. MS_Rule_Url::RULE_ID,  
  177. ); 
  178.  
  179. // Some rules are network-global and get no site_id prefix. 
  180. if ( ! in_array( $rule_type, $network_global_rules ) ) { 
  181. $key = MS_Factory::current_blog_id() . ':' . $rule_type; 
  182.  
  183. return $key; 
  184.  
  185. /** 
  186. * Checks if the specified rule-key defines a rule that is relevant for the 
  187. * current site in the network. 
  188. * If network-wide protection is disabled this function always returns true. 
  189. * @since 1.0.0 
  190. * @param string $key 
  191. * @return bool 
  192. */ 
  193. public static function is_current_site( $key ) { 
  194. $res = true; 
  195. $site_id = 0; 
  196.  
  197. if ( MS_Plugin::is_network_wide() ) { 
  198. $parts = explode( ':', $key ); 
  199.  
  200. // Some rules have no site_id prefix (like URL rules) 
  201. if ( 2 == count( $parts ) ) { 
  202. list( $site_id, $type ) = $parts; 
  203. $site_id = intval( $site_id ); 
  204. $res = (MS_Factory::current_blog_id() == $site_id ); 
  205.  
  206. return $res; 
  207.  
  208. /** 
  209. * Tries to determine the queried post-type of the specified WP_Query object. 
  210. * If the query targetet multiple post_types at once, then an array of 
  211. * all queried post_types is returned. 
  212. * @since 1.0.0 
  213. * @param WP_Query $wp_query 
  214. * @return string|array The post-type(s) that was queried. 
  215. */ 
  216. public static function get_post_type( $wp_query ) { 
  217. $post_type = $wp_query->get( 'post_type' ); 
  218.  
  219. if ( empty( $post_type ) 
  220. && isset( $wp_query->queried_object ) 
  221. && isset( $wp_query->queried_object->post_type ) 
  222. ) { 
  223. // This might set $post_type to an array. 
  224. $post_type = $wp_query->queried_object->post_type; 
  225.  
  226. if ( empty( $post_type ) ) { 
  227. // The WP_Query does not explicitely specify a post_type. Guess. 
  228. $qv = $wp_query->query_vars; 
  229. $qq = $wp_query->query; 
  230.  
  231. if ( class_exists( 'WooCommerce' ) ) { 
  232. if ( ! empty( $qv['wc_query'] ) 
  233. || ! empty( $qq['product_cat'] ) 
  234. || ! empty( $qq['product_tag'] ) 
  235. || ! empty( $qq['product_shipping_class'] ) 
  236. ) { 
  237. // WooCommerce Product. 
  238. $post_type = 'product'; 
  239.  
  240. if ( $wp_query->is_home ) { 
  241. // Home page, showing latest posts. 
  242. $post_type = 'post'; 
  243.  
  244. if ( ! empty( $qv['withcomments'] ) ) { 
  245. // Seems to be posts, since it has comments. 
  246. $post_type = 'post'; 
  247.  
  248. $post_type = apply_filters( 
  249. 'ms_rule_post_type',  
  250. $post_type,  
  251. $wp_query 
  252. ); 
  253.  
  254. return $post_type; 
  255.  
  256. /** 
  257. * Set up the rule. 
  258. * This is called right before either the protect_content() or 
  259. * protect_admin_content() function is called. 
  260. * To be overridden by children classes. 
  261. * @since 1.0.0 
  262. * @param MS_Model_Relationship The membership relationship to protect content from. 
  263. */ 
  264. public function prepare_rule( $subscription = false ) { 
  265. if ( $subscription ) { 
  266. $this->_subscription_id = $subscription->id; 
  267. $this->membership_id = $subscription->membership_id; 
  268.  
  269. do_action( 
  270. 'ms_rule_initialize',  
  271. $subscription,  
  272. $this 
  273. ); 
  274.  
  275. /** 
  276. * Set initial protection for front-end. 
  277. * To be overridden by children classes. 
  278. * @since 1.0.0 
  279. */ 
  280. public function protect_content() { 
  281. do_action( 
  282. 'ms_rule_protect_content',  
  283. $this 
  284. ); 
  285.  
  286. /** 
  287. * Set initial protection for admin side. 
  288. * To be overridden by children classes. 
  289. * @since 1.0.0 
  290. */ 
  291. public function protect_admin_content() { 
  292. do_action( 
  293. 'ms_rule_protect_admin_content',  
  294. $this 
  295. ); 
  296.  
  297. /** 
  298. * Verify if this model has rules set. 
  299. * @since 1.0.0 
  300. * @return boolean True if it has rules, false otherwise. 
  301. */ 
  302. public function has_rules() { 
  303. $has_rules = false; 
  304. foreach ( $this->rule_value as $val ) { 
  305. if ( $val ) { 
  306. $has_rules = true; break; 
  307.  
  308. return apply_filters( 
  309. 'ms_rule_has_rules',  
  310. $has_rules,  
  311. $this 
  312. ); 
  313.  
  314. /** 
  315. * Count protection rules quantity. 
  316. * @since 1.0.0 
  317. * @param bool $has_access_only Optional. Count rules for has_access status only. 
  318. * @return int $count The rule count result. 
  319. */ 
  320. public function count_rules( $has_access_only = true ) { 
  321. $count = 0; 
  322.  
  323. if ( $has_access_only ) { 
  324. foreach ( $this->rule_value as $val ) { 
  325. if ( $val ) { $count++; } 
  326. } else { 
  327. $count = count( $this->rule_value ); 
  328.  
  329. return apply_filters( 
  330. 'ms_rule_count_rules',  
  331. $count,  
  332. $has_access_only,  
  333. $this 
  334. ); 
  335.  
  336. /** 
  337. * Get rule value for a specific content. 
  338. * @since 1.0.0 
  339. * @param string $id The content id to get rule value for. 
  340. * @return boolean The rule value for the requested content. Default $rule_value_default. 
  341. */ 
  342. public function get_rule_value( $id ) { 
  343. if ( is_scalar( $id ) && isset( $this->rule_value[ $id ] ) ) { 
  344. // A rule is defined. It is either TRUE or FALSE 
  345. $value = (bool) $this->rule_value[ $id ]; 
  346. } else { 
  347. // Default response is NULL: "Not-Denied" 
  348. $value = MS_Model_Rule::RULE_VALUE_UNDEFINED; 
  349.  
  350. return apply_filters( 
  351. 'ms_get_rule_value',  
  352. $value,  
  353. $id,  
  354. $this->rule_type,  
  355. $this 
  356. ); 
  357.  
  358. /** 
  359. * Serializes this rule in a single array. 
  360. * We don't use the PHP `serialize()` function to serialize the whole object 
  361. * because a lot of unrequired and duplicate data will be serialized 
  362. * Can be overwritten by child classes to implement a distinct 
  363. * serialization logic. 
  364. * @since 1.0.0 
  365. * @return array The serialized values of the Rule. 
  366. */ 
  367. public function serialize() { 
  368. $access = array(); 
  369. foreach ( $this->rule_value as $id => $state ) { 
  370. if ( $state ) { 
  371. if ( isset( $this->dripped[$id] ) ) { 
  372. $access[] = array( 
  373. 'id' => $id,  
  374. 'dripped' => array( 
  375. $this->dripped[$id]['type'],  
  376. $this->dripped[$id]['date'],  
  377. $this->dripped[$id]['delay_unit'],  
  378. $this->dripped[$id]['delay_type'],  
  379. ),  
  380. ); 
  381. } else { 
  382. $access[] = $id; 
  383.  
  384. return $access; 
  385.  
  386. /** 
  387. * Populates the rule_value array with the specified value list. 
  388. * This function is used when de-serializing a membership to re-create the 
  389. * rules associated with the membership. 
  390. * Can be overwritten by child classes to implement a distinct 
  391. * deserialization logic. 
  392. * @since 1.0.0 
  393. * @param array $values A list of allowed IDs. 
  394. */ 
  395. public function populate( $values ) { 
  396. foreach ( $values as $data ) { 
  397. if ( is_scalar( $data ) ) { 
  398. $this->give_access( $data ); 
  399. } else { 
  400.  
  401. if ( isset( $data['id'] ) 
  402. && ! empty( $data['dripped'] ) 
  403. && is_array( $data['dripped'] ) 
  404. && count( $data['dripped'] ) > 3 
  405. ) { 
  406. $this->give_access( $data['id'] ); 
  407. $this->dripped[ $data['id'] ] = array( 
  408. 'type' => $data['dripped'][0],  
  409. 'date' => $data['dripped'][1],  
  410. 'delay_unit' => $data['dripped'][2],  
  411. 'delay_type' => $data['dripped'][3],  
  412. ); 
  413.  
  414. /** 
  415. * Returns an array of membership that protect the specified rule item. 
  416. * @since 1.0.0 
  417. * @param string $id The content id to check. 
  418. * @return array List of memberships (ID => name) 
  419. */ 
  420. public function get_memberships( $id ) { 
  421. static $All_Memberships = null; 
  422. $res = array(); 
  423.  
  424. if ( null === $All_Memberships ) { 
  425. $All_Memberships = MS_Model_Membership::get_memberships(); 
  426.  
  427. foreach ( $All_Memberships as $membership ) { 
  428. $rule = $membership->get_rule( $this->rule_type ); 
  429. if ( isset( $rule->rule_value[ $id ] ) && $rule->rule_value[ $id ] ) { 
  430. $res[$membership->id] = $membership->name; 
  431.  
  432. return $res; 
  433.  
  434. /** 
  435. * Defines, which memberships protect the specified rule item. 
  436. * Note: This method should only be called for the BASE membership! 
  437. * @since 1.0.0 
  438. * @param string $id The content id to check. 
  439. * @return array List of memberships (ID => name) 
  440. */ 
  441. public function set_memberships( $id, $memberships ) { 
  442. static $All_Memberships = null; 
  443.  
  444. if ( ! $this->is_base_rule ) { 
  445. throw new Exception( 'set_memberships() must be called on the base-rule!', 1 ); 
  446. return; 
  447.  
  448. if ( null === $All_Memberships ) { 
  449. $All_Memberships = MS_Model_Membership::get_memberships(); 
  450.  
  451. foreach ( $All_Memberships as $membership ) { 
  452. $rule = $membership->get_rule( $this->rule_type ); 
  453. if ( in_array( $membership->id, $memberships ) ) { 
  454. $rule->give_access( $id ); 
  455. } else { 
  456. $rule->remove_access( $id ); 
  457. $membership->set_rule( $this->rule_type, $rule ); 
  458. $membership->save(); 
  459.  
  460. /** 
  461. * Verify access to the current content. 
  462. * @since 1.0.0 
  463. * @param string $id The content id to verify access. 
  464. * @param bool $admin_has_access Default true: Admin will always have access,  
  465. * no matter how protection is set up. False will ignore the 
  466. * admin status and check protection rules normaly. 
  467. * @return boolean TRUE if has access, FALSE otherwise. 
  468. */ 
  469. public function has_access( $id, $admin_has_access = true ) { 
  470. if ( $admin_has_access && MS_Model_Member::is_normal_admin() ) { 
  471. return true; 
  472.  
  473. /** 
  474. * $access will be one of these: 
  475. * - TRUE .. Access explicitly granted 
  476. * - FALSE .. Access explicitly denied 
  477. * - NULL .. Access implicitly allowed (i.e. "not-denied") 
  478. */ 
  479. $access = $this->get_rule_value( $id ); 
  480.  
  481. if ( $this->is_base_rule ) { 
  482. /** 
  483. * Base rule .. 
  484. * - The meaning of TRUE/FALSE is inverted 
  485. * - NULL is always "allowed" 
  486. */ 
  487. $access = ! $access; 
  488. } else { 
  489. // Apply dripped-content rules if neccessary. 
  490. if ( $access && $this->has_dripped_rules( $id ) ) { 
  491. if ( ! empty( $this->_subscription_id ) ) { 
  492. $subscription = MS_Factory::load( 
  493. 'MS_Model_Relationship',  
  494. $this->_subscription_id 
  495. ); 
  496. $start_date = $subscription->start_date; 
  497. } else { 
  498. $start_date = null; 
  499.  
  500. $avail_date = $this->get_dripped_avail_date( $id, $start_date ); 
  501. $now = MS_Helper_Period::current_date(); 
  502.  
  503. $access = strtotime( $now ) >= strtotime( $avail_date ); 
  504.  
  505. if ( MS_Model_Rule::RULE_VALUE_UNDEFINED === $access ) { 
  506. // NULL .. "not-denied" is translated to "allowed" 
  507. $access = true; 
  508.  
  509. // At this point $access can either be TRUE or FALSE, not NULL! 
  510. $access = (bool) $access; 
  511.  
  512. return apply_filters( 
  513. 'ms_rule_has_access',  
  514. $access,  
  515. $id,  
  516. $this->rule_type,  
  517. $this 
  518. ); 
  519.  
  520. /** 
  521. * Verify if has dripped rules. 
  522. * @since 1.0.0 
  523. * @param string $id The content id to verify. 
  524. * @return boolean True if has dripped rules. 
  525. */ 
  526. public function has_dripped_rules( $id = null ) { 
  527. if ( ! is_array( $this->dripped ) ) { $this->dripped = array(); } 
  528.  
  529. if ( empty( $id ) ) { 
  530. $has_dripped = ! empty( $this->dripped ); 
  531. } else { 
  532. $has_dripped = ! empty( $this->dripped[$id] ); 
  533.  
  534. return apply_filters( 
  535. 'ms_rule_has_dripped_rules',  
  536. $has_dripped,  
  537. $id,  
  538. $this 
  539. ); 
  540.  
  541. /** 
  542. * Set dripped value. 
  543. * Handler for setting dripped data content. 
  544. * @since 1.0.0 
  545. * @param int $item_id Drip-Settings of this item are changed. 
  546. * @param string $drip_type Any of MS_Model_Rule::DRIPPED_TYPE_* values. 
  547. * @param string $date Only used for type 'specific_date' 
  548. * @param string $delay_unit Only used for type 'from_registration' 
  549. * @param string $delay_type Only used for type 'from_registration' 
  550. */ 
  551. public function set_dripped_value( $item_id, $drip_type, $date = '', $delay_unit = '', $delay_type = '' ) { 
  552. $this->give_access( $item_id ); 
  553. $this->dripped[ $item_id ] = apply_filters( 
  554. 'ms_rule_set_dripped_value',  
  555. array( 
  556. 'type' => $drip_type,  
  557. 'date' => $date,  
  558. 'delay_unit' => $delay_unit,  
  559. 'delay_type' => $delay_type,  
  560. ),  
  561. $this 
  562. ); 
  563.  
  564. do_action( 
  565. 'ms_rule_set_dripped_value_after',  
  566. $drip_type,  
  567. $item_id,  
  568. $this 
  569. ); 
  570.  
  571. /** 
  572. * Returns the effective date on which the specified item becomes available. 
  573. * @since 1.0.0 
  574. * @param string $item_id The content id to verify dripped access. 
  575. * @param string $start_date The start date of the member membership. 
  576. * @return string Date on which the item is revealed (e.g. '2015-02-16') 
  577. */ 
  578. public function get_dripped_avail_date( $item_id, $start_date = null ) { 
  579. $avail_date = MS_Helper_Period::current_date(); 
  580. $drip_data = false; 
  581.  
  582. if ( ! is_array( $this->dripped ) ) { $this->dripped = array(); } 
  583. if ( isset( $this->dripped[ $item_id ] ) ) { 
  584. $drip_data = $this->dripped[ $item_id ]; 
  585.  
  586. if ( is_array( $drip_data ) ) { 
  587. lib3()->array->equip( $drip_data, 'type', 'date', 'delay_unit', 'delay_type' ); 
  588.  
  589. switch ( $drip_data['type'] ) { 
  590. case MS_Model_Rule::DRIPPED_TYPE_SPEC_DATE: 
  591. $avail_date = $drip_data['date']; 
  592. break; 
  593.  
  594. case MS_Model_Rule::DRIPPED_TYPE_FROM_REGISTRATION: 
  595. if ( empty( $start_date ) ) { 
  596. $start_date = MS_Helper_Period::current_date( null, false ); 
  597.  
  598. $period_unit = $drip_data['delay_unit']; 
  599. $period_type = $drip_data['delay_type']; 
  600. $avail_date = MS_Helper_Period::add_interval( 
  601. $period_unit,  
  602. $period_type,  
  603. $start_date 
  604. ); 
  605. break; 
  606.  
  607. case MS_Model_Rule::DRIPPED_TYPE_INSTANTLY: 
  608. default: 
  609. $avail_date = MS_Helper_Period::current_date(); 
  610. break; 
  611.  
  612. return apply_filters( 
  613. 'ms_rule_get_dripped_avail_date',  
  614. $avail_date,  
  615. $item_id,  
  616. $start_date,  
  617. $this 
  618. ); 
  619.  
  620. /** 
  621. * Returns a string that describes the dripped rule. 
  622. * @since 1.0.0 
  623. * @param string $item_id The content id to verify dripped access. 
  624. * @return string Text like "Instantly" or "After 7 days" 
  625. */ 
  626. public function get_dripped_description( $item_id ) { 
  627.  
  628. $desc = ''; 
  629. $drip_data = false; 
  630.  
  631. if ( ! is_array( $this->dripped ) ) { $this->dripped = array(); } 
  632. if ( isset( $this->dripped[ $item_id ] ) ) { 
  633. $drip_data = $this->dripped[ $item_id ]; 
  634.  
  635. if ( is_array( $drip_data ) ) { 
  636. lib3()->array->equip( $drip_data, 'type', 'date', 'delay_unit', 'delay_type' ); 
  637.  
  638. switch ( $drip_data['type'] ) { 
  639. case MS_Model_Rule::DRIPPED_TYPE_SPEC_DATE: 
  640. $desc = sprintf( 
  641. __( 'On <b>%1$s</b>', 'membership2' ),  
  642. MS_Helper_Period::format_date( $drip_data['date'] ) 
  643. ); 
  644. break; 
  645.  
  646. case MS_Model_Rule::DRIPPED_TYPE_FROM_REGISTRATION: 
  647. $periods = MS_Helper_Period::get_period_types(); 
  648. $period_key = $drip_data['delay_type']; 
  649.  
  650. if ( 0 == $drip_data['delay_unit'] ) { 
  651. $desc = __( '<b>Instantly</b>', 'membership2' ); 
  652. } elseif ( 1 == $drip_data['delay_unit'] ) { 
  653. $desc = sprintf( 
  654. __( 'After <b>%1$s %2$s</b>', 'membership2' ),  
  655. $periods['1' . $period_key],  
  656. '' 
  657. ); 
  658. } else { 
  659. $desc = sprintf( 
  660. __( 'After <b>%1$s %2$s</b>', 'membership2' ),  
  661. $drip_data['delay_unit'],  
  662. $periods[$period_key] 
  663. ); 
  664. break; 
  665.  
  666. case MS_Model_Rule::DRIPPED_TYPE_INSTANTLY: 
  667. default: 
  668. $desc = __( '<b>Instantly</b>', 'membership2' ); 
  669. break; 
  670.  
  671. return apply_filters( 
  672. 'ms_rule_get_dripped_description',  
  673. $desc,  
  674. $item_id,  
  675. $this 
  676. ); 
  677.  
  678. /** 
  679. * Count item Membership2 summary. 
  680. * @since 1.0.0 
  681. * @param $args The query post args 
  682. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  683. * @return array { 
  684. * @type int $total The total content count. 
  685. * @type int $accessible The has access content count. 
  686. * @type int $restricted The Membership2 count. 
  687. * } 
  688. */ 
  689. public function count_item_access( $args = null ) { 
  690. if ( $this->is_base_rule ) { 
  691. $args['default'] = 1; 
  692.  
  693. $args['posts_per_page'] = 0; 
  694. $args['offset'] = false; 
  695. $total = $this->get_content_count( $args ); 
  696. $contents = $this->get_contents( $args ); 
  697. $count_accessible = 0; 
  698. $count_restricted = 0; 
  699.  
  700. if ( ! is_array( $this->rule_value ) ) { 
  701. $this->rule_value = array(); 
  702.  
  703. foreach ( $contents as $id => $content ) { 
  704. if ( $content->access ) { 
  705. $count_accessible++; 
  706. } else { 
  707. $count_restricted++; 
  708.  
  709. if ( $this->is_base_rule ) { 
  710. $count_restricted = $total - $count_accessible; 
  711. } else { 
  712. $count_accessible = $total - $count_restricted; 
  713.  
  714. $count = array( 
  715. 'total' => $total,  
  716. 'accessible' => $count_accessible,  
  717. 'restricted' => $count_restricted,  
  718. ); 
  719.  
  720. return apply_filters( 'ms_rule_count_item_access', $count ); 
  721.  
  722. /** 
  723. * Get content to protect. 
  724. * To be overridden in children classes. 
  725. * @since 1.0.0 
  726. * @param $args The query post args 
  727. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  728. * @return array The contents array. 
  729. */ 
  730. public function get_contents( $args = null ) { 
  731. return array(); 
  732.  
  733. /** 
  734. * Get content count. 
  735. * To be overridden in children classes. 
  736. * @since 1.0.0 
  737. * @param $args The query post args 
  738. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  739. * @return int The content count. 
  740. */ 
  741. public function get_content_count( $args = null ) { 
  742. return 0; 
  743.  
  744. /** 
  745. * Reset the rule value data. 
  746. * @since 1.0.0 
  747. * @param $args The query post args 
  748. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  749. * @return int The content count. 
  750. */ 
  751. public function reset_rule_values() { 
  752. $this->rule_value = apply_filters( 
  753. 'ms_rule_reset_values',  
  754. array(),  
  755. $this 
  756. ); 
  757.  
  758. /** 
  759. * Denies access to all items that are defined in the base-rule but 
  760. * not in the current rule. 
  761. * @since 1.0.0 
  762. * @param MS_Rule $base_rule The source rule model to merge rules to. 
  763. */ 
  764. public function protect_undefined_items( $base_rule ) { 
  765. if ( $base_rule->rule_type != $this->rule_type ) { return; } 
  766.  
  767. if ( ! is_array( $this->rule_value ) ) { 
  768. $this->rule_value = array(); 
  769. if ( ! is_array( $base_rule->rule_value ) ) { 
  770. $base_rule->rule_value = array(); 
  771.  
  772. $base_rule_value = $base_rule->rule_value; 
  773.  
  774. /** 
  775. * Remove protection of items that are protected by the current rule 
  776. * but NOT protected by the base-rule. 
  777. * I.e. remove invalid protection information. 
  778. */ 
  779. foreach ( $this->rule_value as $id => $access ) { 
  780. if ( ! isset( $base_rule->rule_value[$id] ) ) { 
  781. unset( $this->rule_value[ $id ] ); 
  782.  
  783. /** 
  784. * Get the items that are protected by base but not allowed by 
  785. * the membership. Deny access to these items. 
  786. */ 
  787. $base_rule_value = array_diff_key( 
  788. $base_rule_value,  
  789. $this->rule_value 
  790. ); 
  791.  
  792. foreach ( $base_rule_value as $id => $access ) { 
  793. if ( $access ) { 
  794. $this->rule_value[ $id ] = MS_Model_Rule::RULE_VALUE_NO_ACCESS; 
  795.  
  796. do_action( 'ms_merge_rule_values', $this, $base_rule ); 
  797.  
  798. /** 
  799. * Set access status to content. 
  800. * @since 1.0.0 
  801. * @param string $id The content id to set access to. 
  802. * @param bool $access The access status to set. 
  803. */ 
  804. public function set_access( $id, $access ) { 
  805. if ( $access ) { 
  806. $rule_usage = 1; 
  807. $this->rule_value[ $id ] = MS_Model_Rule::RULE_VALUE_HAS_ACCESS; 
  808. } else { 
  809. $rule_usage = 0; 
  810. unset( $this->rule_value[ $id ] ); 
  811. unset( $this->dripped[ $id ] ); 
  812.  
  813. // Update the base rule. 
  814. if ( ! $this->is_base_rule ) { 
  815. $base = MS_Model_Membership::get_base(); 
  816. $base_rule = $base->get_rule( $this->rule_type ); 
  817.  
  818. if ( ! $rule_usage ) { 
  819. $all_memberships = MS_Model_Membership::get_memberships(); 
  820. foreach ( $all_memberships as $membership ) { 
  821. if ( $membership->is_base ) { continue; } 
  822. $mem_rule = $membership->get_rule( $this->rule_type ); 
  823. if ( ! $mem_rule->get_rule_value( $id ) ) { continue; } 
  824.  
  825. $rule_usage += 1; 
  826.  
  827. if ( ! $rule_usage ) { 
  828. $base_rule->remove_access( $id ); 
  829. $base->set_rule( $this->rule_type, $base_rule ); 
  830. $base->save(); 
  831. } elseif ( ! $base_rule->get_rule_value( $id ) ) { 
  832. // Only `give_access()` when the item is not protected yet. 
  833. $base_rule->give_access( $id ); 
  834. $base->set_rule( $this->rule_type, $base_rule ); 
  835. $base->save(); 
  836.  
  837. do_action( 'ms_rule_set_access', $id, $access, $this ); 
  838.  
  839. /** 
  840. * Give access to content. 
  841. * @since 1.0.0 
  842. * @param string $id The content id to give access. 
  843. */ 
  844. public function give_access( $id ) { 
  845. $this->set_access( 
  846. $id,  
  847. MS_Model_Rule::RULE_VALUE_HAS_ACCESS 
  848. ); 
  849.  
  850. do_action( 'ms_rule_give_access', $id, $this ); 
  851.  
  852. /** 
  853. * Remove access to content. 
  854. * @since 1.0.0 
  855. * @param string $id The content id to remove access. 
  856. */ 
  857. public function remove_access( $id ) { 
  858. $this->set_access( 
  859. $id,  
  860. MS_Model_Rule::RULE_VALUE_NO_ACCESS 
  861. ); 
  862.  
  863. do_action( 'ms_rule_remove_access', $id, $this ); 
  864.  
  865. /** 
  866. * Toggle access to content. 
  867. * @since 1.0.0 
  868. * @param string $id The content id to toggle access. 
  869. */ 
  870. public function toggle_access( $id ) { 
  871. $current_value = $this->get_rule_value( $id ); 
  872. $has_access = MS_Model_Rule::RULE_VALUE_HAS_ACCESS !== $current_value; 
  873.  
  874. $this->set_access( 
  875. $id,  
  876. $has_access 
  877. ); 
  878.  
  879. do_action( 'ms_rule_toggle_access', $id, $this ); 
  880.  
  881. /** 
  882. * Get WP_Query object arguments. 
  883. * Return default search arguments. 
  884. * @since 1.0.0 
  885. * @param $args The query post args 
  886. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  887. * @return array $args The parsed args. 
  888. */ 
  889. public function prepare_query_args( $args = null, $args_type = 'wp_query' ) { 
  890. $filter = $this->get_exclude_include( $args ); 
  891.  
  892. /** 
  893. * By default the $args collection is supposed to be passed to a 
  894. * WP_Query constructor. However, we can also prepare the filter 
  895. * arguments to be used for another type of query, like get_pages() 
  896. */ 
  897. $args_type = strtolower( $args_type ); 
  898.  
  899. switch ( $args_type ) { 
  900. case 'get_pages': 
  901. $defaults = array( 
  902. 'number' => false,  
  903. 'hierarchical' => 1,  
  904. 'sort_column' => 'post_title',  
  905. 'sort_order' => 'ASC',  
  906. 'post_type' => 'page',  
  907. ); 
  908. $args['exclude'] = $filter->exclude; 
  909. $args['include'] = $filter->include; 
  910. break; 
  911.  
  912. case 'get_categories': 
  913. $defaults = array( 
  914. 'get' => 'all', // interpreted by get_terms() 
  915. ); 
  916.  
  917. if ( isset( $args['s'] ) ) { 
  918. $args['search'] = $args['s']; 
  919.  
  920. $args['exclude'] = $filter->exclude; 
  921. $args['include'] = $filter->include; 
  922. break; 
  923.  
  924. case 'get_posts': 
  925. case 'wp_query': 
  926. default: 
  927. $defaults = array( 
  928. 'posts_per_page' => -1,  
  929. 'ignore_sticky_posts' => true,  
  930. 'offset' => 0,  
  931. 'orderby' => 'ID',  
  932. 'order' => 'DESC',  
  933. 'post_status' => 'publish',  
  934. ); 
  935. $args['post__not_in'] = $filter->exclude; 
  936. $args['post__in'] = $filter->include; 
  937. break; 
  938.  
  939. $args = wp_parse_args( $args, $defaults ); 
  940. $args = $this->validate_query_args( $args, $args_type ); 
  941.  
  942. return apply_filters( 
  943. 'ms_rule_' . $this->id . '_get_query_args',  
  944. $args,  
  945. $args_type,  
  946. $this 
  947. ); 
  948.  
  949. /** 
  950. * Returns a list of post_ids to exclude or include to fullfil the specified 
  951. * Membership/Status filter. 
  952. * @since 1.0.0 
  953. * @param array $args 
  954. * @return array { 
  955. * List of post_ids to exclude or include 
  956. * array $include 
  957. * array $exclude 
  958. * } 
  959. */ 
  960. public function get_exclude_include( $args ) { 
  961. // Filter for Membership and Protection status via 'exclude'/'include' 
  962. $include = array(); 
  963. $exclude = array(); 
  964. $base_rule = $this; 
  965. $child_rule = $this; 
  966.  
  967. if ( ! $this->is_base_rule ) { 
  968. $base_rule = MS_Model_Membership::get_base()->get_rule( $this->rule_type ); 
  969. if ( ! empty( $args['membership_id'] ) ) { 
  970. $child_membership = MS_Factory::load( 
  971. 'MS_Model_Membership',  
  972. $args['membership_id'] 
  973. ); 
  974. $child_rule = $child_membership->get_rule( $this->rule_type ); 
  975.  
  976. $base_items = array_keys( $base_rule->rule_value, true ); 
  977. $child_items = array_keys( $child_rule->rule_value, true ); 
  978.  
  979. $status = ! empty( $args['rule_status'] ) ? $args['rule_status'] : null; 
  980.  
  981. switch ( $status ) { 
  982. case MS_Model_Rule::FILTER_PROTECTED; 
  983. if ( ! empty( $args['membership_id'] ) ) { 
  984. $include = array_intersect( $child_items, $base_items ); 
  985. } else { 
  986. $include = $child_items; 
  987. if ( empty( $include ) ) { 
  988. $include = array( -1 ); 
  989. break; 
  990.  
  991. case MS_Model_Rule::FILTER_NOT_PROTECTED; 
  992. if ( ! empty( $args['membership_id'] ) ) { 
  993. $include = array_diff( $base_items, $child_items ); 
  994. if ( empty( $include ) && empty( $exclude ) ) { 
  995. $include = array( -1 ); 
  996. } else { 
  997. $exclude = $child_items; 
  998. if ( empty( $include ) && empty( $exclude ) ) { 
  999. $exclude = array( -1 ); 
  1000. break; 
  1001.  
  1002. default: 
  1003. // If not visitor membership, just show all Membership2 
  1004. if ( ! $child_rule->is_base_rule ) { 
  1005. $include = $base_items; 
  1006. break; 
  1007.  
  1008. /** 
  1009. * Allow rules/Add-ons to modify the exclude/include list. 
  1010. * @since 1.0.0 
  1011. */ 
  1012. $exclude = array_unique( 
  1013. apply_filters( 
  1014. 'ms_rule_exclude_items-' . $this->rule_type,  
  1015. $exclude,  
  1016. $args 
  1017. ); 
  1018. $include = array_unique( 
  1019. apply_filters( 
  1020. 'ms_rule_include_items-' . $this->rule_type,  
  1021. $include,  
  1022. $args 
  1023. ); 
  1024.  
  1025. $res = (object) array( 
  1026. 'include' => null,  
  1027. 'exclude' => null,  
  1028. ); 
  1029.  
  1030. if ( ! empty( $include ) ) { 
  1031. $res->include = $include; 
  1032. } elseif ( ! empty( $exclude ) ) { 
  1033. $res->exclude = $exclude; 
  1034. } elseif ( ! empty( $args['membership_id'] ) ) { 
  1035. $res->include = array( -1 ); 
  1036.  
  1037. return $res; 
  1038.  
  1039. /** 
  1040. * Validate wp query args. 
  1041. * Avoid post__in and post__not_in conflicts. 
  1042. * @since 1.0.0 
  1043. * @param mixed $args The query post args 
  1044. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  1045. * @return mixed $args The validated args. 
  1046. */ 
  1047. public function validate_query_args( $args, $args_type = 'wp_query' ) { 
  1048. switch ( $args_type ) { 
  1049. case 'get_pages': 
  1050. case 'get_categories': 
  1051. $arg_excl = 'exclude'; 
  1052. $arg_incl = 'include'; 
  1053. break; 
  1054.  
  1055. case 'get_posts': 
  1056. case 'wp_query': 
  1057. default: 
  1058. $arg_excl = 'post__not_in'; 
  1059. $arg_incl = 'post__in'; 
  1060. break; 
  1061.  
  1062. // Remove undefined exclude/include arguments. 
  1063. if ( isset( $args[$arg_incl] ) && null === $args[$arg_incl] ) { 
  1064. unset( $args[$arg_incl] ); 
  1065. if ( isset( $args[$arg_excl] ) && null === $args[$arg_excl] ) { 
  1066. unset( $args[$arg_excl] ); 
  1067.  
  1068. // Cannot use exclude and include at the same time. 
  1069. if ( ! empty( $args[$arg_incl] ) && ! empty( $args[$arg_excl] ) ) { 
  1070. $include = $args[$arg_incl]; 
  1071. $exclude = $args[$arg_excl]; 
  1072.  
  1073. foreach ( $exclude as $id ) { 
  1074. $key = array_search( $id, $include ); 
  1075. unset( $include[ $key ] ); 
  1076. unset( $args[$arg_excl] ); 
  1077.  
  1078. if ( isset( $args[$arg_incl] ) && 0 == count( $args[$arg_incl] ) ) { 
  1079. $args[$arg_incl] = array( -1 ); 
  1080.  
  1081. switch ( $args_type ) { 
  1082. case 'get_pages': 
  1083. // No validation required. 
  1084. break; 
  1085.  
  1086. case 'get_categories': 
  1087. if ( ! empty( $args['number'] ) ) { 
  1088. /** 
  1089. * 'hierarchical' and 'child_of' must be empty in order for 
  1090. * offset/number to work correctly. 
  1091. */ 
  1092. $args['hierarchical'] = false; 
  1093. $args['child_of'] = false; 
  1094. break; 
  1095.  
  1096. case 'wp_query': 
  1097. case 'get_posts': 
  1098. default: 
  1099. if ( ! empty( $args['show_all'] ) 
  1100. || ! empty( $args['category__in'] ) 
  1101. ) { 
  1102. unset( $args['post__in'] ); 
  1103. unset( $args['post__not_in'] ); 
  1104. unset( $args['show_all'] ); 
  1105. break; 
  1106.  
  1107. return apply_filters( 
  1108. 'ms_rule_' . $this->id . '_validate_query_args',  
  1109. $args,  
  1110. $args_type,  
  1111. $this 
  1112. ); 
  1113.  
  1114. /** 
  1115. * Filter content. 
  1116. * @since 1.0.0 
  1117. * @param string $status The status to filter. 
  1118. * @param mixed[] $contents The content object array. 
  1119. * @return mixed[] The filtered contents. 
  1120. */ 
  1121. public function filter_content( $status, $contents ) { 
  1122. foreach ( $contents as $key => $content ) { 
  1123. if ( ! empty( $content->ignore ) ) { 
  1124. continue; 
  1125.  
  1126. switch ( $status ) { 
  1127. case MS_Model_Rule::FILTER_PROTECTED: 
  1128. if ( ! $content->access ) { 
  1129. unset( $contents[ $key ] ); 
  1130. break; 
  1131.  
  1132. case MS_Model_Rule::FILTER_NOT_PROTECTED: 
  1133. if ( $content->access ) { 
  1134. unset( $contents[ $key ] ); 
  1135. break; 
  1136.  
  1137. case MS_Model_Rule::FILTER_DRIPPED: 
  1138. if ( empty( $content->delayed_period ) ) { 
  1139. unset( $contents[ $key ] ); 
  1140. break; 
  1141.  
  1142. return apply_filters( 
  1143. 'ms_rule_filter_content',  
  1144. $contents,  
  1145. $status,  
  1146. $this 
  1147. ); 
  1148.  
  1149. /** 
  1150. * Returns Membership object. 
  1151. * @since 1.0.0 
  1152. * @return MS_Model_Membership The membership object. 
  1153. */ 
  1154. public function get_membership() { 
  1155. $membership = MS_Factory::load( 
  1156. 'MS_Model_Membership',  
  1157. $this->membership_id 
  1158. ); 
  1159.  
  1160. return apply_filters( 'ms_rule_get_membership', $membership ); 
  1161.  
  1162. /** 
  1163. * Returns property associated with the render. 
  1164. * @since 1.0.0 
  1165. * @param string $property The name of a property. 
  1166. * @return mixed Returns mixed value of a property or NULL if a property doesn't exist. 
  1167. */ 
  1168. public function __get( $property ) { 
  1169. $value = null; 
  1170. switch ( $property ) { 
  1171. case 'rule_value': 
  1172. case 'dripped': 
  1173. $this->$property = lib3()->array->get( $this->$property ); 
  1174. $value = $this->$property; 
  1175. break; 
  1176.  
  1177. default: 
  1178. if ( property_exists( $this, $property ) ) { 
  1179. $value = $this->$property; 
  1180. break; 
  1181.  
  1182. return apply_filters( 
  1183. 'ms_rule__get',  
  1184. $value,  
  1185. $property,  
  1186. $this 
  1187. ); 
  1188.  
  1189. /** 
  1190. * Validate specific property before set. 
  1191. * @since 1.0.0 
  1192. * @param string $property The name of a property to associate. 
  1193. * @param mixed $value The value of a property. 
  1194. */ 
  1195. public function __set( $property, $value ) { 
  1196. if ( property_exists( $this, $property ) ) { 
  1197. switch ( $property ) { 
  1198. case 'rule_type': 
  1199. if ( in_array( $value, MS_Model_Rule::get_rule_types() ) ) { 
  1200. $this->$property = $value; 
  1201. break; 
  1202.  
  1203. case 'dripped': 
  1204. if ( is_array( $value ) ) { 
  1205. $this->$property = $value; 
  1206. break; 
  1207.  
  1208. default: 
  1209. $this->$property = $value; 
  1210. break; 
  1211.  
  1212. do_action( 
  1213. 'ms_rule__set_after',  
  1214. $property,  
  1215. $value,  
  1216. $this 
  1217. );