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

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