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

  1. <?php 
  2. /** 
  3. * Main class for protection. 
  4. * 
  5. * @since 1.0.0 
  6. * @package Membership2 
  7. * @subpackage Model 
  8. */ 
  9. class MS_Model_Plugin extends MS_Model { 
  10.  
  11. /** 
  12. * Current Member object. 
  13. * 
  14. * @since 1.0.0 
  15. * 
  16. * @var string $member 
  17. */ 
  18. private $member; 
  19.  
  20. /** 
  21. * Full admin menu, used by the Adminside rule. 
  22. * This property cannot be initialized in the rule-model itself because the 
  23. * rule is loaded long after the menu is rendered and therefore does not 
  24. * have access to the full list of menu items. 
  25. * 
  26. * @since 1.0.0 
  27. * 
  28. * @var array 
  29. */ 
  30. protected $admin_menu = array(); 
  31.  
  32. /** 
  33. * Prepare object. 
  34. * 
  35. * @since 1.0.0 
  36. */ 
  37. public function __construct() { 
  38. do_action( 'ms_model_plugin_constructor', $this ); 
  39.  
  40. // Upgrade membership database if needs to. 
  41. MS_Model_Upgrade::init(); 
  42.  
  43. /** 
  44. * Create our own copy of the full admin menu to be used in the 
  45. * Membership2 settings. 
  46. * 
  47. * These hooks are only executed in the admin side. 
  48. */ 
  49. $this->add_action( '_network_admin_menu', 'store_admin_menu', 1 ); 
  50. $this->add_action( '_user_admin_menu', 'store_admin_menu', 1 ); 
  51. $this->add_action( '_admin_menu', 'store_admin_menu', 1 ); 
  52.  
  53. $this->add_action( 'network_admin_menu', 'store_admin_menu', 99999 ); 
  54. $this->add_action( 'user_admin_menu', 'store_admin_menu', 99999 ); 
  55. $this->add_action( 'admin_menu', 'store_admin_menu', 99999 ); 
  56.  
  57. // Register all Add-ons and load rules BEFORE the user is initialized. 
  58. $this->add_action( 'ms_load_member', 'load_addons', 1 ); 
  59. $this->add_action( 'ms_load_member', 'load_rules', 1 ); 
  60.  
  61. // Setup the page protection AFTER the user was initialized. 
  62. $this->add_action( 'ms_init_done', 'setup_rules', 1 ); 
  63. $this->add_action( 'ms_init_done', 'setup_protection', 2 ); 
  64. $this->add_action( 'ms_init_done', 'setup_admin_protection', 3 ); 
  65.  
  66. /** 
  67. * Some plugins (such as MarketPress) can trigger the set_current_user 
  68. * action hook before this object is initialized. To ensure correct 
  69. * loading order we use the `init` hook, which is called directly after 
  70. * the correct set_current_user call. 
  71. * 
  72. * Most of the plugin logic requires the current user to be known,  
  73. * that's why we do a explicit check here to make sure we have a valid 
  74. * user. 
  75. */ 
  76.  
  77. // Initialize the current member 
  78. $this->run_action( 'init', 'init_member', 1 ); 
  79.  
  80. /** 
  81. * ******************************************************************* * 
  82. * Hooks below are only set up when Content Protection is enabled 
  83. * ******************************************************************* * 
  84. */ 
  85.  
  86. if ( ! MS_Plugin::is_enabled() ) { return; } 
  87.  
  88. // Setup the CRON hooks. 
  89. $this->run_action( 'init', 'setup_cron_services', 1 ); 
  90. $this->add_filter( 'cron_schedules', 'cron_time_period' ); 
  91. $this->add_filter( 'ms_run_cron_services', 'run_cron_services' ); 
  92. $this->add_action( 'ms_cron_check_membership_status', 'check_membership_status' ); 
  93.  
  94. $this->add_action( 'template_redirect', 'protect_current_page', 1 ); 
  95.  
  96. // Init gateways and communications to register actions/filters 
  97. $this->run_action( 'init', array( 'MS_Model_Gateway', 'get_gateways' ), 2 ); 
  98. $this->run_action( 'init', array( 'MS_Model_Communication', 'init' ), 2 ); 
  99.  
  100. /** 
  101. * Initialise current member. 
  102. * 
  103. * Get current member and membership relationships. 
  104. * If user is not logged in (visitor), assign a visitor membership. 
  105. * If user is logged in but has not any memberships, assign a default membership. 
  106. * Deactivated users (active == false) get visitor membership assigned. 
  107. * 
  108. * @since 1.0.0 
  109. */ 
  110. public function init_member() { 
  111. do_action( 'ms_load_member', $this ); 
  112.  
  113. $this->member = MS_Model_Member::get_current_member(); 
  114.  
  115. if ( MS_Plugin::is_enabled() ) { 
  116. if ( ! is_user_logged_in() ) { 
  117. // If a Guest-Membership exists we also assign it to the user. 
  118. $ms_guest = MS_Model_Membership::get_guest(); 
  119. if ( $ms_guest->is_valid() && $ms_guest->active ) { 
  120. $this->member->add_membership( $ms_guest->id ); 
  121. } elseif ( ! $this->member->has_membership() ) { 
  122. // Apply User-Membership to logged-in users without subscriptions. 
  123. $ms_user = MS_Model_Membership::get_user(); 
  124. if ( $ms_user->is_valid() && $ms_user->active ) { 
  125. $this->member->add_membership( $ms_user->id ); 
  126. } elseif ( ! $this->member->is_member ) { 
  127. $this->member->subscriptions = array(); 
  128.  
  129. // No subscription: Assign the base membership, which only denies access. 
  130. if ( ! $this->member->has_membership() ) { 
  131. $this->member->add_membership( 
  132. MS_Model_Membership::get_base()->id 
  133. ); 
  134.  
  135. /** 
  136. * At this point the plugin is initialized and we are here: 
  137. * - All Add-Ons are registered 
  138. * - All Rules are registered 
  139. * - Know the current User 
  140. * - All Subscriptions/Memberships of the user are loaded 
  141. * - System memberships are already assigned (guest/base) 
  142. * - Payment gateways are registered 
  143. * - Communication settings are loaded 
  144. * 
  145. * Next we tell everybody that we are ready to get serious! 
  146. * 
  147. * What happens next: 
  148. * 1. All Membership-Rules are initialized/merged 
  149. * 2. Front-End Protection is applied 
  150. * 3. Admin-Side Protection is applied 
  151. */ 
  152.  
  153. do_action( 'ms_init_done', $this ); 
  154.  
  155. /** 
  156. * Returns an array with access-information on the current page/user 
  157. * 
  158. * @since 1.0.0 
  159. * 
  160. * @return array { 
  161. * Access information 
  162. * 
  163. * @type bool $has_access If the current user can view the current page. 
  164. * @type array $memberships List of active membership-IDs the user has 
  165. * registered to. 
  166. * } 
  167. */ 
  168. public function get_access_info() { 
  169. static $Info = null; 
  170.  
  171. if ( null === $Info ) { 
  172. $Info = array( 
  173. 'has_access' => null,  
  174. 'is_admin' => false,  
  175. 'memberships' => array(),  
  176. 'url' => MS_Helper_Utility::get_current_url(),  
  177. ); 
  178.  
  179. // The ID of the main system membership. 
  180. $base_id = MS_Model_Membership::get_base()->id; 
  181.  
  182. $simulation = $this->member->is_simulated_user() || isset( $_GET['explain'] ) && 'access' == $_GET['explain']; 
  183. if ( $simulation ) { $Info['reason'] = array(); } 
  184.  
  185. if ( $this->member->is_normal_admin() ) { 
  186. // Admins have access to ALL memberships. 
  187. $Info['is_admin'] = true; 
  188. $Info['has_access'] = true; 
  189.  
  190. if ( $simulation ) { 
  191. $Info['reason'][] = __( 'Allow: Admin-User always has access', 'membership2' ); 
  192.  
  193. $memberships = MS_Model_Membership::get_memberships(); 
  194. foreach ( $memberships as $membership ) { 
  195. $Info['memberships'][] = $membership->id; 
  196. } else { 
  197. /** 
  198. * A non-admin visitor is only guaranteed access to special 
  199. * Membership2 pages: 
  200. * Registration, Login, etc. 
  201. */ 
  202. $ms_page = MS_Model_Pages::current_page(); 
  203. if ( $ms_page ) { 
  204. $Info['has_access'] = true; 
  205.  
  206. if ( $simulation ) { 
  207. $Info['reason'][] = __( 'Allow: This is a Membership Page', 'membership2' ); 
  208.  
  209. // Build a list of memberships the user belongs to and check permission. 
  210. foreach ( $this->member->subscriptions as $subscription ) { 
  211. // Verify status of the membership. 
  212. // Only active, trial or canceled (until it expires) status memberships. 
  213. if ( ! $this->member->has_membership( $subscription->membership_id ) ) { 
  214. if ( $simulation ) { 
  215. $Info['reason'][] = sprintf( 
  216. __( 'Skipped: Not a member of "%s"', 'membership2' ),  
  217. $subscription->get_membership()->name 
  218. ); 
  219.  
  220. continue; 
  221.  
  222. if ( $base_id !== $subscription->membership_id ) { 
  223. $Info['memberships'][] = $subscription->membership_id; 
  224.  
  225. // If permission is not clear yet then check current membership... 
  226. if ( true !== $Info['has_access'] ) { 
  227. $membership = $subscription->get_membership(); 
  228. $access = $membership->has_access_to_current_page(); 
  229.  
  230. if ( null === $access ) { 
  231. if ( $simulation ) { 
  232. $Info['reason'][] = sprintf( 
  233. __( 'Ignored: Membership "%s"', 'membership2' ),  
  234. $membership->name 
  235. ); 
  236. $Info['reason'][] = $membership->_access_reason; 
  237. continue; 
  238.  
  239. if ( $simulation ) { 
  240. $Info['reason'][] = sprintf( 
  241. __( '%s: Membership "%s"', 'membership2' ),  
  242. $access ? __( 'Allow', 'membership2' ) : __( 'Deny', 'membership2' ),  
  243. $membership->name 
  244. ); 
  245.  
  246. $Info['deciding_membership'] = $membership->id; 
  247. if ( $access ) { 
  248. $Info['deciding_rule'] = $membership->_allow_rule; 
  249. } else { 
  250. $Info['deciding_rule'] = $membership->_deny_rule; 
  251. $Info['reason'][] = $membership->_access_reason; 
  252.  
  253. $Info['has_access'] = $access; 
  254.  
  255. if ( null === $Info['has_access'] ) { 
  256. $Info['has_access'] = true; 
  257.  
  258. if ( $simulation ) { 
  259. $Info['reason'][] = __( 'Allow: Page is not protected', 'membership2' ); 
  260.  
  261. // "membership-id: 0" means: User does not belong to any membership. 
  262. if ( ! count( $Info['memberships'] ) ) { 
  263. $Info['memberships'][] = 0; 
  264.  
  265. $Info = apply_filters( 'ms_model_plugin_get_access_info', $Info ); 
  266.  
  267. if ( $simulation ) { 
  268. $access = lib3()->session->get_clear( 'ms-access' ); 
  269. lib3()->session->add( 'ms-access', $Info ); 
  270. for ( $i = 0; $i < 9; $i += 1 ) { 
  271. if ( isset( $access[$i] ) ) { 
  272. lib3()->session->add( 'ms-access', $access[$i] ); 
  273.  
  274. if ( WP_DEBUG && isset( $_GET['explain'] ) && 'access' == $_GET['explain'] ) { 
  275. echo '<style>code{background:#EEE;background:rgba(0, 0, 0, 0.1);padding:1px 4px;}</style>'; 
  276. echo '<h3>Note</h3>'; 
  277. echo '<p>To disable the URL param <code>?explain=access</code> you have to set <code>WP_DEBUG</code> to false.</p>'; 
  278. echo '<hr><h3>Recent Access checks</h3>'; 
  279.  
  280. lib3()->debug->stacktrace_off(); 
  281. foreach ( $access as $item ) { 
  282. printf( 
  283. '<a href="%1$s">%1$s</a>: <strong>%2$s</strong>',  
  284. $item['url'],  
  285. $item['has_access'] ? __( 'Allow', 'membership2' ) : __( 'Deny', 'membership2' ) 
  286. ); 
  287. // Intended debug output, leave it here. 
  288. lib3()->debug->dump( $item ); 
  289. wp_die( '' ); 
  290.  
  291. return $Info; 
  292.  
  293. /** 
  294. * Checks member permissions and protects current page. 
  295. * 
  296. * Related Action Hooks: 
  297. * - template_redirect 
  298. * 
  299. * @since 1.0.0 
  300. */ 
  301. public function protect_current_page() { 
  302. do_action( 'ms_model_plugin_protect_current_page_before', $this ); 
  303.  
  304. // Admin user has access to everything 
  305. if ( $this->member->is_normal_admin() ) { 
  306. return; 
  307.  
  308. $access = $this->get_access_info(); 
  309.  
  310. if ( ! $access['has_access'] ) { 
  311. MS_Model_Pages::create_missing_pages(); 
  312. $no_access_page_url = MS_Model_Pages::get_page_url( 
  313. MS_Model_Pages::MS_PAGE_PROTECTED_CONTENT,  
  314. false 
  315. ); 
  316. $current_page_url = MS_Helper_Utility::get_current_url(); 
  317.  
  318. // Don't (re-)redirect the protection page. 
  319. if ( ! MS_Model_Pages::is_membership_page( null, MS_Model_Pages::MS_PAGE_PROTECTED_CONTENT ) ) { 
  320. $no_access_page_url = esc_url_raw( 
  321. add_query_arg( 
  322. array( 'redirect_to' => urlencode( $current_page_url ) ),  
  323. $no_access_page_url 
  324. ); 
  325.  
  326. $no_access_page_url = apply_filters( 
  327. 'ms_model_plugin_protected_content_page',  
  328. $no_access_page_url 
  329. ); 
  330. wp_safe_redirect( $no_access_page_url ); 
  331.  
  332. exit; 
  333.  
  334. do_action( 'ms_model_plugin_protect_current_page_after', $this ); 
  335.  
  336. /** 
  337. * Load all the Add-ons. 
  338. * 
  339. * Related Action Hooks: 
  340. * - ms_load_member 
  341. * 
  342. * @since 1.0.0 
  343. */ 
  344. public function load_addons() { 
  345. do_action( 'ms_load_addons', $this ); 
  346.  
  347. // Initialize all Add-ons 
  348. MS_Model_Addon::get_addons(); 
  349.  
  350. /** 
  351. * Load all the rules that are used by the plugin. 
  352. * 
  353. * Related Action Hooks: 
  354. * - ms_load_member 
  355. * 
  356. * @since 1.0.0 
  357. */ 
  358. public function load_rules() { 
  359. do_action( 'ms_load_rules', $this ); 
  360.  
  361. $rule_types = MS_Model_Rule::get_rule_types(); 
  362. $base = MS_Model_Membership::get_base(); 
  363.  
  364. foreach ( $rule_types as $rule_type ) { 
  365. $rule = $base->get_rule( $rule_type ); 
  366.  
  367. /** 
  368. * Load all the rules that are used by the plugin. 
  369. * 
  370. * Related Action Hooks: 
  371. * - ms_init_done 
  372. * 
  373. * @since 1.0.0 
  374. */ 
  375. public function setup_rules() { 
  376. // Make sure we stick to the correct workflow. 
  377. if ( ! did_action( 'ms_init_done' ) ) { 
  378. throw new Exception( 'setup_rules() is called too early.', 1 ); 
  379. return; 
  380.  
  381. do_action( 'ms_initialize_rules', $this ); 
  382.  
  383. $rule_types = MS_Model_Rule::get_rule_types(); 
  384.  
  385. foreach ( $this->member->subscriptions as $subscription ) { 
  386. foreach ( $rule_types as $rule_type ) { 
  387. $rule = $subscription->get_membership()->get_rule( $rule_type ); 
  388.  
  389. /** 
  390. * Setup initial protection for the front-end. 
  391. * 
  392. * Hide menu and pages, protect media donwload and feeds. 
  393. * Protect feeds. 
  394. * 
  395. * Related Action Hooks: 
  396. * - ms_init_done 
  397. * 
  398. * @since 1.0.0 
  399. */ 
  400. public function setup_protection() { 
  401. if ( is_admin() ) { return; } 
  402.  
  403. // Make sure we stick to the correct workflow. 
  404. if ( ! did_action( 'ms_init_done' ) ) { 
  405. throw new Exception( 'setup_protection() is called too early.', 1 ); 
  406. return; 
  407.  
  408. do_action( 'ms_setup_protection', $this ); 
  409.  
  410. // Search permissions through all memberships joined. 
  411. foreach ( $this->member->subscriptions as $subscription ) { 
  412. // Verify status of the membership. 
  413. // Only active, trial or canceled (until it expires) status memberships. 
  414. if ( ! $this->member->has_membership( $subscription->membership_id ) ) { 
  415. continue; 
  416.  
  417. $membership = $subscription->get_membership(); 
  418. $membership->initialize( $subscription ); 
  419.  
  420. // Protection is not applied for Admin users 
  421. if ( ! $this->member->is_normal_admin() ) { 
  422. $membership->protect_content(); 
  423.  
  424. do_action( 'ms_setup_protection_done', $this ); 
  425.  
  426. /** 
  427. * Setup initial protection for the admin-side. 
  428. * 
  429. * Related Action Hooks: 
  430. * - ms_init_done 
  431. * 
  432. * @since 1.0.0 
  433. */ 
  434. public function setup_admin_protection() { 
  435. if ( ! is_admin() ) { return; } 
  436.  
  437. // Make sure we stick to the correct workflow. 
  438. if ( ! did_action( 'ms_init_done' ) ) { 
  439. throw new Exception( 'setup_admin_protection() is called too early.', 1 ); 
  440. return; 
  441.  
  442. do_action( 'ms_setup_admin_protection', $this ); 
  443.  
  444. // Search permissions through all memberships joined. 
  445. foreach ( $this->member->subscriptions as $subscription ) { 
  446. // Verify status of the membership. 
  447. // Only active, trial or canceled (until it expires) status memberships. 
  448. if ( ! $this->member->has_membership( $subscription->membership_id ) ) { 
  449. continue; 
  450.  
  451. $membership = $subscription->get_membership(); 
  452. $membership->initialize( $subscription ); 
  453.  
  454. // Protection is not applied for Admin users 
  455. if ( ! $this->member->is_normal_admin() ) { 
  456. $membership->protect_admin_content(); 
  457.  
  458. do_action( 'ms_setup_admin_protection_done', $this ); 
  459.  
  460. /** 
  461. * Config cron time period. This is actually not displayed anywhere but 
  462. * used only in function setup_cron_services() 
  463. * 
  464. * Related Action Hooks: 
  465. * - cron_schedules 
  466. * 
  467. * @since 1.0.0 
  468. */ 
  469. public function cron_time_period( $periods ) { 
  470. if ( ! is_array( $periods ) ) { 
  471. $periods = array(); 
  472.  
  473. $periods['6hours'] = array( 
  474. 'interval' => 6 * HOUR_IN_SECONDS,  
  475. 'display' => __( 'Every 6 Hours', 'membership2' ) 
  476. ); 
  477. $periods['30mins'] = array( 
  478. 'interval' => 30 * MINUTE_IN_SECONDS,  
  479. 'display' => __( 'Every 30 Mins', 'membership2' ) 
  480. ); 
  481.  
  482. return apply_filters( 
  483. 'ms_model_plugin_cron_time_period',  
  484. $periods 
  485. ); 
  486.  
  487. /** 
  488. * Runs a single Membership 2 cron service and then re-schedules it. 
  489. * This function is used to manually trigger the cron services. 
  490. * 
  491. * @since 1.0.0 
  492. */ 
  493. public function run_cron_services( $hook ) { 
  494. wp_clear_scheduled_hook( $hook ); 
  495. $this->setup_cron_services( $hook ); 
  496.  
  497. // Note that we only remove the cron job and add it again. 
  498. // As a result the job is re-scheduled with current timestamp and 
  499. // therefore it will be executed instantly. 
  500.  
  501. /** 
  502. * Setup cron plugin services. 
  503. * 
  504. * Setup cron to call actions. 
  505. * The action-hook is called via the WordPress Cron implementation on a 
  506. * regular basis - this hooks are set up only once. 
  507. * 
  508. * The Cron jobs can be manually executed by opening the admin page 
  509. * "Membership2 > Settings" and adding URL param "&run_cron=1" 
  510. * 
  511. * @since 1.0.0 
  512. */ 
  513. public function setup_cron_services( $reschedule = null ) { 
  514. do_action( 'ms_model_plugin_setup_cron_services_before', $this ); 
  515.  
  516. $jobs = array( 
  517. 'ms_cron_check_membership_status' => '6hours',  
  518. 'ms_cron_process_communications' => 'hourly',  
  519. ); 
  520.  
  521. foreach ( $jobs as $hook => $interval ) { 
  522. if ( ! wp_next_scheduled( $hook ) || $hook == $reschedule ) { 
  523. wp_schedule_event( time(), $interval, $hook ); 
  524.  
  525. do_action( 'ms_model_plugin_setup_cron_services_after', $this ); 
  526.  
  527. /** 
  528. * Check membership status. 
  529. * 
  530. * Execute actions when time/period condition are met. 
  531. * E.g. change membership status, add communication to queue, create invoices. 
  532. * 
  533. * @since 1.0.0 
  534. */ 
  535. public function check_membership_status() { 
  536. do_action( 'ms_model_plugin_check_membership_status_before', $this ); 
  537.  
  538. if ( $this->member->is_simulated_user() ) { 
  539. return; 
  540.  
  541. $args = apply_filters( 
  542. 'ms_model_plugin_check_membership_status_get_subscription_args',  
  543. array( 'status' => 'valid' ) 
  544. ); 
  545. $subscriptions = MS_Model_Relationship::get_subscriptions( $args ); 
  546.  
  547. foreach ( $subscriptions as $subscription ) { 
  548. $subscription->check_membership_status(); 
  549.  
  550. do_action( 'ms_model_plugin_check_membership_status_after', $this ); 
  551.  
  552. /** 
  553. * Copies the full WordPress Admin menu before any restriction is applied 
  554. * by WordPress or an Plugin. This menu-information is used on the 
  555. * Membership2/Accessible Content settings pages 
  556. * 
  557. * @since 1.0.0 
  558. * @global array $menu 
  559. * @global array $submenu 
  560. */ 
  561. public function store_admin_menu() { 
  562. global $menu, $submenu; 
  563.  
  564. if ( ! isset( $this->admin_menu['main'] ) ) { 
  565. $this->admin_menu = array( 
  566. 'main' => $menu,  
  567. 'sub' => $submenu,  
  568. ); 
  569. } else { 
  570. foreach ( $menu as $pos => $item ) { 
  571. $this->admin_menu['main'][$pos] = $item; 
  572. foreach ( $submenu as $parent => $item ) { 
  573. $this->admin_menu['sub'][$parent] = $item; 
  574. ksort( $this->admin_menu['main'] ); 
  575.  
  576. /** 
  577. * Returns the previously stored admin menu items. 
  578. * 
  579. * @since 1.0.0 
  580. * 
  581. * @return array 
  582. */ 
  583. public function get_admin_menu() { 
  584. return $this->admin_menu; 
  585.  
.