MS_Model_Upgrade

Upgrade DB model.

Defined (1)

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

/app/model/class-ms-model-upgrade.php  
  1. class MS_Model_Upgrade extends MS_Model { 
  2.  
  3. /** 
  4. * Initialize upgrading check. 
  5. * @since 1.0.0 
  6. */ 
  7. public static function init() { 
  8. self::update(); 
  9.  
  10. MS_Factory::load( 'MS_Model_Upgrade' ); 
  11.  
  12. self::maybe_restore(); 
  13. add_action( 'init', array( __CLASS__, 'maybe_reset' ) ); 
  14.  
  15. do_action( 'ms_model_upgrade_init' ); 
  16.  
  17. /** 
  18. * Upgrade database. 
  19. * @since 1.0.0 
  20. * @param bool $force Also execute update logic when version did not change. 
  21. */ 
  22. public static function update( $force = false ) { 
  23. static $Done = false; 
  24.  
  25. if ( $Done && ! $force ) { return; } 
  26.  
  27. // Migration handler has its own valid_user check. 
  28. self::check_migration_handler(); 
  29.  
  30. // Updates are only triggered from Admin-Side by an Admin user. 
  31. if ( ! self::valid_user() ) { return; } 
  32.  
  33. // Check for correct network-wide protection setup. 
  34. self::check_network_setup(); 
  35.  
  36. $settings = MS_Factory::load( 'MS_Model_Settings' ); 
  37. $old_version = $settings->version; // Old: The version in DB. 
  38. $new_version = MS_Plugin::instance()->version; // New: Version in file. 
  39.  
  40. $is_new_setup = empty( $old_version ); 
  41.  
  42. // Compare current src version to DB version: 
  43. // We only do UP-grades but no DOWN-grades! 
  44. $version_changed = $old_version && version_compare( $old_version, $new_version, 'lt' ); 
  45.  
  46. if ( $force || $version_changed ) { 
  47. $Done = true; 
  48. $msg = array(); 
  49.  
  50. /** 
  51. * ----- General update logic, executed on every update ------------ 
  52. */ 
  53.  
  54. do_action( 
  55. 'ms_model_upgrade_before_update',  
  56. $settings,  
  57. $old_version,  
  58. $new_version,  
  59. $force 
  60. ); 
  61.  
  62. // Prepare the Update message. 
  63. if ( ! $version_changed ) { 
  64. $msg[] = sprintf( 
  65. __( '<strong>Membership2</strong> is set up for version %1$s!' , MS_TEXT_DOMAIN ),  
  66. $new_version 
  67. ); 
  68. } else { 
  69. $msg[] = sprintf( 
  70. __( '<strong>Membership2</strong> was updated to version %1$s!' , MS_TEXT_DOMAIN ),  
  71. $new_version 
  72. ); 
  73.  
  74. // Every time the plugin is updated we clear the cache. 
  75. MS_Factory::clear(); 
  76.  
  77. // Create missing Membership pages. 
  78. $new_pages = MS_Model_Pages::create_missing_pages(); 
  79.  
  80. if ( ! empty( $new_pages ) ) { 
  81. $msg[] = sprintf( 
  82. __( 'New Membership pages created: "%1$s".', MS_TEXT_DOMAIN ),  
  83. implode( '", "', $new_pages ) 
  84. ); 
  85.  
  86. // Remove an old version of Protected Content 
  87. if ( $version_changed ) { 
  88. self::remove_old_copy(); 
  89.  
  90. // Note: We do not create menu items on upgrade! Users might have 
  91. // intentionally removed the items from the menu... 
  92.  
  93. /** 
  94. * ----- Version-Specific update logic ----------------------------- 
  95. */ 
  96.  
  97. // Upgrade from a 0.x version to 1.0.x or higher 
  98. if ( version_compare( $old_version, '1.0.0.0', 'lt' ) ) { 
  99. self::_upgrade_1_0_0_0(); 
  100.  
  101. // Upgrade from any 1.0.x version to 1.1.x or higher 
  102. if ( version_compare( $old_version, '1.1.0.0', 'lt' ) ) { 
  103. self::_upgrade_1_1_0_0(); 
  104.  
  105. // Upgrade from any 1.1.x version to 1.1.0.3 or higher 
  106. if ( version_compare( $old_version, '1.1.0.3', 'lt' ) ) { 
  107. self::_upgrade_1_1_0_3(); 
  108.  
  109. // Upgrade from any 1.1.x version to 1.1.0.5 or higher 
  110. if ( version_compare( $old_version, '1.1.0.5', 'lt' ) ) { 
  111. self::_upgrade_1_1_0_5(); 
  112.  
  113. // Upgrade from any 1.1.x version to 1.1.0.8 or higher 
  114. if ( version_compare( $old_version, '1.1.0.8', 'lt' ) ) { 
  115. self::_upgrade_1_1_0_8(); 
  116.  
  117. // Upgrade from any 1.1.x version to 1.1.1.4 or higher 
  118. if ( version_compare( $old_version, '1.1.1.4', 'lt' ) ) { 
  119. self::_upgrade_1_1_1_4(); 
  120.  
  121. /** 
  122. * ----- General update logic, executed on every update ------------ 
  123. */ 
  124.  
  125. $settings->version = $new_version; 
  126. $settings->save(); 
  127.  
  128. // Display a message after the page is reloaded. 
  129. if ( ! $is_new_setup ) { 
  130. lib2()->ui->admin_message( implode( '<br>', $msg ), '', '', 'ms-update' ); 
  131.  
  132. do_action( 
  133. 'ms_model_upgrade_after_update',  
  134. $settings,  
  135. $old_version,  
  136. $new_version,  
  137. $force 
  138. ); 
  139.  
  140. // This will reload the current page. 
  141. MS_Plugin::flush_rewrite_rules(); 
  142.  
  143.  
  144. # ########################################################################## 
  145. # ########################################################################## 
  146.  
  147.  
  148. /** 
  149. * Upgrade from any 0.x version to a higher version. 
  150. */ 
  151. static private function _upgrade_1_0_0_0() { 
  152. $args = array(); 
  153. $args['post_parent__not_in'] = array( 0 ); 
  154. $memberships = MS_Model_Membership::get_memberships( $args ); 
  155.  
  156. // Delete orphans (junk-data introduced by early bug) 
  157. foreach ( $memberships as $membership ) { 
  158. $parent = MS_Factory::load( 'MS_Model_Membership', $membership->parent_id ); 
  159. if ( ! $parent->is_valid() ) { 
  160. $membership->delete(); 
  161.  
  162. # ########################################################################## 
  163.  
  164. /** 
  165. * Upgrade from any 1.0.x version to a higher version. 
  166. */ 
  167. static private function _upgrade_1_1_0_0() { 
  168. self::snapshot( '1.1.0.0' ); 
  169.  
  170. /** 
  171. * Add-ons 
  172. * 1. The key-name 'addon' changed to 'active' 
  173. */ 
  174. $data = get_option( 'MS_Model_Addon' ); 
  175. // 1. 
  176. if ( ! isset( $data['active'] ) && isset( $data['addons'] ) ) { 
  177. $data['active'] = $data['addons']; 
  178. unset( $data['addons'] ); 
  179. lib2()->updates->add( 'update_option', 'MS_Model_Addon', $data ); 
  180.  
  181. /** 
  182. * Settings 
  183. * 1. The key 'is_first_membership' was introduced 
  184. * 1. The key 'import' was introduced 
  185. */ 
  186. $data = get_option( 'MS_Model_Settings' ); 
  187. // 1. 
  188. if ( ! isset( $data['is_first_membership'] ) ) { 
  189. $data['is_first_membership'] = false; 
  190. // 2. 
  191. if ( ! isset( $data['import'] ) ) { 
  192. $data['import'] = array(); 
  193. lib2()->updates->add( 'update_option', 'MS_Model_Settings', $data ); 
  194.  
  195. /** 
  196. * Memberships 
  197. * 1. The key 'parent_id' was dropped 
  198. * 2. The key 'protected_content' was dropped 
  199. * 3. Types 'content_type' and 'tier' were replaced by 'simple' 
  200. * 4. Key 'rules' was migrated to 'rule_values' 
  201. * 4.1 Rule 'url_group' was renamed to 'url' 
  202. * 4.2 Rule 'more_tag' was renamed to 'content' 
  203. * 4.3 Rule 'comment' was merged with 'content' 
  204. */ 
  205. $args = array( 
  206. 'post_type' => 'ms_membership',  
  207. 'post_status' => 'any',  
  208. 'nopaging' => true,  
  209. ); 
  210. $query = new WP_Query( $args ); 
  211. $memberships = $query->get_posts(); 
  212. // Find the base rules. 
  213. $base = false; 
  214. foreach ( $memberships as $membership ) { 
  215. $is_base = get_post_meta( $membership->ID, 'protected_content', true ); 
  216. if ( ! empty( $is_base ) ) { 
  217. $base = $membership; 
  218. $base_rules = get_post_meta( $base->ID, 'rules', true ); 
  219. foreach ( $base_rules as $key => $data ) { 
  220. if ( 'url_group' === $key ) { $key = 'url'; } 
  221. $base_rules[$key] = self::fix_object( $data ); 
  222. break; 
  223. // Migrate data. 
  224. $base_values = array(); 
  225. $has_dripped_posts = false; 
  226. foreach ( $memberships as $membership ) { 
  227. // 1. 
  228. lib2()->updates->add( 'delete_post_meta', $membership->ID, 'parent_id' ); 
  229. $membership->post_parent = 0; 
  230. // 2. 
  231. $is_base = get_post_meta( $membership->ID, 'protected_content', true ); 
  232. $is_base = ! empty( $is_base ); 
  233. if ( $is_base ) { 
  234. lib2()->updates->add( 'delete_post_meta', $membership->ID, 'protected_content' ); 
  235. lib2()->updates->add( 'update_post_meta', $membership->ID, 'type', 'base' ); 
  236. } else { 
  237. // 3. 
  238. $type = get_post_meta( $membership->ID, 'type', true ); 
  239. if ( 'dripped' != $type ) { 
  240. lib2()->updates->add( 'update_post_meta', $membership->ID, 'type', 'simple' ); 
  241. // 4. 
  242. $rules = get_post_meta( $membership->ID, 'rules', true ); 
  243. if ( is_array( $rules ) ) { $rules = (object) $rules; } 
  244. if ( ! is_object( $rules ) ) { $rules = new stdClass(); } 
  245. $serialized = array(); 
  246. foreach ( $rules as $key => $data ) { 
  247. // 4.1 
  248. if ( 'url_group' === $key ) { $key = 'url'; } 
  249.  
  250. $data = self::fix_object( $data ); 
  251. $data->rule_value = lib2()->array->get( $data->rule_value ); 
  252. $data->dripped = lib2()->array->get( $data->dripped ); 
  253. $access = array(); 
  254.  
  255. if ( ! empty( $data->dripped ) 
  256. && ! empty( $data->dripped['dripped_type'] ) 
  257. ) { 
  258. $is_dripped = true; 
  259. $drip_type = $data->dripped['dripped_type']; 
  260. $drip_data = $data->dripped[ $drip_type ]; 
  261. $data->rule_value = array_fill_keys( array_keys( $drip_data ), 1 ); 
  262. } else { 
  263. $is_dripped = false; 
  264.  
  265. foreach ( $data->rule_value as $id => $state ) { 
  266. if ( $state ) { 
  267. if ( $is_dripped ) { 
  268. // ----- Dripped access 
  269. if ( ! isset( $drip_data[$id] ) ) { 
  270. // No drip-details set, but access granted: Reveal instantly. 
  271. $item_drip = array( 'instantly', '', '', '' ); 
  272. } else { 
  273. $item_drip = array( $drip_type, '', '', '' ); 
  274.  
  275. if ( 'specific_date' == $drip_type ) { 
  276. if ( isset( $drip_data[$id]['spec_date'] ) ) { 
  277. $item_drip[1] = $drip_data[$id]['spec_date']; 
  278. } else { 
  279. if ( ! isset( $drip_data[$id]['period_type'] ) ) { 
  280. $drip_data[$id]['period_type'] = 'days'; 
  281. if ( isset( $drip_data[$id]['period_unit'] ) ) { 
  282. $item_drip[2] = $drip_data[$id]['period_unit']; 
  283. $item_drip[3] = $drip_data[$id]['period_type']; 
  284. if ( 'post' == $key ) { 
  285. $has_dripped_posts = true; 
  286. $access[] = array( 
  287. 'id' => $id,  
  288. 'dripped' => $item_drip,  
  289. ); 
  290. } else { 
  291. // ----- Standard access 
  292. if ( 'url' === $key ) { 
  293. if ( ! $is_base ) { 
  294. // First get the URL from base rule 
  295. $base_url = $base_rules['url']->rule_value; 
  296. if ( isset( $base_url[$id] ) ) { 
  297. $url = $base_url[$id]; 
  298. } else { 
  299. continue; 
  300. } else { 
  301. $url = $state; 
  302. $hash = md5( $url ); 
  303. $access[$hash] = $url; 
  304. } elseif ( 'more_tag' == $key ) { 
  305. // 4.2 
  306. if ( 'more_tag' == $id ) { 
  307. if ( ! isset( $serialized['content'] ) ) { 
  308. $serialized['content'] = array(); 
  309.  
  310. $serialized['content']['no_more'] = 1; 
  311. } elseif ( 'comment' == $key ) { 
  312. // 4.3 
  313. if ( ! isset( $serialized['content'] ) ) { 
  314. $serialized['content'] = array(); 
  315.  
  316. if ( '2' == $state ) { 
  317. $serialized['content']['cmt_none'] = 1; 
  318. } elseif ( '1' == $state ) { 
  319. $serialized['content']['cmt_read'] = 1; 
  320. } elseif ( '0' == $state ) { 
  321. $serialized['content']['cmt_full'] = 1; 
  322. }; 
  323. } else { 
  324. $access[] = $id; 
  325. if ( ! empty( $access ) ) { 
  326. $serialized[$key] = $access; 
  327.  
  328. // Make sure the protected item is listed in the base rule. 
  329. if ( ! isset( $base_values[$key] ) ) { 
  330. $base_values[$key] = array(); 
  331. foreach ( $access as $ind => $state ) { 
  332. if ( is_numeric( $ind ) ) { 
  333. $id = 0; 
  334. if ( is_array( $state ) ) { 
  335. // Dripped rule. 
  336. if ( isset( $state['id'] ) ) { 
  337. $id = $state['id']; 
  338. } else { 
  339. // Normal rule. 
  340. $id = $state; 
  341. if ( $id && ! in_array( $id, $base_values[$key] ) ) { 
  342. $base_values[$key][] = $id; 
  343. } elseif ( is_string( $ind ) ) { 
  344. // URL groups. 
  345. $base_values[$key][$ind] = $state; 
  346. lib2()->updates->add( 'wp_update_post', $membership ); 
  347. // We set the base rules a bit later. 
  348. if ( $base && isset( $base->ID ) && $membership->ID != $base->ID ) { 
  349. lib2()->updates->add( 'update_post_meta', $membership->ID, 'rule_values', $serialized ); 
  350. // Set the base rules after all memberships were parsed. 
  351. if ( $base && isset( $base->ID ) ) { 
  352. lib2()->updates->add( 'update_post_meta', $base->ID, 'rule_values', $base_values ); 
  353. // When dripped rules publish posts then the "Individual Posts" Addon is needed. 
  354. if ( $has_dripped_posts ) { 
  355. $addons = MS_Factory::load( 'MS_Model_Addon' ); 
  356. lib2()->updates->add( array( $addons, 'enable' ), MS_Model_Addon::ADDON_POST_BY_POST ); 
  357.  
  358. /** 
  359. * Remove old cron hooks 
  360. * Names did change: 
  361. * ms_model_plugin_check_membership_status -> ms_cron_check_membership_status 
  362. * ms_model_plugin_process_communications -> ms_cron_process_communications 
  363. * Only remove old hooks here: New hooks are added by MS_Model_Plugin. 
  364. */ 
  365. lib2()->updates->add( 'wp_clear_scheduled_hook', 'ms_cron_check_membership_status' ); 
  366. lib2()->updates->add( 'wp_clear_scheduled_hook', 'ms_cron_process_communications' ); 
  367.  
  368. // Execute all queued actions! 
  369. lib2()->updates->plugin( MS_TEXT_DOMAIN ); 
  370. lib2()->updates->execute(); 
  371.  
  372. // Cleanup 
  373. if ( $base && isset( $base->ID ) ) { 
  374. $base_membership = MS_Factory::load( 'MS_Model_Membership', $base->ID ); 
  375. $base_membership->type = 'base'; 
  376. $base_membership->save(); 
  377.  
  378. foreach ( $memberships as $membership ) { 
  379. $membership = MS_Factory::load( 'MS_Model_Membership', $membership->ID ); 
  380. // This will remove all deprecated properties from DB. 
  381. $membership->save(); 
  382.  
  383. # ########################################################################## 
  384.  
  385. /** 
  386. * Upgrade from any 1.1.x version to 1.1.0.3 or higher 
  387. */ 
  388. static private function _upgrade_1_1_0_3() { 
  389. global $wpdb; 
  390.  
  391. self::snapshot( '1.1.0.3' ); 
  392.  
  393. /** 
  394. * Rename payment gateway IDs 
  395. * 1. The gateway 'paypal_single' was renamed to 'paypalsingle' 
  396. * 1. The gateway 'paypal_standard' was renamed to 'paypalstandard' 
  397. */ 
  398. $sql = " 
  399. SELECT ID 
  400. FROM {$wpdb->posts} 
  401. WHERE post_type IN ( 'ms_relationship', 'ms_invoice' ) 
  402. "; 
  403. $posts = $wpdb->get_col( $sql ); 
  404.  
  405. foreach ( $posts as $post_id ) { 
  406. $gateway = get_post_meta( $post_id, 'gateway_id', true ); 
  407. // 1. 
  408. if ( 'paypal_single' == $gateway ) { 
  409. lib2()->updates->add( 'update_post_meta', $post_id, 'gateway_id', 'paypalsingle' ); 
  410. // 2. 
  411. if ( 'paypal_standard' == $gateway ) { 
  412. lib2()->updates->add( 'update_post_meta', $post_id, 'gateway_id', 'paypalstandard' ); 
  413.  
  414. // Execute all queued actions! 
  415. lib2()->updates->plugin( MS_TEXT_DOMAIN ); 
  416. lib2()->updates->execute(); 
  417.  
  418. # ########################################################################## 
  419.  
  420. /** 
  421. * Upgrade from any 1.1.x version to 1.1.0.5 or higher 
  422. */ 
  423. static private function _upgrade_1_1_0_5() { 
  424. self::snapshot( '1.1.0.5' ); 
  425.  
  426. /** 
  427. * When upgrading from 1.0 to 1.1 the payment gateway details were lost 
  428. * due to a new name of the option-keys. 
  429. * We try to restore the lost settings now. 
  430. * 1. option_name 'MS_Model_Gateway_Authorize' -> 'ms_gateway_authorize' 
  431. * 2. option_name 'MS_Model_Gateway_Manual' -> 'ms_gateway_manual' 
  432. * 3. option_name 'MS_Model_Gateway_Paypal_Single' -> 'ms_gateway_paypalsingle' 
  433. * 4. option_name 'MS_Model_Gateway_Paypal_Standard' -> 'ms_gateway_paypalstandard' 
  434. * 5. option_name 'MS_Model_Gateway_Stripe' -> 'ms_gateway_stripe' 
  435. */ 
  436. $matching = array( 
  437. 'ms_gateway_authorize' => 'MS_Model_Gateway_Authorize',  
  438. 'ms_gateway_manual' => 'MS_Model_Gateway_Manual',  
  439. 'ms_gateway_paypalsingle' => 'MS_Model_Gateway_Paypal_Single',  
  440. 'ms_gateway_paypalstandard' => 'MS_Model_Gateway_Paypal_Standard',  
  441. 'ms_gateway_stripe' => 'MS_Model_Gateway_Stripe',  
  442. ); 
  443.  
  444. foreach ( $matching as $new_key => $old_key ) { 
  445. $old_val = get_option( $old_key ); 
  446. if ( ! get_option( $new_key ) && is_array( $old_val ) ) { 
  447. switch ( $old_val['id'] ) { 
  448. case 'paypal_single': $old_val['id'] = 'paypalsingle'; break; 
  449. case 'paypal_standard': $old_val['id'] = 'paypalstandard'; break; 
  450.  
  451. lib2()->updates->add( 'update_option', $new_key, $old_val ); 
  452.  
  453. // Execute all queued actions! 
  454. lib2()->updates->plugin( MS_TEXT_DOMAIN ); 
  455. lib2()->updates->execute(); 
  456.  
  457. # ########################################################################## 
  458.  
  459. /** 
  460. * Upgrade from any 1.1.x version to 1.1.0.8 or higher 
  461. */ 
  462. static private function _upgrade_1_1_0_8() { 
  463. self::snapshot( '1.1.0.8' ); 
  464.  
  465. /** 
  466. * We introduce the new Add-on "Category Protection" which was a core 
  467. * rule until now, which means it was always active. So activate it 
  468. * when upgrading to the new version! 
  469. */ 
  470. $addons = MS_Factory::load( 'MS_Model_Addon' ); 
  471. lib2()->updates->add( array( $addons, 'enable' ), MS_Addon_Category::ID ); 
  472.  
  473. // Execute all queued actions! 
  474. lib2()->updates->plugin( MS_TEXT_DOMAIN ); 
  475. lib2()->updates->execute(); 
  476.  
  477. # ########################################################################## 
  478.  
  479. /** 
  480. * Upgrade from any 1.1.x version to 1.1.1.4 or higher 
  481. */ 
  482. static private function _upgrade_1_1_1_4() { 
  483. self::snapshot( '1.1.1.4' ); 
  484.  
  485. /** 
  486. * Invoice structure changes: 
  487. * - Field 'trial_period' renamed to 'uses_trial' 
  488. * - New Field added 'trial_price' 
  489. * - New Field added 'trial_ends' 
  490. */ 
  491. $args = array( 
  492. 'post_status' => 'any',  
  493. 'post_type' => MS_Model_Invoice::get_post_type(),  
  494. 'posts_per_page' => 0,  
  495. 'nopaging' => true,  
  496. ); 
  497. // Get a list of all invoices. 
  498. $invoices = get_posts( $args ); 
  499.  
  500. $trial_match = array(); 
  501. foreach ( $invoices as $post ) { 
  502. $is_trial = get_post_meta( $post->ID, 'trial_period', true ); 
  503. $is_trial = lib2()->is_true( $is_trial ); 
  504.  
  505. if ( $is_trial ) { 
  506. $subscription_id = intval( get_post_meta( $post->ID, 'ms_relationship_id', true ) ); 
  507. $invoice_number = intval( get_post_meta( $post->ID, 'invoice_number', true ) ); 
  508.  
  509. $paid_args = array( 
  510. 'meta_query' => array( 
  511. 'relation' => 'AND',  
  512. array( 
  513. 'key' => 'ms_relationship_id',  
  514. 'value' => $subscription_id,  
  515. 'compare' => '=',  
  516. ),  
  517. array( 
  518. 'key' => 'trial_period',  
  519. 'value' => '',  
  520. 'compare' => '=',  
  521. ),  
  522. array( 
  523. 'key' => 'invoice_number',  
  524. 'value' => $invoice_number + 1,  
  525. 'compare' => '=',  
  526. ); 
  527. $paid_invoice = MS_Model_Invoice::get_invoices( $paid_args ); 
  528.  
  529. if ( ! empty( $paid_invoice ) ) { 
  530. $trial_match[$post->ID] = reset( $paid_invoice ); 
  531. } else { 
  532. // Normal invoice. Add new fields with default values. 
  533. lib2()->updates->add( 'update_post_meta', $post->ID, 'uses_trial', '' ); 
  534. lib2()->updates->add( 'update_post_meta', $post->ID, 'trial_price', '0' ); 
  535. lib2()->updates->add( 'update_post_meta', $post->ID, 'trial_ends', '' ); 
  536.  
  537. foreach ( $trial_match as $trial_id => $paid_invoice ) { 
  538. $trial_invoice = MS_Factory::load( 'MS_Model_Invoice', $trial_id ); 
  539. $subscription = $trial_invoice->get_subscription(); 
  540. $trial_ends = $subscription->trial_expire_date; 
  541.  
  542. lib2()->updates->add( 'update_post_meta', $paid_invoice->id, 'uses_trial', '1' ); 
  543. lib2()->updates->add( 'update_post_meta', $paid_invoice->id, 'trial_ends', $trial_ends ); 
  544. lib2()->updates->add( 'wp_delete_post', $trial_id, true ); 
  545.  
  546. if ( $subscription->current_invoice_number == $trial_invoice->invoice_number ) { 
  547. lib2()->updates->add( 
  548. 'update_post_meta',  
  549. $subscription->id,  
  550. 'current_invoice_number',  
  551. $paid_invoice->invoice_number 
  552. ); 
  553.  
  554. // Execute all queued actions! 
  555. lib2()->updates->plugin( MS_TEXT_DOMAIN ); 
  556. lib2()->updates->execute(); 
  557.  
  558. # ########################################################################## 
  559.  
  560. /** 
  561. * Used when upgrading from Membership to M2. If both Membership and 
  562. * Protected Content are installed when upgrading then the old 
  563. * "protected-content" folder may survive the upgrade and needs to be 
  564. * manually removed. 
  565. * @since 2.0.0 
  566. */ 
  567. static private function remove_old_copy() { 
  568. $new_dir = WP_PLUGIN_DIR . '/membership'; 
  569. $old_dir = WP_PLUGIN_DIR . '/protected-content'; 
  570. $old_plugins = array( 
  571. 'protected-content/protected-content.php',  
  572. 'membership/membershippremium.php',  
  573. ); 
  574. $new_plugin = plugin_basename( MS_Plugin::instance()->file ); 
  575.  
  576. // Make sure that the current plugin is the official M2 one. 
  577. if ( false === strpos( MS_Plugin::instance()->dir, $new_dir ) ) { 
  578. // Cancel: This plugin is not the official plugin (maybe a backup or beta version) 
  579. return; 
  580.  
  581. // 1. See if there is a old copy of the plugin directory. Delete it. 
  582. if ( is_dir( $old_dir ) && is_file( $old_dir . '/protected-content.php' ) ) { 
  583. // Looks like the old version of this plugin is still installed. Remove it. 
  584. try { 
  585. unlink( $old_dir . '/protected-content.php' ); 
  586. array_map( 'unlink', glob( "$old_dir/*.*" ) ); 
  587. rmdir( $old_dir ); 
  588. } catch( Exception $e ) { 
  589. // Something went wrong when removing the old plugin. 
  590.  
  591. // 2. See if WordPress uses an old plugin in the DB. Update it. 
  592. if ( is_multisite() ) { 
  593. $global_plugins = (array) get_site_option( 'active_sitewide_plugins', array() ); 
  594. foreach ( $global_plugins as $key => $the_path ) { 
  595. if ( in_array( $the_path, $old_plugins ) ) { 
  596. $global_plugins[$key] = $new_plugin; 
  597. update_site_option( 'active_sitewide_plugins', $global_plugins ); 
  598.  
  599. $site_plugins = (array) get_option( 'active_plugins', array() ); 
  600. foreach ( $site_plugins as $key => $the_path ) { 
  601. if ( in_array( $the_path, $old_plugins ) ) { 
  602. $site_plugins[$key] = $new_plugin; 
  603. update_option( 'active_plugins', $site_plugins ); 
  604.  
  605. # ########################################################################## 
  606. # ########################################################################## 
  607.  
  608.  
  609. /** 
  610. * Creates a current DB Snapshot and clears all items from the update queue. 
  611. * @since 1.1.0.5 
  612. * @param string $next_version Used for snapshot file name. 
  613. */ 
  614. static private function snapshot( $next_version ) { 
  615. // Simply create a snapshot that we can restore later. 
  616. lib2()->updates->plugin( MS_TEXT_DOMAIN ); 
  617. lib2()->updates->snapshot( 
  618. 'upgrade_' . str_replace( '.', '_', $next_version ),  
  619. self::snapshot_data() 
  620. ); 
  621.  
  622. lib2()->updates->clear(); 
  623.  
  624. /** 
  625. * Takes an __PHP_Incomplete_Class and casts it to a stdClass object. 
  626. * All properties will be made public in this step. 
  627. * @since 1.1.0 
  628. * @param object $object __PHP_Incomplete_Class 
  629. * @return object 
  630. */ 
  631. static public function fix_object( $object ) { 
  632. // preg_replace_callback handler. Needed to calculate new key-length. 
  633. $fix_key = create_function( 
  634. '$matches',  
  635. 'return ":" . strlen( $matches[1] ) . ":\"" . $matches[1] . "\"";' 
  636. ); 
  637.  
  638. // Serialize the object to a string. 
  639. $dump = serialize( $object ); 
  640.  
  641. // Change class-type to 'stdClass'. 
  642. $dump = preg_replace( '/^O:\d+:"[^"]++"/', 'O:8:"stdClass"', $dump ); 
  643.  
  644. // Strip "private" and "protected" prefixes. 
  645. $dump = preg_replace_callback( '/:\d+:"\0.*?\0([^"]+)"/', $fix_key, $dump ); 
  646.  
  647. // Unserialize the modified object again. 
  648. return unserialize( $dump ); 
  649.  
  650. /** 
  651. * Returns the option-keys and post-IDs that should be backed-up. 
  652. * @since 1.1.0.2 
  653. * @internal 
  654. * @return object Snapshot data-definition. 
  655. */ 
  656. static private function snapshot_data() { 
  657. global $wpdb; 
  658. $data = (object) array(); 
  659.  
  660. // Options. 
  661. $sql = " 
  662. SELECT option_name 
  663. FROM {$wpdb->options} 
  664. WHERE 
  665. option_name LIKE 'ms_addon_%' 
  666. OR option_name LIKE 'ms_model_%' 
  667. OR option_name LIKE 'ms_gateway_%' 
  668. "; 
  669. $data->options = $wpdb->get_col( $sql ); 
  670.  
  671. // Posts and Post-Meta 
  672. $sql = " 
  673. SELECT ID 
  674. FROM {$wpdb->posts} 
  675. WHERE 
  676. post_type IN ( 
  677. 'ms_membership',  
  678. 'ms_relationship',  
  679. 'ms_event',  
  680. 'ms_invoice',  
  681. 'ms_communication' 
  682. 'ms_coupon' 
  683. "; 
  684. $data->posts = $wpdb->get_col( $sql ); 
  685.  
  686. return $data; 
  687.  
  688. /** 
  689. * Completely whipe all Membership data from Database. 
  690. * Note: This function is not used currently... 
  691. * @since 1.0.0 
  692. */ 
  693. static private function cleanup_db() { 
  694. global $wpdb; 
  695. $sql = array(); 
  696. $trash_ids = array(); 
  697.  
  698. // Delete membership meta-data from users. 
  699. $users = MS_Model_Member::get_members( ); 
  700. foreach ( $users as $user ) { 
  701. $user->delete_all_membership_usermeta(); 
  702. $user->save(); 
  703.  
  704. // Determine IDs of Membership Pages. 
  705. $page_types = MS_Model_Pages::get_page_types(); 
  706. foreach ( $page_types as $type => $name ) { 
  707. $page_id = MS_Model_Pages::get_setting( $type ); 
  708. $trash_ids[] = $page_id; 
  709.  
  710. /** 
  711. * Delete all plugin settings. 
  712. * Settings are saved by classes that extend MS_Model_option 
  713. */ 
  714. foreach ( MS_Model_Gateway::get_gateways() as $option ) { $option->delete(); } 
  715. MS_Factory::load( 'MS_Model_Addon' )->delete(); 
  716. MS_Factory::load( 'MS_Model_Pages' )->delete(); 
  717. MS_Factory::load( 'MS_Model_Settings' )->delete(); 
  718.  
  719. /** 
  720. * Delete transient data 
  721. * Transient data is saved by classed that extend MS_Model_Transient 
  722. */ 
  723. MS_Factory::load( 'MS_Model_Simulate' )->delete(); 
  724.  
  725. /** 
  726. * Delete all plugin content. 
  727. * Content is saved by classes that extend MS_Model_CustomPostType 
  728. */ 
  729. $ms_posttypes = array( 
  730. MS_Model_Communication::get_post_type(),  
  731. MS_Model_Event::get_post_type(),  
  732. MS_Model_Invoice::get_post_type(),  
  733. MS_Model_Membership::get_post_type(),  
  734. MS_Model_Relationship::get_post_type(),  
  735. ); 
  736.  
  737. foreach ( $ms_posttypes as $type ) { 
  738. $sql[] = $wpdb->prepare( 
  739. "DELETE FROM $wpdb->posts WHERE post_type = %s;",  
  740. $type 
  741. ); 
  742.  
  743. // Remove orphaned post-metadata. 
  744. $sql[] = "DELETE FROM $wpdb->postmeta WHERE NOT EXISTS (SELECT 1 FROM wp_posts tmp WHERE tmp.ID = post_id);"; 
  745.  
  746. // Clear all WP transient cache. 
  747. $sql[] = "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_%';"; 
  748.  
  749. foreach ( $sql as $s ) { 
  750. $wpdb->query( $s ); 
  751.  
  752. // Move Membership pages to trash. 
  753. foreach ( $trash_ids as $id ) { 
  754. wp_delete_post( $id, true ); 
  755.  
  756. // Clear all data from WP Object cache. 
  757. wp_cache_flush(); 
  758.  
  759. // Redirect to the main page. 
  760. wp_safe_redirect( MS_Controller_Plugin::get_admin_url() ); 
  761. exit; 
  762.  
  763. /** 
  764. * Makes sure that network-wide protection works by ensuring that the plugin 
  765. * is also network-activated. 
  766. * @since 2.0.0 
  767. */ 
  768. static private function check_network_setup() { 
  769. return; 
  770.  
  771. /** 
  772. * This function checks if we arrive here after a migration, i.e. after the 
  773. * user updated Membership Premium or Protected Content to M2 
  774. * @since 1.0.0 
  775. */ 
  776. static private function check_migration_handler() { 
  777. $migrate = ''; 
  778. $settings = MS_Factory::load( 'MS_Model_Settings' ); 
  779.  
  780. // Check Migration from old Membership plugin. 
  781. $option_m1 = '_wpmudev_update_to_m2'; 
  782. $option_m1_free = '_wporg_update_to_m2'; 
  783. $from_m1 = get_site_option( $option_m1 ); 
  784. $from_m1_free = get_site_option( $option_m1_free ); 
  785.  
  786. if ( $from_m1 || $from_m1_free ) { 
  787. $migrate = 'm1'; 
  788.  
  789. delete_site_option( $option_m1 ); 
  790. delete_site_option( $option_m1_free ); 
  791. $settings->set_special_view( 'MS_View_MigrationM1' ); 
  792.  
  793. $view = $settings->get_special_view(); 
  794.  
  795. if ( $migrate || 'MS_View_MigrationM1' == $view ) { 
  796. if ( ! empty( $_REQUEST['skip_import'] ) ) { 
  797. $settings->reset_special_view(); 
  798. wp_safe_redirect( 
  799. esc_url_raw( remove_query_arg( array( 'skip_import' ) ) ) 
  800. ); 
  801. exit; 
  802. } else { 
  803. $settings->set_special_view( 'MS_View_MigrationM1' ); 
  804.  
  805. // Complete the migration when the import is done. 
  806. add_action( 
  807. 'ms_import_action_done',  
  808. array( 'MS_Model_Settings', 'reset_special_view' ) 
  809. ); 
  810.  
  811. /** 
  812. * Returns a secure token to trigger advanced admin actions like db-reset 
  813. * or restoring a snapshot. 
  814. * - Only one token is valid at any given time. 
  815. * - Each token has a timeout of max. 120 seconds. 
  816. * - Each token can be used once only. 
  817. * @since 1.1.0 
  818. * @internal 
  819. * @param string $action Like a nonce, this is the action to execute. 
  820. * @return array Intended usage: add_query_param( $token, $url ) 
  821. */ 
  822. static public function get_token( $action ) { 
  823. if ( ! is_user_logged_in() ) { return array(); } 
  824. if ( ! is_admin() ) { return array(); } 
  825.  
  826. $one_time_key = uniqid(); 
  827. MS_Factory::set_transient( 'ms_one_time_key-' . $action, $one_time_key, 120 ); 
  828.  
  829. // Token is valid for 86 seconds because of usage of date('B') 
  830. $plain = $action . '-' . date( 'B' ) . ':' . get_current_user_id() . '-' . $one_time_key; 
  831. $token = array( 'ms_token' => wp_create_nonce( $plain ) ); 
  832. return $token; 
  833.  
  834. /** 
  835. * Verfies the admin token in the $_GET collection 
  836. * $_GET['ms_token'] must match the current ms_token 
  837. * $_POST['confirm'] must have value 'yes' 
  838. * @since 1.1.0 
  839. * @internal 
  840. * @param string $action Like a nonce, this is the action to execute. 
  841. * @return bool 
  842. */ 
  843. static private function verify_token( $action ) { 
  844. if ( ! self::valid_user() ) { return false; } 
  845.  
  846. if ( empty( $_GET['ms_token'] ) ) { return false; } 
  847. $get_token = $_GET['ms_token']; 
  848.  
  849. if ( empty( $_POST['confirm'] ) ) { return false; } 
  850. if ( 'yes' != $_POST['confirm'] ) { return false; } 
  851.  
  852. $one_time_key = MS_Factory::get_transient( 'ms_one_time_key-' . $action ); 
  853. MS_Factory::delete_transient( 'ms_one_time_key-' . $action ); 
  854. if ( empty( $one_time_key ) ) { return false; } 
  855.  
  856. // We verify the current and the previous beat 
  857. $plain_token_1 = $action . '-' . date( 'B' ) . ':' . get_current_user_id() . '-' . $one_time_key; 
  858. $plain_token_2 = $action . '-' . ( date( 'B' ) - 1 ) . ':' . get_current_user_id() . '-' . $one_time_key; 
  859.  
  860. if ( wp_verify_nonce( $get_token, $plain_token_1 ) ) { return true; } 
  861. if ( wp_verify_nonce( $get_token, $plain_token_2 ) ) { return true; } 
  862.  
  863. return false; 
  864.  
  865. /** 
  866. * Verifies the following conditions: 
  867. * - Current user is logged in and has admin permissions 
  868. * - The request is an wp-admin request 
  869. * - The request is not an Ajax call 
  870. * @since 1.1.0.4 
  871. * @return bool True if all conditions are true 
  872. */ 
  873. static private function valid_user() { 
  874. if ( ! is_user_logged_in() ) { return false; } 
  875. if ( ! is_admin() ) { return false; } 
  876. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return false; } 
  877. if ( defined( 'DOING_CRON' ) && DOING_CRON ) { return false; } 
  878. if ( ! MS_Model_Member::is_admin_user() ) { return false; } 
  879.  
  880. return true; 
  881.  
  882. /** 
  883. * Checks if valid reset-instructions are present. If yes, then whipe the 
  884. * plugin settings. 
  885. * @since 1.1.0 
  886. */ 
  887. static public function maybe_reset() { 
  888. static $Reset_Done = false; 
  889.  
  890. if ( ! $Reset_Done ) { 
  891. $Reset_Done = true; 
  892. if ( ! self::verify_token( 'reset' ) ) { return false; } 
  893.  
  894. self::cleanup_db(); 
  895. $msg = __( 'Your Membership2 data was reset!', MS_TEXT_DOMAIN ); 
  896. lib2()->ui->admin_message( $msg ); 
  897.  
  898. wp_safe_redirect( MS_Controller_Plugin::get_admin_url( 'MENU_SLUG' ) ); 
  899. exit; 
  900.  
  901. /** 
  902. * Checks if valid restore-options are specified. If they are, the snapshot 
  903. * will be restored. 
  904. * @since 1.1.0.4 
  905. */ 
  906. static private function maybe_restore() { 
  907. static $Restore_Done = false; 
  908.  
  909. if ( ! $Restore_Done ) { 
  910. $Restore_Done = true; 
  911. if ( empty( $_POST['restore_snapshot'] ) ) { return false; } 
  912. $snapshot = $_POST['restore_snapshot']; 
  913.  
  914. if ( ! self::verify_token( 'restore' ) ) { return false; } 
  915.  
  916. lib2()->updates->plugin( MS_TEXT_DOMAIN ); 
  917. if ( lib2()->updates->restore( $snapshot ) ) { 
  918. printf( 
  919. '<p>' . 
  920. __( 'The Membership2 Snapshot "%s" was restored!', MS_TEXT_DOMAIN ) . 
  921. '</p>',  
  922. $snapshot 
  923. ); 
  924.  
  925. printf( 
  926. '<p><b>' . 
  927. __( 'To prevent auto-updating the DB again we stop here!', MS_TEXT_DOMAIN ) . 
  928. '</b></p>' 
  929. ); 
  930.  
  931. printf( 
  932. '<p>' . 
  933. __( 'You now have the option to <br />(A) downgrade the plugin to an earlier version via FTP or <br />(B) to %sre-run the upgrade process%s.', MS_TEXT_DOMAIN ) . 
  934. '</p>',  
  935. '<a href="' . MS_Controller_Plugin::get_admin_url( 'MENU_SLUG' ) . '">',  
  936. '</a>' 
  937. ); 
  938.  
  939. wp_die( '', 'Snapshot Restored' ); 
  940.  
  941. };