/app/class-ms-rule.php

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